$npx -y skills add gooseworks-ai/goose-skills --skill extract-source-sampleGiven the path to a finished content-goose ad-run folder, extract everything that defines that ad — recipe shot list, VO script, characters, voices, world, atom-skills, master mp4 — and emit a source-sample.json in the exact shape the upload-ad-sample skill writes to the Goos
| 1 | # extract-source-sample |
| 2 | |
| 3 | This is an **agent-executed** skill. There are no Python scripts. The agent |
| 4 | reads the run folder, builds the JSON, and stamps catalog links by hand. The |
| 5 | content-goose run folders aren't always cleanly structured (some have empty |
| 6 | production/ JSON, some carry everything in working/) — an agent adapts, a |
| 7 | script would brittle out. |
| 8 | |
| 9 | ## When to use |
| 10 | |
| 11 | - "Extract the source-sample.json for `<run>`." |
| 12 | - "Get the upload-sample JSON for this ad so I can remix it." |
| 13 | - "Prep `<run>` for remix." |
| 14 | |
| 15 | Do NOT use to: |
| 16 | - Rewrite the script for a new brand (that's a separate agent step that |
| 17 | consumes this skill's output). |
| 18 | - Render the remix (that's the existing `remix-ad` skill). |
| 19 | - Upload an ad to the library (that's `upload-ad-sample`). |
| 20 | |
| 21 | ## Inputs |
| 22 | |
| 23 | | Input | Required | Notes | |
| 24 | |---|---|---| |
| 25 | | `run-dir` | yes | Absolute path to a content-goose ad-run folder (e.g. `clients/ladder/ad-runs/run-02-podcast-skit`). | |
| 26 | | `out` | no | Where to write the JSON. Default: `<run-dir>/remix/source-sample.json`. | |
| 27 | |
| 28 | That's the entire interface. |
| 29 | |
| 30 | ## What the agent must do |
| 31 | |
| 32 | ### 1. Read the run |
| 33 | |
| 34 | Open each file if it exists; tolerate missing files (most production/*.json |
| 35 | in older runs are empty stubs — fall back to `working/`): |
| 36 | |
| 37 | - `working/script.json` — **primary source of truth** for scenes, voices, set. |
| 38 | - `production/asset-manifest.json` — `assets[]` with role `active_master` |
| 39 | points at the master mp4; per-asset `provider` + `metadata.model` produce |
| 40 | the atom-skill rows. |
| 41 | - `HOW_TO_MAKE_THIS_VIDEO.md` — gets dumped verbatim into `how_to`. |
| 42 | - `video-project.json` — fallback for title / format when script.json doesn't |
| 43 | carry them. |
| 44 | - `finals/*.mp4` — fallback for the master mp4 if asset-manifest is empty. |
| 45 | - `working/characters/*.png` — anchor portraits per character. |
| 46 | - `working/*.py` — driver scripts (`render_vo.py`, `render_variants.py`, |
| 47 | `render_clips.py`, `stitch.py`, `build_end_card.py`, etc.). These are the |
| 48 | source's runnable code; the remix consumer ports them. Capture in |
| 49 | `production_scripts[]` (step 2 below). |
| 50 | |
| 51 | **For sources with character-pose stills** (any run with a |
| 52 | `working/characters/` folder of `<character>-<pose>.png` files — |
| 53 | podcast-skit, founder-led, testimonial, recreate-ugc, etc.), audit every |
| 54 | PNG with `file`. Do NOT stop at the base portraits. The recipe shot list |
| 55 | references variant expression PNGs (e.g. `brittney-eyebrow-up.png`, |
| 56 | `brad-phone-up.png`) by filename; the consumer assumes they exist on disk |
| 57 | and will spend real money on lipsync calls before discovering they don't. |
| 58 | |
| 59 | For sources without character-pose stills (music-video b-roll, abstract |
| 60 | animated, product-only) — skip this audit; `variant_assets[]` stays empty. |
| 61 | |
| 62 | Record per file (when auditing): |
| 63 | |
| 64 | ```jsonc |
| 65 | { |
| 66 | "file": "brittney-eyebrow-up.png", |
| 67 | "pose_tag": "skeptical-eyebrow", // slug from filename stem |
| 68 | "kind": "real" | "lfs-pointer" | "missing", |
| 69 | "size_bytes": 142336 |
| 70 | } |
| 71 | ``` |
| 72 | |
| 73 | `file <path>` says `PNG image data, …` for real binaries and `ASCII text` |
| 74 | for LFS pointers. A real binary is `>10KB` in practice; an LFS pointer is |
| 75 | `<200 bytes`. |
| 76 | |
| 77 | **Materialize LFS pointers before reading any binary.** An LFS pointer is a |
| 78 | tiny (<200 byte) ASCII file beginning with `version https://git-lfs.github.com`. |
| 79 | If a PNG or mp4 looks like one, run: |
| 80 | |
| 81 | ```bash |
| 82 | cd <run-dir-or-repo-root> |
| 83 | git lfs fetch --include=<relative path> |
| 84 | git lfs checkout <relative path> |
| 85 | ``` |
| 86 | |
| 87 | before referencing it. **If `git lfs pull` no-ops and the LFS endpoint |
| 88 | returns 404** (objects committed as pointers but never pushed — common on |
| 89 | content-goose), leave the entry as `kind: "lfs-pointer"` in |
| 90 | `variant_assets[]`. The consumer will regenerate or scrape; this skill |
| 91 | does NOT fabricate. See [[feedback_lfs_pointer_audit_before_paid_calls]] |
| 92 | and [[feedback_fal_subscribe_error_envelope]] for the downstream cost when |
| 93 | this audit is skipped — Hume run-03 lost ~$3 + 25 min to it. |
| 94 | |
| 95 | ### 2. Build `source-sample.json` |
| 96 | |
| 97 | Shape (every key always present, arrays may be empty): |
| 98 | |
| 99 | ```jsonc |
| 100 | { |
| 101 | "title": "<from script.json or video-project.json>", |
| 102 | "format": "video", |
| 103 | "ratio": "<aspect_ratio from script.json — e.g. 9:16>", |
| 104 | "formatProfile": "podcast-skit-fabricated", // enum, see below |
| 105 | "media_url": "fil |