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