c26ba7e1b3be49f5d784757b2980784b9b9617b6
[ardour.git] / libs / ardour / export_handler.cc
1 /*
2     Copyright (C) 2008 Paul Davis
3     Author: Sakari Bergen
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
19 */
20
21 #include "ardour/export_handler.h"
22
23 #include "pbd/convert.h"
24 #include "pbd/filesystem.h"
25
26 #include "ardour/ardour.h"
27 #include "ardour/configuration.h"
28 #include "ardour/export_timespan.h"
29 #include "ardour/export_channel_configuration.h"
30 #include "ardour/export_status.h"
31 #include "ardour/export_format_specification.h"
32 #include "ardour/export_filename.h"
33 #include "ardour/export_processor.h"
34 #include "ardour/export_failed.h"
35
36 using namespace PBD;
37 using std::ofstream;
38
39 namespace ARDOUR
40 {
41
42 /*** ExportElementFactory ***/
43
44 ExportElementFactory::ExportElementFactory (Session & session) :
45   session (session)
46 {
47
48 }
49
50 ExportElementFactory::~ExportElementFactory ()
51 {
52
53 }
54
55 ExportElementFactory::TimespanPtr
56 ExportElementFactory::add_timespan ()
57 {
58         return TimespanPtr (new ExportTimespan (session.get_export_status(), session.frame_rate()));
59 }
60
61 ExportElementFactory::ChannelConfigPtr
62 ExportElementFactory::add_channel_config ()
63 {
64         return ChannelConfigPtr (new ExportChannelConfiguration (session));
65 }
66
67 ExportElementFactory::FormatPtr
68 ExportElementFactory::add_format ()
69 {
70         return FormatPtr (new ExportFormatSpecification (session));
71 }
72
73 ExportElementFactory::FormatPtr
74 ExportElementFactory::add_format (XMLNode const & state)
75 {
76         return FormatPtr (new ExportFormatSpecification (session, state));
77 }
78
79 ExportElementFactory::FormatPtr
80 ExportElementFactory::add_format_copy (FormatPtr other)
81 {
82         return FormatPtr (new ExportFormatSpecification (*other));
83 }
84
85 ExportElementFactory::FilenamePtr
86 ExportElementFactory::add_filename ()
87 {
88         return FilenamePtr (new ExportFilename (session));
89 }
90
91 ExportElementFactory::FilenamePtr
92 ExportElementFactory::add_filename_copy (FilenamePtr other)
93 {
94         return FilenamePtr (new ExportFilename (*other));
95 }
96
97 /*** ExportHandler ***/
98
99 ExportHandler::ExportHandler (Session & session) :
100   ExportElementFactory (session),
101   session (session),
102   export_status (session.get_export_status ()),
103   realtime (false)
104 {
105         processor.reset (new ExportProcessor (session));
106
107         files_written_connection = ExportProcessor::WritingFile.connect (sigc::mem_fun (files_written, &std::list<Glib::ustring>::push_back));
108 }
109
110 ExportHandler::~ExportHandler ()
111 {
112         if (export_status->aborted()) {
113                 for (std::list<Glib::ustring>::iterator it = files_written.begin(); it != files_written.end(); ++it) {
114                         sys::remove (sys::path (*it));
115                 }
116         }
117         
118         channel_config_connection.disconnect();
119         files_written_connection.disconnect();
120 }
121
122 bool
123 ExportHandler::add_export_config (TimespanPtr timespan, ChannelConfigPtr channel_config, FormatPtr format, FilenamePtr filename)
124 {
125         FileSpec spec (channel_config, format, filename);
126         ConfigPair pair (timespan, spec);
127         config_map.insert (pair);
128         
129         return true;
130 }
131
132 /// Starts exporting the registered configurations
133 /** The following happens, when do_export is called:
134  * 1. Session is prepared in do_export
135  * 2. start_timespan is called, which then registers all necessary channel configs to a timespan
136  * 3. The timespan reads each unique channel into a tempfile and calls Session::stop_export when the end is reached
137  * 4. stop_export emits ExportReadFinished after stopping the transport, this ends up calling finish_timespan
138  * 5. finish_timespan registers all the relevant formats and filenames to relevant channel configurations
139  * 6. finish_timespan does a manual call to timespan_thread_finished, which gets the next channel configuration
140  *    for the current timespan, calling write_files for it
141  * 7. write_files writes the actual export files, composing them from the individual channels from tempfiles and
142  *    emits FilesWritten when it is done, which ends up calling timespan_thread_finished
143  * 8. Once all channel configs are written, a new timespan is started by calling start_timespan
144  * 9. When all timespans are written the session is taken out of export.
145  */
146
147 void
148 ExportHandler::do_export (bool rt)
149 {
150         /* Count timespans */
151
152         export_status->init();
153         std::set<TimespanPtr> timespan_set;
154         for (ConfigMap::iterator it = config_map.begin(); it != config_map.end(); ++it) {
155                 timespan_set.insert (it->first);
156         }
157         export_status->total_timespans = timespan_set.size();
158
159         /* Start export */
160
161         realtime = rt;
162
163         session.ExportReadFinished.connect (sigc::mem_fun (*this, &ExportHandler::finish_timespan));
164         start_timespan ();
165 }
166
167 struct LocationSortByStart {
168     bool operator() (Location *a, Location *b) {
169             return a->start() < b->start();
170     }
171 };
172
173 void
174 ExportHandler::export_cd_marker_file (TimespanPtr timespan, FormatPtr file_format, std::string filename, CDMarkerFormat format)
175 {
176         string filepath;
177         void (ExportHandler::*header_func) (CDMarkerStatus &);
178         void (ExportHandler::*track_func) (CDMarkerStatus &);
179         void (ExportHandler::*index_func) (CDMarkerStatus &);
180
181         switch (format) {
182           case CDMarkerTOC:
183                 filepath = filename + ".toc";
184                 header_func = &ExportHandler::write_toc_header;
185                 track_func = &ExportHandler::write_track_info_toc;
186                 index_func = &ExportHandler::write_index_info_toc;
187                 break;
188           case CDMarkerCUE:
189                 filepath = filename + ".cue";
190                 header_func = &ExportHandler::write_cue_header;
191                 track_func = &ExportHandler::write_track_info_cue;
192                 index_func = &ExportHandler::write_index_info_cue;
193                 break;
194           default:
195                 return;
196         }
197         
198         CDMarkerStatus status (filepath, timespan, file_format, filename);
199
200         if (!status.out) {
201                 error << string_compose(_("Editor: cannot open \"%1\" as export file for CD marker file"), filepath) << endmsg;
202                 return;
203         }
204         
205         (this->*header_func) (status);
206         
207         /* Get locations and sort */
208         
209         Locations::LocationList const & locations (session.locations()->list());
210         Locations::LocationList::const_iterator i;
211         Locations::LocationList temp;
212
213         for (i = locations.begin(); i != locations.end(); ++i) {
214                 if ((*i)->start() >= timespan->get_start() && (*i)->end() <= timespan->get_end() && (*i)->is_cd_marker() && !(*i)->is_end()) {
215                         temp.push_back (*i);
216                 }
217         }
218
219         if (temp.empty()) {
220                 // TODO One index marker for whole thing
221                 return;
222         }
223         
224         LocationSortByStart cmp;
225         temp.sort (cmp);
226         Locations::LocationList::const_iterator nexti;
227
228         /* Start actual marker stuff */
229
230         nframes_t last_end_time = timespan->get_start(), last_start_time = timespan->get_start();
231         status.track_position = last_start_time - timespan->get_start();
232
233         for (i = temp.begin(); i != temp.end(); ++i) {
234
235                 status.marker = *i;
236
237                 if ((*i)->start() < last_end_time) {
238                         if ((*i)->is_mark()) {
239                                 /* Index within track */
240                                 
241                                 status.index_position = (*i)->start() - timespan->get_start();
242                                 (this->*index_func) (status);
243                         }
244                         
245                         continue;
246                 }
247
248                 /* A track, defined by a cd range marker or a cd location marker outside of a cd range */
249                 
250                 status.track_position = last_end_time - timespan->get_start();
251                 status.track_start_frame = (*i)->start() - timespan->get_start();  // everything before this is the pregap
252                 status.track_duration = 0;
253                 
254                 if ((*i)->is_mark()) {
255                         // a mark track location needs to look ahead to the next marker's start to determine length
256                         nexti = i;
257                         ++nexti;
258                         
259                         if (nexti != temp.end()) {
260                                 status.track_duration = (*nexti)->start() - last_end_time;
261                                 
262                                 last_start_time = (*i)->start();
263                                 last_end_time = (*nexti)->start();
264                         } else {
265                                 // this was the last marker, use timespan end
266                                 status.track_duration = timespan->get_end() - last_end_time;
267                                 
268                                 last_start_time = (*i)->start();
269                                 last_end_time = timespan->get_end();
270                         }
271                 } else {
272                         // range
273                         status.track_duration = (*i)->end() - last_end_time;
274                         
275                         last_start_time = (*i)->start();
276                         last_end_time = (*i)->end();
277                 }
278                 
279                 (this->*track_func) (status);
280         }
281 }
282
283 void
284 ExportHandler::write_cue_header (CDMarkerStatus & status)
285 {
286         Glib::ustring title = status.timespan->name().compare ("Session") ? status.timespan->name() : (Glib::ustring) session.name();
287
288         status.out << "REM Cue file generated by Ardour" << endl;
289         status.out << "TITLE \"" << title << "\"" << endl;
290
291         // TODO
292         if (!status.format->format_name().compare ("WAV")) {
293                   status.out << "FILE " << status.filename  << " WAVE" << endl;
294         } else {
295                   status.out << "FILE \"" << status.filename  << "\" " << status.format->format_name() << endl;
296         }
297 }
298
299 void
300 ExportHandler::write_toc_header (CDMarkerStatus & status)
301 {
302         Glib::ustring title = status.timespan->name().compare ("Session") ? status.timespan->name() : (Glib::ustring) session.name();
303
304         status.out << "CD_DA" << endl;
305         status.out << "CD_TEXT {" << endl << "  LANGUAGE_MAP {" << endl << "    0 : EN" << endl << "  }" << endl;
306         status.out << "  LANGUAGE 0 {" << endl << "    TITLE \"" << title << "\"" << endl << "  }" << endl << "}" << endl;
307 }
308
309 void
310 ExportHandler::write_track_info_cue (CDMarkerStatus & status)
311 {
312         gchar buf[18];
313
314         status.out << endl << "TRACK " << status.track_number << " AUDIO" << endl;
315         status.out << "FLAGS " ;
316         
317         if (status.marker->cd_info.find("scms") != status.marker->cd_info.end())  {
318                 status.out << "SCMS ";
319         } else {
320                 status.out << "DCP ";
321         }
322         
323         if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end())  {
324                 status.out << "PRE";
325         }
326         status.out << endl;
327         
328         if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end())  {
329                 status.out << "ISRC " << status.marker->cd_info["isrc"] << endl;
330                 
331         }
332         if (status.marker->name() != "") {
333                 status.out << "TITLE \"" << status.marker->name() << "\"" << endl;
334         }
335         
336         if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
337                 status.out << "PERFORMER \"" <<  status.marker->cd_info["performer"] << "\"" << endl;
338         }
339         
340         if (status.marker->cd_info.find("string_composer") != status.marker->cd_info.end()) {
341                 status.out << "SONGWRITER \"" << status.marker->cd_info["string_composer"]  << "\"" << endl;
342         }
343
344         frames_to_cd_frames_string (buf, status.track_position);
345         status.out << "INDEX 00" << buf << endl;
346         
347         frames_to_cd_frames_string (buf, status.track_start_frame);
348         status.out << "INDEX 01" << buf << endl;
349         
350         status.index_number = 2;
351         status.track_number++;
352 }
353
354 void
355 ExportHandler::write_track_info_toc (CDMarkerStatus & status)
356 {
357         gchar buf[18];
358
359         status.out << endl << "TRACK AUDIO" << endl;
360                 
361         if (status.marker->cd_info.find("scms") != status.marker->cd_info.end())  {
362                 status.out << "NO ";
363         }
364         status.out << "COPY" << endl;
365         
366         if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end())  {
367                 status.out << "PRE_EMPHASIS" << endl;
368         } else {
369                 status.out << "NO PRE_EMPHASIS" << endl;
370         }
371         
372         if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end())  {
373                 status.out << "ISRC \"" << status.marker->cd_info["isrc"] << "\"" << endl;
374         }
375         
376         status.out << "CD_TEXT {" << endl << "  LANGUAGE 0 {" << endl << "     TITLE \"" << status.marker->name() << "\"" << endl;
377         if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
378                 status.out << "     PERFORMER \"" << status.marker->cd_info["performer"]  << "\"" << endl;
379         }
380         if (status.marker->cd_info.find("string_composer") != status.marker->cd_info.end()) {
381                 status.out  << "     COMPOSER \"" << status.marker->cd_info["string_composer"] << "\"" << endl;
382         }
383         
384         if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {                        
385                 status.out  << "     ISRC \"";
386                 status.out << status.marker->cd_info["isrc"].substr(0,2) << "-";
387                 status.out << status.marker->cd_info["isrc"].substr(2,3) << "-";
388                 status.out << status.marker->cd_info["isrc"].substr(5,2) << "-";
389                 status.out << status.marker->cd_info["isrc"].substr(7,5) << "\"" << endl;
390         }
391         
392         status.out << "  }" << endl << "}" << endl;
393         
394         frames_to_cd_frames_string (buf, status.track_position);
395         status.out << "FILE \"" << status.filename << "\" " << buf;
396         
397         frames_to_cd_frames_string (buf, status.track_duration);
398         status.out << buf << endl;
399         
400         frames_to_cd_frames_string (buf, status.track_start_frame - status.track_position);
401         status.out << "START" << buf << endl;
402 }
403
404 void
405 ExportHandler::write_index_info_cue (CDMarkerStatus & status)
406 {
407         gchar buf[18];
408
409         snprintf (buf, sizeof(buf), "INDEX %02d", cue_indexnum);
410         status.out << buf;
411         frames_to_cd_frames_string (buf, status.index_position);
412         status.out << buf << endl;
413         
414         cue_indexnum++;
415 }
416
417 void
418 ExportHandler::write_index_info_toc (CDMarkerStatus & status)
419 {
420         gchar buf[18];
421
422         frames_to_cd_frames_string (buf, status.index_position - status.track_position);
423         status.out << "INDEX" << buf << endl;
424 }
425
426 void
427 ExportHandler::frames_to_cd_frames_string (char* buf, nframes_t when)
428 {
429         nframes_t remainder;
430         nframes_t fr = session.nominal_frame_rate();
431         int mins, secs, frames;
432
433         mins = when / (60 * fr);
434         remainder = when - (mins * 60 * fr);
435         secs = remainder / fr;
436         remainder -= secs * fr;
437         frames = remainder / (fr / 75);
438         sprintf (buf, " %02d:%02d:%02d", mins, secs, frames);
439 }
440
441 void
442 ExportHandler::start_timespan ()
443 {
444         export_status->timespan++;
445
446         if (config_map.empty()) {
447                 export_status->finish ();
448                 return;
449         }
450
451         current_timespan = config_map.begin()->first;
452         
453         /* Register channel configs with timespan */
454         
455         timespan_bounds = config_map.equal_range (current_timespan);
456         
457         for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second; ++it) {
458                 it->second.channel_config->register_with_timespan (current_timespan);
459         }
460
461         /* connect stuff and start export */
462
463         current_timespan->process_connection = session.ProcessExport.connect (sigc::mem_fun (*current_timespan, &ExportTimespan::process));
464         session.start_audio_export (current_timespan->get_start(), realtime);
465 }
466
467 void
468 ExportHandler::finish_timespan ()
469 {
470         current_timespan->process_connection.disconnect ();
471         
472         /* Register formats and filenames to relevant channel configs */
473         
474         export_status->total_formats = 0;
475         export_status->format = 0;
476         
477         for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second; ++it) {
478         
479                 export_status->total_formats++;
480         
481                 /* Setup filename */
482                 
483                 it->second.filename->set_timespan (current_timespan);
484                 it->second.filename->set_channel_config (it->second.channel_config);
485                 
486                 /* Do actual registration */
487         
488                 ChannelConfigPtr chan_config = it->second.channel_config;
489                 chan_config->register_file_config (it->second.format, it->second.filename);
490         }
491         
492         /* Start writing files by doing a manual call to timespan_thread_finished */
493         
494         current_map_it = timespan_bounds.first;
495         timespan_thread_finished ();
496 }
497
498 void
499 ExportHandler::timespan_thread_finished ()
500 {
501         channel_config_connection.disconnect();
502
503         if (current_map_it != timespan_bounds.second) {
504         
505                 /* Get next configuration as long as no new export process is started */
506                 
507                 ChannelConfigPtr cc = current_map_it->second.channel_config;
508                 while (!cc->write_files(processor)) {
509         
510                         ++current_map_it;
511                         
512                         if (current_map_it == timespan_bounds.second) {
513                         
514                                 /* reached end of bounds, this call will end up in the else block below */
515                         
516                                 timespan_thread_finished ();
517                                 return;
518                         }
519                         
520                         cc = current_map_it->second.channel_config;
521                 }
522         
523                 channel_config_connection = cc->FilesWritten.connect (sigc::mem_fun (*this, &ExportHandler::timespan_thread_finished));
524                 ++current_map_it;
525         
526         } else { /* All files are written from current timespan, reset timespan and start new */
527         
528                 /* Unregister configs and remove configs with this timespan */
529         
530                 for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second;) {
531                         it->second.channel_config->unregister_all ();
532                         
533                         ConfigMap::iterator to_erase = it;
534                         ++it;
535                         config_map.erase (to_erase);
536                 }
537                 
538                 /* Start new timespan */
539         
540                 start_timespan ();
541         
542         }
543 }
544
545 } // namespace ARDOUR