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 // This sample application demonstrates how to use the matroska parser
10 // library, which allows clients to handle a matroska format file.
11
12 #include "sample_muxer_metadata.h"
13
14 #include <cstdio>
15 #include <string>
16
17 #include "mkvmuxer/mkvmuxer.h"
18 #include "webvtt/vttreader.h"
19
SampleMuxerMetadata()20 SampleMuxerMetadata::SampleMuxerMetadata() : segment_(NULL) {}
21
Init(mkvmuxer::Segment * segment)22 bool SampleMuxerMetadata::Init(mkvmuxer::Segment* segment) {
23 if (segment == NULL || segment_ != NULL)
24 return false;
25
26 segment_ = segment;
27 return true;
28 }
29
Load(const char * file,Kind kind)30 bool SampleMuxerMetadata::Load(const char* file, Kind kind) {
31 if (kind == kChapters)
32 return LoadChapters(file);
33
34 uint64_t track_num;
35
36 if (!AddTrack(kind, &track_num)) {
37 printf("Unable to add track for WebVTT file \"%s\"\n", file);
38 return false;
39 }
40
41 return Parse(file, kind, track_num);
42 }
43
AddChapters()44 bool SampleMuxerMetadata::AddChapters() {
45 typedef cue_list_t::const_iterator iter_t;
46 iter_t i = chapter_cues_.begin();
47 const iter_t j = chapter_cues_.end();
48
49 while (i != j) {
50 const cue_t& chapter = *i++;
51
52 if (!AddChapter(chapter))
53 return false;
54 }
55
56 return true;
57 }
58
Write(int64_t time_ns)59 bool SampleMuxerMetadata::Write(int64_t time_ns) {
60 typedef cues_set_t::iterator iter_t;
61
62 iter_t i = cues_set_.begin();
63 const iter_t j = cues_set_.end();
64
65 while (i != j) {
66 const cues_set_t::value_type& v = *i;
67
68 if (time_ns >= 0 && v > time_ns)
69 return true; // nothing else to do just yet
70
71 if (!v.Write(segment_)) {
72 printf("\nCould not add metadata.\n");
73 return false; // error
74 }
75
76 cues_set_.erase(i++);
77 }
78
79 return true;
80 }
81
LoadChapters(const char * file)82 bool SampleMuxerMetadata::LoadChapters(const char* file) {
83 if (!chapter_cues_.empty()) {
84 printf("Support for more than one chapters file is not yet implemented\n");
85 return false;
86 }
87
88 cue_list_t cues;
89
90 if (!ParseChapters(file, &cues))
91 return false;
92
93 // TODO(matthewjheaney): support more than one chapters file
94 chapter_cues_.swap(cues);
95
96 return true;
97 }
98
ParseChapters(const char * file,cue_list_t * cues_ptr)99 bool SampleMuxerMetadata::ParseChapters(const char* file,
100 cue_list_t* cues_ptr) {
101 cue_list_t& cues = *cues_ptr;
102 cues.clear();
103
104 libwebvtt::VttReader r;
105 int e = r.Open(file);
106
107 if (e) {
108 printf("Unable to open WebVTT file: \"%s\"\n", file);
109 return false;
110 }
111
112 libwebvtt::Parser p(&r);
113 e = p.Init();
114
115 if (e < 0) { // error
116 printf("Error parsing WebVTT file: \"%s\"\n", file);
117 return false;
118 }
119
120 libwebvtt::Time t;
121 t.hours = -1;
122
123 for (;;) {
124 cue_t c;
125 e = p.Parse(&c);
126
127 if (e < 0) { // error
128 printf("Error parsing WebVTT file: \"%s\"\n", file);
129 return false;
130 }
131
132 if (e > 0) // EOF
133 return true;
134
135 if (c.start_time < t) {
136 printf("bad WebVTT cue timestamp (out-of-order)\n");
137 return false;
138 }
139
140 if (c.stop_time < c.start_time) {
141 printf("bad WebVTT cue timestamp (stop < start)\n");
142 return false;
143 }
144
145 t = c.start_time;
146 cues.push_back(c);
147 }
148 }
149
AddChapter(const cue_t & cue)150 bool SampleMuxerMetadata::AddChapter(const cue_t& cue) {
151 // TODO(matthewjheaney): support language and country
152
153 mkvmuxer::Chapter* const chapter = segment_->AddChapter();
154
155 if (chapter == NULL) {
156 printf("Unable to add chapter\n");
157 return false;
158 }
159
160 if (cue.identifier.empty()) {
161 chapter->set_id(NULL);
162 } else {
163 const char* const id = cue.identifier.c_str();
164 if (!chapter->set_id(id)) {
165 printf("Unable to set chapter id\n");
166 return false;
167 }
168 }
169
170 typedef libwebvtt::presentation_t time_ms_t;
171 const time_ms_t start_time_ms = cue.start_time.presentation();
172 const time_ms_t stop_time_ms = cue.stop_time.presentation();
173
174 enum { kNsPerMs = 1000000 };
175 const uint64_t start_time_ns = start_time_ms * kNsPerMs;
176 const uint64_t stop_time_ns = stop_time_ms * kNsPerMs;
177
178 chapter->set_time(*segment_, start_time_ns, stop_time_ns);
179
180 typedef libwebvtt::Cue::payload_t::const_iterator iter_t;
181 iter_t i = cue.payload.begin();
182 const iter_t j = cue.payload.end();
183
184 std::string title;
185
186 for (;;) {
187 title += *i++;
188
189 if (i == j)
190 break;
191
192 enum { kLF = '\x0A' };
193 title += kLF;
194 }
195
196 if (!chapter->add_string(title.c_str(), NULL, NULL)) {
197 printf("Unable to set chapter title\n");
198 return false;
199 }
200
201 return true;
202 }
203
AddTrack(Kind kind,uint64_t * track_num)204 bool SampleMuxerMetadata::AddTrack(Kind kind, uint64_t* track_num) {
205 *track_num = 0;
206
207 // Track number value 0 means "let muxer choose track number"
208 mkvmuxer::Track* const track = segment_->AddTrack(0);
209
210 if (track == NULL) // error
211 return false;
212
213 // Return the track number value chosen by the muxer
214 *track_num = track->number();
215
216 int type;
217 const char* codec_id;
218
219 switch (kind) {
220 case kSubtitles:
221 type = 0x11;
222 codec_id = "D_WEBVTT/SUBTITLES";
223 break;
224
225 case kCaptions:
226 type = 0x11;
227 codec_id = "D_WEBVTT/CAPTIONS";
228 break;
229
230 case kDescriptions:
231 type = 0x21;
232 codec_id = "D_WEBVTT/DESCRIPTIONS";
233 break;
234
235 case kMetadata:
236 type = 0x21;
237 codec_id = "D_WEBVTT/METADATA";
238 break;
239
240 default:
241 return false;
242 }
243
244 track->set_type(type);
245 track->set_codec_id(codec_id);
246
247 // TODO(matthewjheaney): set name and language
248
249 return true;
250 }
251
Parse(const char * file,Kind,uint64_t track_num)252 bool SampleMuxerMetadata::Parse(const char* file, Kind /* kind */,
253 uint64_t track_num) {
254 libwebvtt::VttReader r;
255 int e = r.Open(file);
256
257 if (e) {
258 printf("Unable to open WebVTT file: \"%s\"\n", file);
259 return false;
260 }
261
262 libwebvtt::Parser p(&r);
263
264 e = p.Init();
265
266 if (e < 0) { // error
267 printf("Error parsing WebVTT file: \"%s\"\n", file);
268 return false;
269 }
270
271 SortableCue cue;
272 cue.track_num = track_num;
273
274 libwebvtt::Time t;
275 t.hours = -1;
276
277 for (;;) {
278 cue_t& c = cue.cue;
279 e = p.Parse(&c);
280
281 if (e < 0) { // error
282 printf("Error parsing WebVTT file: \"%s\"\n", file);
283 return false;
284 }
285
286 if (e > 0) // EOF
287 return true;
288
289 if (c.start_time >= t) {
290 t = c.start_time;
291 } else {
292 printf("bad WebVTT cue timestamp (out-of-order)\n");
293 return false;
294 }
295
296 if (c.stop_time < c.start_time) {
297 printf("bad WebVTT cue timestamp (stop < start)\n");
298 return false;
299 }
300
301 cues_set_.insert(cue);
302 }
303 }
304
MakeFrame(const cue_t & c,std::string * pf)305 void SampleMuxerMetadata::MakeFrame(const cue_t& c, std::string* pf) {
306 pf->clear();
307 WriteCueIdentifier(c.identifier, pf);
308 WriteCueSettings(c.settings, pf);
309 WriteCuePayload(c.payload, pf);
310 }
311
WriteCueIdentifier(const std::string & identifier,std::string * pf)312 void SampleMuxerMetadata::WriteCueIdentifier(const std::string& identifier,
313 std::string* pf) {
314 pf->append(identifier);
315 pf->push_back('\x0A'); // LF
316 }
317
WriteCueSettings(const cue_t::settings_t & settings,std::string * pf)318 void SampleMuxerMetadata::WriteCueSettings(const cue_t::settings_t& settings,
319 std::string* pf) {
320 if (settings.empty()) {
321 pf->push_back('\x0A'); // LF
322 return;
323 }
324
325 typedef cue_t::settings_t::const_iterator iter_t;
326
327 iter_t i = settings.begin();
328 const iter_t j = settings.end();
329
330 for (;;) {
331 const libwebvtt::Setting& setting = *i++;
332
333 pf->append(setting.name);
334 pf->push_back(':');
335 pf->append(setting.value);
336
337 if (i == j)
338 break;
339
340 pf->push_back(' '); // separate settings with whitespace
341 }
342
343 pf->push_back('\x0A'); // LF
344 }
345
WriteCuePayload(const cue_t::payload_t & payload,std::string * pf)346 void SampleMuxerMetadata::WriteCuePayload(const cue_t::payload_t& payload,
347 std::string* pf) {
348 typedef cue_t::payload_t::const_iterator iter_t;
349
350 iter_t i = payload.begin();
351 const iter_t j = payload.end();
352
353 while (i != j) {
354 const std::string& line = *i++;
355 pf->append(line);
356 pf->push_back('\x0A'); // LF
357 }
358 }
359
Write(mkvmuxer::Segment * segment) const360 bool SampleMuxerMetadata::SortableCue::Write(mkvmuxer::Segment* segment) const {
361 // Cue start time expressed in milliseconds
362 const int64_t start_ms = cue.start_time.presentation();
363
364 // Cue start time expressed in nanoseconds (MKV time)
365 const int64_t start_ns = start_ms * 1000000;
366
367 // Cue stop time expressed in milliseconds
368 const int64_t stop_ms = cue.stop_time.presentation();
369
370 // Cue stop time expressed in nanonseconds
371 const int64_t stop_ns = stop_ms * 1000000;
372
373 // Metadata blocks always specify the block duration.
374 const int64_t duration_ns = stop_ns - start_ns;
375
376 std::string frame;
377 MakeFrame(cue, &frame);
378
379 typedef const uint8_t* data_t;
380 const data_t buf = reinterpret_cast<data_t>(frame.data());
381 const uint64_t len = frame.length();
382
383 mkvmuxer::Frame muxer_frame;
384 if (!muxer_frame.Init(buf, len))
385 return 0;
386 muxer_frame.set_track_number(track_num);
387 muxer_frame.set_timestamp(start_ns);
388 muxer_frame.set_duration(duration_ns);
389 muxer_frame.set_is_key(true); // All metadata frames are keyframes.
390 return segment->AddGenericFrame(&muxer_frame);
391 }