Communitygithub.com

Tanush-Gupta1812/py-shorts-studio

Genera, escribe guiones, anima y renderiza videos explicativos virales en 2D, verticales a 60FPS para YouTube Shorts y Reels, usando PyCairo, subtítulos karaoke por palabra activa con Edge TTS, efectos de sonido procedurales y codificación directa por tubería FFmpeg.

¿Qué es py-shorts-studio?

Este skill proporciona un framework completo en Python para crear videos cortos explicativos en 2D, vectoriales y verticales (1080x1920 a 60FPS) diseñados para maximizar la retención en plataformas como YouTube Shorts e Instagram Reels. Su característica principal es la implementación de un bucle perfecto de audio y vídeo ('match-cut loop') que engaña al espectador para que no perciba dónde termina y comienza el video, logrando una retención infinita. Para ello, exige que el guion termine con una frase puente que enlace gramaticalmente con la pregunta inicial, y que el primer y último fotograma sean visualmente idénticos mediante una función `draw_anchor_state` que controla el zoom. El framework integra la generación de voz con Edge TTS, subtítulos karaoke sincronizados por palabra, efectos de sonido sintetizados y la mezcla de audio multicapa, todo orquestado para una ejecución autónoma. Es ideal para cualquier usuario o agente de IA que necesite producir contenido viral narrado sobre temas de historia, ingeniería, ciencia, economía, tecnología, supervivencia o misterios.

Compatible con~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Tanush-Gupta1812/py-shorts-studio/tree/HEAD/.agents/skills/py-shorts-studio

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Py-Shorts-Studio: Motor de Explicaciones 2D Vectoriales Virales

py-shorts-studio es un framework Python de alto rendimiento para crear Shorts explicativos de gráficos en movimiento, verticales (1080x1920, 9:16), a 60 FPS. Evita la E/S de disco transmitiendo fotogramas vectoriales de PyCairo directamente a la entrada estándar de FFmpeg, alinea las voces en off con subtítulos karaoke de palabra activa, sintetiza efectos de sonido personalizados y mezcla audio multitrack.


1. Bucle de Corte Visual y de Audio Perfecto (Retención Infinita)

Para maximizar el Porcentaje de Visualización Promedio (>100% de retención en bucle en YouTube Shorts e Instagram Reels), cada Short generado DEBE imponer tanto un puente de oración de audio como un corte visual idéntico píxel a píxel.

A. La Regla del Guion Puente-Oración

Cuando YouTube reproduce un Short en bucle, el final del audio se conecta instantáneamente con el inicio. El guion debe explotar esto:

  1. Gancho de Apertura: DEBE comenzar inmediatamente con una pregunta aguda de Por qué / Cómo / Qué.
    • Ejemplo: "¿Por qué el traje de Iron Man mataría a Tony Stark en tres segundos?"
    • Ejemplo: "¿Por qué las autopistas modernas se desmoronan en quince años mientras que las carreteras romanas duran dos mil?"
  2. Frase de Cierre Puente: La oración final DEBE terminar en una frase de cierre anticipatoria y final, en lugar de una despedida definitiva o un llamado a suscribirse:
    • Ejemplo: "...por eso la gente todavía pregunta,"
    • Ejemplo: "...así que la próxima vez que alguien te pregunte,"
    • Ejemplo: "...y eso explica,"
  3. Bucle Gramatical Continuo: Cuando el video se repite desde la frase final hasta la pregunta de apertura, forma una oración gramatical perfecta:

    "...por eso la gente todavía pregunta — ¿Por qué el traje de Iron Man mataría a Tony Stark en tres segundos?"

  4. Silencio Final Cero:
    • duración_total = duración_audio + 0.05 (NUNCA agregues 0.4s o 0.75s de silencio muerto al final).
    • Cualquier pausa al final rompe la ilusión de un bucle infinito. El bucle de audio debe alcanzar la primera palabra del reinicio sin una pausa perceptible.

B. Corte de Coincidencia de Fotograma 0 Idéntico Píxel a Píxel

El primer fotograma ($t = 0.0\text{s}$) y el último fotograma ($t = \text{duración_total}$) deben ser visualmente idénticos para que el espectador no pueda detectar cuándo se repite el video.

Patrón de Implementación: draw_anchor_state

Cada script de proyecto debe definir una función compartida draw_anchor_state(ctx, zoom=1.0, pan=(0, 0)) que contenga el visual "Hero" principal y el banner superior.

def draw_anchor_state(ctx: cairo.Context, zoom: float = 1.0, pan: tuple[float, float] = (0.0, 0.0)) -> None:
    """Renderiza el diagrama héroe maestro y el banner superior en un centro de zoom/pan arbitrario.

    Utilizado tanto por la Escena 1 (zoom de salida de apertura) como por la escena final (zoom de entrada de cierre)
    para garantizar un corte de coincidencia idéntico píxel a píxel a través del límite del bucle.
    """
    ctx.save()
    # Transformación de origen centrado (el centro de 1080x1920 es 540, 960)
    ctx.translate(540.0 + pan[0], 960.0 + pan[1])
    ctx.scale(zoom, zoom)
    ctx.translate(-540.0, -960.0)

    # 1. Fondo
    draw_dark_bg(ctx, 1080, 1920)

    # 2. Banner Superior
    draw_top_banner(ctx, "TÍTULO DEL TEMA PRINCIPAL", "EXPLICACIÓN DE INGENIERÍA", progress=1.0, theme="dark")

    # 3. Visual Héroe / Diagrama en la Zona de Acción
    # (Dibuja aquí el diagrama central, corte transversal o aparato)
    ...
    ctx.restore()

Dinámicas de Zoom de Corte de Coincidencia (0.0s – 1.2s y Últimos 1.2s)

  • Apertura ($t = 0.0\text{s} \to 1.2\text{s}$):
    • Comienza en $t = 0.0\text{s}$ muy aumentado en el diagrama focal con zoom = 1.45.
    • Durante $0.0\text{s} \to 1.2\text{s}$, se aleja suavemente a una vista amplia (zoom = 1.0) usando ease_out_cubic:
      if time_sec <= 1.2:
          zoom_t = ease_out_cubic(time_sec / 1.2)
          cur_zoom = lerp(1.45, 1.0, zoom_t)
          draw_anchor_state(ctx, zoom=cur_zoom)
      
  • Cierre ($t = \text{duración_total} - 1.2\text{s} \to \text{duración_total}$):
    • Transiciona suavemente de vuelta a ese mismo diagrama héroe.
    • Durante los últimos 1.2s, acerca desde zoom = 1.0 de vuelta a zoom = 1.45 usando ease_in_out_cubic:
      t_closing_start = total_duration - 1.2
      if time_sec >= t_closing_start:
          zoom_t = ease_in_out_cubic((time_sec - t_closing_start) / 1.2)
          cur_zoom = lerp(1.0, 1.45, zoom_t)
          draw_anchor_state(ctx, zoom=cur_zoom)
      
  • Resultado: El fotograma en $t = 0.0\text{s}$ (zoom = 1.45) y el fotograma en $t = \text{duración_total}$ (zoom = 1.45) son idénticos píxel a píxel.

2. La Fórmula de Guion de Alta Retención en 4 Partes

Los Shorts de alta retención siguen este arco narrativo estricto:

ParteDuración (Short 45–55s)Duración (Short 25–30s)Propósito y Ejecución
1. Gancho de Paradoja0.0s – 4.0s0.0s – 5.0sPlantea un contraste marcado y contraintuitivo o un misterio imposible comenzando con Por qué / Cómo / Qué. Prohíbe el relleno, las presentaciones o los saludos.
2. Restricción / Escalada4.0s – 20.0s5.0s – 12.0sEscala el problema con números concretos, apuestas históricas o peligros ambientales. Introduce métricas comparativas o barras de estado HUD.
3. El Giro Ingenioso20.0s – 45.0s12.0s – 22.0sEl secreto arquitectónico o científico revelado. Desglosa la mecánica de ingeniería utilizando diagramas animados, cortes, rutas o capas de componentes.
4. Recompensa y Puente FinalÚltimos 5.0sÚltimos 4.0sEntrega la recompensa del "¡ajá!" y termina con una frase puente final que se enlaza de nuevo con el gancho de apertura sin silencio final.
  • Ritmo del Habla: 150–170 palabras por minuto (PPM).
  • Recuentos de Palabras Objetivo:
    • Short de 45–55s: 110–145 palabras.
    • Short de 25–30s: 65–75 palabras.

3. Reglas Visuales y de Diseño

  1. Especificaciones del Lienzo:
    • Resolución: 1080x1920 (relación de aspecto vertical 9:16).
    • Fotogramas por Segundo: 60 FPS constante.
    • Precisión Vectorial: Todas las formas, iconos y líneas dibujadas a través de PyCairo para un suavizado nítido.
  2. Zonas Seguras (Crucial para la IU Móvil):
    • Zona Segura Superior ($y \ge 130$): Coloca títulos, píldoras de capítulo y banners superiores entre $y = 130$ y $y = 260$ para evitar el identificador del canal y las superposiciones de la interfaz de usuario superior.
    • Zona de Acción Central ($y = 280$ a $y = 1420$): Todos los gráficos focales, cortes, rutas de mapas e insignias de estadísticas viven aquí.
    • Zona Segura de Subtítulos Inferior: Los subtítulos se anclan en $y = 1480$–$1540$. Mantén los elementos focales de fondo por encima de $y = 1400$ para evitar superponer el texto del subtítulo. Evita los detalles visuales esenciales en los 380px inferiores (donde residen los botones de título, sonido y suscripción en YouTube y TikTok).
  3. Regla de Ritmo (Cambios de Estado cada 3–5 Segundos):
    • La atención del espectador decae rápidamente. Cambia el estado visual, el enfoque de la cámara o activa una animación cada 3 a 5 segundos (p. ej., aparición elástica de una insignia, relleno de métrica HUD, progresión de ruta o revelación de capa).
  4. Diseño de Sonido Sincronizado con Palabras Exactas:
    • Nunca adivines los tiempos de los efectos de sonido.
    • Ejecuta generate_voiceover primero para producir words.json.
    • Inspecciona words.json en busca de palabras clave desencadenantes (p. ej., números, verbos de acción, cambios de escena) y coloca las señales de whoosh, pop y thud en el start_sec exacto de esas palabras.

4. Referencia Completa de la API del Motor

Todos los módulos se exportan de forma limpia desde engine:

from engine import (
    # Renderizador
    VideoRenderer,
    # Matemáticas de Interpolación
    clamp, lerp, ease_out_cubic, ease_in_out_cubic, spring_pop,
    cubic_bezier_point, cubic_bezier_tangent, cubic_bezier_angle,
    # Primitivas Vectoriales 2D
    draw_parchment_bg, draw_dark_bg, draw_top_banner, draw_map_route,
    draw_hud_bars, draw_stat_badge, draw_rounded_rect, draw_card_shadow,
    # Subtítulos Karaoke de Palabra Activa
    draw_karaoke_captions, chunk_words,
    # Síntesis, Mezcla y Multiplexación de Audio
    generate_voiceover, generate_voiceover_async,
    ensure_default_sfx, get_downloads_dir, mix_final_audio, mux_video_audio,
)

VideoRenderer (engine.renderer)

class VideoRenderer:
    def __init__(
        self,
        output_path: Union[str, Path],
        width: int = 1080,
        height: int = 1920,
        fps: int = 60,
        crf: int = 18,
        preset: str = "fast",
        audio_path: Optional[Union[str, Path]] = None,
        ffmpeg_bin: Optional[str] = None,
    ) -> None: ...

    def create_surface(self) -> Tuple[cairo.ImageSurface, cairo.Context]: ...
    def write_surface(self, surface: cairo.ImageSurface) -> None: ...
    def render(
        self,
        draw_fn: Callable[[cairo.Context, int, float], None],
        total_frames: Optional[int] = None,
        duration_sec: Optional[float] = None,
        on_progress: Optional[Callable[[int, int], None]] = None,
    ) -> Path: ...

Ayudantes de Interpolación (engine.easing)

def clamp(val: float, min_val: float = 0.0, max_val: float = 1.0) -> float: ...
def lerp(a: float, b: float, t: float) -> float: ...
def ease_out_cubic(t: float) -> float: ...
def ease_in_out_cubic(t: float) -> float: ...
def spring_pop(t: float, overshoot: float = 1.2) -> float: ...
def cubic_bezier_point(p0: Point2D | float, p1: Point2D | float, p2: Point2D | float, p3: Point2D | float, t: float) -> Tuple[float, float] | float: ...
def cubic_bezier_tangent(p0: Point2D, p1: Point2D, p2: Point2D, p3: Point2D, t: float) -> Tuple[float, float]: ...
def cubic_bezier_angle(p0: Point2D, p1: Point2D, p2: Point2D, p3: Point2D, t: float) -> float: ...

Primitivas Vectoriales Reutilizables (engine.primitives)

def draw_parchment_bg(ctx: cairo.Context, width: int = 1080, height: int = 1920, seed: int = 42) -> None: ...
def draw_dark_bg(ctx: cairo.Context, width: int = 1080, height: int = 1920) -> None: ...
def draw_top_banner(ctx: cairo.Context, title: str, subtitle: Optional[str] = None, progress: float = 1.0, theme: str = "light") -> None: ...
def draw_map_route(ctx: cairo.Context, points: Sequence[Tuple[float, float]], progress: float, color: Tuple[float, float, float] = (0.86, 0.15, 0.15), line_width: float = 8.0, dashed: bool = True, show_arrow: bool = True, pulse_time: float = 0.0) -> None: ...
def draw_hud_bars(ctx: cairo.Context, metrics: List[Dict[str, Any]], x: float, y: float, width: float = 880.0, theme: str = "light") -> None: ...
def draw_stat_badge(ctx: cairo.Context, value_text: str, label_text: str, x: float, y: float, scale: float = 1.0, accent_color: Tuple[float, float, float] = (0.98, 0.80, 0.08), theme: str = "light") -> None: ...
def draw_rounded_rect(ctx: cairo.Context, x: float, y: float, w: float, h: float, r: float) -> None: ...
def draw_card_shadow(ctx: cairo.Context, x: float, y: float, w: float, h: float, r: float, blur_layers: int = 4, offset_y: float = 6.0, base_alpha: float = 0.12) -> None: ...

Subtítulos Karaoke de Palabra Activa (engine.captions)

def chunk_words(words: Sequence[Dict[str, Any]], max_words: int = 3, max_chars: int = 18) -> List[Dict[str, Any]]: ...
def draw_karaoke_captions(ctx: cairo.Context, words: Sequence[Dict[str, Any]], current_time: float, center_x: float = 540.0, y: float = 1480.0, font_path: Optional[Union[str, Path]] = None, font_size: int = 76, stroke_width: int = 8, active_color: Tuple[int, int, int] = (250, 204, 21), inactive_color: Tuple[int, int, int] = (255, 255, 255), active_scale: float = 1.12) -> None: ...

Voz en Off, Efectos de Sonido y Mezcla (engine.tts_align, engine.audio_mixer)

def generate_voiceover(script_text: str, output_audio_path: Union[str, Path], output_words_json: Optional[Union[str, Path]] = None, voice: str = "en-US-AndrewNeural", rate: str = "+12%") -> float: ...
def ensure_default_sfx(sfx_dir: Union[str, Path] = "assets/sfx") -> Dict[str, Path]: ...
def get_downloads_dir() -> Path: ...
def mix_final_audio(voiceover_path: Union[str, Path], sfx_events: Sequence[Tuple[float, str]], output_audio_path: Union[str, Path], total_duration: float, include_ambient: bool = True, sfx_dir: Union[str, Path] = "assets/sfx", ffmpeg_bin: Optional[str] = None) -> Path: ...
def mux_video_audio(video_path: Union[str, Path], audio_path: Union[str, Path], final_output_path: Optional[Union[str, Path]] = None, filename: Optional[str] = None, ffmpeg_bin: Optional[str] = None) -> str: ...

5. Flujo de Trabajo de Ejecución Autónoma

Cuando se te asigne generar un Short sobre cualquier tema, ejecuta estos pasos secuencialmente:

Paso 1: Composición del Guion (Regla del Puente-Oración)

Escribe un guion ajustado de 65 a 140 palabras siguiendo la fórmula de 4 partes.

  • DEBE comenzar con una pregunta aguda de Por qué / Cómo / Qué.
  • DEBE terminar con una frase puente final ("...por eso la gente todavía pregunta,").
  • Elige un slug de tema claro (p. ej., colosseum_flood o vibranium_physics).

Paso 2: Generación de Voz en Off y Marcas de Tiempo en scratch/

Mantén output/ limpio almacenando todos los archivos intermedios en scratch/:

.venv\Scripts\python -c "from engine import generate_voiceover; generate_voiceover('''<script>''', 'scratch/<slug>_vo.mp3', 'scratch/<slug>_words.json')"

Inspecciona scratch/<slug>_words.json para determinar:

  • audio_duration exacta
  • Marcas de tiempo de palabras clave (p. ej., dónde termina el gancho, dónde se pronuncian los números, dónde ocurren el giro y el puente final).

Paso 3: Generación de la Tubería de Escenas con Script

Crea projects/<slug>.py importando desde engine:

  • Establece total_duration = audio_dur + 0.05 (Silencio final cero para mantener el bucle instantáneo).
  • Escribe los archivos de video/audio intermedios en scratch/<slug>_silent.mp4 y scratch/<slug>_audio.aac.
  • Define draw_anchor_state(ctx, zoom, pan) para el diagrama héroe.
  • Usa draw_anchor_state con zoom = 1.45 -> 1.0 a través de ease_out_cubic para la apertura de $0.0\text{s} \to 1.2\text{s}$.
  • Usa draw_anchor_state con zoom = 1.0 -> 1.45 a través de ease_in_out_cubic para los últimos $1.2\text{s}$ ($t_{\text{cierre}} \to \text{duración_total}$), asegurando un corte de coincidencia idéntico píxel a píxel con el fotograma 0.
  • Define las transiciones de escena intermedias que coincidan con el ritmo real de la narración de scratch/<slug>_words.json.
  • Asigna eventos SFX [(t1, 'whoosh'), (t2, 'pop'), (t3, 'thud')] a las marcas de tiempo de palabras exactas.
  • Implementa draw_frame(ctx, frame_idx, time_sec) renderizando el fotograma actual basado en la escena activa y el estado de animación.

Paso 4: Renderizar Video Mudo

.venv\Scripts\python projects/<slug>.py

Esto produce scratch/<slug>_silent.mp4.

Paso 5: Mezcla de Audio Final

.venv\Scripts\python -c "from engine import mix_final_audio; mix_final_audio('scratch/<slug>_vo.mp3', [(0.5, 'whoosh'), (12.3, 'pop'), (25.0, 'thud')], 'scratch/<slug>_audio.mp3', total_duration=audio_dur+0.05)"

Paso 6: Multiplexar Video y Audio

.venv\Scripts\python -c "from engine import mux_video_audio; mux_video_audio('scratch/<slug>_silent.mp4', 'scratch/<slug>_audio.mp3', filename='<slug>.mp4')"

El Short final renderizado aparece en output/<slug>.mp4.


6. Convenciones de Proyecto y Mejores Prácticas

  • Estructura del Proyecto:
    py-shorts-studio/
    ├── engine/           # Módulo del motor (no tocar)
    ├── projects/         # Scripts de proyecto de usuario (<slug>.py)
    ├── scratch/          # Archivos temporales (ignorados por git)
    ├── output/           # Videos finales
    └── assets/sfx/       # Efectos de sonido predeterminados
    
  • Ajuste de Voz: Generalmente se usa "en-US-AndrewNeural" con rate="+12%". Ajusta la voz si es necesario (disponible en edge-tts --list-voices).
  • Ruta de FFmpeg: Especifica ffmpeg_bin explícitamente si FFmpeg no está en tu PATH del sistema. Busca en C:\ffmpeg\bin\ffmpeg.exe en Windows o /usr/local/bin/ffmpeg en macOS/Linux.
  • Primero el Audio: Siempre ejecuta la generación de voz en off antes de codificar cualquier escena. Los tiempos de las palabras impulsan el ritmo de la animación y la colocación de los efectos de sonido.
  • Convención de Nombres de Slug: Usa nombres de slug cortos y descriptivos (p. ej., colosseum_flood, vibranium_physics, roman_concrete). Sin espacios.
  • Gestión de Errores: Si una parte del proceso falla, corrige el problema y reanuda desde el paso apropiado. La tubería está diseñada para una ejecución modular y depurable.

7. Conceptos Técnicos Fundamentales

API render() de VideoRenderer

El método principal render() acepta una función draw_fn que recibe (ctx, frame_idx, time_sec):

def render(
    self,
    draw_fn: Callable[[cairo.Context, int, float], None],
    total_frames: Optional[int] = None,
    duration_sec: Optional[float] = None,
    on_progress: Optional[Callable[[int, int], None]] = None,
) -> Path:
  • draw_fn: Tu función de renderizado de fotogramas personalizada.
    • ctx: cairo.Context – El contexto de Cairo para dibujar.
    • frame_idx: int – El índice de fotograma actual (basado en 0).
    • time_sec: float – El tiempo actual en segundos.
  • total_frames o duration_sec: Especifica la duración total.
  • on_progress: Callback opcional para informes de progreso.

Diseño de Escenas Basado en Marcas de Tiempo

Estructura la función draw_frame alrededor de las marcas de tiempo de las palabras del archivo JSON:

def draw_frame(ctx, frame_idx, time_sec):
    hold_frame = False  # Se vuelve True cuando la escena termina

    # --- Escena 1: Gancho (t = 0.0s a 4.0s) ---
    if time_sec < 4.0:
        if not hold_frame:
            # Animación de apertura con zoom de coincidencia exacta
            zoom_t = ease_out_cubic(min(1.0, time_sec / 1.2))
            cur_zoom = lerp(1.45, 1.0, zoom_t)
            draw_anchor_state(ctx, zoom=cur_zoom)
            # Dibujar detalles de la escena 1...
    # --- Escena 2: Restricción (t = 4.0s a 20.0s) ---
    elif time_sec < 20.0:
        # ...
    # --- Escena 3: Giro (t = 20.0s a (duración_total-1.2)s) ---
    elif time_sec < total_duration - 1.2:
        # ...
    # --- Escena 4: Cierre / Recompensa (últimos 1.2s) ---
    else:
        # Zoom de salida de coincidencia exacta de vuelta al fotograma 0
        zoom_t = ease_in_out_cubic((time_sec - (total_duration - 1.2)) / 1.2)
        cur_zoom = lerp(1.0, 1.45, zoom_t)
        draw_anchor_state(ctx, zoom=cur_zoom)

Sistema de Efectos de Sonido de Eventos

Pasa una lista de tuplas (timestamp_in_seconds, sfx_name) a mix_final_audio():

sfx_events = [
    (4.5, "whoosh"),    # Aparece la escena 2
    (12.3, "pop"),      # Aparece el primer número/estadística
    (25.0, "thud"),     # Momento de revelación del giro
    (38.5, "whoosh"),   # Transición a la escena 4
]
mix_final_audio(..., sfx_events, ...)

Los nombres de efectos de sonido disponibles (whoosh, pop, thud, etc.) se gestionan a través de ensure_default_sfx().


8. Preguntas Frecuentes y Resolución de Problemas

P: Recibo "No module named 'engine'"

R: Asegúrate de estar ejecutando desde el directorio raíz del proyecto py-shorts-studio/ o instala el paquete en modo editable: pip install -e .

P: FFmpeg no se encuentra

R: Pasa ffmpeg_bin="/ruta/completa/a/ffmpeg" a VideoRenderer o mix_final_audio. En Windows, las rutas comunes incluyen "C:\ffmpeg\bin\ffmpeg.exe".

P: El bucle de video tiene un salto notable

R: Verifica:

  1. total_duration = audio_dur + 0.05 (No 0.4 o 0.75)
  2. draw_anchor_state se usa tanto para el fotograma 0 como para el fotograma final.
  3. La última escena alcanza exactamente zoom = 1.45 en el último fotograma.

P: ¿Cómo cambio la velocidad de la voz?

R: Ajusta el parámetro rate en generate_voiceover. Los valores comunes son "+12%", "+20%", o "-10%".

P: Las palabras del archivo words.json están desordenadas.

R: Las marcas de tiempo de Edge TTS pueden tener pequeñas variaciones. Usa las marcas de tiempo proporcionadas como guía; los tiempos reales de las palabras son más importantes que la estructura del guion. Confía en los valores start_sec y end_sec.


9. Notas Legales

  • Derechos de Autor: Todo el contenido generado por este script es responsabilidad del usuario.
  • Voces TTS: Edge TTS proporciona voces neurales. Verifica los términos de servicio de la plataforma para el uso de voces generadas por IA.
  • Efectos de Sonido: Los efectos de sonido predeterminados son libres de regalías.
  • Uso del Bucle: La técnica de bucle descrita aquí está diseñada para cumplir con las pautas de la plataforma. No engaña a los algoritmos; simplemente mejora la experiencia del espectador.

Documentación generada para py-shorts-studio v1.0.0

Skills relacionados