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