From ad54bfb572c68ed93c374d36495c86a63e40559e Mon Sep 17 00:00:00 2001 From: Matthew Heaney Date: Mon, 22 Oct 2012 16:20:20 -0700 Subject: [PATCH] sample_muxer: added support for WebVTT chapters Change-Id: Ic5ab8097c0981ef300eadc4a3c151f63b2aad81d --- sample_muxer.cpp | 29 +++++--- sample_muxer_metadata.cc | 141 +++++++++++++++++++++++++++++++++++++++ sample_muxer_metadata.h | 28 +++++++- 3 files changed, 184 insertions(+), 14 deletions(-) diff --git a/sample_muxer.cpp b/sample_muxer.cpp index 62659ce..1e84410 100644 --- a/sample_muxer.cpp +++ b/sample_muxer.cpp @@ -63,6 +63,8 @@ void Usage() { "add WebVTT descriptions as metadata track\n"); printf(" -webvtt-metadata " "add WebVTT subtitles as metadata track\n"); + printf(" -webvtt-chapters " + "add WebVTT chapters as MKV chapters element\n"); } struct MetadataFile { @@ -98,13 +100,14 @@ int ParseArgWebVTT( metadata_files_t* metadata_files) { int& i = *argv_index; - enum { kCount = 4 }; + enum { kCount = 5 }; struct Arg { const char* name; SampleMuxerMetadata::Kind kind; }; const Arg args[kCount] = { { "-webvtt-subtitles", SampleMuxerMetadata::kSubtitles }, { "-webvtt-captions", SampleMuxerMetadata::kCaptions }, { "-webvtt-descriptions", SampleMuxerMetadata::kDescriptions }, - { "-webvtt-metadata", SampleMuxerMetadata::kMetadata } + { "-webvtt-metadata", SampleMuxerMetadata::kMetadata }, + { "-webvtt-chapters", SampleMuxerMetadata::kChapters } }; for (int idx = 0; idx < kCount; ++idx) { @@ -147,8 +150,8 @@ int main(int argc, char* argv[]) { uint64 max_cluster_duration = 0; uint64 max_cluster_size = 0; bool switch_tracks = false; - int audio_track_number = 0; // 0 tells muxer to decide. - int video_track_number = 0; // 0 tells muxer to decide. + int audio_track_number = 0; // 0 tells muxer to decide. + int video_track_number = 0; // 0 tells muxer to decide. bool chunking = false; const char* chunk_name = NULL; @@ -291,8 +294,8 @@ int main(int argc, char* argv[]) { // Set Tracks element attributes const mkvparser::Tracks* const parser_tracks = parser_segment->GetTracks(); unsigned long i = 0; - uint64 vid_track = 0; // no track added - uint64 aud_track = 0; // no track added + uint64 vid_track = 0; // no track added + uint64 aud_track = 0; // no track added using mkvparser::Track; @@ -408,6 +411,9 @@ int main(int argc, char* argv[]) { if (!LoadMetadataFiles(metadata_files, &metadata)) return EXIT_FAILURE; + if (!metadata.AddChapters()) + return EXIT_FAILURE; + // Set Cues element attributes mkvmuxer::Cues* const cues = muxer_segment.GetCues(); cues->set_output_block_number(output_cues_block_number); @@ -427,8 +433,7 @@ int main(int argc, char* argv[]) { long status = cluster->GetFirst(block_entry); - if (status) - { + if (status) { printf("\n Could not get first block of cluster.\n"); return EXIT_FAILURE; } @@ -483,8 +488,7 @@ int main(int argc, char* argv[]) { status = cluster->GetNext(block_entry, block_entry); - if (status) - { + if (status) { printf("\n Could not get next block of cluster.\n"); return EXIT_FAILURE; } @@ -498,7 +502,10 @@ int main(int argc, char* argv[]) { if (!metadata.Write(-1)) return EXIT_FAILURE; - muxer_segment.Finalize(); + if (!muxer_segment.Finalize()) { + printf("Finalization of segment failed.\n"); + return EXIT_FAILURE; + } delete [] data; delete parser_segment; diff --git a/sample_muxer_metadata.cc b/sample_muxer_metadata.cc index 4066670..c3a2e3f 100644 --- a/sample_muxer_metadata.cc +++ b/sample_muxer_metadata.cc @@ -16,6 +16,9 @@ bool SampleMuxerMetadata::Init(mkvmuxer::Segment* segment) { } bool SampleMuxerMetadata::Load(const char* file, Kind kind) { + if (kind == kChapters) + return LoadChapters(file); + mkvmuxer::uint64 track_num; if (!AddTrack(kind, &track_num)) { @@ -26,6 +29,21 @@ bool SampleMuxerMetadata::Load(const char* file, Kind kind) { return Parse(file, kind, track_num); } +bool SampleMuxerMetadata::AddChapters() { + typedef cue_list_t::const_iterator iter_t; + iter_t i = chapter_cues_.begin(); + const iter_t j = chapter_cues_.end(); + + while (i != j) { + const cue_t& chapter = *i++; + + if (!AddChapter(chapter)) + return false; + } + + return true; +} + bool SampleMuxerMetadata::Write(mkvmuxer::int64 time_ns) { typedef cues_set_t::iterator iter_t; @@ -49,6 +67,129 @@ bool SampleMuxerMetadata::Write(mkvmuxer::int64 time_ns) { return true; } +bool SampleMuxerMetadata::LoadChapters(const char* file) { + if (!chapter_cues_.empty()) { + printf("Support for more than one chapters file is not yet implemented\n"); + return false; + } + + cue_list_t cues; + + if (!ParseChapters(file, &cues)) + return false; + + // TODO(matthewjheaney): support more than one chapters file + chapter_cues_.swap(cues); + + return true; +} + +bool SampleMuxerMetadata::ParseChapters( + const char* file, + cue_list_t* cues_ptr) { + cue_list_t& cues = *cues_ptr; + cues.clear(); + + libwebvtt::VttReader r; + int e = r.Open(file); + + if (e) { + printf("Unable to open WebVTT file: \"%s\"\n", file); + return false; + } + + libwebvtt::Parser p(&r); + e = p.Init(); + + if (e < 0) { // error + printf("Error parsing WebVTT file: \"%s\"\n", file); + return false; + } + + libwebvtt::Time t; + t.hours = -1; + + for (;;) { + cue_t c; + e = p.Parse(&c); + + if (e < 0) { // error + printf("Error parsing WebVTT file: \"%s\"\n", file); + return false; + } + + if (e > 0) // EOF + return true; + + if (c.start_time < t) { + printf("bad WebVTT cue timestamp (out-of-order)\n"); + return false; + } + + if (c.stop_time < c.start_time) { + printf("bad WebVTT cue timestamp (stop < start)\n"); + return false; + } + + t = c.start_time; + cues.push_back(c); + } +} + +bool SampleMuxerMetadata::AddChapter(const cue_t& cue) { + // TODO(matthewjheaney): support language and country + + mkvmuxer::Chapter* const chapter = segment_->AddChapter(); + + if (chapter == NULL) { + printf("Unable to add chapter\n"); + return false; + } + + if (cue.identifier.empty()) { + chapter->set_id(NULL); + } else { + const char* const id = cue.identifier.c_str(); + if (!chapter->set_id(id)) { + printf("Unable to set chapter id\n"); + return false; + } + } + + typedef libwebvtt::presentation_t time_ms_t; + const time_ms_t start_time_ms = cue.start_time.presentation(); + const time_ms_t stop_time_ms = cue.stop_time.presentation(); + + enum { kNsPerMs = 1000000 }; + const mkvmuxer::uint64 start_time_ns = start_time_ms * kNsPerMs; + const mkvmuxer::uint64 stop_time_ns = stop_time_ms * kNsPerMs; + + chapter->set_time(*segment_, start_time_ns, stop_time_ns); + + typedef libwebvtt::Cue::payload_t::const_iterator iter_t; + iter_t i = cue.payload.begin(); + const iter_t j = cue.payload.end(); + + string title; + + for (;;) { + title += *i++; + + if (i == j) + break; + + enum { kLF = '\x0A' }; + title += kLF; + } + + if (!chapter->add_string(title.c_str(), NULL, NULL)) { + printf("Unable to set chapter title\n"); + return false; + } + + return true; +} + bool SampleMuxerMetadata::AddTrack( Kind kind, mkvmuxer::uint64* track_num) { diff --git a/sample_muxer_metadata.h b/sample_muxer_metadata.h index a714721..353cf19 100644 --- a/sample_muxer_metadata.h +++ b/sample_muxer_metadata.h @@ -21,7 +21,8 @@ class SampleMuxerMetadata { kSubtitles, kCaptions, kDescriptions, - kMetadata + kMetadata, + kChapters }; SampleMuxerMetadata(); @@ -31,10 +32,12 @@ class SampleMuxerMetadata { bool Init(mkvmuxer::Segment* segment); // Parse the WebVTT file |filename| having the indicated |kind|, and - // create a corresponding track in the segment. Returns false on - // error. + // create a corresponding track (or chapters element) in the + // segment. Returns false on error. bool Load(const char* filename, Kind kind); + bool AddChapters(); + // Write any WebVTT cues whose time is less or equal to |time_ns| as // a metadata block in its corresponding track. If |time_ns| is // negative, write all remaining cues. Returns false on error. @@ -74,6 +77,21 @@ class SampleMuxerMetadata { }; typedef std::multiset cues_set_t; + typedef std::list cue_list_t; + + // Parse the WebVTT cues in the named |file|, returning false on + // error. We handle chapters as a special case, because they are + // stored in their own, dedicated level-1 element. + bool LoadChapters(const char* file); + + // Parse the WebVTT chapters in |file| to populate |cues|. Returns + // false on error. + static bool ParseChapters(const char* file, + cue_list_t* cues); + + // Adds WebVTT cue |chapter| to the chapters element of the output + // file's segment element. Returns false on error. + bool AddChapter(const cue_t& chapter); // Add a metadata track to the segment having the indicated |kind|, // returning the |track_num| that has been chosen for this track. @@ -105,6 +123,10 @@ class SampleMuxerMetadata { // Set of cues ordered by time and then by track number. cues_set_t cues_set_; + // The cues that will be used to populate the Chapters level-1 + // element of the output file. + cue_list_t chapter_cues_; + // Disable copy ctor and copy assign. SampleMuxerMetadata(const SampleMuxerMetadata&); SampleMuxerMetadata& operator=(const SampleMuxerMetadata&);