Verified Commit e8c58849 authored by Jordan Petridis's avatar Jordan Petridis 🌱

closedcaption: Add SCC parser and encoder plugins

parent ca012cd4
Pipeline #18291 passed with stages
in 11 minutes and 51 seconds
......@@ -15,17 +15,17 @@ lazy_static = "1.2"
[dependencies.gst]
git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs"
features = ["subclassing", "v1_10"]
features = ["subclassing", "v1_12"]
package="gstreamer"
[dependencies.gst-base]
git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs"
features = ["subclassing", "v1_10"]
features = ["subclassing", "v1_12"]
package="gstreamer-base"
[dependencies.gst-video]
git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs"
features = ["v1_10"]
features = ["v1_12"]
package="gstreamer-video"
[dev-dependencies]
......
......@@ -37,10 +37,15 @@ mod line_reader;
mod mcc_enc;
mod mcc_parse;
mod mcc_parser;
mod scc_enc;
mod scc_parse;
mod scc_parser;
fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
mcc_parse::register(plugin)?;
mcc_enc::register(plugin)?;
scc_parse::register(plugin)?;
scc_enc::register(plugin)?;
Ok(())
}
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
// Copyright (C) 2019 Sebastian Dröge <sebastian@centricular.com>
// Copyright (C) 2019 Jordan Petridis <jordan@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.
#[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().unwrap();
});
}
/// Encode a single raw CEA608 packet and compare the output
#[test]
fn test_encode_single_packet() {
init();
let input = [148, 44];
let expected_output = b"Scenarist_SCC V1.0\r\n\r\n11:12:13;14\t942c\r\n\r\n";
let mut h = gst_check::Harness::new("sccenc");
h.set_src_caps_str("closedcaption/x-cea-608, format=raw, framerate=(fraction)30000/1001");
let tc = gst_video::ValidVideoTimeCode::new(
gst::Fraction::new(30000, 1001),
None,
gst_video::VideoTimeCodeFlags::DROP_FRAME,
11,
12,
13,
14,
0,
)
.unwrap();
let buf = {
let mut buf = gst::Buffer::from_mut_slice(Vec::from(&input[..]));
let buf_ref = buf.get_mut().unwrap();
gst_video::VideoTimeCodeMeta::add(buf_ref, &tc);
buf_ref.set_pts(gst::ClockTime::from_seconds(0));
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 timecode = buf
.get_meta::<gst_video::VideoTimeCodeMeta>()
.expect("No timecode for buffer")
.get_tc();
assert_eq!(timecode, tc);
let pts = buf.get_pts();
assert_eq!(pts, gst::ClockTime::from_seconds(0));
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())
);
}
/// Encode a multiple raw CEA608 packets and compare the output
#[test]
fn test_encode_multiple_packets() {
init();
let input1 = [148, 44];
let input2 = [
148, 32, 148, 32, 148, 174, 148, 174, 148, 84, 148, 84, 16, 174, 16, 174, 70, 242, 239,
109, 32, 206, 229, 247, 32, 217, 239, 242, 107, 44, 148, 242, 148, 242, 16, 174, 16, 174,
244, 104, 233, 115, 32, 233, 115, 32, 196, 229, 109, 239, 227, 242, 97, 227, 121, 32, 206,
239, 247, 161, 148, 47, 148, 47,
];
let expected_output1 = b"Scenarist_SCC V1.0\r\n\r\n00:00:00;00\t942c 942c\r\n\r\n";
let expected_output2 = b"00:00:14;01\t9420 9420 94ae 94ae 9454 9454 10ae 10ae 46f2 ef6d 20ce e5f7 20d9 eff2 6b2c 94f2\r\n\r\n";
let expected_output3 = b"00:00:14;17\t94f2 10ae 10ae f468 e973 20e9 7320 c4e5 6def e3f2 61e3 7920 ceef f7a1 942f 942f\r\n\r\n";
let mut h = gst_check::Harness::new("sccenc");
h.set_src_caps_str("closedcaption/x-cea-608, format=raw, framerate=(fraction)30000/1001");
let tc1 = gst_video::ValidVideoTimeCode::new(
gst::Fraction::new(30000, 1001),
None,
gst_video::VideoTimeCodeFlags::DROP_FRAME,
00,
00,
00,
00,
0,
)
.unwrap();
let tc2 = gst_video::ValidVideoTimeCode::new(
gst::Fraction::new(30000, 1001),
None,
gst_video::VideoTimeCodeFlags::DROP_FRAME,
00,
00,
14,
01,
0,
)
.unwrap();
let buf1 = {
let mut buf = gst::Buffer::from_mut_slice(Vec::from(&input1[..]));
let buf_ref = buf.get_mut().unwrap();
gst_video::VideoTimeCodeMeta::add(buf_ref, &tc1);
buf_ref.set_pts(gst::ClockTime::from_seconds(0));
buf
};
let buf2 = {
let mut buf = gst::Buffer::from_mut_slice(Vec::from(&input1[..]));
let buf_ref = buf.get_mut().unwrap();
let mut tc = tc1.clone();
tc.increment_frame();
gst_video::VideoTimeCodeMeta::add(buf_ref, &tc);
buf_ref.set_pts(gst::ClockTime::from_seconds(0));
buf
};
let mut t = tc2.clone();
let mut buffers = input2
.chunks(2)
.map(move |bytes| {
let mut buf = gst::Buffer::from_mut_slice(Vec::from(&bytes[..]));
let buf_ref = buf.get_mut().unwrap();
gst_video::VideoTimeCodeMeta::add(buf_ref, &t);
t.increment_frame();
buf
})
.collect::<Vec<gst::Buffer>>();
buffers.insert(0, buf1);
buffers.insert(1, buf2);
buffers.iter().for_each(|buf| {
assert_eq!(h.push(buf.clone()), Ok(gst::FlowSuccess::Ok));
});
h.push_event(gst::Event::new_eos().build());
// Pull 1
let buf = h.pull().expect("Couldn't pull buffer");
let timecode = buf
.get_meta::<gst_video::VideoTimeCodeMeta>()
.expect("No timecode for buffer")
.get_tc();
assert_eq!(timecode, tc1);
let pts = buf.get_pts();
assert_eq!(pts, gst::ClockTime::from_seconds(0));
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_output1.as_ref())
);
// Pull 2
let buf = h.pull().expect("Couldn't pull buffer");
let timecode = buf
.get_meta::<gst_video::VideoTimeCodeMeta>()
.expect("No timecode for buffer")
.get_tc();
assert_eq!(timecode, tc2);
// let pts = buf.get_pts();
// assert_eq!(pts, gst::ClockTime::from_seconds(0));
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_output2.as_ref())
);
let tc3 = gst_video::ValidVideoTimeCode::new(
gst::Fraction::new(30000, 1001),
None,
gst_video::VideoTimeCodeFlags::DROP_FRAME,
00,
00,
14,
17,
0,
)
.unwrap();
// Pull 3
let buf = h.pull().expect("Couldn't pull buffer");
let timecode = buf
.get_meta::<gst_video::VideoTimeCodeMeta>()
.expect("No timecode for buffer")
.get_tc();
assert_eq!(timecode, tc3);
// let pts = buf.get_pts();
// assert_eq!(pts, gst::ClockTime::from_seconds(0));
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_output3.as_ref())
);
}
// Copyright (C) 2019 Sebastian Dröge <sebastian@centricular.com>
// Copyright (C) 2019 Jordan Petridis <jordan@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.
#[macro_use]
extern crate pretty_assertions;
use gst::prelude::*;
use gst_video::{ValidVideoTimeCode, VideoTimeCode};
use rand::{Rng, SeedableRng};
use std::collections::VecDeque;
fn init() {
use std::sync::{Once, ONCE_INIT};
static INIT: Once = ONCE_INIT;
INIT.call_once(|| {
gst::init().unwrap();
gstrsclosedcaption::plugin_register_static().unwrap();
});
}
/// Randomized test passing buffers of arbitrary sizes to the parser
#[test]
fn test_parse() {
init();
let mut data = include_bytes!("dn2018-1217.scc").as_ref();
let mut rnd = if let Ok(seed) = std::env::var("SCC_PARSE_TEST_SEED") {
rand::rngs::SmallRng::seed_from_u64(
seed.parse::<u64>()
.expect("SCC_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("sccparse");
h.set_src_caps_str("application/x-scc");
let mut input_len = 0;
let mut output_len = 0;
let mut checksum = 0u32;
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)),
);
}
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)),
);
}
assert_eq!(input_len, 241152);
assert_eq!(output_len, 89084);
assert_eq!(checksum, 12554799);
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-608")
.field("format", &"raw")
.field("framerate", &gst::Fraction::new(30000, 1001))
.build()
);
}
/// Test that ensures timecode parsing is the expected one
#[test]
fn test_timecodes() {
init();
let data = include_bytes!("timecodes-cut-down-sample.scc").as_ref();
let mut h = gst_check::Harness::new("sccparse");
h.set_src_caps_str("application/x-scc");
let timecodes = [
"00:00:00;00",
"00:00:14;01",
"00:00:17;26",
"00:00:19;01",
"00:00:21;02",
"00:00:23;10",
"00:00:25;18",
"00:00:28;13",
"00:00:30;29",
"00:00:34;29",
"00:00:37;27",
"00:00:40;01",
"00:00:43;27",
"00:00:45;13",
"00:00:49;16",
"00:58:51;01",
"00:58:52;29",
"00:58:55;00",
"00:59:00;25",
];
let mut valid_timecodes: VecDeque<ValidVideoTimeCode> = timecodes
.iter()
.map(|s| {
let mut t = VideoTimeCode::from_string(s).unwrap();
t.set_fps(gst::Fraction::new(30000, 1001));
t.set_flags(gst_video::VideoTimeCodeFlags::DROP_FRAME);
t
})
.map(|t| t.try_into().unwrap())
.collect();
let mut output_len = 0;
let mut checksum = 0u32;
let mut expected_timecode = valid_timecodes.pop_front().unwrap();
let buf = gst::Buffer::from_mut_slice(Vec::from(&data[..]));
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)),
);
// get the timecode of the buffer
let tc = buf
.get_meta::<gst_video::VideoTimeCodeMeta>()
.expect("No timecode meta")
.get_tc();
// if the timecode matches one of expected codes,
// pop the valid_timecodes deque and set expected_timecode,
// to the next timecode.
if Some(&tc) == valid_timecodes.front() {
expected_timecode = valid_timecodes.pop_front().unwrap();
}
assert_eq!(tc, expected_timecode);
expected_timecode.increment_frame();
}
assert_eq!(output_len, 1268);
assert_eq!(checksum, 174295);
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-608")
.field("format", &"raw")
.field("framerate", &gst::Fraction::new(30000, 1001))
.build()
);
}
Scenarist_SCC V1.0
00:00:00;00 942c 942c
00:00:14;01 9420 9420 94ae 94ae 9454 9454 10ae 10ae 46f2 ef6d 20ce e5f7 20d9 eff2 6b2c 94f2 94f2 10ae 10ae f468 e973 20e9 7320 c4e5 6def e3f2 61e3 7920 ceef f7a1 942f 942f
00:00:17;26 9420 9420 94ae 94ae 9452 9452 97a1 97a1 10ae 10ae d9e5 732c 942c 942c 2049 a76d 2073 7570 70ef f2f4 e96e 6780 94f4 94f4 10ae 10ae c4ef 6e61 ec64 2054 f275 6d70 ae80 942f 942f
00:00:19;01 9420 9420 94ae 94ae 94d0 94d0 10ae 10ae 49a7 6d20 64ef e96e 6720 73ef 2061 7320 e56e f468 7573 e961 73f4 e9e3 61ec ec79 94f4 94f4 97a2 97a2 10ae 10ae 6173 2049 20e3 616e 2c80 942f 942f
00:00:21;02 9420 9420 94ae 94ae 9452 9452 10ae 10ae e576 e56e 20f4 68e5 20e6 61e3 f420 4920 f468 e96e 6b80 9470 9470 97a1 97a1 10ae 10ae 68e5 a773 2061 20f4 e5f2 f2e9 62ec e520 6875 6d61 6e20 62e5 e96e 67ae 942f 942f
00:00:23;10 9420 9420 94ae 94ae 94d0 94d0 10ae 10ae c275 f420 f468 e520 e368 efe9 e3e5 20ef 6e20 f468 e520 eff4 68e5 f220 73e9 64e5 94f2 94f2 9723 9723 10ae 10ae e973 20ea 7573 f420 6173 2062 6164 ae80 942f 942f
00:00:25;18 9420 9420 94ae 94ae 9454 9454 97a2 97a2 10ae 10ae 54f2 756d 7020 e973 2061 9470 9470 9723 9723 10ae 10ae a2f4 e5f2 f2e9 62ec e520 6875 6d61 6e20 62e5 e96e 67ae a280 942f 942f
00:00:28;13 9420 9420 94ae 94ae 9452 9452 97a1 97a1 10ae 10ae 5468 ef73 e520 61f2 e520 f468 942c 942c e520 f7ef f264 7380 94f2 94f2 97a2 97a2 10ae 10ae efe6 20cd e9e3 6b20 cd75 ec76 616e e579 2c80 942f 942f
00:00:30;29 9420 9420 94ae 94ae 94d0 94d0 9723 9723 10ae 10ae f468 e520 6d61 6e20 54f2 756d 7020 6861 7320 e368 ef73 e56e 9470 9470 10ae 10ae f4ef 942c 942c 2062 e520 68e9 7320 6ee5 f720 e368 e9e5 e620 efe6 2073 f461 e6e6 ae80 942f 942f
00:00:34;29 9420 9420 94ae 94ae 94d0 94d0 9723 9723 10ae 10ae 57e5 a7ec ec20 7370 e561 6b20 f7e9 f468 20e6 eff2 942c 942c 6de5 f280 94f2 94f2 10ae 10ae 70f2 e573 e964 e56e f4e9 61ec 20e3 616e 64e9 6461 f4e5 942f 942f
00:00:37;27 9420 9420 94ae 94ae 9454 9454 97a1 97a1 10ae 10ae 5261 ec70 6820 ce61 64e5 f280 94f2 94f2 9723 9723 10ae 10ae 6162 ef75 f420 cd75 ec76 616e e579 2c80 942f 942f
00:00:40;01 9420 9420 94ae 94ae 9452 9452 97a2 97a2 10ae 10ae f468 e520 f2e5 73e9 676e 61f4 e9ef 6e20 efe6 9470 9470 9723 9723 10ae 10ae 496e f4e5 f2e9 eff2 20d3 e5e3 f2e5 f461 f279 2052 7961 6e80 942f 942f
00:00:43;27 9420 9420 94ae 94ae 94f2 94f2 97a1 97a1 10ae 10ae dae9 6e6b e52c 20f4 68e5 2070 ef73 73e9 62ec e580 942f 942f
00:00:45;13 9420 9420 94ae 94ae 94d0 94d0 10ae 10ae 4368 f2e9 73f4 6d61 7320 67ef 76e5 f26e 6de5 6ef4 2073 6875 f464 eff7 6e80 94f2 94f2 10ae 10ae ef76 e5f2 20f4 68e5 2062 eff2 64e5 f220 f761 ecec 2c80 942f 942f
00:00:49;16 9420 9420 94ae 94ae 94d0 94d0 97a1 97a1 10ae 10ae 616e 6420 f468 e520 e675 f475 f2e5 20ef e620 4f62 616d 61e3 61f2 e580 94f2 94f2 97a1 97a1 10ae 10ae 61e6 f4e5 f220 6120 e3ef 6e73 e5f2 7661 f4e9 76e5 942f 942f
00:58:51;01 9420 9420 94ae 94ae 9452 9452 9723 9723 10ae 10ae f7e5 20f7 e9ec ec20 ece9 6e6b 20f4 ef2c 9470 9470 9723 9723 10ae 10ae a246 f2ef 6d20 c1f2 e97a ef6e 6120 f4ef 20d9 e56d e56e ba80 942f 942f
00:58:52;29 9420 9420 94ae 94ae 9454 9454 97a1 97a1 10ae 10ae 5468 e520 4aef 75f2 6ee5 7980 94f2 94f2 10ae 10ae efe6 2061 6e20 c16d e5f2 e9e3 616e 20c2 ef6d 62ae a280 942f 942f
00:58:55;00 9420 9420 94ae 94ae 9452 9452 9723 9723 10ae 10ae 49a7 6d20 c16d 7920 c7ef ef64 6d61 6eae 9470 9470 10ae 10ae 5468 616e 6b73 2073 ef20 6d75 e368 20e6 eff2 20ea efe9 6ee9 6e67 2075 73ae 942f 942f
00:59:00;25 942c 942c
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment