1 // Copyright (c) 2012 The WebM project authors. All Rights Reserved.
3 // Use of this source code is governed by a BSD-style license
4 // that can be found in the LICENSE file in the root of the source
5 // tree. An additional intellectual property rights grant can be found
6 // in the file PATENTS. All contributing project authors may
7 // be found in the AUTHORS file in the root of the source tree.
14 #include "./mkvparser.hpp"
15 #include "./mkvreader.hpp"
16 #include "./webvttparser.h"
22 typedef long long mkvtime_t; // NOLINT
23 typedef long long mkvpos_t; // NOLINT
24 typedef std::auto_ptr<mkvparser::Segment> segment_ptr_t;
26 // WebVTT metadata tracks have a type (encoded in the CodecID for the track).
27 // We use |type| to synthesize a filename for the out-of-band WebVTT |file|.
38 // We use a map, indexed by track number, to collect information about
39 // each track in the input file.
40 typedef std::map<long, MetadataInfo> metadata_map_t; // NOLINT
42 // The distinguished key value we use to store the chapters
43 // information in the metadata map.
44 enum { kChaptersKey = 0 };
46 // The data from the original WebVTT Cue is stored as a WebM block.
47 // The FrameParser is used to parse the lines of text out from the
48 // block, in order to reconstruct the original WebVTT Cue.
49 class FrameParser : public libwebvtt::LineReader {
51 // Bind the FrameParser instance to a WebM block.
52 explicit FrameParser(const mkvparser::BlockGroup* block_group);
53 virtual ~FrameParser();
55 // The Webm block (group) to which this instance is bound. We
56 // treat the payload of the block as a stream of characters.
57 const mkvparser::BlockGroup* const block_group_;
60 // Read the next character from the character stream (the payload
61 // of the WebM block). We increment the stream pointer |pos_| as
62 // each character from the stream is consumed.
63 virtual int GetChar(char* c);
65 // End-of-line handling requires that we put a character back into
66 // the stream. Here we need only decrement the stream pointer |pos_|
67 // to unconsume the character.
68 virtual void UngetChar(char c);
70 // The current position in the character stream (the payload of the block).
73 // The position of the end of the character stream. When the current
74 // position |pos_| equals the end position |pos_end_|, the entire
75 // stream (block payload) has been consumed and end-of-stream is indicated.
79 // Disable copy ctor and copy assign
80 FrameParser(const FrameParser&);
81 FrameParser& operator=(const FrameParser&);
84 // The data from the original WebVTT Cue is stored as an MKV Chapters
85 // Atom element (the cue payload is stored as a Display sub-element).
86 // The ChapterAtomParser is used to parse the lines of text out from
87 // the String sub-element of the Display element (though it would be
88 // admittedly odd if there were more than one line).
89 class ChapterAtomParser : public libwebvtt::LineReader {
91 explicit ChapterAtomParser(const mkvparser::Chapters::Display* display);
92 virtual ~ChapterAtomParser();
94 const mkvparser::Chapters::Display* const display_;
97 // Read the next character from the character stream (the title
98 // member of the atom's display). We increment the stream pointer
99 // |str_| as each character from the stream is consumed.
100 virtual int GetChar(char* c);
102 // End-of-line handling requires that we put a character back into
103 // the stream. Here we need only decrement the stream pointer |str_|
104 // to unconsume the character.
105 virtual void UngetChar(char c);
107 // The current position in the character stream (the title of the
111 // The position of the end of the character stream. When the current
112 // position |str_| equals the end position |str_end_|, the entire
113 // stream (title of the display) has been consumed and end-of-stream
115 const char* str_end_;
118 ChapterAtomParser(const ChapterAtomParser&);
119 ChapterAtomParser& operator=(const ChapterAtomParser&);
122 // Parse the EBML header of the WebM input file, to determine whether we
123 // actually have a WebM file. Returns false if this is not a WebM file.
124 bool ParseHeader(mkvparser::IMkvReader* reader, mkvpos_t* pos);
126 // Parse the Segment of the input file and load all of its clusters.
127 // Returns false if there was an error parsing the file.
129 mkvparser::IMkvReader* reader,
131 segment_ptr_t* segment);
133 // If |segment| has a Chapters element (in which case, there will be a
134 // corresponding entry in |metadata_map|), convert the MKV chapters to
135 // WebVTT chapter cues and write them to the output file. Returns
137 bool WriteChaptersFile(
138 const metadata_map_t& metadata_map,
139 const mkvparser::Segment* segment);
141 // Convert an MKV Chapters Atom to a WebVTT cue and write it to the
142 // output |file|. Returns false on error.
143 bool WriteChaptersCue(
145 const mkvparser::Chapters* chapters,
146 const mkvparser::Chapters::Atom* atom,
147 const mkvparser::Chapters::Display* display);
149 // Write the Cue Identifier line of the WebVTT cue, if it's present.
150 // Returns false on error.
151 bool WriteChaptersCueIdentifier(
153 const mkvparser::Chapters::Atom* atom);
155 // Use the timecodes from the chapters |atom| to write just the
156 // timings line of the WebVTT cue. Returns false on error.
157 bool WriteChaptersCueTimings(
159 const mkvparser::Chapters* chapters,
160 const mkvparser::Chapters::Atom* atom);
162 // Parse the String sub-element of the |display| and write the payload
163 // of the WebVTT cue. Returns false on error.
164 bool WriteChaptersCuePayload(
166 const mkvparser::Chapters::Display* display);
168 // Iterate over the tracks of the input file (and any chapters
169 // element) and cache information about each metadata track.
170 void BuildMap(const mkvparser::Segment* segment, metadata_map_t* metadata_map);
172 // For each track listed in the cache, synthesize its output filename
173 // and open a file handle that designates the out-of-band file.
174 // Returns false if we were unable to open an output file for a track.
175 bool OpenFiles(metadata_map_t* metadata_map, const char* filename);
177 // Close the file handle for each track in the cache.
178 void CloseFiles(metadata_map_t* metadata_map);
180 // Iterate over the clusters of the input file, and write a WebVTT cue
181 // for each metadata block. Returns false if processing of a cluster
183 bool WriteFiles(const metadata_map_t& m, mkvparser::Segment* s);
185 // Write the WebVTT header for each track in the cache. We do this
186 // immediately before writing the actual WebVTT cues. Returns false
187 // if the write failed.
188 bool InitializeFiles(const metadata_map_t& metadata_map);
190 // Iterate over the blocks of the |cluster|, writing a WebVTT cue to
191 // its associated output file for each block of metadata. Returns
192 // false if processing a block failed, or there was a parse error.
194 const metadata_map_t& metadata_map,
195 const mkvparser::Cluster* cluster);
197 // Look up this track number in the cache, and if found (meaning this
198 // is a metadata track), write a WebVTT cue to the associated output
199 // file. Returns false if writing the WebVTT cue failed.
200 bool ProcessBlockEntry(
201 const metadata_map_t& metadata_map,
202 const mkvparser::BlockEntry* block_entry);
204 // Parse the lines of text from the |block_group| to reconstruct the
205 // original WebVTT cue, and write it to the associated output |file|.
206 // Returns false if there was an error writing to the output file.
207 bool WriteCue(FILE* file, const mkvparser::BlockGroup* block_group);
209 // Consume a line of text from the character stream, and if the line
210 // is not empty write the cue identifier to the associated output
211 // file. Returns false if there was an error writing to the file.
212 bool WriteCueIdentifier(FILE* f, FrameParser* parser);
214 // Consume a line of text from the character stream (which holds any
215 // cue settings) and write the cue timings line for this cue to the
216 // associated output file. Returns false if there was an error
217 // writing to the file.
218 bool WriteCueTimings(
220 FrameParser* parser);
222 // Write the timestamp (representating either the start time or stop
223 // time of the cue) to the output file. Returns false if there was an
224 // error writing to the file.
229 // Consume the remaining lines of text from the character stream
230 // (these lines are the actual payload of the WebVTT cue), and write
231 // them to the associated output file. Returns false if there was an
232 // error writing to the file.
233 bool WriteCuePayload(
235 FrameParser* parser);
236 } // namespace vttdemux
238 int main(int argc, const char* argv[]) {
240 printf("usage: vttdemux <webmfile>\n");
244 const char* const filename = argv[1];
245 mkvparser::MkvReader reader;
247 int e = reader.Open(filename);
250 printf("unable to open file\n");
254 vttdemux::mkvpos_t pos;
256 if (!vttdemux::ParseHeader(&reader, &pos))
259 vttdemux::segment_ptr_t segment_ptr;
261 if (!vttdemux::ParseSegment(&reader, pos, &segment_ptr))
264 vttdemux::metadata_map_t metadata_map;
266 BuildMap(segment_ptr.get(), &metadata_map);
268 if (metadata_map.empty()) {
269 printf("no WebVTT metadata found\n");
273 if (!OpenFiles(&metadata_map, filename)) {
274 CloseFiles(&metadata_map); // nothing to flush, so not strictly necessary
278 if (!WriteFiles(metadata_map, segment_ptr.get())) {
279 CloseFiles(&metadata_map); // might as well flush what we do have
283 CloseFiles(&metadata_map);
290 FrameParser::FrameParser(const mkvparser::BlockGroup* block_group)
291 : block_group_(block_group) {
292 const mkvparser::Block* const block = block_group->GetBlock();
293 const mkvparser::Block::Frame& f = block->GetFrame(0);
295 // The beginning and end of the character stream corresponds to the
296 // position of this block's frame within the WebM input file.
299 pos_end_ = f.pos + f.len;
302 FrameParser::~FrameParser() {
305 int FrameParser::GetChar(char* c) {
306 if (pos_ >= pos_end_) // end-of-stream
307 return 1; // per the semantics of libwebvtt::Reader::GetChar
309 const mkvparser::Cluster* const cluster = block_group_->GetCluster();
310 const mkvparser::Segment* const segment = cluster->m_pSegment;
311 mkvparser::IMkvReader* const reader = segment->m_pReader;
313 unsigned char* const buf = reinterpret_cast<unsigned char*>(c);
314 const int result = reader->Read(pos_, 1, buf);
316 if (result < 0) // error
319 ++pos_; // consume this character in the stream
323 void FrameParser::UngetChar(char /* c */ ) {
324 // All we need to do here is decrement the position in the stream.
325 // The next time GetChar is called the same character will be
326 // re-read from the input file.
330 ChapterAtomParser::ChapterAtomParser(
331 const mkvparser::Chapters::Display* display)
332 : display_(display) {
333 str_ = display->GetString();
334 const size_t len = strlen(str_);
335 str_end_ = str_ + len;
338 ChapterAtomParser::~ChapterAtomParser() {
341 int ChapterAtomParser::GetChar(char* c) {
342 if (str_ >= str_end_) // end-of-stream
343 return 1; // per the semantics of libwebvtt::Reader::GetChar
345 *c = *str_++; // consume this character in the stream
349 void ChapterAtomParser::UngetChar(char /* c */ ) {
350 // All we need to do here is decrement the position in the stream.
351 // The next time GetChar is called the same character will be
352 // re-read from the input file.
356 } // namespace vttdemux
358 bool vttdemux::ParseHeader(
359 mkvparser::IMkvReader* reader,
361 mkvparser::EBMLHeader h;
362 const mkvpos_t status = h.Parse(reader, *pos);
365 printf("error parsing EBML header\n");
369 if (strcmp(h.m_docType, "webm") != 0) {
370 printf("bad doctype\n");
374 return true; // success
377 bool vttdemux::ParseSegment(
378 mkvparser::IMkvReader* reader,
380 segment_ptr_t* segment_ptr) {
381 // We first create the segment object.
383 mkvparser::Segment* p;
384 const mkvpos_t create = mkvparser::Segment::CreateInstance(reader, pos, p);
387 printf("error parsing segment element\n");
391 segment_ptr->reset(p);
393 // Now parse all of the segment's sub-elements, in toto.
395 const long status = p->Load(); // NOLINT
398 printf("error loading segment\n");
405 void vttdemux::BuildMap(
406 const mkvparser::Segment* segment,
407 metadata_map_t* map_ptr) {
408 metadata_map_t& m = *map_ptr;
411 if (segment->GetChapters()) {
414 info.type = MetadataInfo::kChapters;
416 m[kChaptersKey] = info;
419 const mkvparser::Tracks* const tt = segment->GetTracks();
423 const long tc = tt->GetTracksCount(); // NOLINT
427 // Iterate over the tracks in the intput file. We determine whether
428 // a track holds metadata by inspecting its CodecID.
430 for (long idx = 0; idx < tc; ++idx) { // NOLINT
431 const mkvparser::Track* const t = tt->GetTrackByIndex(idx);
433 if (t == NULL) // weird
436 const long tn = t->GetNumber(); // NOLINT
438 if (tn <= 0) // weird
441 const char* const codec_id = t->GetCodecId();
443 if (codec_id == NULL) // weird
449 if (strcmp(codec_id, "D_WEBVTT/SUBTITLES") == 0) {
450 info.type = MetadataInfo::kSubtitles;
451 } else if (strcmp(codec_id, "D_WEBVTT/CAPTIONS") == 0) {
452 info.type = MetadataInfo::kCaptions;
453 } else if (strcmp(codec_id, "D_WEBVTT/DESCRIPTIONS") == 0) {
454 info.type = MetadataInfo::kDescriptions;
455 } else if (strcmp(codec_id, "D_WEBVTT/METADATA") == 0) {
456 info.type = MetadataInfo::kMetadata;
461 m[tn] = info; // create an entry in the cache for this track
465 bool vttdemux::OpenFiles(metadata_map_t* metadata_map, const char* filename) {
466 if (metadata_map == NULL || metadata_map->empty())
469 if (filename == NULL)
472 // Find the position of the filename extension. We synthesize the
473 // output filename from the directory path and basename of the input
476 const char* const ext = strrchr(filename, '.');
478 if (ext == NULL) // TODO(matthewjheaney): liberalize?
481 // Remember whether a track of this type has already been seen (the
482 // map key) by keeping a count (the map item). We quality the
483 // output filename with the track number if there is more than one
484 // track having a given type.
486 std::map<MetadataInfo::Type, int> exists;
488 typedef metadata_map_t::iterator iter_t;
490 metadata_map_t& m = *metadata_map;
491 const iter_t ii = m.begin();
492 const iter_t j = m.end();
494 // Make a first pass over the cache to determine whether there is
495 // more than one track corresponding to a given metadata type.
499 const metadata_map_t::value_type& v = *i++;
500 const MetadataInfo& info = v.second;
501 const MetadataInfo::Type type = info.type;
505 // Make a second pass over the cache, synthesizing the filename of
506 // each output file (from the input file basename, the input track
507 // metadata type, and its track number if necessary), and then
508 // opening a WebVTT output file having that filename.
512 metadata_map_t::value_type& v = *i++;
513 MetadataInfo& info = v.second;
514 const MetadataInfo::Type type = info.type;
516 // Start with the basename of the input file.
518 string name(filename, ext);
520 // Next append the metadata kind.
523 case MetadataInfo::kSubtitles:
524 name += "_SUBTITLES";
527 case MetadataInfo::kCaptions:
531 case MetadataInfo::kDescriptions:
532 name += "_DESCRIPTIONS";
535 case MetadataInfo::kMetadata:
539 case MetadataInfo::kChapters:
547 // If there is more than one metadata track having a given type
548 // (the WebVTT-in-WebM spec doesn't preclude this), then qualify
549 // the output filename with the input track number.
551 if (exists[type] > 1) {
553 char str[kLen]; // max 126 tracks, so only 4 chars really needed
555 snprintf(str, kLen, "%ld", v.first); // track number
557 _snprintf_s(str, sizeof(str), kLen, "%ld", v.first); // track number
562 // Finally append the output filename extension.
566 // We have synthesized the full output filename, so attempt to
567 // open the WebVTT output file.
570 info.file = fopen(name.c_str(), "wb");
571 const bool success = (info.file != NULL);
573 const errno_t e = fopen_s(&info.file, name.c_str(), "wb");
574 const bool success = (e == 0);
578 printf("unable to open output file %s\n", name.c_str());
586 void vttdemux::CloseFiles(metadata_map_t* metadata_map) {
587 if (metadata_map == NULL)
590 metadata_map_t& m = *metadata_map;
592 typedef metadata_map_t::iterator iter_t;
594 iter_t i = m.begin();
595 const iter_t j = m.end();
597 // Gracefully close each output file, to ensure all output gets
598 // propertly flushed.
601 metadata_map_t::value_type& v = *i++;
602 MetadataInfo& info = v.second;
609 bool vttdemux::WriteFiles(const metadata_map_t& m, mkvparser::Segment* s) {
610 // First write the WebVTT header.
614 if (!WriteChaptersFile(m, s))
617 // Now iterate over the clusters, writing the WebVTT cue as we parse
618 // each metadata block.
620 const mkvparser::Cluster* cluster = s->GetFirst();
622 while (cluster != NULL && !cluster->EOS()) {
623 if (!ProcessCluster(m, cluster))
626 cluster = s->GetNext(cluster);
632 bool vttdemux::InitializeFiles(const metadata_map_t& m) {
633 // Write the WebVTT header for each output file in the cache.
635 typedef metadata_map_t::const_iterator iter_t;
636 iter_t i = m.begin();
637 const iter_t j = m.end();
640 const metadata_map_t::value_type& v = *i++;
641 const MetadataInfo& info = v.second;
642 FILE* const f = info.file;
644 if (fputs("WEBVTT\n", f) < 0) {
645 printf("unable to initialize output file\n");
653 bool vttdemux::WriteChaptersFile(
654 const metadata_map_t& m,
655 const mkvparser::Segment* s) {
656 const metadata_map_t::const_iterator info_iter = m.find(kChaptersKey);
657 if (info_iter == m.end()) // no chapters, so nothing to do
660 const mkvparser::Chapters* const chapters = s->GetChapters();
661 if (chapters == NULL) // weird
664 const MetadataInfo& info = info_iter->second;
665 FILE* const file = info.file;
667 const int edition_count = chapters->GetEditionCount();
669 if (edition_count <= 0) // weird
670 return true; // nothing to do
672 if (edition_count > 1) {
673 // TODO(matthewjheaney): figure what to do here
674 printf("more than one chapter edition detected\n");
678 const mkvparser::Chapters::Edition* const edition = chapters->GetEdition(0);
680 const int atom_count = edition->GetAtomCount();
682 for (int idx = 0; idx < atom_count; ++idx) {
683 const mkvparser::Chapters::Atom* const atom = edition->GetAtom(idx);
684 const int display_count = atom->GetDisplayCount();
686 if (display_count <= 0)
689 if (display_count > 1) {
690 // TODO(matthewjheaney): handle case of multiple languages
691 printf("more than 1 display in atom detected\n");
695 const mkvparser::Chapters::Display* const display = atom->GetDisplay(0);
697 if (const char* language = display->GetLanguage()) {
698 if (strcmp(language, "eng") != 0) {
699 // TODO(matthewjheaney): handle case of multiple languages.
701 // We must create a separate webvtt file for each language.
702 // This isn't a simple problem (which is why we defer it for
703 // now), because there's nothing in the header that tells us
704 // what languages we have as cues. We must parse the displays
705 // of each atom to determine that.
707 // One solution is to make two passes over the input data.
708 // First parse the displays, creating an in-memory cache of
709 // all the chapter cues, sorted according to their language.
710 // After we have read all of the chapter atoms from the input
711 // file, we can then write separate output files for each
714 printf("only English-language chapter cues are supported\n");
719 if (!WriteChaptersCue(file, chapters, atom, display))
726 bool vttdemux::WriteChaptersCue(
728 const mkvparser::Chapters* chapters,
729 const mkvparser::Chapters::Atom* atom,
730 const mkvparser::Chapters::Display* display) {
731 // We start a new cue by writing a cue separator (an empty line)
734 if (fputc('\n', f) < 0)
737 // A WebVTT Cue comprises 3 things: a cue identifier, followed by
738 // the cue timings, followed by the payload of the cue. We write
739 // each part of the cue in sequence.
741 if (!WriteChaptersCueIdentifier(f, atom))
744 if (!WriteChaptersCueTimings(f, chapters, atom))
747 if (!WriteChaptersCuePayload(f, display))
753 bool vttdemux::WriteChaptersCueIdentifier(
755 const mkvparser::Chapters::Atom* atom) {
757 const char* const identifier = atom->GetStringUID();
759 if (identifier == NULL)
760 return true; // nothing else to do
762 if (fprintf(f, "%s\n", identifier) < 0)
768 bool vttdemux::WriteChaptersCueTimings(
770 const mkvparser::Chapters* chapters,
771 const mkvparser::Chapters::Atom* atom) {
772 const mkvtime_t start_ns = atom->GetStartTime(chapters);
777 const mkvtime_t stop_ns = atom->GetStopTime(chapters);
782 if (!WriteCueTime(f, start_ns))
785 if (fputs(" --> ", f) < 0)
788 if (!WriteCueTime(f, stop_ns))
791 if (fputc('\n', f) < 0)
797 bool vttdemux::WriteChaptersCuePayload(
799 const mkvparser::Chapters::Display* display) {
800 // Bind a Chapter parser object to the display, which allows us to
801 // extract each line of text from the title-part of the display.
802 ChapterAtomParser parser(display);
804 int count = 0; // count of lines of payload text written to output file
805 for (string line;;) {
806 const int e = parser.GetLine(&line);
808 if (e < 0) // error (only -- we allow EOS here)
811 if (line.empty()) // TODO(matthewjheaney): retain this check?
814 if (fprintf(f, "%s\n", line.c_str()) < 0)
820 if (count <= 0) // WebVTT cue requires non-empty payload
826 bool vttdemux::ProcessCluster(
827 const metadata_map_t& m,
828 const mkvparser::Cluster* c) {
829 // Visit the blocks in this cluster, writing a WebVTT cue for each
832 const mkvparser::BlockEntry* block_entry;
834 long result = c->GetFirst(block_entry); // NOLINT
835 if (result < 0) { // error
836 printf("bad cluster (unable to get first block)\n");
840 while (block_entry != NULL && !block_entry->EOS()) {
841 if (!ProcessBlockEntry(m, block_entry))
844 result = c->GetNext(block_entry, block_entry);
845 if (result < 0) { // error
846 printf("bad cluster (unable to get next block)\n");
854 bool vttdemux::ProcessBlockEntry(
855 const metadata_map_t& m,
856 const mkvparser::BlockEntry* block_entry) {
857 // If the track number for this block is in the cache, then we have
858 // a metadata block, so write the WebVTT cue to the output file.
860 const mkvparser::Block* const block = block_entry->GetBlock();
861 const long long tn = block->GetTrackNumber(); // NOLINT
863 typedef metadata_map_t::const_iterator iter_t;
864 const iter_t i = m.find(static_cast<metadata_map_t::key_type>(tn));
866 if (i == m.end()) // not a metadata track
867 return true; // nothing else to do
869 if (block_entry->GetKind() != mkvparser::BlockEntry::kBlockGroup)
870 return false; // weird
872 typedef mkvparser::BlockGroup BG;
873 const BG* const block_group = static_cast<const BG*>(block_entry);
875 const MetadataInfo& info = i->second;
876 FILE* const f = info.file;
878 return WriteCue(f, block_group);
881 bool vttdemux::WriteCue(
883 const mkvparser::BlockGroup* block_group) {
884 // Bind a FrameParser object to the block, which allows us to
885 // extract each line of text from the payload of the block.
886 FrameParser parser(block_group);
888 // We start a new cue by writing a cue separator (an empty line)
891 if (fputc('\n', f) < 0)
894 // A WebVTT Cue comprises 3 things: a cue identifier, followed by
895 // the cue timings, followed by the payload of the cue. We write
896 // each part of the cue in sequence.
898 if (!WriteCueIdentifier(f, &parser))
901 if (!WriteCueTimings(f, &parser))
904 if (!WriteCuePayload(f, &parser))
910 bool vttdemux::WriteCueIdentifier(
912 FrameParser* parser) {
914 int e = parser->GetLine(&line);
916 if (e) // error or EOS
919 // If the cue identifier line is empty, this means that the original
920 // WebVTT cue did not have a cue identifier, so we don't bother
921 // writing an extra line terminator to the output file (though doing
922 // so would be harmless).
925 if (fputs(line.c_str(), f) < 0)
928 if (fputc('\n', f) < 0)
935 bool vttdemux::WriteCueTimings(
937 FrameParser* parser) {
938 const mkvparser::BlockGroup* const block_group = parser->block_group_;
939 const mkvparser::Cluster* const cluster = block_group->GetCluster();
940 const mkvparser::Block* const block = block_group->GetBlock();
942 // A WebVTT Cue "timings" line comprises two parts: the start and
943 // stop time for this cue, followed by the (optional) cue settings,
944 // such as orientation of the rendered text or its size. Only the
945 // settings part of the cue timings line is stored in the WebM
946 // block. We reconstruct the start and stop times of the WebVTT cue
947 // from the timestamp and duration of the WebM block.
949 const mkvtime_t start_ns = block->GetTime(cluster);
951 if (!WriteCueTime(f, start_ns))
954 if (fputs(" --> ", f) < 0)
957 const mkvtime_t duration_timecode = block_group->GetDurationTimeCode();
959 if (duration_timecode < 0)
962 const mkvparser::Segment* const segment = cluster->m_pSegment;
963 const mkvparser::SegmentInfo* const info = segment->GetInfo();
968 const mkvtime_t timecode_scale = info->GetTimeCodeScale();
970 if (timecode_scale <= 0)
973 const mkvtime_t duration_ns = duration_timecode * timecode_scale;
974 const mkvtime_t stop_ns = start_ns + duration_ns;
976 if (!WriteCueTime(f, stop_ns))
980 int e = parser->GetLine(&line);
982 if (e) // error or EOS
986 if (fputc(' ', f) < 0)
989 if (fputs(line.c_str(), f) < 0)
993 if (fputc('\n', f) < 0)
999 bool vttdemux::WriteCueTime(
1001 mkvtime_t time_ns) {
1002 mkvtime_t ms = time_ns / 1000000; // WebVTT time has millisecond resolution
1004 mkvtime_t sec = ms / 1000;
1007 mkvtime_t min = sec / 60;
1010 mkvtime_t hr = min / 60;
1014 if (fprintf(f, "%02lld:", hr) < 0)
1018 if (fprintf(f, "%02lld:%02lld.%03lld", min, sec, ms) < 0)
1024 bool vttdemux::WriteCuePayload(
1026 FrameParser* parser) {
1027 int count = 0; // count of lines of payload text written to output file
1028 for (string line;;) {
1029 const int e = parser->GetLine(&line);
1031 if (e < 0) // error (only -- we allow EOS here)
1034 if (line.empty()) // TODO(matthewjheaney): retain this check?
1037 if (fprintf(f, "%s\n", line.c_str()) < 0)
1043 if (count <= 0) // WebVTT cue requires non-empty payload