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/debug.h"
32 #include "ardour/export_graph_builder.h"
33 #include "ardour/export_timespan.h"
34 #include "ardour/export_channel_configuration.h"
35 #include "ardour/export_status.h"
36 #include "ardour/export_format_specification.h"
37 #include "ardour/export_filename.h"
38 #include "ardour/soundcloud_upload.h"
39 #include "ardour/system_exec.h"
40 #include "pbd/openuri.h"
41 #include "pbd/basename.h"
42 #include "ardour/session_metadata.h"
52 /*** ExportElementFactory ***/
54 ExportElementFactory::ExportElementFactory (Session & session) :
60 ExportElementFactory::~ExportElementFactory ()
66 ExportElementFactory::add_timespan ()
68 return ExportTimespanPtr (new ExportTimespan (session.get_export_status(), session.frame_rate()));
71 ExportChannelConfigPtr
72 ExportElementFactory::add_channel_config ()
74 return ExportChannelConfigPtr (new ExportChannelConfiguration (session));
78 ExportElementFactory::add_format ()
80 return ExportFormatSpecPtr (new ExportFormatSpecification (session));
84 ExportElementFactory::add_format (XMLNode const & state)
86 return ExportFormatSpecPtr (new ExportFormatSpecification (session, state));
90 ExportElementFactory::add_format_copy (ExportFormatSpecPtr other)
92 return ExportFormatSpecPtr (new ExportFormatSpecification (*other));
96 ExportElementFactory::add_filename ()
98 return ExportFilenamePtr (new ExportFilename (session));
102 ExportElementFactory::add_filename_copy (ExportFilenamePtr other)
104 return ExportFilenamePtr (new ExportFilename (*other));
107 /*** ExportHandler ***/
109 ExportHandler::ExportHandler (Session & session)
110 : ExportElementFactory (session)
112 , graph_builder (new ExportGraphBuilder (session))
113 , export_status (session.get_export_status ())
114 , post_processing (false)
120 ExportHandler::~ExportHandler ()
122 graph_builder->cleanup (export_status->aborted () );
125 /** Add an export to the `to-do' list */
127 ExportHandler::add_export_config (ExportTimespanPtr timespan, ExportChannelConfigPtr channel_config,
128 ExportFormatSpecPtr format, ExportFilenamePtr filename,
129 BroadcastInfoPtr broadcast_info)
131 FileSpec spec (channel_config, format, filename, broadcast_info);
132 config_map.insert (make_pair (timespan, spec));
138 ExportHandler::do_export ()
140 /* Count timespans */
142 export_status->init();
143 std::set<ExportTimespanPtr> timespan_set;
144 for (ConfigMap::iterator it = config_map.begin(); it != config_map.end(); ++it) {
145 bool new_timespan = timespan_set.insert (it->first).second;
147 export_status->total_frames += it->first->get_length();
150 export_status->total_timespans = timespan_set.size();
152 if (export_status->total_timespans > 1) {
153 // always include timespan if there's more than one.
154 for (ConfigMap::iterator it = config_map.begin(); it != config_map.end(); ++it) {
155 FileSpec & spec = it->second;
156 spec.filename->include_timespan = true;
162 Glib::Threads::Mutex::Lock l (export_status->lock());
167 ExportHandler::start_timespan ()
169 export_status->timespan++;
171 if (config_map.empty()) {
172 // freewheeling has to be stopped from outside the process cycle
173 export_status->set_running (false);
177 /* finish_timespan pops the config_map entry that has been done, so
178 this is the timespan to do this time
180 current_timespan = config_map.begin()->first;
182 export_status->total_frames_current_timespan = current_timespan->get_length();
183 export_status->timespan_name = current_timespan->name();
184 export_status->processed_frames_current_timespan = 0;
186 /* Register file configurations to graph builder */
188 /* Here's the config_map entries that use this timespan */
189 timespan_bounds = config_map.equal_range (current_timespan);
190 graph_builder->reset ();
191 graph_builder->set_current_timespan (current_timespan);
192 handle_duplicate_format_extensions();
193 bool realtime = current_timespan->realtime ();
194 bool region_export = true;
195 for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second; ++it) {
196 // Filenames can be shared across timespans
197 FileSpec & spec = it->second;
198 spec.filename->set_timespan (it->first);
199 switch (spec.channel_config->region_processing_type ()) {
200 case RegionExportChannelFactory::None:
201 case RegionExportChannelFactory::Processed:
202 region_export = false;
207 graph_builder->add_config (spec, realtime);
210 // ExportDialog::update_realtime_selection does not allow this
211 assert (!region_export || !realtime);
215 post_processing = false;
216 session.ProcessExport.connect_same_thread (process_connection, boost::bind (&ExportHandler::process, this, _1));
217 process_position = current_timespan->get_start();
218 // TODO check if it's a RegionExport.. set flag to skip process_without_events()
219 session.start_audio_export (process_position, realtime, region_export);
223 ExportHandler::handle_duplicate_format_extensions()
225 typedef std::map<std::string, int> ExtCountMap;
228 for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second; ++it) {
229 counts[it->second.format->extension()]++;
232 bool duplicates_found = false;
233 for (ExtCountMap::iterator it = counts.begin(); it != counts.end(); ++it) {
234 if (it->second > 1) { duplicates_found = true; }
237 // Set this always, as the filenames are shared...
238 for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second; ++it) {
239 it->second.filename->include_format_name = duplicates_found;
244 ExportHandler::process (framecnt_t frames)
246 if (!export_status->running ()) {
248 } else if (post_processing) {
249 Glib::Threads::Mutex::Lock l (export_status->lock());
250 if (AudioEngine::instance()->freewheeling ()) {
251 return post_process ();
253 // wait until we're freewheeling
257 Glib::Threads::Mutex::Lock l (export_status->lock());
258 return process_timespan (frames);
263 ExportHandler::process_timespan (framecnt_t frames)
265 export_status->active_job = ExportStatus::Exporting;
266 /* update position */
268 framecnt_t frames_to_read = 0;
269 framepos_t const end = current_timespan->get_end();
271 bool const last_cycle = (process_position + frames >= end);
274 frames_to_read = end - process_position;
275 export_status->stop = true;
277 frames_to_read = frames;
280 process_position += frames_to_read;
281 export_status->processed_frames += frames_to_read;
282 export_status->processed_frames_current_timespan += frames_to_read;
284 /* Do actual processing */
285 int ret = graph_builder->process (frames_to_read, last_cycle);
287 /* Start post-processing/normalizing if necessary */
289 post_processing = graph_builder->need_postprocessing ();
290 if (post_processing) {
291 export_status->total_postprocessing_cycles = graph_builder->get_postprocessing_cycle_count();
292 export_status->current_postprocessing_cycle = 0;
303 ExportHandler::post_process ()
305 if (graph_builder->post_process ()) {
307 export_status->active_job = ExportStatus::Exporting;
309 if (graph_builder->realtime ()) {
310 export_status->active_job = ExportStatus::Encoding;
312 export_status->active_job = ExportStatus::Normalizing;
316 export_status->current_postprocessing_cycle++;
322 ExportHandler::command_output(std::string output, size_t size)
324 std::cerr << "command: " << size << ", " << output << std::endl;
325 info << output << endmsg;
329 ExportHandler::finish_timespan ()
331 graph_builder->get_analysis_results (export_status->result_map);
333 while (config_map.begin() != timespan_bounds.second) {
335 ExportFormatSpecPtr fmt = config_map.begin()->second.format;
336 std::string filename = config_map.begin()->second.filename->get_path(fmt);
337 if (fmt->with_cue()) {
338 export_cd_marker_file (current_timespan, fmt, filename, CDMarkerCUE);
341 if (fmt->with_toc()) {
342 export_cd_marker_file (current_timespan, fmt, filename, CDMarkerTOC);
345 if (fmt->with_mp4chaps()) {
346 export_cd_marker_file (current_timespan, fmt, filename, MP4Chaps);
349 Session::Exported (current_timespan->name(), filename); /* EMIT SIGNAL */
351 /* close file first, otherwise TagLib enounters an ERROR_SHARING_VIOLATION
352 * The process cannot access the file because it is being used.
353 * ditto for post-export and upload.
355 graph_builder->reset ();
358 /* TODO: check Umlauts and encoding in filename.
359 * TagLib eventually calls CreateFileA(),
361 export_status->active_job = ExportStatus::Tagging;
362 AudiofileTagger::tag_file(filename, *SessionMetadata::Metadata());
365 if (!fmt->command().empty()) {
366 SessionMetadata const & metadata (*SessionMetadata::Metadata());
368 #if 0 // would be nicer with C++11 initialiser...
369 std::map<char, std::string> subs {
371 { 'd', Glib::path_get_dirname(filename) + G_DIR_SEPARATOR },
372 { 'b', PBD::basename_nosuffix(filename) },
376 export_status->active_job = ExportStatus::Command;
377 PBD::ScopedConnection command_connection;
378 std::map<char, std::string> subs;
380 std::stringstream track_number;
381 track_number << metadata.track_number ();
382 std::stringstream total_tracks;
383 total_tracks << metadata.total_tracks ();
384 std::stringstream year;
385 year << metadata.year ();
387 subs.insert (std::pair<char, std::string> ('a', metadata.artist ()));
388 subs.insert (std::pair<char, std::string> ('b', PBD::basename_nosuffix (filename)));
389 subs.insert (std::pair<char, std::string> ('c', metadata.copyright ()));
390 subs.insert (std::pair<char, std::string> ('d', Glib::path_get_dirname (filename) + G_DIR_SEPARATOR));
391 subs.insert (std::pair<char, std::string> ('f', filename));
392 subs.insert (std::pair<char, std::string> ('l', metadata.lyricist ()));
393 subs.insert (std::pair<char, std::string> ('n', session.name ()));
394 subs.insert (std::pair<char, std::string> ('s', session.path ()));
395 subs.insert (std::pair<char, std::string> ('o', metadata.conductor ()));
396 subs.insert (std::pair<char, std::string> ('t', metadata.title ()));
397 subs.insert (std::pair<char, std::string> ('z', metadata.organization ()));
398 subs.insert (std::pair<char, std::string> ('A', metadata.album ()));
399 subs.insert (std::pair<char, std::string> ('C', metadata.comment ()));
400 subs.insert (std::pair<char, std::string> ('E', metadata.engineer ()));
401 subs.insert (std::pair<char, std::string> ('G', metadata.genre ()));
402 subs.insert (std::pair<char, std::string> ('L', total_tracks.str ()));
403 subs.insert (std::pair<char, std::string> ('M', metadata.mixer ()));
404 subs.insert (std::pair<char, std::string> ('N', current_timespan->name())); // =?= config_map.begin()->first->name ()
405 subs.insert (std::pair<char, std::string> ('O', metadata.composer ()));
406 subs.insert (std::pair<char, std::string> ('P', metadata.producer ()));
407 subs.insert (std::pair<char, std::string> ('S', metadata.disc_subtitle ()));
408 subs.insert (std::pair<char, std::string> ('T', track_number.str ()));
409 subs.insert (std::pair<char, std::string> ('Y', year.str ()));
410 subs.insert (std::pair<char, std::string> ('Z', metadata.country ()));
412 ARDOUR::SystemExec *se = new ARDOUR::SystemExec(fmt->command(), subs);
413 info << "Post-export command line : {" << se->to_s () << "}" << endmsg;
414 se->ReadStdout.connect_same_thread(command_connection, boost::bind(&ExportHandler::command_output, this, _1, _2));
415 int ret = se->start (2);
417 // successfully started
418 while (se->is_running ()) {
419 // wait for system exec to terminate
423 error << "Post-export command FAILED with Error: " << ret << endmsg;
428 if (fmt->soundcloud_upload()) {
429 SoundcloudUploader *soundcloud_uploader = new SoundcloudUploader;
430 std::string token = soundcloud_uploader->Get_Auth_Token(soundcloud_username, soundcloud_password);
431 DEBUG_TRACE (DEBUG::Soundcloud, string_compose(
432 "uploading %1 - username=%2, password=%3, token=%4",
433 filename, soundcloud_username, soundcloud_password, token) );
434 std::string path = soundcloud_uploader->Upload (
436 PBD::basename_nosuffix(filename), // title
438 soundcloud_make_public,
439 soundcloud_downloadable,
442 if (path.length() != 0) {
443 info << string_compose ( _("File %1 uploaded to %2"), filename, path) << endmsg;
444 if (soundcloud_open_page) {
445 DEBUG_TRACE (DEBUG::Soundcloud, string_compose ("opening %1", path) );
446 open_uri(path.c_str()); // open the soundcloud website to the new file
449 error << _("upload to Soundcloud failed. Perhaps your email or password are incorrect?\n") << endmsg;
451 delete soundcloud_uploader;
453 config_map.erase (config_map.begin());
459 /*** CD Marker stuff ***/
461 struct LocationSortByStart {
462 bool operator() (Location *a, Location *b) {
463 return a->start() < b->start();
468 ExportHandler::export_cd_marker_file (ExportTimespanPtr timespan, ExportFormatSpecPtr file_format,
469 std::string filename, CDMarkerFormat format)
471 string filepath = get_cd_marker_filename(filename, format);
474 void (ExportHandler::*header_func) (CDMarkerStatus &);
475 void (ExportHandler::*track_func) (CDMarkerStatus &);
476 void (ExportHandler::*index_func) (CDMarkerStatus &);
480 header_func = &ExportHandler::write_toc_header;
481 track_func = &ExportHandler::write_track_info_toc;
482 index_func = &ExportHandler::write_index_info_toc;
485 header_func = &ExportHandler::write_cue_header;
486 track_func = &ExportHandler::write_track_info_cue;
487 index_func = &ExportHandler::write_index_info_cue;
490 header_func = &ExportHandler::write_mp4ch_header;
491 track_func = &ExportHandler::write_track_info_mp4ch;
492 index_func = &ExportHandler::write_index_info_mp4ch;
498 CDMarkerStatus status (filepath, timespan, file_format, filename);
500 (this->*header_func) (status);
502 /* Get locations and sort */
504 Locations::LocationList const & locations (session.locations()->list());
505 Locations::LocationList::const_iterator i;
506 Locations::LocationList temp;
508 for (i = locations.begin(); i != locations.end(); ++i) {
509 if ((*i)->start() >= timespan->get_start() && (*i)->end() <= timespan->get_end() && (*i)->is_cd_marker() && !(*i)->is_session_range()) {
515 // TODO One index marker for whole thing
519 LocationSortByStart cmp;
521 Locations::LocationList::const_iterator nexti;
523 /* Start actual marker stuff */
525 framepos_t last_end_time = timespan->get_start();
526 status.track_position = 0;
528 for (i = temp.begin(); i != temp.end(); ++i) {
532 if ((*i)->start() < last_end_time) {
533 if ((*i)->is_mark()) {
534 /* Index within track */
536 status.index_position = (*i)->start() - timespan->get_start();
537 (this->*index_func) (status);
543 /* A track, defined by a cd range marker or a cd location marker outside of a cd range */
545 status.track_position = last_end_time - timespan->get_start();
546 status.track_start_frame = (*i)->start() - timespan->get_start(); // everything before this is the pregap
547 status.track_duration = 0;
549 if ((*i)->is_mark()) {
550 // a mark track location needs to look ahead to the next marker's start to determine length
554 if (nexti != temp.end()) {
555 status.track_duration = (*nexti)->start() - last_end_time;
557 last_end_time = (*nexti)->start();
559 // this was the last marker, use timespan end
560 status.track_duration = timespan->get_end() - last_end_time;
562 last_end_time = timespan->get_end();
566 status.track_duration = (*i)->end() - last_end_time;
568 last_end_time = (*i)->end();
571 (this->*track_func) (status);
574 } catch (std::exception& e) {
575 error << string_compose (_("an error occurred while writing a TOC/CUE file: %1"), e.what()) << endmsg;
576 ::g_unlink (filepath.c_str());
577 } catch (Glib::Exception& e) {
578 error << string_compose (_("an error occurred while writing a TOC/CUE file: %1"), e.what()) << endmsg;
579 ::g_unlink (filepath.c_str());
584 ExportHandler::get_cd_marker_filename(std::string filename, CDMarkerFormat format)
586 /* do not strip file suffix because there may be more than one format,
587 and we do not want the CD marker file from one format to overwrite
588 another (e.g. foo.wav.cue > foo.aiff.cue)
593 return filename + ".toc";
595 return filename + ".cue";
598 unsigned lastdot = filename.find_last_of('.');
599 return filename.substr(0,lastdot) + ".chapters.txt";
602 return filename + ".marker"; // Should not be reached when actually creating a file
607 ExportHandler::write_cue_header (CDMarkerStatus & status)
609 string title = status.timespan->name().compare ("Session") ? status.timespan->name() : (string) session.name();
612 string barcode = SessionMetadata::Metadata()->barcode();
613 string album_artist = SessionMetadata::Metadata()->album_artist();
614 string album_title = SessionMetadata::Metadata()->album();
616 status.out << "REM Cue file generated by " << PROGRAM_NAME << endl;
619 status.out << "CATALOG " << barcode << endl;
621 if (album_artist != "")
622 status.out << "PERFORMER " << cue_escape_cdtext (album_artist) << endl;
624 if (album_title != "")
627 status.out << "TITLE " << cue_escape_cdtext (title) << endl;
629 /* The original cue sheet spec mentions five file types
631 BINARY = "header-less" audio (44.1 kHz, 16 Bit, little endian),
632 MOTOROLA = "header-less" audio (44.1 kHz, 16 Bit, big endian),
635 We try to use these file types whenever appropriate and
636 default to our own names otherwise.
638 status.out << "FILE \"" << Glib::path_get_basename(status.filename) << "\" ";
639 if (!status.format->format_name().compare ("WAV") || !status.format->format_name().compare ("BWF")) {
640 status.out << "WAVE";
641 } else if (status.format->format_id() == ExportFormatBase::F_RAW &&
642 status.format->sample_format() == ExportFormatBase::SF_16 &&
643 status.format->sample_rate() == ExportFormatBase::SR_44_1) {
644 // Format is RAW 16bit 44.1kHz
645 if (status.format->endianness() == ExportFormatBase::E_Little) {
646 status.out << "BINARY";
648 status.out << "MOTOROLA";
651 // no special case for AIFF format it's name is already "AIFF"
652 status.out << status.format->format_name();
658 ExportHandler::write_toc_header (CDMarkerStatus & status)
660 string title = status.timespan->name().compare ("Session") ? status.timespan->name() : (string) session.name();
663 string barcode = SessionMetadata::Metadata()->barcode();
664 string album_artist = SessionMetadata::Metadata()->album_artist();
665 string album_title = SessionMetadata::Metadata()->album();
668 status.out << "CATALOG \"" << barcode << "\"" << endl;
670 if (album_title != "")
673 status.out << "CD_DA" << endl;
674 status.out << "CD_TEXT {" << endl << " LANGUAGE_MAP {" << endl << " 0 : EN" << endl << " }" << endl;
675 status.out << " LANGUAGE 0 {" << endl << " TITLE " << toc_escape_cdtext (title) << endl ;
676 status.out << " PERFORMER " << toc_escape_cdtext (album_artist) << endl;
677 status.out << " }" << endl << "}" << endl;
681 ExportHandler::write_mp4ch_header (CDMarkerStatus & status)
683 status.out << "00:00:00.000 Intro" << endl;
687 ExportHandler::write_track_info_cue (CDMarkerStatus & status)
691 snprintf (buf, sizeof(buf), " TRACK %02d AUDIO", status.track_number);
692 status.out << buf << endl;
694 status.out << " FLAGS" ;
695 if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) {
696 status.out << " SCMS ";
698 status.out << " DCP ";
701 if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) {
702 status.out << " PRE";
706 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
707 status.out << " ISRC " << status.marker->cd_info["isrc"] << endl;
710 if (status.marker->name() != "") {
711 status.out << " TITLE " << cue_escape_cdtext (status.marker->name()) << endl;
714 if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
715 status.out << " PERFORMER " << cue_escape_cdtext (status.marker->cd_info["performer"]) << endl;
718 if (status.marker->cd_info.find("composer") != status.marker->cd_info.end()) {
719 status.out << " SONGWRITER " << cue_escape_cdtext (status.marker->cd_info["composer"]) << endl;
722 if (status.track_position != status.track_start_frame) {
723 frames_to_cd_frames_string (buf, status.track_position);
724 status.out << " INDEX 00" << buf << endl;
727 frames_to_cd_frames_string (buf, status.track_start_frame);
728 status.out << " INDEX 01" << buf << endl;
730 status.index_number = 2;
731 status.track_number++;
735 ExportHandler::write_track_info_toc (CDMarkerStatus & status)
739 status.out << endl << "TRACK AUDIO" << endl;
741 if (status.marker->cd_info.find("scms") != status.marker->cd_info.end()) {
744 status.out << "COPY" << endl;
746 if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end()) {
747 status.out << "PRE_EMPHASIS" << endl;
749 status.out << "NO PRE_EMPHASIS" << endl;
752 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
753 status.out << "ISRC \"" << status.marker->cd_info["isrc"] << "\"" << endl;
756 status.out << "CD_TEXT {" << endl << " LANGUAGE 0 {" << endl;
757 status.out << " TITLE " << toc_escape_cdtext (status.marker->name()) << endl;
759 status.out << " PERFORMER ";
760 if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
761 status.out << toc_escape_cdtext (status.marker->cd_info["performer"]) << endl;
763 status.out << "\"\"" << endl;
766 if (status.marker->cd_info.find("composer") != status.marker->cd_info.end()) {
767 status.out << " SONGWRITER " << toc_escape_cdtext (status.marker->cd_info["composer"]) << endl;
770 if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
771 status.out << " ISRC \"";
772 status.out << status.marker->cd_info["isrc"].substr(0,2) << "-";
773 status.out << status.marker->cd_info["isrc"].substr(2,3) << "-";
774 status.out << status.marker->cd_info["isrc"].substr(5,2) << "-";
775 status.out << status.marker->cd_info["isrc"].substr(7,5) << "\"" << endl;
778 status.out << " }" << endl << "}" << endl;
780 frames_to_cd_frames_string (buf, status.track_position);
781 status.out << "FILE " << toc_escape_filename (status.filename) << ' ' << buf;
783 frames_to_cd_frames_string (buf, status.track_duration);
784 status.out << buf << endl;
786 frames_to_cd_frames_string (buf, status.track_start_frame - status.track_position);
787 status.out << "START" << buf << endl;
790 void ExportHandler::write_track_info_mp4ch (CDMarkerStatus & status)
794 frames_to_chapter_marks_string(buf, status.track_start_frame);
795 status.out << buf << " " << status.marker->name() << endl;
799 ExportHandler::write_index_info_cue (CDMarkerStatus & status)
803 snprintf (buf, sizeof(buf), " INDEX %02d", cue_indexnum);
805 frames_to_cd_frames_string (buf, status.index_position);
806 status.out << buf << endl;
812 ExportHandler::write_index_info_toc (CDMarkerStatus & status)
816 frames_to_cd_frames_string (buf, status.index_position - status.track_position);
817 status.out << "INDEX" << buf << endl;
821 ExportHandler::write_index_info_mp4ch (CDMarkerStatus & status)
826 ExportHandler::frames_to_cd_frames_string (char* buf, framepos_t when)
828 framecnt_t remainder;
829 framecnt_t fr = session.nominal_frame_rate();
830 int mins, secs, frames;
832 mins = when / (60 * fr);
833 remainder = when - (mins * 60 * fr);
834 secs = remainder / fr;
835 remainder -= secs * fr;
836 frames = remainder / (fr / 75);
837 sprintf (buf, " %02d:%02d:%02d", mins, secs, frames);
841 ExportHandler::frames_to_chapter_marks_string (char* buf, framepos_t when)
843 framecnt_t remainder;
844 framecnt_t fr = session.nominal_frame_rate();
845 int hours, mins, secs, msecs;
847 hours = when / (3600 * fr);
848 remainder = when - (hours * 3600 * fr);
849 mins = remainder / (60 * fr);
850 remainder -= mins * 60 * fr;
851 secs = remainder / fr;
852 remainder -= secs * fr;
853 msecs = (remainder * 1000) / fr;
854 sprintf (buf, "%02d:%02d:%02d.%03d", hours, mins, secs, msecs);
858 ExportHandler::toc_escape_cdtext (const std::string& txt)
860 Glib::ustring check (txt);
862 std::string latin1_txt;
866 latin1_txt = Glib::convert_with_fallback (txt, "ISO-8859-1", "UTF-8", "_");
867 } catch (Glib::ConvertError& err) {
868 throw Glib::ConvertError (err.code(), string_compose (_("Cannot convert %1 to Latin-1 text"), txt));
873 for (std::string::const_iterator c = latin1_txt.begin(); c != latin1_txt.end(); ++c) {
877 } else if ((*c) == '\\') {
879 } else if (isprint (*c)) {
882 snprintf (buf, sizeof (buf), "\\%03o", (int) (unsigned char) *c);
893 ExportHandler::toc_escape_filename (const std::string& txt)
899 // We iterate byte-wise not character-wise over a UTF-8 string here,
900 // because we only want to translate backslashes and double quotes
901 for (std::string::const_iterator c = txt.begin(); c != txt.end(); ++c) {
905 } else if (*c == '\\') {
918 ExportHandler::cue_escape_cdtext (const std::string& txt)
920 std::string latin1_txt;
924 latin1_txt = Glib::convert (txt, "ISO-8859-1", "UTF-8");
925 } catch (Glib::ConvertError& err) {
926 throw Glib::ConvertError (err.code(), string_compose (_("Cannot convert %1 to Latin-1 text"), txt));
929 // does not do much mor than UTF-8 to Latin1 translation yet, but
930 // that may have to change if cue parsers in burning programs change
931 out = '"' + latin1_txt + '"';
936 } // namespace ARDOUR