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). - ✅
.srtde 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 yexamples/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
(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
.assya trae la resolución (1080×1920 por defecto). Si cambias la resolución usaresolution=(W, H)enCaptionGenerator.
⚠️ 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:
-
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).
-
libass no garantiza emojis a color. Al quemar el
.asscon 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
captionwaveesté libre en https://pypi.org. Si no lo está, renómbralo: cambia la carpetasrc/captionwave/y el camponamedepyproject.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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9c126b707d2b97d1251b4d30ac0f9f55f1ccd96adfb048bb4144b014b0401ccb
|
|
| MD5 |
377bfbc6b01f8a7e8cdf4e0cf3412027
|
|
| BLAKE2b-256 |
282a68bb7d864cf718d2c962d2089fc50d7084654c22aeef70d330b214fb5be6
|
Provenance
The following attestation bundles were made for captionwave-0.1.0.tar.gz:
Publisher:
publish.yml on Fortex-GT/Captionwave
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
captionwave-0.1.0.tar.gz -
Subject digest:
9c126b707d2b97d1251b4d30ac0f9f55f1ccd96adfb048bb4144b014b0401ccb - Sigstore transparency entry: 1864702600
- Sigstore integration time:
-
Permalink:
Fortex-GT/Captionwave@bf7c1bb91d8adc53bf381106263ac2c15e2f478a -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/Fortex-GT
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@bf7c1bb91d8adc53bf381106263ac2c15e2f478a -
Trigger Event:
release
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
93f6a7a9047fe0640164d4b979cf8d1a041c5393cd341609658596e143c4ea3c
|
|
| MD5 |
2344f35cdbb94aa5213afee668e598db
|
|
| BLAKE2b-256 |
ddbcf4e90e989960b4e7417153a537ee2233ab5ba0e49a6420db7600a19d66ce
|
Provenance
The following attestation bundles were made for captionwave-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on Fortex-GT/Captionwave
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
captionwave-0.1.0-py3-none-any.whl -
Subject digest:
93f6a7a9047fe0640164d4b979cf8d1a041c5393cd341609658596e143c4ea3c - Sigstore transparency entry: 1864702604
- Sigstore integration time:
-
Permalink:
Fortex-GT/Captionwave@bf7c1bb91d8adc53bf381106263ac2c15e2f478a -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/Fortex-GT
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@bf7c1bb91d8adc53bf381106263ac2c15e2f478a -
Trigger Event:
release
-
Statement type: