Files
mediapreview/tests/files/generate_fixtures.py
T
LeoVasanko 11df5cf580 test: add low-level preview success tests for all formats
Add fixtures and tests covering image EXIF orientations, HDR AVIF, SDR video, 90/270 rotated video, HDR video with and without rotation, PDF, dispatch, and OnlyOffice (skipped unless configured).

Regenerate rotated/HDR video fixtures and sample.pdf with tests/files/generate_fixtures.py.
2026-08-13 00:20:13 +00:00

181 lines
5.5 KiB
Python

#!/usr/bin/env python3
"""Generate missing test fixtures for the low-level preview tests.
This script only creates files that are not already present in tests/files/.
Run it whenever you need to rebuild the rotated/HDR video fixtures or the
sample PDF.
"""
from __future__ import annotations
import logging
import subprocess
import sys
from pathlib import Path
try:
import pymupdf
except ImportError as e: # pragma: no cover - optional pdf extra
raise ImportError(
"Fixture generation requires pymupdf: pip install mediapreview[pdf]"
) from e
ROOT = Path(__file__).resolve().parent
FILES = ROOT
logger = logging.getLogger(__name__)
def run(cmd: list[str]) -> None:
subprocess.run(cmd, check=True, capture_output=True, text=True)
def _find_box(data: bytearray, box_type: bytes, start: int = 0, end: int | None = None) -> int:
end = end or len(data)
i = start
while i + 8 <= end:
size = int.from_bytes(data[i : i + 4], "big")
btype = data[i + 4 : i + 8]
if btype == box_type:
return i
if size == 0:
break
if size == 1:
size = int.from_bytes(data[i + 8 : i + 16], "big")
if size < 8:
raise ValueError(f"Invalid box size {size} for {btype.decode('ascii', errors='replace')}")
i += size
return -1
def _matrix_90_cw() -> list[int]:
return [0, 1 << 16, 0, -(1 << 16), 0, 0, 0, 0, 1 << 16]
def _matrix_270_cw() -> list[int]:
return [0, -(1 << 16), 0, 1 << 16, 0, 0, 0, 0, 1 << 16]
def _patch_tkhd_rotation(in_path: Path, out_path: Path, degrees: int) -> None:
"""Write a copy of *in_path* with the video track display matrix rotated."""
data = bytearray(in_path.read_bytes())
moov_idx = _find_box(data, b"moov")
if moov_idx < 0:
raise ValueError("moov box not found")
moov_end = moov_idx + int.from_bytes(data[moov_idx : moov_idx + 4], "big")
trak_idx = _find_box(data, b"trak", moov_idx + 8, moov_end)
while trak_idx >= 0:
trak_size = int.from_bytes(data[trak_idx : trak_idx + 4], "big")
trak_end = trak_idx + trak_size
mdia_idx = _find_box(data, b"mdia", trak_idx + 8, trak_end)
if mdia_idx < 0:
trak_idx = _find_box(data, b"trak", trak_end, moov_end)
continue
mdia_end = mdia_idx + int.from_bytes(data[mdia_idx : mdia_idx + 4], "big")
hdlr_idx = _find_box(data, b"hdlr", mdia_idx + 8, mdia_end)
if hdlr_idx < 0:
trak_idx = _find_box(data, b"trak", trak_end, moov_end)
continue
handler_type = data[hdlr_idx + 16 : hdlr_idx + 20]
if handler_type == b"vide":
tkhd_idx = _find_box(data, b"tkhd", trak_idx + 8, trak_end)
if tkhd_idx < 0:
raise ValueError("video track has no tkhd")
version = data[tkhd_idx + 8]
if version != 0:
raise ValueError(f"unsupported tkhd version {version}")
matrix_offset = tkhd_idx + 48
matrix = _matrix_90_cw() if degrees == 90 else _matrix_270_cw()
for i, val in enumerate(matrix):
data[matrix_offset + i * 4 : matrix_offset + (i + 1) * 4] = val.to_bytes(
4, "big", signed=True
)
out_path.write_bytes(data)
return
trak_idx = _find_box(data, b"trak", trak_end, moov_end)
raise ValueError("video track not found")
def generate_pdf() -> None:
pdf_path = FILES / "sample.pdf"
if pdf_path.exists():
return
doc = pymupdf.open()
page = doc.new_page(width=595, height=842)
page.insert_text((100, 100), "Hello, PDF preview test!")
page.draw_rect((100, 150, 400, 250), color=(1, 0, 0), width=2)
doc.save(str(pdf_path))
doc.close()
logger.info("generated %s", pdf_path)
def generate_sdr_rotated() -> None:
source = FILES / "sample-1mb.mp4"
if not source.exists():
raise FileNotFoundError(f"Missing source fixture: {source}")
for degrees in (90, 270):
out = FILES / f"rotated_{degrees}.mp4"
if out.exists():
continue
_patch_tkhd_rotation(source, out, degrees)
logger.info("generated %s", out)
def generate_hdr_video() -> None:
out = FILES / "hdr_video.mp4"
if out.exists():
return
run(
[
"ffmpeg",
"-y",
"-hide_banner",
"-loglevel",
"error",
"-nostdin",
"-f",
"lavfi",
"-i",
"testsrc=duration=1:size=320x240:rate=30",
"-c:v",
"libx265",
"-preset",
"fast",
"-crf",
"30",
"-pix_fmt",
"yuv420p10le",
"-x265-params",
"colorprim=bt2020:transfer=smpte2084:colormatrix=bt2020nc:repeat-headers=1",
"-an",
str(out),
]
)
logger.info("generated %s", out)
def generate_hdr_rotated() -> None:
source = FILES / "hdr_video.mp4"
if not source.exists():
raise FileNotFoundError(f"Generate hdr_video.mp4 first: {source}")
for degrees in (90, 270):
out = FILES / f"hdr_rotated_{degrees}.mp4"
if out.exists():
continue
_patch_tkhd_rotation(source, out, degrees)
logger.info("generated %s", out)
def main() -> int:
logging.basicConfig(level=logging.INFO, format="%(message)s")
generate_pdf()
generate_sdr_rotated()
generate_hdr_video()
generate_hdr_rotated()
return 0
if __name__ == "__main__":
sys.exit(main())