Improve help text and error-reporting for ardour-export
[ardour.git] / session_utils / export.cc
1 #include <iostream>
2 #include <cstdlib>
3 #include <getopt.h>
4 #include <glibmm.h>
5
6 #include "common.h"
7
8 #include "pbd/basename.h"
9
10 #include "ardour/export_handler.h"
11 #include "ardour/export_status.h"
12 #include "ardour/export_timespan.h"
13 #include "ardour/export_channel_configuration.h"
14 #include "ardour/export_format_specification.h"
15 #include "ardour/export_filename.h"
16 #include "ardour/route.h"
17 #include "ardour/session_metadata.h"
18 #include "ardour/broadcast_info.h"
19
20 using namespace std;
21 using namespace ARDOUR;
22 using namespace SessionUtils;
23
24 static int export_session (Session *session,
25                 std::string outfile,
26                 std::string samplerate,
27                 bool normalize)
28 {
29         ExportTimespanPtr tsp = session->get_export_handler()->add_timespan();
30         boost::shared_ptr<ExportChannelConfiguration> ccp = session->get_export_handler()->add_channel_config();
31         boost::shared_ptr<ARDOUR::ExportFilename> fnp = session->get_export_handler()->add_filename();
32         boost::shared_ptr<AudioGrapher::BroadcastInfo> b;
33
34         XMLTree tree;
35
36         tree.read_buffer(std::string(
37 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
38 "<ExportFormatSpecification name=\"UTIL-WAV-16\" id=\"14792644-44ab-4209-a4f9-7ce6c2910cac\">"
39 "  <Encoding id=\"F_WAV\" type=\"T_Sndfile\" extension=\"wav\" name=\"WAV\" has-sample-format=\"true\" channel-limit=\"256\"/>"
40 "  <SampleRate rate=\""+ samplerate +"\"/>"
41 "  <SRCQuality quality=\"SRC_SincBest\"/>"
42 "  <EncodingOptions>"
43 "    <Option name=\"sample-format\" value=\"SF_16\"/>"
44 "    <Option name=\"dithering\" value=\"D_None\"/>"
45 "    <Option name=\"tag-metadata\" value=\"true\"/>"
46 "    <Option name=\"tag-support\" value=\"false\"/>"
47 "    <Option name=\"broadcast-info\" value=\"false\"/>"
48 "  </EncodingOptions>"
49 "  <Processing>"
50 "    <Normalize enabled=\""+ (normalize ? "true" : "false") +"\" target=\"0\"/>"
51 "    <Silence>"
52 "      <Start>"
53 "        <Trim enabled=\"false\"/>"
54 "        <Add enabled=\"false\">"
55 "          <Duration format=\"Timecode\" hours=\"0\" minutes=\"0\" seconds=\"0\" frames=\"0\"/>"
56 "        </Add>"
57 "      </Start>"
58 "      <End>"
59 "        <Trim enabled=\"false\"/>"
60 "        <Add enabled=\"false\">"
61 "          <Duration format=\"Timecode\" hours=\"0\" minutes=\"0\" seconds=\"0\" frames=\"0\"/>"
62 "        </Add>"
63 "      </End>"
64 "    </Silence>"
65 "  </Processing>"
66 "</ExportFormatSpecification>"
67 ));
68
69         boost::shared_ptr<ExportFormatSpecification> fmp = session->get_export_handler()->add_format(*tree.root());
70
71         /* set up range */
72         framepos_t start, end;
73         start = session->current_start_frame();
74         end   = session->current_end_frame();
75         tsp->set_range (start, end);
76         tsp->set_range_id ("session");
77
78         /* add master outs as default */
79         IO* master_out = session->master_out()->output().get();
80         if (!master_out) {
81                 PBD::warning << _("Export Util: No Master Out Ports to Connect for Audio Export") << endmsg;
82                 return -1;
83         }
84
85         for (uint32_t n = 0; n < master_out->n_ports().n_audio(); ++n) {
86                 PortExportChannel * channel = new PortExportChannel ();
87                 channel->add_port (master_out->audio (n));
88                 ExportChannelPtr chan_ptr (channel);
89                 ccp->register_channel (chan_ptr);
90         }
91
92         /* output filename */
93         if (outfile.empty ()) {
94                 tsp->set_name ("session");
95         } else {
96                 std::string dirname = Glib::path_get_dirname (outfile);
97                 std::string basename = Glib::path_get_basename (outfile);
98
99                 if (basename.size() > 4 && !basename.compare (basename.size() - 4, 4, ".wav")) {
100                         basename = PBD::basename_nosuffix (basename);
101                 }
102
103                 fnp->set_folder(dirname);
104                 tsp->set_name (basename);
105         }
106
107         cout << "* Writing " << Glib::build_filename (fnp->get_folder(), tsp->name() + ".wav") << endl;
108
109
110         /* output */
111         fnp->set_timespan(tsp);
112         fnp->include_label = false;
113
114         /* do audio export */
115         fmp->set_soundcloud_upload(false);
116         session->get_export_handler()->add_export_config (tsp, ccp, fmp, fnp, b);
117         session->get_export_handler()->do_export();
118
119         boost::shared_ptr<ARDOUR::ExportStatus> status = session->get_export_status ();
120
121         // TODO trap SIGINT -> status->abort();
122
123         while (status->running ()) {
124                 double progress = 0.0;
125                 switch (status->active_job) {
126                 case ExportStatus::Normalizing:
127                         progress = ((float) status->current_postprocessing_cycle) / status->total_postprocessing_cycles;
128                         printf ("* Normalizing %.1f%%      \r", 100. * progress); fflush (stdout);
129                         break;
130                 case ExportStatus::Exporting:
131                         progress = ((float) status->processed_frames_current_timespan) / status->total_frames_current_timespan;
132                         printf ("* Exporting Audio %.1f%%  \r", 100. * progress); fflush (stdout);
133                         break;
134                 default:
135                         printf ("* Exporting...            \r");
136                         break;
137                 }
138                 Glib::usleep (1000000);
139         }
140         printf("\n");
141
142         status->finish ();
143
144         printf ("* Done.\n");
145         return 0;
146 }
147
148 static void usage (int status) {
149         // help2man compatible format (standard GNU help-text)
150         printf (UTILNAME " - export an ardour session from the commandline.\n\n");
151         printf ("Usage: " UTILNAME " [ OPTIONS ] <session-dir> <session/snapshot-name>\n\n");
152         printf ("Options:\n\
153   -h, --help                 display this help and exit\n\
154   -n, --normalize            normalize signal level (to 0dBFS)\n\
155   -o, --output  <file>       export output file name\n\
156   -s, --samplerate <rate>    samplerate to use (default: 48000)\n\
157   -V, --version              print version information and exit\n\
158 \n");
159         printf ("\n\
160 This tool exports the session-range of a given ardour-session to a 16bit wav,\n\
161 using the master-bus outputs.\n\
162 If the no output-file is given, the session's export dir is used.\n\
163 \n\
164 Note: the tool expects a session-name without .ardour file-name extension.\n\
165 \n");
166
167         printf ("Report bugs to <http://tracker.ardour.org/>\n"
168                 "Website: <http://ardour.org/>\n");
169         ::exit (status);
170 }
171
172 int main (int argc, char* argv[])
173 {
174         std::string rate = "48000";
175         std::string outfile;
176         bool normalize = false;
177
178         const char *optstring = "hno:s:V";
179
180         const struct option longopts[] = {
181                 { "help",       0, 0, 'h' },
182                 { "normalize",  0, 0, 'n' },
183                 { "output",     1, 0, 'o' },
184                 { "samplerate", 1, 0, 's' },
185                 { "version",    0, 0, 'V' },
186         };
187
188         int c = 0;
189         while (EOF != (c = getopt_long (argc, argv,
190                                         optstring, longopts, (int *) 0))) {
191                 switch (c) {
192
193                         case 'n':
194                                 normalize = true;
195                                 break;
196
197                         case 'o':
198                                 outfile = optarg;
199                                 break;
200
201                         case 's':
202                                 {
203                                         const int sr = atoi (optarg);
204                                         if (sr >= 8000 && sr <= 192000) {
205                                                 stringstream ss;
206                                                 ss << sr;
207                                                 rate = ss.str();
208                                         } else {
209                                                 fprintf(stderr, "Invalid Samplerate\n");
210                                         }
211                                 }
212                                 break;
213
214                         case 'V':
215                                 printf ("ardour-utils version %s\n\n", VERSIONSTRING);
216                                 printf ("Copyright (C) GPL 2015 Robin Gareus <robin@gareus.org>\n");
217                                 exit (0);
218                                 break;
219
220                         case 'h':
221                                 usage (0);
222                                 break;
223
224                         default:
225                                         usage (EXIT_FAILURE);
226                                         break;
227                 }
228         }
229
230         if (optind + 2 > argc) {
231                 usage (EXIT_FAILURE);
232         }
233
234         SessionUtils::init(false);
235         Session* s = 0;
236
237         s = SessionUtils::load_session (argv[optind], argv[optind+1]);
238
239         export_session (s, outfile, rate, normalize);
240
241         SessionUtils::unload_session(s);
242         SessionUtils::cleanup();
243
244         return 0;
245 }