Arcads AI Video Generator
Skill by ara.so — Claude Code Skills collection.
Create AI marketing videos and images using the Arcads API. Supports the full creative stack: Seedance 2.0 (flagship video), Sora 2, Veo 3.1, Kling 3.0, Grok Video, Nano Banana 2/Pro/Edit, ChatGPT Image 2, OmniHuman, Audio-driven models, plus a 37-template static Meta image-ad library and multi-step pipelines for Pixar-style and claymation animated ads.
Installation
git clone https://github.com/krusemediallc/arcads-claude-code.git
cd arcads-claude-code
./scripts/setup.sh
The setup script will:
- Prompt for your Arcads API key (get it at app.arcads.ai/settings/api)
- Save credentials to
.env - Verify API connection
- Create
MASTER_CONTEXT.mdworkspace file
Prerequisites
| Tool | Required for | Install |
|---|---|---|
| Python 3.10+ | Core API operations | brew install python@3.12 |
ffmpeg |
Video stitching, chroma-key | brew install ffmpeg |
jq |
JSON parsing in bash scripts | brew install jq |
| Node.js | Caption burn-in (hyperframes) | brew install node |
whisper |
Caption transcription | pip install openai-whisper |
Environment Setup
Create .env in the project root:
ARCADS_API_KEY=your_api_key_here
Core API Patterns
Video Generation (Seedance 2.0)
Endpoint: POST https://api.arcads.ai/v1/seedance2/video
import requests
import os
import time
API_KEY = os.getenv('ARCADS_API_KEY')
BASE_URL = 'https://api.arcads.ai'
def generate_seedance_video(prompt, duration=12):
"""Generate a Seedance 2.0 video."""
headers = {
'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json'
}
payload = {
'prompt': prompt,
'duration': duration,
'shotStyle': 'ugc-selfie' # or 'premium-reveal', 'product-hero', etc.
}
# Start generation
response = requests.post(
f'{BASE_URL}/v1/seedance2/video',
headers=headers,
json=payload
)
job = response.json()
job_id = job['id']
# Poll until complete
while True:
status_response = requests.get(
f'{BASE_URL}/v1/jobs/{job_id}',
headers=headers
)
status = status_response.json()
if status['status'] == 'completed':
return status['videoUrl']
elif status['status'] == 'failed':
raise Exception(f"Generation failed: {status.get('error')}")
time.sleep(5)
# Example: UGC product review
video_url = generate_seedance_video(
prompt="""
[SCENE: NATURAL KITCHEN LIGHTING]
Medium shot, slight handheld motion.
Woman, early 30s, messy bun, casual tank top.
Holding [PRODUCT] at chest level, genuine smile.
[DIALOGUE]
"Okay so I used to buy [COMPETITOR] every month but then I found THIS—"
*lifts product slightly, direct eye contact*
"—and honestly? I'm never going back."
[VISUAL BEATS]
0-3s: Establishing shot, product reveal
3-7s: Close on face during key claim
7-12s: Pull back, casual product demo gesture
""",
duration=12
)
print(f"Video ready: {video_url}")
Seedance 2.0 Prompt Formulas
Five battle-tested formulas ship with the skill:
1. UGC Selfie-Style Review
[SCENE: NATURAL KITCHEN LIGHTING]
Medium shot, slight handheld motion.
Woman, early 30s, messy bun, casual tank top.
Holding [PRODUCT] at chest level, genuine smile.
[DIALOGUE]
"Okay so I used to buy [COMPETITOR] every month..."
[VISUAL BEATS]
0-3s: Establishing shot
3-7s: Close on face
7-12s: Product demo gesture
2. Premium Product Reveal (No Person)
[SCENE: DARK VOID]
No human. Product only.
Black background, dramatic side lighting.
[TEXT OVERLAYS]
Beat 1 (0-3s): "Most [CATEGORY] products..."
Beat 2 (3-6s): "[PRODUCT] is different."
Beat 3 (6-9s): Hero rotation + key feature callout
[CAMERA]
Slow push-in, 360° rotation at beat 3
3. Product Hero with Elements
[SCENE: ELEMENTAL SHOWCASE]
Product suspended mid-frame.
Water splash from below, mist rising.
Slow rotation (15°/sec).
[VISUAL FX]
0-2s: Water splash entry
2-5s: Mist buildup
5-8s: Light rays piercing mist
8-12s: Hero rotation + feature close-up
Image Generation (Nano Banana)
Endpoint: POST https://api.arcads.ai/v1/nano-banana/image
import base64
def generate_nano_banana_image(prompt, reference_image_path=None, model='nano-banana-2'):
"""Generate a Nano Banana image with optional reference."""
headers = {
'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json'
}
payload = {
'prompt': prompt,
'model': model, # 'nano-banana-2' or 'nano-banana' (Pro) or 'nano-banana-edit'
'aspectRatio': '9:16'
}
# Add reference image if provided
if reference_image_path:
with open(reference_image_path, 'rb') as f:
img_base64 = base64.b64encode(f.read()).decode('utf-8')
payload['refImageAsBase64'] = img_base64
response = requests.post(
f'{BASE_URL}/v1/nano-banana/image',
headers=headers,
json=payload
)
job = response.json()
job_id = job['id']
# Poll for completion
while True:
status_response = requests.get(
f'{BASE_URL}/v1/jobs/{job_id}',
headers=headers
)
status = status_response.json()
if status['status'] == 'completed':
return status['imageUrl']
elif status['status'] == 'failed':
raise Exception(f"Generation failed: {status.get('error')}")
time.sleep(3)
# Example: UGC selfie with product
image_url = generate_nano_banana_image(
prompt="""
iPhone selfie camera, natural bedroom lighting.
Woman, 22, freckles, golden hour glow.
Holding [PRODUCT] at chest level, genuine smile.
Messy hair, no makeup, casual tank top.
Slight camera shake, realistic skin texture.
Background: unmade bed, string lights, plants.
""",
reference_image_path='references/influencers/sofia_hero.jpg'
)
Veo 3.1 (Start-Frame Animation)
Endpoint: POST https://api.arcads.ai/v1/veo3/video
def animate_with_veo(start_frame_path, prompt, duration=8):
"""Animate a still image with Veo 3.1."""
headers = {
'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json'
}
# Load start frame
with open(start_frame_path, 'rb') as f:
img_base64 = base64.b64encode(f.read()).decode('utf-8')
payload = {
'prompt': prompt,
'startFrame': img_base64,
'duration': duration,
'dialogue': 'I switched to this product and never looked back'
}
response = requests.post(
f'{BASE_URL}/v1/veo3/video',
headers=headers,
json=payload
)
job = response.json()
return poll_job(job['id'])
def poll_job(job_id):
"""Generic job polling."""
headers = {'Authorization': f'Bearer {API_KEY}'}
while True:
response = requests.get(
f'{BASE_URL}/v1/jobs/{job_id}',
headers=headers
)
status = response.json()
if status['status'] == 'completed':
return status.get('videoUrl') or status.get('imageUrl')
elif status['status'] == 'failed':
raise Exception(f"Job failed: {status.get('error')}")
time.sleep(5)
Sora 2 Video Generation
Endpoint: POST https://api.arcads.ai/v1/sora2/video
def generate_sora_video(prompt, duration=16, reference_image_path=None):
"""Generate a Sora 2 video (up to 20s)."""
headers = {
'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json'
}
payload = {
'prompt': prompt,
'duration': duration
}
if reference_image_path:
with open(reference_image_path, 'rb') as f:
img_base64 = base64.b64encode(f.read()).decode('utf-8')
payload['styleReference'] = img_base64
response = requests.post(
f'{BASE_URL}/v1/sora2/video',
headers=headers,
json=payload
)
return poll_job(response.json()['id'])
# Example: Longer narrative scene
video_url = generate_sora_video(
prompt="""
Cozy coffee shop interior, warm afternoon light.
Camera tracks across table as woman opens laptop.
Smooth dolly shot, shallow depth of field.
Steam rising from coffee mug in foreground.
Natural color grading, 24fps cinematic feel.
""",
duration=16
)
B-Roll / Scene Generation (Kling 3.0)
Endpoint: POST https://api.arcads.ai/v1/b-roll or POST https://api.arcads.ai/v1/scene
def generate_broll(prompt, duration=5):
"""Generate b-roll footage with Kling 3.0."""
headers = {
'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json'
}
payload = {
'prompt': prompt,
'duration': duration
}
response = requests.post(
f'{BASE_URL}/v1/b-roll',
headers=headers,
json=payload
)
return poll_job(response.json()['id'])
# Example: Product environment b-roll
broll_url = generate_broll(
prompt="""
Close-up of coffee beans being poured into grinder.
Slow motion, 120fps.
Natural light from window, wooden countertop.
Shallow focus on falling beans.
"""
)
Static Meta Image Ad Library
The repo includes 37 validated prompt templates for static Meta image ads. Two primary generators:
ChatGPT Image 2 (Typography/UI-Heavy)
Best for: Apple Notes lists, editorial layouts, comparison tables, UI mockups, text-heavy designs
def generate_chatgpt_image_ad(template_name, product_details):
"""Generate static ad using ChatGPT Image 2."""
# Load template from library
template_path = f'shared/skills/image-ad-prompting/library/{template_name}.md'
with open(template_path, 'r') as f:
template = f.read()
# Fill template with product details
prompt = template.format(**product_details)
headers = {
'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json'
}
payload = {
'prompt': prompt,
'model': 'gpt-image-2',
'aspectRatio': '4:5' # Standard Meta image ad ratio
}
response = requests.post(
f'{BASE_URL}/v1/chatgpt-image/generate',
headers=headers,
json=payload
)
return poll_job(response.json()['id'])
# Example: Apple Notes style ad
image_url = generate_chatgpt_image_ad(
template_name='apple-notes-list',
product_details={
'product_name': 'FocusFlow',
'category': 'productivity app',
'benefits': [
'Blocks distractions automatically',
'AI suggests optimal focus times',
'Syncs across all devices'
],
'cta': 'Download free today'
}
)
Nano Banana (Photoreal/Lifestyle)
Best for: Lifestyle product shots, before/after, founder letters with portraits, real-world settings
def generate_nano_banana_ad(template_name, product_details, reference_images=None):
"""Generate static ad using Nano Banana."""
template_path = f'shared/skills/image-ad-prompting/library/{template_name}.md'
with open(template_path, 'r') as f:
template = f.read()
prompt = template.format(**product_details)
headers = {
'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json'
}
payload = {
'prompt': prompt,
'model': 'nano-banana-2',
'aspectRatio': '4:5'
}
# Add reference images if provided
if reference_images:
ref_images_b64 = []
for img_path in reference_images:
with open(img_path, 'rb') as f:
ref_images_b64.append(base64.b64encode(f.read()).decode('utf-8'))
payload['referenceImages'] = ref_images_b64
response = requests.post(
f'{BASE_URL}/v1/nano-banana/image',
headers=headers,
json=payload
)
return poll_job(response.json()['id'])
Available Templates (37 total)
Key templates in the library:
apple-notes-list— Simple checklist aestheticforbes-editorial— Magazine hero layoutcomparison-table— Side-by-side feature gridfake-google-search— Search results UI mockupsticky-note-flatlay— Scattered notes on deskimessage-screenshot— Text conversation mockupchatgpt-conversation— AI chat interfaceweather-forecast-ui— Weather app parodydating-app-card— Swipe-style card layoutbillboard-mockup— Outdoor advertising placementmuseum-exhibit— Art gallery presentationscratch-off-ticket— Lottery/game aesthetic
See shared/skills/image-ad-prompting/OVERVIEW.md for full template list and compatibility matrix.
Multi-Step Pipelines
Pixar-Style 3D Animated Ad
Creates an 8-beat story arc with anthropomorphized characters, stitched into a final video with captions.
Workflow:
- Define character sheet (mascot design)
- Generate storyboard stills (ChatGPT Image 2)
- Animate each beat (Seedance 2.0 i2v)
- Stitch segments (ffmpeg)
- Burn captions (HyperFrames)
# Run the full pipeline
./shared/skills/pixar-style-ad/pipeline.sh \
--product "EcoBottle" \
--duration 60 \
--output output/pixar-ad.mp4
Python API approach:
def create_pixar_ad(product_name, story_beats):
"""Generate a Pixar-style animated ad."""
# Step 1: Generate character design
character_prompt = f"""
3D Pixar style mascot for {product_name}.
Anthropomorphized product with expressive face.
Big eyes, friendly smile, rounded shapes.
Soft lighting, vibrant colors, clean geometry.
T-pose reference, white background.
"""
character_img = generate_chatgpt_image(character_prompt)
# Step 2: Generate storyboard frames
storyboard_frames = []
prev_frame = character_img
for beat in story_beats:
frame_prompt = f"""
{beat['scene_description']}
Character from reference image.
Pixar 3D render style, consistent lighting.
"""
frame_url = generate_chatgpt_image(
prompt=frame_prompt,
reference_images=[prev_frame]
)
storyboard_frames.append(frame_url)
prev_frame = frame_url
# Step 3: Animate each frame with Seedance 2.0
video_segments = []
for i, frame in enumerate(storyboard_frames):
video_url = animate_with_seedance_i2v(
start_frame=frame,
duration=story_beats[i]['duration'],
motion_prompt=story_beats[i]['motion']
)
video_segments.append(video_url)
# Step 4: Stitch (requires ffmpeg)
final_video = stitch_videos(video_segments)
return final_video
def animate_with_seedance_i2v(start_frame, duration, motion_prompt):
"""Animate a still frame with Seedance 2.0 image-to-video."""
headers = {
'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json'
}
# Download and encode start frame
import requests as req
img_data = req.get(start_frame).content
img_base64 = base64.b64encode(img_data).decode('utf-8')
payload = {
'startFrame': img_base64,
'motionPrompt': motion_prompt,
'duration': duration
}
response = requests.post(
f'{BASE_URL}/v1/seedance2/video',
headers=headers,
json=payload
)
return poll_job(response.json()['id'])
# Example usage
pixar_ad = create_pixar_ad(
product_name='EcoBottle',
story_beats=[
{
'scene_description': 'Mascot looking sad at pile of plastic waste',
'duration': 6,
'motion': 'Subtle head turn, eyes downcast'
},
{
'scene_description': 'Mascot has idea, lightbulb appears',
'duration': 4,
'motion': 'Excited jump, arms raised'
},
{
'scene_description': 'Mascot presents EcoBottle to camera',
'duration': 8,
'motion': 'Slow rotation of bottle, proud smile'
}
# ... 5 more beats
]
)
AI Influencer Creation (10-Image Character Sheet)
Generates a consistent AI influencer with multiple angles for reuse.
def create_ai_influencer(description):
"""Create a 10-image character sheet for an AI influencer."""
# Step 1: Generate hero front portrait
hero_prompt = f"""
Front-facing portrait, iPhone selfie camera.
{description}
Natural lighting, genuine smile.
9:16 aspect ratio, eye-level camera.
Realistic skin texture, no AI smoothing.
"""
hero_url = generate_nano_banana_image(
prompt=hero_prompt,
model='nano-banana' # Use Pro for tighter identity lock
)
print(f"Hero generated: {hero_url}")
print("Review and approve before generating additional angles...")
# Wait for user approval (in agent context)
input("Press Enter when approved...")
# Step 2: Generate 9 additional angles
angles = [
('3/4 view, slight smile, looking slightly left', 3),
('Profile view, side lighting', 3),
('Close-up of face, neutral expression', 2),
('Full body shot, standing pose', 1),
('Laughing expression, candid moment', 2),
('Serious expression, professional', 2)
# ... 3 more angles
]
character_sheet = [hero_url]
for angle_desc, count in angles:
for _ in range(count):
angle_prompt = f"""
{description}
{angle_desc}
Match character from reference image exactly.
Same lighting style, same person.
"""
img_url = generate_nano_banana_image(
prompt=angle_prompt,
reference_image_path=download_temp(hero_url),
model='nano-banana' # Pro for consistency
)
character_sheet.append(img_url)
# Save to references folder
save_character_sheet('sofia', character_sheet)
return character_sheet
# Example
influencer = create_ai_influencer(
description="""
Woman, 24 years old, warm smile.
Light freckles, wavy brown hair.
College student aesthetic.
Natural makeup, approachable energy.
"""
)
Caption Burn-In Workflow
Add captions to any finished video using Whisper + HyperFrames.
# Requires: pip install openai-whisper, npm install -g hyperframes
./shared/skills/caption-video/burn_captions.sh \
--input output/video.mp4 \
--output output/video_captioned.mp4 \
--style "viral"
Python implementation:
import whisper
import subprocess
import json
def burn_captions(video_path, output_path, style='viral'):
"""Add captions to video using Whisper + HyperFrames."""
# Step 1: Transcribe with Whisper
model = whisper.load_model("medium.en")
result = model.transcribe(video_path, word_timestamps=True)
# Step 2: Group words into reading phrases
phrases = group_into_phrases(result['segments'])
# Step 3: Generate HyperFrames caption JSON
caption_json = generate_hyperframes_config(phrases, style)
with open('captions_config.json', 'w') as f:
json.dump(caption_json, f)
# Step 4: Render with HyperFrames
subprocess.run([
'npx', 'hyperframes',
'--input', video_path,
'--config', 'captions_config.json',
'--output', output_path
])
return output_path
def group_into_phrases(segments, max_words=4):
"""Group word-level timestamps into reading phrases."""
phrases = []
current_phrase = []
for segment in segments:
for word in segment.get('words', []):
current_phrase.append(word)
if len(current_phrase) >= max_words or word['word'].endswith(('.', '!', '?')):
phrases.append({
'text': ' '.join([w['word'] for w in current_phrase]),
'start': current_phrase[0]['start'],
'end': current_phrase[-1]['end']
})
current_phrase = []
return phrases
def generate_hyperframes_config(phrases, style):
"""Generate HyperFrames caption configuration."""
styles = {
'viral': {
'font': 'Montserrat-ExtraBold',
'fontSize': 72,
'color': '#FFFFFF',
'stroke': '#000000',
'strokeWidth': 6,
'position': 'center'
},
'minimal': {
'font': 'Helvetica-Neue',
'fontSize': 48,
'color': '#FFFFFF',
'stroke': 'none',
'position': 'bottom'
}
}
return {
'captions': phrases,
'style': styles.get(style, styles['viral'])
}
Configuration
API Base URL
Default: https://api.arcads.ai
Override via environment variable:
export ARCADS_API_BASE_URL=https://custom-endpoint.com
Model Selection
# Video models
SEEDANCE_2 = 'seedance-2'
SORA_2 = 'sora-2'
VEO_3_1 = 'veo-3.1'
KLING_3 = 'kling-3.0'
GROK_VIDEO = 'grok-video'
# Image models
NANO_BANANA_2 = 'nano-banana-2' # Standard
NANO_BANANA_PRO = 'nano-banana' # Gemini 3 Pro (higher fidelity)
NANO_BANANA_EDIT = 'nano-banana-edit' # Inpainting
CHATGPT_IMAGE_2 = 'gpt-image-2'
Aspect Ratios
ASPECT_RATIOS = {
'vertical': '9:16', # Stories, Reels
'square': '1:1', # Feed posts
'meta-image': '4:5', # Meta image ads
'landscape': '16:9' # YouTube, horizontal
}
Duration Limits
DURATION_LIMITS = {
'seedance-2': (4, 15),
'sora-2': (1, 20),
'veo-3.1': (4, 15),
'kling-3.0': (1, 10),
'grok-video': (1, 10)
}
Common Patterns
Pattern: UGC Workflow (Still → Video)
def ugc_workflow(influencer_ref, product_ref, script):
"""Complete UGC workflow: still generation → video animation."""
# Step 1: Generate UGC still
still_prompt = f"""
iPhone selfie camera, natural bedroom lighting.
{influencer_ref['description']}
Holding product at chest level, genuine smile.
Messy hair, casual tank top.
Background: unmade bed, string lights.
"""
still_url = generate_nano_banana_image(
prompt=still_prompt,
reference_image_path=influencer_ref['hero_path']
)
print(f"Still generated: {still_url}")
input("Review and approve...")
# Step 2: Animate with Veo 3.1
video_url = animate_with_veo(
start_frame_path=download_temp(still_url),
prompt=f"""
Natural head and hand movement.
Maintains eye contact with camera.
Slight smile, authentic energy.
Product stays in frame throughout.
""",
duration=12
)
return video_url
Pattern: Batch Generation
def batch_generate_variations(base_prompt, variations, model='nano-banana-2'):
"""Generate multiple variations in parallel."""
import concurrent.futures
def generate_variation(variation):
full_prompt = f"{base_prompt}\n\nVariation: {variation}"
return generate_nano_banana_image(full_prompt, model=model)
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
urls = list(executor.map(generate_variation, variations))
return urls
# Example: Generate 6 thumbnail variations
thumbnails = batch_generate_variations(
base_prompt="""
YouTube thumbnail, 16:9 aspect ratio.
Man, 30s, excited expression, peace sign.
Product held in other hand.
Bold text overlay: "THIS CHANGED EVERYTHING"
High contrast, vibrant colors.
""",
variations=[
'Blue background, indoor studio lighting',
'Yellow background, warm tone',
'Outdoor natural light, green background',
'Split-screen comparison layout',
'Close-up face, product in foreground',
'Full body shot, product placement emphasized'
]
)
Pattern: Cost Estimation
COST_TABLE = {
'seedance-2': 0.25, # per second
'sora-2': 0.30,
'veo-3.1': 0.20,
'kling-3.0': 0.15,
'nano-banana-2': 0.08, # per image
'nano-banana': 0.12, # Pro
'gpt-image-2': 0.10
}
def estimate_cost(model, duration=None, image_count=None):
"""Estimate generation cost."""
unit_cost = COST_TABLE.get(model, 0)
if duration:
return unit_cost * duration
elif image_count:
return unit_cost * image_count
return unit_cost
# Example
cost = estimate_cost('seedance-2', duration=12)
print(f"Estimated cost: ${cost:.2f}")
user_input = input(f"Proceed with generation? (${cost:.2f}) [y/N]: ")