use std::io::{Cursor, Read, Write, Seek, SeekFrom};
use libfreemkv::*;
use libfreemkv::mux::meta::M2tsMeta;
fn sample_disc_title() -> DiscTitle {
DiscTitle {
playlist: "Test Movie".into(),
playlist_id: 0,
duration_secs: 7200.0,
size_bytes: 0,
clips: Vec::new(),
streams: vec![
Stream::Video(VideoStream {
pid: 0x1011, codec: Codec::Hevc,
resolution: "2160p".into(), frame_rate: "23.976".into(),
hdr: HdrFormat::Hdr10, color_space: ColorSpace::Bt709,
secondary: false, label: "Main".into(),
}),
Stream::Audio(AudioStream {
pid: 0x1100, codec: Codec::TrueHd,
channels: "7.1".into(), language: "eng".into(),
sample_rate: "48kHz".into(), secondary: false,
label: "English Atmos".into(),
}),
Stream::Audio(AudioStream {
pid: 0x1101, codec: Codec::Ac3,
channels: "5.1".into(), language: "fra".into(),
sample_rate: "48kHz".into(), secondary: false,
label: "French".into(),
}),
Stream::Subtitle(SubtitleStream {
pid: 0x1200, codec: Codec::Pgs,
language: "eng".into(), forced: false,
}),
],
extents: Vec::new(),
}
}
#[test]
fn parse_url_disc() {
let u = parse_url("disc://");
assert_eq!(u.scheme, "disc");
assert_eq!(u.path, "");
}
#[test]
fn parse_url_disc_device() {
let u = parse_url("disc:///dev/sg4");
assert_eq!(u.scheme, "disc");
assert_eq!(u.path, "/dev/sg4");
}
#[test]
fn parse_url_mkv() {
let u = parse_url("mkv://Dune.mkv");
assert_eq!(u.scheme, "mkv");
assert_eq!(u.path, "Dune.mkv");
}
#[test]
fn parse_url_network() {
let u = parse_url("network://10.1.7.11:9000");
assert_eq!(u.scheme, "network");
assert_eq!(u.path, "10.1.7.11:9000");
}
#[test]
fn parse_url_bare_path_rejected() {
let u = parse_url("Dune.mkv");
assert_eq!(u.scheme, "unknown");
}
#[test]
fn parse_url_null() {
let u = parse_url("null://");
assert_eq!(u.scheme, "null");
assert_eq!(u.path, "");
}
#[test]
fn parse_url_m2ts_with_path() {
let u = parse_url("m2ts:///tmp/Dune.m2ts");
assert_eq!(u.scheme, "m2ts");
assert_eq!(u.path, "/tmp/Dune.m2ts");
}
#[test]
fn parse_url_m2ts_relative() {
let u = parse_url("m2ts://Dune.m2ts");
assert_eq!(u.scheme, "m2ts");
assert_eq!(u.path, "Dune.m2ts");
}
#[test]
fn open_input_bare_path_errors() {
let result = libfreemkv::open_input("Dune.mkv", &libfreemkv::InputOptions::default());
assert!(result.is_err());
let msg = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
assert!(msg.contains("not a valid stream URL"), "got: {}", msg);
}
#[test]
fn open_output_bare_path_errors() {
let dt = sample_disc_title();
let result = libfreemkv::open_output("Dune.mkv", &dt);
assert!(result.is_err());
let msg = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
assert!(msg.contains("not a valid stream URL"), "got: {}", msg);
}
#[test]
fn open_input_m2ts_empty_path_errors() {
let result = libfreemkv::open_input("m2ts://", &libfreemkv::InputOptions::default());
assert!(result.is_err());
let msg = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
assert!(msg.contains("requires a file path"), "got: {}", msg);
}
#[test]
fn open_output_null_input_errors() {
let result = libfreemkv::open_input("null://", &libfreemkv::InputOptions::default());
assert!(result.is_err());
let msg = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
assert!(msg.contains("write-only"), "got: {}", msg);
}
#[test]
fn open_output_disc_errors() {
let dt = sample_disc_title();
let result = libfreemkv::open_output("disc://", &dt);
assert!(result.is_err());
let msg = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
assert!(msg.contains("read-only"), "got: {}", msg);
}
#[test]
fn open_input_network_no_port_errors() {
let result = libfreemkv::open_input("network://10.0.0.1", &libfreemkv::InputOptions::default());
assert!(result.is_err());
let msg = match result { Err(e) => e.to_string(), Ok(_) => panic!("expected error") };
assert!(msg.contains("missing port"), "got: {}", msg);
}
#[test]
fn parse_url_stdio() {
let u = parse_url("stdio://");
assert_eq!(u.scheme, "stdio");
assert_eq!(u.path, "");
}
#[test]
fn m2ts_meta_roundtrip() {
let dt = sample_disc_title();
let meta = M2tsMeta::from_title(&dt);
let restored = meta.to_title();
assert_eq!(restored.playlist, dt.playlist);
assert_eq!(restored.duration_secs, dt.duration_secs);
assert_eq!(restored.streams.len(), dt.streams.len());
if let Stream::Video(v) = &restored.streams[0] {
assert_eq!(v.codec, Codec::Hevc);
assert_eq!(v.resolution, "2160p");
assert_eq!(v.label, "Main");
} else { panic!("expected video"); }
if let Stream::Audio(a) = &restored.streams[1] {
assert_eq!(a.codec, Codec::TrueHd);
assert_eq!(a.language, "eng");
assert_eq!(a.label, "English Atmos");
} else { panic!("expected audio"); }
if let Stream::Subtitle(s) = &restored.streams[3] {
assert_eq!(s.language, "eng");
assert!(!s.forced);
} else { panic!("expected subtitle"); }
}
#[test]
fn m2ts_header_write_read() {
let dt = sample_disc_title();
let meta = M2tsMeta::from_title(&dt);
let mut buf = Vec::new();
libfreemkv::mux::meta::write_header(&mut buf, &meta).unwrap();
assert_eq!(&buf[..4], b"FMKV");
assert_eq!(buf.len() % 192, 0);
let mut cursor = Cursor::new(&buf);
let read_back = libfreemkv::mux::meta::read_header(&mut cursor).unwrap().unwrap();
assert_eq!(read_back.title, "Test Movie");
assert_eq!(read_back.duration, 7200.0);
assert_eq!(read_back.streams.len(), 4);
assert_eq!(cursor.position() as usize, buf.len());
}
#[test]
fn m2ts_stream_write_read() {
let dt = sample_disc_title();
let mut ts_data = Vec::new();
for i in 0..10u8 {
let mut pkt = [0u8; 192];
pkt[4] = 0x47;
pkt[5] = 0x10;
pkt[6] = 0x11;
pkt[7] = 0x10;
pkt[8] = i;
ts_data.extend_from_slice(&pkt);
}
let output = Cursor::new(Vec::new());
let mut stream = M2tsStream::new(output).meta(&dt);
stream.write_all(&ts_data).unwrap();
stream.finish().unwrap();
let mut encoded = Vec::new();
let meta = M2tsMeta::from_title(&dt);
libfreemkv::mux::meta::write_header(&mut encoded, &meta).unwrap();
encoded.extend_from_slice(&ts_data);
let cursor = Cursor::new(encoded);
let mut stream = M2tsStream::open(cursor).unwrap();
let info = stream.info();
assert_eq!(info.streams.len(), 4);
assert_eq!(info.duration_secs, 7200.0);
let mut read_buf = vec![0u8; 192 * 10];
let mut total = 0;
loop {
match stream.read(&mut read_buf[total..]) {
Ok(0) => break,
Ok(n) => total += n,
Err(_) => break,
}
}
assert_eq!(total, 192 * 10);
for i in 0..10u8 {
assert_eq!(read_buf[i as usize * 192 + 4], 0x47);
assert_eq!(read_buf[i as usize * 192 + 8], i);
}
}
#[test]
fn m2ts_passthrough_preserves_data() {
let dt = sample_disc_title();
let mut original = Vec::new();
for i in 0..100u8 {
let mut pkt = [0u8; 192];
pkt[4] = 0x47;
pkt[5] = (i % 3) << 4;
pkt[6] = i;
for j in 8..192 { pkt[j] = i.wrapping_add(j as u8); }
original.extend_from_slice(&pkt);
}
let mut encoded = Vec::new();
let meta = M2tsMeta::from_title(&dt);
libfreemkv::mux::meta::write_header(&mut encoded, &meta).unwrap();
encoded.extend_from_slice(&original);
let cursor = Cursor::new(encoded);
let mut stream = M2tsStream::open(cursor).unwrap();
let mut decoded = vec![0u8; original.len()];
let mut total = 0;
loop {
match stream.read(&mut decoded[total..]) {
Ok(0) => break,
Ok(n) => total += n,
Err(_) => break,
}
}
assert_eq!(total, original.len());
assert_eq!(decoded, original);
}
#[test]
fn m2ts_implements_iostream() {
let dt = sample_disc_title();
let output = Cursor::new(Vec::new());
let stream = M2tsStream::new(output).meta(&dt);
let mut boxed: Box<dyn IOStream> = Box::new(stream);
let meta = boxed.info();
assert_eq!(meta.streams.len(), 4);
let pkt = [0u8; 192];
boxed.write_all(&pkt).unwrap();
boxed.finish().unwrap();
}
#[test]
fn m2ts_read_returns_error_on_write_stream() {
let output = Cursor::new(Vec::new());
let stream = M2tsStream::new(output);
let mut boxed: Box<dyn IOStream> = Box::new(stream);
let mut buf = [0u8; 10];
assert!(boxed.read(&mut buf).is_err());
}
#[test]
fn disc_title_empty() {
let dt = DiscTitle::empty();
assert_eq!(dt.streams.len(), 0);
assert_eq!(dt.duration_secs, 0.0);
assert!(dt.playlist.is_empty());
}