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