{"skill":{"slug":"picasso-tiktok","displayName":"Picasso TikTok","summary":"Full TikTok/Reels video pipeline: script → TTS voiceover (ElevenLabs) → HeyGen talking avatar → auto-subtitles (Whisper) → ffmpeg compose → 1080x1920 final v...","description":"---\nname: picasso-tiktok\ndescription: |\n  Full TikTok/Reels video pipeline: script → TTS voiceover (ElevenLabs) → HeyGen talking avatar → auto-subtitles (Whisper) → ffmpeg compose → 1080x1920 final video. Also supports AI B-roll generation with Runway Gen-4.5 when no source video exists.\n  \n  ✅ USE WHEN:\n  - Creating TikTok or Instagram Reels from scratch with voiceover\n  - Turning a news article, topic, or script into a 9:16 video\n  - Adding a talking avatar to any video\n  - Generating AI B-roll clips (Runway Gen-4.5) and editing into a Reel\n  \n  ❌ DON'T USE WHEN:\n  - You only need an image (use nano-banana-pro)\n  - You only need TTS audio (use ElevenLabs directly)\n  \n  Required env vars: ELEVENLABS_API_KEY, ELEVENLABS_VOICE_ID, HEYGEN_API_KEY, YOUR_HEYGEN_AVATAR_ID, OPENAI_API_KEY, REPLICATE_API_TOKEN\n  Required system tools: ffmpeg, yt-dlp, Python 3\nversion: 1.2.0\n---\n\n# Picasso TikTok 🎨\n\nGenera videos 9:16 para TikTok/Reels combinando un video fuente + avatar HeyGen + subtítulos sincronizados.\n\n## ⚠️ FLUJO OBLIGATORIO — paso a paso con validación\n\n**NUNCA correr el pipeline completo de una sola vez. Siempre:**\n\n1. Descargar + analizar video → **mostrar duración e info**\n2. Escribir guión → **mostrar a Paul, esperar OK**\n3. Generar audio → **enviar para escuchar, esperar OK**\n4. Preguntar configuración del video (layout, música fuente) → **esperar OK**\n5. Generar avatar HeyGen\n6. Transcribir + corregir subtítulos contra guión original\n7. Componer video final\n\n---\n\n## Paso 1: Descargar video\n\n### Google Drive\n```bash\npip install gdown -q\ngdown \"https://drive.google.com/uc?id=FILE_ID&confirm=t\" -O output.mp4\n```\nSi falla (permisos): pedir a Paul que comparta \"cualquier persona con el link\" o mande por Telegram.\n\n### TikTok / YouTube\n```bash\nyt-dlp -o \"output.mp4\" \"URL\"\n```\n\n### Telegram (archivo adjunto)\nLos archivos adjuntos llegan a `~/.openclaw/media/inbound/`\n\n### Verificar\n```bash\nffprobe video.mp4 2>&1 | grep -E \"Duration|Video:|Audio:\"\n```\n\n---\n\n## Paso 2: Guión\n\n**Reglas:**\n- Español argentino rioplatense (voseo: \"grabás\", \"actualizás\", \"imaginá\")\n- Hook fuerte en los primeros 3 segundos\n- Dinámico, sin relleno\n- Sin notas de dirección, solo el texto que se lee\n- CTA al final (ej: \"sumate a Morfeo Labs\")\n- Duración objetivo: igual o levemente mayor que el video fuente\n\n**Mostrar guión y esperar aprobación antes de generar audio.**\n\n---\n\n## Paso 3: Audio — TTS\n\n### ✅ DEFAULT: ElevenLabs Paul Pro\n\n**SIEMPRE generar 3 variaciones de voz y enviar para que Paul elija antes de continuar.**\n\n```python\nimport requests, time\n\nCACHE = \"/home/ubuntu/clawd/projects/picasso-tiktok/cache/JOB_NAME\"\nBASE_URL = \"https://api.elevenlabs.io/v1/text-to-speech/$ELEVENLABS_VOICE_ID\"\nHEADERS = {\"xi-api-key\": \"$ELEVENLABS_API_KEY\", \"Content-Type\": \"application/json\"}\n\n# Variación A — expresivo, pausas fuertes\n# Variación B — DEFAULT ganador: guiones largos para pausas dramáticas (stability 0.45)\n# Variación C — fluido y periodístico, sin cortes (stability 0.62)\n\nconfigs = [\n    (\"A\", script_a, {\"stability\": 0.35, \"similarity_boost\": 0.80, \"style\": 0.25}),\n    (\"B\", script_b, {\"stability\": 0.45, \"similarity_boost\": 0.82, \"style\": 0.15}),  # ← ganador recurrente\n    (\"C\", script_c, {\"stability\": 0.62, \"similarity_boost\": 0.78, \"style\": 0.05}),\n]\n\nfor ver, text, settings in configs:\n    r = requests.post(BASE_URL, headers=HEADERS,\n        json={\"text\": text, \"model_id\": \"eleven_multilingual_v2\", \"voice_settings\": settings})\n    with open(f\"{CACHE}/audio_{ver}.mp3\", \"wb\") as f:\n        f.write(r.content)\n    print(f\"✅ V{ver} {len(r.content)//1024}KB\")\n    time.sleep(1)\n```\n\n**Trucos de puntuación para controlar ritmo:**\n- Frases separadas en párrafos propios → pausas naturales largas (VA)\n- Guiones largos `—` dentro de frases → pausa dramática mid-sentence (VB, ganador)\n- Todo junto sin cortes → fluido tipo documental (VC)\n\n**⚠️ IMPORTANTE:**\n- Modelo: `eleven_multilingual_v2` — NUNCA `eleven_v3` (cambia el acento)\n- Voice ID Paul Pro: `$ELEVENLABS_VOICE_ID`\n- API Key: `$ELEVENLABS_API_KEY`\n\n### Backup: Cartesia sonic-3\n```python\nr = requests.post(\"https://api.cartesia.ai/tts/bytes\",\n    headers={\"X-API-Key\": \"$CARTESIA_API_KEY\",\n             \"Cartesia-Version\": \"2025-04-16\", \"Content-Type\": \"application/json\"},\n    json={\"model_id\": \"sonic-3\",  # SIEMPRE sonic-3, nunca sonic-2\n          \"transcript\": SCRIPT,\n          \"voice\": {\"mode\": \"id\", \"id\": \"$CARTESIA_VOICE_ID\"},\n          \"language\": \"es\",\n          \"output_format\": {\"container\": \"mp3\", \"sample_rate\": 44100, \"bit_rate\": 128000}})\n```\n\n**Enviar audio para escuchar y esperar OK antes de continuar.**\n\n---\n\n## Paso 4: Preguntar configuración\n\nAntes de generar avatar y componer, confirmar:\n- **Layout:** 60/40 (source top), 50/50, 40/60 (avatar top)\n- **Subtítulos:** sí/no\n- **Audio fuente:** ¿mezclar música original? Si sí, ¿a qué volumen? (típico: 30%)\n- **Título para primeros segundos** (ej: \"Esta chica no existe 👁️\")\n- **Caption TikTok** con hashtags\n\n---\n\n## Paso 5: Avatar HeyGen\n\n### Subir audio a uguu.se (requerido por HeyGen)\n```python\nimport requests\n\nwith open(\"audio.mp3\", \"rb\") as f:\n    r = requests.post(\"https://uguu.se/upload\",\n        files={\"files[]\": (\"audio.mp3\", f.read(), \"audio/mpeg\")}, timeout=30)\naudio_url = r.json()[\"files\"][0][\"url\"]\n```\n\n### Generar video\n```python\nHEYGEN_KEY = \"$HEYGEN_API_KEY\"\nAVATAR_ID  = \"aa7ca06de7454b9caa147b97a534e813\"  # Paul default\n\nr = requests.post(\"https://api.heygen.com/v2/video/generate\",\n    headers={\"X-Api-Key\": HEYGEN_KEY, \"Content-Type\": \"application/json\"},\n    json={\n        \"video_inputs\": [{\n            \"character\": {\"type\": \"avatar\", \"avatar_id\": AVATAR_ID, \"avatar_style\": \"normal\"},\n            \"voice\": {\"type\": \"audio\", \"audio_url\": audio_url},\n            \"background\": {\"type\": \"color\", \"value\": \"#000000\"}\n        }],\n        \"dimension\": {\"width\": 432, \"height\": 768},  # 9:16 pequeño, escala mejor\n        \"aspect_ratio\": \"9:16\"\n    })\nvideo_id = r.json()[\"data\"][\"video_id\"]\n```\n\n### Poll hasta completar (~2-4 min)\n```python\nimport time\nwhile True:\n    r = requests.get(f\"https://api.heygen.com/v1/video_status.get?video_id={video_id}\",\n        headers={\"X-Api-Key\": HEYGEN_KEY})\n    data = r.json().get(\"data\") or {}\n    if data.get(\"status\") == \"completed\":\n        avatar_url = data[\"video_url\"]\n        break\n    time.sleep(15)\n```\n\n### Descargar y cropdetect\n```bash\ncurl -sL \"$AVATAR_URL\" -o avatar.mp4\n\n# Auto-detect crop (quita padding negro de HeyGen)\nffmpeg -ss 2 -i avatar.mp4 -vframes 10 -vf cropdetect=24:2:0 -f null - 2>&1 | grep crop= | tail -2\n# Típico resultado: crop=432:244:0:262\n```\n\n---\n\n## Paso 6: Subtítulos — SIEMPRE contrastar con guión original\n\n### Transcribir con Whisper\n```python\nimport requests, os\n\nwith open(\"audio.mp3\", \"rb\") as f:\n    r = requests.post(\"https://api.openai.com/v1/audio/transcriptions\",\n        headers={\"Authorization\": f\"Bearer {os.environ['OPENAI_API_KEY']}\"},\n        files={\"file\": (\"audio.mp3\", f, \"audio/mpeg\")},\n        data={\"model\": \"whisper-1\", \"response_format\": \"verbose_json\",\n              \"timestamp_granularities[]\": \"word\", \"language\": \"es\"})\nwords = r.json()[\"words\"]\n```\n\n### ⚠️ SIEMPRE revisar y corregir contra el guión original\n\nWhisper comete errores frecuentes en español/rioplatense + términos técnicos:\n\n| Whisper escribe | Correcto |\n|-----------------|----------|\n| Cling | KLING |\n| Confi / Confy | COMFY |\n| Imagina | IMAGINÁ |\n| Grabas | GRABÁS |\n| Actualizas | ACTUALIZÁS |\n| Buscas | BUSCÁS |\n| Preparas | PREPARÁS |\n| I A | IA |\n| cualquier nombre de marca | verificar siempre |\n\nCorregir en el .ass antes de renderizar:\n```python\nfixes = [(\"CLING\", \"KLING\"), (\"CONFI\", \"COMFY\"), (\"IMAGINA\", \"IMAGINÁ\"), ...]\nfor wrong, right in fixes:\n    ass = ass.replace(wrong, right)\n```\n\n### Generar .ass\n```python\nimport sys\nsys.path.insert(0, \"/home/ubuntu/clawd/projects/picasso-tiktok/picasso-api/workers\")\nfrom subtitles import generate_ass\n\nass = generate_ass(words)\n# Aplicar correcciones de marca/voseo\n# Aplicar MarginV según layout (ver tabla abajo)\nwith open(\"subs.ass\", \"w\") as f:\n    f.write(ass)\n```\n\n### MarginV según layout\n\n| Layout | Top px | Bot px | MarginV recomendado |\n|--------|--------|--------|---------------------|\n| 60/40  | 1152   | 768    | 720                 |\n| 50/50  | 960    | 960    | 880                 |\n| 40/60  | 768    | 1152   | ~1000               |\n\nParchear en el .ass:\n```python\nimport re\nass = re.sub(\n    r'(Style: Word,\\S+,\\d+,\\S+,\\S+,\\S+,\\S+,-?\\d+,\\d+,\\d+,\\d+,\\d+,\\d+,\\d+,\\d+,\\d+,[\\d.]+,[\\d.]+,\\d+,\\d+,\\d+,)\\d+,',\n    lambda m: m.group(1) + str(MARGIN_V) + ',', ass)\n```\n\n---\n\n## Paso 7: Componer video final\n\n### Splits de altura\n\n| Layout | top_h | bot_h |\n|--------|-------|-------|\n| 60/40  | 1152  | 768   |\n| 50/50  | 960   | 960   |\n| 40/60  | 768   | 1152  |\n\n### Filter chain base (sin mezcla de audio)\n```bash\nffmpeg -y \\\n  -i source.mp4 -i avatar.mp4 -i audio.mp3 \\\n  -filter_complex \"\n    [0:v]scale=1080:{top_h}:force_original_aspect_ratio=increase,crop=1080:{top_h},setsar=1[top];\n    [top]pad=1080:1920:0:0:black[bg];\n    [1:v]crop={crop_str},scale=1080:{bot_h}:force_original_aspect_ratio=increase,crop=1080:{bot_h},setsar=1[av];\n    [bg][av]overlay=0:{top_h}[ov];\n    [ov]ass=subs.ass[final]\n  \" \\\n  -map \"[final]\" -map \"2:a\" \\\n  -c:v libx264 -profile:v high -level 4.0 -crf 23 \\\n  -c:a aac -b:a 192k -movflags +faststart -shortest \\\n  output.mp4\n```\n\n### Con mezcla de audio (música fuente al X%)\n```bash\n  -filter_complex \"\n    [0:v]...[ov];\n    [ov]ass=subs.ass[final];\n    [0:a]volume=0.3[src_a];\n    [2:a]volume=1.0[tts_a];\n    [src_a][tts_a]amix=inputs=2:duration=shortest[mix_a]\n  \" \\\n  -map \"[final]\" -map \"[mix_a]\" \\\n```\n\n### ⚠️ NUNCA estirar el avatar\n\n```bash\n# ✅ CORRECTO: scale to fill + crop (sin deformación, sin negro)\n[1:v]crop={crop_str},scale=1080:{bot_h}:force_original_aspect_ratio=increase,crop=1080:{bot_h},setsar=1[av]\n\n# ❌ MAL: escala directa deforma\n[1:v]crop={crop_str},scale=1080:{bot_h},setsar=1[av]\n\n# ❌ MAL: pad con negro (Paul no quiere franjas negras)\n[1:v]crop={crop_str},scale=1080:-2,pad=1080:{bot_h}:...,setsar=1[av]\n```\n\n---\n\n## Output final\n\n- Resolución: 1080x1920\n- Codec: H.264 high profile level 4.0\n- Audio: AAC 192k\n- `-movflags +faststart`\n\n## Layouts adicionales\n\n### 2/3 top + 1/3 bottom (Chiqui Tapia, Claude Opus style)\n```\ntop_h=1280, bot_h=640, MarginV=640\n```\n```bash\n[0:v]scale=1080:1280:force_original_aspect_ratio=increase,crop=1080:1280,setsar=1[top];\n[top]pad=1080:1920:0:0:black[bg];\n[1:v]crop=432:240:0:264,scale=1080:640:force_original_aspect_ratio=increase,crop=1080:640,setsar=1[av];\n[bg][av]overlay=0:1280[ov]\n```\n\n### Video fuente que debe verse ENTERO (letterbox, sin recorte)\n```bash\n# Usar decrease + pad en vez de increase + crop\n[0:v]scale=1080:{top_h}:force_original_aspect_ratio=decrease,pad=1080:{top_h}:(ow-iw)/2:(oh-ih)/2:black,setsar=1[top]\n```\n\n### Loop de video fuente cuando el audio es más largo\n```bash\nAUDIO_DUR=$(ffprobe -v quiet -show_entries format=duration -of csv=p=0 audio.mp3)\nffmpeg -y -stream_loop 3 -i source.mp4 -t $AUDIO_DUR \\\n  -c:v libx264 -preset fast -crf 18 -c:a aac source_looped.mp4\n```\n\n### Recorte del inicio del video fuente\n```bash\n# Agregar -ss SEGUNDOS antes del -i source.mp4 en el loop\nffmpeg -y -ss 1.25 -stream_loop 3 -i source.mp4 -t $AUDIO_DUR ...\n```\n\n---\n\n## B-roll generado con IA — Runway Gen-4.5\n\nCuando no hay video fuente real, generar B-roll con Runway Gen-4.5 via Replicate.\n\n**Variables:**\n- `prompt` — descripción cinematográfica del clip\n- `image` — frame inicial opcional (para image-to-video)\n- `duration` — segundos (default 5, máximo 10)\n- `aspect_ratio` — default `16:9`; usar `768:1344` para 9:16 vertical\n\n**Costo:** ~$0.05 por clip de 10s\n\n```python\nimport requests, os, time\n\nr = requests.post(\"https://api.replicate.com/v1/models/runwayml/gen-4.5/predictions\",\n    headers={\"Authorization\": f\"Token {os.environ['REPLICATE_API_TOKEN']}\", \"Content-Type\": \"application/json\"},\n    json={\"input\": {\n        \"prompt\": \"DESCRIPCION_CINEMATOGRAFICA\",\n        \"duration\": 10,\n        \"ratio\": \"768:1344\"  # vertical 9:16\n    }})\npred_id = r.json()[\"id\"]\n\n# Poll\nwhile True:\n    r = requests.get(f\"https://api.replicate.com/v1/predictions/{pred_id}\",\n        headers={\"Authorization\": f\"Token {os.environ['REPLICATE_API_TOKEN']}\"})\n    status = r.json()[\"status\"]\n    if status == \"succeeded\":\n        url = r.json()[\"output\"]\n        if isinstance(url, list): url = url[0]\n        # descargar...\n        break\n    time.sleep(15)\n```\n\n**Workflow para videos con guión largo:**\n1. Generar audio TTS primero → obtener duración total\n2. Dividir guión en segmentos temáticos con timestamps aproximados\n3. Asignar duración a cada clip de Runway (suma debe cubrir la duración total)\n4. Generar todos los clips en paralelo (lanzar predicciones, luego poll)\n5. Concatenar clips con ffmpeg, usar como source en Picasso\n\n---\n\n## Checklist antes de entregar\n\n- [ ] Guión aprobado por Paul\n- [ ] 3 variaciones de audio enviadas — Paul elige antes de continuar\n- [ ] Layout confirmado por Paul\n- [ ] Subtítulos corregidos contra guión original (errores de Whisper fixeados)\n- [ ] Avatar sin deformación (AR mantenido)\n- [ ] Audio fuente mezclado al volumen correcto (si aplica)\n- [ ] Video es 1080x1920 verificado con ffprobe\n","tags":{"latest":"1.2.0"},"stats":{"comments":0,"downloads":701,"installsAllTime":1,"installsCurrent":1,"stars":0,"versions":1},"createdAt":1773348070978,"updatedAt":1778998609652},"latestVersion":{"version":"1.2.0","createdAt":1773348070978,"changelog":"v1.2: ElevenLabs 3-variation flow, Runway Gen-4.5 B-roll, image-to-video con Nano Banana Pro + animación con Gen-4.5, layouts avanzados, correcciones de Whisper para voseo rioplatense, checklist completo","license":"MIT-0"},"metadata":null,"owner":{"handle":"pauldelavallaz","userId":"s17es8yqc61443cxz7kbvr73rs884ggf","displayName":"Paul de Lavallaz","image":"https://avatars.githubusercontent.com/u/51497690?v=4"},"moderation":null}