blob: 6eadcfddc3307ea3156084fb48a2e240deef70cc [file] [log] [blame]
Sergey Ulanov0f7815b2014-01-16 11:31:13 -08001// 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
Matthew Heaney7ef225d2012-08-14 16:40:33 -070012#include "sample_muxer_metadata.h"
Tom Fineganb6d8d922016-03-09 14:07:22 -080013
14#include <cstdio>
Matthew Heaney7ef225d2012-08-14 16:40:33 -070015#include <string>
Tom Fineganb6d8d922016-03-09 14:07:22 -080016
Tom Finegan504e0f22016-03-21 11:20:48 -070017#include "mkvmuxer/mkvmuxer.h"
Tom Finegan5f1065e2016-03-17 15:09:46 -070018#include "webvtt/vttreader.h"
Matthew Heaney7ef225d2012-08-14 16:40:33 -070019
Vignesh Venkatasubramanian1a0130d2014-04-14 12:09:20 -070020SampleMuxerMetadata::SampleMuxerMetadata() : segment_(NULL) {}
Matthew Heaney7ef225d2012-08-14 16:40:33 -070021
Matthew Heaneycb8899a2012-10-08 12:50:41 -070022bool SampleMuxerMetadata::Init(mkvmuxer::Segment* segment) {
23 if (segment == NULL || segment_ != NULL)
24 return false;
25
26 segment_ = segment;
27 return true;
Matthew Heaney7ef225d2012-08-14 16:40:33 -070028}
29
30bool SampleMuxerMetadata::Load(const char* file, Kind kind) {
Matthew Heaneyad54bfb2012-10-22 16:20:20 -070031 if (kind == kChapters)
32 return LoadChapters(file);
33
Tom Finegan12f6dc32016-03-16 21:14:26 -070034 uint64_t track_num;
Matthew Heaney7ef225d2012-08-14 16:40:33 -070035
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
Matthew Heaneyad54bfb2012-10-22 16:20:20 -070044bool 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
Tom Finegan12f6dc32016-03-16 21:14:26 -070059bool SampleMuxerMetadata::Write(int64_t time_ns) {
Matthew Heaney7ef225d2012-08-14 16:40:33 -070060 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
Matthew Heaneyad54bfb2012-10-22 16:20:20 -070082bool 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
Vignesh Venkatasubramanian1a0130d2014-04-14 12:09:20 -070099bool SampleMuxerMetadata::ParseChapters(const char* file,
100 cue_list_t* cues_ptr) {
Matthew Heaneyad54bfb2012-10-22 16:20:20 -0700101 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
150bool 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 };
Tom Finegan12f6dc32016-03-16 21:14:26 -0700175 const uint64_t start_time_ns = start_time_ms * kNsPerMs;
176 const uint64_t stop_time_ns = stop_time_ms * kNsPerMs;
Matthew Heaneyad54bfb2012-10-22 16:20:20 -0700177
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
Vignesh Venkatasubramanian4ac7b752013-06-13 15:25:03 -0700184 std::string title;
Matthew Heaneyad54bfb2012-10-22 16:20:20 -0700185
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
Tom Finegan12f6dc32016-03-16 21:14:26 -0700204bool SampleMuxerMetadata::AddTrack(Kind kind, uint64_t* track_num) {
Matthew Heaney7ef225d2012-08-14 16:40:33 -0700205 *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) {
Matthew Heaneycb8899a2012-10-08 12:50:41 -0700220 case kSubtitles:
221 type = 0x11;
222 codec_id = "D_WEBVTT/SUBTITLES";
223 break;
Matthew Heaney7ef225d2012-08-14 16:40:33 -0700224
Matthew Heaneycb8899a2012-10-08 12:50:41 -0700225 case kCaptions:
226 type = 0x11;
227 codec_id = "D_WEBVTT/CAPTIONS";
228 break;
Matthew Heaney7ef225d2012-08-14 16:40:33 -0700229
Matthew Heaneycb8899a2012-10-08 12:50:41 -0700230 case kDescriptions:
231 type = 0x21;
232 codec_id = "D_WEBVTT/DESCRIPTIONS";
233 break;
Matthew Heaney7ef225d2012-08-14 16:40:33 -0700234
Matthew Heaneycb8899a2012-10-08 12:50:41 -0700235 case kMetadata:
236 type = 0x21;
237 codec_id = "D_WEBVTT/METADATA";
238 break;
Matthew Heaney7ef225d2012-08-14 16:40:33 -0700239
Matthew Heaneycb8899a2012-10-08 12:50:41 -0700240 default:
241 return false;
Matthew Heaney7ef225d2012-08-14 16:40:33 -0700242 }
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
Vignesh Venkatasubramanian1a0130d2014-04-14 12:09:20 -0700252bool SampleMuxerMetadata::Parse(const char* file, Kind /* kind */,
Tom Finegan12f6dc32016-03-16 21:14:26 -0700253 uint64_t track_num) {
Matthew Heaney7ef225d2012-08-14 16:40:33 -0700254 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
Vignesh Venkatasubramanian09dd90f2013-06-12 11:23:35 -0700305void SampleMuxerMetadata::MakeFrame(const cue_t& c, std::string* pf) {
Matthew Heaney7ef225d2012-08-14 16:40:33 -0700306 pf->clear();
307 WriteCueIdentifier(c.identifier, pf);
308 WriteCueSettings(c.settings, pf);
309 WriteCuePayload(c.payload, pf);
310}
311
Vignesh Venkatasubramanian1a0130d2014-04-14 12:09:20 -0700312void SampleMuxerMetadata::WriteCueIdentifier(const std::string& identifier,
313 std::string* pf) {
Matthew Heaney7ef225d2012-08-14 16:40:33 -0700314 pf->append(identifier);
315 pf->push_back('\x0A'); // LF
316}
317
Vignesh Venkatasubramanian1a0130d2014-04-14 12:09:20 -0700318void SampleMuxerMetadata::WriteCueSettings(const cue_t::settings_t& settings,
319 std::string* pf) {
Matthew Heaney7ef225d2012-08-14 16:40:33 -0700320 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
Vignesh Venkatasubramanian1a0130d2014-04-14 12:09:20 -0700346void SampleMuxerMetadata::WriteCuePayload(const cue_t::payload_t& payload,
347 std::string* pf) {
Matthew Heaney7ef225d2012-08-14 16:40:33 -0700348 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) {
Vignesh Venkatasubramanian09dd90f2013-06-12 11:23:35 -0700354 const std::string& line = *i++;
Matthew Heaney7ef225d2012-08-14 16:40:33 -0700355 pf->append(line);
356 pf->push_back('\x0A'); // LF
357 }
358}
359
Vignesh Venkatasubramanian1a0130d2014-04-14 12:09:20 -0700360bool SampleMuxerMetadata::SortableCue::Write(mkvmuxer::Segment* segment) const {
Matthew Heaney7ef225d2012-08-14 16:40:33 -0700361 // Cue start time expressed in milliseconds
Tom Finegan12f6dc32016-03-16 21:14:26 -0700362 const int64_t start_ms = cue.start_time.presentation();
Matthew Heaney7ef225d2012-08-14 16:40:33 -0700363
364 // Cue start time expressed in nanoseconds (MKV time)
Tom Finegan12f6dc32016-03-16 21:14:26 -0700365 const int64_t start_ns = start_ms * 1000000;
Matthew Heaney7ef225d2012-08-14 16:40:33 -0700366
367 // Cue stop time expressed in milliseconds
Tom Finegan12f6dc32016-03-16 21:14:26 -0700368 const int64_t stop_ms = cue.stop_time.presentation();
Matthew Heaney7ef225d2012-08-14 16:40:33 -0700369
370 // Cue stop time expressed in nanonseconds
Tom Finegan12f6dc32016-03-16 21:14:26 -0700371 const int64_t stop_ns = stop_ms * 1000000;
Matthew Heaney7ef225d2012-08-14 16:40:33 -0700372
373 // Metadata blocks always specify the block duration.
Tom Finegan12f6dc32016-03-16 21:14:26 -0700374 const int64_t duration_ns = stop_ns - start_ns;
Matthew Heaney7ef225d2012-08-14 16:40:33 -0700375
Vignesh Venkatasubramanian09dd90f2013-06-12 11:23:35 -0700376 std::string frame;
Matthew Heaney7ef225d2012-08-14 16:40:33 -0700377 MakeFrame(cue, &frame);
378
Tom Finegan12f6dc32016-03-16 21:14:26 -0700379 typedef const uint8_t* data_t;
Matthew Heaney7ef225d2012-08-14 16:40:33 -0700380 const data_t buf = reinterpret_cast<data_t>(frame.data());
Tom Finegan12f6dc32016-03-16 21:14:26 -0700381 const uint64_t len = frame.length();
Matthew Heaney7ef225d2012-08-14 16:40:33 -0700382
Vignesh Venkatasubramaniana9e48192015-05-07 16:25:18 -0700383 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);
Tom Finegancbe5c402016-03-21 12:16:30 -0700391}