$npx -y skills add gooseworks-ai/goose-skills --skill ugc-fixloopthe UGC fix-loop toolkit — surgically re-render a bad window/beat of a single-take UGC master (stitch_replacement.py, pure FFmpeg) and GPT cross-model review a Seedance prompt before render (vet_seedance_prompt.py, routed through the openai-proxy). Fetch it into a one-shot UGC re
| 1 | # ugc-fixloop |
| 2 | |
| 3 | The UGC fix-loop toolkit. The one-shot UGC video recipes (`create-ugc-*-video-from-refs`) |
| 4 | render a single continuous Seedance 2.0 reference-to-video master with native lip-synced |
| 5 | audio. This capability ships the two scripts those recipes run, so they exist on the remote |
| 6 | machine (fetched into `/tmp/gooseworks-scripts/ugc-fixloop/`). |
| 7 | |
| 8 | > Any re-render of a replacement clip goes through the same proxy path the recipe uses for |
| 9 | > the take (`create-video-fal` / fal-proxy), NEVER a direct `fal.run` call. |
| 10 | |
| 11 | ## Env / deps |
| 12 | - **`stitch_replacement.py`** — no API key, no network. Needs **`ffmpeg` + `ffprobe` on PATH** (all local FFmpeg). |
| 13 | - **`vet_seedance_prompt.py`** — routes through the GooseWorks **openai-proxy** (`<api_base>/api/internal/openai-proxy/v1/chat/completions`), reading creds from `~/.gooseworks/credentials.json` — **no direct OpenAI call, no local key**; the call **bills the Ads agent**. Exits 3 if the proxy/creds are unavailable so the recipe can fall back to an inline self-review (the vet is advisory, not a gate). |
| 14 | |
| 15 | ## Run — vet_seedance_prompt.py (GPT cross-model prompt review) |
| 16 | A deliberately NON-Claude second opinion on the Seedance prompt before you spend the render |
| 17 | (Claude reviewing its own prompt is a weaker signal). Takes the prompt as an argument: |
| 18 | ``` |
| 19 | vet_seedance_prompt.py --prompt-file working/seedance-prompt.txt \ |
| 20 | [--brief "one-line intent"] [--refs "@Image1=avatar; @Image2=product; @Image3=env"] \ |
| 21 | [--words 28] [--out working/seedance-review.md] |
| 22 | ``` |
| 23 | Prints + saves the structured review (verdict, line edits, word budget, consistency risk). |
| 24 | |
| 25 | ## Run — stitch_replacement.py (surgical beat/window swap, deterministic) |
| 26 | Replaces one segment of the master on the VIDEO track only; the master's audio (VO + ambience) |
| 27 | plays straight through, so lip-sync on talking beats is never touched. Output is re-encoded |
| 28 | H.264 / yuv420p at the master's fps + resolution. |
| 29 | |
| 30 | Required: `--master M.mp4 --replacement R.mp4 --output O.mp4`. Pick the window ONE of two ways: |
| 31 | ``` |
| 32 | # By beat (1-indexed segment between auto-detected scene cuts): |
| 33 | stitch_replacement.py --master M.mp4 --replacement R.mp4 --output O.mp4 --replace-beat 2 |
| 34 | |
| 35 | # By explicit window (seconds): |
| 36 | stitch_replacement.py --master M.mp4 --replacement R.mp4 --output O.mp4 \ |
| 37 | --window-start 4.21 --window-end 8.75 --fit stretch |
| 38 | ``` |
| 39 | All args: |
| 40 | - `--master` (required) — the single-take master mp4. |
| 41 | - `--replacement` (required) — the re-rendered silent replacement clip (generated via `create-video-fal`). |
| 42 | - `--output` (required) — output mp4 path. |
| 43 | - `--window-start` / `--window-end` (float seconds) — explicit hole to replace. |
| 44 | - `--replace-beat` (int, 1-indexed) — pick the segment between detected scene cuts. |
| 45 | - `--scene-threshold` (float, default `0.3`) — scene-cut sensitivity for `--replace-beat`. |
| 46 | - `--fit {stretch,trim,freeze}` (default `stretch`) — reconcile replacement length to the hole. |
| 47 | - `--dry-run` — print the ffmpeg command without running. |
| 48 | |
| 49 | Warns if output duration drifts >0.15s from the master (audio-sync check). |