Fix 64bit OSX/MacOS builds
[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 #include "pbd/i18n.h"
41
42 using namespace std;
43 using namespace ARDOUR;
44 using namespace SessionUtils;
45
46 struct ExportSettings
47 {
48         ExportSettings ()
49                 : _samplerate (0)
50                 , _sample_format (ExportFormatBase::SF_16)
51                 , _normalize (false)
52                 , _bwf (false)
53         {}
54
55         std::string samplerate () const
56         {
57                 stringstream ss;
58                 ss << _samplerate;
59                 return ss.str();
60         }
61
62         std::string sample_format () const
63         {
64                 return enum_2_string (_sample_format);
65         }
66
67         std::string normalize () const
68         {
69                 return _normalize ? "true" : "false";
70         }
71
72         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
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 ));
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         session->get_export_handler()->do_export();
182
183         boost::shared_ptr<ARDOUR::ExportStatus> status = session->get_export_status ();
184
185         // TODO trap SIGINT -> status->abort();
186
187         while (status->running ()) {
188                 double progress = 0.0;
189                 switch (status->active_job) {
190                 case ExportStatus::Normalizing:
191                         progress = ((float) status->current_postprocessing_cycle) / status->total_postprocessing_cycles;
192                         printf ("* Normalizing %.1f%%      \r", 100. * progress); fflush (stdout);
193                         break;
194                 case ExportStatus::Exporting:
195                         progress = ((float) status->processed_samples_current_timespan) / status->total_samples_current_timespan;
196                         printf ("* Exporting Audio %.1f%%  \r", 100. * progress); fflush (stdout);
197                         break;
198                 default:
199                         printf ("* Exporting...            \r");
200                         break;
201                 }
202                 Glib::usleep (1000000);
203         }
204         printf("\n");
205
206         status->finish (TRS_UI);
207
208         printf ("* Done.\n");
209         return 0;
210 }
211
212 static void usage () {
213         // help2man compatible format (standard GNU help-text)
214         printf (UTILNAME " - export an ardour session from the commandline.\n\n");
215         printf ("Usage: " UTILNAME " [ OPTIONS ] <session-dir> <session/snapshot-name>\n\n");
216         printf ("Options:\n\
217   -b, --bitdepth <depth>     set export-format (16, 24, 32, float)\n\
218   -B, --broadcast            include broadcast wave header\n\
219   -h, --help                 display this help and exit\n\
220   -n, --normalize            normalize signal level (to 0dBFS)\n\
221   -o, --output  <file>       export output file name\n\
222   -s, --samplerate <rate>    samplerate to use\n\
223   -V, --version              print version information and exit\n\
224 \n");
225         printf ("\n\
226 This tool exports the session-range of a given ardour-session to a wave file,\n\
227 using the master-bus outputs.\n\
228 By default a 16bit signed .wav file at session-rate is exported.\n\
229 If the no output-file is given, the session's export dir is used.\n\
230 \n\
231 Note: the tool expects a session-name without .ardour file-name extension.\n\
232 \n");
233
234         printf ("Report bugs to <http://tracker.ardour.org/>\n"
235                 "Website: <http://ardour.org/>\n");
236         ::exit (EXIT_SUCCESS);
237 }
238
239 int main (int argc, char* argv[])
240 {
241         ExportSettings settings;
242         std::string outfile;
243
244         const char *optstring = "b:Bhno:s:V";
245
246         const struct option longopts[] = {
247                 { "bitdepth",   1, 0, 'b' },
248                 { "broadcast",  0, 0, 'B' },
249                 { "help",       0, 0, 'h' },
250                 { "normalize",  0, 0, 'n' },
251                 { "output",     1, 0, 'o' },
252                 { "samplerate", 1, 0, 's' },
253                 { "version",    0, 0, 'V' },
254         };
255
256         int c = 0;
257         while (EOF != (c = getopt_long (argc, argv,
258                                         optstring, longopts, (int *) 0))) {
259                 switch (c) {
260
261                         case 'b':
262                                 switch (atoi (optarg)) {
263                                         case 16:
264                                                 settings._sample_format = ExportFormatBase::SF_16;
265                                                 break;
266                                         case 24:
267                                                 settings._sample_format = ExportFormatBase::SF_24;
268                                                 break;
269                                         case 32:
270                                                 settings._sample_format = ExportFormatBase::SF_32;
271                                                 break;
272                                         case 0:
273                                                 if (0 == strcmp (optarg, "float")) {
274                                                         settings._sample_format = ExportFormatBase::SF_Float;
275                                                         break;
276                                                 }
277                                                 /* fallthrough */
278                                         default:
279                                                 fprintf(stderr, "Invalid Bit Depth\n");
280                                                 break;
281                                 }
282                                 break;
283
284                         case 'B':
285                                 settings._bwf = true;
286                                 break;
287
288                         case 'n':
289                                 settings._normalize = true;
290                                 break;
291
292                         case 'o':
293                                 outfile = optarg;
294                                 break;
295
296                         case 's':
297                                 {
298                                         const int sr = atoi (optarg);
299                                         if (sr >= 8000 && sr <= 192000) {
300                                                 settings._samplerate = sr;
301                                         } else {
302                                                 fprintf(stderr, "Invalid Samplerate\n");
303                                         }
304                                 }
305                                 break;
306
307                         case 'V':
308                                 printf ("ardour-utils version %s\n\n", VERSIONSTRING);
309                                 printf ("Copyright (C) GPL 2015,2017 Robin Gareus <robin@gareus.org>\n");
310                                 exit (EXIT_SUCCESS);
311                                 break;
312
313                         case 'h':
314                                 usage ();
315                                 break;
316
317                         default:
318                                 cerr << "Error: unrecognized option. See --help for usage information.\n";
319                                 ::exit (EXIT_FAILURE);
320                                 break;
321                 }
322         }
323
324         if (optind + 2 > argc) {
325                 cerr << "Error: Missing parameter. See --help for usage information.\n";
326                 ::exit (EXIT_FAILURE);
327         }
328
329         SessionUtils::init(false);
330         Session* s = 0;
331
332         s = SessionUtils::load_session (argv[optind], argv[optind+1]);
333
334         if (settings._samplerate == 0) {
335                 settings._samplerate = s->nominal_sample_rate ();
336         }
337
338         export_session (s, outfile, settings);
339
340         SessionUtils::unload_session(s);
341         SessionUtils::cleanup();
342
343         return 0;
344 }