clipforge / humeo-core /tests /test_compile.py
moonlantern1's picture
Deploy ClipForge Docker Space
eda316b verified
from pathlib import Path
from humeo_core.primitives import compile as compile_mod
from humeo_core.primitives.compile import (
_ensure_windows_fontconfig,
build_ffmpeg_cmd,
plan_title_drawtext,
)
from humeo_core.schemas import Clip, LayoutInstruction, LayoutKind, RenderRequest, RenderTheme
def _req(**overrides):
c = Clip(clip_id="1", topic="t", start_time_sec=10.0, end_time_sec=40.0)
li = LayoutInstruction(clip_id="1", layout=LayoutKind.SIT_CENTER)
data = dict(
source_path="/tmp/src.mp4",
clip=c,
layout=li,
output_path="/tmp/out.mp4",
render_theme=RenderTheme.LEGACY,
mode="dry_run",
)
data.update(overrides)
return RenderRequest(**data)
def test_ffmpeg_cmd_has_ss_duration_filtergraph_output():
cmd = build_ffmpeg_cmd(_req())
assert "-ss" in cmd
assert "-t" in cmd
assert "-filter_complex" in cmd
# duration = 30.0
t_idx = cmd.index("-t")
assert float(cmd[t_idx + 1]) == 30.0
ss_idx = cmd.index("-ss")
assert float(cmd[ss_idx + 1]) == 10.0
assert cmd[-1] == "/tmp/out.mp4"
def test_title_text_injects_drawtext():
cmd = build_ffmpeg_cmd(_req(title_text="Hello: world's"))
fg = cmd[cmd.index("-filter_complex") + 1]
assert "drawtext" in fg
# colon should be escaped
assert "Hello\\:" in fg
assert "worlds" in fg
assert "world's" not in fg
assert "expansion=none" in fg
def test_map_vout_and_primary_audio():
cmd = build_ffmpeg_cmd(_req())
assert "[vout]" in cmd
assert "0:a:0" in cmd
def test_subtitle_style_uses_requested_font_and_margin():
cmd = build_ffmpeg_cmd(
_req(subtitle_path="/tmp/clip.srt", subtitle_font_size=18, subtitle_margin_v=64)
)
fg = cmd[cmd.index("-filter_complex") + 1]
assert "subtitles='" in fg
assert "FontSize=18" in fg
assert "MarginV=64" in fg
# Smart word wrap so long captions break into multiple readable lines.
assert "WrapStyle=0" in fg
def test_subtitle_original_size_pins_libass_to_output_resolution():
"""Without original_size=W x H, libass uses PlayResY=288 and blows up fonts/margins.
This is the root cause of the "subtitles floating in the middle of the
frame / blocked" bug the user reported.
"""
cmd = build_ffmpeg_cmd(_req(subtitle_path="/tmp/clip.srt"))
fg = cmd[cmd.index("-filter_complex") + 1]
assert "original_size=1080x1920" in fg
def test_subtitles_applied_after_crop_and_title():
"""Order: crop/compose -> drawtext title -> subtitles.
The pipeline must crop **first**, then draw text on the finished frame.
"""
cmd = build_ffmpeg_cmd(
_req(title_text="Hook", subtitle_path="/tmp/clip.srt")
)
fg = cmd[cmd.index("-filter_complex") + 1]
crop_pos = fg.index("[0:v]crop=")
drawtext_pos = fg.index("drawtext")
subs_pos = fg.index("subtitles=")
assert crop_pos < drawtext_pos < subs_pos
def test_build_is_layout_specific():
c = Clip(clip_id="1", topic="t", start_time_sec=0, end_time_sec=10)
split_req = _req(
clip=c,
layout=LayoutInstruction(clip_id="1", layout=LayoutKind.SPLIT_CHART_PERSON),
)
cmd = build_ffmpeg_cmd(split_req)
fg = cmd[cmd.index("-filter_complex") + 1]
assert "vstack" in fg
def test_title_is_suppressed_on_split_layouts():
"""Split layouts already contain a slide/chart with its own title.
Overlaying an additional drawtext title just obscures content -- that's
what was happening in the Cathy Wood "chart overlaps subject" report.
"""
for kind in (
LayoutKind.SPLIT_CHART_PERSON,
LayoutKind.SPLIT_TWO_PERSONS,
LayoutKind.SPLIT_TWO_CHARTS,
):
cmd = build_ffmpeg_cmd(
_req(
layout=LayoutInstruction(clip_id="1", layout=kind),
title_text="This should not render",
)
)
fg = cmd[cmd.index("-filter_complex") + 1]
assert "drawtext" not in fg, f"title leaked into split layout {kind}"
def test_title_is_drawn_on_single_subject_layouts():
"""Titles are still rendered on ZOOM_CALL_CENTER and SIT_CENTER."""
for kind in (LayoutKind.ZOOM_CALL_CENTER, LayoutKind.SIT_CENTER):
cmd = build_ffmpeg_cmd(
_req(
layout=LayoutInstruction(clip_id="1", layout=kind),
title_text="Hook title",
)
)
fg = cmd[cmd.index("-filter_complex") + 1]
assert "drawtext=text='Hook title'" in fg
# ---------------------------------------------------------------------------
# Title wrapping / auto-shrink (P2: fixes the "Prediction Markets vs
# Derivatives" clipped-title bug reported against the Cathy Wood run).
# ---------------------------------------------------------------------------
def test_plan_title_short_stays_single_line_at_72px():
"""Backward compat: short titles keep the pre-P2 single-drawtext form.
Byte-identical output for short titles is important because it keeps
previously-calibrated visual output unchanged and avoids needless cache
churn on existing renders.
"""
frag = plan_title_drawtext("Hook title", out_w=1080)
assert frag is not None
assert frag.count("drawtext=") == 1
assert "fontsize=72" in frag
assert "y=80" in frag
assert "drawtext=text='Hook title'" in frag
def test_plan_title_long_wraps_to_two_lines_below_72px():
"""Long titles wrap at the best word boundary and shrink to fit.
"Prediction Markets vs Derivatives" is 33 chars — it overflows a 1080px
canvas at 72px. It must wrap into "Prediction Markets" / "vs Derivatives"
(balanced halves) at a smaller font.
"""
frag = plan_title_drawtext("Prediction Markets vs Derivatives", out_w=1080)
assert frag is not None
assert frag.count("drawtext=") == 2, "long titles must split into two drawtext calls"
assert "drawtext=text='Prediction Markets'" in frag
assert "drawtext=text='vs Derivatives'" in frag
assert "fontsize=72" not in frag, "two-line layout must use a smaller font"
# Both lines share the same shrunken fontsize.
import re
sizes = re.findall(r"fontsize=(\d+)", frag)
assert len(sizes) == 2 and sizes[0] == sizes[1]
assert 44 <= int(sizes[0]) <= 64
def test_plan_title_empty_returns_none():
assert plan_title_drawtext("", out_w=1080) is None
assert plan_title_drawtext(" ", out_w=1080) is None
def test_plan_title_single_huge_word_shrinks_instead_of_wrapping():
"""A single word cannot be word-wrapped; it must shrink to fit."""
frag = plan_title_drawtext("Supercalifragilisticexpialidocious", out_w=1080)
assert frag is not None
assert frag.count("drawtext=") == 1 # no wrap possible
assert "fontsize=72" not in frag
def test_title_uses_arial_font_not_default_serif():
"""Titles must render in Arial (matching the ASS subtitle font), not the
platform default which is Times New Roman on Windows.
Regression test for the "ugly serif title on the finance short" bug.
Both the single-line and the two-line drawtext variants must carry a
``font=Arial`` directive so fontconfig resolves to the same family as
the subtitle ``Fontname=Arial``.
"""
short = plan_title_drawtext("Hook title", out_w=1080)
assert short is not None
assert "font=Arial" in short or "fontfile='" in short
long_frag = plan_title_drawtext("Prediction Markets vs Derivatives", out_w=1080)
assert long_frag is not None
if "font=Arial" in long_frag:
assert long_frag.count("font=Arial") == 2
else:
assert long_frag.count("fontfile='") == 2
def test_title_font_matches_subtitle_font_family():
"""Title overlay and subtitle captions must read as one typographic
family. Both routes through ``build_ffmpeg_cmd`` should carry the same
Arial reference.
"""
cmd = build_ffmpeg_cmd(
_req(
title_text="Hook title",
subtitle_path="/tmp/clip.ass",
)
)
fg = cmd[cmd.index("-filter_complex") + 1]
assert "font=Arial" in fg or "fontfile='" in fg
assert "Fontname=Arial" in fg
def test_long_title_pipes_through_build_ffmpeg_cmd():
"""End-to-end: a long title routed through the full command builder
produces a valid filtergraph with two drawtext filters and no syntax
errors ffmpeg would choke on.
"""
cmd = build_ffmpeg_cmd(_req(title_text="Prediction Markets vs Derivatives"))
fg = cmd[cmd.index("-filter_complex") + 1]
assert fg.count("drawtext=") == 2
assert "[v_prepad]drawtext=text='Prediction Markets'" in fg
assert "[vout]" in fg
assert ";;" not in fg # no empty chain links
assert ",," not in fg # no stray commas
def test_reference_theme_draws_title_and_caption_bars():
cmd = build_ffmpeg_cmd(
_req(
title_text="A Multi-Trillion Dollar Opportunity",
subtitle_path="/tmp/clip.ass",
render_theme=RenderTheme.REFERENCE_LOWER_THIRD,
)
)
fg = cmd[cmd.index("-filter_complex") + 1]
assert "drawbox=x=28:y=32" in fg
assert "drawbox=x=0:y=" in fg
assert "Fontname=Source Sans 3" in fg
assert "Alignment=2" in fg
assert "Outline=2" in fg
def test_reference_theme_wraps_long_titles_inside_the_title_bar():
cmd = build_ffmpeg_cmd(
_req(
title_text="12% Youth Unemployment? Start a Business With AI",
render_theme=RenderTheme.REFERENCE_LOWER_THIRD,
)
)
fg = cmd[cmd.index("-filter_complex") + 1]
assert fg.count("drawtext=") >= 2
assert "..." not in fg
def test_reference_theme_draws_frosted_caption_ribbon_when_subtitles_exist():
cmd = build_ffmpeg_cmd(
_req(
title_text="Hook title",
subtitle_path="/tmp/clip.ass",
render_theme=RenderTheme.REFERENCE_LOWER_THIRD,
)
)
fg = cmd[cmd.index("-filter_complex") + 1]
assert "drawbox=x=0:y=" in fg
def test_reference_theme_allows_titles_on_split_layouts():
cmd = build_ffmpeg_cmd(
_req(
layout=LayoutInstruction(clip_id="1", layout=LayoutKind.SPLIT_CHART_PERSON),
title_text="Hook title",
render_theme=RenderTheme.REFERENCE_LOWER_THIRD,
)
)
fg = cmd[cmd.index("-filter_complex") + 1]
assert "drawtext=" in fg
def test_native_highlight_theme_skips_title_card_and_keeps_ass_styles():
cmd = build_ffmpeg_cmd(
_req(
title_text="This title should not render",
subtitle_path="/tmp/clip.ass",
render_theme=RenderTheme.NATIVE_HIGHLIGHT,
)
)
fg = cmd[cmd.index("-filter_complex") + 1]
assert "drawtext" not in fg
assert "subtitles='" in fg
assert "force_style='" not in fg
def test_ensure_windows_fontconfig_is_noop_off_windows():
env = _ensure_windows_fontconfig()
assert isinstance(env, dict)
def test_ensure_windows_fontconfig_creates_config(monkeypatch, tmp_path):
monkeypatch.setattr(compile_mod.os, "name", "nt", raising=False)
monkeypatch.delenv("FONTCONFIG_FILE", raising=False)
monkeypatch.setenv("LOCALAPPDATA", str(tmp_path / "localappdata"))
monkeypatch.setenv("WINDIR", str(tmp_path / "winroot"))
env = _ensure_windows_fontconfig()
cfg_file = Path(env["FONTCONFIG_FILE"])
assert cfg_file.is_file()
text = cfg_file.read_text(encoding="utf-8")
assert (tmp_path / "winroot" / "Fonts").as_posix() in text
assert "fontconfig-cache" in text