Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / third_party / libwebm / source / vttdemux.cc
1 // Copyright (c) 2012 The WebM project authors. All Rights Reserved.
2 //
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.
8
9 #include <cstdio>
10 #include <cstring>
11 #include <map>
12 #include <memory>
13 #include <string>
14 #include "./mkvparser.hpp"
15 #include "./mkvreader.hpp"
16 #include "./webvttparser.h"
17
18 using std::string;
19
20 namespace vttdemux {
21
22 typedef long long mkvtime_t;  // NOLINT
23 typedef long long mkvpos_t;  // NOLINT
24 typedef std::auto_ptr<mkvparser::Segment> segment_ptr_t;
25
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|.
28 struct MetadataInfo {
29   enum Type {
30     kSubtitles,
31     kCaptions,
32     kDescriptions,
33     kMetadata,
34     kChapters } type;
35   FILE* file;
36 };
37
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
41
42 // The distinguished key value we use to store the chapters
43 // information in the metadata map.
44 enum { kChaptersKey = 0 };
45
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 {
50  public:
51   //  Bind the FrameParser instance to a WebM block.
52   explicit FrameParser(const mkvparser::BlockGroup* block_group);
53   virtual ~FrameParser();
54
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_;
58
59  protected:
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);
64
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);
69
70   // The current position in the character stream (the payload of the block).
71   mkvpos_t pos_;
72
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.
76   mkvpos_t pos_end_;
77
78  private:
79   // Disable copy ctor and copy assign
80   FrameParser(const FrameParser&);
81   FrameParser& operator=(const FrameParser&);
82 };
83
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 {
90  public:
91   explicit ChapterAtomParser(const mkvparser::Chapters::Display* display);
92   virtual ~ChapterAtomParser();
93
94   const mkvparser::Chapters::Display* const display_;
95
96  protected:
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);
101
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);
106
107   // The current position in the character stream (the title of the
108   // atom's display).
109   const char* str_;
110
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
114   // is indicated.
115   const char* str_end_;
116
117  private:
118   ChapterAtomParser(const ChapterAtomParser&);
119   ChapterAtomParser& operator=(const ChapterAtomParser&);
120 };
121
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);
125
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.
128 bool ParseSegment(
129     mkvparser::IMkvReader* reader,
130     mkvpos_t pos,
131     segment_ptr_t* segment);
132
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
136 // false on error.
137 bool WriteChaptersFile(
138     const metadata_map_t& metadata_map,
139     const mkvparser::Segment* segment);
140
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(
144     FILE* file,
145     const mkvparser::Chapters* chapters,
146     const mkvparser::Chapters::Atom* atom,
147     const mkvparser::Chapters::Display* display);
148
149 // Write the Cue Identifier line of the WebVTT cue, if it's present.
150 // Returns false on error.
151 bool WriteChaptersCueIdentifier(
152     FILE* file,
153     const mkvparser::Chapters::Atom* atom);
154
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(
158     FILE* file,
159     const mkvparser::Chapters* chapters,
160     const mkvparser::Chapters::Atom* atom);
161
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(
165     FILE* file,
166     const mkvparser::Chapters::Display* display);
167
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);
171
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);
176
177 // Close the file handle for each track in the cache.
178 void CloseFiles(metadata_map_t* metadata_map);
179
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
182 // failed.
183 bool WriteFiles(const metadata_map_t& m, mkvparser::Segment* s);
184
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);
189
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.
193 bool ProcessCluster(
194     const metadata_map_t& metadata_map,
195     const mkvparser::Cluster* cluster);
196
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);
203
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);
208
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);
213
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(
219     FILE* f,
220     FrameParser* parser);
221
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.
225 bool WriteCueTime(
226     FILE* f,
227     mkvtime_t time_ns);
228
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(
234     FILE* f,
235     FrameParser* parser);
236 }  // namespace vttdemux
237
238 int main(int argc, const char* argv[]) {
239   if (argc != 2) {
240     printf("usage: vttdemux <webmfile>\n");
241     return EXIT_SUCCESS;
242   }
243
244   const char* const filename = argv[1];
245   mkvparser::MkvReader reader;
246
247   int e = reader.Open(filename);
248
249   if (e) {  // error
250     printf("unable to open file\n");
251     return EXIT_FAILURE;
252   }
253
254   vttdemux::mkvpos_t pos;
255
256   if (!vttdemux::ParseHeader(&reader, &pos))
257     return EXIT_FAILURE;
258
259   vttdemux::segment_ptr_t segment_ptr;
260
261   if (!vttdemux::ParseSegment(&reader, pos, &segment_ptr))
262     return EXIT_FAILURE;
263
264   vttdemux::metadata_map_t metadata_map;
265
266   BuildMap(segment_ptr.get(), &metadata_map);
267
268   if (metadata_map.empty()) {
269     printf("no WebVTT metadata found\n");
270     return EXIT_FAILURE;
271   }
272
273   if (!OpenFiles(&metadata_map, filename)) {
274     CloseFiles(&metadata_map);  // nothing to flush, so not strictly necessary
275     return EXIT_FAILURE;
276   }
277
278   if (!WriteFiles(metadata_map, segment_ptr.get())) {
279     CloseFiles(&metadata_map);  // might as well flush what we do have
280     return EXIT_FAILURE;
281   }
282
283   CloseFiles(&metadata_map);
284
285   return EXIT_SUCCESS;
286 }
287
288 namespace vttdemux {
289
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);
294
295   // The beginning and end of the character stream corresponds to the
296   // position of this block's frame within the WebM input file.
297
298   pos_ = f.pos;
299   pos_end_ = f.pos + f.len;
300 }
301
302 FrameParser::~FrameParser() {
303 }
304
305 int FrameParser::GetChar(char* c) {
306   if (pos_ >= pos_end_)  // end-of-stream
307     return 1;  // per the semantics of libwebvtt::Reader::GetChar
308
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;
312
313   unsigned char* const buf = reinterpret_cast<unsigned char*>(c);
314   const int result = reader->Read(pos_, 1, buf);
315
316   if (result < 0)  // error
317     return -1;
318
319   ++pos_;  // consume this character in the stream
320   return 0;
321 }
322
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.
327   --pos_;
328 }
329
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;
336 }
337
338 ChapterAtomParser::~ChapterAtomParser() {
339 }
340
341 int ChapterAtomParser::GetChar(char* c) {
342   if (str_ >= str_end_)  // end-of-stream
343     return 1;  // per the semantics of libwebvtt::Reader::GetChar
344
345   *c = *str_++;  // consume this character in the stream
346   return 0;
347 }
348
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.
353   --str_;
354 }
355
356 }  // namespace vttdemux
357
358 bool vttdemux::ParseHeader(
359     mkvparser::IMkvReader* reader,
360     mkvpos_t* pos) {
361   mkvparser::EBMLHeader h;
362   const mkvpos_t status = h.Parse(reader, *pos);
363
364   if (status) {
365     printf("error parsing EBML header\n");
366     return false;
367   }
368
369   if (strcmp(h.m_docType, "webm") != 0) {
370     printf("bad doctype\n");
371     return false;
372   }
373
374   return true;  // success
375 }
376
377 bool vttdemux::ParseSegment(
378     mkvparser::IMkvReader* reader,
379     mkvpos_t pos,
380     segment_ptr_t* segment_ptr) {
381   // We first create the segment object.
382
383   mkvparser::Segment* p;
384   const mkvpos_t create = mkvparser::Segment::CreateInstance(reader, pos, p);
385
386   if (create) {
387     printf("error parsing segment element\n");
388     return false;
389   }
390
391   segment_ptr->reset(p);
392
393   // Now parse all of the segment's sub-elements, in toto.
394
395   const long status = p->Load();  // NOLINT
396
397   if (status < 0) {
398     printf("error loading segment\n");
399     return false;
400   }
401
402   return true;
403 }
404
405 void vttdemux::BuildMap(
406     const mkvparser::Segment* segment,
407     metadata_map_t* map_ptr) {
408   metadata_map_t& m = *map_ptr;
409   m.clear();
410
411   if (segment->GetChapters()) {
412     MetadataInfo info;
413     info.file = NULL;
414     info.type = MetadataInfo::kChapters;
415
416     m[kChaptersKey] = info;
417   }
418
419   const mkvparser::Tracks* const tt = segment->GetTracks();
420   if (tt == NULL)
421     return;
422
423   const long tc = tt->GetTracksCount();  // NOLINT
424   if (tc <= 0)
425     return;
426
427   // Iterate over the tracks in the intput file.  We determine whether
428   // a track holds metadata by inspecting its CodecID.
429
430   for (long idx = 0; idx < tc; ++idx) {  // NOLINT
431     const mkvparser::Track* const t = tt->GetTrackByIndex(idx);
432
433     if (t == NULL)  // weird
434       continue;
435
436     const long tn = t->GetNumber();  // NOLINT
437
438     if (tn <= 0)  // weird
439       continue;
440
441     const char* const codec_id = t->GetCodecId();
442
443     if (codec_id == NULL)  // weird
444       continue;
445
446     MetadataInfo info;
447     info.file = NULL;
448
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;
457     } else {
458       continue;
459     }
460
461     m[tn] = info;  // create an entry in the cache for this track
462   }
463 }
464
465 bool vttdemux::OpenFiles(metadata_map_t* metadata_map, const char* filename) {
466   if (metadata_map == NULL || metadata_map->empty())
467     return false;
468
469   if (filename == NULL)
470     return false;
471
472   // Find the position of the filename extension.  We synthesize the
473   // output filename from the directory path and basename of the input
474   // filename.
475
476   const char* const ext = strrchr(filename, '.');
477
478   if (ext == NULL)  // TODO(matthewjheaney): liberalize?
479     return false;
480
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.
485
486   std::map<MetadataInfo::Type, int> exists;
487
488   typedef metadata_map_t::iterator iter_t;
489
490   metadata_map_t& m = *metadata_map;
491   const iter_t ii = m.begin();
492   const iter_t j = m.end();
493
494   // Make a first pass over the cache to determine whether there is
495   // more than one track corresponding to a given metadata type.
496
497   iter_t i = ii;
498   while (i != j) {
499     const metadata_map_t::value_type& v = *i++;
500     const MetadataInfo& info = v.second;
501     const MetadataInfo::Type type = info.type;
502     ++exists[type];
503   }
504
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.
509
510   i = ii;
511   while (i != j) {
512     metadata_map_t::value_type& v = *i++;
513     MetadataInfo& info = v.second;
514     const MetadataInfo::Type type = info.type;
515
516     // Start with the basename of the input file.
517
518     string name(filename, ext);
519
520     // Next append the metadata kind.
521
522     switch (type) {
523       case MetadataInfo::kSubtitles:
524         name += "_SUBTITLES";
525         break;
526
527       case MetadataInfo::kCaptions:
528         name += "_CAPTIONS";
529         break;
530
531       case MetadataInfo::kDescriptions:
532         name += "_DESCRIPTIONS";
533         break;
534
535       case MetadataInfo::kMetadata:
536         name += "_METADATA";
537         break;
538
539       case MetadataInfo::kChapters:
540         name += "_CHAPTERS";
541         break;
542
543       default:
544         return false;
545     }
546
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.
550
551     if (exists[type] > 1) {
552       enum { kLen = 33 };
553       char str[kLen];  // max 126 tracks, so only 4 chars really needed
554 #ifndef _MSC_VER
555       snprintf(str, kLen, "%ld", v.first);  // track number
556 #else
557       _snprintf_s(str, sizeof(str), kLen, "%ld", v.first);  // track number
558 #endif
559       name += str;
560     }
561
562     // Finally append the output filename extension.
563
564     name += ".vtt";
565
566     // We have synthesized the full output filename, so attempt to
567     // open the WebVTT output file.
568
569 #ifndef _MSC_VER
570     info.file = fopen(name.c_str(), "wb");
571     const bool success = (info.file != NULL);
572 #else
573     const errno_t e = fopen_s(&info.file, name.c_str(), "wb");
574     const bool success = (e == 0);
575 #endif
576
577     if (!success) {
578       printf("unable to open output file %s\n", name.c_str());
579       return false;
580     }
581   }
582
583   return true;
584 }
585
586 void vttdemux::CloseFiles(metadata_map_t* metadata_map) {
587   if (metadata_map == NULL)
588     return;
589
590   metadata_map_t& m = *metadata_map;
591
592   typedef metadata_map_t::iterator iter_t;
593
594   iter_t i = m.begin();
595   const iter_t j = m.end();
596
597   // Gracefully close each output file, to ensure all output gets
598   // propertly flushed.
599
600   while (i != j) {
601     metadata_map_t::value_type& v = *i++;
602     MetadataInfo& info = v.second;
603
604     fclose(info.file);
605     info.file = NULL;
606   }
607 }
608
609 bool vttdemux::WriteFiles(const metadata_map_t& m, mkvparser::Segment* s) {
610   // First write the WebVTT header.
611
612   InitializeFiles(m);
613
614   if (!WriteChaptersFile(m, s))
615     return false;
616
617   // Now iterate over the clusters, writing the WebVTT cue as we parse
618   // each metadata block.
619
620   const mkvparser::Cluster* cluster = s->GetFirst();
621
622   while (cluster != NULL && !cluster->EOS()) {
623     if (!ProcessCluster(m, cluster))
624       return false;
625
626     cluster = s->GetNext(cluster);
627   }
628
629   return true;
630 }
631
632 bool vttdemux::InitializeFiles(const metadata_map_t& m) {
633   // Write the WebVTT header for each output file in the cache.
634
635   typedef metadata_map_t::const_iterator iter_t;
636   iter_t i = m.begin();
637   const iter_t j = m.end();
638
639   while (i != j) {
640     const metadata_map_t::value_type& v = *i++;
641     const MetadataInfo& info = v.second;
642     FILE* const f = info.file;
643
644     if (fputs("WEBVTT\n", f) < 0) {
645       printf("unable to initialize output file\n");
646       return false;
647     }
648   }
649
650   return true;
651 }
652
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
658     return true;
659
660   const mkvparser::Chapters* const chapters = s->GetChapters();
661   if (chapters == NULL)  // weird
662     return true;
663
664   const MetadataInfo& info = info_iter->second;
665   FILE* const file = info.file;
666
667   const int edition_count = chapters->GetEditionCount();
668
669   if (edition_count <= 0)  // weird
670     return true;  // nothing to do
671
672   if (edition_count > 1) {
673     // TODO(matthewjheaney): figure what to do here
674     printf("more than one chapter edition detected\n");
675     return false;
676   }
677
678   const mkvparser::Chapters::Edition* const edition = chapters->GetEdition(0);
679
680   const int atom_count = edition->GetAtomCount();
681
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();
685
686     if (display_count <= 0)
687       continue;
688
689     if (display_count > 1) {
690       // TODO(matthewjheaney): handle case of multiple languages
691       printf("more than 1 display in atom detected\n");
692       return false;
693     }
694
695     const mkvparser::Chapters::Display* const display = atom->GetDisplay(0);
696
697     if (const char* language = display->GetLanguage()) {
698       if (strcmp(language, "eng") != 0) {
699         // TODO(matthewjheaney): handle case of multiple languages.
700
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.
706
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
712         // language.
713
714         printf("only English-language chapter cues are supported\n");
715         return false;
716       }
717     }
718
719     if (!WriteChaptersCue(file, chapters, atom, display))
720       return false;
721   }
722
723   return true;
724 }
725
726 bool vttdemux::WriteChaptersCue(
727     FILE* f,
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)
732   // into the stream.
733
734   if (fputc('\n', f) < 0)
735     return false;
736
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.
740
741   if (!WriteChaptersCueIdentifier(f, atom))
742     return false;
743
744   if (!WriteChaptersCueTimings(f, chapters, atom))
745     return false;
746
747   if (!WriteChaptersCuePayload(f, display))
748     return false;
749
750   return true;
751 }
752
753 bool vttdemux::WriteChaptersCueIdentifier(
754     FILE* f,
755     const mkvparser::Chapters::Atom* atom) {
756
757   const char* const identifier = atom->GetStringUID();
758
759   if (identifier == NULL)
760     return true;  // nothing else to do
761
762   if (fprintf(f, "%s\n", identifier) < 0)
763     return false;
764
765   return true;
766 }
767
768 bool vttdemux::WriteChaptersCueTimings(
769     FILE* f,
770     const mkvparser::Chapters* chapters,
771     const mkvparser::Chapters::Atom* atom) {
772   const mkvtime_t start_ns = atom->GetStartTime(chapters);
773
774   if (start_ns < 0)
775     return false;
776
777   const mkvtime_t stop_ns = atom->GetStopTime(chapters);
778
779   if (stop_ns < 0)
780     return false;
781
782   if (!WriteCueTime(f, start_ns))
783     return false;
784
785   if (fputs(" --> ", f) < 0)
786     return false;
787
788   if (!WriteCueTime(f, stop_ns))
789     return false;
790
791   if (fputc('\n', f) < 0)
792     return false;
793
794   return true;
795 }
796
797 bool vttdemux::WriteChaptersCuePayload(
798     FILE* f,
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);
803
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);
807
808     if (e < 0)  // error (only -- we allow EOS here)
809       return false;
810
811     if (line.empty())  // TODO(matthewjheaney): retain this check?
812       break;
813
814     if (fprintf(f, "%s\n", line.c_str()) < 0)
815       return false;
816
817     ++count;
818   }
819
820   if (count <= 0)  // WebVTT cue requires non-empty payload
821     return false;
822
823   return true;
824 }
825
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
830   // metadata block.
831
832   const mkvparser::BlockEntry* block_entry;
833
834   long result = c->GetFirst(block_entry);  // NOLINT
835   if (result < 0) {  // error
836     printf("bad cluster (unable to get first block)\n");
837     return false;
838   }
839
840   while (block_entry != NULL && !block_entry->EOS()) {
841     if (!ProcessBlockEntry(m, block_entry))
842       return false;
843
844     result = c->GetNext(block_entry, block_entry);
845     if (result < 0) {  // error
846       printf("bad cluster (unable to get next block)\n");
847       return false;
848     }
849   }
850
851   return true;
852 }
853
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.
859
860   const mkvparser::Block* const block = block_entry->GetBlock();
861   const long long tn = block->GetTrackNumber();  // NOLINT
862
863   typedef metadata_map_t::const_iterator iter_t;
864   const iter_t i = m.find(static_cast<metadata_map_t::key_type>(tn));
865
866   if (i == m.end())  // not a metadata track
867     return true;     // nothing else to do
868
869   if (block_entry->GetKind() != mkvparser::BlockEntry::kBlockGroup)
870     return false;  // weird
871
872   typedef mkvparser::BlockGroup BG;
873   const BG* const block_group = static_cast<const BG*>(block_entry);
874
875   const MetadataInfo& info = i->second;
876   FILE* const f = info.file;
877
878   return WriteCue(f, block_group);
879 }
880
881 bool vttdemux::WriteCue(
882     FILE* f,
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);
887
888   // We start a new cue by writing a cue separator (an empty line)
889   // into the stream.
890
891   if (fputc('\n', f) < 0)
892     return false;
893
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.
897
898   if (!WriteCueIdentifier(f, &parser))
899     return false;
900
901   if (!WriteCueTimings(f, &parser))
902     return false;
903
904   if (!WriteCuePayload(f, &parser))
905     return false;
906
907   return true;
908 }
909
910 bool vttdemux::WriteCueIdentifier(
911     FILE* f,
912     FrameParser* parser) {
913   string line;
914   int e = parser->GetLine(&line);
915
916   if (e)  // error or EOS
917     return false;
918
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).
923
924   if (!line.empty()) {
925     if (fputs(line.c_str(), f) < 0)
926       return false;
927
928     if (fputc('\n', f) < 0)
929       return false;
930   }
931
932   return true;
933 }
934
935 bool vttdemux::WriteCueTimings(
936     FILE* f,
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();
941
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.
948
949   const mkvtime_t start_ns = block->GetTime(cluster);
950
951   if (!WriteCueTime(f, start_ns))
952     return false;
953
954   if (fputs(" --> ", f) < 0)
955     return false;
956
957   const mkvtime_t duration_timecode = block_group->GetDurationTimeCode();
958
959   if (duration_timecode < 0)
960     return false;
961
962   const mkvparser::Segment* const segment = cluster->m_pSegment;
963   const mkvparser::SegmentInfo* const info = segment->GetInfo();
964
965   if (info == NULL)
966     return false;
967
968   const mkvtime_t timecode_scale = info->GetTimeCodeScale();
969
970   if (timecode_scale <= 0)
971     return false;
972
973   const mkvtime_t duration_ns = duration_timecode * timecode_scale;
974   const mkvtime_t stop_ns = start_ns + duration_ns;
975
976   if (!WriteCueTime(f, stop_ns))
977     return false;
978
979   string line;
980   int e = parser->GetLine(&line);
981
982   if (e)  // error or EOS
983     return false;
984
985   if (!line.empty()) {
986     if (fputc(' ', f) < 0)
987       return false;
988
989     if (fputs(line.c_str(), f) < 0)
990       return false;
991   }
992
993   if (fputc('\n', f) < 0)
994     return false;
995
996   return true;
997 }
998
999 bool vttdemux::WriteCueTime(
1000     FILE* f,
1001     mkvtime_t time_ns) {
1002   mkvtime_t ms = time_ns / 1000000;  // WebVTT time has millisecond resolution
1003
1004   mkvtime_t sec = ms / 1000;
1005   ms -= sec * 1000;
1006
1007   mkvtime_t min = sec / 60;
1008   sec -= 60 * min;
1009
1010   mkvtime_t hr = min / 60;
1011   min -= 60 * hr;
1012
1013   if (hr > 0) {
1014     if (fprintf(f, "%02lld:", hr) < 0)
1015       return false;
1016   }
1017
1018   if (fprintf(f, "%02lld:%02lld.%03lld", min, sec, ms) < 0)
1019       return false;
1020
1021   return true;
1022 }
1023
1024 bool vttdemux::WriteCuePayload(
1025     FILE* f,
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);
1030
1031     if (e < 0)  // error (only -- we allow EOS here)
1032       return false;
1033
1034     if (line.empty())  // TODO(matthewjheaney): retain this check?
1035       break;
1036
1037     if (fprintf(f, "%s\n", line.c_str()) < 0)
1038       return false;
1039
1040     ++count;
1041   }
1042
1043   if (count <= 0)  // WebVTT cue requires non-empty payload
1044     return false;
1045
1046   return true;
1047 }