Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Zoen-DEV/repurpose-youtube-video/llms.txt
Use this file to discover all available pages before exploring further.
Every post includes a visual by default. If you pass images: in the command, those files are used as-is. Otherwise, the skill generates images with Freepik Mystic and applies text overlays using Pillow via image_overlay.py. All rendered images are uploaded to Blotato before the approval block is shown, so the URLs in Step 6 are stable and ready to publish.
Each platform has a fixed format and dimensions.
LinkedIn
- One image, aspect ratio 4:5 (1080×1350 px)
- A hook overlay is rendered at the bottom of the image — a distilled version of the post’s first line, 8–12 words — so the image stops the scroll before a viewer reads the caption.
Instagram — single image
- One image, aspect ratio 1:1 (1080×1080 px)
- A title overlay is rendered at the bottom of the image — a distilled hook of 8–10 words.
Instagram — carousel
- Three images, all 1:1 (1080×1080 px), with fixed roles:
| Slide | Role | Overlay content |
|---|
| 1 | Hook | Large centered title (max 10 words) + “Desliza →” (ES) or “Swipe →” (EN) |
| 2 | Info | Small heading (“CLAVES” / “KEY POINTS”) + 3–5 short bullet points |
| 3 | Credits | ”VIDEO ORIGINAL” / “ORIGINAL VIDEO” + video title + channel name + “Link en bio 🔗” / “Link in bio 🔗“ |
Image generation with Freepik Mystic
Images are generated by calling fp.generate_image():
def generate_image(
prompt: str,
*,
api_key: str,
aspect_ratio: str = "square_1_1",
model: str = "realism",
) -> list[str]:
For carousels, fp.generate_carousel() calls generate_image() sequentially for each prompt, skipping any slide that fails without breaking the rest.
Prompt rules
Claude writes all prompts in English — Mystic produces better results with English descriptions. Every prompt follows these constraints:
- No text requested. Mystic cannot render copy reliably. The modifier
"no text, no typography, no logos, no watermarks" is appended to every prompt.
- Negative space for overlays. Prompts specify where the overlay will land:
- LinkedIn 4:5:
"composition with negative space at the bottom for overlay text"
- Instagram single:
"composition with negative space at the bottom for overlay text"
- Carousel slide 1: centered composition
- Carousel slide 3: simple, low-saturation composition
- Under 300 characters per prompt. Prompts describe a concrete scene — style, lighting, composition, palette — not an abstract idea.
- Content-related. Each prompt is derived from the video’s
title, transcript, keyPoints, and chapters so the visual relates to what the video is actually about.
Aspect ratios
| Platform | aspect_ratio value |
|---|
| Instagram (single or carousel) | "square_1_1" |
| LinkedIn | "social_post_4_5" |
Text overlay with Pillow (image_overlay.py)
image_overlay.py exposes five renderer functions. Each downloads the base image from the Mystic URL, crops it to the target size, applies a gradient for readability, renders text with Pillow, and returns PNG bytes.
ov.render_hook(base_url, title, *, lang="es") -> bytes
Instagram carousel slide 1. Large centered title with automatic font scaling if the text overflows, plus “Desliza →” or “Swipe →” at the bottom.
ov.render_info(base_url, body_lines, *, lang="es", heading=None) -> bytes
Instagram carousel slide 2. body_lines is a list of 3–5 strings (≤80 characters each). If heading is None, defaults to “CLAVES” (ES) or “KEY POINTS” (EN).
ov.render_credits(base_url, channel, video_title, *, lang="es") -> bytes
Instagram carousel slide 3. channel comes from content["channel"] returned by Step 1.
ov.render_single(base_url, title, *, lang="es") -> bytes
Instagram single image. Title is rendered left-aligned at the bottom over a strong bottom gradient.
ov.render_linkedin_hook(base_url, title, *, lang="es") -> bytes
LinkedIn 4:5. Operates on a 1080×1350 canvas. Title is rendered left-aligned at the bottom over a gradient.
Font resolution order
The renderers resolve fonts in this order, stopping at the first match:
OVERLAY_FONT_PATH environment variable (must point to a .ttf or .otf file)
font-bold.ttf / font.ttf in the scripts/ directory
- System fonts:
- Windows:
arialbd.ttf / arial.ttf → segoeuib.ttf / segoeui.ttf
- macOS:
/System/Library/Fonts/Helvetica.ttc
- Linux: DejaVuSans-Bold / DejaVuSans → LiberationSans-Bold / LiberationSans-Regular
- Pillow’s default bitmap font (last resort — prints a warning, quality is reduced)
To use a custom typeface, copy your .ttf to scripts/font-bold.ttf (and optionally scripts/font.ttf), or set OVERLAY_FONT_PATH to the full path.
All five renderers return PNG bytes. These are passed to bc.upload_media_local() to obtain a Blotato-hosted public URL for use in the mediaUrls of a post.
Custom images
When images: is provided in the command, AI generation and overlays are both skipped entirely.
- Local paths are uploaded to Blotato with
bc.upload_media_local() to get public URLs.
- Public URLs are used as-is.
- For Instagram carousel, all provided images are used as the carousel slides.
- For Instagram single and LinkedIn, only the first image is used.
Error handling
The skill degrades gracefully when any part of the visual pipeline fails.
| Failure | What happens |
|---|
fp.generate_image() raises an exception | media_urls = [] for that platform; post publishes as text-only with an [aviso] |
Pillow not installed (import PIL fails) | [aviso] Pillow no instalado — publicando la base limpia de Mystic sin overlay. Proceeds with the clean Mystic URL |
ov.render_*() raises an exception | Warns, uses the clean Mystic URL as fallback |
bc.upload_media_local() fails | Warns, uses the Freepik CDN URL directly (note: Freepik URLs may expire; this is an acceptable fallback) |
| Carousel has fewer than 2 successful slides | Degrades to a single image using the first available slide |
Freepik CDN URLs used as fallbacks are not permanently hosted and may expire. If bc.upload_media_local() fails consistently, check that you have not exceeded Blotato’s presigned upload rate limit (10 requests/minute).