captionwave 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- captionwave-0.1.0/LICENSE +21 -0
- captionwave-0.1.0/PKG-INFO +269 -0
- captionwave-0.1.0/README.md +234 -0
- captionwave-0.1.0/pyproject.toml +48 -0
- captionwave-0.1.0/setup.cfg +4 -0
- captionwave-0.1.0/src/captionwave/__init__.py +22 -0
- captionwave-0.1.0/src/captionwave/ass_writer.py +209 -0
- captionwave-0.1.0/src/captionwave/core.py +182 -0
- captionwave-0.1.0/src/captionwave/emojis.py +161 -0
- captionwave-0.1.0/src/captionwave/srt_writer.py +32 -0
- captionwave-0.1.0/src/captionwave/styles.py +153 -0
- captionwave-0.1.0/src/captionwave/tts.py +157 -0
- captionwave-0.1.0/src/captionwave.egg-info/PKG-INFO +269 -0
- captionwave-0.1.0/src/captionwave.egg-info/SOURCES.txt +20 -0
- captionwave-0.1.0/src/captionwave.egg-info/dependency_links.txt +1 -0
- captionwave-0.1.0/src/captionwave.egg-info/requires.txt +11 -0
- captionwave-0.1.0/src/captionwave.egg-info/top_level.txt +1 -0
- captionwave-0.1.0/tests/test_core.py +59 -0
- captionwave-0.1.0/tests/test_emojis.py +31 -0
- captionwave-0.1.0/tests/test_styles.py +40 -0
- captionwave-0.1.0/tests/test_tts.py +38 -0
- captionwave-0.1.0/tests/test_writers.py +43 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Wilfredo Guillén
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: captionwave
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Genera audio + subtítulos animados sincronizados (.ass/.srt) a partir de texto, con emojis compatibles con iOS.
|
|
5
|
+
Author-email: Wilfredo Guillén <wilfredoguillensalazar@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Fortex-GT/Captionwave
|
|
8
|
+
Project-URL: Repository, https://github.com/Fortex-GT/Captionwave
|
|
9
|
+
Project-URL: Issues, https://github.com/Fortex-GT/Captionwave/issues
|
|
10
|
+
Keywords: subtitles,captions,tts,ass,shorts,reels,tiktok,karaoke,emoji
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
21
|
+
Classifier: Topic :: Multimedia :: Video
|
|
22
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: edge-tts>=7.0
|
|
27
|
+
Requires-Dist: emoji>=2.8
|
|
28
|
+
Provides-Extra: duration
|
|
29
|
+
Requires-Dist: mutagen>=1.45; extra == "duration"
|
|
30
|
+
Provides-Extra: video
|
|
31
|
+
Requires-Dist: moviepy>=2.0; extra == "video"
|
|
32
|
+
Provides-Extra: test
|
|
33
|
+
Requires-Dist: pytest>=7; extra == "test"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# captionwave
|
|
37
|
+
|
|
38
|
+
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…?".
|
|
39
|
+
|
|
40
|
+
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.
|
|
41
|
+
|
|
42
|
+
- ✅ Voz con **edge-tts** y tiempos **por palabra** → el audio y los subtítulos quedan pegados *por construcción* (no se desfasan).
|
|
43
|
+
- ✅ Subtítulos animados en **`.ass`** (karaoke, palabra activa, "pop", sticker estilo Hormozi, palabra-por-palabra…) que **FFmpeg/libass renderiza de forma nativa** (rápido).
|
|
44
|
+
- ✅ `.srt` de respaldo para plataformas que no aceptan `.ass`.
|
|
45
|
+
- ✅ **Emojis filtrados a los que existen en iOS** (sin "tofus"/cuadritos), con sus tiempos exportados para superponer tu propio arte.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Instalación
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install captionwave # una vez publicado en PyPI
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Mientras tanto —o en notebooks como Google Colab— instálalo desde GitHub:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install "git+https://github.com/Fortex-GT/Captionwave.git"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
O desde el código fuente (este repositorio):
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install . # o, para desarrollo: pip install -e .
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Requisitos:
|
|
68
|
+
- **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`).
|
|
69
|
+
- **FFmpeg** instalado si vas a quemar los subtítulos en el video (`ffmpeg -version`).
|
|
70
|
+
- Opcional, recomendado: `pip install captionwave[duration]` (usa *mutagen* para medir con exactitud la duración del audio y alinear el último subtítulo).
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Uso rápido
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from captionwave import CaptionGenerator
|
|
78
|
+
|
|
79
|
+
gen = CaptionGenerator(
|
|
80
|
+
voice="es-MX-DaliaNeural", # cualquier voz de edge-tts
|
|
81
|
+
rate="+18%", # más rápido = más dinámico
|
|
82
|
+
style="hormozi", # ver estilos abajo
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
r = gen.generate(
|
|
86
|
+
"El Sol es una estrella que contiene el 99% de la masa del sistema solar.",
|
|
87
|
+
out_audio="voz.mp3",
|
|
88
|
+
out_ass="subs.ass",
|
|
89
|
+
out_srt="subs.srt", # opcional
|
|
90
|
+
out_emojis="emojis.json", # opcional (para superponer arte de emoji)
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
print(r["duration"], "segundos")
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Esto crea `voz.mp3`, `subs.ass`, `subs.srt` y `emojis.json`, listos para montar.
|
|
97
|
+
|
|
98
|
+
### Con un gancho/intro a otro ritmo
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
r = gen.generate(
|
|
102
|
+
"el sol es una estrella enorme.",
|
|
103
|
+
intro="¿Sabías que...?", # se dice antes
|
|
104
|
+
intro_rate="+5%", # el gancho un poco más pausado
|
|
105
|
+
out_audio="voz.mp3", out_ass="subs.ass",
|
|
106
|
+
)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Sin internet (a partir de tiempos que ya tienes)
|
|
110
|
+
|
|
111
|
+
Si ya tienes los tiempos por palabra (de otra fuente o para hacer pruebas),
|
|
112
|
+
puedes generar los subtítulos **sin TTS ni conexión** con `build_from_words`:
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from captionwave import CaptionGenerator
|
|
116
|
+
|
|
117
|
+
words = [
|
|
118
|
+
{"word": "El", "start": 0.00, "dur": 0.18},
|
|
119
|
+
{"word": "Sol", "start": 0.18, "dur": 0.34},
|
|
120
|
+
{"word": "es", "start": 0.52, "dur": 0.16},
|
|
121
|
+
{"word": "una", "start": 0.68, "dur": 0.18},
|
|
122
|
+
{"word": "estrella", "start": 0.86, "dur": 0.52},
|
|
123
|
+
]
|
|
124
|
+
|
|
125
|
+
gen = CaptionGenerator(style="hormozi")
|
|
126
|
+
r = gen.build_from_words(words, duration=1.5, out_ass="subs.ass", out_srt="subs.srt")
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Devuelve el mismo `dict` que `generate` (con `audio=None`). Ver `examples/offline_sin_internet.py`.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Estilos disponibles
|
|
134
|
+
|
|
135
|
+

|
|
136
|
+
|
|
137
|
+
*(Palabra activa "ESTRELLA" resaltada en 4 de los estilos. El resaltado avanza palabra por palabra al ritmo de la voz.)*
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
from captionwave import list_styles
|
|
141
|
+
print(list_styles())
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
| Estilo | Animación | Descripción |
|
|
145
|
+
|-------------|------------------|-------------|
|
|
146
|
+
| `hormozi` | sticker amarillo | Palabra activa con fondo sólido (clásico de Shorts). |
|
|
147
|
+
| `green` | sticker verde | Igual que hormozi pero en verde. |
|
|
148
|
+
| `karaoke` | barrido | El texto se "llena" de color al ritmo de la voz. |
|
|
149
|
+
| `pop` | rebote | La palabra activa crece y cambia de color. |
|
|
150
|
+
| `fire` | rebote naranja | Variante de `pop` en tono fuego. |
|
|
151
|
+
| `neon` | color + glow | Palabra activa cian con contorno azul. |
|
|
152
|
+
| `single` | palabra única | Una sola palabra a la vez, grande y centrada. |
|
|
153
|
+
| `clean` | color suave | Subtítulo abajo, sobrio (lower third). |
|
|
154
|
+
|
|
155
|
+
### Personalizar cualquier estilo
|
|
156
|
+
|
|
157
|
+
Cada estilo es un `Style` que puedes copiar y modificar:
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
from captionwave import get_style, CaptionGenerator
|
|
161
|
+
|
|
162
|
+
mi_estilo = get_style("hormozi").copy(
|
|
163
|
+
active_color="#FF3366",
|
|
164
|
+
sticker_color="#FF3366",
|
|
165
|
+
sticker_text_color="#FFFFFF",
|
|
166
|
+
font="Montserrat", # debe estar instalada en el sistema que renderiza
|
|
167
|
+
font_size=96,
|
|
168
|
+
max_words=2, # menos palabras por línea
|
|
169
|
+
position="lower", # "center" | "lower" | "upper"
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
gen = CaptionGenerator(style=mi_estilo, rate="+20%")
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
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`.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Montar el video con FFmpeg
|
|
180
|
+
|
|
181
|
+
La librería te da `voz.mp3` + `subs.ass`. Para quemar los subtítulos sobre un fondo:
|
|
182
|
+
|
|
183
|
+
**Sobre un video de fondo** (gameplay, b-roll, etc.):
|
|
184
|
+
```bash
|
|
185
|
+
ffmpeg -i fondo.mp4 -i voz.mp3 \
|
|
186
|
+
-vf "scale=1080:1920,ass=subs.ass" \
|
|
187
|
+
-map 0:v -map 1:a -shortest salida.mp4
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Sobre una imagen fija**:
|
|
191
|
+
```bash
|
|
192
|
+
ffmpeg -loop 1 -i fondo.jpg -i voz.mp3 \
|
|
193
|
+
-vf "scale=1080:1920,ass=subs.ass" \
|
|
194
|
+
-c:v libx264 -pix_fmt yuv420p -c:a aac -shortest salida.mp4
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
> El `.ass` ya trae la resolución (1080×1920 por defecto). Si cambias la resolución usa `resolution=(W, H)` en `CaptionGenerator`.
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## ⚠️ Sobre los emojis (léelo)
|
|
202
|
+
|
|
203
|
+
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.
|
|
204
|
+
|
|
205
|
+
Dos cosas importantes sobre la **apariencia**:
|
|
206
|
+
|
|
207
|
+
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).
|
|
208
|
+
|
|
209
|
+
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:
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
r = gen.generate("...", out_emojis="emojis.json")
|
|
213
|
+
for e in r["emojis"]:
|
|
214
|
+
print(e) # {"word": "estrella", "emoji": "⭐", "start": 1.38, "dur": 0.58}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
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.
|
|
218
|
+
|
|
219
|
+
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.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Qué devuelve `generate(...)`
|
|
224
|
+
|
|
225
|
+
Un `dict` con:
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
{
|
|
229
|
+
"audio": "voz.mp3",
|
|
230
|
+
"ass": "subs.ass",
|
|
231
|
+
"srt": "subs.srt" | None,
|
|
232
|
+
"duration": 6.37, # segundos
|
|
233
|
+
"words": [{"word","start","dur"}, ...],
|
|
234
|
+
"lines": [{"text","start","end","emoji"}, ...],
|
|
235
|
+
"emojis": [{"word","emoji","start","dur"}, ...],
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Desarrollo y tests
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
pip install -e ".[test]"
|
|
245
|
+
pytest
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Los tests **no necesitan internet** (no llaman al TTS): cubren los estilos, el
|
|
249
|
+
troceado en líneas, la selección de emojis y la escritura de `.ass`/`.srt`.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Publicar tu propia copia
|
|
254
|
+
|
|
255
|
+
> **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`.
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
pip install build twine
|
|
259
|
+
python -m build
|
|
260
|
+
twine upload dist/*
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Para GitHub solo inicializa el repo y súbelo normal.
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Licencia
|
|
268
|
+
|
|
269
|
+
MIT © 2026 Wilfredo Guillén — ver [LICENSE](LICENSE).
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# captionwave
|
|
2
|
+
|
|
3
|
+
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…?".
|
|
4
|
+
|
|
5
|
+
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.
|
|
6
|
+
|
|
7
|
+
- ✅ Voz con **edge-tts** y tiempos **por palabra** → el audio y los subtítulos quedan pegados *por construcción* (no se desfasan).
|
|
8
|
+
- ✅ Subtítulos animados en **`.ass`** (karaoke, palabra activa, "pop", sticker estilo Hormozi, palabra-por-palabra…) que **FFmpeg/libass renderiza de forma nativa** (rápido).
|
|
9
|
+
- ✅ `.srt` de respaldo para plataformas que no aceptan `.ass`.
|
|
10
|
+
- ✅ **Emojis filtrados a los que existen en iOS** (sin "tofus"/cuadritos), con sus tiempos exportados para superponer tu propio arte.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Instalación
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install captionwave # una vez publicado en PyPI
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Mientras tanto —o en notebooks como Google Colab— instálalo desde GitHub:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install "git+https://github.com/Fortex-GT/Captionwave.git"
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
O desde el código fuente (este repositorio):
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install . # o, para desarrollo: pip install -e .
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Requisitos:
|
|
33
|
+
- **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`).
|
|
34
|
+
- **FFmpeg** instalado si vas a quemar los subtítulos en el video (`ffmpeg -version`).
|
|
35
|
+
- Opcional, recomendado: `pip install captionwave[duration]` (usa *mutagen* para medir con exactitud la duración del audio y alinear el último subtítulo).
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Uso rápido
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from captionwave import CaptionGenerator
|
|
43
|
+
|
|
44
|
+
gen = CaptionGenerator(
|
|
45
|
+
voice="es-MX-DaliaNeural", # cualquier voz de edge-tts
|
|
46
|
+
rate="+18%", # más rápido = más dinámico
|
|
47
|
+
style="hormozi", # ver estilos abajo
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
r = gen.generate(
|
|
51
|
+
"El Sol es una estrella que contiene el 99% de la masa del sistema solar.",
|
|
52
|
+
out_audio="voz.mp3",
|
|
53
|
+
out_ass="subs.ass",
|
|
54
|
+
out_srt="subs.srt", # opcional
|
|
55
|
+
out_emojis="emojis.json", # opcional (para superponer arte de emoji)
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
print(r["duration"], "segundos")
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Esto crea `voz.mp3`, `subs.ass`, `subs.srt` y `emojis.json`, listos para montar.
|
|
62
|
+
|
|
63
|
+
### Con un gancho/intro a otro ritmo
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
r = gen.generate(
|
|
67
|
+
"el sol es una estrella enorme.",
|
|
68
|
+
intro="¿Sabías que...?", # se dice antes
|
|
69
|
+
intro_rate="+5%", # el gancho un poco más pausado
|
|
70
|
+
out_audio="voz.mp3", out_ass="subs.ass",
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Sin internet (a partir de tiempos que ya tienes)
|
|
75
|
+
|
|
76
|
+
Si ya tienes los tiempos por palabra (de otra fuente o para hacer pruebas),
|
|
77
|
+
puedes generar los subtítulos **sin TTS ni conexión** con `build_from_words`:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from captionwave import CaptionGenerator
|
|
81
|
+
|
|
82
|
+
words = [
|
|
83
|
+
{"word": "El", "start": 0.00, "dur": 0.18},
|
|
84
|
+
{"word": "Sol", "start": 0.18, "dur": 0.34},
|
|
85
|
+
{"word": "es", "start": 0.52, "dur": 0.16},
|
|
86
|
+
{"word": "una", "start": 0.68, "dur": 0.18},
|
|
87
|
+
{"word": "estrella", "start": 0.86, "dur": 0.52},
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
gen = CaptionGenerator(style="hormozi")
|
|
91
|
+
r = gen.build_from_words(words, duration=1.5, out_ass="subs.ass", out_srt="subs.srt")
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Devuelve el mismo `dict` que `generate` (con `audio=None`). Ver `examples/offline_sin_internet.py`.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Estilos disponibles
|
|
99
|
+
|
|
100
|
+

|
|
101
|
+
|
|
102
|
+
*(Palabra activa "ESTRELLA" resaltada en 4 de los estilos. El resaltado avanza palabra por palabra al ritmo de la voz.)*
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from captionwave import list_styles
|
|
106
|
+
print(list_styles())
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
| Estilo | Animación | Descripción |
|
|
110
|
+
|-------------|------------------|-------------|
|
|
111
|
+
| `hormozi` | sticker amarillo | Palabra activa con fondo sólido (clásico de Shorts). |
|
|
112
|
+
| `green` | sticker verde | Igual que hormozi pero en verde. |
|
|
113
|
+
| `karaoke` | barrido | El texto se "llena" de color al ritmo de la voz. |
|
|
114
|
+
| `pop` | rebote | La palabra activa crece y cambia de color. |
|
|
115
|
+
| `fire` | rebote naranja | Variante de `pop` en tono fuego. |
|
|
116
|
+
| `neon` | color + glow | Palabra activa cian con contorno azul. |
|
|
117
|
+
| `single` | palabra única | Una sola palabra a la vez, grande y centrada. |
|
|
118
|
+
| `clean` | color suave | Subtítulo abajo, sobrio (lower third). |
|
|
119
|
+
|
|
120
|
+
### Personalizar cualquier estilo
|
|
121
|
+
|
|
122
|
+
Cada estilo es un `Style` que puedes copiar y modificar:
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from captionwave import get_style, CaptionGenerator
|
|
126
|
+
|
|
127
|
+
mi_estilo = get_style("hormozi").copy(
|
|
128
|
+
active_color="#FF3366",
|
|
129
|
+
sticker_color="#FF3366",
|
|
130
|
+
sticker_text_color="#FFFFFF",
|
|
131
|
+
font="Montserrat", # debe estar instalada en el sistema que renderiza
|
|
132
|
+
font_size=96,
|
|
133
|
+
max_words=2, # menos palabras por línea
|
|
134
|
+
position="lower", # "center" | "lower" | "upper"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
gen = CaptionGenerator(style=mi_estilo, rate="+20%")
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
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`.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Montar el video con FFmpeg
|
|
145
|
+
|
|
146
|
+
La librería te da `voz.mp3` + `subs.ass`. Para quemar los subtítulos sobre un fondo:
|
|
147
|
+
|
|
148
|
+
**Sobre un video de fondo** (gameplay, b-roll, etc.):
|
|
149
|
+
```bash
|
|
150
|
+
ffmpeg -i fondo.mp4 -i voz.mp3 \
|
|
151
|
+
-vf "scale=1080:1920,ass=subs.ass" \
|
|
152
|
+
-map 0:v -map 1:a -shortest salida.mp4
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Sobre una imagen fija**:
|
|
156
|
+
```bash
|
|
157
|
+
ffmpeg -loop 1 -i fondo.jpg -i voz.mp3 \
|
|
158
|
+
-vf "scale=1080:1920,ass=subs.ass" \
|
|
159
|
+
-c:v libx264 -pix_fmt yuv420p -c:a aac -shortest salida.mp4
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
> El `.ass` ya trae la resolución (1080×1920 por defecto). Si cambias la resolución usa `resolution=(W, H)` en `CaptionGenerator`.
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## ⚠️ Sobre los emojis (léelo)
|
|
167
|
+
|
|
168
|
+
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.
|
|
169
|
+
|
|
170
|
+
Dos cosas importantes sobre la **apariencia**:
|
|
171
|
+
|
|
172
|
+
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).
|
|
173
|
+
|
|
174
|
+
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:
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
r = gen.generate("...", out_emojis="emojis.json")
|
|
178
|
+
for e in r["emojis"]:
|
|
179
|
+
print(e) # {"word": "estrella", "emoji": "⭐", "start": 1.38, "dur": 0.58}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
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.
|
|
183
|
+
|
|
184
|
+
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.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Qué devuelve `generate(...)`
|
|
189
|
+
|
|
190
|
+
Un `dict` con:
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
{
|
|
194
|
+
"audio": "voz.mp3",
|
|
195
|
+
"ass": "subs.ass",
|
|
196
|
+
"srt": "subs.srt" | None,
|
|
197
|
+
"duration": 6.37, # segundos
|
|
198
|
+
"words": [{"word","start","dur"}, ...],
|
|
199
|
+
"lines": [{"text","start","end","emoji"}, ...],
|
|
200
|
+
"emojis": [{"word","emoji","start","dur"}, ...],
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Desarrollo y tests
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
pip install -e ".[test]"
|
|
210
|
+
pytest
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Los tests **no necesitan internet** (no llaman al TTS): cubren los estilos, el
|
|
214
|
+
troceado en líneas, la selección de emojis y la escritura de `.ass`/`.srt`.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Publicar tu propia copia
|
|
219
|
+
|
|
220
|
+
> **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`.
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
pip install build twine
|
|
224
|
+
python -m build
|
|
225
|
+
twine upload dist/*
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Para GitHub solo inicializa el repo y súbelo normal.
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Licencia
|
|
233
|
+
|
|
234
|
+
MIT © 2026 Wilfredo Guillén — ver [LICENSE](LICENSE).
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "captionwave"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Genera audio + subtítulos animados sincronizados (.ass/.srt) a partir de texto, con emojis compatibles con iOS."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "Wilfredo Guillén", email = "wilfredoguillensalazar@gmail.com" }]
|
|
14
|
+
keywords = ["subtitles", "captions", "tts", "ass", "shorts", "reels", "tiktok", "karaoke", "emoji"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.9",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
26
|
+
"Topic :: Multimedia :: Video",
|
|
27
|
+
"Topic :: Multimedia :: Sound/Audio :: Speech",
|
|
28
|
+
]
|
|
29
|
+
dependencies = [
|
|
30
|
+
"edge-tts>=7.0",
|
|
31
|
+
"emoji>=2.8",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.optional-dependencies]
|
|
35
|
+
# Mejora la precisión de la duración del audio (recomendado).
|
|
36
|
+
duration = ["mutagen>=1.45"]
|
|
37
|
+
# Para los ejemplos que montan el video.
|
|
38
|
+
video = ["moviepy>=2.0"]
|
|
39
|
+
# Para ejecutar la batería de tests (offline).
|
|
40
|
+
test = ["pytest>=7"]
|
|
41
|
+
|
|
42
|
+
[project.urls]
|
|
43
|
+
Homepage = "https://github.com/Fortex-GT/Captionwave"
|
|
44
|
+
Repository = "https://github.com/Fortex-GT/Captionwave"
|
|
45
|
+
Issues = "https://github.com/Fortex-GT/Captionwave/issues"
|
|
46
|
+
|
|
47
|
+
[tool.setuptools.packages.find]
|
|
48
|
+
where = ["src"]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
captionwave — genera audio + subtítulos animados sincronizados a partir de texto.
|
|
3
|
+
|
|
4
|
+
Salidas: .ass (animado), .srt (respaldo), audio (.mp3) y emojis con tiempos.
|
|
5
|
+
Tú montas el video con FFmpeg/MoviePy usando esos archivos.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .core import CaptionGenerator, chunk_words
|
|
9
|
+
from .styles import Style, PRESETS, get_style, list_styles, hex_to_ass
|
|
10
|
+
from .emojis import EmojiPicker
|
|
11
|
+
|
|
12
|
+
__version__ = "0.1.0"
|
|
13
|
+
__all__ = [
|
|
14
|
+
"CaptionGenerator",
|
|
15
|
+
"chunk_words",
|
|
16
|
+
"Style",
|
|
17
|
+
"PRESETS",
|
|
18
|
+
"get_style",
|
|
19
|
+
"list_styles",
|
|
20
|
+
"hex_to_ass",
|
|
21
|
+
"EmojiPicker",
|
|
22
|
+
]
|