2 Copyright (C) 2008-2009 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"
23 #include "pbd/gstdio_compat.h"
25 #include <glibmm/convert.h>
27 #include "pbd/convert.h"
29 #include "ardour/audioengine.h"
30 #include "ardour/audiofile_tagger.h"
31 #include "ardour/audio_port.h"
32 #include "ardour/debug.h"
33 #include "ardour/export_graph_builder.h"
34 #include "ardour/export_timespan.h"
35 #include "ardour/export_channel_configuration.h"
36 #include "ardour/export_status.h"
37 #include "ardour/export_format_specification.h"
38 #include "ardour/export_filename.h"
39 #include "ardour/soundcloud_upload.h"
40 #include "ardour/system_exec.h"
41 #include "pbd/openuri.h"
42 #include "pbd/basename.h"
43 #include "ardour/session_metadata.h"
53 /*** ExportElementFactory ***/
55 ExportElementFactory::ExportElementFactory (Session & session) :
61 ExportElementFactory::~ExportElementFactory ()
67 ExportElementFactory::add_timespan ()
69 return ExportTimespanPtr (new ExportTimespan (session.get_export_status(), session.frame_rate()));
72 ExportChannelConfigPtr
73 ExportElementFactory::add_channel_config ()
75 return ExportChannelConfigPtr (new ExportChannelConfiguration (session));
79 ExportElementFactory::add_format ()
81 return ExportFormatSpecPtr (new ExportFormatSpecification (session));
85 ExportElementFactory::add_format (XMLNode const & state)
87 return ExportFormatSpecPtr (new ExportFormatSpecification (session, state));
91 ExportElementFactory::add_format_copy (ExportFormatSpecPtr other)
93 return ExportFormatSpecPtr (new ExportFormatSpecification (*other));
97 ExportElementFactory::add_filename ()
99 return ExportFilenamePtr (new ExportFilename (session));
103 ExportElementFactory::add_filename_copy (ExportFilenamePtr other)
105 return ExportFilenamePtr (new ExportFilename (*other));
108 /*** ExportHandler ***/
110 ExportHandler::ExportHandler (Session & session)
111 : ExportElementFactory (session)
113 , graph_builder (new ExportGraphBuilder (session))
114 , export_status (session.get_export_status ())
115 , post_processing (false)
121 ExportHandler::~ExportHandler ()
123 graph_builder->cleanup (export_status->aborted () );
126 /** Add an export to the `to-do' list */
128 ExportHandler::add_export_config (ExportTimespanPtr timespan, ExportChannelConfigPtr channel_config,
129 ExportFormatSpecPtr format, ExportFilenamePtr filename,
130 BroadcastInfoPtr broadcast_info)
132 FileSpec spec (channel_config, format, filename, broadcast_info);
133 config_map.insert (make_pair (timespan, spec));
139 ExportHandler::do_export ()
141 /* Count timespans */
143 export_status->init();
144 std::set<ExportTimespanPtr> timespan_set;
145 for (ConfigMap::iterator it = config_map.begin(); it != config_map.end(); ++it) {
146 bool new_timespan = timespan_set.insert (it->first).second;
148 export_status->total_frames += it->first->get_length();
151 export_status->total_timespans = timespan_set.size();
153 if (export_status->total_timespans > 1) {
154 // always include timespan if there's more than one.
155 for (ConfigMap::iterator it = config_map.begin(); it != config_map.end(); ++it) {
156 FileSpec & spec = it->second;
157 spec.filename->include_timespan = true;
163 Glib::Threads::Mutex::Lock l (export_status->lock());
168 ExportHandler::start_timespan ()
170 export_status->timespan++;
172 if (config_map.empty()) {
173 // freewheeling has to be stopped from outside the process cycle
174 export_status->set_running (false);
178 /* finish_timespan pops the config_map entry that has been done, so
179 this is the timespan to do this time
181 current_timespan = config_map.begin()->first;
183 export_status->total_frames_current_timespan = current_timespan->get_length();
184 export_status->timespan_name = current_timespan->name();
185 export_status->processed_frames_current_timespan = 0;
187 /* Register file configurations to graph builder */
189 /* Here's the config_map entries that use this timespan */
190 timespan_bounds = config_map.equal_range (current_timespan);
191 graph_builder->reset ();
192 graph_builder->set_current_timespan (current_timespan);
193 handle_duplicate_format_extensions();
194 bool realtime = current_timespan->realtime ();
195 bool region_export = true;
196 bool incl_master_bus = false;
197 for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second; ++it) {
198 // Filenames can be shared across timespans
199 FileSpec & spec = it->second;
200 spec.filename->set_timespan (it->first);
201 switch (spec.channel_config->region_processing_type ()) {
202 case RegionExportChannelFactory::None:
203 case RegionExportChannelFactory::Processed:
204 region_export = false;
209 #if 1 // hack alert -- align master bus, compensate master latency
211 /* there's no easier way to get this information here.
212 * Ports are configured in the PortExportChannelSelector GUI,
213 * This ExportHandler has no context of routes.
215 boost::shared_ptr<Route> master_bus = session.master_out ();
217 const PortSet& ps = master_bus->output ()->ports();
219 const ExportChannelConfiguration::ChannelList& channels = spec.channel_config->get_channels ();
220 for (ExportChannelConfiguration::ChannelList::const_iterator it = channels.begin(); it != channels.end(); ++it) {
222 boost::shared_ptr <PortExportChannel> pep = boost::dynamic_pointer_cast<PortExportChannel> (*it);
226 PortExportChannel::PortSet const& ports = pep->get_ports ();
227 for (PortExportChannel::PortSet::const_iterator it = ports.begin(); it != ports.end(); ++it) {
228 boost::shared_ptr<AudioPort> ap = (*it).lock();
229 if (ps.contains (ap)) {
230 incl_master_bus = true;
236 graph_builder->add_config (spec, realtime);
239 // ExportDialog::update_realtime_selection does not allow this
240 assert (!region_export || !realtime);
244 post_processing = false;
245 session.ProcessExport.connect_same_thread (process_connection, boost::bind (&ExportHandler::process, this, _1));
246 process_position = current_timespan->get_start();
247 // TODO check if it's a RegionExport.. set flag to skip process_without_events()
248 session.start_audio_export (process_position, realtime, region_export, incl_master_bus);
252 ExportHandler::handle_duplicate_format_extensions()
254 typedef std::map<std::string, int> ExtCountMap;
257 for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second; ++it) {
258 counts[it->second.format->extension()]++;
261 bool duplicates_found = false;
262 for (ExtCountMap::iterator it = counts.begin(); it != counts.end(); ++it) {
263 if (it->second > 1) { duplicates_found = true; }
266 // Set this always, as the filenames are shared...
267 for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second; ++it) {
268 it->second.filename->include_format_name = duplicates_found;
273 ExportHandler::process (framecnt_t frames)
275 if (!export_status->running ()) {
277 } else if (post_processing) {
278 Glib::Threads::Mutex::Lock l (export_status->lock());
279 if (AudioEngine::instance()->freewheeling ()) {
280 return post_process ();
282 // wait until we're freewheeling
286 Glib::Threads::Mutex::Lock l (export_status->lock());
287 return process_timespan (frames);
292 ExportHandler::process_timespan (framecnt_t frames)
294 export_status->active_job = ExportStatus::Exporting;
295 /* update position */
297 framecnt_t frames_to_read = 0;
298 framepos_t const end = current_timespan->get_end();
300 bool const last_cycle = (process_position + frames >= end);
303 frames_to_read = end - process_position;
304 export_status->stop = true;
306 frames_to_read = frames;
309 process_position += frames_to_read;
310 export_status->processed_frames += frames_to_read;
311 export_status->processed_frames_current_timespan += frames_to_read;
313 /* Do actual processing */
314 int ret = graph_builder->process (frames_to_read, last_cycle);
316 /* Start post-processing/normalizing if necessary */
318 post_processing = graph_builder->need_postprocessing ();
319 if (post_processing) {
320 export_status->total_postprocessing_cycles = graph_builder->get_postprocessing_cycle_count();
321 export_status->current_postprocessing_cycle = 0;
332 ExportHandler::post_process ()
334 if (graph_builder->post_process ()) {
336 export_status->active_job = ExportStatus::Exporting;
338 if (graph_builder->realtime ()) {
339 export_status->active_job = ExportStatus::Encoding;
341 export_status->active_job = ExportStatus::Normalizing;
345 export_status->current_postprocessing_cycle++;
351 ExportHandler::command_output(std::string output, size_t size)
353 std::cerr << "command: " << size << ", " << output << std::endl;
354 info << output << endmsg;
358 ExportHandler::finish_timespan ()
360 graph_builder->get_analysis_results (export_status->result_map);
362 while (config_map.begin() != timespan_bounds.second) {
364 ExportFormatSpecPtr fmt = config_map.begin()->second.format;
365 std::string filename = config_map.begin()->second.filename->get_path(fmt);
366 if (fmt->with_cue()) {
367 export_cd_marker_file (current_timespan, fmt, filename, CDMarkerCUE);
370 if (fmt->with_toc()) {
371 export_cd_marker_file (current_timespan, fmt, filename, CDMarkerTOC);
374 if (fmt->with_mp4chaps()) {
375 export_cd_marker_file (current_timespan, fmt, filename, MP4Chaps);
378 Session::Exported (current_timespan->name(), filename); /* EMIT SIGNAL */
380 /* close file first, otherwise TagLib enounters an ERROR_SHARING_VIOLATION
381 * The process cannot access the file because it is being used.
382 * ditto for post-export and upload.
384 graph_builder->reset ();
387 /* TODO: check Umlauts and encoding in filename.
388 * TagLib eventually calls CreateFileA(),
390 export_status->active_job = ExportStatus::Tagging;
391 AudiofileTagger::tag_file(filename, *SessionMetadata::Metadata());
394 if (!fmt->command().empty()) {
395 SessionMetadata const & metadata (*SessionMetadata::Metadata());
397 #if 0 // would be nicer with C++11 initialiser...
398 std::map<char, std::string> subs {
400 { 'd', Glib::path_get_dirname(filename) + G_DIR_SEPARATOR },
401 { 'b', PBD::basename_nosuffix(filename) },
405 export_status->active_job = ExportStatus::Command;
406 PBD::ScopedConnection command_connection;
407 std::map<char, std::string> subs;
409 std::stringstream track_number;
410 track_number << metadata.track_number ();
411 std::stringstream total_tracks;
412 total_tracks << metadata.total_tracks ();
413 std::stringstream year;
414 year << metadata.year ();
416 subs.insert (std::pair<char, std::string> ('a', metadata.artist ()));
417 subs.insert (std::pair<char, std::string> ('b', PBD::basename_nosuffix (filename)));
418 subs.insert (std::pair<char, std::string> ('c', metadata.copyright ()));
419 subs.insert (std::pair<char, std::string> ('d', Glib::path_get_dirname (filename) + G_DIR_SEPARATOR));
420 subs.insert (std::pair<char, std::string> ('f', filename));
421 subs.insert (std::pair<char, std::string> ('l', metadata.lyricist ()));
422 subs.insert (std::pair<char, std::string> ('n', session.name ()));
423 subs.insert (std::pair<char, std::string> ('s', session.path ()));
424 subs.insert (std::pair<char, std::string> ('o', metadata.conductor ()));
425 subs.insert (std::pair<char, std::string> ('t', metadata.title ()));
426 subs.insert (std::pair<char, std::string> ('z', metadata.organization ()));
427 subs.insert (std::pair<char, std::string> ('A', metadata.album ()));
428 subs.insert (std::pair<char, std::string> ('C', metadata.comment ()));
429 subs.insert (std::pair<char, std::string> ('E', metadata.engineer ()));
430 subs.insert (std::pair<char, std::string> ('G', metadata.genre ()));
431 subs.insert (std::pair<char, std::string> ('L', total_tracks.str ()));
432 subs.insert (std::pair<char, std::string> ('M', metadata.mixer ()));
433 subs.insert (std::pair<char, std::string> ('N', current_timespan->name())); // =?= config_map.begin()->first->name ()
434 subs.insert (std::pair<char, std::string> ('O', metadata.composer ()));
435 subs.insert (std::pair<char, std::string> ('P', metadata.producer ()));
436 subs.insert (std::pair<char, std::string> ('S', metadata.disc_subtitle ()));
437 subs.insert (std::pair<char, std::string> ('T', track_number.str ()));
438 subs.insert (std::pair<char, std::string> ('Y', year.str ()));
439 subs.insert (std::pair<char, std::string> ('Z', metadata.country ()));
441 ARDOUR::SystemExec *se = new ARDOUR::SystemExec(fmt->command(), subs);
442 info << "Post-export command line : {" << se->to_s () << "}" << endmsg;
443 se->ReadStdout.connect_same_thread(command_connection, boost::bind(&ExportHandler::command_output, this, _1, _2));
444 int ret = se->start (2);
446 // successfully started
447 while (se->is_running ()) {
448 // wait for system exec to terminate
452 error << "Post-export command FAILED with Error: " << ret << endmsg;
457 if (fmt->soundcloud_upload()) {
458 SoundcloudUploader *soundcloud_uploader = new SoundcloudUploader;
459 std::string token = soundcloud_uploader->Get_Auth_Token(soundcloud_username, soundcloud_password);
460 DEBUG_TRACE (DEBUG::Soundcloud, string_compose(
461 "uploading %1 - username=%2, password=%3, token=%4",
462 filename, soundcloud_username, soundcloud_password, token) );
463 std::string path = soundcloud_uploader->Upload (
465 PBD::basename_nosuffix(filename), // title
467 soundcloud_make_public,
468 soundcloud_downloadable,
471 if (path.length() != 0) {
472 info << string_compose ( _("File %1 uploaded to %2"), filename, path) << endmsg;
473 if (soundcloud_open_page) {
474 DEBUG_TRACE (DEBUG::Soundcloud, string_compose ("opening %1", path) );
475 open_uri(path.c_str()); // open the soundcloud website to the new file
478 error << _("upload to Soundcloud failed. Perhaps your email or password are incorrect?\n") << endmsg;
480 delete soundcloud_uploader;
482 config_map.erase (config_map.begin());
488 /*** CD Marker stuff ***/
490 struct LocationSortByStart {
491 bool operator() (Location *a, Location *b) {
492 return a->start() < b->start();
497 ExportHandler::export_cd_marker_file (ExportTimespanPtr timespan, ExportFormatSpecPtr file_format,
498 std::string filename, CDMarkerFormat format)
500 string filepath = get_cd_marker_filename(filename, format);
503 void (ExportHandler::*header_func) (CDMarkerStatus &);
504 void (ExportHandler::*track_func) (CDMarkerStatus &);
505 void (ExportHandler::*index_func) (CDMarkerStatus &);
509 header_func = &ExportHandler::write_toc_header;
510 track_func = &ExportHandler::write_track_info_toc;
511 index_func = &ExportHandler::write_index_info_toc;
514 header_func = &ExportHandler::write_cue_header;
515 track_func = &ExportHandler::write_track_info_cue;
516 index_func = &ExportHandler::write_index_info_cue;
519 header_func = &ExportHandler::write_mp4ch_header;
520 track_func = &ExportHandler::write_track_info_mp4ch;
521 index_func = &ExportHandler::write_index_info_mp4ch;
527 CDMarkerStatus status (filepath, timespan, file_format, filename);
529 (this->*header_func) (status);
531 /* Get locations and sort */
533 Locations::LocationList const & locations (session.locations()->list());
534 Locations::LocationList::const_iterator i;
535 Locations::LocationList temp;
537 for (i = locations.begin(); i != locations.end(); ++i) {
538 if ((*i)->start() >= timespan->get_start() && (*i)->end() <= timespan->get_end() && (*i)->is_cd_marker() && !(*i)->is_session_range()) {
544 // TODO One index marker for whole thing
548 LocationSortByStart cmp;
550 Locations::LocationList::const_iterator nexti;
552 /* Start actual marker stuff */
554 framepos_t last_end_time = timespan->get_start();
555 status.track_position = 0;
557 for (i = temp.begin(); i != temp.end(); ++i) {
561 if ((*i)->start() < last_end_time) {
562 if ((*i)->is_mark()) {
563 /* Index within track */
565 status.index_position = (*i)->start() - timespan->get_start();
566 (this->*index_func) (status);
572 /* A track, defined by a cd range marker or a cd location marker outside of a cd range */
574 status.track_position = last_end_time - timespan->get_start();
575 status.track_start_frame = (*i)->start() - timespan->get_start(); // everything before this is the pregap
576 status.track_duration = 0;
578 if ((*i)->is_mark()) {
579 // a mark track location needs to look ahead to the next marker's start to determine length
583 if (nexti != temp.end()) {
584 status.track_duration = (*nexti)->start() - last_end_time;
586 last_end_time = (*nexti)->start();
588 // this was the last marker, use timespan end
589 status.track_duration = timespan->get_end() - last_end_time;
591 last_end_time = timespan->get_end();
595 status.track_duration = (*i)->end() - last_end_time;
597 last_end_time = (*i)->end();
600 (this->*track_func) (status);
603 } catch (std::exception& e) {
604 error << string_compose (_("an error occurred while writing a TOC/CUE file: %1"), e.what()) << endmsg;
605 ::g_unlink (filepath.c_str());
606 } catch (Glib::Exception& e) {
607 error << string_compose (_("an error occurred while writing a TOC/CUE file: %1"), e.what()) << endmsg;
608 ::g_unlink (filepath.c_str());
613 ExportHandler::get_cd_marker_filename(std::string filename, CDMarkerFormat format)
615 /* do not strip file suffix because there may be more than one format,
616 and we do not want the CD marker file from one format to overwrite
617 another (e.g. foo.wav.cue > foo.aiff.cue)
622 return filename + ".toc";
624 return filename + ".cue";
627 unsigned lastdot = filename.find_last_of('.');
628 return filename.substr(0,lastdot) + ".chapters.txt";
631 return filename + ".marker"; // Should not be reached when actually creating a file
636 ExportHandler::write_cue_header (CDMarkerStatus & status)
638 string title = status.timespan->name().compare ("Session") ? status.timespan->name() : (string) session.name();
641 string barcode = SessionMetadata::Metadata()->barcode();
642 string album_artist = SessionMetadata::Metadata()->album_artist();
643 string album_title = SessionMetadata::Metadata()->album();
645 status.out << "REM Cue file generated by " << PROGRAM_NAME << endl;
648 status.out << "CATALOG " << barcode << endl;
650 if (album_artist != "")
651 status.out << "PERFORMER " << cue_escape_cdtext (album_artist) << endl;
653 if (album_title != "")
656 status.out << "TITLE " << cue_escape_cdtext (title) << endl;
658 /* The original cue sheet spec mentions five file types
660 BINARY = "header-less" audio (44.1 kHz, 16 Bit, little endian),
661 MOTOROLA = "header-less" audio (44.1 kHz, 16 Bit, big endian),
664 We try to use these file types whenever appropriate and
665 default to our own names otherwise.
667 status.out << "FILE \"" << Glib::path_get_basename(status.filename) << "\" ";
668 if (!status.format->format_name().compare ("WAV") || !status.format->format_name().compare ("BWF")) {
669 status.out << "WAVE";
670 } else if (status.format->format_id() == ExportFormatBase::F_RAW &&
671 status.format->sample_format() == ExportFormatBase::SF_16 &&
672 status.format->sample_rate() == ExportFormatBase::SR_44_1) {
673 // Format is RAW 16bit 44.1kHz
674 if (status.format->endianness() == ExportFormatBase::E_Little) {
675 status.out << "BINARY";
677 status.out << "MOTOROLA";
680 // no special case for AIFF format it's name is already "AIFF"
681 status.out << status.format->format_name();
687 ExportHandler::write_toc_header (CDMarkerStatus & status)
689 string title = status.timespan->name().compare ("Session") ? status.timespan->name() : (string) session.name();
692 string barcode = SessionMetadata::Metadata()->barcode();
693 string album_artist = SessionMetadata::Metadata()->album_artist();
694 string album_title = SessionMetadata::Metadata()->album();
697 status.out << "CATALOG \"" << barcode << "\"" << endl;
699 if (album_title != "")
702 status.out << "CD_DA" << endl;
703 status.out << "CD_TEXT {" << endl << " LANGUAGE_MAP {" << endl << " 0 : EN" << endl << " }" << endl;
704 status.out << " LANGUAGE 0 {" << endl << " TITLE " << toc_escape_cdtext (title) << endl ;
705 status.out << " PERFORMER " << toc_escape_cdtext (album_artist) << endl;
706 status.out << " }" << endl << "}" << endl;
710 ExportHandler::write_mp4ch_header (CDMarkerStatus & status)
712 status.out << "00:00:00.000 Intro" << endl;
716 ExportHandler::write_track_info_cue (CDMarkerStatus & status)
720 snprintf (buf, sizeof(buf), " TRACK %02d AUDIO", status.track_number);
721 status.out << buf << endl;
723 status.out << " FLAGS" ;
724 if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) {
725 status.out << " SCMS ";
727 status.out << " DCP ";
730 if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) {
731 status.out << " PRE";
735 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
736 status.out << " ISRC " << status.marker->cd_info["isrc"] << endl;
739 if (status.marker->name() != "") {
740 status.out << " TITLE " << cue_escape_cdtext (status.marker->name()) << endl;
743 if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
744 status.out << " PERFORMER " << cue_escape_cdtext (status.marker->cd_info["performer"]) << endl;
747 if (status.marker->cd_info.find("composer") != status.marker->cd_info.end()) {
748 status.out << " SONGWRITER " << cue_escape_cdtext (status.marker->cd_info["composer"]) << endl;
751 if (status.track_position != status.track_start_frame) {
752 frames_to_cd_frames_string (buf, status.track_position);
753 status.out << " INDEX 00" << buf << endl;
756 frames_to_cd_frames_string (buf, status.track_start_frame);
757 status.out << " INDEX 01" << buf << endl;
759 status.index_number = 2;
760 status.track_number++;
764 ExportHandler::write_track_info_toc (CDMarkerStatus & status)
768 status.out << endl << "TRACK AUDIO" << endl;
770 if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) {
773 status.out << "COPY" << endl;
775 if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) {
776 status.out << "PRE_EMPHASIS" << endl;
778 status.out << "NO PRE_EMPHASIS" << endl;
781 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
782 status.out << "ISRC \"" << status.marker->cd_info["isrc"] << "\"" << endl;
785 status.out << "CD_TEXT {" << endl << " LANGUAGE 0 {" << endl;
786 status.out << " TITLE " << toc_escape_cdtext (status.marker->name()) << endl;
788 status.out << " PERFORMER ";
789 if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
790 status.out << toc_escape_cdtext (status.marker->cd_info["performer"]) << endl;
792 status.out << "\"\"" << endl;
795 if (status.marker->cd_info.find("composer") != status.marker->cd_info.end()) {
796 status.out << " SONGWRITER " << toc_escape_cdtext (status.marker->cd_info["composer"]) << endl;
799 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
800 status.out << " ISRC \"";
801 status.out << status.marker->cd_info["isrc"].substr(0,2) << "-";
802 status.out << status.marker->cd_info["isrc"].substr(2,3) << "-";
803 status.out << status.marker->cd_info["isrc"].substr(5,2) << "-";
804 status.out << status.marker->cd_info["isrc"].substr(7,5) << "\"" << endl;
807 status.out << " }" << endl << "}" << endl;
809 frames_to_cd_frames_string (buf, status.track_position);
810 status.out << "FILE " << toc_escape_filename (status.filename) << ' ' << buf;
812 frames_to_cd_frames_string (buf, status.track_duration);
813 status.out << buf << endl;
815 frames_to_cd_frames_string (buf, status.track_start_frame - status.track_position);
816 status.out << "START" << buf << endl;
819 void ExportHandler::write_track_info_mp4ch (CDMarkerStatus & status)
823 frames_to_chapter_marks_string(buf, status.track_start_frame);
824 status.out << buf << " " << status.marker->name() << endl;
828 ExportHandler::write_index_info_cue (CDMarkerStatus & status)
832 snprintf (buf, sizeof(buf), " INDEX %02d", cue_indexnum);
834 frames_to_cd_frames_string (buf, status.index_position);
835 status.out << buf << endl;
841 ExportHandler::write_index_info_toc (CDMarkerStatus & status)
845 frames_to_cd_frames_string (buf, status.index_position - status.track_position);
846 status.out << "INDEX" << buf << endl;
850 ExportHandler::write_index_info_mp4ch (CDMarkerStatus & status)
855 ExportHandler::frames_to_cd_frames_string (char* buf, framepos_t when)
857 framecnt_t remainder;
858 framecnt_t fr = session.nominal_frame_rate();
859 int mins, secs, frames;
861 mins = when / (60 * fr);
862 remainder = when - (mins * 60 * fr);
863 secs = remainder / fr;
864 remainder -= secs * fr;
865 frames = remainder / (fr / 75);
866 sprintf (buf, " %02d:%02d:%02d", mins, secs, frames);
870 ExportHandler::frames_to_chapter_marks_string (char* buf, framepos_t when)
872 framecnt_t remainder;
873 framecnt_t fr = session.nominal_frame_rate();
874 int hours, mins, secs, msecs;
876 hours = when / (3600 * fr);
877 remainder = when - (hours * 3600 * fr);
878 mins = remainder / (60 * fr);
879 remainder -= mins * 60 * fr;
880 secs = remainder / fr;
881 remainder -= secs * fr;
882 msecs = (remainder * 1000) / fr;
883 sprintf (buf, "%02d:%02d:%02d.%03d", hours, mins, secs, msecs);
887 ExportHandler::toc_escape_cdtext (const std::string& txt)
889 Glib::ustring check (txt);
891 std::string latin1_txt;
895 latin1_txt = Glib::convert_with_fallback (txt, "ISO-8859-1", "UTF-8", "_");
896 } catch (Glib::ConvertError& err) {
897 throw Glib::ConvertError (err.code(), string_compose (_("Cannot convert %1 to Latin-1 text"), txt));
902 for (std::string::const_iterator c = latin1_txt.begin(); c != latin1_txt.end(); ++c) {
906 } else if ((*c) == '\\') {
908 } else if (isprint (*c)) {
911 snprintf (buf, sizeof (buf), "\\%03o", (int) (unsigned char) *c);
922 ExportHandler::toc_escape_filename (const std::string& txt)
928 // We iterate byte-wise not character-wise over a UTF-8 string here,
929 // because we only want to translate backslashes and double quotes
930 for (std::string::const_iterator c = txt.begin(); c != txt.end(); ++c) {
934 } else if (*c == '\\') {
947 ExportHandler::cue_escape_cdtext (const std::string& txt)
949 std::string latin1_txt;
953 latin1_txt = Glib::convert (txt, "ISO-8859-1", "UTF-8");
954 } catch (Glib::ConvertError& err) {
955 throw Glib::ConvertError (err.code(), string_compose (_("Cannot convert %1 to Latin-1 text"), txt));
958 // does not do much mor than UTF-8 to Latin1 translation yet, but
959 // that may have to change if cue parsers in burning programs change
960 out = '"' + latin1_txt + '"';
965 } // namespace ARDOUR