use std::time::Duration;
use hisui::{
decoder_libvpx::LibvpxDecoder,
decoder_opus::OpusDecoder,
metadata::SourceId,
reader_mp4::{Mp4AudioReader, Mp4VideoReader},
stats::{Mp4AudioReaderStats, Mp4VideoReaderStats},
subcommand_legacy::{Args, Runner},
types::CodecName,
};
use orfail::OrFail;
#[test]
fn empty_source() -> noargs::Result<()> {
let out_file = tempfile::NamedTempFile::new().or_fail()?;
let args = Args::parse(noargs::RawArgs::new(
[
"hisui",
"--show-progress-bar=false",
"-f",
"testdata/e2e/empty_source/report.json",
"--out-file",
&out_file.path().display().to_string(),
]
.into_iter()
.map(|s| s.to_string()),
))?;
Runner::new(args).run()?;
assert!(out_file.path().exists());
assert_eq!(
Mp4AudioReader::new(SourceId::new("dummy"), out_file.path(), audio_stats())
.or_fail()?
.count(),
0
);
assert_eq!(
Mp4VideoReader::new(SourceId::new("dummy"), out_file.path(), video_stats())
.or_fail()?
.count(),
0
);
Ok(())
}
#[test]
fn simple_single_source() -> noargs::Result<()> {
let out_file = tempfile::NamedTempFile::new().or_fail()?;
let args = Args::parse(noargs::RawArgs::new(
[
"hisui",
"--show-progress-bar=false",
"-f",
"testdata/e2e/simple_single_source/report.json",
"--out-file",
&out_file.path().display().to_string(),
]
.into_iter()
.map(|s| s.to_string()),
))?;
Runner::new(args).run()?;
assert!(out_file.path().exists());
let mut audio_reader =
Mp4AudioReader::new(SourceId::new("dummy"), out_file.path(), audio_stats()).or_fail()?;
let mut video_reader =
Mp4VideoReader::new(SourceId::new("dummy"), out_file.path(), video_stats()).or_fail()?;
let audio_samples = audio_reader.by_ref().collect::<orfail::Result<Vec<_>>>()?;
let video_samples = video_reader.by_ref().collect::<orfail::Result<Vec<_>>>()?;
let audio_stats = audio_reader.stats();
assert_eq!(audio_stats.codec, Some(CodecName::Opus));
assert_eq!(audio_stats.total_sample_count.get(), 51);
assert_eq!(
audio_stats.total_track_duration.get(),
Duration::from_millis(1020)
);
let video_stats = video_reader.stats();
assert_eq!(video_stats.codec.get(), Some(CodecName::Vp9));
assert_eq!(
video_stats
.resolutions
.get()
.into_iter()
.map(|r| (r.width, r.height))
.collect::<Vec<_>>(),
[(320, 240)]
);
assert_eq!(video_stats.total_sample_count.get(), 25);
assert_eq!(
video_stats.total_track_duration.get(),
Duration::from_secs(1)
);
let mut decoder = OpusDecoder::new().or_fail()?;
for data in audio_samples {
let decoded = decoder.decode(&data).or_fail()?;
assert!(!decoded.data.iter().all(|v| *v == 0));
}
let check_decoded_frames = |decoder: &mut LibvpxDecoder| -> orfail::Result<()> {
while let Some(decoded) = decoder.next_decoded_frame() {
let (y_plane, u_plane, v_plane) = decoded.as_yuv_planes().or_fail()?;
y_plane
.iter()
.for_each(|x| assert!(matches!(x, 80..=82), "y={x}"));
u_plane.iter().for_each(|x| assert_eq!(*x, 90));
v_plane.iter().for_each(|x| assert_eq!(*x, 240));
}
Ok(())
};
let mut decoder = LibvpxDecoder::new_vp9().or_fail()?;
for frame in video_samples {
decoder.decode(&frame).or_fail()?;
check_decoded_frames(&mut decoder).or_fail()?;
}
decoder.finish().or_fail()?;
check_decoded_frames(&mut decoder).or_fail()?;
Ok(())
}
#[test]
fn odd_resolution_single_source() -> noargs::Result<()> {
let out_file = tempfile::NamedTempFile::new().or_fail()?;
let args = Args::parse(noargs::RawArgs::new(
[
"hisui",
"--show-progress-bar=false",
"-f",
"testdata/e2e/odd_resolution_single_source/report.json",
"--out-file",
&out_file.path().display().to_string(),
]
.into_iter()
.map(|s| s.to_string()),
))?;
Runner::new(args).run()?;
assert!(out_file.path().exists());
let mut audio_reader =
Mp4AudioReader::new(SourceId::new("dummy"), out_file.path(), audio_stats()).or_fail()?;
let mut video_reader =
Mp4VideoReader::new(SourceId::new("dummy"), out_file.path(), video_stats()).or_fail()?;
let audio_samples = audio_reader.by_ref().collect::<orfail::Result<Vec<_>>>()?;
let video_samples = video_reader.by_ref().collect::<orfail::Result<Vec<_>>>()?;
let audio_stats = audio_reader.stats();
assert_eq!(audio_stats.codec, Some(CodecName::Opus));
assert_eq!(audio_stats.total_sample_count.get(), 51);
assert_eq!(
audio_stats.total_track_duration.get(),
Duration::from_millis(1020)
);
let video_stats = video_reader.stats();
assert_eq!(video_stats.codec.get(), Some(CodecName::Vp9));
assert_eq!(
video_stats
.resolutions
.get()
.into_iter()
.map(|r| (r.width, r.height))
.collect::<Vec<_>>(),
[(320, 240)]
);
assert_eq!(video_stats.total_sample_count.get(), 25);
assert_eq!(
video_stats.total_track_duration.get(),
Duration::from_secs(1)
);
let mut decoder = OpusDecoder::new().or_fail()?;
for data in audio_samples {
let decoded = decoder.decode(&data).or_fail()?;
assert!(!decoded.data.iter().all(|v| *v == 0));
}
let check_decoded_frames = |decoder: &mut LibvpxDecoder| -> orfail::Result<()> {
while let Some(decoded) = decoder.next_decoded_frame() {
let (y_plane, u_plane, v_plane) = decoded.as_yuv_planes().or_fail()?;
y_plane.iter().enumerate().for_each(|(i, &x)| {
let col = i % 320;
let row = i / 320;
if col >= 318 || row >= 238 {
assert!(matches!(x, 0..=3), "Expected black Y value, got y={x}",);
} else {
assert!(matches!(x, 79..=83), "Expected red Y value, got y={x}",);
}
});
u_plane.iter().enumerate().for_each(|(i, &x)| {
let col = (i % 160) * 2;
let row = (i / 160) * 2;
if col >= 318 || row >= 238 {
assert!(matches!(x, 124..=131), "Expected black U value, got u={x}");
} else {
assert!(matches!(x, 87..=95), "Expected red U value, got u={x}");
}
});
v_plane.iter().enumerate().for_each(|(i, &x)| {
let col = (i % 160) * 2;
let row = (i / 160) * 2;
if col >= 318 || row >= 238 {
assert!(matches!(x, 122..=131), "Expected black V value, got v={x}");
} else {
assert!(matches!(x, 238..=244), "Expected red V value, got v={x}");
}
});
}
Ok(())
};
let mut decoder = LibvpxDecoder::new_vp9().or_fail()?;
for frame in video_samples {
decoder.decode(&frame).or_fail()?;
check_decoded_frames(&mut decoder).or_fail()?;
}
decoder.finish().or_fail()?;
check_decoded_frames(&mut decoder).or_fail()?;
Ok(())
}
#[test]
fn simple_multi_sources() -> noargs::Result<()> {
let out_file = tempfile::NamedTempFile::new().or_fail()?;
let args = Args::parse(noargs::RawArgs::new(
[
"hisui",
"--show-progress-bar=false",
"-f",
"testdata/e2e/simple_multi_sources/report.json",
"--out-file",
&out_file.path().display().to_string(),
]
.into_iter()
.map(|s| s.to_string()),
))?;
Runner::new(args).run()?;
assert!(out_file.path().exists());
let mut audio_reader =
Mp4AudioReader::new(SourceId::new("dummy"), out_file.path(), audio_stats()).or_fail()?;
let mut video_reader =
Mp4VideoReader::new(SourceId::new("dummy"), out_file.path(), video_stats()).or_fail()?;
let _audio_samples = audio_reader.by_ref().collect::<orfail::Result<Vec<_>>>()?;
let _video_samples = video_reader.by_ref().collect::<orfail::Result<Vec<_>>>()?;
let audio_stats = audio_reader.stats();
assert_eq!(audio_stats.codec, Some(CodecName::Opus));
assert_eq!(audio_stats.total_sample_count.get(), 51);
assert_eq!(
audio_stats.total_track_duration.get(),
Duration::from_millis(1020)
);
let video_stats = video_reader.stats();
assert_eq!(video_stats.codec.get(), Some(CodecName::Vp9));
assert_eq!(
video_stats
.resolutions
.get()
.into_iter()
.map(|r| (r.width, r.height))
.collect::<Vec<_>>(),
[(320 * 3, 240 * 1)]
);
assert_eq!(video_stats.total_sample_count.get(), 25);
assert_eq!(
video_stats.total_track_duration.get(),
Duration::from_secs(1)
);
Ok(())
}
#[test]
fn simple_split_archive() -> noargs::Result<()> {
let out_file = tempfile::NamedTempFile::new().or_fail()?;
let args = Args::parse(noargs::RawArgs::new(
[
"hisui",
"--show-progress-bar=false",
"--layout",
"testdata/e2e/simple_split_archive/layout.jsonc",
"--out-file",
&out_file.path().display().to_string(),
]
.into_iter()
.map(|s| s.to_string()),
))?;
Runner::new(args).run()?;
assert!(out_file.path().exists());
let mut audio_reader =
Mp4AudioReader::new(SourceId::new("dummy"), out_file.path(), audio_stats()).or_fail()?;
let mut video_reader =
Mp4VideoReader::new(SourceId::new("dummy"), out_file.path(), video_stats()).or_fail()?;
let audio_samples = audio_reader.by_ref().collect::<orfail::Result<Vec<_>>>()?;
let video_samples = video_reader.by_ref().collect::<orfail::Result<Vec<_>>>()?;
let audio_stats = audio_reader.stats();
assert_eq!(audio_stats.codec, Some(CodecName::Opus));
assert_eq!(audio_stats.total_sample_count.get(), 153); assert_eq!(
audio_stats.total_track_duration.get(),
Duration::from_millis(3060) );
let video_stats = video_reader.stats();
assert_eq!(video_stats.codec.get(), Some(CodecName::Vp9));
assert_eq!(
video_stats
.resolutions
.get()
.into_iter()
.map(|r| (r.width, r.height))
.collect::<Vec<_>>(),
[(16, 16)] );
assert_eq!(video_stats.total_sample_count.get(), 75); assert_eq!(
video_stats.total_track_duration.get(),
Duration::from_secs(3)
);
let mut decoder = OpusDecoder::new().or_fail()?;
for data in audio_samples {
let decoded = decoder.decode(&data).or_fail()?;
assert!(!decoded.data.iter().all(|v| *v == 0));
}
let check_decoded_frames =
|decoder: &mut LibvpxDecoder, frame_index: &mut usize| -> orfail::Result<()> {
while let Some(decoded) = decoder.next_decoded_frame() {
let (y_plane, _u_plane, v_plane) = decoded.as_yuv_planes().or_fail()?;
if *frame_index < 25 {
(y_plane.iter().zip(v_plane.iter())).for_each(|(&y, &v)| {
assert!(
matches!(y, 80..=82) && matches!(v, 240),
"Expected red Y / V value, got y={y} / v={v} at frame {}",
*frame_index
);
});
} else if *frame_index < 50 {
(y_plane.iter().zip(v_plane.iter())).for_each(|(&y, &v)| {
assert!(
matches!(y, 80..=82) && matches!(v, 81),
"Expected green Y / V value, got y={y} / v={v} at frame {}",
*frame_index
);
});
} else if *frame_index < 75 {
y_plane.iter().for_each(|&y| {
assert!(
matches!(y, 40..=42),
"Expected blue Y value, got y={y} at frame {}",
*frame_index
);
});
}
*frame_index += 1;
}
Ok(())
};
let mut decoder = LibvpxDecoder::new_vp9().or_fail()?;
let mut frame_index = 0;
for frame in video_samples {
decoder.decode(&frame).or_fail()?;
check_decoded_frames(&mut decoder, &mut frame_index).or_fail()?;
}
decoder.finish().or_fail()?;
check_decoded_frames(&mut decoder, &mut frame_index).or_fail()?;
assert_eq!(frame_index, 75);
Ok(())
}
#[test]
fn multi_sources_single_column() -> noargs::Result<()> {
let out_file = tempfile::NamedTempFile::new().or_fail()?;
let args = Args::parse(noargs::RawArgs::new(
[
"hisui",
"--show-progress-bar=false",
"--layout",
"testdata/e2e/multi_sources_single_column/layout.json",
"--out-file",
&out_file.path().display().to_string(),
]
.into_iter()
.map(|s| s.to_string()),
))?;
Runner::new(args).run()?;
assert!(out_file.path().exists());
let mut audio_reader =
Mp4AudioReader::new(SourceId::new("dummy"), out_file.path(), audio_stats()).or_fail()?;
let mut video_reader =
Mp4VideoReader::new(SourceId::new("dummy"), out_file.path(), video_stats()).or_fail()?;
let audio_samples = audio_reader.by_ref().collect::<orfail::Result<Vec<_>>>()?;
let video_samples = video_reader.by_ref().collect::<orfail::Result<Vec<_>>>()?;
let audio_stats = audio_reader.stats();
assert_eq!(audio_stats.codec, Some(CodecName::Opus));
assert_eq!(audio_stats.total_sample_count.get(), 51);
assert_eq!(
audio_stats.total_track_duration.get(),
Duration::from_millis(1020)
);
let video_stats = video_reader.stats();
assert_eq!(video_stats.codec.get(), Some(CodecName::Vp9));
assert_eq!(
video_stats
.resolutions
.get()
.into_iter()
.map(|r| (r.width, r.height))
.collect::<Vec<_>>(),
[(16, 52)]
);
assert_eq!(video_stats.total_sample_count.get(), 25);
assert_eq!(
video_stats.total_track_duration.get(),
Duration::from_secs(1)
);
let mut decoder = OpusDecoder::new().or_fail()?;
for data in audio_samples {
let decoded = decoder.decode(&data).or_fail()?;
assert!(!decoded.data.iter().all(|v| *v == 0));
}
let check_decoded_frames = |decoder: &mut LibvpxDecoder| -> orfail::Result<()> {
while let Some(decoded) = decoder.next_decoded_frame() {
let (y_plane, _u_plane, _v_plane) = decoded.as_yuv_planes().or_fail()?;
let width = 16;
for (i, y) in y_plane.iter().copied().enumerate() {
if i / width < 16 {
assert!(matches!(y, 40..=42), "y={y}");
} else if i / width < 16 + 2 {
assert!(matches!(y, 0..=2), "y={y}");
} else if i / width < 16 + 2 + 16 {
assert!(matches!(y, 186 | 187 | 188 | 189), "y={y}");
} else if i / width < 16 + 2 + 16 + 2 {
assert!(matches!(y, 0..=2), "y={y}");
} else if i / width < 16 + 2 + 16 + 2 + 16 {
assert!(matches!(y, 80..=82), "y={y}");
} else {
unreachable!()
}
}
}
Ok(())
};
let mut decoder = LibvpxDecoder::new_vp9().or_fail()?;
for frame in video_samples {
decoder.decode(&frame).or_fail()?;
check_decoded_frames(&mut decoder).or_fail()?;
}
decoder.finish().or_fail()?;
check_decoded_frames(&mut decoder).or_fail()?;
Ok(())
}
#[test]
fn two_regions() -> noargs::Result<()> {
let out_file = tempfile::NamedTempFile::new().or_fail()?;
let args = Args::parse(noargs::RawArgs::new(
[
"hisui",
"--show-progress-bar=false",
"--layout",
"testdata/e2e/two_regions/layout.json",
"--out-file",
&out_file.path().display().to_string(),
]
.into_iter()
.map(|s| s.to_string()),
))?;
Runner::new(args).run()?;
assert!(out_file.path().exists());
let mut video_reader =
Mp4VideoReader::new(SourceId::new("dummy"), out_file.path(), video_stats()).or_fail()?;
assert_eq!(
Mp4AudioReader::new(SourceId::new("dummy"), out_file.path(), audio_stats())
.or_fail()?
.count(),
0
);
let video_samples = video_reader.by_ref().collect::<orfail::Result<Vec<_>>>()?;
let video_stats = video_reader.stats();
assert_eq!(video_stats.codec.get(), Some(CodecName::Vp9));
assert_eq!(
video_stats
.resolutions
.get()
.into_iter()
.map(|r| (r.width, r.height))
.collect::<Vec<_>>(),
[(16, 34)]
);
assert_eq!(video_stats.total_sample_count.get(), 25);
assert_eq!(
video_stats.total_track_duration.get(),
Duration::from_secs(1)
);
let check_decoded_frames = |decoder: &mut LibvpxDecoder| -> orfail::Result<()> {
while let Some(decoded) = decoder.next_decoded_frame() {
let (y_plane, _u_plane, _v_plane) = decoded.as_yuv_planes().or_fail()?;
let width = 16;
for (i, y) in y_plane.iter().copied().enumerate() {
if i / width < 8 {
assert!(matches!(y, 40..=42), "y={y}");
} else if i / width < 8 + 2 {
assert!(matches!(y, 0..=2), "y={y}");
} else if i / width < 8 + 2 + 16 {
assert!(matches!(y, 80..=82), "y={y}");
} else if i / width < 8 + 2 + 16 + 2 {
assert!(matches!(y, 0..=2), "y={y}");
} else if i / width < 8 + 2 + 16 + 2 + 6 {
assert!(matches!(y, 186..=188), "y={y}");
} else {
unreachable!()
}
}
}
Ok(())
};
let mut decoder = LibvpxDecoder::new_vp9().or_fail()?;
for frame in video_samples {
decoder.decode(&frame).or_fail()?;
check_decoded_frames(&mut decoder).or_fail()?;
}
decoder.finish().or_fail()?;
check_decoded_frames(&mut decoder).or_fail()?;
Ok(())
}
fn audio_stats() -> Mp4AudioReaderStats {
Mp4AudioReaderStats {
codec: Some(CodecName::Opus),
..Default::default()
}
}
fn video_stats() -> Mp4VideoReaderStats {
Mp4VideoReaderStats::default()
}