fix(downloads): GIF review round 1 — shape, HDR, caps, audio, copy

- A GIF squares its pixels before the size, so an anamorphic source keeps
  its shown shape (a 720x480 DVD at 16:9 was 480x320).
- An editor GIF of an HDR upload is tonemapped, as a clip GIF is.
- The length caps are judged in whole milliseconds: a range exactly at a
  cap is no longer refused for a float hair over it.
- A GIF is made of a video only: an audio file is refused with its reason
  and the editor offers no GIF for it.
- A GIF clip keeps no audio track: the track neither splits its cache key
  nor fails it.
- The editor's GIF notes keep their live region mounted; one size reads
  as one ("about 1 MB"), none is shown with nothing kept; the size choices
  show their unit; a shared picture that fails to load says so.
- Copy: the two-sentence GIF notes keep their last stop (a copy-rule test
  now holds it); the English warns of "only a still picture".
This commit is contained in:
2026-11-12 23:24:25 +01:00
parent 0b92b40db5
commit 8845d1aa49
23 changed files with 403 additions and 78 deletions
+33 -1
View File
@@ -66,11 +66,25 @@ def test_a_crop_or_a_tonemap_comes_before_the_scale_and_the_palette():
assert graph.index("crop=100:100:0:0") < graph.index("scale=") < graph.index("palettegen")
def _video(path: Path, size: str, seconds: int = 2) -> Path:
def test_the_length_cap_counts_whole_milliseconds():
# Two ends kept to the millisecond whose float difference lands a hair over 30 s.
assert 60.529 - 30.529 > gifmod.MAX_S
assert not gifmod.too_long(30.529, 60.529)
assert not gifmod.too_long(0, 30)
assert gifmod.too_long(0, 30.001)
def test_the_pixels_are_squared_after_the_crop_and_before_the_size():
graph = gifmod.filter_graph("crop=100:100:0:0", 480)
assert graph.index("crop=") < graph.index("setsar=1") < graph.index("flags=lanczos")
def _video(path: Path, size: str, seconds: int = 2, sar: str = "1") -> Path:
subprocess.run(
[
"ffmpeg", "-y", "-loglevel", "error",
"-f", "lavfi", "-i", f"testsrc2=size={size}:rate=25:duration={seconds}",
"-vf", f"setsar={sar}",
"-c:v", "libx264", "-pix_fmt", "yuv420p", str(path),
],
check=True,
@@ -78,6 +92,24 @@ def _video(path: Path, size: str, seconds: int = 2) -> Path:
return path
@needs_ffmpeg
@pytest.mark.parametrize(
("size", "sar", "expected"),
[
# A DVD stored 720x480 and shown at 16:9: a GIF shows its stored pixels as they are, so
# they are squared first, or the picture comes out 480x320 (3:2), squeezed.
("720x480", "32/27", (480, 270)),
# HDV: 1440x1080 shown at 16:9.
("1440x1080", "4/3", (480, 270)),
],
)
def test_a_source_with_wide_pixels_keeps_its_shown_shape(tmp_path, size, sar, expected):
src = _video(tmp_path / "in.mp4", size, sar=sar)
out = tmp_path / "out.gif"
subprocess.run(gifmod.build_cmd(src, out, 0.0, 1.0, pre="", side=480), check=True)
assert gifmod.size(out) == expected
@needs_ffmpeg
@pytest.mark.parametrize(
("size", "side", "expected"),