2 Copyright (C) 2008 Paul Davis
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.
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.
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.
21 #include "ardour/export_handler.h"
25 #include "pbd/convert.h"
26 #include "pbd/filesystem.h"
28 #include "ardour/ardour.h"
29 #include "ardour/configuration.h"
30 #include "ardour/export_timespan.h"
31 #include "ardour/export_channel_configuration.h"
32 #include "ardour/export_status.h"
33 #include "ardour/export_format_specification.h"
34 #include "ardour/export_filename.h"
35 #include "ardour/export_processor.h"
36 #include "ardour/export_failed.h"
44 /*** ExportElementFactory ***/
46 ExportElementFactory::ExportElementFactory (Session & session) :
52 ExportElementFactory::~ExportElementFactory ()
57 ExportElementFactory::TimespanPtr
58 ExportElementFactory::add_timespan ()
60 return TimespanPtr (new ExportTimespan (session.get_export_status(), session.frame_rate()));
63 ExportElementFactory::ChannelConfigPtr
64 ExportElementFactory::add_channel_config ()
66 return ChannelConfigPtr (new ExportChannelConfiguration (session));
69 ExportElementFactory::FormatPtr
70 ExportElementFactory::add_format ()
72 return FormatPtr (new ExportFormatSpecification (session));
75 ExportElementFactory::FormatPtr
76 ExportElementFactory::add_format (XMLNode const & state)
78 return FormatPtr (new ExportFormatSpecification (session, state));
81 ExportElementFactory::FormatPtr
82 ExportElementFactory::add_format_copy (FormatPtr other)
84 return FormatPtr (new ExportFormatSpecification (*other));
87 ExportElementFactory::FilenamePtr
88 ExportElementFactory::add_filename ()
90 return FilenamePtr (new ExportFilename (session));
93 ExportElementFactory::FilenamePtr
94 ExportElementFactory::add_filename_copy (FilenamePtr other)
96 return FilenamePtr (new ExportFilename (*other));
99 /*** ExportHandler ***/
101 ExportHandler::ExportHandler (Session & session) :
102 ExportElementFactory (session),
104 export_status (session.get_export_status ()),
107 processor.reset (new ExportProcessor (session));
109 files_written_connection = ExportProcessor::WritingFile.connect (sigc::mem_fun (files_written, &std::list<Glib::ustring>::push_back));
112 ExportHandler::~ExportHandler ()
114 if (export_status->aborted()) {
115 for (std::list<Glib::ustring>::iterator it = files_written.begin(); it != files_written.end(); ++it) {
116 sys::remove (sys::path (*it));
120 channel_config_connection.disconnect();
121 files_written_connection.disconnect();
125 ExportHandler::add_export_config (TimespanPtr timespan, ChannelConfigPtr channel_config, FormatPtr format, FilenamePtr filename)
127 FileSpec spec (channel_config, format, filename);
128 ConfigPair pair (timespan, spec);
129 config_map.insert (pair);
134 /// Starts exporting the registered configurations
135 /** The following happens, when do_export is called:
136 * 1. Session is prepared in do_export
137 * 2. start_timespan is called, which then registers all necessary channel configs to a timespan
138 * 3. The timespan reads each unique channel into a tempfile and calls Session::stop_export when the end is reached
139 * 4. stop_export emits ExportReadFinished after stopping the transport, this ends up calling finish_timespan
140 * 5. finish_timespan registers all the relevant formats and filenames to relevant channel configurations
141 * 6. finish_timespan does a manual call to timespan_thread_finished, which gets the next channel configuration
142 * for the current timespan, calling write_files for it
143 * 7. write_files writes the actual export files, composing them from the individual channels from tempfiles and
144 * emits FilesWritten when it is done, which ends up calling timespan_thread_finished
145 * 8. Once all channel configs are written, a new timespan is started by calling start_timespan
146 * 9. When all timespans are written the session is taken out of export.
150 ExportHandler::do_export (bool rt)
152 /* Count timespans */
154 export_status->init();
155 std::set<TimespanPtr> timespan_set;
156 for (ConfigMap::iterator it = config_map.begin(); it != config_map.end(); ++it) {
157 timespan_set.insert (it->first);
159 export_status->total_timespans = timespan_set.size();
165 session.ExportReadFinished.connect (sigc::mem_fun (*this, &ExportHandler::finish_timespan));
169 struct LocationSortByStart {
170 bool operator() (Location *a, Location *b) {
171 return a->start() < b->start();
176 ExportHandler::export_cd_marker_file (TimespanPtr timespan, FormatPtr file_format, std::string filename, CDMarkerFormat format)
179 string basename = Glib::path_get_basename(filename);
181 size_t ext_pos = basename.rfind('.');
182 if (ext_pos != string::npos) {
183 basename = basename.substr(0, ext_pos); /* strip file extension, if there is one */
186 void (ExportHandler::*header_func) (CDMarkerStatus &);
187 void (ExportHandler::*track_func) (CDMarkerStatus &);
188 void (ExportHandler::*index_func) (CDMarkerStatus &);
192 filepath = Glib::build_filename(Glib::path_get_dirname(filename), basename + ".toc");
193 header_func = &ExportHandler::write_toc_header;
194 track_func = &ExportHandler::write_track_info_toc;
195 index_func = &ExportHandler::write_index_info_toc;
198 filepath = Glib::build_filename(Glib::path_get_dirname(filename), basename + ".cue");
199 header_func = &ExportHandler::write_cue_header;
200 track_func = &ExportHandler::write_track_info_cue;
201 index_func = &ExportHandler::write_index_info_cue;
207 CDMarkerStatus status (filepath, timespan, file_format, filename);
210 error << string_compose(_("Editor: cannot open \"%1\" as export file for CD marker file"), filepath) << endmsg;
214 (this->*header_func) (status);
216 /* Get locations and sort */
218 Locations::LocationList const & locations (session.locations()->list());
219 Locations::LocationList::const_iterator i;
220 Locations::LocationList temp;
222 for (i = locations.begin(); i != locations.end(); ++i) {
223 if ((*i)->start() >= timespan->get_start() && (*i)->end() <= timespan->get_end() && (*i)->is_cd_marker() && !(*i)->is_end()) {
229 // TODO One index marker for whole thing
233 LocationSortByStart cmp;
235 Locations::LocationList::const_iterator nexti;
237 /* Start actual marker stuff */
239 nframes_t last_end_time = timespan->get_start(), last_start_time = timespan->get_start();
240 status.track_position = last_start_time - timespan->get_start();
242 for (i = temp.begin(); i != temp.end(); ++i) {
246 if ((*i)->start() < last_end_time) {
247 if ((*i)->is_mark()) {
248 /* Index within track */
250 status.index_position = (*i)->start() - timespan->get_start();
251 (this->*index_func) (status);
257 /* A track, defined by a cd range marker or a cd location marker outside of a cd range */
259 status.track_position = last_end_time - timespan->get_start();
260 status.track_start_frame = (*i)->start() - timespan->get_start(); // everything before this is the pregap
261 status.track_duration = 0;
263 if ((*i)->is_mark()) {
264 // a mark track location needs to look ahead to the next marker's start to determine length
268 if (nexti != temp.end()) {
269 status.track_duration = (*nexti)->start() - last_end_time;
271 last_start_time = (*i)->start();
272 last_end_time = (*nexti)->start();
274 // this was the last marker, use timespan end
275 status.track_duration = timespan->get_end() - last_end_time;
277 last_start_time = (*i)->start();
278 last_end_time = timespan->get_end();
282 status.track_duration = (*i)->end() - last_end_time;
284 last_start_time = (*i)->start();
285 last_end_time = (*i)->end();
288 (this->*track_func) (status);
293 ExportHandler::write_cue_header (CDMarkerStatus & status)
295 Glib::ustring title = status.timespan->name().compare ("Session") ? status.timespan->name() : (Glib::ustring) session.name();
297 status.out << "REM Cue file generated by Ardour" << endl;
298 status.out << "TITLE \"" << title << "\"" << endl;
300 /* The cue sheet syntax has originally five file types:
301 WAVE : 44.1 kHz, 16 Bit (little endian)
302 AIFF : 44.1 kHz, 16 Bit (big endian)
303 BINARY : 44.1 kHz, 16 Bit (little endian)
304 MOTOROLA : 44.1 kHz, 16 Bit (big endian)
307 We want to use cue sheets not only as CD images but also as general playlyist
308 format, thus for WAVE and AIFF we don't care if it's really 44.1 kHz/16 Bit, the
309 soundfile's header shows it anyway. But for the raw formats, i.e. BINARY
310 and MOTOROLA we do care, because no header would tell us about a different format.
312 For all other formats we just make up our own file type. MP3 is not supported
316 status.out << "FILE \"" << Glib::path_get_basename(status.filename) << "\" ";
317 if (!status.format->format_name().compare ("WAV")) {
318 status.out << "WAVE";
319 } else if (status.format->format_name() == ExportFormatBase::F_RAW &&
320 status.format->sample_format() == ExportFormatBase::SF_16 &&
321 status.format->sample_rate() == ExportFormatBase::SR_44_1) {
322 // Format is RAW 16bit 44.1kHz
323 if (status.format->endianness() == ExportFormatBase::E_Little) {
324 status.out << "BINARY";
326 status.out << "MOTOROLA";
329 // AIFF should return "AIFF"
330 status.out << status.format->format_name();
336 ExportHandler::write_toc_header (CDMarkerStatus & status)
338 Glib::ustring title = status.timespan->name().compare ("Session") ? status.timespan->name() : (Glib::ustring) session.name();
340 status.out << "CD_DA" << endl;
341 status.out << "CD_TEXT {" << endl << " LANGUAGE_MAP {" << endl << " 0 : EN" << endl << " }" << endl;
342 status.out << " LANGUAGE 0 {" << endl << " TITLE \"" << title << "\"" << endl << " }" << endl << "}" << endl;
346 ExportHandler::write_track_info_cue (CDMarkerStatus & status)
350 snprintf (buf, sizeof(buf), " TRACK %02d AUDIO", status.track_number);
351 status.out << buf << endl;
353 status.out << " FLAGS" ;
354 if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) {
355 status.out << " SCMS ";
357 status.out << " DCP ";
360 if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) {
361 status.out << " PRE";
365 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
366 status.out << " ISRC " << status.marker->cd_info["isrc"] << endl;
369 if (status.marker->name() != "") {
370 status.out << " TITLE \"" << status.marker->name() << "\"" << endl;
373 if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
374 status.out << " PERFORMER \"" << status.marker->cd_info["performer"] << "\"" << endl;
377 if (status.marker->cd_info.find("string_composer") != status.marker->cd_info.end()) {
378 status.out << " SONGWRITER \"" << status.marker->cd_info["string_composer"] << "\"" << endl;
381 if (status.track_position != status.track_start_frame) {
382 frames_to_cd_frames_string (buf, status.track_position);
383 status.out << " INDEX 00" << buf << endl;
386 frames_to_cd_frames_string (buf, status.track_start_frame);
387 status.out << " INDEX 01" << buf << endl;
389 status.index_number = 2;
390 status.track_number++;
394 ExportHandler::write_track_info_toc (CDMarkerStatus & status)
398 status.out << endl << "TRACK AUDIO" << endl;
400 if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) {
403 status.out << "COPY" << endl;
405 if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) {
406 status.out << "PRE_EMPHASIS" << endl;
408 status.out << "NO PRE_EMPHASIS" << endl;
411 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
412 status.out << "ISRC \"" << status.marker->cd_info["isrc"] << "\"" << endl;
415 status.out << "CD_TEXT {" << endl << " LANGUAGE 0 {" << endl << " TITLE \"" << status.marker->name() << "\"" << endl;
416 if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
417 status.out << " PERFORMER \"" << status.marker->cd_info["performer"] << "\"" << endl;
419 if (status.marker->cd_info.find("string_composer") != status.marker->cd_info.end()) {
420 status.out << " COMPOSER \"" << status.marker->cd_info["string_composer"] << "\"" << endl;
423 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
424 status.out << " ISRC \"";
425 status.out << status.marker->cd_info["isrc"].substr(0,2) << "-";
426 status.out << status.marker->cd_info["isrc"].substr(2,3) << "-";
427 status.out << status.marker->cd_info["isrc"].substr(5,2) << "-";
428 status.out << status.marker->cd_info["isrc"].substr(7,5) << "\"" << endl;
431 status.out << " }" << endl << "}" << endl;
433 frames_to_cd_frames_string (buf, status.track_position);
434 status.out << "FILE \"" << status.filename << "\" " << buf;
436 frames_to_cd_frames_string (buf, status.track_duration);
437 status.out << buf << endl;
439 frames_to_cd_frames_string (buf, status.track_start_frame - status.track_position);
440 status.out << "START" << buf << endl;
444 ExportHandler::write_index_info_cue (CDMarkerStatus & status)
448 snprintf (buf, sizeof(buf), " INDEX %02d", cue_indexnum);
450 frames_to_cd_frames_string (buf, status.index_position);
451 status.out << buf << endl;
457 ExportHandler::write_index_info_toc (CDMarkerStatus & status)
461 frames_to_cd_frames_string (buf, status.index_position - status.track_position);
462 status.out << "INDEX" << buf << endl;
466 ExportHandler::frames_to_cd_frames_string (char* buf, nframes_t when)
469 nframes_t fr = session.nominal_frame_rate();
470 int mins, secs, frames;
472 mins = when / (60 * fr);
473 remainder = when - (mins * 60 * fr);
474 secs = remainder / fr;
475 remainder -= secs * fr;
476 frames = remainder / (fr / 75);
477 sprintf (buf, " %02d:%02d:%02d", mins, secs, frames);
481 ExportHandler::start_timespan ()
483 export_status->timespan++;
485 if (config_map.empty()) {
486 export_status->finish ();
490 current_timespan = config_map.begin()->first;
492 /* Register channel configs with timespan */
494 timespan_bounds = config_map.equal_range (current_timespan);
496 for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second; ++it) {
497 it->second.channel_config->register_with_timespan (current_timespan);
500 /* connect stuff and start export */
502 current_timespan->process_connection = session.ProcessExport.connect (sigc::mem_fun (*current_timespan, &ExportTimespan::process));
503 session.start_audio_export (current_timespan->get_start(), realtime);
507 ExportHandler::finish_timespan ()
509 current_timespan->process_connection.disconnect ();
511 /* Register formats and filenames to relevant channel configs */
513 export_status->total_formats = 0;
514 export_status->format = 0;
516 for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second; ++it) {
518 export_status->total_formats++;
522 it->second.filename->set_timespan (current_timespan);
523 it->second.filename->set_channel_config (it->second.channel_config);
525 /* Do actual registration */
527 ChannelConfigPtr chan_config = it->second.channel_config;
528 chan_config->register_file_config (it->second.format, it->second.filename);
531 /* Start writing files by doing a manual call to timespan_thread_finished */
533 current_map_it = timespan_bounds.first;
534 timespan_thread_finished ();
538 ExportHandler::timespan_thread_finished ()
540 channel_config_connection.disconnect();
542 if (current_map_it != timespan_bounds.second) {
544 /* Get next configuration as long as no new export process is started */
546 ChannelConfigPtr cc = current_map_it->second.channel_config;
547 while (!cc->write_files(processor)) {
551 if (current_map_it == timespan_bounds.second) {
553 /* reached end of bounds, this call will end up in the else block below */
555 timespan_thread_finished ();
559 cc = current_map_it->second.channel_config;
562 channel_config_connection = cc->FilesWritten.connect (sigc::mem_fun (*this, &ExportHandler::timespan_thread_finished));
565 } else { /* All files are written from current timespan, reset timespan and start new */
567 /* Unregister configs and remove configs with this timespan */
569 for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second;) {
570 it->second.channel_config->unregister_all ();
572 ConfigMap::iterator to_erase = it;
574 config_map.erase (to_erase);
577 /* Start new timespan */
584 } // namespace ARDOUR