File size: 2,432 Bytes
eda316b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
"""Thin adapter from the product pipeline to the reusable render primitive."""

from __future__ import annotations

import logging
from pathlib import Path

from humeo_core.primitives import compile as compile_mod
from humeo_core.schemas import (
    Clip,
    LayoutInstruction,
    LayoutKind,
    RenderRequest,
    RenderTheme,
)

logger = logging.getLogger(__name__)


def layout_for_clip(

    clip: Clip,

    default_layout: LayoutKind = LayoutKind.SIT_CENTER,

    zoom: float = 1.0,

) -> LayoutInstruction:
    """Build the layout instruction for a clip using the shared schema."""
    layout = clip.layout or default_layout
    return LayoutInstruction(clip_id=clip.clip_id, layout=layout, zoom=zoom)


def reframe_clip_ffmpeg(

    input_path: Path | str,

    output_path: Path | str,

    clip: Clip,

    *,

    zoom: float = 1.0,

    layout_instruction: LayoutInstruction | None = None,

    subtitle_path: Path | str | None = None,

    subtitle_font_size: int = 48,

    subtitle_margin_v: int = 160,

    title_text: str = "",

    render_theme: RenderTheme = RenderTheme.NATIVE_HIGHLIGHT,

    dry_run: bool = False,

) -> RenderRequest:
    """Render a single clip to 9:16 via one ffmpeg call.



    If ``layout_instruction`` is set (e.g. from Gemini vision), it is used in full

    including ``person_x_norm``, ``chart_x_norm``, and optional split bbox fields.

    Otherwise defaults are derived from ``clip.layout`` via ``layout_for_clip``.

    """

    instr = layout_instruction if layout_instruction is not None else layout_for_clip(clip, zoom=zoom)
    req = RenderRequest(
        source_path=str(input_path),
        clip=clip,
        layout=instr,
        output_path=str(output_path),
        subtitle_path=str(subtitle_path) if subtitle_path else None,
        subtitle_font_size=subtitle_font_size,
        subtitle_margin_v=subtitle_margin_v,
        title_text=title_text,
        render_theme=render_theme,
        mode="dry_run" if dry_run else "normal",
    )
    result = compile_mod.render_clip(req)
    if not result.success and not dry_run:
        raise RuntimeError(f"ffmpeg failed for clip {clip.clip_id}: {result.error}")
    logger.info(
        "reframe_clip_ffmpeg: clip=%s layout=%s output=%s success=%s",
        clip.clip_id,
        instr.layout.value,
        output_path,
        result.success,
    )
    return req