SuperAPI AI Directory

remix-ad

Marketing & growth

View repository

Updated 10-15 21:54

About this skill

remix-ad

Take a winning ad that's already in the Goose Ads library and make another one from it — usually for a different brand, sometimes the same brand with a new angle. The library sample carries a remix payload (written by the upload-ad-sample skill); this skill reads it, keeps the proven skeleton, swaps the brand-specific layer, and regenerates. It's the consumer end of the upload↔remix loop.

This skill is a planner + router: it fetches the payload, makes explicit keep-vs-swap decisions, rewrites the script, and hands the actual generation to the production template that made the original (e.g. create-podcast-skit-ad).

When to use

  • "Remix this ad for [other brand]" / "make the laundromat podcast ad but for [X]."
  • "Same format, new brand" / "riff on sample <id>" / "give me a new cut of this."
  • Any time a library sample's structure is worth reusing on fresh brand content.

Do NOT use to: research a brand from scratch (use brand-research), or make an ad with no source to remix (use a production template directly).

What it reads — the remix payload

Full contract in references/remix-payload.md. The library has no GET-by-id route, so fetch_sample.py lists samples and filters by id/title. Each sample carries: recipe (shot list + timing), extracted_script, skills_used, how_to, and remix_spec (skills, voices, worlds, characters).

The skills are atoms, never a molecule. remix_spec.skills / skills_used list atomic capability slugs (generate-voiceover, generate-broll-shot, compose-master). Run exactly those atoms in recipe order — never substitute a molecule, which would over-specify and re-pull steps the source didn't use. Keep that discipline when you re-upload the remix.

Keep vs swap (default policy)

Layer Default Notes
recipe (format + beat arc) KEEP The proven format (e.g. two-host skit) and beat arc (hook → mechanism → payoff). The recipe is a reference; the new ad's scene count flexes with the new brand's copy density.
remix_spec.skills (atoms) KEEP Run in recipe order to regenerate.
how_to gotchas KEEP Carry forward.
script copy SWAP Rewrite freely for the new brand within the source's format + beat arc. Do NOT preserve the source's literal scene count — the new brand's mechanism complexity / copy density determines how many scenes it needs.
brand assets (wordmark, product, end card) SWAP New brand's real assets. Never AI-render brand text.
voices keep (swap on request) Reuse the selected voice unless the new brand needs a different read.
world/set keep (swap on request) Reuse the set, or pick a new themed one.
characters swap (keep on request) Usually recast for the new brand; reuse a soul_id only if the persona transfers.

Setup

pip install -r requirements.txt          # python-dotenv (fetch/plan are stdlib)
cp .env.example .env                     # set GOOSEWORKS_API_TOKEN (+ GOOSEWORKS_API_BASE_URL for local)

The regeneration runs a production template — install ITS deps/keys when you reach the route step.

Workflow

  1. Get the source payload — local-chain OR library. Two cases:

    • Local-chain (the common case in content-goose). The extract-source-sample skill has already written <source-run-dir>/remix/source-sample.json to disk. Read it directly. Skip fetch_sample.py — no library round-trip needed.
    • Library-only. When the source is a sample id (no local run on disk), fetch from the library:
      python scripts/fetch_sample.py --id <sample-id> --out ./remix/source-sample.json
      # or: --title "hemp serum"
      

    Review the source's printed summary or the JSON itself: recipe shots, formatProfile, atom-skills, voices/worlds/characters, variant_assets[] audit results, production_scripts[].

  2. Intake the target — ASK explicitly for the four swap dimensions.

    Use AskUserQuestion (or equivalent in the harness) to get:

    • World: keep the source's set, or swap to a new themed set?
    • Angle: one-line creative direction. If clients/<slug>/concept-brief.md exists, surface its top concepts as the option list rather than inventing.
    • Voices: keep / swap which voice_ids? (One row per source voice.)
    • Characters: keep / swap / recast per source character (by sourceKey).

    Do NOT infer these from a one-line product description. The user's brief is usually skeletal — every Som and Hume run hit this and asked the same four questions; standardize.

    Get the new brand + product (+ guardrails). If the new brand has no context yet (ls clients/<slug>/brand-research/ returns nothing — check case-insensitively: clients/Hume-health/ and clients/hume-health/ are the same brand), run the brand-research skill first and pass its brand_dir.

    Brand-pack-current-SKU wins over the user's one-liner. If the user says "can on nightstand" but 01-company-overview.md flags the can line as EOL, use the current SKU and flag the swap. No question unless they explicitly override.

  3. Build the remix plan.

    python scripts/build_remix_plan.py --source ./remix/source-sample.json \
      --brand "<NewBrand>" --product "<New product>" \
      [--keep-voices] [--keep-world] [--recast-characters] \
      --out ./remix/remix-plan.json
    

    The plan records keep/swap per layer, the ordered atom-skills, and the route_to_template. Confirm the routed template with the user before generating.

  4. Rewrite the script. Using extracted_script and recipe as a reference, write the new brand's script that follows the same format + beat arc (hook → mechanism → payoff in the source's archetype, e.g. two-host skit). Do NOT force the same scene count — different brands have different copy density and mechanism complexity, so the new script flexes longer or shorter. What's proven and must carry: the format and the arc. Honor the new brand's guardrails (no competitor names, claim limits).

  5. Route + regenerate. Hand the plan to the route_to_template skill, passing the kept layers (recipe beats, voices/world if kept, the atom-skill list) and the new brand assets + rewritten script. Run the kept atoms in recipe order. Lock the new timeline to the freshly rendered VO, not the source duration.

  6. Assemble + QC. The routed template stitches, captions, builds the new brand's end card, and you /watch the master — confirm pacing matches the source's energy and every line lands.

  7. Re-upload (optional). Push the remix back to the library via the upload-ad-sample skill. Keep skills_used / remix_spec.skills atom-level so the next remix stays clean.

Decision rules

  • Keep the format + beat arc; honor the source's sceneCount property. Read source-sample.json.formatProfileProperties.sceneCount:

    • "flexible" (most formats) — scene count flexes with the new brand's copy density. The archetype + arc are what's proven, not the literal count.
    • "locked-to-source" (any format where audio meter sets timing — sung music, scored cinematic, etc.) — mirror the source 1:1. Flexing would desync the audio.
    • null or missing — surface to the user.
  • Atoms, not molecules. Run the exact atoms in remix_spec.skills, in recipe order. Never expand a molecule.

  • Lock timeline to the new VO, never the source duration — new copy runs to a different length. (Exception: locked-meter sung music.)

  • New brand's guardrails win. Re-apply claim/competitor limits for the target brand, not the source's.

  • Brand text composited, never AI-rendered — new wordmark + end card.

  • Confirm the routed template with the user before paid generation — unless the operator's prompt already names a format that uniquely matches the inferred template (e.g. "Ladder podcast skit" → create-podcast-skit-ad). Then skip the confirm.

  • For molecule routes without an executable driver (e.g. create-podcast-skit-ad) OR when route_to_template = (confirm-with-user): port the source run's working/*.py scripts and adapt brand-specific paths. The molecule SKILL.md is a recipe, not executable; the source's driver scripts are the closest runnable template. Read source-sample.json.production_scripts[] to find them.

    Common adaptations when porting:

    • REPO_ROOT path depth. Older drivers used Path(__file__).resolve().parents[3]; the current clients/<brand>/ad-runs/<run>/working/ layout needs parents[4]. Better: anchor on .git / CLAUDE.md presence. See [[feedback_repo_root_parents4_clients_layout]].
    • End-card brand assets (logo, hero PNG) → swap to the new brand's assets after the audit below.
    • Caption color → wire through the --caption-style flag (see below).
    • python vs python3 shebangs — many machines only have python3.
  • Verify source variant + brand-asset availability BEFORE any paid call. Audit results live in source-sample.json.remix_spec.characters[].variant_assets[] — read them. For every kind: "lfs-pointer" or kind: "missing":

    1. Try git lfs pull --include=<path> and git lfs checkout.
    2. If git lfs ls-files returns the pointer but the LFS endpoint returns 404 (objects committed as pointers but never pushed), regenerate via the source's image atom (generate-character-image via Higgsfield nano_banana_pro; spillover to create-image-nano-banana-2-fal at $0.08/image on not_enough_credits). Don't try to lipsync against a pointer text file — fal will return "Failed to load image" wrapped in a generic RuntimeError that's hard to debug.

    Same protocol for brand-asset PNGs / SVGs (logo, wordmark, hero product shot) — file every one before the end-card build. If LFS-pointer-only AND git lfs pull doesn't materialize, fetch from the brand's public CDN (URLs in clients/<brand>/brand-research/asset-urls.md). Stage replacements under <run-dir>/working/end-card-assets/ so the build script doesn't depend on brand-assets/.

    Surface the count of regenerated variants and replaced brand assets in the plan summary.

  • Caption pipeline is audio-type-dependent. Read source-sample.json.formatProfileProperties.audioType:

    • "sung-music" → drive captions from script.json scene windows (vo per scene, tStartSec/tEndSec). Whisper-large-v3 returns 🎵 Music Playing 🎵 on atmospheric mixes — don't even try.
    • "spoken-vo" → Whisper word-level on the rendered audio, per [[feedback_subtitles_from_whisper_word_level]] Case A.
    • "mixed" → split per segment by audio type and apply the matching pipeline to each.
    • null / missing → surface to the user.

    Pick the pipeline FROM the source-sample's audioType, not by trial and error.

  • --caption-style flag — standardize the three known-good options to avoid the per-run AskUserQuestion. Pass on stitch.py / burn_captions.py:

    • brand-accent (default): the new brand's accent color from visual-identity.md.
    • outline-white: white text + black outline only; safest for brand-bright paid-social (Hume / Ladder lime). See [[feedback_caption_outline_only_for_paid_social]].
    • source-color: keep the source's caption color verbatim.
  • Lipsync failure debugging. When a fal lipsync batch fails with truncated RuntimeError: fal subscribe failed for ..., don't iterate on the orchestrator — run ONE clip directly via the atom's generate.py to see the full chained FalClientHTTPError. See [[feedback_fal_subscribe_error_envelope]]. The common chained causes: image > 10MB (fal cap, fix with sips -Z 1536), LFS pointer not materialized, wallet exhausted, rate limit at high concurrency.

Output

  • ./remix/source-sample.json — the fetched payload.
  • ./remix/remix-plan.json — keep/swap decisions + atom-skill order + routed template.
  • A rewritten script for the new brand.
  • The new ad master (produced by the routed production template), optionally re-uploaded to the library.

Failure modes

  • No GET-by-id → expected; fetch_sample.py filters the list. If --title matches >1, it asks for --id.
  • Sample has no remix payload (recipe/remix_spec empty) → it was a bare showcase upload; you can still mimic from media_url by watching it, but there's no structured recipe to keep — tell the user.
  • Routed template guess is wrong → override it in remix-plan.json before generating.
  • Source voice doesn't fit the new brand → re-run the plan without --keep-voices and recast.
  • Auth 401 → GOOSEWORKS_API_TOKEN missing/invalid.

Provider-specific gotchas (each costs ~20-30 min to debug from scratch)

  • Higgsfield CLI: --medias <path> rejected as "should be array, got string" → the right flag is --start-image <path> (per higgsfield generate create --help's media-flag list). higgsfield model get <model> lists medias array as a param but the CLI translates per-model — --help is authoritative. See [[feedback_higgsfield_cli_media_flag]].

  • Higgsfield: not_enough_credits mid-batch → switch workspace first (higgsfield workspace list → set <id> to one with credits; Gooseworks is usually the right one but verify, balances drift). On full exhaustion, spillover to create-image-nano-banana-2-fal ($0.08/image, same quality for character variants). See [[feedback_higgsfield_burst_credit_reserve]] and [[reference_higgsfield_gooseworks_workspace]].

  • fal subscribe failed with truncated stderr → re-run ONE clip via the atom's generate.py directly to surface the chained FalClientHTTPError. See [[feedback_fal_subscribe_error_envelope]].

  • fal upload returns generic error past 10MB → fal caps uploads at 10MB. sips -Z 1536 <file> downscales the longest side to 1536px (or ffmpeg -vf scale=1536:-2). See [[feedback_fal_10mb_upload_cap]].

  • Groq Whisper 403 / error code: 1010 → Cloudflare blocks default Python urllib User-Agent. Add User-Agent: <app>/<version> and Accept: application/json headers on every Groq request. See [[feedback_groq_whisper_cloudflare_ua]].

  • Whisper transcribes sung music as 🎵 Music Playing 🎵 → expected whenever audioType === "sung-music". Drive captions from script.json scene windows instead. See [[feedback_subtitles_from_whisper_word_level]] Case B.

  • ElevenLabs Music 422 on music_length_ms + composition_plan together → drop music_length_ms. Composition plan section durations sum to the total. See [[reference_elevenlabs_music_api_quirks]].

  • ElevenLabs Music words_timestamps come back empty / null on sung mixes → expected. The withTimestamps: true flag is unreliable for atmospheric music. Drive captions from script.json scene windows.

  • Ported driver script's REPO_ROOT resolves wrong → bump Path(__file__).resolve().parents[N] by 1 under the clients/<brand>/ad-runs/<run>/working/ layout (or anchor on .git/CLAUDE.md presence). See [[feedback_repo_root_parents4_clients_layout]].

  • python not found, only python3 → many macOS / Linux machines only have python3 on PATH. Invoke scripts as python3 … or set a shebang #!/usr/bin/env python3.