d2a97823f5
Per sub-project-2 plan Task 5 / spec §5.2. ffmpeg-only dominant color (no image library), opt-in by design; extract_frame for review previews. Runners injectable so the unit suite never shells out. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
59 lines
1.4 KiB
Python
59 lines
1.4 KiB
Python
"""ffmpeg helpers: representative frame; OPTIONAL dominant color. Runner injectable."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
|
|
|
|
def _run_bytes(args) -> bytes:
|
|
return subprocess.run(args, capture_output=True, check=True).stdout
|
|
|
|
|
|
def dominant_color_from_rgb(rgb: bytes) -> str:
|
|
r, g, b = rgb[0], rgb[1], rgb[2]
|
|
return f"#{r:02x}{g:02x}{b:02x}"
|
|
|
|
|
|
def compute_dominant_color(path, *, midpoint_s=0.0, runner=_run_bytes) -> str:
|
|
"""ffmpeg-only single-color palette of a mid-segment frame -> #rrggbb."""
|
|
args = [
|
|
"ffmpeg",
|
|
"-v",
|
|
"quiet",
|
|
"-ss",
|
|
str(midpoint_s),
|
|
"-i",
|
|
str(path),
|
|
"-vf",
|
|
"thumbnail,palettegen=max_colors=1",
|
|
"-frames:v",
|
|
"1",
|
|
"-f",
|
|
"rawvideo",
|
|
"-pix_fmt",
|
|
"rgb24",
|
|
"-",
|
|
]
|
|
return dominant_color_from_rgb(runner(args))
|
|
|
|
|
|
def extract_frame(path, dest, *, midpoint_s=0.0, runner=None):
|
|
"""Write one representative frame to dest (PNG) for the review preview."""
|
|
runner = runner or (lambda a: subprocess.run(a, check=True))
|
|
runner(
|
|
[
|
|
"ffmpeg",
|
|
"-v",
|
|
"quiet",
|
|
"-y",
|
|
"-ss",
|
|
str(midpoint_s),
|
|
"-i",
|
|
str(path),
|
|
"-frames:v",
|
|
"1",
|
|
str(dest),
|
|
]
|
|
)
|
|
return dest
|