1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
//! Integration tests for preview generation (SpriteSheet, GifPreview).
//!
//! Tests verify that `SpriteSheet` produces a PNG whose pixel dimensions
//! match the configured grid layout — no external image crate needed. PNG
//! dimensions are read directly from the IHDR chunk (bytes 16–23).
#![allow(clippy::unwrap_used)]
mod fixtures;
use ff_encode::SpriteSheet;
fn test_video_path() -> std::path::PathBuf {
let manifest_dir = env!("CARGO_MANIFEST_DIR");
std::path::PathBuf::from(format!("{manifest_dir}/../../assets/video/gameplay.mp4"))
}
/// Reads exactly `n` bytes from the start of a file.
fn read_file_prefix(path: &std::path::Path, n: usize) -> std::io::Result<Vec<u8>> {
use std::io::Read as _;
let mut buf = vec![0u8; n];
let mut f = std::fs::File::open(path)?;
f.read_exact(&mut buf)?;
Ok(buf)
}
// ── Functional tests ──────────────────────────────────────────────────────────
#[test]
fn sprite_sheet_should_produce_correct_pixel_dimensions() {
let input = test_video_path();
if !input.exists() {
println!("Skipping: test video not found at {}", input.display());
return;
}
let output = fixtures::test_output_path("preview_sprite_sheet_dimensions.png");
let _guard = fixtures::FileGuard::new(output.clone());
// 5 columns × 160 px wide = 800 px; 4 rows × 90 px tall = 360 px.
let result = SpriteSheet::new(&input)
.cols(5)
.rows(4)
.frame_width(160)
.frame_height(90)
.output(&output)
.run();
match result {
Ok(()) => {}
Err(e) => {
// SpriteSheet uses the `movie` filter which can fail on Windows
// due to colon escaping in drive-letter paths.
println!("Skipping: SpriteSheet::run failed ({e})");
return;
}
}
// ── PNG magic bytes ──────────────────────────────────────────────────────
// The first 4 bytes of every PNG file are the magic signature \x89PNG.
let header = read_file_prefix(&output, 24).expect("output file must be readable");
assert_eq!(
&header[0..4],
b"\x89PNG",
"output file does not begin with PNG magic bytes"
);
// ── IHDR dimensions ──────────────────────────────────────────────────────
// PNG layout: [8-byte sig][4-byte len][4-byte "IHDR"][4-byte width][4-byte height]…
// Width is at byte offset 16, height at byte offset 20, both big-endian u32.
let width = u32::from_be_bytes(header[16..20].try_into().unwrap());
let height = u32::from_be_bytes(header[20..24].try_into().unwrap());
assert_eq!(
width, 800,
"expected PNG width 800 (5 cols × 160 px), got {width}"
);
assert_eq!(
height, 360,
"expected PNG height 360 (4 rows × 90 px), got {height}"
);
}