paper-collage-ad-codex
Complete paper-cut collage advertisement production workflow with local IndexTTS-2 voice cloning, animation, audio mixing, and MP4 quality control
How do I install this agent skill?
npx skills add https://github.com/reason-machines/codex-skills --skill paper-collage-ad-codexIs this agent skill safe to install?
- Gen Agent Trust Hubfail
The skill downloads scripts from an untrusted repository and is vulnerable to command injection through unsafe interpolation of manifest data into shell commands.
- Socketpass
No alerts
- Snykwarn
Risk: MEDIUM · 1 issue
What does this agent skill do?
Paper Collage Ad for Codex
Skill by ara.so — Codex Skills collection.
A complete paper-cut/collage advertisement production skill for OpenAI Codex. Handles creative concept, script, storyboard, keyframes, animation, voiceover (including local IndexTTS-2 voice cloning), music, sound effects, compositing, and MP4 quality checking.
What This Does
This skill guides you through producing 30–60 second paper collage advertisements:
- Extract a visual metaphor from product materials
- Generate script, dialogue, and timecoded storyboard
- Create style-locked paper-cut keyframes using brand assets
- Animate via Seedance, HyperFrames, layered PNG, or FFmpeg
- Generate voiceover using standard TTS or local IndexTTS-2 MLX for zero-shot voice cloning (Apple Silicon)
- Add music, paper foley, and action sound effects
- Output H.264/AAC MP4 with stream-level validation
Installation
Install the Skill
Global (all Codex projects):
git clone https://github.com/Jane-xiaoer/paper-collage-ad-codex.git \
~/.codex/skills/paper-collage-ad
Project-local:
mkdir -p .codex/skills
git clone https://github.com/Jane-xiaoer/paper-collage-ad-codex.git \
.codex/skills/paper-collage-ad
Restart Codex or start a new task, then say:
Use paper-collage-ad to create a fun 45-second paper collage ad for this product.
System Dependencies
macOS:
brew install ffmpeg node
bash ~/.codex/skills/paper-collage-ad/scripts/check-deps.sh
FFmpeg and Node.js are required. No API keys needed for static keyframes, layered animation, and final compositing.
Project Structure
When you start a new ad project, create:
<project>/
assets/
brand/ # Logo, product images, color palette
keyframes/ # Generated paper-cut PNG frames
voice-reference/ # reference.wav (6–12s clean single-speaker audio)
voice-model/ # speaker-v2.npz (local voice embedding)
voice-final/ # 01.wav, 02.wav... (scene voiceovers)
music/ # Background music loops
sfx/ # Paper rustle, pop, swish sounds
manifests/
storyboard.json # Timecoded scene descriptions
voice.indextts2.json # Voiceover text + emotion per scene
audio-mix.json # Track volumes, fade, timing
output/
final.mp4 # Deliverable
Copy the privacy-safe .gitignore:
cp ~/.codex/skills/paper-collage-ad/examples/project.gitignore <project>/.gitignore
This excludes voice-reference/, voice-model/, voice-final/, and .env.
Workflow
1. Script & Storyboard
Provide product info and target audience. The skill will generate:
- Storyboard JSON with scene descriptions, visual metaphor, timing
- Dialogue script with emotion tags
Example manifests/storyboard.json:
{
"title": "Morning Rush Coffee Ad",
"duration": 45,
"scenes": [
{
"id": "01",
"start": 0.0,
"end": 8.0,
"visual": "Paper-cut alarm clock explodes into coffee beans",
"voiceover": "Every morning is a battle.",
"emotion": "tense"
},
{
"id": "02",
"start": 8.0,
"end": 18.0,
"visual": "Coffee cup rises like a superhero cape",
"voiceover": "Until Morning Rush Coffee arrives.",
"emotion": "heroic"
}
]
}
2. Generate Keyframes
Use brand assets and the storyboard to create paper-cut style PNG keyframes.
Manual (recommended for brand control):
- Design in Photoshop/Figma, export to
assets/keyframes/01.png,02.png...
AI-assisted:
# Example: use DALL-E or Midjourney to generate, then refine
# Store results in assets/keyframes/
Each keyframe should match the scene's visual description.
3. Animation
Option A: Static Keyframes with Transitions
Simple crossfades via FFmpeg:
node ~/.codex/skills/paper-collage-ad/scripts/render-keyframe-video.mjs \
--manifest manifests/storyboard.json \
--keyframes assets/keyframes \
--output output/video-base.mp4 \
--fps 30 \
--transition crossfade
Option B: Layered PNG Animation
For parallax or multi-layer movement:
# Place layered PNGs in assets/keyframes/01/bg.png, 01/mid.png, 01/fg.png
node ~/.codex/skills/paper-collage-ad/scripts/animate-layers.mjs \
--scene 01 \
--duration 8.0 \
--output output/scene-01.mp4
Option C: Seedance / HyperFrames (API)
If you have API access:
export SEEDANCE_API_KEY=your_key_here
node ~/.codex/skills/paper-collage-ad/scripts/animate-seedance.mjs \
--keyframe assets/keyframes/01.png \
--prompt "paper coffee cup rises and rotates" \
--duration 8.0 \
--output output/scene-01.mp4
4. Voice Clone with IndexTTS-2 MLX (Local)
Setup (one-time):
bash ~/.codex/skills/paper-collage-ad/scripts/setup-indextts2-mlx.sh
This downloads models to ~/.local/share/paper-collage-ad/mlx-indextts/models/.
Prepare Voice Model (per speaker):
Place authorized reference audio in assets/voice-reference/reference.wav (6–12s, 48kHz mono recommended).
bash ~/.codex/skills/paper-collage-ad/scripts/prepare-indextts2-voice.sh \
"assets/voice-reference/reference.wav" \
"assets/voice-model/speaker-v2.npz" \
--i-have-permission
Generate Voiceovers:
Copy and edit the manifest:
cp ~/.codex/skills/paper-collage-ad/examples/voice-manifest.indextts2.json \
manifests/voice.indextts2.json
Example manifests/voice.indextts2.json:
{
"speaker": "assets/voice-model/speaker-v2.npz",
"output_dir": "assets/voice-final",
"sample_rate": 48000,
"lines": [
{
"id": "01",
"text": "Every morning is a battle.",
"emotion": "tense",
"speed": 1.0
},
{
"id": "02",
"text": "Until Morning Rush Coffee arrives.",
"emotion": "heroic",
"speed": 1.05
}
]
}
Run synthesis:
node ~/.codex/skills/paper-collage-ad/scripts/narrate-indextts2.mjs \
--manifest manifests/voice.indextts2.json
Outputs: assets/voice-final/01.wav, 02.wav...
Privacy: Only clone your own voice or voices with explicit written permission. Mark deliverables as "AI-generated voiceover."
5. Audio Mixing
Prepare background music and SFX in assets/music/ and assets/sfx/.
Create manifests/audio-mix.json:
{
"output": "output/audio-mix.wav",
"sample_rate": 48000,
"tracks": [
{
"type": "music",
"file": "assets/music/upbeat-loop.wav",
"volume": 0.3,
"loop": true
},
{
"type": "voiceover",
"file": "assets/voice-final/01.wav",
"start": 0.5,
"volume": 1.0
},
{
"type": "voiceover",
"file": "assets/voice-final/02.wav",
"start": 8.5,
"volume": 1.0
},
{
"type": "sfx",
"file": "assets/sfx/paper-rustle.wav",
"start": 2.0,
"volume": 0.6
}
]
}
Mix:
node ~/.codex/skills/paper-collage-ad/scripts/mix-audio.mjs \
--manifest manifests/audio-mix.json
6. Final Composite & QC
Merge video and audio:
ffmpeg -i output/video-base.mp4 -i output/audio-mix.wav \
-c:v libx264 -preset slow -crf 20 -c:a aac -b:a 192k \
-movflags +faststart output/final.mp4
Validate:
bash ~/.codex/skills/paper-collage-ad/scripts/qc-mp4.sh output/final.mp4
Checks:
- H.264 video codec
- AAC audio codec, 48kHz
- Duration matches storyboard
- No stream errors
- Fast-start moov atom for web playback
Configuration
Environment Variables
Optional API services (only if you use them):
# .env (never commit this)
SEEDANCE_API_KEY=sk-...
ELEVENLAB_API_KEY=...
MINIMAX_API_KEY=...
Load in Node.js scripts:
import dotenv from 'dotenv';
dotenv.config();
const apiKey = process.env.SEEDANCE_API_KEY;
Voice Emotion Tags
IndexTTS-2 supports emotion control. Common tags:
neutralhappy,excitedsad,tenseangry,calmheroic,mysterious
Adjust emotion in voice.indextts2.json per line.
Animation Parameters
Layered animation script accepts:
// scripts/animate-layers.mjs usage
{
scene: "01",
duration: 8.0,
layers: [
{ file: "bg.png", motion: "static" },
{ file: "mid.png", motion: "pan", speed: 20 },
{ file: "fg.png", motion: "zoom", speed: 1.1 }
]
}
Code Examples
Programmatic Storyboard Generation
// generate-storyboard.mjs
import fs from 'fs';
const product = {
name: "Morning Rush Coffee",
tagline: "Wake up ready",
audience: "busy professionals"
};
const storyboard = {
title: `${product.name} Ad`,
duration: 45,
scenes: [
{
id: "01",
start: 0.0,
end: 8.0,
visual: "Alarm clock explodes into coffee beans",
voiceover: "Every morning is a battle.",
emotion: "tense"
},
{
id: "02",
start: 8.0,
end: 18.0,
visual: "Coffee cup superhero cape",
voiceover: `Until ${product.name} arrives.`,
emotion: "heroic"
},
{
id: "03",
start: 18.0,
end: 28.0,
visual: "Paper commuter runs on coffee steam train",
voiceover: "Now you're unstoppable.",
emotion: "excited"
},
{
id: "04",
start: 28.0,
end: 45.0,
visual: "Product pack with logo, sunburst",
voiceover: `${product.name}. ${product.tagline}.`,
emotion: "confident"
}
]
};
fs.writeFileSync('manifests/storyboard.json', JSON.stringify(storyboard, null, 2));
console.log('✓ Storyboard written');
Custom FFmpeg Transition
// scripts/render-keyframe-video.mjs (simplified)
import { execSync } from 'child_process';
import fs from 'fs';
const manifest = JSON.parse(fs.readFileSync('manifests/storyboard.json', 'utf-8'));
const keyframesDir = 'assets/keyframes';
const output = 'output/video-base.mp4';
const fps = 30;
let filterComplex = '';
let inputs = '';
manifest.scenes.forEach((scene, i) => {
const duration = scene.end - scene.start;
const kf = `${keyframesDir}/${scene.id}.png`;
inputs += `-loop 1 -t ${duration} -i ${kf} `;
if (i === 0) {
filterComplex += `[0:v]fade=t=in:st=0:d=0.5,fade=t=out:st=${duration - 0.5}:d=0.5[v0]; `;
} else {
filterComplex += `[${i}:v]fade=t=in:st=0:d=0.5,fade=t=out:st=${duration - 0.5}:d=0.5[v${i}]; `;
}
});
// Concatenate
const concatInputs = manifest.scenes.map((_, i) => `[v${i}]`).join('');
filterComplex += `${concatInputs}concat=n=${manifest.scenes.length}:v=1:a=0[outv]`;
const cmd = `ffmpeg ${inputs} -filter_complex "${filterComplex}" -map "[outv]" -r ${fps} -c:v libx264 -preset medium -crf 23 -pix_fmt yuv420p ${output}`;
execSync(cmd, { stdio: 'inherit' });
console.log(`✓ Video rendered: ${output}`);
IndexTTS-2 Synthesis Script
// scripts/narrate-indextts2.mjs (simplified)
import { execSync } from 'child_process';
import fs from 'fs';
import path from 'path';
const manifestPath = process.argv[2] || 'manifests/voice.indextts2.json';
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
const mlxIndexTTSPath = path.join(
process.env.HOME,
'.local/share/paper-collage-ad/mlx-indextts'
);
manifest.lines.forEach(line => {
const outFile = path.join(manifest.output_dir, `${line.id}.wav`);
const cmd = [
`cd ${mlxIndexTTSPath}`,
`source venv/bin/activate`,
`python -m mlx_indextts.synthesize`,
`--model models/mlx-indextts2-standard-fp16`,
`--speaker ${manifest.speaker}`,
`--text "${line.text}"`,
`--emotion ${line.emotion}`,
`--speed ${line.speed || 1.0}`,
`--output ${outFile}`,
`--sample-rate ${manifest.sample_rate}`
].join(' && ');
console.log(`Synthesizing ${line.id}...`);
execSync(cmd, { stdio: 'inherit', shell: '/bin/bash' });
});
console.log('✓ All voiceovers generated');
Audio Mixing with FFmpeg
// scripts/mix-audio.mjs (simplified)
import { execSync } from 'child_process';
import fs from 'fs';
const manifest = JSON.parse(fs.readFileSync('manifests/audio-mix.json', 'utf-8'));
let filterComplex = '';
let inputs = '';
manifest.tracks.forEach((track, i) => {
inputs += `-i ${track.file} `;
const vol = track.volume || 1.0;
const delay = (track.start || 0) * 1000; // ms
filterComplex += `[${i}:a]volume=${vol},adelay=${delay}|${delay}[a${i}]; `;
});
const mixInputs = manifest.tracks.map((_, i) => `[a${i}]`).join('');
filterComplex += `${mixInputs}amix=inputs=${manifest.tracks.length}:duration=longest[outa]`;
const cmd = `ffmpeg ${inputs} -filter_complex "${filterComplex}" -map "[outa]" -ar ${manifest.sample_rate} ${manifest.output}`;
execSync(cmd, { stdio: 'inherit' });
console.log(`✓ Audio mixed: ${manifest.output}`);
Common Patterns
Pattern: Multi-Scene Video from Keyframes
# 1. Generate storyboard
node generate-storyboard.mjs
# 2. Create keyframes (manual or AI)
# Save to assets/keyframes/01.png, 02.png...
# 3. Render video
node ~/.codex/skills/paper-collage-ad/scripts/render-keyframe-video.mjs \
--manifest manifests/storyboard.json \
--keyframes assets/keyframes \
--output output/video-base.mp4
# 4. Generate voiceovers
node ~/.codex/skills/paper-collage-ad/scripts/narrate-indextts2.mjs \
--manifest manifests/voice.indextts2.json
# 5. Mix audio
node ~/.codex/skills/paper-collage-ad/scripts/mix-audio.mjs \
--manifest manifests/audio-mix.json
# 6. Composite
ffmpeg -i output/video-base.mp4 -i output/audio-mix.wav \
-c:v libx264 -crf 20 -c:a aac -b:a 192k \
-movflags +faststart output/final.mp4
# 7. QC
bash ~/.codex/skills/paper-collage-ad/scripts/qc-mp4.sh output/final.mp4
Pattern: Voice Clone for Multiple Projects
Keep a single authorized voice model in a shared location:
# One-time voice model prep
bash ~/.codex/skills/paper-collage-ad/scripts/prepare-indextts2-voice.sh \
~/my-voice/reference.wav \
~/my-voice/speaker-v2.npz \
--i-have-permission
# In each project manifest, reference:
{
"speaker": "~/my-voice/speaker-v2.npz",
...
}
Pattern: Batch QC Multiple Ads
for mp4 in output/*.mp4; do
echo "Checking $mp4..."
bash ~/.codex/skills/paper-collage-ad/scripts/qc-mp4.sh "$mp4"
done
Troubleshooting
IndexTTS-2 Voice Sounds Robotic
- Check reference audio: Must be clean, 6–12s, single speaker, no background noise
- Increase sample rate: Use 48000 Hz in manifest
- Adjust speed: Try
"speed": 0.95for more natural pacing - Emotion mismatch: Ensure emotion tag matches spoken content
FFmpeg "Invalid duration" Error
- Check timecodes: Ensure
end > startin storyboard - Keyframe missing: Verify
assets/keyframes/XX.pngexists for each scene - Use absolute paths: If relative paths fail, try absolute
Video/Audio Out of Sync
- Sample rate mismatch: Ensure all audio is 48kHz before mixing
- Incorrect start times: Verify
startinaudio-mix.jsonmatches storyboard - Re-encode audio:
ffmpeg -i in.wav -ar 48000 out.wav
QC Script Reports "moov atom not at start"
- Add faststart flag:
ffmpeg -i input.mp4 -c copy -movflags +faststart output.mp4
Voice Clone Fails with "Permission Denied"
- Activate venv: Ensure script runs
source venv/bin/activate - Check paths: Verify
~/.local/share/paper-collage-ad/mlx-indextts/exists - Re-run setup:
bash ~/.codex/skills/paper-collage-ad/scripts/setup-indextts2-mlx.sh
Animation Looks Choppy
- Increase FPS: Use
--fps 60in render script - Check keyframe resolution: Ensure all PNGs are same size (e.g., 1920×1080)
- Use better codec: Add
-preset slowfor higher quality
References
- SKILL.md: Main workflow orchestration
- references/: Detailed guides for storyboard, visuals, animation, voice, music, QC
- examples/: Copyable JSON manifests and HTML templates
- scripts/: All executable Node.js and Bash utilities
Privacy & Security
- No API keys, voice samples, or brand assets included
.gitignoreexcludesvoice-reference/,voice-model/,voice-final/,.env- Before public release, run:
bash ~/.codex/skills/paper-collage-ad/scripts/privacy-check.sh
Voice Cloning Ethics: Only clone voices you own or have written permission to clone. Disclose AI generation in all deliverables.
License
MIT License. Third-party models, fonts, music, and APIs are subject to their own licenses. IndexTTS-2 model weights are not bundled.
Quick Start Summary:
- Install skill globally or locally
- Run
bash scripts/check-deps.sh - Create project structure with
assets/,manifests/,output/ - Generate storyboard → keyframes → video → voice → audio → composite → QC
- Use local IndexTTS-2 for voice cloning (Apple Silicon) or fallback to standard TTS
- Deliver privacy-compliant, stream-validated MP4
For full Chinese workflow, see WORKFLOW.zh-CN.md.
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/reason-machines/codex-skills/paper-collage-ad-codex">View paper-collage-ad-codex on skillZs</a>