Don't print usage to stdout for invalid parameters
[ardour.git] / session_utils / export.cc
1 /*
2  * Copyright (C) 2015-2019 Robin Gareus <robin@gareus.org>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  */
18
19 #include <iostream>
20 #include <cstdlib>
21 #include <getopt.h>
22 #include <glibmm.h>
23
24 #include "common.h"
25
26 #include "pbd/basename.h"
27 #include "pbd/enumwriter.h"
28
29 #include "ardour/broadcast_info.h"
30 #include "ardour/export_handler.h"
31 #include "ardour/export_status.h"
32 #include "ardour/export_timespan.h"
33 #include "ardour/export_channel_configuration.h"
34 #include "ardour/export_format_specification.h"
35 #include "ardour/export_filename.h"
36 #include "ardour/route.h"
37 #include "ardour/session_metadata.h"
38 #include "ardour/broadcast_info.h"
39
40 using namespace std;
41 using namespace ARDOUR;
42 using namespace SessionUtils;
43
44 struct ExportSettings
45 {
46         ExportSettings ()
47                 : _samplerate (0)
48                 , _sample_format (ExportFormatBase::SF_16)
49                 , _normalize (false)
50                 , _bwf (false)
51         {}
52
53         std::string samplerate () const
54         {
55                 stringstream ss;
56                 ss << _samplerate;
57                 return ss.str();
58         }
59
60         std::string sample_format () const
61         {
62                 return enum_2_string (_sample_format);
63         }
64
65         std::string normalize () const
66         {
67                 return _normalize ? "true" : "false";
68         }
69
70         std::string bwf () const
71         {
72                 return _bwf ? "true" : "false";
73         }
74
75         int _samplerate;
76         ExportFormatBase::SampleFormat _sample_format;
77         bool _normalize;
78         bool _bwf;
79 };
80
81 static int export_session (Session *session,
82                 std::string outfile,
83                 ExportSettings const& settings)
84 {
85         ExportTimespanPtr tsp = session->get_export_handler()->add_timespan();
86         boost::shared_ptr<ExportChannelConfiguration> ccp = session->get_export_handler()->add_channel_config();
87         boost::shared_ptr<ARDOUR::ExportFilename> fnp = session->get_export_handler()->add_filename();
88         boost::shared_ptr<ARDOUR::BroadcastInfo> b;
89
90         XMLTree tree;
91
92         tree.read_buffer(std::string(
93 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
94 "<ExportFormatSpecification name=\"UTIL-WAV-EXPORT\" id=\"b1280899-0459-4aef-9dc9-7e2277fa6d24\">"
95 "  <Encoding id=\"F_WAV\" type=\"T_Sndfile\" extension=\"wav\" name=\"WAV\" has-sample-format=\"true\" channel-limit=\"256\"/>"
96 "  <SampleRate rate=\""+ settings.samplerate () +"\"/>"
97 "  <SRCQuality quality=\"SRC_SincBest\"/>"
98 "  <EncodingOptions>"
99 "    <Option name=\"sample-format\" value=\"" + settings.sample_format () + "\"/>"
100 "    <Option name=\"dithering\" value=\"D_None\"/>"
101 "    <Option name=\"tag-metadata\" value=\"true\"/>"
102 "    <Option name=\"tag-support\" value=\"false\"/>"
103 "    <Option name=\"broadcast-info\" value=\"" + settings.bwf () +"\"/>"
104 "  </EncodingOptions>"
105 "  <Processing>"
106 "    <Normalize enabled=\""+ settings.normalize () +"\" target=\"0\"/>"
107 "    <Silence>"
108 "      <Start>"
109 "        <Trim enabled=\"false\"/>"
110 "        <Add enabled=\"false\">"
111 "          <Duration format=\"Timecode\" hours=\"0\" minutes=\"0\" seconds=\"0\" frames=\"0\"/>"
112 "        </Add>"
113 "      </Start>"
114 "      <End>"
115 "        <Trim enabled=\"false\"/>"
116 "        <Add enabled=\"false\">"
117 "          <Duration format=\"Timecode\" hours=\"0\" minutes=\"0\" seconds=\"0\" frames=\"0\"/>"
118 "        </Add>"
119 "      </End>"
120 "    </Silence>"
121 "  </Processing>"
122 "</ExportFormatSpecification>"
123 ));
124
125         boost::shared_ptr<ExportFormatSpecification> fmp = session->get_export_handler()->add_format(*tree.root());
126
127         /* set up range */
128         samplepos_t start, end;
129         start = session->current_start_sample();
130         end   = session->current_end_sample();
131         tsp->set_range (start, end);
132         tsp->set_range_id ("session");
133
134         /* add master outs as default */
135         IO* master_out = session->master_out()->output().get();
136         if (!master_out) {
137                 PBD::warning << _("Export Util: No Master Out Ports to Connect for Audio Export") << endmsg;
138                 return -1;
139         }
140
141         for (uint32_t n = 0; n < master_out->n_ports().n_audio(); ++n) {
142                 PortExportChannel * channel = new PortExportChannel ();
143                 channel->add_port (master_out->audio (n));
144                 ExportChannelPtr chan_ptr (channel);
145                 ccp->register_channel (chan_ptr);
146         }
147
148         /* output filename */
149         if (outfile.empty ()) {
150                 tsp->set_name ("session");
151         } else {
152                 std::string dirname = Glib::path_get_dirname (outfile);
153                 std::string basename = Glib::path_get_basename (outfile);
154
155                 if (basename.size() > 4 && !basename.compare (basename.size() - 4, 4, ".wav")) {
156                         basename = PBD::basename_nosuffix (basename);
157                 }
158
159                 fnp->set_folder(dirname);
160                 tsp->set_name (basename);
161         }
162
163         /* set broadcast info */
164         if (settings._bwf) {
165                 b.reset (new BroadcastInfo);
166                 b->set_from_session (*session, tsp->get_start ());
167         }
168
169         cout << "* Writing " << Glib::build_filename (fnp->get_folder(), tsp->name() + ".wav") << endl;
170
171
172         /* output */
173         fnp->set_timespan(tsp);
174         fnp->include_label = false;
175
176         /* do audio export */
177         fmp->set_soundcloud_upload(false);
178         session->get_export_handler()->add_export_config (tsp, ccp, fmp, fnp, b);
179         session->get_export_handler()->do_export();
180
181         boost::shared_ptr<ARDOUR::ExportStatus> status = session->get_export_status ();
182
183         // TODO trap SIGINT -> status->abort();
184
185         while (status->running ()) {
186                 double progress = 0.0;
187                 switch (status->active_job) {
188                 case ExportStatus::Normalizing:
189                         progress = ((float) status->current_postprocessing_cycle) / status->total_postprocessing_cycles;
190                         printf ("* Normalizing %.1f%%      \r", 100. * progress); fflush (stdout);
191                         break;
192                 case ExportStatus::Exporting:
193                         progress = ((float) status->processed_samples_current_timespan) / status->total_samples_current_timespan;
194                         printf ("* Exporting Audio %.1f%%  \r", 100. * progress); fflush (stdout);
195                         break;
196                 default:
197                         printf ("* Exporting...            \r");
198                         break;
199                 }
200                 Glib::usleep (1000000);
201         }
202         printf("\n");
203
204         status->finish ();
205
206         printf ("* Done.\n");
207         return 0;
208 }
209
210 static void usage () {
211         // help2man compatible format (standard GNU help-text)
212         printf (UTILNAME " - export an ardour session from the commandline.\n\n");
213         printf ("Usage: " UTILNAME " [ OPTIONS ] <session-dir> <session/snapshot-name>\n\n");
214         printf ("Options:\n\
215   -b, --bitdepth <depth>     set export-format (16, 24, 32, float)\n\
216   -B, --broadcast            include broadcast wave header\n\
217   -h, --help                 display this help and exit\n\
218   -n, --normalize            normalize signal level (to 0dBFS)\n\
219   -o, --output  <file>       export output file name\n\
220   -s, --samplerate <rate>    samplerate to use\n\
221   -V, --version              print version information and exit\n\
222 \n");
223         printf ("\n\
224 This tool exports the session-range of a given ardour-session to a wave file,\n\
225 using the master-bus outputs.\n\
226 By default a 16bit signed .wav file at session-rate is exported.\n\
227 If the no output-file is given, the session's export dir is used.\n\
228 \n\
229 Note: the tool expects a session-name without .ardour file-name extension.\n\
230 \n");
231
232         printf ("Report bugs to <http://tracker.ardour.org/>\n"
233                 "Website: <http://ardour.org/>\n");
234         ::exit (EXIT_SUCCESS);
235 }
236
237 int main (int argc, char* argv[])
238 {
239         ExportSettings settings;
240         std::string outfile;
241
242         const char *optstring = "b:Bhno:s:V";
243
244         const struct option longopts[] = {
245                 { "bitdepth",   1, 0, 'b' },
246                 { "broadcast",  0, 0, 'B' },
247                 { "help",       0, 0, 'h' },
248                 { "normalize",  0, 0, 'n' },
249                 { "output",     1, 0, 'o' },
250                 { "samplerate", 1, 0, 's' },
251                 { "version",    0, 0, 'V' },
252         };
253
254         int c = 0;
255         while (EOF != (c = getopt_long (argc, argv,
256                                         optstring, longopts, (int *) 0))) {
257                 switch (c) {
258
259                         case 'b':
260                                 switch (atoi (optarg)) {
261                                         case 16:
262                                                 settings._sample_format = ExportFormatBase::SF_16;
263                                                 break;
264                                         case 24:
265                                                 settings._sample_format = ExportFormatBase::SF_24;
266                                                 break;
267                                         case 32:
268                                                 settings._sample_format = ExportFormatBase::SF_32;
269                                                 break;
270                                         case 0:
271                                                 if (0 == strcmp (optarg, "float")) {
272                                                         settings._sample_format = ExportFormatBase::SF_Float;
273                                                         break;
274                                                 }
275                                                 /* fall through */
276                                         default:
277                                                 fprintf(stderr, "Invalid Bit Depth\n");
278                                                 break;
279                                 }
280                                 break;
281
282                         case 'B':
283                                 settings._bwf = true;
284                                 break;
285
286                         case 'n':
287                                 settings._normalize = true;
288                                 break;
289
290                         case 'o':
291                                 outfile = optarg;
292                                 break;
293
294                         case 's':
295                                 {
296                                         const int sr = atoi (optarg);
297                                         if (sr >= 8000 && sr <= 192000) {
298                                                 settings._samplerate = sr;
299                                         } else {
300                                                 fprintf(stderr, "Invalid Samplerate\n");
301                                         }
302                                 }
303                                 break;
304
305                         case 'V':
306                                 printf ("ardour-utils version %s\n\n", VERSIONSTRING);
307                                 printf ("Copyright (C) GPL 2015,2017 Robin Gareus <robin@gareus.org>\n");
308                                 exit (EXIT_SUCCESS);
309                                 break;
310
311                         case 'h':
312                                 usage ();
313                                 break;
314
315                         default:
316                                 cerr << "Error: unrecognized option. See --help for usage information.\n";
317                                 ::exit (EXIT_FAILURE);
318                                 break;
319                 }
320         }
321
322         if (optind + 2 > argc) {
323                 cerr << "Error: Missing parameter. See --help for usage information.\n";
324                 ::exit (EXIT_FAILURE);
325         }
326
327         SessionUtils::init(false);
328         Session* s = 0;
329
330         s = SessionUtils::load_session (argv[optind], argv[optind+1]);
331
332         if (settings._samplerate == 0) {
333                 settings._samplerate = s->nominal_sample_rate ();
334         }
335
336         export_session (s, outfile, settings);
337
338         SessionUtils::unload_session(s);
339         SessionUtils::cleanup();
340
341         return 0;
342 }