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