mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2025-01-19 23:55:42 +00:00
closedcaption: Add closed caption plugin with an MCC parser and encoder
This commit is contained in:
parent
e930133bdf
commit
774110ec0a
11 changed files with 3462 additions and 0 deletions
|
@ -8,6 +8,7 @@ members = [
|
|||
"gst-plugin-togglerecord",
|
||||
"gst-plugin-threadshare",
|
||||
"gst-plugin-tutorial",
|
||||
"gst-plugin-closedcaption",
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
|
|
26
gst-plugin-closedcaption/Cargo.toml
Normal file
26
gst-plugin-closedcaption/Cargo.toml
Normal file
|
@ -0,0 +1,26 @@
|
|||
[package]
|
||||
name = "gst-plugin-closedcaption"
|
||||
version = "0.4.0"
|
||||
authors = ["Sebastian Dröge <sebastian@centricular.com>", "Jordan Petridis <jordan@centricular.com>"]
|
||||
license = "LGPL-2.1+"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
glib = { git = "https://github.com/gtk-rs/glib", features = ["subclassing"] }
|
||||
gstreamer = { git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["subclassing", "v1_10"] }
|
||||
gstreamer-base = { git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["subclassing", "v1_10"] }
|
||||
gstreamer-video = { git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_10"] }
|
||||
combine = "3.6"
|
||||
either = "1"
|
||||
uuid = { version = "0.7", features = ["v4"] }
|
||||
chrono = "0.4"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "0.5.1"
|
||||
gstreamer-check = { git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
||||
rand = "0.6"
|
||||
|
||||
[lib]
|
||||
name = "gstrsclosedcaption"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
path = "src/lib.rs"
|
58
gst-plugin-closedcaption/src/lib.rs
Normal file
58
gst-plugin-closedcaption/src/lib.rs
Normal file
|
@ -0,0 +1,58 @@
|
|||
// Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
|
||||
// Boston, MA 02110-1335, USA.
|
||||
|
||||
#![crate_type = "cdylib"]
|
||||
|
||||
#[macro_use]
|
||||
extern crate glib;
|
||||
#[macro_use]
|
||||
extern crate gstreamer as gst;
|
||||
extern crate gstreamer_base as gst_base;
|
||||
extern crate gstreamer_video as gst_video;
|
||||
|
||||
extern crate combine;
|
||||
extern crate either;
|
||||
|
||||
extern crate chrono;
|
||||
extern crate uuid;
|
||||
|
||||
#[cfg(test)]
|
||||
#[macro_use]
|
||||
extern crate pretty_assertions;
|
||||
|
||||
mod line_reader;
|
||||
mod mcc_enc;
|
||||
mod mcc_parse;
|
||||
mod mcc_parser;
|
||||
|
||||
fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||
mcc_parse::register(plugin)?;
|
||||
mcc_enc::register(plugin)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
gst_plugin_define!(
|
||||
"rsclosedcaption",
|
||||
"Rust Closed Caption Plugin",
|
||||
plugin_init,
|
||||
"0.1.0",
|
||||
"LGPL",
|
||||
"rsclosedcaption",
|
||||
"rsclosedcaption",
|
||||
"https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs",
|
||||
"2018-12-17"
|
||||
);
|
333
gst-plugin-closedcaption/src/line_reader.rs
Normal file
333
gst-plugin-closedcaption/src/line_reader.rs
Normal file
|
@ -0,0 +1,333 @@
|
|||
// Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
|
||||
// Boston, MA 02110-1335, USA.
|
||||
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LineReader<T: AsRef<[u8]>> {
|
||||
queue: VecDeque<T>,
|
||||
// Read position into the queue in bytes
|
||||
read_pos: usize,
|
||||
// Offset into queue where we have to look for a newline
|
||||
// All previous items don't contain a newline
|
||||
search_pos: usize,
|
||||
buf: Vec<u8>,
|
||||
}
|
||||
|
||||
impl<T: AsRef<[u8]>> LineReader<T> {
|
||||
pub fn new() -> LineReader<T> {
|
||||
Self {
|
||||
queue: VecDeque::new(),
|
||||
read_pos: 0,
|
||||
search_pos: 0,
|
||||
buf: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, b: T) {
|
||||
self.queue.push_back(b);
|
||||
}
|
||||
|
||||
/// Drops everything from the internal queue that was previously returned, i.e.
|
||||
/// if previously a line was returned we drop this whole line so that we can
|
||||
/// proceed with the next line
|
||||
fn drop_previous_line(&mut self) {
|
||||
// Drop everything we read the last time now
|
||||
while self.read_pos > 0
|
||||
&& self.read_pos >= self.queue.front().map(|f| f.as_ref().len()).unwrap_or(0)
|
||||
{
|
||||
self.read_pos -= self.queue.front().map(|f| f.as_ref().len()).unwrap_or(0);
|
||||
self.queue.pop_front();
|
||||
if self.search_pos > 0 {
|
||||
self.search_pos -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
self.buf.clear();
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn get_line_or_drain(&mut self) -> Option<&[u8]> {
|
||||
self.get_line_with_drain(true)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn get_line(&mut self) -> Option<&[u8]> {
|
||||
self.get_line_with_drain(false)
|
||||
}
|
||||
|
||||
/// Searches the first '\n' in the currently queued buffers and returns the index in buffers
|
||||
/// inside the queue and the index in bytes from the beginning of the queue, or None.
|
||||
///
|
||||
/// Also updates the search_pos so that we don't look again in the previous buffers on the next
|
||||
/// call
|
||||
fn find_newline(&mut self) -> Option<(usize, usize)> {
|
||||
let mut offset = 0;
|
||||
for (idx, buf) in self.queue.iter().enumerate() {
|
||||
let buf = buf.as_ref();
|
||||
|
||||
// Fast skip-ahead
|
||||
if idx < self.search_pos {
|
||||
offset += buf.len();
|
||||
continue;
|
||||
}
|
||||
|
||||
let pos = buf
|
||||
.iter()
|
||||
.enumerate()
|
||||
.skip(if idx == 0 { self.read_pos } else { 0 })
|
||||
.find(|(_, b)| **b == b'\n')
|
||||
.map(|(idx, _)| idx);
|
||||
|
||||
if let Some(pos) = pos {
|
||||
// On the next call we have to search in this buffer again
|
||||
// as it might contain a second newline
|
||||
self.search_pos = idx;
|
||||
return Some((idx, offset + pos + 1));
|
||||
}
|
||||
|
||||
// This buffer did not contain a newline so we don't have to look
|
||||
// in it again next time
|
||||
self.search_pos = idx + 1;
|
||||
offset += buf.len();
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Copies length bytes from all buffers from the beginning until last_idx into our internal
|
||||
/// buffer, and skips the first offset bytes from the first buffer.
|
||||
fn copy_into_internal_buffer(&mut self, last_idx: usize, offset: usize, len: usize) {
|
||||
// Reserve space for the whole line beforehand
|
||||
if self.buf.capacity() < len {
|
||||
self.buf.reserve(len - self.buf.capacity());
|
||||
}
|
||||
|
||||
// Then iterate over all buffers inside the queue until the one that contains
|
||||
// the newline character
|
||||
for (idx, buf) in self.queue.iter().enumerate().take(last_idx + 1) {
|
||||
let buf = buf.as_ref();
|
||||
|
||||
// Calculate how much data we still have to copy
|
||||
let rem = len - self.buf.len();
|
||||
assert!(rem > 0);
|
||||
|
||||
// For the first index we need to take into account the offset. The first
|
||||
// bytes might have to be skipped and as such we have fewer bytes available
|
||||
// than the whole length of the buffer
|
||||
let buf_len = if idx == 0 {
|
||||
assert!(offset < buf.len());
|
||||
buf.len() - offset
|
||||
} else {
|
||||
buf.len()
|
||||
};
|
||||
|
||||
// Calculate how much we can copy from this buffer. At most the size of the buffer
|
||||
// itself, but never more than the amount we still have to copy overall
|
||||
let copy_len = if rem > buf_len { buf_len } else { rem };
|
||||
assert!(copy_len > 0);
|
||||
|
||||
if idx == 0 {
|
||||
self.buf
|
||||
.extend_from_slice(&buf[offset..(offset + copy_len)]);
|
||||
} else {
|
||||
self.buf.extend_from_slice(&buf[..copy_len]);
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(self.buf.len(), len);
|
||||
}
|
||||
|
||||
pub fn get_line_with_drain(&mut self, drain: bool) -> Option<&[u8]> {
|
||||
// Drop all data from the previous line
|
||||
self.drop_previous_line();
|
||||
|
||||
// read_pos must always be inside the first buffer of our queue here
|
||||
// or otherwise we went into an inconsistent state: the first buffer(s)
|
||||
// would've had to be dropped above then as they are not relevant anymore
|
||||
assert!(
|
||||
self.read_pos == 0
|
||||
|| self.read_pos < self.queue.front().map(|f| f.as_ref().len()).unwrap_or(0)
|
||||
);
|
||||
|
||||
// Find the next newline character from our previous position
|
||||
if let Some((idx, pos)) = self.find_newline() {
|
||||
// We now need to copy everything from the old read_pos to the new
|
||||
// pos, and on the next call we have to start from the new pos
|
||||
let old_read_pos = self.read_pos;
|
||||
self.read_pos = pos;
|
||||
|
||||
assert!(self.read_pos > old_read_pos);
|
||||
assert!(idx < self.queue.len());
|
||||
|
||||
// If the newline is found in the first buffer in our queue, we can directly return
|
||||
// the slice from it without doing any copying.
|
||||
//
|
||||
// On average this should be the most common case.
|
||||
if idx == 0 {
|
||||
let buf = self.queue.front().unwrap().as_ref();
|
||||
return Some(&buf[old_read_pos..self.read_pos]);
|
||||
} else {
|
||||
// Copy into our internal buffer as the current line spans multiple buffers
|
||||
let len = self.read_pos - old_read_pos;
|
||||
self.copy_into_internal_buffer(idx, old_read_pos, len);
|
||||
|
||||
return Some(&self.buf[0..len]);
|
||||
}
|
||||
}
|
||||
|
||||
// No newline found above and we're not draining, so let's wait until
|
||||
// more data is available that might contain a newline character
|
||||
if !drain {
|
||||
return None;
|
||||
}
|
||||
|
||||
if self.queue.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// When draining and we only have a single buffer in the queue we can
|
||||
// directly return a slice into it
|
||||
if self.queue.len() == 1 {
|
||||
let res = &self.queue.front().unwrap().as_ref()[self.read_pos..];
|
||||
self.read_pos += res.len();
|
||||
self.search_pos = 1;
|
||||
return Some(res);
|
||||
}
|
||||
|
||||
// Otherwise we have to copy everything that is remaining into our
|
||||
// internal buffer and then return a slice from that
|
||||
let len = self.queue.iter().map(|v| v.as_ref().len()).sum();
|
||||
if self.buf.capacity() < len {
|
||||
self.buf.reserve(len - self.buf.capacity());
|
||||
}
|
||||
|
||||
for (idx, ref v) in self.queue.iter().enumerate() {
|
||||
if idx == 0 {
|
||||
self.buf.extend_from_slice(&v.as_ref()[self.read_pos..]);
|
||||
} else {
|
||||
self.buf.extend_from_slice(v.as_ref());
|
||||
}
|
||||
}
|
||||
|
||||
self.read_pos += self.buf.len();
|
||||
self.search_pos = self.queue.len();
|
||||
|
||||
Some(self.buf.as_ref())
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.queue.clear();
|
||||
self.read_pos = 0;
|
||||
self.search_pos = 0;
|
||||
self.buf.clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::LineReader;
|
||||
|
||||
#[test]
|
||||
fn test_single_buffer() {
|
||||
let mut r = LineReader::new();
|
||||
r.push(Vec::from(b"abcd\nefgh\nijkl\n".as_ref()));
|
||||
|
||||
assert_eq!(r.get_line(), Some(b"abcd\n".as_ref()));
|
||||
assert_eq!(r.get_line(), Some(b"efgh\n".as_ref()));
|
||||
assert_eq!(r.get_line(), Some(b"ijkl\n".as_ref()));
|
||||
assert_eq!(r.get_line(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_line() {
|
||||
let mut r = LineReader::new();
|
||||
r.push(Vec::from(b"abcd\nefgh\n\nijkl\n".as_ref()));
|
||||
|
||||
assert_eq!(r.get_line(), Some(b"abcd\n".as_ref()));
|
||||
assert_eq!(r.get_line(), Some(b"efgh\n".as_ref()));
|
||||
assert_eq!(r.get_line(), Some(b"\n".as_ref()));
|
||||
assert_eq!(r.get_line(), Some(b"ijkl\n".as_ref()));
|
||||
assert_eq!(r.get_line(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multi_buffer_split() {
|
||||
let mut r = LineReader::new();
|
||||
r.push(Vec::from(b"abcd\nef".as_ref()));
|
||||
r.push(Vec::from(b"gh\nijkl\n".as_ref()));
|
||||
|
||||
assert_eq!(r.get_line(), Some(b"abcd\n".as_ref()));
|
||||
assert_eq!(r.get_line(), Some(b"efgh\n".as_ref()));
|
||||
assert_eq!(r.get_line(), Some(b"ijkl\n".as_ref()));
|
||||
assert_eq!(r.get_line(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multi_buffer_split_2() {
|
||||
let mut r = LineReader::new();
|
||||
r.push(Vec::from(b"abcd\ne".as_ref()));
|
||||
r.push(Vec::from(b"f".as_ref()));
|
||||
r.push(Vec::from(b"g".as_ref()));
|
||||
r.push(Vec::from(b"h\nijkl\n".as_ref()));
|
||||
|
||||
assert_eq!(r.get_line(), Some(b"abcd\n".as_ref()));
|
||||
assert_eq!(r.get_line(), Some(b"efgh\n".as_ref()));
|
||||
assert_eq!(r.get_line(), Some(b"ijkl\n".as_ref()));
|
||||
assert_eq!(r.get_line(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single_buffer_drain() {
|
||||
let mut r = LineReader::new();
|
||||
r.push(Vec::from(b"abcd\nefgh\nijkl".as_ref()));
|
||||
|
||||
assert_eq!(r.get_line(), Some(b"abcd\n".as_ref()));
|
||||
assert_eq!(r.get_line(), Some(b"efgh\n".as_ref()));
|
||||
assert_eq!(r.get_line(), None);
|
||||
assert_eq!(r.get_line_or_drain(), Some(b"ijkl".as_ref()));
|
||||
assert_eq!(r.get_line_or_drain(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single_buffer_drain_multi_line() {
|
||||
let mut r = LineReader::new();
|
||||
r.push(Vec::from(b"abcd\nefgh\n".as_ref()));
|
||||
r.push(Vec::from(b"ijkl".as_ref()));
|
||||
|
||||
assert_eq!(r.get_line(), Some(b"abcd\n".as_ref()));
|
||||
assert_eq!(r.get_line(), Some(b"efgh\n".as_ref()));
|
||||
assert_eq!(r.get_line(), None);
|
||||
assert_eq!(r.get_line_or_drain(), Some(b"ijkl".as_ref()));
|
||||
assert_eq!(r.get_line_or_drain(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single_buffer_drain_multi_line_2() {
|
||||
let mut r = LineReader::new();
|
||||
r.push(Vec::from(b"abcd\nefgh\ni".as_ref()));
|
||||
r.push(Vec::from(b"j".as_ref()));
|
||||
r.push(Vec::from(b"k".as_ref()));
|
||||
r.push(Vec::from(b"l".as_ref()));
|
||||
|
||||
assert_eq!(r.get_line(), Some(b"abcd\n".as_ref()));
|
||||
assert_eq!(r.get_line(), Some(b"efgh\n".as_ref()));
|
||||
assert_eq!(r.get_line(), None);
|
||||
assert_eq!(r.get_line_or_drain(), Some(b"ijkl".as_ref()));
|
||||
assert_eq!(r.get_line_or_drain(), None);
|
||||
}
|
||||
}
|
641
gst-plugin-closedcaption/src/mcc_enc.rs
Normal file
641
gst-plugin-closedcaption/src/mcc_enc.rs
Normal file
|
@ -0,0 +1,641 @@
|
|||
// Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
|
||||
// Boston, MA 02110-1335, USA.
|
||||
|
||||
use glib;
|
||||
use glib::prelude::*;
|
||||
use glib::subclass;
|
||||
use glib::subclass::prelude::*;
|
||||
use gst;
|
||||
use gst::prelude::*;
|
||||
use gst::subclass::prelude::*;
|
||||
use gst_video;
|
||||
|
||||
use chrono::prelude::*;
|
||||
use uuid::Uuid;
|
||||
|
||||
use std::io::Write;
|
||||
use std::sync::Mutex;
|
||||
|
||||
#[path = "mcc_enc_headers.rs"]
|
||||
mod mcc_enc_headers;
|
||||
use self::mcc_enc_headers::*;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum Format {
|
||||
Cea708Cdp,
|
||||
Cea608,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct State {
|
||||
format: Option<Format>,
|
||||
need_headers: bool,
|
||||
start_position: gst::ClockTime,
|
||||
last_position: gst::ClockTime,
|
||||
}
|
||||
|
||||
impl Default for State {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
format: None,
|
||||
need_headers: true,
|
||||
start_position: gst::CLOCK_TIME_NONE,
|
||||
last_position: gst::CLOCK_TIME_NONE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Settings {
|
||||
uuid: Option<String>,
|
||||
creation_date: Option<glib::DateTime>,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
uuid: None,
|
||||
creation_date: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static PROPERTIES: [subclass::Property; 2] = [
|
||||
subclass::Property("uuid", |name| {
|
||||
glib::ParamSpec::string(
|
||||
name,
|
||||
"UUID",
|
||||
"UUID for the output file",
|
||||
None,
|
||||
glib::ParamFlags::READWRITE,
|
||||
)
|
||||
}),
|
||||
subclass::Property("creation-date", |name| {
|
||||
glib::ParamSpec::boxed(
|
||||
name,
|
||||
"Creation Date",
|
||||
"Creation date for the output file",
|
||||
glib::DateTime::static_type(),
|
||||
glib::ParamFlags::READWRITE,
|
||||
)
|
||||
}),
|
||||
];
|
||||
|
||||
struct MccEnc {
|
||||
cat: gst::DebugCategory,
|
||||
srcpad: gst::Pad,
|
||||
sinkpad: gst::Pad,
|
||||
state: Mutex<State>,
|
||||
settings: Mutex<Settings>,
|
||||
}
|
||||
|
||||
impl MccEnc {
|
||||
fn set_pad_functions(sinkpad: &gst::Pad, srcpad: &gst::Pad) {
|
||||
sinkpad.set_chain_function(|pad, parent, buffer| {
|
||||
MccEnc::catch_panic_pad_function(
|
||||
parent,
|
||||
|| Err(gst::FlowError::Error),
|
||||
|enc, element| enc.sink_chain(pad, element, buffer),
|
||||
)
|
||||
});
|
||||
sinkpad.set_event_function(|pad, parent, event| {
|
||||
MccEnc::catch_panic_pad_function(
|
||||
parent,
|
||||
|| false,
|
||||
|enc, element| enc.sink_event(pad, element, event),
|
||||
)
|
||||
});
|
||||
|
||||
srcpad.set_event_function(|pad, parent, event| {
|
||||
MccEnc::catch_panic_pad_function(
|
||||
parent,
|
||||
|| false,
|
||||
|enc, element| enc.src_event(pad, element, event),
|
||||
)
|
||||
});
|
||||
srcpad.set_query_function(|pad, parent, query| {
|
||||
MccEnc::catch_panic_pad_function(
|
||||
parent,
|
||||
|| false,
|
||||
|enc, element| enc.src_query(pad, element, query),
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
fn generate_headers(&self, _state: &State, buffer: &mut Vec<u8>) -> Result<(), gst::FlowError> {
|
||||
let settings = self.settings.lock().unwrap();
|
||||
|
||||
let caps = self
|
||||
.sinkpad
|
||||
.get_current_caps()
|
||||
.ok_or(gst::FlowError::NotNegotiated)?;
|
||||
let framerate = caps
|
||||
.get_structure(0)
|
||||
.unwrap()
|
||||
.get::<gst::Fraction>("framerate")
|
||||
.ok_or(gst::FlowError::NotNegotiated)?;
|
||||
|
||||
if framerate == gst::Fraction::new(60000, 1001) {
|
||||
buffer.extend_from_slice(PREAMBLE_V2);
|
||||
} else {
|
||||
buffer.extend_from_slice(PREAMBLE_V1);
|
||||
}
|
||||
|
||||
if let Some(ref uuid) = settings.uuid {
|
||||
let _ = write!(buffer, "UUID={}\r\n", uuid);
|
||||
} else {
|
||||
let _ = write!(buffer, "UUID={:X}\r\n", Uuid::new_v4().to_hyphenated());
|
||||
}
|
||||
|
||||
let _ = write!(
|
||||
buffer,
|
||||
"Creation Program=GStreamer MCC Encoder {}\r\n",
|
||||
env!("CARGO_PKG_VERSION")
|
||||
);
|
||||
|
||||
if let Some(ref creation_date) = settings.creation_date {
|
||||
let creation_date = Utc
|
||||
.ymd(
|
||||
creation_date.get_year() as i32,
|
||||
creation_date.get_month() as u32,
|
||||
creation_date.get_day_of_month() as u32,
|
||||
)
|
||||
.and_hms(
|
||||
creation_date.get_hour() as u32,
|
||||
creation_date.get_minute() as u32,
|
||||
creation_date.get_seconds() as u32,
|
||||
)
|
||||
.with_timezone(&FixedOffset::east(
|
||||
(creation_date.get_utc_offset() / 1_000_000) as i32,
|
||||
));
|
||||
|
||||
let _ = write!(
|
||||
buffer,
|
||||
"Creation Date={}\r\n",
|
||||
creation_date.format("%A, %B %d, %Y")
|
||||
);
|
||||
let _ = write!(
|
||||
buffer,
|
||||
"Creation Time={}\r\n",
|
||||
creation_date.format("%H:%M:%S")
|
||||
);
|
||||
} else {
|
||||
let creation_date = Local::now();
|
||||
let _ = write!(
|
||||
buffer,
|
||||
"Creation Date={}\r\n",
|
||||
creation_date.format("%A, %B %d, %Y")
|
||||
);
|
||||
let _ = write!(
|
||||
buffer,
|
||||
"Creation Time={}\r\n",
|
||||
creation_date.format("%H:%M:%S")
|
||||
);
|
||||
}
|
||||
|
||||
if *framerate.denom() == 1 {
|
||||
let _ = write!(buffer, "Time Code Rate={}\r\n", *framerate.numer());
|
||||
} else {
|
||||
assert_eq!(*framerate.denom(), 1001);
|
||||
let _ = write!(buffer, "Time Code Rate={}DF\r\n", *framerate.numer() / 1000);
|
||||
}
|
||||
let _ = write!(buffer, "\r\n");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encode_payload(outbuf: &mut Vec<u8>, mut slice: &[u8]) {
|
||||
while !slice.is_empty() {
|
||||
if slice.starts_with(
|
||||
[
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA,
|
||||
0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00,
|
||||
0x00,
|
||||
]
|
||||
.as_ref(),
|
||||
) {
|
||||
outbuf.push(b'O');
|
||||
slice = &slice[27..];
|
||||
} else if slice.starts_with(
|
||||
[
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA,
|
||||
0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
]
|
||||
.as_ref(),
|
||||
) {
|
||||
outbuf.push(b'N');
|
||||
slice = &slice[24..];
|
||||
} else if slice.starts_with(
|
||||
[
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA,
|
||||
0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
]
|
||||
.as_ref(),
|
||||
) {
|
||||
outbuf.push(b'M');
|
||||
slice = &slice[21..];
|
||||
} else if slice.starts_with(
|
||||
[
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA,
|
||||
0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
]
|
||||
.as_ref(),
|
||||
) {
|
||||
outbuf.push(b'L');
|
||||
slice = &slice[18..];
|
||||
} else if slice.starts_with(
|
||||
[
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA,
|
||||
0x00, 0x00,
|
||||
]
|
||||
.as_ref(),
|
||||
) {
|
||||
outbuf.push(b'K');
|
||||
slice = &slice[15..];
|
||||
} else if slice.starts_with(
|
||||
[
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
]
|
||||
.as_ref(),
|
||||
) {
|
||||
outbuf.push(b'J');
|
||||
slice = &slice[12..];
|
||||
} else if slice
|
||||
.starts_with([0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00].as_ref())
|
||||
{
|
||||
outbuf.push(b'I');
|
||||
slice = &slice[9..];
|
||||
} else if slice.starts_with([0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00].as_ref()) {
|
||||
outbuf.push(b'H');
|
||||
slice = &slice[6..];
|
||||
} else if slice.starts_with([0xFA, 0x00, 0x00].as_ref()) {
|
||||
outbuf.push(b'G');
|
||||
slice = &slice[3..];
|
||||
} else if slice.starts_with([0xFB, 0x80, 0x80].as_ref()) {
|
||||
outbuf.push(b'P');
|
||||
slice = &slice[3..];
|
||||
} else if slice.starts_with([0xFC, 0x80, 0x80].as_ref()) {
|
||||
outbuf.push(b'Q');
|
||||
slice = &slice[3..];
|
||||
} else if slice.starts_with([0xFD, 0x80, 0x80].as_ref()) {
|
||||
outbuf.push(b'R');
|
||||
slice = &slice[3..];
|
||||
} else if slice.starts_with([0x96, 0x69].as_ref()) {
|
||||
outbuf.push(b'S');
|
||||
slice = &slice[2..];
|
||||
} else if slice.starts_with([0x61, 0x01].as_ref()) {
|
||||
outbuf.push(b'T');
|
||||
slice = &slice[2..];
|
||||
} else if slice.starts_with([0xE1, 0x00, 0x00, 0x00].as_ref()) {
|
||||
outbuf.push(b'U');
|
||||
slice = &slice[4..];
|
||||
} else if slice[0] == 0x00 {
|
||||
outbuf.push(b'Z');
|
||||
slice = &slice[1..];
|
||||
} else {
|
||||
let _ = write!(outbuf, "{:02X}", slice[0]);
|
||||
slice = &slice[1..];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_caption(
|
||||
&self,
|
||||
element: &gst::Element,
|
||||
state: &State,
|
||||
buffer: &gst::Buffer,
|
||||
outbuf: &mut Vec<u8>,
|
||||
) -> Result<(), gst::FlowError> {
|
||||
let meta = buffer
|
||||
.get_meta::<gst_video::VideoTimeCodeMeta>()
|
||||
.ok_or_else(|| {
|
||||
gst_element_error!(
|
||||
element,
|
||||
gst::StreamError::Format,
|
||||
["Stream with timecodes on each buffer required"]
|
||||
);
|
||||
|
||||
gst::FlowError::Error
|
||||
})?;
|
||||
|
||||
let _ = write!(outbuf, "{}\t", meta.get_tc());
|
||||
|
||||
let map = buffer.map_readable().ok_or_else(|| {
|
||||
gst_element_error!(
|
||||
element,
|
||||
gst::StreamError::Format,
|
||||
["Failed to map buffer readable"]
|
||||
);
|
||||
|
||||
gst::FlowError::Error
|
||||
})?;
|
||||
|
||||
let len = map.len();
|
||||
if len >= 256 {
|
||||
gst_element_error!(
|
||||
element,
|
||||
gst::StreamError::Format,
|
||||
["Too big buffer: {}", map.len()]
|
||||
);
|
||||
|
||||
return Err(gst::FlowError::Error);
|
||||
}
|
||||
let len = len as u8;
|
||||
|
||||
match state.format {
|
||||
Some(Format::Cea608) => {
|
||||
let _ = write!(outbuf, "6102{:02X}", len);
|
||||
}
|
||||
Some(Format::Cea708Cdp) => {
|
||||
let _ = write!(outbuf, "T{:02X}", len);
|
||||
}
|
||||
_ => return Err(gst::FlowError::NotNegotiated),
|
||||
};
|
||||
|
||||
let checksum = map.iter().fold(0u8, |sum, b| sum.wrapping_add(*b));
|
||||
Self::encode_payload(outbuf, &*map);
|
||||
|
||||
if checksum == 0 {
|
||||
outbuf.push(b'Z');
|
||||
} else {
|
||||
let _ = write!(outbuf, "{:02X}", checksum);
|
||||
}
|
||||
|
||||
outbuf.extend_from_slice(b"\r\n".as_ref());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sink_chain(
|
||||
&self,
|
||||
pad: &gst::Pad,
|
||||
element: &gst::Element,
|
||||
buffer: gst::Buffer,
|
||||
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
gst_log!(self.cat, obj: pad, "Handling buffer {:?}", buffer);
|
||||
|
||||
let mut state = self.state.lock().unwrap();
|
||||
|
||||
let mut outbuf = Vec::new();
|
||||
if state.need_headers {
|
||||
state.need_headers = false;
|
||||
self.generate_headers(&*state, &mut outbuf)?;
|
||||
}
|
||||
|
||||
self.generate_caption(element, &*state, &buffer, &mut outbuf)?;
|
||||
drop(state);
|
||||
|
||||
self.srcpad.push(gst::Buffer::from_mut_slice(outbuf))
|
||||
}
|
||||
|
||||
fn sink_event(&self, pad: &gst::Pad, element: &gst::Element, event: gst::Event) -> bool {
|
||||
use gst::EventView;
|
||||
|
||||
gst_log!(self.cat, obj: pad, "Handling event {:?}", event);
|
||||
|
||||
match event.view() {
|
||||
EventView::Caps(ev) => {
|
||||
let caps = ev.get_caps();
|
||||
let s = caps.get_structure(0).unwrap();
|
||||
let framerate = if let Some(framerate) = s.get::<gst::Fraction>("framerate") {
|
||||
framerate
|
||||
} else {
|
||||
gst_error!(self.cat, obj: pad, "Caps without framerate");
|
||||
return false;
|
||||
};
|
||||
|
||||
let mut state = self.state.lock().unwrap();
|
||||
if s.get_name() == "closedcaption/x-cea-608" {
|
||||
state.format = Some(Format::Cea608);
|
||||
} else {
|
||||
state.format = Some(Format::Cea708Cdp);
|
||||
}
|
||||
drop(state);
|
||||
|
||||
// We send our own caps downstream
|
||||
let caps = gst::Caps::builder("application/x-mcc")
|
||||
.field(
|
||||
"version",
|
||||
if framerate == gst::Fraction::new(60000, 1001) {
|
||||
&2i32
|
||||
} else {
|
||||
&1i32
|
||||
},
|
||||
)
|
||||
.build();
|
||||
self.srcpad.push_event(gst::Event::new_caps(&caps).build())
|
||||
}
|
||||
_ => pad.event_default(element, event),
|
||||
}
|
||||
}
|
||||
|
||||
fn src_event(&self, pad: &gst::Pad, element: &gst::Element, event: gst::Event) -> bool {
|
||||
use gst::EventView;
|
||||
|
||||
gst_log!(self.cat, obj: pad, "Handling event {:?}", event);
|
||||
match event.view() {
|
||||
EventView::Seek(_) => {
|
||||
gst_log!(self.cat, obj: pad, "Dropping seek event");
|
||||
false
|
||||
}
|
||||
_ => pad.event_default(element, event),
|
||||
}
|
||||
}
|
||||
|
||||
fn src_query(&self, pad: &gst::Pad, element: &gst::Element, query: &mut gst::QueryRef) -> bool {
|
||||
use gst::QueryView;
|
||||
|
||||
gst_log!(self.cat, obj: pad, "Handling query {:?}", query);
|
||||
|
||||
match query.view_mut() {
|
||||
QueryView::Seeking(mut q) => {
|
||||
// We don't support any seeking at all
|
||||
let fmt = q.get_format();
|
||||
q.set(
|
||||
false,
|
||||
gst::GenericFormattedValue::Other(fmt, -1),
|
||||
gst::GenericFormattedValue::Other(fmt, -1),
|
||||
);
|
||||
true
|
||||
}
|
||||
QueryView::Position(ref mut q) => {
|
||||
// For Time answer ourselfs, otherwise forward
|
||||
if q.get_format() == gst::Format::Time {
|
||||
let state = self.state.lock().unwrap();
|
||||
q.set(state.last_position);
|
||||
true
|
||||
} else {
|
||||
self.sinkpad.peer_query(query)
|
||||
}
|
||||
}
|
||||
_ => pad.query_default(element, query),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectSubclass for MccEnc {
|
||||
const NAME: &'static str = "RsMccEnc";
|
||||
type ParentType = gst::Element;
|
||||
type Instance = gst::subclass::ElementInstanceStruct<Self>;
|
||||
type Class = subclass::simple::ClassStruct<Self>;
|
||||
|
||||
glib_object_subclass!();
|
||||
|
||||
fn new_with_class(klass: &subclass::simple::ClassStruct<Self>) -> Self {
|
||||
let templ = klass.get_pad_template("sink").unwrap();
|
||||
let sinkpad = gst::Pad::new_from_template(&templ, "sink");
|
||||
let templ = klass.get_pad_template("src").unwrap();
|
||||
let srcpad = gst::Pad::new_from_template(&templ, "src");
|
||||
|
||||
MccEnc::set_pad_functions(&sinkpad, &srcpad);
|
||||
|
||||
Self {
|
||||
cat: gst::DebugCategory::new(
|
||||
"mccenc",
|
||||
gst::DebugColorFlags::empty(),
|
||||
"Mcc Encoder Element",
|
||||
),
|
||||
srcpad,
|
||||
sinkpad,
|
||||
state: Mutex::new(State::default()),
|
||||
settings: Mutex::new(Settings::default()),
|
||||
}
|
||||
}
|
||||
|
||||
fn class_init(klass: &mut subclass::simple::ClassStruct<Self>) {
|
||||
klass.set_metadata(
|
||||
"Mcc Encoder",
|
||||
"Encoder/ClosedCaption",
|
||||
"Encodes MCC Closed Caption Files",
|
||||
"Sebastian Dröge <sebastian@centricular.com>",
|
||||
);
|
||||
|
||||
let mut caps = gst::Caps::new_empty();
|
||||
{
|
||||
let caps = caps.get_mut().unwrap();
|
||||
|
||||
let framerates = gst::List::new(&[
|
||||
&gst::Fraction::new(24, 1),
|
||||
&gst::Fraction::new(25, 1),
|
||||
&gst::Fraction::new(30000, 1001),
|
||||
&gst::Fraction::new(30, 1),
|
||||
&gst::Fraction::new(50, 1),
|
||||
&gst::Fraction::new(60000, 1001),
|
||||
&gst::Fraction::new(60, 1),
|
||||
]);
|
||||
|
||||
let s = gst::Structure::builder("closedcaption/x-cea-708")
|
||||
.field("format", &"cdp")
|
||||
.field("framerate", &framerates)
|
||||
.build();
|
||||
caps.append_structure(s);
|
||||
|
||||
let s = gst::Structure::builder("closedcaption/x-cea-608")
|
||||
.field("format", &"s334-1a")
|
||||
.field("framerate", &framerates)
|
||||
.build();
|
||||
caps.append_structure(s);
|
||||
}
|
||||
let sink_pad_template = gst::PadTemplate::new(
|
||||
"sink",
|
||||
gst::PadDirection::Sink,
|
||||
gst::PadPresence::Always,
|
||||
&caps,
|
||||
);
|
||||
klass.add_pad_template(sink_pad_template);
|
||||
|
||||
let caps = gst::Caps::builder("application/x-mcc").build();
|
||||
let src_pad_template = gst::PadTemplate::new(
|
||||
"src",
|
||||
gst::PadDirection::Src,
|
||||
gst::PadPresence::Always,
|
||||
&caps,
|
||||
);
|
||||
klass.add_pad_template(src_pad_template);
|
||||
|
||||
klass.install_properties(&PROPERTIES);
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for MccEnc {
|
||||
glib_object_impl!();
|
||||
|
||||
fn set_property(&self, _obj: &glib::Object, id: usize, value: &glib::Value) {
|
||||
let prop = &PROPERTIES[id];
|
||||
|
||||
match *prop {
|
||||
subclass::Property("uuid", ..) => {
|
||||
let mut settings = self.settings.lock().unwrap();
|
||||
settings.uuid = value.get();
|
||||
}
|
||||
subclass::Property("creation-date", ..) => {
|
||||
let mut settings = self.settings.lock().unwrap();
|
||||
settings.creation_date = value.get();
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_property(&self, _obj: &glib::Object, id: usize) -> Result<glib::Value, ()> {
|
||||
let prop = &PROPERTIES[id];
|
||||
|
||||
match *prop {
|
||||
subclass::Property("uuid", ..) => {
|
||||
let settings = self.settings.lock().unwrap();
|
||||
Ok(settings.uuid.to_value())
|
||||
}
|
||||
subclass::Property("creation-date", ..) => {
|
||||
let settings = self.settings.lock().unwrap();
|
||||
Ok(settings.creation_date.to_value())
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn constructed(&self, obj: &glib::Object) {
|
||||
self.parent_constructed(obj);
|
||||
|
||||
let element = obj.downcast_ref::<gst::Element>().unwrap();
|
||||
element.add_pad(&self.sinkpad).unwrap();
|
||||
element.add_pad(&self.srcpad).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl ElementImpl for MccEnc {
|
||||
fn change_state(
|
||||
&self,
|
||||
element: &gst::Element,
|
||||
transition: gst::StateChange,
|
||||
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||
gst_trace!(self.cat, obj: element, "Changing state {:?}", transition);
|
||||
|
||||
match transition {
|
||||
gst::StateChange::ReadyToPaused | gst::StateChange::PausedToReady => {
|
||||
// Reset the whole state
|
||||
let mut state = self.state.lock().unwrap();
|
||||
*state = State::default();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
self.parent_change_state(element, transition)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||
gst::Element::register(plugin, "mccenc", 0, MccEnc::get_type())
|
||||
}
|
95
gst-plugin-closedcaption/src/mcc_enc_headers.rs
Normal file
95
gst-plugin-closedcaption/src/mcc_enc_headers.rs
Normal file
|
@ -0,0 +1,95 @@
|
|||
// Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
|
||||
// Boston, MA 02110-1335, USA.
|
||||
|
||||
pub const PREAMBLE_V1: &[u8] = b"File Format=MacCaption_MCC V1.0\r\n\
|
||||
\r\n\
|
||||
///////////////////////////////////////////////////////////////////////////////////\r\n\
|
||||
// Computer Prompting and Captioning Company\r\n\
|
||||
// Ancillary Data Packet Transfer File\r\n\
|
||||
//\r\n\
|
||||
// Permission to generate this format is granted provided that\r\n\
|
||||
// 1. This ANC Transfer file format is used on an as-is basis and no warranty is given, and\r\n\
|
||||
// 2. This entire descriptive information text is included in a generated .mcc file.\r\n\
|
||||
//\r\n\
|
||||
// General file format:\r\n\
|
||||
// HH:MM:SS:FF(tab)[Hexadecimal ANC data in groups of 2 characters]\r\n\
|
||||
// Hexadecimal data starts with the Ancillary Data Packet DID (Data ID defined in S291M)\r\n\
|
||||
// and concludes with the Check Sum following the User Data Words.\r\n\
|
||||
// Each time code line must contain at most one complete ancillary data packet.\r\n\
|
||||
// To transfer additional ANC Data successive lines may contain identical time code.\r\n\
|
||||
// Time Code Rate=[24, 25, 30, 30DF, 50, 60]\r\n\
|
||||
//\r\n\
|
||||
// ANC data bytes may be represented by one ASCII character according to the following schema:\r\n\
|
||||
// G FAh 00h 00h\r\n\
|
||||
// H 2 x (FAh 00h 00h)\r\n\
|
||||
// I 3 x (FAh 00h 00h)\r\n\
|
||||
// J 4 x (FAh 00h 00h)\r\n\
|
||||
// K 5 x (FAh 00h 00h)\r\n\
|
||||
// L 6 x (FAh 00h 00h)\r\n\
|
||||
// M 7 x (FAh 00h 00h)\r\n\
|
||||
// N 8 x (FAh 00h 00h)\r\n\
|
||||
// O 9 x (FAh 00h 00h)\r\n\
|
||||
// P FBh 80h 80h\r\n\
|
||||
// Q FCh 80h 80h\r\n\
|
||||
// R FDh 80h 80h\r\n\
|
||||
// S 96h 69h\r\n\
|
||||
// T 61h 01h\r\n\
|
||||
// U E1h 00h 00h 00h\r\n\
|
||||
// Z 00h\r\n\
|
||||
//\r\n\
|
||||
///////////////////////////////////////////////////////////////////////////////////\r\n\
|
||||
\r\n";
|
||||
|
||||
pub const PREAMBLE_V2: &[u8] = b"File Format=MacCaption_MCC V2.0\r\n\
|
||||
\r\n\
|
||||
///////////////////////////////////////////////////////////////////////////////////\r\n\
|
||||
// Computer Prompting and Captioning Company\r\n\
|
||||
// Ancillary Data Packet Transfer File\r\n\
|
||||
//\r\n\
|
||||
// Permission to generate this format is granted provided that\r\n\
|
||||
// 1. This ANC Transfer file format is used on an as-is basis and no warranty is given, and\r\n\
|
||||
// 2. This entire descriptive information text is included in a generated .mcc file.\r\n\
|
||||
//\r\n\
|
||||
// General file format:\r\n\
|
||||
// HH:MM:SS:FF(tab)[Hexadecimal ANC data in groups of 2 characters]\r\n\
|
||||
// Hexadecimal data starts with the Ancillary Data Packet DID (Data ID defined in S291M)\r\n\
|
||||
// and concludes with the Check Sum following the User Data Words.\r\n\
|
||||
// Each time code line must contain at most one complete ancillary data packet.\r\n\
|
||||
// To transfer additional ANC Data successive lines may contain identical time code.\r\n\
|
||||
// Time Code Rate=[24, 25, 30, 30DF, 50, 60, 60DF]\r\n\
|
||||
// Time Code Rate=[24, 25, 30, 30DF, 50, 60]\r\n\
|
||||
//\r\n\
|
||||
// ANC data bytes may be represented by one ASCII character according to the following schema:\r\n\
|
||||
// G FAh 00h 00h\r\n\
|
||||
// H 2 x (FAh 00h 00h)\r\n\
|
||||
// I 3 x (FAh 00h 00h)\r\n\
|
||||
// J 4 x (FAh 00h 00h)\r\n\
|
||||
// K 5 x (FAh 00h 00h)\r\n\
|
||||
// L 6 x (FAh 00h 00h)\r\n\
|
||||
// M 7 x (FAh 00h 00h)\r\n\
|
||||
// N 8 x (FAh 00h 00h)\r\n\
|
||||
// O 9 x (FAh 00h 00h)\r\n\
|
||||
// P FBh 80h 80h\r\n\
|
||||
// Q FCh 80h 80h\r\n\
|
||||
// R FDh 80h 80h\r\n\
|
||||
// S 96h 69h\r\n\
|
||||
// T 61h 01h\r\n\
|
||||
// U E1h 00h 00h 00h\r\n\
|
||||
// Z 00h\r\n\
|
||||
//\r\n\
|
||||
///////////////////////////////////////////////////////////////////////////////////\r\n\
|
||||
\r\n";
|
614
gst-plugin-closedcaption/src/mcc_parse.rs
Normal file
614
gst-plugin-closedcaption/src/mcc_parse.rs
Normal file
|
@ -0,0 +1,614 @@
|
|||
// Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
|
||||
// Boston, MA 02110-1335, USA.
|
||||
|
||||
use glib;
|
||||
use glib::prelude::*;
|
||||
use glib::subclass;
|
||||
use glib::subclass::prelude::*;
|
||||
use gst;
|
||||
use gst::prelude::*;
|
||||
use gst::subclass::prelude::*;
|
||||
use gst_video;
|
||||
|
||||
use std::sync::Mutex;
|
||||
|
||||
use crate::line_reader::LineReader;
|
||||
use crate::mcc_parser::{MccLine, MccParser};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum Format {
|
||||
Cea708Cdp,
|
||||
Cea608,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct State {
|
||||
reader: LineReader<gst::MappedBuffer<gst::buffer::Readable>>,
|
||||
parser: MccParser,
|
||||
format: Option<Format>,
|
||||
need_segment: bool,
|
||||
pending_events: Vec<gst::Event>,
|
||||
start_position: gst::ClockTime,
|
||||
last_position: gst::ClockTime,
|
||||
last_timecode: Option<gst_video::ValidVideoTimeCode>,
|
||||
timecode_rate: Option<(u8, bool)>,
|
||||
}
|
||||
|
||||
impl Default for State {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
reader: LineReader::new(),
|
||||
parser: MccParser::new(),
|
||||
format: None,
|
||||
need_segment: true,
|
||||
pending_events: Vec::new(),
|
||||
start_position: gst::CLOCK_TIME_NONE,
|
||||
last_position: gst::CLOCK_TIME_NONE,
|
||||
last_timecode: None,
|
||||
timecode_rate: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn get_line(
|
||||
&mut self,
|
||||
drain: bool,
|
||||
) -> Result<
|
||||
Option<MccLine>,
|
||||
(
|
||||
&[u8],
|
||||
combine::easy::Errors<u8, &[u8], combine::stream::PointerOffset>,
|
||||
),
|
||||
> {
|
||||
let line = match self.reader.get_line_with_drain(drain) {
|
||||
None => {
|
||||
return Ok(None);
|
||||
}
|
||||
Some(line) => line,
|
||||
};
|
||||
|
||||
self.parser
|
||||
.parse_line(line)
|
||||
.map(Option::Some)
|
||||
.map_err(|err| (line, err))
|
||||
}
|
||||
}
|
||||
|
||||
struct MccParse {
|
||||
cat: gst::DebugCategory,
|
||||
srcpad: gst::Pad,
|
||||
sinkpad: gst::Pad,
|
||||
state: Mutex<State>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct OffsetVec {
|
||||
vec: Vec<u8>,
|
||||
offset: usize,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for OffsetVec {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.vec[self.offset..(self.offset + self.len)]
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<[u8]> for OffsetVec {
|
||||
fn as_mut(&mut self) -> &mut [u8] {
|
||||
&mut self.vec[self.offset..(self.offset + self.len)]
|
||||
}
|
||||
}
|
||||
|
||||
impl MccParse {
|
||||
fn set_pad_functions(sinkpad: &gst::Pad, srcpad: &gst::Pad) {
|
||||
sinkpad.set_chain_function(|pad, parent, buffer| {
|
||||
MccParse::catch_panic_pad_function(
|
||||
parent,
|
||||
|| Err(gst::FlowError::Error),
|
||||
|parse, element| parse.sink_chain(pad, element, buffer),
|
||||
)
|
||||
});
|
||||
sinkpad.set_event_function(|pad, parent, event| {
|
||||
MccParse::catch_panic_pad_function(
|
||||
parent,
|
||||
|| false,
|
||||
|parse, element| parse.sink_event(pad, element, event),
|
||||
)
|
||||
});
|
||||
|
||||
srcpad.set_event_function(|pad, parent, event| {
|
||||
MccParse::catch_panic_pad_function(
|
||||
parent,
|
||||
|| false,
|
||||
|parse, element| parse.src_event(pad, element, event),
|
||||
)
|
||||
});
|
||||
srcpad.set_query_function(|pad, parent, query| {
|
||||
MccParse::catch_panic_pad_function(
|
||||
parent,
|
||||
|| false,
|
||||
|parse, element| parse.src_query(pad, element, query),
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
fn handle_buffer(
|
||||
&self,
|
||||
element: &gst::Element,
|
||||
buffer: Option<gst::Buffer>,
|
||||
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
let mut state = self.state.lock().unwrap();
|
||||
|
||||
let drain;
|
||||
if let Some(buffer) = buffer {
|
||||
let buffer = buffer.into_mapped_buffer_readable().map_err(|_| {
|
||||
gst_element_error!(
|
||||
element,
|
||||
gst::ResourceError::Read,
|
||||
["Failed to map buffer readable"]
|
||||
);
|
||||
|
||||
gst::FlowError::Error
|
||||
})?;
|
||||
|
||||
state.reader.push(buffer);
|
||||
drain = false;
|
||||
} else {
|
||||
drain = true;
|
||||
}
|
||||
|
||||
loop {
|
||||
let line = state.get_line(drain);
|
||||
match line {
|
||||
Ok(Some(MccLine::Caption(tc, data))) => {
|
||||
gst_trace!(
|
||||
self.cat,
|
||||
obj: element,
|
||||
"Got caption buffer with timecode {:?} and size {}",
|
||||
tc,
|
||||
data.len()
|
||||
);
|
||||
|
||||
if data.len() < 3 {
|
||||
gst_debug!(
|
||||
self.cat,
|
||||
obj: element,
|
||||
"Too small caption packet: {}",
|
||||
data.len(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
let format = match (data[0], data[1]) {
|
||||
(0x61, 0x01) => Format::Cea708Cdp,
|
||||
(0x61, 0x02) => Format::Cea608,
|
||||
(did, sdid) => {
|
||||
gst_debug!(
|
||||
self.cat,
|
||||
obj: element,
|
||||
"Unknown DID {:x} SDID {:x}",
|
||||
did,
|
||||
sdid
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let len = data[2];
|
||||
if data.len() < 3 + len as usize {
|
||||
gst_debug!(
|
||||
self.cat,
|
||||
obj: element,
|
||||
"Too small caption packet: {} < {}",
|
||||
data.len(),
|
||||
3 + len,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
let (framerate, drop_frame) = match state.timecode_rate {
|
||||
Some((rate, false)) => (gst::Fraction::new(rate as i32, 1), false),
|
||||
Some((rate, true)) => (gst::Fraction::new(rate as i32 * 1000, 1001), true),
|
||||
None => {
|
||||
gst_element_error!(
|
||||
element,
|
||||
gst::StreamError::Decode,
|
||||
["Got caption before time code rate"]
|
||||
);
|
||||
|
||||
break Err(gst::FlowError::Error);
|
||||
}
|
||||
};
|
||||
|
||||
let mut events = Vec::new();
|
||||
|
||||
if state.format != Some(format) {
|
||||
state.format = Some(format);
|
||||
|
||||
let caps = match format {
|
||||
Format::Cea708Cdp => gst::Caps::builder("closedcaption/x-cea-708")
|
||||
.field("format", &"cdp")
|
||||
.field("framerate", &framerate)
|
||||
.build(),
|
||||
Format::Cea608 => gst::Caps::builder("closedcaption/x-cea-608")
|
||||
.field("format", &"s334-1a")
|
||||
.field("framerate", &framerate)
|
||||
.build(),
|
||||
};
|
||||
|
||||
events.push(gst::Event::new_caps(&caps).build());
|
||||
}
|
||||
|
||||
if state.need_segment {
|
||||
let segment = gst::FormattedSegment::<gst::format::Time>::new();
|
||||
events.push(gst::Event::new_segment(&segment).build());
|
||||
state.need_segment = false;
|
||||
}
|
||||
|
||||
events.extend(state.pending_events.drain(..));
|
||||
|
||||
let timecode = gst_video::VideoTimeCode::new(
|
||||
framerate,
|
||||
None,
|
||||
if drop_frame {
|
||||
gst_video::VideoTimeCodeFlags::DROP_FRAME
|
||||
} else {
|
||||
gst_video::VideoTimeCodeFlags::empty()
|
||||
},
|
||||
tc.hours,
|
||||
tc.minutes,
|
||||
tc.seconds,
|
||||
tc.frames,
|
||||
0,
|
||||
);
|
||||
|
||||
let timecode = match timecode.try_into() {
|
||||
Ok(timecode) => timecode,
|
||||
Err(timecode) => {
|
||||
let last_timecode =
|
||||
state.last_timecode.as_ref().map(Clone::clone).ok_or_else(
|
||||
|| {
|
||||
gst_element_error!(
|
||||
element,
|
||||
gst::StreamError::Decode,
|
||||
["Invalid first timecode {:?}", timecode]
|
||||
);
|
||||
|
||||
gst::FlowError::Error
|
||||
},
|
||||
)?;
|
||||
|
||||
gst_warning!(
|
||||
self.cat,
|
||||
obj: element,
|
||||
"Invalid timecode {:?}, using previous {:?}",
|
||||
timecode,
|
||||
last_timecode
|
||||
);
|
||||
|
||||
last_timecode
|
||||
}
|
||||
};
|
||||
|
||||
let mut buffer = gst::Buffer::from_mut_slice(OffsetVec {
|
||||
vec: data,
|
||||
offset: 3,
|
||||
len: len as usize,
|
||||
});
|
||||
|
||||
{
|
||||
let buffer = buffer.get_mut().unwrap();
|
||||
gst_video::VideoTimeCodeMeta::add(buffer, &timecode);
|
||||
|
||||
// Calculate a timestamp from the timecode and make sure to
|
||||
// not produce timestamps jumping backwards
|
||||
let nsecs = gst::ClockTime::from(timecode.nsec_since_daily_jam());
|
||||
if state.start_position.is_none() {
|
||||
state.start_position = nsecs;
|
||||
}
|
||||
|
||||
let nsecs = if nsecs < state.start_position {
|
||||
gst_fixme!(
|
||||
self.cat,
|
||||
obj: element,
|
||||
"New position {} < start position {}",
|
||||
nsecs,
|
||||
state.start_position
|
||||
);
|
||||
state.start_position
|
||||
} else {
|
||||
nsecs - state.start_position
|
||||
};
|
||||
|
||||
if nsecs >= state.last_position {
|
||||
state.last_position = nsecs;
|
||||
} else {
|
||||
gst_fixme!(
|
||||
self.cat,
|
||||
obj: element,
|
||||
"New position {} < last position {}",
|
||||
nsecs,
|
||||
state.last_position
|
||||
);
|
||||
}
|
||||
|
||||
buffer.set_pts(state.last_position);
|
||||
buffer.set_duration(
|
||||
gst::SECOND
|
||||
.mul_div_ceil(*framerate.denom() as u64, *framerate.numer() as u64)
|
||||
.unwrap_or(gst::CLOCK_TIME_NONE),
|
||||
);
|
||||
}
|
||||
|
||||
// Drop our state mutex while we push out buffers or events
|
||||
drop(state);
|
||||
|
||||
for event in events {
|
||||
gst_debug!(self.cat, obj: element, "Pushing event {:?}", event);
|
||||
self.srcpad.push_event(event);
|
||||
}
|
||||
|
||||
self.srcpad.push(buffer).map_err(|err| {
|
||||
gst_error!(self.cat, obj: element, "Pushing buffer returned {:?}", err);
|
||||
err
|
||||
})?;
|
||||
|
||||
state = self.state.lock().unwrap();
|
||||
}
|
||||
Ok(Some(MccLine::TimeCodeRate(rate, df))) => {
|
||||
gst_debug!(
|
||||
self.cat,
|
||||
obj: element,
|
||||
"Got timecode rate {} (drop frame {})",
|
||||
rate,
|
||||
df
|
||||
);
|
||||
state.timecode_rate = Some((rate, df));
|
||||
}
|
||||
Ok(Some(line)) => {
|
||||
gst_debug!(self.cat, obj: element, "Got line '{:?}'", line);
|
||||
}
|
||||
Err((line, err)) => {
|
||||
gst_element_error!(
|
||||
element,
|
||||
gst::StreamError::Decode,
|
||||
["Couldn't parse line '{:?}': {:?}", line, err]
|
||||
);
|
||||
|
||||
break Err(gst::FlowError::Error);
|
||||
}
|
||||
Ok(None) => break Ok(gst::FlowSuccess::Ok),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sink_chain(
|
||||
&self,
|
||||
pad: &gst::Pad,
|
||||
element: &gst::Element,
|
||||
buffer: gst::Buffer,
|
||||
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
||||
gst_log!(self.cat, obj: pad, "Handling buffer {:?}", buffer);
|
||||
|
||||
self.handle_buffer(element, Some(buffer))
|
||||
}
|
||||
|
||||
fn sink_event(&self, pad: &gst::Pad, element: &gst::Element, event: gst::Event) -> bool {
|
||||
use gst::EventView;
|
||||
|
||||
gst_log!(self.cat, obj: pad, "Handling event {:?}", event);
|
||||
|
||||
match event.view() {
|
||||
EventView::Caps(_) => {
|
||||
// We send a proper caps event from the chain function later
|
||||
gst_log!(self.cat, obj: pad, "Dropping caps event");
|
||||
true
|
||||
}
|
||||
EventView::Segment(_) => {
|
||||
// We send a gst::Format::Time segment event later when needed
|
||||
gst_log!(self.cat, obj: pad, "Dropping segment event");
|
||||
true
|
||||
}
|
||||
EventView::FlushStop(_) => {
|
||||
let mut state = self.state.lock().unwrap();
|
||||
state.reader.clear();
|
||||
state.parser.reset();
|
||||
state.need_segment = true;
|
||||
state.pending_events.clear();
|
||||
|
||||
pad.event_default(element, event)
|
||||
}
|
||||
EventView::Eos(_) => {
|
||||
gst_log!(self.cat, obj: pad, "Draining");
|
||||
if let Err(err) = self.handle_buffer(element, None) {
|
||||
gst_error!(self.cat, obj: pad, "Failed to drain parser: {:?}", err);
|
||||
}
|
||||
pad.event_default(element, event)
|
||||
}
|
||||
_ => {
|
||||
if event.is_sticky()
|
||||
&& !self.srcpad.has_current_caps()
|
||||
&& event.get_type() > gst::EventType::Caps
|
||||
{
|
||||
gst_log!(
|
||||
self.cat,
|
||||
obj: pad,
|
||||
"Deferring sticky event until we have caps"
|
||||
);
|
||||
let mut state = self.state.lock().unwrap();
|
||||
state.pending_events.push(event);
|
||||
true
|
||||
} else {
|
||||
pad.event_default(element, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn src_event(&self, pad: &gst::Pad, element: &gst::Element, event: gst::Event) -> bool {
|
||||
use gst::EventView;
|
||||
|
||||
gst_log!(self.cat, obj: pad, "Handling event {:?}", event);
|
||||
match event.view() {
|
||||
EventView::Seek(_) => {
|
||||
gst_log!(self.cat, obj: pad, "Dropping seek event");
|
||||
false
|
||||
}
|
||||
_ => pad.event_default(element, event),
|
||||
}
|
||||
}
|
||||
|
||||
fn src_query(&self, pad: &gst::Pad, element: &gst::Element, query: &mut gst::QueryRef) -> bool {
|
||||
use gst::QueryView;
|
||||
|
||||
gst_log!(self.cat, obj: pad, "Handling query {:?}", query);
|
||||
|
||||
match query.view_mut() {
|
||||
QueryView::Seeking(mut q) => {
|
||||
// We don't support any seeking at all
|
||||
let fmt = q.get_format();
|
||||
q.set(
|
||||
false,
|
||||
gst::GenericFormattedValue::Other(fmt, -1),
|
||||
gst::GenericFormattedValue::Other(fmt, -1),
|
||||
);
|
||||
true
|
||||
}
|
||||
QueryView::Position(ref mut q) => {
|
||||
// For Time answer ourselfs, otherwise forward
|
||||
if q.get_format() == gst::Format::Time {
|
||||
let state = self.state.lock().unwrap();
|
||||
q.set(state.last_position);
|
||||
true
|
||||
} else {
|
||||
self.sinkpad.peer_query(query)
|
||||
}
|
||||
}
|
||||
_ => pad.query_default(element, query),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectSubclass for MccParse {
|
||||
const NAME: &'static str = "RsMccParse";
|
||||
type ParentType = gst::Element;
|
||||
type Instance = gst::subclass::ElementInstanceStruct<Self>;
|
||||
type Class = subclass::simple::ClassStruct<Self>;
|
||||
|
||||
glib_object_subclass!();
|
||||
|
||||
fn new_with_class(klass: &subclass::simple::ClassStruct<Self>) -> Self {
|
||||
let templ = klass.get_pad_template("sink").unwrap();
|
||||
let sinkpad = gst::Pad::new_from_template(&templ, "sink");
|
||||
let templ = klass.get_pad_template("src").unwrap();
|
||||
let srcpad = gst::Pad::new_from_template(&templ, "src");
|
||||
|
||||
MccParse::set_pad_functions(&sinkpad, &srcpad);
|
||||
|
||||
Self {
|
||||
cat: gst::DebugCategory::new(
|
||||
"mccparse",
|
||||
gst::DebugColorFlags::empty(),
|
||||
"Mcc Parser Element",
|
||||
),
|
||||
srcpad,
|
||||
sinkpad,
|
||||
state: Mutex::new(State::default()),
|
||||
}
|
||||
}
|
||||
|
||||
fn class_init(klass: &mut subclass::simple::ClassStruct<Self>) {
|
||||
klass.set_metadata(
|
||||
"Mcc Parse",
|
||||
"Parser/ClosedCaption",
|
||||
"Parses MCC Closed Caption Files",
|
||||
"Sebastian Dröge <sebastian@centricular.com>",
|
||||
);
|
||||
|
||||
let mut caps = gst::Caps::new_empty();
|
||||
{
|
||||
let caps = caps.get_mut().unwrap();
|
||||
|
||||
let s = gst::Structure::builder("closedcaption/x-cea-708")
|
||||
.field("format", &"cdp")
|
||||
.build();
|
||||
caps.append_structure(s);
|
||||
|
||||
let s = gst::Structure::builder("closedcaption/x-cea-608")
|
||||
.field("format", &"s334-1a")
|
||||
.build();
|
||||
caps.append_structure(s);
|
||||
}
|
||||
let src_pad_template = gst::PadTemplate::new(
|
||||
"src",
|
||||
gst::PadDirection::Src,
|
||||
gst::PadPresence::Always,
|
||||
&caps,
|
||||
);
|
||||
klass.add_pad_template(src_pad_template);
|
||||
|
||||
let caps = gst::Caps::builder("application/x-mcc")
|
||||
.field("version", &gst::List::new(&[&1i32, &2i32]))
|
||||
.build();
|
||||
let sink_pad_template = gst::PadTemplate::new(
|
||||
"sink",
|
||||
gst::PadDirection::Sink,
|
||||
gst::PadPresence::Always,
|
||||
&caps,
|
||||
);
|
||||
klass.add_pad_template(sink_pad_template);
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for MccParse {
|
||||
glib_object_impl!();
|
||||
|
||||
fn constructed(&self, obj: &glib::Object) {
|
||||
self.parent_constructed(obj);
|
||||
|
||||
let element = obj.downcast_ref::<gst::Element>().unwrap();
|
||||
element.add_pad(&self.sinkpad).unwrap();
|
||||
element.add_pad(&self.srcpad).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl ElementImpl for MccParse {
|
||||
fn change_state(
|
||||
&self,
|
||||
element: &gst::Element,
|
||||
transition: gst::StateChange,
|
||||
) -> Result<gst::StateChangeSuccess, gst::StateChangeError> {
|
||||
gst_trace!(self.cat, obj: element, "Changing state {:?}", transition);
|
||||
|
||||
match transition {
|
||||
gst::StateChange::ReadyToPaused | gst::StateChange::PausedToReady => {
|
||||
// Reset the whole state
|
||||
let mut state = self.state.lock().unwrap();
|
||||
*state = State::default();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
self.parent_change_state(element, transition)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||
gst::Element::register(plugin, "mccparse", 0, MccParse::get_type())
|
||||
}
|
793
gst-plugin-closedcaption/src/mcc_parser.rs
Normal file
793
gst-plugin-closedcaption/src/mcc_parser.rs
Normal file
|
@ -0,0 +1,793 @@
|
|||
// Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
|
||||
// Boston, MA 02110-1335, USA.
|
||||
|
||||
use either::Either;
|
||||
|
||||
use combine;
|
||||
use combine::parser::byte::hex_digit;
|
||||
use combine::parser::range::{range, take_while1};
|
||||
use combine::parser::repeat::skip_many;
|
||||
use combine::{any, choice, eof, from_str, many1, one_of, optional, token, unexpected_any, value};
|
||||
use combine::{ParseError, Parser, RangeStream};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct TimeCode {
|
||||
pub hours: u32,
|
||||
pub minutes: u32,
|
||||
pub seconds: u32,
|
||||
pub frames: u32,
|
||||
pub drop_frame: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum MccLine<'a> {
|
||||
Header,
|
||||
Comment,
|
||||
Empty,
|
||||
UUID(&'a [u8]),
|
||||
Metadata(&'a [u8], &'a [u8]),
|
||||
TimeCodeRate(u8, bool),
|
||||
Caption(TimeCode, Vec<u8>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum State {
|
||||
Init,
|
||||
Header,
|
||||
Comments,
|
||||
Metadata,
|
||||
Captions,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MccParser {
|
||||
state: State,
|
||||
}
|
||||
|
||||
/// Parser for parsing a run of ASCII, decimal digits and converting them into a `u32`
|
||||
fn digits<'a, I: 'a>() -> impl Parser<Input = I, Output = u32>
|
||||
where
|
||||
I: RangeStream<Item = u8, Range = &'a [u8]>,
|
||||
I::Error: ParseError<I::Item, I::Range, I::Position>,
|
||||
{
|
||||
from_str(take_while1(|c: u8| c >= b'0' && c <= b'9').message("while parsing digits"))
|
||||
}
|
||||
|
||||
/// Copy from std::ops::RangeBounds as it's not stabilized yet.
|
||||
///
|
||||
/// Checks if `item` is in the range `range`.
|
||||
fn contains<R: std::ops::RangeBounds<U>, U>(range: &R, item: &U) -> bool
|
||||
where
|
||||
U: ?Sized + PartialOrd<U>,
|
||||
{
|
||||
(match range.start_bound() {
|
||||
std::ops::Bound::Included(ref start) => *start <= item,
|
||||
std::ops::Bound::Excluded(ref start) => *start < item,
|
||||
std::ops::Bound::Unbounded => true,
|
||||
}) && (match range.end_bound() {
|
||||
std::ops::Bound::Included(ref end) => item <= *end,
|
||||
std::ops::Bound::Excluded(ref end) => item < *end,
|
||||
std::ops::Bound::Unbounded => true,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parser for a run of decimal digits, that converts them into a `u32` and checks if the result is
|
||||
/// in the allowed range.
|
||||
fn digits_range<'a, I: 'a, R: std::ops::RangeBounds<u32>>(
|
||||
range: R,
|
||||
) -> impl Parser<Input = I, Output = u32>
|
||||
where
|
||||
I: RangeStream<Item = u8, Range = &'a [u8]>,
|
||||
I::Error: ParseError<I::Item, I::Range, I::Position>,
|
||||
{
|
||||
digits().then(move |v| {
|
||||
if contains(&range, &v) {
|
||||
value(v).left()
|
||||
} else {
|
||||
unexpected_any("digits out of range").right()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Parser for a timecode in the form `hh:mm:ss:fs`
|
||||
fn timecode<'a, I: 'a>() -> impl Parser<Input = I, Output = TimeCode>
|
||||
where
|
||||
I: RangeStream<Item = u8, Range = &'a [u8]>,
|
||||
I::Error: ParseError<I::Item, I::Range, I::Position>,
|
||||
{
|
||||
(
|
||||
digits(),
|
||||
token(b':'),
|
||||
digits_range(0..60),
|
||||
token(b':'),
|
||||
digits_range(0..60),
|
||||
one_of([b':', b'.', b';', b','].iter().cloned()),
|
||||
digits(),
|
||||
)
|
||||
.map(|(hours, _, minutes, _, seconds, sep, frames)| TimeCode {
|
||||
hours,
|
||||
minutes,
|
||||
seconds,
|
||||
frames,
|
||||
drop_frame: sep == b';' || sep == b',',
|
||||
})
|
||||
.message("while parsing timecode")
|
||||
}
|
||||
|
||||
/// Parser that checks for EOF and optionally `\n` or `\r\n` before EOF
|
||||
fn end_of_line<'a, I: 'a>() -> impl Parser<Input = I, Output = ()> + 'a
|
||||
where
|
||||
I: RangeStream<Item = u8, Range = &'a [u8]>,
|
||||
I::Error: ParseError<I::Item, I::Range, I::Position>,
|
||||
{
|
||||
(
|
||||
optional(choice((range(b"\n".as_ref()), range(b"\r\n".as_ref())))),
|
||||
eof(),
|
||||
)
|
||||
.map(|_| ())
|
||||
.message("while parsing end of line")
|
||||
}
|
||||
|
||||
/// Parser for the MCC header
|
||||
fn header<'a, I: 'a>() -> impl Parser<Input = I, Output = MccLine<'a>>
|
||||
where
|
||||
I: RangeStream<Item = u8, Range = &'a [u8]>,
|
||||
I::Error: ParseError<I::Item, I::Range, I::Position>,
|
||||
{
|
||||
(
|
||||
range(b"File Format=MacCaption_MCC V".as_ref()),
|
||||
choice!(range(b"1.0".as_ref()), range(b"2.0".as_ref())),
|
||||
end_of_line(),
|
||||
)
|
||||
.map(|_| MccLine::Header)
|
||||
.message("while parsing header")
|
||||
}
|
||||
|
||||
/// Parser that accepts only an empty line
|
||||
fn empty_line<'a, I: 'a>() -> impl Parser<Input = I, Output = MccLine<'a>>
|
||||
where
|
||||
I: RangeStream<Item = u8, Range = &'a [u8]>,
|
||||
I::Error: ParseError<I::Item, I::Range, I::Position>,
|
||||
{
|
||||
end_of_line()
|
||||
.map(|_| MccLine::Empty)
|
||||
.message("while parsing empty line")
|
||||
}
|
||||
|
||||
/// Parser for an MCC comment, i.e. a line starting with `//`. We don't return the actual comment
|
||||
/// text as it's irrelevant for us.
|
||||
fn comment<'a, I: 'a>() -> impl Parser<Input = I, Output = MccLine<'a>>
|
||||
where
|
||||
I: RangeStream<Item = u8, Range = &'a [u8]>,
|
||||
I::Error: ParseError<I::Item, I::Range, I::Position>,
|
||||
{
|
||||
(token(b'/'), token(b'/'), skip_many(any()))
|
||||
.map(|_| MccLine::Comment)
|
||||
.message("while parsing comment")
|
||||
}
|
||||
|
||||
/// Parser for the MCC UUID line.
|
||||
fn uuid<'a, I: 'a>() -> impl Parser<Input = I, Output = MccLine<'a>>
|
||||
where
|
||||
I: RangeStream<Item = u8, Range = &'a [u8]>,
|
||||
I::Error: ParseError<I::Item, I::Range, I::Position>,
|
||||
{
|
||||
(
|
||||
range(b"UUID=".as_ref()),
|
||||
take_while1(|b| b != b'\n' && b != b'\r'),
|
||||
end_of_line(),
|
||||
)
|
||||
.map(|(_, uuid, _)| MccLine::UUID(uuid))
|
||||
.message("while parsing UUID")
|
||||
}
|
||||
|
||||
/// Parser for the MCC Time Code Rate line.
|
||||
fn time_code_rate<'a, I: 'a>() -> impl Parser<Input = I, Output = MccLine<'a>>
|
||||
where
|
||||
I: RangeStream<Item = u8, Range = &'a [u8]>,
|
||||
I::Error: ParseError<I::Item, I::Range, I::Position>,
|
||||
{
|
||||
(
|
||||
range(b"Time Code Rate=".as_ref()),
|
||||
digits_range(1..256)
|
||||
.and(optional(range(b"DF".as_ref())))
|
||||
.map(|(v, df)| MccLine::TimeCodeRate(v as u8, df.is_some())),
|
||||
end_of_line(),
|
||||
)
|
||||
.map(|(_, v, _)| v)
|
||||
.message("while parsing time code rate")
|
||||
}
|
||||
|
||||
/// Parser for generic MCC metadata lines in the form `key=value`.
|
||||
fn metadata<'a, I: 'a>() -> impl Parser<Input = I, Output = MccLine<'a>>
|
||||
where
|
||||
I: RangeStream<Item = u8, Range = &'a [u8]>,
|
||||
I::Error: ParseError<I::Item, I::Range, I::Position>,
|
||||
{
|
||||
(
|
||||
take_while1(|b| b != b'='),
|
||||
token(b'='),
|
||||
take_while1(|b| b != b'\n' && b != b'\r'),
|
||||
end_of_line(),
|
||||
)
|
||||
.map(|(name, _, value, _)| MccLine::Metadata(name, value))
|
||||
.message("while parsing metadata")
|
||||
}
|
||||
|
||||
/// A single MCC payload item. This is ASCII hex encoded bytes plus some single-character
|
||||
/// short-cuts for common byte sequences.
|
||||
///
|
||||
/// It returns an `Either` of the single hex encoded byte or the short-cut byte sequence as a
|
||||
/// static byte slice.
|
||||
fn mcc_payload_item<'a, I: 'a>() -> impl Parser<Input = I, Output = Either<u8, &'static [u8]>>
|
||||
where
|
||||
I: RangeStream<Item = u8, Range = &'a [u8]>,
|
||||
I::Error: ParseError<I::Item, I::Range, I::Position>,
|
||||
{
|
||||
choice!(
|
||||
token(b'G').map(|_| Either::Right([0xfau8, 0x00, 0x00].as_ref())),
|
||||
token(b'H').map(|_| Either::Right([0xfau8, 0x00, 0x00, 0xfa, 0x00, 0x00].as_ref())),
|
||||
token(b'I').map(|_| Either::Right(
|
||||
[0xfau8, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00].as_ref()
|
||||
)),
|
||||
token(b'J').map(|_| Either::Right(
|
||||
[0xfau8, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00].as_ref()
|
||||
)),
|
||||
token(b'K').map(|_| Either::Right(
|
||||
[
|
||||
0xfau8, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa,
|
||||
0x00, 0x00
|
||||
]
|
||||
.as_ref()
|
||||
)),
|
||||
token(b'L').map(|_| Either::Right(
|
||||
[
|
||||
0xfau8, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa,
|
||||
0x00, 0x00, 0xfa, 0x00, 0x00
|
||||
]
|
||||
.as_ref()
|
||||
)),
|
||||
token(b'M').map(|_| Either::Right(
|
||||
[
|
||||
0xfau8, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa,
|
||||
0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00
|
||||
]
|
||||
.as_ref()
|
||||
)),
|
||||
token(b'N').map(|_| Either::Right(
|
||||
[
|
||||
0xfau8, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa,
|
||||
0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00
|
||||
]
|
||||
.as_ref()
|
||||
)),
|
||||
token(b'O').map(|_| Either::Right(
|
||||
[
|
||||
0xfau8, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa,
|
||||
0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00
|
||||
]
|
||||
.as_ref()
|
||||
)),
|
||||
token(b'P').map(|_| Either::Right([0xfbu8, 0x80, 0x80].as_ref())),
|
||||
token(b'Q').map(|_| Either::Right([0xfcu8, 0x80, 0x80].as_ref())),
|
||||
token(b'R').map(|_| Either::Right([0xfdu8, 0x80, 0x80].as_ref())),
|
||||
token(b'S').map(|_| Either::Right([0x96u8, 0x69].as_ref())),
|
||||
token(b'T').map(|_| Either::Right([0x61u8, 0x01].as_ref())),
|
||||
token(b'U').map(|_| Either::Right([0xe1u8, 0x00, 0x00].as_ref())),
|
||||
token(b'Z').map(|_| Either::Left(0x00u8)),
|
||||
(hex_digit(), hex_digit()).map(|(u, l)| {
|
||||
let hex_to_u8 = |v: u8| match v {
|
||||
v if v >= b'0' && v <= b'9' => v - b'0',
|
||||
v if v >= b'A' && v <= b'F' => 10 + v - b'A',
|
||||
v if v >= b'a' && v <= b'f' => 10 + v - b'a',
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let val = (hex_to_u8(u) << 4) | hex_to_u8(l);
|
||||
Either::Left(val)
|
||||
})
|
||||
)
|
||||
.message("while parsing MCC payload")
|
||||
}
|
||||
|
||||
/// A wrapper around `Vec<u8>` that implements `Extend` in a special way. It can be
|
||||
/// extended from an iterator of `Either<u8, &[u8]>` while the default `Extend` implementation for
|
||||
/// `Vec` only allows to extend over vector items.
|
||||
struct VecExtend(Vec<u8>);
|
||||
|
||||
impl Default for VecExtend {
|
||||
fn default() -> Self {
|
||||
VecExtend(Vec::with_capacity(256))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Extend<Either<u8, &'a [u8]>> for VecExtend {
|
||||
fn extend<T>(&mut self, iter: T)
|
||||
where
|
||||
T: IntoIterator<Item = Either<u8, &'a [u8]>>,
|
||||
{
|
||||
for item in iter {
|
||||
match item {
|
||||
Either::Left(v) => self.0.push(v),
|
||||
Either::Right(v) => self.0.extend_from_slice(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parser for the whole MCC payload with conversion to the underlying byte values.
|
||||
fn mcc_payload<'a, I: 'a>() -> impl Parser<Input = I, Output = Vec<u8>>
|
||||
where
|
||||
I: RangeStream<Item = u8, Range = &'a [u8]>,
|
||||
I::Error: ParseError<I::Item, I::Range, I::Position>,
|
||||
{
|
||||
many1(mcc_payload_item())
|
||||
.map(|v: VecExtend| v.0)
|
||||
.message("while parsing MCC payloads")
|
||||
}
|
||||
|
||||
/// Parser for a MCC caption line in the form `timecode\tpayload`.
|
||||
fn caption<'a, I: 'a>() -> impl Parser<Input = I, Output = MccLine<'a>>
|
||||
where
|
||||
I: RangeStream<Item = u8, Range = &'a [u8]>,
|
||||
I::Error: ParseError<I::Item, I::Range, I::Position>,
|
||||
{
|
||||
(
|
||||
timecode(),
|
||||
optional((
|
||||
token(b'.'),
|
||||
one_of([b'0', b'1'].iter().cloned()),
|
||||
optional((token(b','), digits())),
|
||||
)),
|
||||
token(b'\t'),
|
||||
mcc_payload(),
|
||||
end_of_line(),
|
||||
)
|
||||
.map(|(tc, _, _, value, _)| MccLine::Caption(tc, value))
|
||||
.message("while parsing caption")
|
||||
}
|
||||
|
||||
/// MCC parser the parses line-by-line and keeps track of the current state in the file.
|
||||
impl MccParser {
|
||||
pub fn new() -> Self {
|
||||
Self { state: State::Init }
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.state = State::Init;
|
||||
}
|
||||
|
||||
pub fn parse_line<'a>(
|
||||
&mut self,
|
||||
line: &'a [u8],
|
||||
) -> Result<MccLine<'a>, combine::easy::Errors<u8, &'a [u8], combine::stream::PointerOffset>>
|
||||
{
|
||||
match self.state {
|
||||
State::Init => header()
|
||||
.message("while in Init state")
|
||||
.easy_parse(line)
|
||||
.map(|v| {
|
||||
self.state = State::Header;
|
||||
v.0
|
||||
}),
|
||||
State::Header => empty_line()
|
||||
.message("while in Header state")
|
||||
.easy_parse(line)
|
||||
.map(|v| {
|
||||
self.state = State::Comments;
|
||||
v.0
|
||||
}),
|
||||
State::Comments => choice!(empty_line(), comment())
|
||||
.message("while in Comments state")
|
||||
.easy_parse(line)
|
||||
.map(|v| {
|
||||
if v.0 == MccLine::Empty {
|
||||
self.state = State::Metadata;
|
||||
}
|
||||
|
||||
v.0
|
||||
}),
|
||||
State::Metadata => choice!(empty_line(), uuid(), time_code_rate(), metadata())
|
||||
.message("while in Metadata state")
|
||||
.easy_parse(line)
|
||||
.map(|v| {
|
||||
if v.0 == MccLine::Empty {
|
||||
self.state = State::Captions;
|
||||
}
|
||||
|
||||
v.0
|
||||
}),
|
||||
State::Captions => caption()
|
||||
.message("while in Captions state")
|
||||
.easy_parse(line)
|
||||
.map(|v| v.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use combine::error::UnexpectedParse;
|
||||
|
||||
#[test]
|
||||
fn test_timecode() {
|
||||
let mut parser = timecode();
|
||||
assert_eq!(
|
||||
parser.parse(b"11:12:13;14".as_ref()),
|
||||
Ok((
|
||||
TimeCode {
|
||||
hours: 11,
|
||||
minutes: 12,
|
||||
seconds: 13,
|
||||
frames: 14,
|
||||
drop_frame: true
|
||||
},
|
||||
b"".as_ref()
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parser.parse(b"11:12:13:14".as_ref()),
|
||||
Ok((
|
||||
TimeCode {
|
||||
hours: 11,
|
||||
minutes: 12,
|
||||
seconds: 13,
|
||||
frames: 14,
|
||||
drop_frame: false
|
||||
},
|
||||
b"".as_ref()
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parser.parse(b"11:12:13:14abcd".as_ref()),
|
||||
Ok((
|
||||
TimeCode {
|
||||
hours: 11,
|
||||
minutes: 12,
|
||||
seconds: 13,
|
||||
frames: 14,
|
||||
drop_frame: false
|
||||
},
|
||||
b"abcd".as_ref()
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parser.parse(b"abcd11:12:13:14".as_ref()),
|
||||
Err(UnexpectedParse::Unexpected)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_header() {
|
||||
let mut parser = header();
|
||||
assert_eq!(
|
||||
parser.parse(b"File Format=MacCaption_MCC V1.0".as_ref()),
|
||||
Ok((MccLine::Header, b"".as_ref()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parser.parse(b"File Format=MacCaption_MCC V1.0\n".as_ref()),
|
||||
Ok((MccLine::Header, b"".as_ref()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parser.parse(b"File Format=MacCaption_MCC V1.0\r\n".as_ref()),
|
||||
Ok((MccLine::Header, b"".as_ref()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parser.parse(b"File Format=MacCaption_MCC V2.0\r\n".as_ref()),
|
||||
Ok((MccLine::Header, b"".as_ref()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parser.parse(b"File Format=MacCaption_MCC V1.1".as_ref()),
|
||||
Err(UnexpectedParse::Unexpected)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_line() {
|
||||
let mut parser = empty_line();
|
||||
assert_eq!(
|
||||
parser.parse(b"".as_ref()),
|
||||
Ok((MccLine::Empty, b"".as_ref()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parser.parse(b"\n".as_ref()),
|
||||
Ok((MccLine::Empty, b"".as_ref()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parser.parse(b"\r\n".as_ref()),
|
||||
Ok((MccLine::Empty, b"".as_ref()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parser.parse(b" \r\n".as_ref()),
|
||||
Err(UnexpectedParse::Unexpected)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_comment() {
|
||||
let mut parser = comment();
|
||||
assert_eq!(
|
||||
parser.parse(b"// blabla".as_ref()),
|
||||
Ok((MccLine::Comment, b"".as_ref()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parser.parse(b"//\n".as_ref()),
|
||||
Ok((MccLine::Comment, b"".as_ref()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parser.parse(b"//".as_ref()),
|
||||
Ok((MccLine::Comment, b"".as_ref()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parser.parse(b" //".as_ref()),
|
||||
Err(UnexpectedParse::Unexpected)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_uuid() {
|
||||
let mut parser = uuid();
|
||||
assert_eq!(
|
||||
parser.parse(b"UUID=1234".as_ref()),
|
||||
Ok((MccLine::UUID(b"1234".as_ref()), b"".as_ref()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parser.parse(b"UUID=1234\n".as_ref()),
|
||||
Ok((MccLine::UUID(b"1234".as_ref()), b"".as_ref()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parser.parse(b"UUID=1234\r\n".as_ref()),
|
||||
Ok((MccLine::UUID(b"1234".as_ref()), b"".as_ref()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parser.parse(b"UUID=".as_ref()),
|
||||
Err(UnexpectedParse::Unexpected)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parser.parse(b"uUID=1234".as_ref()),
|
||||
Err(UnexpectedParse::Unexpected)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time_code_rate() {
|
||||
let mut parser = time_code_rate();
|
||||
assert_eq!(
|
||||
parser.parse(b"Time Code Rate=30".as_ref()),
|
||||
Ok((MccLine::TimeCodeRate(30, false), b"".as_ref()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parser.parse(b"Time Code Rate=30DF".as_ref()),
|
||||
Ok((MccLine::TimeCodeRate(30, true), b"".as_ref()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parser.parse(b"Time Code Rate=60".as_ref()),
|
||||
Ok((MccLine::TimeCodeRate(60, false), b"".as_ref()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parser.parse(b"Time Code Rate=17F".as_ref()),
|
||||
Err(UnexpectedParse::Unexpected)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parser.parse(b"Time Code Rate=256".as_ref()),
|
||||
Err(UnexpectedParse::Unexpected)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_metadata() {
|
||||
let mut parser = metadata();
|
||||
assert_eq!(
|
||||
parser.parse(b"Creation Date=Thursday, June 04, 2015".as_ref()),
|
||||
Ok((
|
||||
MccLine::Metadata(
|
||||
b"Creation Date".as_ref(),
|
||||
b"Thursday, June 04, 2015".as_ref()
|
||||
),
|
||||
b"".as_ref()
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parser.parse(b"Creation Date= ".as_ref()),
|
||||
Ok((
|
||||
MccLine::Metadata(b"Creation Date".as_ref(), b" ".as_ref()),
|
||||
b"".as_ref()
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parser.parse(b"Creation Date".as_ref()),
|
||||
Err(UnexpectedParse::Eoi)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parser.parse(b"Creation Date\n".as_ref()),
|
||||
Err(UnexpectedParse::Eoi)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parser.parse(b"Creation Date=".as_ref()),
|
||||
Err(UnexpectedParse::Unexpected)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parser.parse(b"Creation Date=\n".as_ref()),
|
||||
Err(UnexpectedParse::Unexpected)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_caption() {
|
||||
let mut parser = caption();
|
||||
assert_eq!(
|
||||
parser.parse(b"00:00:00:00\tT52S524F67ZZ72F4QROO7391UC13FFF74ZZAEB4".as_ref()),
|
||||
Ok((
|
||||
MccLine::Caption(
|
||||
TimeCode {
|
||||
hours: 0,
|
||||
minutes: 0,
|
||||
seconds: 0,
|
||||
frames: 0,
|
||||
drop_frame: false
|
||||
},
|
||||
vec![
|
||||
0x61, 0x01, 0x52, 0x96, 0x69, 0x52, 0x4F, 0x67, 0x00, 0x00, 0x72, 0xF4,
|
||||
0xFC, 0x80, 0x80, 0xFD, 0x80, 0x80, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0x73, 0x91, 0xE1, 0x00, 0x00, 0xC1, 0x3F, 0xFF, 0x74, 0x00, 0x00, 0xAE,
|
||||
0xB4
|
||||
]
|
||||
),
|
||||
b"".as_ref()
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parser.parse(b"00:00:00:00.0\tT52S524F67ZZ72F4QROO7391UC13FFF74ZZAEB4".as_ref()),
|
||||
Ok((
|
||||
MccLine::Caption(
|
||||
TimeCode {
|
||||
hours: 0,
|
||||
minutes: 0,
|
||||
seconds: 0,
|
||||
frames: 0,
|
||||
drop_frame: false
|
||||
},
|
||||
vec![
|
||||
0x61, 0x01, 0x52, 0x96, 0x69, 0x52, 0x4F, 0x67, 0x00, 0x00, 0x72, 0xF4,
|
||||
0xFC, 0x80, 0x80, 0xFD, 0x80, 0x80, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0x73, 0x91, 0xE1, 0x00, 0x00, 0xC1, 0x3F, 0xFF, 0x74, 0x00, 0x00, 0xAE,
|
||||
0xB4
|
||||
]
|
||||
),
|
||||
b"".as_ref()
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parser.parse(b"00:00:00:00.0,9\tT52S524F67ZZ72F4QROO7391UC13FFF74ZZAEB4".as_ref()),
|
||||
Ok((
|
||||
MccLine::Caption(
|
||||
TimeCode {
|
||||
hours: 0,
|
||||
minutes: 0,
|
||||
seconds: 0,
|
||||
frames: 0,
|
||||
drop_frame: false
|
||||
},
|
||||
vec![
|
||||
0x61, 0x01, 0x52, 0x96, 0x69, 0x52, 0x4F, 0x67, 0x00, 0x00, 0x72, 0xF4,
|
||||
0xFC, 0x80, 0x80, 0xFD, 0x80, 0x80, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00, 0xFA, 0x00, 0x00,
|
||||
0x73, 0x91, 0xE1, 0x00, 0x00, 0xC1, 0x3F, 0xFF, 0x74, 0x00, 0x00, 0xAE,
|
||||
0xB4
|
||||
]
|
||||
),
|
||||
b"".as_ref()
|
||||
))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
parser.parse(b"Creation Date=\n".as_ref()),
|
||||
Err(UnexpectedParse::Unexpected)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parser() {
|
||||
let mcc_file = include_bytes!("../tests/captions-test_708.mcc");
|
||||
let mut reader = crate::line_reader::LineReader::new();
|
||||
let mut parser = MccParser::new();
|
||||
let mut line_cnt = 0;
|
||||
|
||||
reader.push(Vec::from(mcc_file.as_ref()));
|
||||
|
||||
while let Some(line) = reader.get_line() {
|
||||
let res = match parser.parse_line(line) {
|
||||
Ok(res) => res,
|
||||
Err(err) => panic!("Couldn't parse line {}: {:?}", line_cnt, err),
|
||||
};
|
||||
|
||||
match line_cnt {
|
||||
0 => assert_eq!(res, MccLine::Header),
|
||||
1 | 37 | 43 => assert_eq!(res, MccLine::Empty),
|
||||
x if x >= 2 && x <= 36 => assert_eq!(res, MccLine::Comment),
|
||||
38 => assert_eq!(
|
||||
res,
|
||||
MccLine::UUID(b"CA8BC94D-9931-4EEE-812F-2D68FA74F287".as_ref())
|
||||
),
|
||||
39 => assert_eq!(
|
||||
res,
|
||||
MccLine::Metadata(
|
||||
b"Creation Program".as_ref(),
|
||||
b"Adobe Premiere Pro CC (Windows)".as_ref()
|
||||
)
|
||||
),
|
||||
40 => assert_eq!(
|
||||
res,
|
||||
MccLine::Metadata(
|
||||
b"Creation Date".as_ref(),
|
||||
b"Thursday, June 04, 2015".as_ref()
|
||||
)
|
||||
),
|
||||
41 => assert_eq!(
|
||||
res,
|
||||
MccLine::Metadata(b"Creation Time".as_ref(), b"13:48:25".as_ref())
|
||||
),
|
||||
42 => assert_eq!(res, MccLine::TimeCodeRate(30, true)),
|
||||
_ => match res {
|
||||
MccLine::Caption(_, _) => (),
|
||||
res => panic!("Expected caption at line {}, got {:?}", line_cnt, res),
|
||||
},
|
||||
}
|
||||
|
||||
line_cnt += 1;
|
||||
}
|
||||
}
|
||||
}
|
622
gst-plugin-closedcaption/tests/captions-test_708.mcc
Normal file
622
gst-plugin-closedcaption/tests/captions-test_708.mcc
Normal file
|
@ -0,0 +1,622 @@
|
|||
File Format=MacCaption_MCC V1.0
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Computer Prompting and Captioning Company
|
||||
// Ancillary Data Packet Transfer File
|
||||
//
|
||||
// Permission to generate this format is granted provided that
|
||||
// 1. This ANC Transfer file format is used on an as-is basis and no warranty is given, and
|
||||
// 2. This entire descriptive information text is included in a generated .mcc file.
|
||||
//
|
||||
// General file format:
|
||||
// HH:MM:SS:FF(tab)[Hexadecimal ANC data in groups of 2 characters]
|
||||
// Hexadecimal data starts with the Ancillary Data Packet DID (Data ID defined in S291M)
|
||||
// and concludes with the Check Sum following the User Data Words.
|
||||
// Each time code line must contain at most one complete ancillary data packet.
|
||||
// To transfer additional ANC Data successive lines may contain identical time code.
|
||||
// Time Code Rate=[24, 25, 30, 30DF, 50, 60]
|
||||
//
|
||||
// ANC data bytes may be represented by one ASCII character according to the following schema:
|
||||
// G FAh 00h 00h
|
||||
// H 2 x (FAh 00h 00h)
|
||||
// I 3 x (FAh 00h 00h)
|
||||
// J 4 x (FAh 00h 00h)
|
||||
// K 5 x (FAh 00h 00h)
|
||||
// L 6 x (FAh 00h 00h)
|
||||
// M 7 x (FAh 00h 00h)
|
||||
// N 8 x (FAh 00h 00h)
|
||||
// O 9 x (FAh 00h 00h)
|
||||
// P FBh 80h 80h
|
||||
// Q FCh 80h 80h
|
||||
// R FDh 80h 80h
|
||||
// S 96h 69h
|
||||
// T 61h 01h
|
||||
// U E1h 00h 00h 00h
|
||||
// Z 00h
|
||||
//
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
UUID=CA8BC94D-9931-4EEE-812F-2D68FA74F287
|
||||
Creation Program=Adobe Premiere Pro CC (Windows)
|
||||
Creation Date=Thursday, June 04, 2015
|
||||
Creation Time=13:48:25
|
||||
Time Code Rate=30DF
|
||||
|
||||
00:00:00:00 T52S524F67ZZ72F4QRFF0222FE8CFFOM739181656E67817FFF74ZZ1CB4
|
||||
00:00:00:01 T52S524F67Z0172F4QRFF4527FE98ZFEZZFE0116FE11ZOJ739181656E67817FFF74Z0191B4
|
||||
00:00:00:02 T52S524F67Z0272F4QRFF8B34FE9004FE0354FE6865FE7365FE2061FE7265FE2037FE3038FE2063FE6103M739181656E67817FFF74Z0256B4
|
||||
00:00:00:03 T52S524F67Z0372F4QRFFC528FE7074FE696FFE6E73FE2003OJ739181656E67817FFF74Z030CB4
|
||||
00:00:00:04 T52S524F67Z0472F4QRFF082EFE9201FEZ28FE746FFE7020FE6C65FE6674FE2903OG739181656E67817FFF74Z0470B4
|
||||
00:00:00:05 T52S524F67Z0572F4QRFF4324FE88ZFE8BFFOL739181656E67817FFF74Z0544B4
|
||||
00:00:00:06 T52S524F67Z0672F4QRFF8222FE8CFEOM739181656E67817FFF74Z0691B4
|
||||
00:00:00:07 T52S524F67Z0772F4QRFFC527FE99ZFE1EZFE011BFE11ZOJ739181656E67817FFF74Z07E1B4
|
||||
00:00:00:08 T52S524F67Z0872F4QRFF0B34FE9004FE0392FEZ05FE5468FE6573FE6520FE6172FE6520FE3730FE3803M739181656E67817FFF74Z0817B4
|
||||
00:00:00:09 T52S524F67Z0972F4QRFF472BFE2063FE6170FE7469FE6F6EFE7320FE03ZOH739181656E67817FFF74Z098FB4
|
||||
00:00:00:10 T52S524F67Z0A72F4QRFF872CFE9201FE0E28FE6D69FE6464FE6C65FE2903OH739181656E67817FFF74Z0A8CB4
|
||||
00:00:00:11 T49S494F43Z0B72F4QROO74Z0BA9AB
|
||||
00:00:00:12 T49S494F43Z0C72F4QROO74Z0CA7AB
|
||||
00:00:00:13 T49S494F43Z0D72F4QROO74Z0DA5AB
|
||||
00:00:00:14 T49S494F43Z0E72F4QROO74Z0EA3AB
|
||||
00:00:00:15 T49S494F43Z0F72F4QROO74Z0FA1AB
|
||||
00:00:00:16 T49S494F43Z1072F4QROO74Z109FAB
|
||||
00:00:00:17 T49S494F43Z1172F4QROO74Z119DAB
|
||||
00:00:00:18 T49S494F43Z1272F4QROO74Z129BAB
|
||||
00:00:00:19 T49S494F43Z1372F4QROO74Z1399AB
|
||||
00:00:00:20 T49S494F43Z1472F4QROO74Z1497AB
|
||||
00:00:00:21 T49S494F43Z1572F4QROO74Z1595AB
|
||||
00:00:00:22 T49S494F43Z1672F4QROO74Z1693AB
|
||||
00:00:00:23 T49S494F43Z1772F4QROO74Z1791AB
|
||||
00:00:00:24 T49S494F43Z1872F4QROO74Z188FAB
|
||||
00:00:00:25 T49S494F43Z1972F4QROO74Z198DAB
|
||||
00:00:00:26 T49S494F43Z1A72F4QROO74Z1A8BAB
|
||||
00:00:00:27 T49S494F43Z1B72F4QROO74Z1B89AB
|
||||
00:00:00:28 T49S494F43Z1C72F4QROO74Z1C87AB
|
||||
00:00:00:29 T49S494F43Z1D72F4QROO74Z1D85AB
|
||||
00:00:01:00 T49S494F43Z1E72F4QROO74Z1E83AB
|
||||
00:00:01:01 T49S494F43Z1F72F4QROO74Z1F81AB
|
||||
00:00:01:02 T49S494F43Z2072F4QROO74Z207FAB
|
||||
00:00:01:03 T49S494F43Z2172F4QROO74Z217DAB
|
||||
00:00:01:04 T49S494F43Z2272F4QROO74Z227BAB
|
||||
00:00:01:05 T49S494F43Z2372F4QROO74Z2379AB
|
||||
00:00:01:06 T49S494F43Z2472F4QROO74Z2477AB
|
||||
00:00:01:07 T49S494F43Z2572F4QROO74Z2575AB
|
||||
00:00:01:08 T49S494F43Z2672F4QROO74Z2673AB
|
||||
00:00:01:09 T49S494F43Z2772F4QROO74Z2771AB
|
||||
00:00:01:10 T49S494F43Z2872F4QROO74Z286FAB
|
||||
00:00:01:11 T49S494F43Z2972F4QROO74Z296DAB
|
||||
00:00:01:12 T49S494F43Z2A72F4QROO74Z2A6BAB
|
||||
00:00:01:13 T49S494F43Z2B72F4QROO74Z2B69AB
|
||||
00:00:01:14 T49S494F43Z2C72F4QROO74Z2C67AB
|
||||
00:00:01:15 T49S494F43Z2D72F4QROO74Z2D65AB
|
||||
00:00:01:16 T49S494F43Z2E72F4QROO74Z2E63AB
|
||||
00:00:01:17 T49S494F43Z2F72F4QROO74Z2F61AB
|
||||
00:00:01:18 T49S494F43Z3072F4QROO74Z305FAB
|
||||
00:00:01:19 T49S494F43Z3172F4QROO74Z315DAB
|
||||
00:00:01:20 T49S494F43Z3272F4QROO74Z325BAB
|
||||
00:00:01:21 T49S494F43Z3372F4QROO74Z3359AB
|
||||
00:00:01:22 T49S494F43Z3472F4QROO74Z3457AB
|
||||
00:00:01:23 T49S494F43Z3572F4QROO74Z3555AB
|
||||
00:00:01:24 T49S494F43Z3672F4QROO74Z3653AB
|
||||
00:00:01:25 T49S494F43Z3772F4QROO74Z3751AB
|
||||
00:00:01:26 T49S494F43Z3872F4QROO74Z384FAB
|
||||
00:00:01:27 T49S494F43Z3972F4QROO74Z394DAB
|
||||
00:00:01:28 T49S494F43Z3A72F4QROO74Z3A4BAB
|
||||
00:00:01:29 T49S494F43Z3B72F4QROO74Z3B49AB
|
||||
00:00:02:00 T49S494F43Z3C72F4QROO74Z3C47AB
|
||||
00:00:02:01 T49S494F43Z3D72F4QROO74Z3D45AB
|
||||
00:00:02:02 T49S494F43Z3E72F4QROO74Z3E43AB
|
||||
00:00:02:03 T49S494F43Z3F72F4QROO74Z3F41AB
|
||||
00:00:02:04 T49S494F43Z4072F4QROO74Z403FAB
|
||||
00:00:02:05 T49S494F43Z4172F4QROO74Z413DAB
|
||||
00:00:02:06 T49S494F43Z4272F4QROO74Z423BAB
|
||||
00:00:02:07 T49S494F43Z4372F4QROO74Z4339AB
|
||||
00:00:02:08 T49S494F43Z4472F4QROO74Z4437AB
|
||||
00:00:02:09 T49S494F43Z4572F4QROO74Z4535AB
|
||||
00:00:02:10 T49S494F43Z4672F4QROO74Z4633AB
|
||||
00:00:02:11 T49S494F43Z4772F4QROO74Z4731AB
|
||||
00:00:02:12 T49S494F43Z4872F4QROO74Z482FAB
|
||||
00:00:02:13 T49S494F43Z4972F4QROO74Z492DAB
|
||||
00:00:02:14 T49S494F43Z4A72F4QROO74Z4A2BAB
|
||||
00:00:02:15 T49S494F43Z4B72F4QROO74Z4B29AB
|
||||
00:00:02:16 T49S494F43Z4C72F4QROO74Z4C27AB
|
||||
00:00:02:17 T49S494F43Z4D72F4QROO74Z4D25AB
|
||||
00:00:02:18 T49S494F43Z4E72F4QROO74Z4E23AB
|
||||
00:00:02:19 T49S494F43Z4F72F4QROO74Z4F21AB
|
||||
00:00:02:20 T49S494F43Z5072F4QROO74Z501FAB
|
||||
00:00:02:21 T49S494F43Z5172F4QROO74Z511DAB
|
||||
00:00:02:22 T49S494F43Z5272F4QROO74Z521BAB
|
||||
00:00:02:23 T49S494F43Z5372F4QROO74Z5319AB
|
||||
00:00:02:24 T49S494F43Z5472F4QROO74Z5417AB
|
||||
00:00:02:25 T49S494F43Z5572F4QROO74Z5515AB
|
||||
00:00:02:26 T49S494F43Z5672F4QROO74Z5613AB
|
||||
00:00:02:27 T49S494F43Z5772F4QROO74Z5711AB
|
||||
00:00:02:28 T49S494F43Z5872F4QROO74Z580FAB
|
||||
00:00:02:29 T49S494F43Z5972F4QROO74Z590DAB
|
||||
00:00:03:00 T49S494F43Z5A72F4QROO74Z5A0BAB
|
||||
00:00:03:01 T49S494F43Z5B72F4QROO74Z5B09AB
|
||||
00:00:03:02 T49S494F43Z5C72F4QROO74Z5C07AB
|
||||
00:00:03:03 T49S494F43Z5D72F4QROO74Z5D05AB
|
||||
00:00:03:04 T49S494F43Z5E72F4QROO74Z5E03AB
|
||||
00:00:03:05 T49S494F43Z5F72F4QROO74Z5F01AB
|
||||
00:00:03:06 T49S494F43Z6072F4QROO74Z60FFAB
|
||||
00:00:03:07 T49S494F43Z6172F4QROO74Z61FDAB
|
||||
00:00:03:08 T49S494F43Z6272F4QROO74Z62FBAB
|
||||
00:00:03:09 T49S494F43Z6372F4QROO74Z63F9AB
|
||||
00:00:03:10 T49S494F43Z6472F4QROO74Z64F7AB
|
||||
00:00:03:11 T49S494F43Z6572F4QROO74Z65F5AB
|
||||
00:00:03:12 T49S494F43Z6672F4QROO74Z66F3AB
|
||||
00:00:03:13 T49S494F43Z6772F4QROO74Z67F1AB
|
||||
00:00:03:14 T49S494F43Z6872F4QROO74Z68EFAB
|
||||
00:00:03:15 T49S494F43Z6972F4QROO74Z69EDAB
|
||||
00:00:03:16 T49S494F43Z6A72F4QROO74Z6AEBAB
|
||||
00:00:03:17 T49S494F43Z6B72F4QROO74Z6BE9AB
|
||||
00:00:03:18 T49S494F43Z6C72F4QROO74Z6CE7AB
|
||||
00:00:03:19 T49S494F43Z6D72F4QROO74Z6DE5AB
|
||||
00:00:03:20 T49S494F43Z6E72F4QROO74Z6EE3AB
|
||||
00:00:03:21 T49S494F43Z6F72F4QROO74Z6FE1AB
|
||||
00:00:03:22 T49S494F43Z7072F4QROO74Z70DFAB
|
||||
00:00:03:23 T49S494F43Z7172F4QROO74Z71DDAB
|
||||
00:00:03:24 T49S494F43Z7272F4QROO74Z72DBAB
|
||||
00:00:03:25 T49S494F43Z7372F4QROO74Z73D9AB
|
||||
00:00:03:26 T49S494F43Z7472F4QROO74Z74D7AB
|
||||
00:00:03:27 T49S494F43Z7572F4QROO74Z75D5AB
|
||||
00:00:03:28 T49S494F43Z7672F4QROO74Z76D3AB
|
||||
00:00:03:29 T49S494F43Z7772F4QROO74Z77D1AB
|
||||
00:00:04:00 T49S494F43Z7872F4QROO74Z78CFAB
|
||||
00:00:04:01 T49S494F43Z7972F4QROO74Z79CDAB
|
||||
00:00:04:02 T49S494F43Z7A72F4QROO74Z7ACBAB
|
||||
00:00:04:03 T49S494F43Z7B72F4QROO74Z7BC9AB
|
||||
00:00:04:04 T49S494F43Z7C72F4QROO74Z7CC7AB
|
||||
00:00:04:05 T49S494F43Z7D72F4QROO74Z7DC5AB
|
||||
00:00:04:06 T49S494F43Z7E72F4QROO74Z7EC3AB
|
||||
00:00:04:07 T49S494F43Z7F72F4QROO74Z7FC1AB
|
||||
00:00:04:08 T49S494F43Z8072F4QROO74Z80BFAB
|
||||
00:00:04:09 T49S494F43Z8172F4QROO74Z81BDAB
|
||||
00:00:04:10 T49S494F43Z8272F4QROO74Z82BBAB
|
||||
00:00:04:11 T49S494F43Z8372F4QROO74Z83B9AB
|
||||
00:00:04:12 T49S494F43Z8472F4QROO74Z84B7AB
|
||||
00:00:04:13 T49S494F43Z8572F4QROO74Z85B5AB
|
||||
00:00:04:14 T49S494F43Z8672F4QROO74Z86B3AB
|
||||
00:00:04:15 T49S494F43Z8772F4QROO74Z87B1AB
|
||||
00:00:04:16 T49S494F43Z8872F4QROO74Z88AFAB
|
||||
00:00:04:17 T49S494F43Z8972F4QROO74Z89ADAB
|
||||
00:00:04:18 T49S494F43Z8A72F4QROO74Z8AABAB
|
||||
00:00:04:19 T49S494F43Z8B72F4QROO74Z8BA9AB
|
||||
00:00:04:20 T49S494F43Z8C72F4QROO74Z8CA7AB
|
||||
00:00:04:21 T49S494F43Z8D72F4QROO74Z8DA5AB
|
||||
00:00:04:22 T49S494F43Z8E72F4QROO74Z8EA3AB
|
||||
00:00:04:23 T49S494F43Z8F72F4QROO74Z8FA1AB
|
||||
00:00:04:24 T49S494F43Z9072F4QROO74Z909FAB
|
||||
00:00:04:25 T49S494F43Z9172F4QROO74Z919DAB
|
||||
00:00:04:26 T49S494F43Z9272F4QROO74Z929BAB
|
||||
00:00:04:27 T52S524F67Z9372F4QRFFC222FE8C01OM739181656E67817FFF74Z9334B4
|
||||
00:00:04:28 T49S494F43Z9472F4QROO74Z9497AB
|
||||
00:00:04:29 T49S494F43Z9572F4QROO74Z9595AB
|
||||
00:00:05:00 T49S494F43Z9672F4QROO74Z9693AB
|
||||
00:00:05:01 T49S494F43Z9772F4QROO74Z9791AB
|
||||
00:00:05:02 T49S494F43Z9872F4QROO74Z988FAB
|
||||
00:00:05:03 T49S494F43Z9972F4QROO74Z998DAB
|
||||
00:00:05:04 T49S494F43Z9A72F4QROO74Z9A8BAB
|
||||
00:00:05:05 T49S494F43Z9B72F4QROO74Z9B89AB
|
||||
00:00:05:06 T49S494F43Z9C72F4QROO74Z9C87AB
|
||||
00:00:05:07 T52S524F67Z9D72F4QRFF4324FE88ZFE8BFFOL739181656E67817FFF74Z9D14B4
|
||||
00:00:05:08 T52S524F67Z9E72F4QRFF8222FE8CFDOM739181656E67817FFF74Z9E62B4
|
||||
00:00:05:09 T52S524F67Z9F72F4QRFFC527FE98ZFE41ZFE0116FE11ZOJ739181656E67817FFF74Z9F94B4
|
||||
00:00:05:10 T52S524F67ZA072F4QRFF0B34FE9004FE0354FE6865FE7365FE2061FE7265FE2037FE3038FE2063FE6103M739181656E67817FFF74ZA09AB4
|
||||
00:00:05:11 T52S524F67ZA172F4QRFF4528FE7074FE696FFE6E73FE2003OJ739181656E67817FFF74ZA150B4
|
||||
00:00:05:12 T52S524F67ZA272F4QRFF8A31FE9201FEZ28FE626FFE7474FE6F6DFE206CFE6566FE7429FE03ZN739181656E67817FFF74ZA265B4
|
||||
00:00:05:13 T49S494F43ZA372F4QROO74ZA379AB
|
||||
00:00:05:14 T49S494F43ZA472F4QROO74ZA477AB
|
||||
00:00:05:15 T49S494F43ZA572F4QROO74ZA575AB
|
||||
00:00:05:16 T49S494F43ZA672F4QROO74ZA673AB
|
||||
00:00:05:17 T49S494F43ZA772F4QROO74ZA771AB
|
||||
00:00:05:18 T49S494F43ZA872F4QROO74ZA86FAB
|
||||
00:00:05:19 T49S494F43ZA972F4QROO74ZA96DAB
|
||||
00:00:05:20 T49S494F43ZAA72F4QROO74ZAA6BAB
|
||||
00:00:05:21 T49S494F43ZAB72F4QROO74ZAB69AB
|
||||
00:00:05:22 T49S494F43ZAC72F4QROO74ZAC67AB
|
||||
00:00:05:23 T49S494F43ZAD72F4QROO74ZAD65AB
|
||||
00:00:05:24 T49S494F43ZAE72F4QROO74ZAE63AB
|
||||
00:00:05:25 T49S494F43ZAF72F4QROO74ZAF61AB
|
||||
00:00:05:26 T49S494F43ZB072F4QROO74ZB05FAB
|
||||
00:00:05:27 T49S494F43ZB172F4QROO74ZB15DAB
|
||||
00:00:05:28 T49S494F43ZB272F4QROO74ZB25BAB
|
||||
00:00:05:29 T49S494F43ZB372F4QROO74ZB359AB
|
||||
00:00:06:00 T49S494F43ZB472F4QROO74ZB457AB
|
||||
00:00:06:01 T49S494F43ZB572F4QROO74ZB555AB
|
||||
00:00:06:02 T49S494F43ZB672F4QROO74ZB653AB
|
||||
00:00:06:03 T49S494F43ZB772F4QROO74ZB751AB
|
||||
00:00:06:04 T49S494F43ZB872F4QROO74ZB84FAB
|
||||
00:00:06:05 T49S494F43ZB972F4QROO74ZB94DAB
|
||||
00:00:06:06 T49S494F43ZBA72F4QROO74ZBA4BAB
|
||||
00:00:06:07 T49S494F43ZBB72F4QROO74ZBB49AB
|
||||
00:00:06:08 T49S494F43ZBC72F4QROO74ZBC47AB
|
||||
00:00:06:09 T49S494F43ZBD72F4QROO74ZBD45AB
|
||||
00:00:06:10 T49S494F43ZBE72F4QROO74ZBE43AB
|
||||
00:00:06:11 T49S494F43ZBF72F4QROO74ZBF41AB
|
||||
00:00:06:12 T49S494F43ZC072F4QROO74ZC03FAB
|
||||
00:00:06:13 T49S494F43ZC172F4QROO74ZC13DAB
|
||||
00:00:06:14 T49S494F43ZC272F4QROO74ZC23BAB
|
||||
00:00:06:15 T49S494F43ZC372F4QROO74ZC339AB
|
||||
00:00:06:16 T49S494F43ZC472F4QROO74ZC437AB
|
||||
00:00:06:17 T49S494F43ZC572F4QROO74ZC535AB
|
||||
00:00:06:18 T49S494F43ZC672F4QROO74ZC633AB
|
||||
00:00:06:19 T49S494F43ZC772F4QROO74ZC731AB
|
||||
00:00:06:20 T49S494F43ZC872F4QROO74ZC82FAB
|
||||
00:00:06:21 T49S494F43ZC972F4QROO74ZC92DAB
|
||||
00:00:06:22 T49S494F43ZCA72F4QROO74ZCA2BAB
|
||||
00:00:06:23 T49S494F43ZCB72F4QROO74ZCB29AB
|
||||
00:00:06:24 T49S494F43ZCC72F4QROO74ZCC27AB
|
||||
00:00:06:25 T49S494F43ZCD72F4QROO74ZCD25AB
|
||||
00:00:06:26 T49S494F43ZCE72F4QROO74ZCE23AB
|
||||
00:00:06:27 T49S494F43ZCF72F4QROO74ZCF21AB
|
||||
00:00:06:28 T49S494F43ZD072F4QROO74ZD01FAB
|
||||
00:00:06:29 T49S494F43ZD172F4QROO74ZD11DAB
|
||||
00:00:07:00 T49S494F43ZD272F4QROO74ZD21BAB
|
||||
00:00:07:01 T49S494F43ZD372F4QROO74ZD319AB
|
||||
00:00:07:02 T49S494F43ZD472F4QROO74ZD417AB
|
||||
00:00:07:03 T49S494F43ZD572F4QROO74ZD515AB
|
||||
00:00:07:04 T49S494F43ZD672F4QROO74ZD613AB
|
||||
00:00:07:05 T49S494F43ZD772F4QROO74ZD711AB
|
||||
00:00:07:06 T49S494F43ZD872F4QROO74ZD80FAB
|
||||
00:00:07:07 T49S494F43ZD972F4QROO74ZD90DAB
|
||||
00:00:07:08 T49S494F43ZDA72F4QROO74ZDA0BAB
|
||||
00:00:07:09 T49S494F43ZDB72F4QROO74ZDB09AB
|
||||
00:00:07:10 T49S494F43ZDC72F4QROO74ZDC07AB
|
||||
00:00:07:11 T49S494F43ZDD72F4QROO74ZDD05AB
|
||||
00:00:07:12 T49S494F43ZDE72F4QROO74ZDE03AB
|
||||
00:00:07:13 T49S494F43ZDF72F4QROO74ZDF01AB
|
||||
00:00:07:14 T49S494F43ZE072F4QROO74ZE0FFAB
|
||||
00:00:07:15 T49S494F43ZE172F4QROO74ZE1FDAB
|
||||
00:00:07:16 T49S494F43ZE272F4QROO74ZE2FBAB
|
||||
00:00:07:17 T49S494F43ZE372F4QROO74ZE3F9AB
|
||||
00:00:07:18 T49S494F43ZE472F4QROO74ZE4F7AB
|
||||
00:00:07:19 T49S494F43ZE572F4QROO74ZE5F5AB
|
||||
00:00:07:20 T49S494F43ZE672F4QROO74ZE6F3AB
|
||||
00:00:07:21 T49S494F43ZE772F4QROO74ZE7F1AB
|
||||
00:00:07:22 T49S494F43ZE872F4QROO74ZE8EFAB
|
||||
00:00:07:23 T49S494F43ZE972F4QROO74ZE9EDAB
|
||||
00:00:07:24 T49S494F43ZEA72F4QROO74ZEAEBAB
|
||||
00:00:07:25 T49S494F43ZEB72F4QROO74ZEBE9AB
|
||||
00:00:07:26 T49S494F43ZEC72F4QROO74ZECE7AB
|
||||
00:00:07:27 T49S494F43ZED72F4QROO74ZEDE5AB
|
||||
00:00:07:28 T49S494F43ZEE72F4QROO74ZEEE3AB
|
||||
00:00:07:29 T49S494F43ZEF72F4QROO74ZEFE1AB
|
||||
00:00:08:00 T49S494F43ZF072F4QROO74ZF0DFAB
|
||||
00:00:08:01 T49S494F43ZF172F4QROO74ZF1DDAB
|
||||
00:00:08:02 T49S494F43ZF272F4QROO74ZF2DBAB
|
||||
00:00:08:03 T49S494F43ZF372F4QROO74ZF3D9AB
|
||||
00:00:08:04 T49S494F43ZF472F4QROO74ZF4D7AB
|
||||
00:00:08:05 T49S494F43ZF572F4QROO74ZF5D5AB
|
||||
00:00:08:06 T49S494F43ZF672F4QROO74ZF6D3AB
|
||||
00:00:08:07 T49S494F43ZF772F4QROO74ZF7D1AB
|
||||
00:00:08:08 T49S494F43ZF872F4QROO74ZF8CFAB
|
||||
00:00:08:09 T49S494F43ZF972F4QROO74ZF9CDAB
|
||||
00:00:08:10 T49S494F43ZFA72F4QROO74ZFACBAB
|
||||
00:00:08:11 T49S494F43ZFB72F4QROO74ZFBC9AB
|
||||
00:00:08:12 T49S494F43ZFC72F4QROO74ZFCC7AB
|
||||
00:00:08:13 T49S494F43ZFD72F4QROO74ZFDC5AB
|
||||
00:00:08:14 T49S494F43ZFE72F4QROO74ZFEC3AB
|
||||
00:00:08:15 T49S494F43ZFF72F4QROO74ZFFC1AB
|
||||
00:00:08:16 T49S494F4301Z72F4QROO7401ZBDAB
|
||||
00:00:08:17 T49S494F43010172F4QROO740101BBAB
|
||||
00:00:08:18 T49S494F43010272F4QROO740102B9AB
|
||||
00:00:08:19 T49S494F43010372F4QROO740103B7AB
|
||||
00:00:08:20 T49S494F43010472F4QROO740104B5AB
|
||||
00:00:08:21 T49S494F43010572F4QROO740105B3AB
|
||||
00:00:08:22 T49S494F43010672F4QROO740106B1AB
|
||||
00:00:08:23 T49S494F43010772F4QROO740107AFAB
|
||||
00:00:08:24 T49S494F43010872F4QROO740108ADAB
|
||||
00:00:08:25 T49S494F43010972F4QROO740109ABAB
|
||||
00:00:08:26 T49S494F43010A72F4QROO74010AA9AB
|
||||
00:00:08:27 T49S494F43010B72F4QROO74010BA7AB
|
||||
00:00:08:28 T49S494F43010C72F4QROO74010CA5AB
|
||||
00:00:08:29 T49S494F43010D72F4QROO74010DA3AB
|
||||
00:00:09:00 T49S494F43010E72F4QROO74010EA1AB
|
||||
00:00:09:01 T49S494F43010F72F4QROO74010F9FAB
|
||||
00:00:09:02 T49S494F43011072F4QROO7401109DAB
|
||||
00:00:09:03 T49S494F43011172F4QROO7401119BAB
|
||||
00:00:09:04 T49S494F43011272F4QROO74011299AB
|
||||
00:00:09:05 T49S494F43011372F4QROO74011397AB
|
||||
00:00:09:06 T49S494F43011472F4QROO74011495AB
|
||||
00:00:09:07 T49S494F43011572F4QROO74011593AB
|
||||
00:00:09:08 T49S494F43011672F4QROO74011691AB
|
||||
00:00:09:09 T49S494F43011772F4QROO7401178FAB
|
||||
00:00:09:10 T49S494F43011872F4QROO7401188DAB
|
||||
00:00:09:11 T49S494F43011972F4QROO7401198BAB
|
||||
00:00:09:12 T49S494F43011A72F4QROO74011A89AB
|
||||
00:00:09:13 T49S494F43011B72F4QROO74011B87AB
|
||||
00:00:09:14 T49S494F43011C72F4QROO74011C85AB
|
||||
00:00:09:15 T49S494F43011D72F4QROO74011D83AB
|
||||
00:00:09:16 T49S494F43011E72F4QROO74011E81AB
|
||||
00:00:09:17 T49S494F43011F72F4QROO74011F7FAB
|
||||
00:00:09:18 T49S494F43012072F4QROO7401207DAB
|
||||
00:00:09:19 T49S494F43012172F4QROO7401217BAB
|
||||
00:00:09:20 T49S494F43012272F4QROO74012279AB
|
||||
00:00:09:21 T49S494F43012372F4QROO74012377AB
|
||||
00:00:09:22 T49S494F43012472F4QROO74012475AB
|
||||
00:00:09:23 T49S494F43012572F4QROO74012573AB
|
||||
00:00:09:24 T49S494F43012672F4QROO74012671AB
|
||||
00:00:09:25 T49S494F43012772F4QROO7401276FAB
|
||||
00:00:09:26 T49S494F43012872F4QROO7401286DAB
|
||||
00:00:09:27 T49S494F43012972F4QROO7401296BAB
|
||||
00:00:09:28 T49S494F43012A72F4QROO74012A69AB
|
||||
00:00:09:29 T49S494F43012B72F4QROO74012B67AB
|
||||
00:00:10:00 T49S494F43012C72F4QROO74012C65AB
|
||||
00:00:10:01 T49S494F43012D72F4QROO74012D63AB
|
||||
00:00:10:02 T49S494F43012E72F4QROO74012E61AB
|
||||
00:00:10:03 T49S494F43012F72F4QROO74012F5FAB
|
||||
00:00:10:04 T49S494F43013072F4QROO7401305DAB
|
||||
00:00:10:05 T49S494F43013172F4QROO7401315BAB
|
||||
00:00:10:06 T49S494F43013272F4QROO74013259AB
|
||||
00:00:10:07 T49S494F43013372F4QROO74013357AB
|
||||
00:00:10:08 T49S494F43013472F4QROO74013455AB
|
||||
00:00:10:09 T49S494F43013572F4QROO74013553AB
|
||||
00:00:10:10 T49S494F43013672F4QROO74013651AB
|
||||
00:00:10:11 T49S494F43013772F4QROO7401374FAB
|
||||
00:00:10:12 T49S494F43013872F4QROO7401384DAB
|
||||
00:00:10:13 T49S494F43013972F4QROO7401394BAB
|
||||
00:00:10:14 T49S494F43013A72F4QROO74013A49AB
|
||||
00:00:10:15 T49S494F43013B72F4QROO74013B47AB
|
||||
00:00:10:16 T49S494F43013C72F4QROO74013C45AB
|
||||
00:00:10:17 T49S494F43013D72F4QROO74013D43AB
|
||||
00:00:10:18 T49S494F43013E72F4QROO74013E41AB
|
||||
00:00:10:19 T49S494F43013F72F4QROO74013F3FAB
|
||||
00:00:10:20 T49S494F43014072F4QROO7401403DAB
|
||||
00:00:10:21 T49S494F43014172F4QROO7401413BAB
|
||||
00:00:10:22 T49S494F43014272F4QROO74014239AB
|
||||
00:00:10:23 T49S494F43014372F4QROO74014337AB
|
||||
00:00:10:24 T49S494F43014472F4QROO74014435AB
|
||||
00:00:10:25 T49S494F43014572F4QROO74014533AB
|
||||
00:00:10:26 T49S494F43014672F4QROO74014631AB
|
||||
00:00:10:27 T49S494F43014772F4QROO7401472FAB
|
||||
00:00:10:28 T49S494F43014872F4QROO7401482DAB
|
||||
00:00:10:29 T49S494F43014972F4QROO7401492BAB
|
||||
00:00:11:00 T49S494F43014A72F4QROO74014A29AB
|
||||
00:00:11:01 T49S494F43014B72F4QROO74014B27AB
|
||||
00:00:11:02 T49S494F43014C72F4QROO74014C25AB
|
||||
00:00:11:03 T49S494F43014D72F4QROO74014D23AB
|
||||
00:00:11:04 T49S494F43014E72F4QROO74014E21AB
|
||||
00:00:11:05 T49S494F43014F72F4QROO74014F1FAB
|
||||
00:00:11:06 T49S494F43015072F4QROO7401501DAB
|
||||
00:00:11:07 T49S494F43015172F4QROO7401511BAB
|
||||
00:00:11:08 T49S494F43015272F4QROO74015219AB
|
||||
00:00:11:09 T49S494F43015372F4QROO74015317AB
|
||||
00:00:11:10 T49S494F43015472F4QROO74015415AB
|
||||
00:00:11:11 T49S494F43015572F4QROO74015513AB
|
||||
00:00:11:12 T49S494F43015672F4QROO74015611AB
|
||||
00:00:11:13 T49S494F43015772F4QROO7401570FAB
|
||||
00:00:11:14 T49S494F43015872F4QROO7401580DAB
|
||||
00:00:11:15 T49S494F43015972F4QROO7401590BAB
|
||||
00:00:11:16 T49S494F43015A72F4QROO74015A09AB
|
||||
00:00:11:17 T49S494F43015B72F4QROO74015B07AB
|
||||
00:00:11:18 T49S494F43015C72F4QROO74015C05AB
|
||||
00:00:11:19 T49S494F43015D72F4QROO74015D03AB
|
||||
00:00:11:20 T49S494F43015E72F4QROO74015E01AB
|
||||
00:00:11:21 T49S494F43015F72F4QROO74015FFFAB
|
||||
00:00:11:22 T49S494F43016072F4QROO740160FDAB
|
||||
00:00:11:23 T49S494F43016172F4QROO740161FBAB
|
||||
00:00:11:24 T49S494F43016272F4QROO740162F9AB
|
||||
00:00:11:25 T49S494F43016372F4QROO740163F7AB
|
||||
00:00:11:26 T49S494F43016472F4QROO740164F5AB
|
||||
00:00:11:27 T52S524F67016572F4QRFF4222FE8C02OM739181656E67817FFF7401650DB4
|
||||
00:00:11:28 T49S494F43016672F4QROO740166F1AB
|
||||
00:00:11:29 T49S494F43016772F4QROO740167EFAB
|
||||
00:00:12:00 T49S494F43016872F4QROO740168EDAB
|
||||
00:00:12:01 T49S494F43016972F4QROO740169EBAB
|
||||
00:00:12:02 T49S494F43016A72F4QROO74016AE9AB
|
||||
00:00:12:03 T49S494F43016B72F4QROO74016BE7AB
|
||||
00:00:12:04 T49S494F43016C72F4QROO74016CE5AB
|
||||
00:00:12:05 T49S494F43016D72F4QROO74016DE3AB
|
||||
00:00:12:06 T49S494F43016E72F4QROO74016EE1AB
|
||||
00:00:12:07 T52S524F67016F72F4QRFFC324FE88ZFE8BFFOL739181656E67817FFF74016FEEB4
|
||||
00:00:12:08 T49S494F43017072F4QROO740170DDAB
|
||||
00:00:12:09 T49S494F43017172F4QROO740171DBAB
|
||||
00:00:12:10 T49S494F43017272F4QROO740172D9AB
|
||||
00:00:12:11 T49S494F43017372F4QROO740173D7AB
|
||||
00:00:12:12 T49S494F43017472F4QROO740174D5AB
|
||||
00:00:12:13 T49S494F43017572F4QROO740175D3AB
|
||||
00:00:12:14 T49S494F43017672F4QROO740176D1AB
|
||||
00:00:12:15 T49S494F43017772F4QROO740177CFAB
|
||||
00:00:12:16 T49S494F43017872F4QROO740178CDAB
|
||||
00:00:12:17 T49S494F43017972F4QROO740179CBAB
|
||||
00:00:12:18 T49S494F43017A72F4QROO74017AC9AB
|
||||
00:00:12:19 T49S494F43017B72F4QROO74017BC7AB
|
||||
00:00:12:20 T49S494F43017C72F4QROO74017CC5AB
|
||||
00:00:12:21 T49S494F43017D72F4QROO74017DC3AB
|
||||
00:00:12:22 T49S494F43017E72F4QROO74017EC1AB
|
||||
00:00:12:23 T49S494F43017F72F4QROO74017FBFAB
|
||||
00:00:12:24 T49S494F43018072F4QROO740180BDAB
|
||||
00:00:12:25 T49S494F43018172F4QROO740181BBAB
|
||||
00:00:12:26 T49S494F43018272F4QROO740182B9AB
|
||||
00:00:12:27 T49S494F43018372F4QROO740183B7AB
|
||||
00:00:12:28 T49S494F43018472F4QROO740184B5AB
|
||||
00:00:12:29 T49S494F43018572F4QROO740185B3AB
|
||||
00:00:13:00 T49S494F43018672F4QROO740186B1AB
|
||||
00:00:13:01 T49S494F43018772F4QROO740187AFAB
|
||||
00:00:13:02 T49S494F43018872F4QROO740188ADAB
|
||||
00:00:13:03 T49S494F43018972F4QROO740189ABAB
|
||||
00:00:13:04 T49S494F43018A72F4QROO74018AA9AB
|
||||
00:00:13:05 T49S494F43018B72F4QROO74018BA7AB
|
||||
00:00:13:06 T49S494F43018C72F4QROO74018CA5AB
|
||||
00:00:13:07 T49S494F43018D72F4QROO74018DA3AB
|
||||
00:00:13:08 T49S494F43018E72F4QROO74018EA1AB
|
||||
00:00:13:09 T49S494F43018F72F4QROO74018F9FAB
|
||||
00:00:13:10 T49S494F43019072F4QROO7401909DAB
|
||||
00:00:13:11 T49S494F43019172F4QROO7401919BAB
|
||||
00:00:13:12 T49S494F43019272F4QROO74019299AB
|
||||
00:00:13:13 T49S494F43019372F4QROO74019397AB
|
||||
00:00:13:14 T49S494F43019472F4QROO74019495AB
|
||||
00:00:13:15 T49S494F43019572F4QROO74019593AB
|
||||
00:00:13:16 T49S494F43019672F4QROO74019691AB
|
||||
00:00:13:17 T49S494F43019772F4QROO7401978FAB
|
||||
00:00:13:18 T49S494F43019872F4QROO7401988DAB
|
||||
00:00:13:19 T49S494F43019972F4QROO7401998BAB
|
||||
00:00:13:20 T49S494F43019A72F4QROO74019A89AB
|
||||
00:00:13:21 T49S494F43019B72F4QROO74019B87AB
|
||||
00:00:13:22 T49S494F43019C72F4QROO74019C85AB
|
||||
00:00:13:23 T49S494F43019D72F4QROO74019D83AB
|
||||
00:00:13:24 T49S494F43019E72F4QROO74019E81AB
|
||||
00:00:13:25 T49S494F43019F72F4QROO74019F7FAB
|
||||
00:00:13:26 T49S494F4301A072F4QROO7401A07DAB
|
||||
00:00:13:27 T49S494F4301A172F4QROO7401A17BAB
|
||||
00:00:13:28 T49S494F4301A272F4QROO7401A279AB
|
||||
00:00:13:29 T49S494F4301A372F4QROO7401A377AB
|
||||
00:00:14:00 T49S494F4301A472F4QROO7401A475AB
|
||||
00:00:14:01 T49S494F4301A572F4QROO7401A573AB
|
||||
00:00:14:02 T49S494F4301A672F4QROO7401A671AB
|
||||
00:00:14:03 T49S494F4301A772F4QROO7401A76FAB
|
||||
00:00:14:04 T49S494F4301A872F4QROO7401A86DAB
|
||||
00:00:14:05 T49S494F4301A972F4QROO7401A96BAB
|
||||
00:00:14:06 T49S494F4301AA72F4QROO7401AA69AB
|
||||
00:00:14:07 T49S494F4301AB72F4QROO7401AB67AB
|
||||
00:00:14:08 T49S494F4301AC72F4QROO7401AC65AB
|
||||
00:00:14:09 T49S494F4301AD72F4QROO7401AD63AB
|
||||
00:00:14:10 T49S494F4301AE72F4QROO7401AE61AB
|
||||
00:00:14:11 T49S494F4301AF72F4QROO7401AF5FAB
|
||||
00:00:14:12 T49S494F4301B072F4QROO7401B05DAB
|
||||
00:00:14:13 T49S494F4301B172F4QROO7401B15BAB
|
||||
00:00:14:14 T49S494F4301B272F4QROO7401B259AB
|
||||
00:00:14:15 T49S494F4301B372F4QROO7401B357AB
|
||||
00:00:14:16 T49S494F4301B472F4QROO7401B455AB
|
||||
00:00:14:17 T49S494F4301B572F4QROO7401B553AB
|
||||
00:00:14:18 T49S494F4301B672F4QROO7401B651AB
|
||||
00:00:14:19 T49S494F4301B772F4QROO7401B74FAB
|
||||
00:00:14:20 T49S494F4301B872F4QROO7401B84DAB
|
||||
00:00:14:21 T49S494F4301B972F4QROO7401B94BAB
|
||||
00:00:14:22 T49S494F4301BA72F4QROO7401BA49AB
|
||||
00:00:14:23 T49S494F4301BB72F4QROO7401BB47AB
|
||||
00:00:14:24 T49S494F4301BC72F4QROO7401BC45AB
|
||||
00:00:14:25 T49S494F4301BD72F4QROO7401BD43AB
|
||||
00:00:14:26 T49S494F4301BE72F4QROO7401BE41AB
|
||||
00:00:14:27 T49S494F4301BF72F4QROO7401BF3FAB
|
||||
00:00:14:28 T49S494F4301C072F4QROO7401C03DAB
|
||||
00:00:14:29 T49S494F4301C172F4QROO7401C13BAB
|
||||
00:00:15:00 T49S494F4301C272F4QROO7401C239AB
|
||||
00:00:15:01 T49S494F4301C372F4QROO7401C337AB
|
||||
00:00:15:02 T49S494F4301C472F4QROO7401C435AB
|
||||
00:00:15:03 T49S494F4301C572F4QROO7401C533AB
|
||||
00:00:15:04 T49S494F4301C672F4QROO7401C631AB
|
||||
00:00:15:05 T49S494F4301C772F4QROO7401C72FAB
|
||||
00:00:15:06 T49S494F4301C872F4QROO7401C82DAB
|
||||
00:00:15:07 T49S494F4301C972F4QROO7401C92BAB
|
||||
00:00:15:08 T49S494F4301CA72F4QROO7401CA29AB
|
||||
00:00:15:09 T49S494F4301CB72F4QROO7401CB27AB
|
||||
00:00:15:10 T49S494F4301CC72F4QROO7401CC25AB
|
||||
00:00:15:11 T49S494F4301CD72F4QROO7401CD23AB
|
||||
00:00:15:12 T49S494F4301CE72F4QROO7401CE21AB
|
||||
00:00:15:13 T49S494F4301CF72F4QROO7401CF1FAB
|
||||
00:00:15:14 T49S494F4301D072F4QROO7401D01DAB
|
||||
00:00:15:15 T49S494F4301D172F4QROO7401D11BAB
|
||||
00:00:15:16 T49S494F4301D272F4QROO7401D219AB
|
||||
00:00:15:17 T49S494F4301D372F4QROO7401D317AB
|
||||
00:00:15:18 T49S494F4301D472F4QROO7401D415AB
|
||||
00:00:15:19 T49S494F4301D572F4QROO7401D513AB
|
||||
00:00:15:20 T49S494F4301D672F4QROO7401D611AB
|
||||
00:00:15:21 T49S494F4301D772F4QROO7401D70FAB
|
||||
00:00:15:22 T49S494F4301D872F4QROO7401D80DAB
|
||||
00:00:15:23 T49S494F4301D972F4QROO7401D90BAB
|
||||
00:00:15:24 T49S494F4301DA72F4QROO7401DA09AB
|
||||
00:00:15:25 T49S494F4301DB72F4QROO7401DB07AB
|
||||
00:00:15:26 T49S494F4301DC72F4QROO7401DC05AB
|
||||
00:00:15:27 T49S494F4301DD72F4QROO7401DD03AB
|
||||
00:00:15:28 T49S494F4301DE72F4QROO7401DE01AB
|
||||
00:00:15:29 T49S494F4301DF72F4QROO7401DFFFAB
|
||||
00:00:16:00 T49S494F4301E072F4QROO7401E0FDAB
|
||||
00:00:16:01 T49S494F4301E172F4QROO7401E1FBAB
|
||||
00:00:16:02 T49S494F4301E272F4QROO7401E2F9AB
|
||||
00:00:16:03 T49S494F4301E372F4QROO7401E3F7AB
|
||||
00:00:16:04 T49S494F4301E472F4QROO7401E4F5AB
|
||||
00:00:16:05 T49S494F4301E572F4QROO7401E5F3AB
|
||||
00:00:16:06 T49S494F4301E672F4QROO7401E6F1AB
|
||||
00:00:16:07 T49S494F4301E772F4QROO7401E7EFAB
|
||||
00:00:16:08 T49S494F4301E872F4QROO7401E8EDAB
|
||||
00:00:16:09 T49S494F4301E972F4QROO7401E9EBAB
|
||||
00:00:16:10 T49S494F4301EA72F4QROO7401EAE9AB
|
||||
00:00:16:11 T49S494F4301EB72F4QROO7401EBE7AB
|
||||
00:00:16:12 T49S494F4301EC72F4QROO7401ECE5AB
|
||||
00:00:16:13 T49S494F4301ED72F4QROO7401EDE3AB
|
||||
00:00:16:14 T49S494F4301EE72F4QROO7401EEE1AB
|
||||
00:00:16:15 T49S494F4301EF72F4QROO7401EFDFAB
|
||||
00:00:16:16 T49S494F4301F072F4QROO7401F0DDAB
|
||||
00:00:16:17 T49S494F4301F172F4QROO7401F1DBAB
|
||||
00:00:16:18 T49S494F4301F272F4QROO7401F2D9AB
|
||||
00:00:16:19 T49S494F4301F372F4QROO7401F3D7AB
|
||||
00:00:16:20 T49S494F4301F472F4QROO7401F4D5AB
|
||||
00:00:16:21 T49S494F4301F572F4QROO7401F5D3AB
|
||||
00:00:16:22 T49S494F4301F672F4QROO7401F6D1AB
|
||||
00:00:16:23 T49S494F4301F772F4QROO7401F7CFAB
|
||||
00:00:16:24 T49S494F4301F872F4QROO7401F8CDAB
|
||||
00:00:16:25 T49S494F4301F972F4QROO7401F9CBAB
|
||||
00:00:16:26 T49S494F4301FA72F4QROO7401FAC9AB
|
||||
00:00:16:27 T49S494F4301FB72F4QROO7401FBC7AB
|
||||
00:00:16:28 T49S494F4301FC72F4QROO7401FCC5AB
|
||||
00:00:16:29 T49S494F4301FD72F4QROO7401FDC3AB
|
||||
00:00:17:00 T49S494F4301FE72F4QROO7401FEC1AB
|
||||
00:00:17:01 T49S494F4301FF72F4QROO7401FFBFAB
|
||||
00:00:17:02 T49S494F4302Z72F4QROO7402ZBBAB
|
||||
00:00:17:03 T49S494F43020172F4QROO740201B9AB
|
||||
00:00:17:04 T49S494F43020272F4QROO740202B7AB
|
||||
00:00:17:05 T49S494F43020372F4QROO740203B5AB
|
||||
00:00:17:06 T49S494F43020472F4QROO740204B3AB
|
||||
00:00:17:07 T49S494F43020572F4QROO740205B1AB
|
||||
00:00:17:08 T49S494F43020672F4QROO740206AFAB
|
||||
00:00:17:09 T49S494F43020772F4QROO740207ADAB
|
||||
00:00:17:10 T49S494F43020872F4QROO740208ABAB
|
||||
00:00:17:11 T49S494F43020972F4QROO740209A9AB
|
||||
00:00:17:12 T49S494F43020A72F4QROO74020AA7AB
|
||||
00:00:17:13 T49S494F43020B72F4QROO74020BA5AB
|
||||
00:00:17:14 T49S494F43020C72F4QROO74020CA3AB
|
||||
00:00:17:15 T49S494F43020D72F4QROO74020DA1AB
|
||||
00:00:17:16 T49S494F43020E72F4QROO74020E9FAB
|
||||
00:00:17:17 T49S494F43020F72F4QROO74020F9DAB
|
||||
00:00:17:18 T49S494F43021072F4QROO7402109BAB
|
||||
00:00:17:19 T49S494F43021172F4QROO74021199AB
|
||||
00:00:17:20 T49S494F43021272F4QROO74021297AB
|
||||
00:00:17:21 T49S494F43021372F4QROO74021395AB
|
||||
00:00:17:22 T49S494F43021472F4QROO74021493AB
|
||||
00:00:17:23 T49S494F43021572F4QROO74021591AB
|
||||
00:00:17:24 T49S494F43021672F4QROO7402168FAB
|
||||
00:00:17:25 T49S494F43021772F4QROO7402178DAB
|
||||
00:00:17:26 T49S494F43021872F4QROO7402188BAB
|
||||
00:00:17:27 T49S494F43021972F4QROO74021989AB
|
||||
00:00:17:28 T49S494F43021A72F4QROO74021A87AB
|
||||
00:00:17:29 T49S494F43021B72F4QROO74021B85AB
|
||||
00:00:18:00 T49S494F43021C72F4QROO74021C83AB
|
||||
00:00:18:01 T49S494F43021D72F4QROO74021D81AB
|
||||
00:00:18:02 T49S494F43021E72F4QROO74021E7FAB
|
||||
00:00:18:03 T49S494F43021F72F4QROO74021F7DAB
|
||||
00:00:18:04 T49S494F43022072F4QROO7402207BAB
|
||||
00:00:18:05 T49S494F43022172F4QROO74022179AB
|
||||
00:00:18:06 T49S494F43022272F4QROO74022277AB
|
||||
00:00:18:07 T49S494F43022372F4QROO74022375AB
|
||||
00:00:18:08 T49S494F43022472F4QROO74022473AB
|
||||
00:00:18:09 T49S494F43022572F4QROO74022571AB
|
||||
00:00:18:10 T49S494F43022672F4QROO7402266FAB
|
||||
00:00:18:11 T49S494F43022772F4QROO7402276DAB
|
||||
00:00:18:12 T49S494F43022872F4QROO7402286BAB
|
||||
00:00:18:13 T49S494F43022972F4QROO74022969AB
|
||||
00:00:18:14 T49S494F43022A72F4QROO74022A67AB
|
||||
00:00:18:15 T49S494F43022B72F4QROO74022B65AB
|
||||
00:00:18:16 T49S494F43022C72F4QROO74022C63AB
|
||||
00:00:18:17 T49S494F43022D72F4QROO74022D61AB
|
||||
00:00:18:18 T49S494F43022E72F4QROO74022E5FAB
|
||||
00:00:18:19 T49S494F43022F72F4QROO74022F5DAB
|
||||
00:00:18:20 T49S494F43023072F4QROO7402305BAB
|
||||
00:00:18:21 T49S494F43023172F4QROO74023159AB
|
||||
00:00:18:22 T49S494F43023272F4QROO74023257AB
|
||||
00:00:18:23 T49S494F43023372F4QROO74023355AB
|
||||
00:00:18:24 T49S494F43023472F4QROO74023453AB
|
||||
00:00:18:25 T49S494F43023572F4QROO74023551AB
|
||||
00:00:18:26 T49S494F43023672F4QROO7402364FAB
|
||||
00:00:18:27 T49S494F43023772F4QROO7402374DAB
|
||||
00:00:18:28 T49S494F43023872F4QROO7402384BAB
|
||||
00:00:18:29 T49S494F43023972F4QROO74023949AB
|
||||
00:00:19:00 T49S494F43023A72F4QROO74023A47AB
|
||||
00:00:19:01 T49S494F43023B72F4QROO74023B45AB
|
||||
00:00:19:02 T49S494F43023C72F4QROO74023C43AB
|
||||
00:00:19:03 T49S494F43023D72F4QROO74023D41AB
|
||||
00:00:19:04 T49S494F43023E72F4QROO74023E3FAB
|
||||
00:00:19:05 T49S494F43023F72F4QROO74023F3DAB
|
||||
00:00:19:06 T49S494F43024072F4QROO7402403BAB
|
||||
00:00:19:07 T52S524F67024172F4QRFF4222FE8CFFOM739181656E67817FFF74024156B4
|
143
gst-plugin-closedcaption/tests/mcc_enc.rs
Normal file
143
gst-plugin-closedcaption/tests/mcc_enc.rs
Normal file
|
@ -0,0 +1,143 @@
|
|||
// Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
|
||||
// Boston, MA 02110-1335, USA.
|
||||
|
||||
extern crate glib;
|
||||
use glib::prelude::*;
|
||||
|
||||
extern crate gstreamer as gst;
|
||||
extern crate gstreamer_check as gst_check;
|
||||
extern crate gstreamer_video as gst_video;
|
||||
|
||||
extern crate gstrsclosedcaption;
|
||||
|
||||
#[macro_use]
|
||||
extern crate pretty_assertions;
|
||||
|
||||
fn init() {
|
||||
use std::sync::{Once, ONCE_INIT};
|
||||
static INIT: Once = ONCE_INIT;
|
||||
|
||||
INIT.call_once(|| {
|
||||
gst::init().unwrap();
|
||||
gstrsclosedcaption::plugin_register_static();
|
||||
});
|
||||
}
|
||||
|
||||
/// Encode a single CDP packet and compare the output
|
||||
#[test]
|
||||
fn test_encode() {
|
||||
init();
|
||||
|
||||
let input = [
|
||||
0x96, 0x69, 0x52, 0x4f, 0x67, 0x00, 0x00, 0x72, 0xf4, 0xfc, 0x80, 0x80, 0xfd, 0x80, 0x80,
|
||||
0xff, 0x02, 0x22, 0xfe, 0x8c, 0xff, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
|
||||
0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
|
||||
0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00,
|
||||
0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x73, 0x91, 0x81, 0x65, 0x6e, 0x67,
|
||||
0x81, 0x7f, 0xff, 0x74, 0x00, 0x00, 0x1c,
|
||||
];
|
||||
|
||||
let expected_output = b"File Format=MacCaption_MCC V1.0\r\n\
|
||||
\r\n\
|
||||
///////////////////////////////////////////////////////////////////////////////////\r\n\
|
||||
// Computer Prompting and Captioning Company\r\n\
|
||||
// Ancillary Data Packet Transfer File\r\n\
|
||||
//\r\n\
|
||||
// Permission to generate this format is granted provided that\r\n\
|
||||
// 1. This ANC Transfer file format is used on an as-is basis and no warranty is given, and\r\n\
|
||||
// 2. This entire descriptive information text is included in a generated .mcc file.\r\n\
|
||||
//\r\n\
|
||||
// General file format:\r\n\
|
||||
// HH:MM:SS:FF(tab)[Hexadecimal ANC data in groups of 2 characters]\r\n\
|
||||
// Hexadecimal data starts with the Ancillary Data Packet DID (Data ID defined in S291M)\r\n\
|
||||
// and concludes with the Check Sum following the User Data Words.\r\n\
|
||||
// Each time code line must contain at most one complete ancillary data packet.\r\n\
|
||||
// To transfer additional ANC Data successive lines may contain identical time code.\r\n\
|
||||
// Time Code Rate=[24, 25, 30, 30DF, 50, 60]\r\n\
|
||||
//\r\n\
|
||||
// ANC data bytes may be represented by one ASCII character according to the following schema:\r\n\
|
||||
// G FAh 00h 00h\r\n\
|
||||
// H 2 x (FAh 00h 00h)\r\n\
|
||||
// I 3 x (FAh 00h 00h)\r\n\
|
||||
// J 4 x (FAh 00h 00h)\r\n\
|
||||
// K 5 x (FAh 00h 00h)\r\n\
|
||||
// L 6 x (FAh 00h 00h)\r\n\
|
||||
// M 7 x (FAh 00h 00h)\r\n\
|
||||
// N 8 x (FAh 00h 00h)\r\n\
|
||||
// O 9 x (FAh 00h 00h)\r\n\
|
||||
// P FBh 80h 80h\r\n\
|
||||
// Q FCh 80h 80h\r\n\
|
||||
// R FDh 80h 80h\r\n\
|
||||
// S 96h 69h\r\n\
|
||||
// T 61h 01h\r\n\
|
||||
// U E1h 00h 00h 00h\r\n\
|
||||
// Z 00h\r\n\
|
||||
//\r\n\
|
||||
///////////////////////////////////////////////////////////////////////////////////\r\n\
|
||||
\r\n\
|
||||
UUID=14720C04-857D-40E2-86FC-F080DE44CE74\r\n\
|
||||
Creation Program=GStreamer MCC Encoder 0.4.0\r\n\
|
||||
Creation Date=Thursday, December 27, 2018\r\n\
|
||||
Creation Time=17:34:47\r\n\
|
||||
Time Code Rate=30DF\r\n\
|
||||
\r\n\
|
||||
11:12:13;14 T52S524F67ZZ72F4QRFF0222FE8CFFOM739181656E67817FFF74ZZ1CZ\r\n";
|
||||
|
||||
let mut h = gst_check::Harness::new("mccenc");
|
||||
{
|
||||
let enc = h.get_element().expect("could not create encoder");
|
||||
enc.set_property("uuid", &"14720C04-857D-40E2-86FC-F080DE44CE74")
|
||||
.unwrap();
|
||||
enc.set_property(
|
||||
"creation-date",
|
||||
&glib::DateTime::new_utc(2018, 12, 27, 17, 34, 47.0),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
h.set_src_caps_str(
|
||||
"closedcaption/x-cea-708, format=(string)cdp, framerate=(fraction)30000/1001",
|
||||
);
|
||||
|
||||
let buf = {
|
||||
let mut buf = gst::Buffer::from_mut_slice(Vec::from(&input[..]));
|
||||
let buf_ref = buf.get_mut().unwrap();
|
||||
let tc = gst_video::ValidVideoTimeCode::new(
|
||||
gst::Fraction::new(30000, 1001),
|
||||
None,
|
||||
gst_video::VideoTimeCodeFlags::DROP_FRAME,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
0,
|
||||
)
|
||||
.unwrap();
|
||||
gst_video::VideoTimeCodeMeta::add(buf_ref, &tc);
|
||||
buf
|
||||
};
|
||||
|
||||
assert_eq!(h.push(buf), Ok(gst::FlowSuccess::Ok));
|
||||
h.push_event(gst::Event::new_eos().build());
|
||||
|
||||
let buf = h.pull().expect("Couldn't pull buffer");
|
||||
let map = buf.map_readable().expect("Couldn't map buffer readable");
|
||||
assert_eq!(
|
||||
std::str::from_utf8(map.as_ref()),
|
||||
std::str::from_utf8(expected_output.as_ref())
|
||||
);
|
||||
}
|
136
gst-plugin-closedcaption/tests/mcc_parse.rs
Normal file
136
gst-plugin-closedcaption/tests/mcc_parse.rs
Normal file
|
@ -0,0 +1,136 @@
|
|||
// Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Library General Public
|
||||
// License as published by the Free Software Foundation; either
|
||||
// version 2 of the License, or (at your option) any later version.
|
||||
//
|
||||
// This library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// Library General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Library General Public
|
||||
// License along with this library; if not, write to the
|
||||
// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
|
||||
// Boston, MA 02110-1335, USA.
|
||||
|
||||
extern crate glib;
|
||||
|
||||
extern crate gstreamer as gst;
|
||||
use gst::prelude::*;
|
||||
extern crate gstreamer_check as gst_check;
|
||||
extern crate gstreamer_video as gst_video;
|
||||
|
||||
extern crate rand;
|
||||
use rand::{Rng, SeedableRng};
|
||||
|
||||
extern crate gstrsclosedcaption;
|
||||
|
||||
#[macro_use]
|
||||
extern crate pretty_assertions;
|
||||
|
||||
fn init() {
|
||||
use std::sync::{Once, ONCE_INIT};
|
||||
static INIT: Once = ONCE_INIT;
|
||||
|
||||
INIT.call_once(|| {
|
||||
gst::init().unwrap();
|
||||
gstrsclosedcaption::plugin_register_static();
|
||||
});
|
||||
}
|
||||
|
||||
/// Randomized test passing buffers of arbitrary sizes to the parser
|
||||
#[test]
|
||||
fn test_parse() {
|
||||
init();
|
||||
let mut data = include_bytes!("captions-test_708.mcc").as_ref();
|
||||
|
||||
let mut rnd = if let Ok(seed) = std::env::var("MCC_PARSE_TEST_SEED") {
|
||||
rand::rngs::SmallRng::seed_from_u64(
|
||||
seed.parse::<u64>()
|
||||
.expect("MCC_PARSE_TEST_SEED has to contain a 64 bit integer seed"),
|
||||
)
|
||||
} else {
|
||||
let seed = rand::random::<u64>();
|
||||
println!("seed {}", seed);
|
||||
rand::rngs::SmallRng::seed_from_u64(seed)
|
||||
};
|
||||
|
||||
let mut h = gst_check::Harness::new("mccparse");
|
||||
|
||||
let mut input_len = 0;
|
||||
let mut output_len = 0;
|
||||
let mut checksum = 0u32;
|
||||
let mut expected_timecode = None;
|
||||
|
||||
while !data.is_empty() {
|
||||
let l = if data.len() == 1 {
|
||||
1
|
||||
} else {
|
||||
rnd.gen_range(1, data.len())
|
||||
};
|
||||
let buf = gst::Buffer::from_mut_slice(Vec::from(&data[0..l]));
|
||||
input_len += buf.get_size();
|
||||
assert_eq!(h.push(buf), Ok(gst::FlowSuccess::Ok));
|
||||
while let Some(buf) = h.try_pull() {
|
||||
output_len += buf.get_size();
|
||||
checksum = checksum.wrapping_add(
|
||||
buf.map_readable()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.fold(0u32, |s, v| s.wrapping_add(*v as u32)),
|
||||
);
|
||||
|
||||
let tc_meta = buf
|
||||
.get_meta::<gst_video::VideoTimeCodeMeta>()
|
||||
.expect("No timecode meta");
|
||||
if let Some(ref timecode) = expected_timecode {
|
||||
assert_eq!(&tc_meta.get_tc(), timecode);
|
||||
} else {
|
||||
expected_timecode = Some(tc_meta.get_tc());
|
||||
}
|
||||
expected_timecode.as_mut().map(|tc| tc.increment_frame());
|
||||
}
|
||||
data = &data[l..];
|
||||
}
|
||||
|
||||
h.push_event(gst::Event::new_eos().build());
|
||||
while let Some(buf) = h.try_pull() {
|
||||
output_len += buf.get_size();
|
||||
checksum = checksum.wrapping_add(
|
||||
buf.map_readable()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.fold(0u32, |s, v| s.wrapping_add(*v as u32)),
|
||||
);
|
||||
|
||||
let tc_meta = buf
|
||||
.get_meta::<gst_video::VideoTimeCodeMeta>()
|
||||
.expect("No timecode meta");
|
||||
if let Some(ref timecode) = expected_timecode {
|
||||
assert_eq!(&tc_meta.get_tc(), timecode);
|
||||
} else {
|
||||
expected_timecode = Some(tc_meta.get_tc());
|
||||
}
|
||||
expected_timecode.as_mut().map(|tc| tc.increment_frame());
|
||||
}
|
||||
|
||||
assert!(expected_timecode.is_some());
|
||||
assert_eq!(input_len, 28818);
|
||||
assert_eq!(output_len, 42383);
|
||||
assert_eq!(checksum, 3988480);
|
||||
|
||||
let caps = h
|
||||
.get_sinkpad()
|
||||
.expect("harness has no sinkpad")
|
||||
.get_current_caps()
|
||||
.expect("pad has no caps");
|
||||
assert_eq!(
|
||||
caps,
|
||||
gst::Caps::builder("closedcaption/x-cea-708")
|
||||
.field("format", &"cdp")
|
||||
.field("framerate", &gst::Fraction::new(30000, 1001))
|
||||
.build()
|
||||
);
|
||||
}
|
Loading…
Reference in a new issue