remove debug output
[ardour.git] / libs / ardour / export_handler.cc
1 /*
2     Copyright (C) 2008-2009 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/gstdio_compat.h"
24 #include <glibmm.h>
25 #include <glibmm/convert.h>
26
27 #include "pbd/convert.h"
28
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"
44
45 #include "pbd/i18n.h"
46
47 using namespace std;
48 using namespace PBD;
49
50 namespace ARDOUR
51 {
52
53 /*** ExportElementFactory ***/
54
55 ExportElementFactory::ExportElementFactory (Session & session) :
56   session (session)
57 {
58
59 }
60
61 ExportElementFactory::~ExportElementFactory ()
62 {
63
64 }
65
66 ExportTimespanPtr
67 ExportElementFactory::add_timespan ()
68 {
69         return ExportTimespanPtr (new ExportTimespan (session.get_export_status(), session.frame_rate()));
70 }
71
72 ExportChannelConfigPtr
73 ExportElementFactory::add_channel_config ()
74 {
75         return ExportChannelConfigPtr (new ExportChannelConfiguration (session));
76 }
77
78 ExportFormatSpecPtr
79 ExportElementFactory::add_format ()
80 {
81         return ExportFormatSpecPtr (new ExportFormatSpecification (session));
82 }
83
84 ExportFormatSpecPtr
85 ExportElementFactory::add_format (XMLNode const & state)
86 {
87         return ExportFormatSpecPtr (new ExportFormatSpecification (session, state));
88 }
89
90 ExportFormatSpecPtr
91 ExportElementFactory::add_format_copy (ExportFormatSpecPtr other)
92 {
93         return ExportFormatSpecPtr (new ExportFormatSpecification (*other));
94 }
95
96 ExportFilenamePtr
97 ExportElementFactory::add_filename ()
98 {
99         return ExportFilenamePtr (new ExportFilename (session));
100 }
101
102 ExportFilenamePtr
103 ExportElementFactory::add_filename_copy (ExportFilenamePtr other)
104 {
105         return ExportFilenamePtr (new ExportFilename (*other));
106 }
107
108 /*** ExportHandler ***/
109
110 ExportHandler::ExportHandler (Session & session)
111   : ExportElementFactory (session)
112   , session (session)
113   , graph_builder (new ExportGraphBuilder (session))
114   , export_status (session.get_export_status ())
115   , post_processing (false)
116   , cue_tracknum (0)
117   , cue_indexnum (0)
118 {
119 }
120
121 ExportHandler::~ExportHandler ()
122 {
123         graph_builder->cleanup (export_status->aborted () );
124 }
125
126 /** Add an export to the `to-do' list */
127 bool
128 ExportHandler::add_export_config (ExportTimespanPtr timespan, ExportChannelConfigPtr channel_config,
129                                   ExportFormatSpecPtr format, ExportFilenamePtr filename,
130                                   BroadcastInfoPtr broadcast_info)
131 {
132         FileSpec spec (channel_config, format, filename, broadcast_info);
133         config_map.insert (make_pair (timespan, spec));
134
135         return true;
136 }
137
138 void
139 ExportHandler::do_export ()
140 {
141         /* Count timespans */
142
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;
147                 if (new_timespan) {
148                         export_status->total_frames += it->first->get_length();
149                 }
150         }
151         export_status->total_timespans = timespan_set.size();
152
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;
158                 }
159         }
160
161         /* Start export */
162
163         Glib::Threads::Mutex::Lock l (export_status->lock());
164         start_timespan ();
165 }
166
167 void
168 ExportHandler::start_timespan ()
169 {
170         export_status->timespan++;
171
172         if (config_map.empty()) {
173                 // freewheeling has to be stopped from outside the process cycle
174                 export_status->set_running (false);
175                 return;
176         }
177
178         /* finish_timespan pops the config_map entry that has been done, so
179            this is the timespan to do this time
180         */
181         current_timespan = config_map.begin()->first;
182
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;
186
187         /* Register file configurations to graph builder */
188
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;
205                                 break;
206                         default:
207                                 break;
208                 }
209 #if 1 // hack alert -- align master bus, compensate master latency
210
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.
214                  */
215                 boost::shared_ptr<Route> master_bus = session.master_out ();
216                 if (master_bus) {
217                         const PortSet& ps = master_bus->output ()->ports();
218
219                         const ExportChannelConfiguration::ChannelList& channels = spec.channel_config->get_channels ();
220                         for (ExportChannelConfiguration::ChannelList::const_iterator it = channels.begin(); it != channels.end(); ++it) {
221
222                                 boost::shared_ptr <PortExportChannel> pep = boost::dynamic_pointer_cast<PortExportChannel> (*it);
223                                 if (!pep) {
224                                         continue;
225                                 }
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;
231                                         }
232                                 }
233                         }
234                 }
235 #endif
236                 graph_builder->add_config (spec, realtime);
237         }
238
239         // ExportDialog::update_realtime_selection does not allow this
240         assert (!region_export || !realtime);
241
242         /* start export */
243
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);
249 }
250
251 void
252 ExportHandler::handle_duplicate_format_extensions()
253 {
254         typedef std::map<std::string, int> ExtCountMap;
255
256         ExtCountMap counts;
257         for (ConfigMap::iterator it = timespan_bounds.first; it != timespan_bounds.second; ++it) {
258                 counts[it->second.format->extension()]++;
259         }
260
261         bool duplicates_found = false;
262         for (ExtCountMap::iterator it = counts.begin(); it != counts.end(); ++it) {
263                 if (it->second > 1) { duplicates_found = true; }
264         }
265
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;
269         }
270 }
271
272 int
273 ExportHandler::process (framecnt_t frames)
274 {
275         if (!export_status->running ()) {
276                 return 0;
277         } else if (post_processing) {
278                 Glib::Threads::Mutex::Lock l (export_status->lock());
279                 if (AudioEngine::instance()->freewheeling ()) {
280                         return post_process ();
281                 } else {
282                         // wait until we're freewheeling
283                         return 0;
284                 }
285         } else {
286                 Glib::Threads::Mutex::Lock l (export_status->lock());
287                 return process_timespan (frames);
288         }
289 }
290
291 int
292 ExportHandler::process_timespan (framecnt_t frames)
293 {
294         export_status->active_job = ExportStatus::Exporting;
295         /* update position */
296
297         framecnt_t frames_to_read = 0;
298         framepos_t const end = current_timespan->get_end();
299
300         bool const last_cycle = (process_position + frames >= end);
301
302         if (last_cycle) {
303                 frames_to_read = end - process_position;
304                 export_status->stop = true;
305         } else {
306                 frames_to_read = frames;
307         }
308
309         process_position += frames_to_read;
310         export_status->processed_frames += frames_to_read;
311         export_status->processed_frames_current_timespan += frames_to_read;
312
313         /* Do actual processing */
314         int ret = graph_builder->process (frames_to_read, last_cycle);
315
316         /* Start post-processing/normalizing if necessary */
317         if (last_cycle) {
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;
322                 } else {
323                         finish_timespan ();
324                         return 0;
325                 }
326         }
327
328         return ret;
329 }
330
331 int
332 ExportHandler::post_process ()
333 {
334         if (graph_builder->post_process ()) {
335                 finish_timespan ();
336                 export_status->active_job = ExportStatus::Exporting;
337         } else {
338                 if (graph_builder->realtime ()) {
339                         export_status->active_job = ExportStatus::Encoding;
340                 } else {
341                         export_status->active_job = ExportStatus::Normalizing;
342                 }
343         }
344
345         export_status->current_postprocessing_cycle++;
346
347         return 0;
348 }
349
350 void
351 ExportHandler::command_output(std::string output, size_t size)
352 {
353         std::cerr << "command: " << size << ", " << output << std::endl;
354         info << output << endmsg;
355 }
356
357 void
358 ExportHandler::finish_timespan ()
359 {
360         graph_builder->get_analysis_results (export_status->result_map);
361
362         while (config_map.begin() != timespan_bounds.second) {
363
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);
368                 }
369
370                 if (fmt->with_toc()) {
371                         export_cd_marker_file (current_timespan, fmt, filename, CDMarkerTOC);
372                 }
373
374                 if (fmt->with_mp4chaps()) {
375                         export_cd_marker_file (current_timespan, fmt, filename, MP4Chaps);
376                 }
377
378                 Session::Exported (current_timespan->name(), filename); /* EMIT SIGNAL */
379
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.
383                  */
384                 graph_builder->reset ();
385
386                 if (fmt->tag()) {
387                         /* TODO: check Umlauts and encoding in filename.
388                          * TagLib eventually calls CreateFileA(),
389                          */
390                         export_status->active_job = ExportStatus::Tagging;
391                         AudiofileTagger::tag_file(filename, *SessionMetadata::Metadata());
392                 }
393
394                 if (!fmt->command().empty()) {
395                         SessionMetadata const & metadata (*SessionMetadata::Metadata());
396
397 #if 0   // would be nicer with C++11 initialiser...
398                         std::map<char, std::string> subs {
399                                 { 'f', filename },
400                                 { 'd', Glib::path_get_dirname(filename)  + G_DIR_SEPARATOR },
401                                 { 'b', PBD::basename_nosuffix(filename) },
402                                 ...
403                         };
404 #endif
405                         export_status->active_job = ExportStatus::Command;
406                         PBD::ScopedConnection command_connection;
407                         std::map<char, std::string> subs;
408
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 ();
415
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 ()));
440
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);
445                         if (ret == 0) {
446                                 // successfully started
447                                 while (se->is_running ()) {
448                                         // wait for system exec to terminate
449                                         Glib::usleep (1000);
450                                 }
451                         } else {
452                                 error << "Post-export command FAILED with Error: " << ret << endmsg;
453                         }
454                         delete (se);
455                 }
456
457                 // XXX THIS IS IN REALTIME CONTEXT, CALLED FROM
458                 // AudioEngine::process_callback()
459                 // freewheeling, yes, but still uploading here is NOT
460                 // a good idea.
461                 //
462                 // even less so, since SoundcloudProgress is using
463                 // connect_same_thread() - GUI updates from the RT thread
464                 // will cause crashes. http://pastebin.com/UJKYNGHR
465                 if (fmt->soundcloud_upload()) {
466                         SoundcloudUploader *soundcloud_uploader = new SoundcloudUploader;
467                         std::string token = soundcloud_uploader->Get_Auth_Token(soundcloud_username, soundcloud_password);
468                         DEBUG_TRACE (DEBUG::Soundcloud, string_compose(
469                                                 "uploading %1 - username=%2, password=%3, token=%4",
470                                                 filename, soundcloud_username, soundcloud_password, token) );
471                         std::string path = soundcloud_uploader->Upload (
472                                         filename,
473                                         PBD::basename_nosuffix(filename), // title
474                                         token,
475                                         soundcloud_make_public,
476                                         soundcloud_downloadable,
477                                         this);
478
479                         if (path.length() != 0) {
480                                 info << string_compose ( _("File %1 uploaded to %2"), filename, path) << endmsg;
481                                 if (soundcloud_open_page) {
482                                         DEBUG_TRACE (DEBUG::Soundcloud, string_compose ("opening %1", path) );
483                                         open_uri(path.c_str());  // open the soundcloud website to the new file
484                                 }
485                         } else {
486                                 error << _("upload to Soundcloud failed. Perhaps your email or password are incorrect?\n") << endmsg;
487                         }
488                         delete soundcloud_uploader;
489                 }
490                 config_map.erase (config_map.begin());
491         }
492
493         start_timespan ();
494 }
495
496 /*** CD Marker stuff ***/
497
498 struct LocationSortByStart {
499     bool operator() (Location *a, Location *b) {
500             return a->start() < b->start();
501     }
502 };
503
504 void
505 ExportHandler::export_cd_marker_file (ExportTimespanPtr timespan, ExportFormatSpecPtr file_format,
506                                       std::string filename, CDMarkerFormat format)
507 {
508         string filepath = get_cd_marker_filename(filename, format);
509
510         try {
511                 void (ExportHandler::*header_func) (CDMarkerStatus &);
512                 void (ExportHandler::*track_func) (CDMarkerStatus &);
513                 void (ExportHandler::*index_func) (CDMarkerStatus &);
514
515                 switch (format) {
516                 case CDMarkerTOC:
517                         header_func = &ExportHandler::write_toc_header;
518                         track_func = &ExportHandler::write_track_info_toc;
519                         index_func = &ExportHandler::write_index_info_toc;
520                         break;
521                 case CDMarkerCUE:
522                         header_func = &ExportHandler::write_cue_header;
523                         track_func = &ExportHandler::write_track_info_cue;
524                         index_func = &ExportHandler::write_index_info_cue;
525                         break;
526                 case MP4Chaps:
527                         header_func = &ExportHandler::write_mp4ch_header;
528                         track_func = &ExportHandler::write_track_info_mp4ch;
529                         index_func = &ExportHandler::write_index_info_mp4ch;
530                         break;
531                 default:
532                         return;
533                 }
534
535                 CDMarkerStatus status (filepath, timespan, file_format, filename);
536
537                 (this->*header_func) (status);
538
539                 /* Get locations and sort */
540
541                 Locations::LocationList const & locations (session.locations()->list());
542                 Locations::LocationList::const_iterator i;
543                 Locations::LocationList temp;
544
545                 for (i = locations.begin(); i != locations.end(); ++i) {
546                         if ((*i)->start() >= timespan->get_start() && (*i)->end() <= timespan->get_end() && (*i)->is_cd_marker() && !(*i)->is_session_range()) {
547                                 temp.push_back (*i);
548                         }
549                 }
550
551                 if (temp.empty()) {
552                         // TODO One index marker for whole thing
553                         return;
554                 }
555
556                 LocationSortByStart cmp;
557                 temp.sort (cmp);
558                 Locations::LocationList::const_iterator nexti;
559
560                 /* Start actual marker stuff */
561
562                 framepos_t last_end_time = timespan->get_start();
563                 status.track_position = 0;
564
565                 for (i = temp.begin(); i != temp.end(); ++i) {
566
567                         status.marker = *i;
568
569                         if ((*i)->start() < last_end_time) {
570                                 if ((*i)->is_mark()) {
571                                         /* Index within track */
572
573                                         status.index_position = (*i)->start() - timespan->get_start();
574                                         (this->*index_func) (status);
575                                 }
576
577                                 continue;
578                         }
579
580                         /* A track, defined by a cd range marker or a cd location marker outside of a cd range */
581
582                         status.track_position = last_end_time - timespan->get_start();
583                         status.track_start_frame = (*i)->start() - timespan->get_start();  // everything before this is the pregap
584                         status.track_duration = 0;
585
586                         if ((*i)->is_mark()) {
587                                 // a mark track location needs to look ahead to the next marker's start to determine length
588                                 nexti = i;
589                                 ++nexti;
590
591                                 if (nexti != temp.end()) {
592                                         status.track_duration = (*nexti)->start() - last_end_time;
593
594                                         last_end_time = (*nexti)->start();
595                                 } else {
596                                         // this was the last marker, use timespan end
597                                         status.track_duration = timespan->get_end() - last_end_time;
598
599                                         last_end_time = timespan->get_end();
600                                 }
601                         } else {
602                                 // range
603                                 status.track_duration = (*i)->end() - last_end_time;
604
605                                 last_end_time = (*i)->end();
606                         }
607
608                         (this->*track_func) (status);
609                 }
610
611         } catch (std::exception& e) {
612                 error << string_compose (_("an error occurred while writing a TOC/CUE file: %1"), e.what()) << endmsg;
613                 ::g_unlink (filepath.c_str());
614         } catch (Glib::Exception& e) {
615                 error << string_compose (_("an error occurred while writing a TOC/CUE file: %1"), e.what()) << endmsg;
616                 ::g_unlink (filepath.c_str());
617         }
618 }
619
620 string
621 ExportHandler::get_cd_marker_filename(std::string filename, CDMarkerFormat format)
622 {
623         /* do not strip file suffix because there may be more than one format,
624            and we do not want the CD marker file from one format to overwrite
625            another (e.g. foo.wav.cue > foo.aiff.cue)
626         */
627
628         switch (format) {
629         case CDMarkerTOC:
630                 return filename + ".toc";
631         case CDMarkerCUE:
632                 return filename + ".cue";
633         case MP4Chaps:
634         {
635                 unsigned lastdot = filename.find_last_of('.');
636                 return filename.substr(0,lastdot) + ".chapters.txt";
637         }
638         default:
639                 return filename + ".marker"; // Should not be reached when actually creating a file
640         }
641 }
642
643 void
644 ExportHandler::write_cue_header (CDMarkerStatus & status)
645 {
646         string title = status.timespan->name().compare ("Session") ? status.timespan->name() : (string) session.name();
647
648         // Album metadata
649         string barcode      = SessionMetadata::Metadata()->barcode();
650         string album_artist = SessionMetadata::Metadata()->album_artist();
651         string album_title  = SessionMetadata::Metadata()->album();
652
653         status.out << "REM Cue file generated by " << PROGRAM_NAME << endl;
654
655         if (barcode != "")
656                 status.out << "CATALOG " << barcode << endl;
657
658         if (album_artist != "")
659                 status.out << "PERFORMER " << cue_escape_cdtext (album_artist) << endl;
660
661         if (album_title != "")
662                 title = album_title;
663
664         status.out << "TITLE " << cue_escape_cdtext (title) << endl;
665
666         /*  The original cue sheet spec mentions five file types
667                 WAVE, AIFF,
668                 BINARY   = "header-less" audio (44.1 kHz, 16 Bit, little endian),
669                 MOTOROLA = "header-less" audio (44.1 kHz, 16 Bit, big endian),
670                 and MP3
671
672                 We try to use these file types whenever appropriate and
673                 default to our own names otherwise.
674         */
675         status.out << "FILE \"" << Glib::path_get_basename(status.filename) << "\" ";
676         if (!status.format->format_name().compare ("WAV")  || !status.format->format_name().compare ("BWF")) {
677                 status.out  << "WAVE";
678         } else if (status.format->format_id() == ExportFormatBase::F_RAW &&
679                    status.format->sample_format() == ExportFormatBase::SF_16 &&
680                    status.format->sample_rate() == ExportFormatBase::SR_44_1) {
681                 // Format is RAW 16bit 44.1kHz
682                 if (status.format->endianness() == ExportFormatBase::E_Little) {
683                         status.out << "BINARY";
684                 } else {
685                         status.out << "MOTOROLA";
686                 }
687         } else {
688                 // no special case for AIFF format it's name is already "AIFF"
689                 status.out << status.format->format_name();
690         }
691         status.out << endl;
692 }
693
694 void
695 ExportHandler::write_toc_header (CDMarkerStatus & status)
696 {
697         string title = status.timespan->name().compare ("Session") ? status.timespan->name() : (string) session.name();
698
699         // Album metadata
700         string barcode      = SessionMetadata::Metadata()->barcode();
701         string album_artist = SessionMetadata::Metadata()->album_artist();
702         string album_title  = SessionMetadata::Metadata()->album();
703
704         if (barcode != "")
705                 status.out << "CATALOG \"" << barcode << "\"" << endl;
706
707         if (album_title != "")
708                 title = album_title;
709
710         status.out << "CD_DA" << endl;
711         status.out << "CD_TEXT {" << endl << "  LANGUAGE_MAP {" << endl << "    0 : EN" << endl << "  }" << endl;
712         status.out << "  LANGUAGE 0 {" << endl << "    TITLE " << toc_escape_cdtext (title) << endl ;
713         status.out << "    PERFORMER " << toc_escape_cdtext (album_artist) << endl;
714         status.out << "  }" << endl << "}" << endl;
715 }
716
717 void
718 ExportHandler::write_mp4ch_header (CDMarkerStatus & status)
719 {
720         status.out << "00:00:00.000 Intro" << endl;
721 }
722
723 void
724 ExportHandler::write_track_info_cue (CDMarkerStatus & status)
725 {
726         gchar buf[18];
727
728         snprintf (buf, sizeof(buf), "  TRACK %02d AUDIO", status.track_number);
729         status.out << buf << endl;
730
731         status.out << "    FLAGS" ;
732         if (status.marker->cd_info.find("scms") != status.marker->cd_info.end())  {
733                 status.out << " SCMS ";
734         } else {
735                 status.out << " DCP ";
736         }
737
738         if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end())  {
739                 status.out << " PRE";
740         }
741         status.out << endl;
742
743         if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end())  {
744                 status.out << "    ISRC " << status.marker->cd_info["isrc"] << endl;
745         }
746
747         if (status.marker->name() != "") {
748                 status.out << "    TITLE " << cue_escape_cdtext (status.marker->name()) << endl;
749         }
750
751         if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
752                 status.out <<  "    PERFORMER " << cue_escape_cdtext (status.marker->cd_info["performer"]) << endl;
753         }
754
755         if (status.marker->cd_info.find("composer") != status.marker->cd_info.end()) {
756                 status.out << "    SONGWRITER " << cue_escape_cdtext (status.marker->cd_info["composer"]) << endl;
757         }
758
759         if (status.track_position != status.track_start_frame) {
760                 frames_to_cd_frames_string (buf, status.track_position);
761                 status.out << "    INDEX 00" << buf << endl;
762         }
763
764         frames_to_cd_frames_string (buf, status.track_start_frame);
765         status.out << "    INDEX 01" << buf << endl;
766
767         status.index_number = 2;
768         status.track_number++;
769 }
770
771 void
772 ExportHandler::write_track_info_toc (CDMarkerStatus & status)
773 {
774         gchar buf[18];
775
776         status.out << endl << "TRACK AUDIO" << endl;
777
778         if (status.marker->cd_info.find("scms") != status.marker->cd_info.end())  {
779                 status.out << "NO ";
780         }
781         status.out << "COPY" << endl;
782
783         if (status.marker->cd_info.find("preemph") != status.marker->cd_info.end())  {
784                 status.out << "PRE_EMPHASIS" << endl;
785         } else {
786                 status.out << "NO PRE_EMPHASIS" << endl;
787         }
788
789         if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end())  {
790                 status.out << "ISRC \"" << status.marker->cd_info["isrc"] << "\"" << endl;
791         }
792
793         status.out << "CD_TEXT {" << endl << "  LANGUAGE 0 {" << endl;
794         status.out << "     TITLE " << toc_escape_cdtext (status.marker->name()) << endl;
795
796         status.out << "     PERFORMER ";
797         if (status.marker->cd_info.find("performer") != status.marker->cd_info.end()) {
798                 status.out << toc_escape_cdtext (status.marker->cd_info["performer"]) << endl;
799         } else {
800                 status.out << "\"\"" << endl;
801         }
802
803         if (status.marker->cd_info.find("composer") != status.marker->cd_info.end()) {
804                 status.out  << "     SONGWRITER " << toc_escape_cdtext (status.marker->cd_info["composer"]) << endl;
805         }
806
807         if (status.marker->cd_info.find("isrc") != status.marker->cd_info.end()) {
808                 status.out  << "     ISRC \"";
809                 status.out << status.marker->cd_info["isrc"].substr(0,2) << "-";
810                 status.out << status.marker->cd_info["isrc"].substr(2,3) << "-";
811                 status.out << status.marker->cd_info["isrc"].substr(5,2) << "-";
812                 status.out << status.marker->cd_info["isrc"].substr(7,5) << "\"" << endl;
813         }
814
815         status.out << "  }" << endl << "}" << endl;
816
817         frames_to_cd_frames_string (buf, status.track_position);
818         status.out << "FILE " << toc_escape_filename (status.filename) << ' ' << buf;
819
820         frames_to_cd_frames_string (buf, status.track_duration);
821         status.out << buf << endl;
822
823         frames_to_cd_frames_string (buf, status.track_start_frame - status.track_position);
824         status.out << "START" << buf << endl;
825 }
826
827 void ExportHandler::write_track_info_mp4ch (CDMarkerStatus & status)
828 {
829         gchar buf[18];
830
831         frames_to_chapter_marks_string(buf, status.track_start_frame);
832         status.out << buf << " " << status.marker->name() << endl;
833 }
834
835 void
836 ExportHandler::write_index_info_cue (CDMarkerStatus & status)
837 {
838         gchar buf[18];
839
840         snprintf (buf, sizeof(buf), "    INDEX %02d", cue_indexnum);
841         status.out << buf;
842         frames_to_cd_frames_string (buf, status.index_position);
843         status.out << buf << endl;
844
845         cue_indexnum++;
846 }
847
848 void
849 ExportHandler::write_index_info_toc (CDMarkerStatus & status)
850 {
851         gchar buf[18];
852
853         frames_to_cd_frames_string (buf, status.index_position - status.track_position);
854         status.out << "INDEX" << buf << endl;
855 }
856
857 void
858 ExportHandler::write_index_info_mp4ch (CDMarkerStatus & status)
859 {
860 }
861
862 void
863 ExportHandler::frames_to_cd_frames_string (char* buf, framepos_t when)
864 {
865         framecnt_t remainder;
866         framecnt_t fr = session.nominal_frame_rate();
867         int mins, secs, frames;
868
869         mins = when / (60 * fr);
870         remainder = when - (mins * 60 * fr);
871         secs = remainder / fr;
872         remainder -= secs * fr;
873         frames = remainder / (fr / 75);
874         sprintf (buf, " %02d:%02d:%02d", mins, secs, frames);
875 }
876
877 void
878 ExportHandler::frames_to_chapter_marks_string (char* buf, framepos_t when)
879 {
880         framecnt_t remainder;
881         framecnt_t fr = session.nominal_frame_rate();
882         int hours, mins, secs, msecs;
883
884         hours = when / (3600 * fr);
885         remainder = when - (hours * 3600 * fr);
886         mins = remainder / (60 * fr);
887         remainder -= mins * 60 * fr;
888         secs = remainder / fr;
889         remainder -= secs * fr;
890         msecs = (remainder * 1000) / fr;
891         sprintf (buf, "%02d:%02d:%02d.%03d", hours, mins, secs, msecs);
892 }
893
894 std::string
895 ExportHandler::toc_escape_cdtext (const std::string& txt)
896 {
897         Glib::ustring check (txt);
898         std::string out;
899         std::string latin1_txt;
900         char buf[5];
901
902         try {
903                 latin1_txt = Glib::convert_with_fallback (txt, "ISO-8859-1", "UTF-8", "_");
904         } catch (Glib::ConvertError& err) {
905                 throw Glib::ConvertError (err.code(), string_compose (_("Cannot convert %1 to Latin-1 text"), txt));
906         }
907
908         out = '"';
909
910         for (std::string::const_iterator c = latin1_txt.begin(); c != latin1_txt.end(); ++c) {
911
912                 if ((*c) == '"') {
913                         out += "\\\"";
914                 } else if ((*c) == '\\') {
915                         out += "\\134";
916                 } else if (isprint (*c)) {
917                         out += *c;
918                 } else {
919                         snprintf (buf, sizeof (buf), "\\%03o", (int) (unsigned char) *c);
920                         out += buf;
921                 }
922         }
923
924         out += '"';
925
926         return out;
927 }
928
929 std::string
930 ExportHandler::toc_escape_filename (const std::string& txt)
931 {
932         std::string out;
933
934         out = '"';
935
936         // We iterate byte-wise not character-wise over a UTF-8 string here,
937         // because we only want to translate backslashes and double quotes
938         for (std::string::const_iterator c = txt.begin(); c != txt.end(); ++c) {
939
940                 if (*c == '"') {
941                         out += "\\\"";
942                 } else if (*c == '\\') {
943                         out += "\\134";
944                 } else {
945                         out += *c;
946                 }
947         }
948
949         out += '"';
950
951         return out;
952 }
953
954 std::string
955 ExportHandler::cue_escape_cdtext (const std::string& txt)
956 {
957         std::string latin1_txt;
958         std::string out;
959
960         try {
961                 latin1_txt = Glib::convert (txt, "ISO-8859-1", "UTF-8");
962         } catch (Glib::ConvertError& err) {
963                 throw Glib::ConvertError (err.code(), string_compose (_("Cannot convert %1 to Latin-1 text"), txt));
964         }
965
966         // does not do much mor than UTF-8 to Latin1 translation yet, but
967         // that may have to change if cue parsers in burning programs change
968         out = '"' + latin1_txt + '"';
969
970         return out;
971 }
972
973 } // namespace ARDOUR