mirror of
https://github.com/Ardour/ardour.git
synced 2025-12-06 06:44:57 +01:00
Added support for exporting mp4 chapter marks
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".
This commit is contained in:
parent
9241f58188
commit
acd1ee1989
9 changed files with 93 additions and 8 deletions
|
|
@ -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 ()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -461,7 +461,8 @@ namespace ARDOUR {
|
|||
enum CDMarkerFormat {
|
||||
CDMarkerNone,
|
||||
CDMarkerCUE,
|
||||
CDMarkerTOC
|
||||
CDMarkerTOC,
|
||||
MP4Chaps
|
||||
};
|
||||
|
||||
enum HeaderFormat {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 ("+");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue