CtrlK
BlogDocsLog inGet started
Tessl Logo

gamussa/reels-producer-skill

help quicky produce instagram reels and youtube shorts

94

2.12x
Quality

96%

Does it follow best practices?

Impact

87%

2.12x

Average score across 3 eval scenarios

SecuritybySnyk

Passed

No findings from the security scan

Overview
Quality
Evals
Security
Files

render_moviepy.pyskills/reel-builder/scripts/

#!/usr/bin/env python3
"""Alternative renderer: assemble a cut plan with MoviePy instead of ffmpeg.

Same contract as render_reel.py — same cut_plan.json in, same kind of file out —
so the two can be A/B'd against each other on identical input. Only the
assembly engine differs:

  render_reel.py    cuts each segment to a temp file, concat demuxer, single mux
  render_moviepy.py builds an in-memory clip graph, writes once

Grading is deliberately NOT reimplemented here. The same ffmpeg filter chain
from render_reel.GRADES is applied as a second pass, so a comparison isolates
the assembly engine rather than measuring two different colour pipelines.

Requires: pip install moviepy  (MoviePy 2.x API — subclipped/with_volume_scaled)

Usage:
  render_moviepy.py plan/cut_plan.json --preview --out preview/mp_v1.mp4
  render_moviepy.py plan/cut_plan.json --grade rapha --out work/master_mp.mp4
"""
import argparse, json, subprocess, sys, tempfile, time
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent))
from render_reel import GRADES, RESOLUTIONS   # single source of truth for looks


def apply_grade_pass(src, dst, grade, lut, srt, crf, preset):
    """Second pass: identical filter chain to render_reel.py, via ffmpeg."""
    vf = []
    if lut:
        vf.append(f"lut3d='{lut}'")
    elif GRADES.get(grade):
        vf.append(GRADES[grade])
    if srt:
        vf.append(f"subtitles='{srt}':force_style="
                  "'FontName=Arial Black,FontSize=14,PrimaryColour=&HFFFFFF,"
                  "OutlineColour=&H000000,Outline=2,Alignment=2,MarginV=60'")
    if not vf:
        src.rename(dst)
        return
    r = subprocess.run(
        ["ffmpeg", "-y", "-v", "error", "-i", str(src), "-vf", ",".join(vf),
         "-c:v", "libx264", "-preset", preset, "-crf", crf,
         "-c:a", "copy", "-movflags", "+faststart", str(dst)],
        capture_output=True, text=True)
    if r.returncode != 0:
        sys.exit(f"grade pass failed:\n{r.stderr[-800:]}")


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("plan")
    ap.add_argument("--out", required=True)
    ap.add_argument("--preview", action="store_true", help="fast 540x960 draft")
    ap.add_argument("--grade", default="none", choices=list(GRADES))
    ap.add_argument("--lut", help="Custom .cube LUT (overrides --grade)")
    ap.add_argument("--srt", help="Burn in captions from SRT file")
    ap.add_argument("--resolution", choices=sorted(RESOLUTIONS), default="1080")
    ap.add_argument("--music-gain-db", type=float, default=-3.0)
    ap.add_argument("--natural-audio", type=float, metavar="DB", default=None)
    args = ap.parse_args()

    try:
        from moviepy import (VideoFileClip, AudioFileClip,
                             concatenate_videoclips, CompositeAudioClip)
    except ImportError:
        sys.exit("MoviePy not installed — `pip install moviepy` inside the "
                 "project venv, or use scripts/render_reel.py (ffmpeg engine).")

    plan = json.loads(Path(args.plan).read_text())
    segs = plan["segments"]
    music = plan.get("music")
    nat_db = args.natural_audio
    if not music and nat_db is None:
        nat_db = 0.0
    total = plan.get("total_duration") or sum(s["duration"] for s in segs)

    w, h = (540, 960) if args.preview else RESOLUTIONS[args.resolution]
    crf = "26" if args.preview else "16"
    preset = "veryfast" if args.preview else "slow"

    out = Path(args.out)
    out.parent.mkdir(parents=True, exist_ok=True)

    started = time.time()
    opened, parts = [], []
    for n, s in enumerate(segs):
        clip = VideoFileClip(s["clip"])
        opened.append(clip)
        piece = clip.subclipped(s["in"], s["in"] + s["duration"]).resized((w, h))
        if nat_db is None:
            piece = piece.without_audio()
        parts.append(piece)
        print(f"  cut {n+1}/{len(segs)}", flush=True)

    video = concatenate_videoclips(parts, method="compose")

    tracks = []
    if nat_db is not None and video.audio is not None:
        tracks.append(video.audio.with_volume_scaled(10 ** (nat_db / 20)))
    if music:
        bed = AudioFileClip(music)
        opened.append(bed)
        start = plan.get("music_start", 0)
        bed = bed.subclipped(start, min(start + total, bed.duration))
        tracks.append(bed.with_volume_scaled(10 ** (args.music_gain_db / 20)))
    if tracks:
        mixed = tracks[0] if len(tracks) == 1 else CompositeAudioClip(tracks)
        video = video.with_audio(mixed.with_duration(min(total, video.duration)))

    with tempfile.TemporaryDirectory() as td:
        raw = Path(td) / "assembled.mp4"
        video.write_videofile(
            str(raw), fps=30, codec="libx264", audio_codec="aac",
            preset=preset, ffmpeg_params=["-crf", crf, "-pix_fmt", "yuv420p"],
            logger=None)
        for c in opened:
            c.close()
        video.close()
        out_tmp = out.with_name(out.stem + ".tmp" + out.suffix)
        apply_grade_pass(raw, out_tmp, args.grade, args.lut, args.srt, crf, preset)

    r = subprocess.run(["ffprobe", "-v", "quiet", "-show_entries",
                        "format=duration", "-of", "csv=p=0", str(out_tmp)],
                       capture_output=True, text=True)
    if r.returncode != 0 or not r.stdout.strip():
        out_tmp.unlink(missing_ok=True)
        sys.exit("Rendered file failed ffprobe validation — output discarded")
    out_tmp.rename(out)
    print(f"Rendered {out} ({float(r.stdout.strip()):.1f}s, engine=moviepy, "
          f"{w}x{h}, grade={args.lut or args.grade}) "
          f"in {time.time() - started:.1f}s wall")


if __name__ == "__main__":
    main()

.mcp.json

tile.json