Added support for exporting mp4 chapter marks
authorJohannes Mueller <github@johannes-mueller.org>
Sun, 12 Apr 2015 12:26:39 +0000 (14:26 +0200)
committerRobin Gareus <robin@gareus.org>
Tue, 21 Apr 2015 16:15:29 +0000 (18:15 +0200)
The mp4 file format supports chapter marks using the so called
mp4chaps format to enable chapter wise navigation in an mp4 file. The
format is like

hh:mm:ss.sss Chapter Title

This commit adds the ability to export those kind of chapter marks
along with TOC and CUE marks. The filename extension for the chapter
mark file is "chapters.txt". The format specification description is
"MP4ch".

gtk2_ardour/export_format_dialog.cc
gtk2_ardour/export_format_dialog.h
libs/ardour/ardour/export_format_manager.h
libs/ardour/ardour/export_format_specification.h
libs/ardour/ardour/export_handler.h
libs/ardour/ardour/types.h
libs/ardour/export_format_manager.cc
libs/ardour/export_format_specification.cc
libs/ardour/export_handler.cc

index c2c3909b1b017643f2547a235e7f4102a0979b5f..f76effaebba4d614b4dc08fb87130c07c6704369 100644 (file)
@@ -69,6 +69,7 @@ ExportFormatDialog::ExportFormatDialog (FormatPtr format, bool new_dialog) :
 
   with_cue (_("Create CUE file for disk-at-once CD/DVD creation")),
   with_toc (_("Create TOC file for disk-at-once CD/DVD creation")),
+  with_mp4chaps (_("Create chapter mark file for MP4 chapter marks")),
 
   tag_checkbox (_("Tag file with session's metadata"))
 {
@@ -149,10 +150,12 @@ ExportFormatDialog::ExportFormatDialog (FormatPtr format, bool new_dialog) :
 
        with_cue.signal_toggled().connect (sigc::mem_fun (*this, &ExportFormatDialog::update_with_cue));
        with_toc.signal_toggled().connect (sigc::mem_fun (*this, &ExportFormatDialog::update_with_toc));
+       with_mp4chaps.signal_toggled().connect (sigc::mem_fun (*this, &ExportFormatDialog::update_with_mp4chaps));
        command_entry.signal_changed().connect (sigc::mem_fun (*this, &ExportFormatDialog::update_command));
 
        cue_toc_vbox.pack_start (with_cue, false, false);
        cue_toc_vbox.pack_start (with_toc, false, false);
+       cue_toc_vbox.pack_start (with_mp4chaps, false, false);
 
        /* Load state before hooking up the rest of the signals */
 
@@ -261,6 +264,7 @@ ExportFormatDialog::load_state (FormatPtr spec)
 
        with_cue.set_active (spec->with_cue());
        with_toc.set_active (spec->with_toc());
+       with_mp4chaps.set_active (spec->with_mp4chaps());
 
        for (Gtk::ListStore::Children::iterator it = src_quality_list->children().begin(); it != src_quality_list->children().end(); ++it) {
                if (it->get_value (src_quality_cols.id) == spec->src_quality()) {
@@ -726,6 +730,11 @@ ExportFormatDialog::update_with_toc ()
        manager.select_with_toc (with_toc.get_active());
 }
 
+void
+ExportFormatDialog::update_with_mp4chaps ()
+{
+       manager.select_with_mp4chaps (with_mp4chaps.get_active());
+}
 
 void
 ExportFormatDialog::update_command ()
index 8a3211db233debd8b25d53da7c4fa2671e3f28fd..6a25d707bf02e99a83202bf798d82a4e3e00de16 100644 (file)
@@ -311,11 +311,13 @@ class ExportFormatDialog : public ArdourDialog, public PBD::ScopedConnectionList
 
        Gtk::CheckButton with_cue;
        Gtk::CheckButton with_toc;
+       Gtk::CheckButton with_mp4chaps;
 
        Gtk::VBox cue_toc_vbox;
 
        void update_with_toc ();
        void update_with_cue ();
+       void update_with_mp4chaps();
        void update_command ();
 
        Gtk::TreeView sample_format_view;
index dad7d84b72f58b4406318fee6c98f7a8a475e48c..fff97299e5d8485815d72daf0a7dda6084a76807 100644 (file)
@@ -100,6 +100,7 @@ class LIBARDOUR_API ExportFormatManager : public PBD::ScopedConnectionList
 
        void select_with_cue (bool);
        void select_with_toc (bool);
+       void select_with_mp4chaps (bool);
        void select_upload (bool);
        void set_command (std::string);
        void select_src_quality (ExportFormatBase::SRCQuality value);
index 2a62d792f00f44db0039b4468e45867c08e17a56..ed6e259644e51e7755a861c7a9933d5e9676158d 100644 (file)
@@ -96,6 +96,7 @@ class LIBARDOUR_API ExportFormatSpecification : public ExportFormatBase {
        void set_tag (bool tag_it) { _tag = tag_it; }
        void set_with_cue (bool yn) { _with_cue = yn; }
        void set_with_toc (bool yn) { _with_toc = yn; }
+       void set_with_mp4chaps (bool yn) { _with_mp4chaps = yn; }
        void set_soundcloud_upload (bool yn) { _soundcloud_upload = yn; }
        void set_command (std::string command) { _command = command; }
 
@@ -127,6 +128,8 @@ class LIBARDOUR_API ExportFormatSpecification : public ExportFormatBase {
        float normalize_target () const { return _normalize_target; }
        bool with_toc() const { return _with_toc; }
        bool with_cue() const { return _with_cue; }
+       bool with_mp4chaps() const { return _with_mp4chaps; }
+
        bool soundcloud_upload() const { return _soundcloud_upload; }
        std::string command() const { return _command; }
 
@@ -178,6 +181,7 @@ class LIBARDOUR_API ExportFormatSpecification : public ExportFormatBase {
        float           _normalize_target;
        bool            _with_toc;
        bool            _with_cue;
+       bool            _with_mp4chaps;
        bool            _soundcloud_upload;
        std::string     _command;
 
index 8336cea732aa896718445c15c4feedb7ecee4043..526d32b4c6614b36aab7ee016292e43b88bdb3f2 100644 (file)
@@ -185,14 +185,19 @@ class LIBARDOUR_API ExportHandler : public ExportElementFactory, public sigc::tr
 
        void write_cue_header (CDMarkerStatus & status);
        void write_toc_header (CDMarkerStatus & status);
+       void write_mp4ch_header (CDMarkerStatus & status);
 
        void write_track_info_cue (CDMarkerStatus & status);
        void write_track_info_toc (CDMarkerStatus & status);
+       void write_track_info_mp4ch (CDMarkerStatus & status);
 
        void write_index_info_cue (CDMarkerStatus & status);
        void write_index_info_toc (CDMarkerStatus & status);
+       void write_index_info_mp4ch (CDMarkerStatus & status);
 
        void frames_to_cd_frames_string (char* buf, framepos_t when);
+       void frames_to_chapter_marks_string (char* buf, framepos_t when);
+
        std::string toc_escape_cdtext (const std::string&);
        std::string toc_escape_filename (const std::string&);
        std::string cue_escape_cdtext (const std::string& txt);
index 921cb3e39aa4771ea8c839e1a42d10071ee4eab5..b0fb9faa9c095f0fd59339202136c12535bde682 100644 (file)
@@ -461,7 +461,8 @@ namespace ARDOUR {
        enum CDMarkerFormat {
                CDMarkerNone,
                CDMarkerCUE,
-               CDMarkerTOC
+               CDMarkerTOC,
+               MP4Chaps
        };
 
        enum HeaderFormat {
index 04cfa76677720d8028891000f188aa226565f1a0..f87a08686f6e01f9b17e4ee8aa64d7544aba33e7 100644 (file)
@@ -293,6 +293,12 @@ ExportFormatManager::select_with_toc (bool value)
        check_for_description_change ();
 }
 
+void
+ExportFormatManager::select_with_mp4chaps (bool value)
+{
+       current_selection->set_with_mp4chaps (value);
+       check_for_description_change ();
+}
 
 void
 ExportFormatManager::set_command (std::string command)
index 9a972da0bfa72df7290b0e842495a6a0c4de24af..7e41b209a4f969101311f3780ab3b24186bb0313 100644 (file)
@@ -170,6 +170,7 @@ ExportFormatSpecification::ExportFormatSpecification (Session & s)
        , _normalize_target (1.0)
        , _with_toc (false)
        , _with_cue (false)
+       , _with_mp4chaps (false)
        , _soundcloud_upload (false)
        , _command ("")
 {
@@ -248,6 +249,7 @@ ExportFormatSpecification::get_state ()
        root->add_property ("id", _id.to_s());
        root->add_property ("with-cue", _with_cue ? "true" : "false");
        root->add_property ("with-toc", _with_toc ? "true" : "false");
+       root->add_property ("with-mp4chaps", _with_mp4chaps ? "true" : "false");
        root->add_property ("command", _command);
 
        node = root->add_child ("Encoding");
@@ -319,14 +321,19 @@ ExportFormatSpecification::set_state (const XMLNode & root)
        } else {
                _with_cue = false;
        }
-       
+
        if ((prop = root.property ("with-toc"))) {
                _with_toc = string_is_affirmative (prop->value());
        } else {
                _with_toc = false;
        }
-       
-       
+
+       if ((prop = root.property ("with-mp4chaps"))) {
+               _with_mp4chaps = string_is_affirmative (prop->value());
+       } else {
+               _with_mp4chaps = false;
+       }
+
        if ((prop = root.property ("command"))) {
                _command = prop->value();
        } else {
@@ -602,6 +609,10 @@ ExportFormatSpecification::description (bool include_name)
                components.push_back ("CUE");
        }
 
+       if (_with_mp4chaps) {
+               components.push_back ("MP4ch");
+       }
+
        if (!_command.empty()) {
                components.push_back ("+");
        }
index b44c46e54902983e98f89c406483a023ff3c9bcf..33e19d663f90777bcb41443e881b23b9e060623a 100644 (file)
@@ -305,6 +305,10 @@ ExportHandler::finish_timespan ()
                        export_cd_marker_file (current_timespan, fmt, filename, CDMarkerTOC);
                }
 
+               if (fmt->with_mp4chaps()) {
+                       export_cd_marker_file (current_timespan, fmt, filename, MP4Chaps);
+               }
+
                if (fmt->tag()) {
                        AudiofileTagger::tag_file(filename, *SessionMetadata::Metadata());
                }
@@ -403,6 +407,11 @@ ExportHandler::export_cd_marker_file (ExportTimespanPtr timespan, ExportFormatSp
                        track_func = &ExportHandler::write_track_info_cue;
                        index_func = &ExportHandler::write_index_info_cue;
                        break;
+               case MP4Chaps:
+                       header_func = &ExportHandler::write_mp4ch_header;
+                       track_func = &ExportHandler::write_track_info_mp4ch;
+                       index_func = &ExportHandler::write_index_info_mp4ch;
+                       break;
                default:
                        return;
                }
@@ -500,17 +509,19 @@ ExportHandler::export_cd_marker_file (ExportTimespanPtr timespan, ExportFormatSp
 string
 ExportHandler::get_cd_marker_filename(std::string filename, CDMarkerFormat format)
 {
-       /* do not strip file suffix because there may be more than one format, 
+       /* do not strip file suffix because there may be more than one format,
           and we do not want the CD marker file from one format to overwrite
           another (e.g. foo.wav.cue > foo.aiff.cue)
        */
 
        switch (format) {
-         case CDMarkerTOC:
+       case CDMarkerTOC:
                return filename + ".toc";
-         case CDMarkerCUE:
+       case CDMarkerCUE:
                return filename + ".cue";
-         default:
+       case MP4Chaps:
+               return filename + ".chapters.txt";
+       default:
                return filename + ".marker"; // Should not be reached when actually creating a file
        }
 }
@@ -589,6 +600,11 @@ ExportHandler::write_toc_header (CDMarkerStatus & status)
        status.out << "  }" << endl << "}" << endl;
 }
 
+void
+ExportHandler::write_mp4ch_header (CDMarkerStatus & status)
+{
+}
+
 void
 ExportHandler::write_track_info_cue (CDMarkerStatus & status)
 {
@@ -693,6 +709,14 @@ ExportHandler::write_track_info_toc (CDMarkerStatus & status)
        status.out << "START" << buf << endl;
 }
 
+void ExportHandler::write_track_info_mp4ch (CDMarkerStatus & status)
+{
+       gchar buf[18];
+
+       frames_to_chapter_marks_string(buf, status.track_start_frame);
+       status.out << buf << " " << status.marker->name() << endl;
+}
+
 void
 ExportHandler::write_index_info_cue (CDMarkerStatus & status)
 {
@@ -715,6 +739,11 @@ ExportHandler::write_index_info_toc (CDMarkerStatus & status)
        status.out << "INDEX" << buf << endl;
 }
 
+void
+ExportHandler::write_index_info_mp4ch (CDMarkerStatus & status)
+{
+}
+
 void
 ExportHandler::frames_to_cd_frames_string (char* buf, framepos_t when)
 {
@@ -730,6 +759,23 @@ ExportHandler::frames_to_cd_frames_string (char* buf, framepos_t when)
        sprintf (buf, " %02d:%02d:%02d", mins, secs, frames);
 }
 
+void
+ExportHandler::frames_to_chapter_marks_string (char* buf, framepos_t when)
+{
+       framecnt_t remainder;
+       framecnt_t fr = session.nominal_frame_rate();
+       int hours, mins, secs, msecs;
+
+       hours = when / (3600 * fr);
+       remainder = when - (hours * 3600 * fr);
+       mins = remainder / (60 * fr);
+       remainder -= mins * 60 * fr;
+       secs = remainder / fr;
+       remainder -= secs * fr;
+       msecs = (remainder * 1000) / fr;
+       sprintf (buf, "%02d:%02d:%02d.%03d", hours, mins, secs, msecs);
+}
+
 std::string
 ExportHandler::toc_escape_cdtext (const std::string& txt)
 {