Skip to main content

Genera audio + subtítulos animados sincronizados (.ass/.srt) a partir de texto, con emojis compatibles con iOS.

Project description

captionwave

Genera audio (voz) y subtítulos animados sincronizados (.ass + .srt) a partir de una variable de texto. Pensado para Shorts / Reels / TikTok estilo "¿Sabías que…?".

La librería no arma el video final: te entrega los archivos (audio + subtítulos + emojis con tiempos) para que tú montes el video como prefieras (FFmpeg, tu editor, MoviePy…). Así tienes control total y no reprogramas los subtítulos en cada proyecto.

  • ✅ Voz con edge-tts y tiempos por palabra → el audio y los subtítulos quedan pegados por construcción (no se desfasan).
  • ✅ Subtítulos animados en .ass (karaoke, palabra activa, "pop", sticker estilo Hormozi, palabra-por-palabra…) que FFmpeg/libass renderiza de forma nativa (rápido).
  • .srt de respaldo para plataformas que no aceptan .ass.
  • Emojis filtrados a los que existen en iOS (sin "tofus"/cuadritos), con sus tiempos exportados para superponer tu propio arte.

Instalación

pip install captionwave          # una vez publicado en PyPI

Mientras tanto —o en notebooks como Google Colab— instálalo desde GitHub:

pip install "git+https://github.com/Fortex-GT/Captionwave.git"

O desde el código fuente (este repositorio):

pip install .          # o, para desarrollo:  pip install -e .

Requisitos:

  • Conexión a internet para la voz (edge-tts usa el servicio de Microsoft Edge). Si solo quieres los subtítulos a partir de tiempos que ya tienes, puedes trabajar sin red con build_from_words(...) (ver más abajo y examples/offline_sin_internet.py).
  • FFmpeg instalado si vas a quemar los subtítulos en el video (ffmpeg -version).
  • Opcional, recomendado: pip install captionwave[duration] (usa mutagen para medir con exactitud la duración del audio y alinear el último subtítulo).

Uso rápido

from captionwave import CaptionGenerator

gen = CaptionGenerator(
    voice="es-MX-DaliaNeural",   # cualquier voz de edge-tts
    rate="+18%",                 # más rápido = más dinámico
    style="hormozi",             # ver estilos abajo
)

r = gen.generate(
    "El Sol es una estrella que contiene el 99% de la masa del sistema solar.",
    out_audio="voz.mp3",
    out_ass="subs.ass",
    out_srt="subs.srt",          # opcional
    out_emojis="emojis.json",    # opcional (para superponer arte de emoji)
)

print(r["duration"], "segundos")

Esto crea voz.mp3, subs.ass, subs.srt y emojis.json, listos para montar.

Con un gancho/intro a otro ritmo

r = gen.generate(
    "el sol es una estrella enorme.",
    intro="¿Sabías que...?",   # se dice antes
    intro_rate="+5%",          # el gancho un poco más pausado
    out_audio="voz.mp3", out_ass="subs.ass",
)

Sin internet (a partir de tiempos que ya tienes)

Si ya tienes los tiempos por palabra (de otra fuente o para hacer pruebas), puedes generar los subtítulos sin TTS ni conexión con build_from_words:

from captionwave import CaptionGenerator

words = [
    {"word": "El",       "start": 0.00, "dur": 0.18},
    {"word": "Sol",      "start": 0.18, "dur": 0.34},
    {"word": "es",       "start": 0.52, "dur": 0.16},
    {"word": "una",      "start": 0.68, "dur": 0.18},
    {"word": "estrella", "start": 0.86, "dur": 0.52},
]

gen = CaptionGenerator(style="hormozi")
r = gen.build_from_words(words, duration=1.5, out_ass="subs.ass", out_srt="subs.srt")

Devuelve el mismo dict que generate (con audio=None). Ver examples/offline_sin_internet.py.


Estilos disponibles

Muestra de estilos

(Palabra activa "ESTRELLA" resaltada en 4 de los estilos. El resaltado avanza palabra por palabra al ritmo de la voz.)

from captionwave import list_styles
print(list_styles())
Estilo Animación Descripción
hormozi sticker amarillo Palabra activa con fondo sólido (clásico de Shorts).
green sticker verde Igual que hormozi pero en verde.
karaoke barrido El texto se "llena" de color al ritmo de la voz.
pop rebote La palabra activa crece y cambia de color.
fire rebote naranja Variante de pop en tono fuego.
neon color + glow Palabra activa cian con contorno azul.
single palabra única Una sola palabra a la vez, grande y centrada.
clean color suave Subtítulo abajo, sobrio (lower third).

Personalizar cualquier estilo

Cada estilo es un Style que puedes copiar y modificar:

from captionwave import get_style, CaptionGenerator

mi_estilo = get_style("hormozi").copy(
    active_color="#FF3366",
    sticker_color="#FF3366",
    sticker_text_color="#FFFFFF",
    font="Montserrat",     # debe estar instalada en el sistema que renderiza
    font_size=96,
    max_words=2,           # menos palabras por línea
    position="lower",      # "center" | "lower" | "upper"
)

gen = CaptionGenerator(style=mi_estilo, rate="+20%")

Campos útiles de Style: base_color, active_color, outline_color, outline_w, sticker_color, sticker_text_color, sticker_bord, pop_scale, font, font_size, uppercase, max_words, max_chars, position.


Montar el video con FFmpeg

La librería te da voz.mp3 + subs.ass. Para quemar los subtítulos sobre un fondo:

Sobre un video de fondo (gameplay, b-roll, etc.):

ffmpeg -i fondo.mp4 -i voz.mp3 \
  -vf "scale=1080:1920,ass=subs.ass" \
  -map 0:v -map 1:a -shortest salida.mp4

Sobre una imagen fija:

ffmpeg -loop 1 -i fondo.jpg -i voz.mp3 \
  -vf "scale=1080:1920,ass=subs.ass" \
  -c:v libx264 -pix_fmt yuv420p -c:a aac -shortest salida.mp4

El .ass ya trae la resolución (1080×1920 por defecto). Si cambias la resolución usa resolution=(W, H) en CaptionGenerator.


⚠️ Sobre los emojis (léelo)

La librería elige, para cada palabra/frase, un emoji que sí existe en iOS (filtra por versión de Unicode; ajustable con emoji_max_version). Esto evita que en un iPhone aparezcan cuadritos.

Dos cosas importantes sobre la apariencia:

  1. No se incluye el arte de Apple. Los emojis de Apple son propiedad de Apple y no se pueden empaquetar/redistribuir. La librería entrega el carácter Unicode correcto; el diseño con el que se ve lo pone el sistema donde se reproduce (en iPhone/Mac se verá con el estilo de Apple; en Linux con Noto/Twemoji).

  2. libass no garantiza emojis a color. Al quemar el .ass con FFmpeg, los emojis suelen salir monocromos. Por eso, si quieres el emoji a color (y con el look de Apple), la mejor ruta es superponerlo como imagen en tu editor o con FFmpeg, usando los tiempos que te da la librería:

r = gen.generate("...", out_emojis="emojis.json")
for e in r["emojis"]:
    print(e)  # {"word": "estrella", "emoji": "⭐", "start": 1.38, "dur": 0.58}

Con eso colocas tu PNG de emoji (que tú aportas) en start durante dur. Así el carácter sale de la librería y el arte lo pones tú, sin problemas de copyright.

Si prefieres incrustar el carácter directamente en el .ass de todas formas, está activado por defecto (emoji_in_ass=True); ponlo en False si vas a usar overlay.


Qué devuelve generate(...)

Un dict con:

{
  "audio": "voz.mp3",
  "ass":   "subs.ass",
  "srt":   "subs.srt" | None,
  "duration": 6.37,                 # segundos
  "words":  [{"word","start","dur"}, ...],
  "lines":  [{"text","start","end","emoji"}, ...],
  "emojis": [{"word","emoji","start","dur"}, ...],
}

Desarrollo y tests

pip install -e ".[test]"
pytest

Los tests no necesitan internet (no llaman al TTS): cubren los estilos, el troceado en líneas, la selección de emojis y la escritura de .ass/.srt.


Publicar tu propia copia

Antes de subir a PyPI, verifica que el nombre captionwave esté libre en https://pypi.org. Si no lo está, renómbralo: cambia la carpeta src/captionwave/ y el campo name de pyproject.toml.

pip install build twine
python -m build
twine upload dist/*

Para GitHub solo inicializa el repo y súbelo normal.


Licencia

MIT © 2026 Wilfredo Guillén — ver LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

captionwave-0.1.0.tar.gz (23.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

captionwave-0.1.0-py3-none-any.whl (20.2 kB view details)

Uploaded Python 3

File details

Details for the file captionwave-0.1.0.tar.gz.

File metadata

  • Download URL: captionwave-0.1.0.tar.gz
  • Upload date:
  • Size: 23.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for captionwave-0.1.0.tar.gz
Algorithm Hash digest
SHA256 9c126b707d2b97d1251b4d30ac0f9f55f1ccd96adfb048bb4144b014b0401ccb
MD5 377bfbc6b01f8a7e8cdf4e0cf3412027
BLAKE2b-256 282a68bb7d864cf718d2c962d2089fc50d7084654c22aeef70d330b214fb5be6

See more details on using hashes here.

Provenance

The following attestation bundles were made for captionwave-0.1.0.tar.gz:

Publisher: publish.yml on Fortex-GT/Captionwave

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file captionwave-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: captionwave-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 20.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for captionwave-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 93f6a7a9047fe0640164d4b979cf8d1a041c5393cd341609658596e143c4ea3c
MD5 2344f35cdbb94aa5213afee668e598db
BLAKE2b-256 ddbcf4e90e989960b4e7417153a537ee2233ab5ba0e49a6420db7600a19d66ce

See more details on using hashes here.

Provenance

The following attestation bundles were made for captionwave-0.1.0-py3-none-any.whl:

Publisher: publish.yml on Fortex-GT/Captionwave

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page