Manual práctico para escribir ASS / quemar con libass
1. Plantilla de cabecera (copia directa)
[Script Info]
ScriptType: v4.00+
PlayResX: 2560
PlayResY: 1080
WrapStyle: 2
ScaledBorderAndShadow: yes
YCbCr Matrix: TV.709
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: X,Microsoft YaHei,100,&H00F4F1EA,&H00F4F1EA,&H00140F11,&HFF000000,0,0,0,0,100,100,0,0,1,0,0,5,30,30,30,1
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:00:00.39,0:00:03.75,X,,0,0,0,,{\an5\pos(500,300)\fnAnton\fs150\1c&H00F4F1EA&\bord3\3c&H00140F11&\frz-4\fscx118\fscy118\t(0,130,\fscx100\fscy100)\fad(100,100)}WALK
2. Cuatro errores fatales de formato (todos los hemos sufrido en este proyecto)
- Timestamp debe ser
H:MM:SS.cc- Correcto:
0:00:01.50,0:01:02.30 - Incorrecto:
0:01.50(libass muestraBad timestamp, la línea entera se descarta → el video no tiene texto, pero ffmpeg devuelve 0)
- Correcto:
- El color en la línea Style no debe terminar con
&- Correcto:
&H00F4F1EA,&H00F4F1EA,&H00140F11,&HFF000000 - Incorrecto:
&H00F4F1EA&,...→ falla el parseo de la línea Style → el resultado es el mismo: «éxito pero sin texto» - Las etiquetas de color en línea sí deben llevar
&:\1c&H00F4F1EA&
- Correcto:
- El directorio de fuentes
fontsdirdebe ser una ruta relativa + directorio de trabajo- Coloca
fonts/en el directorio de salida, hazcda ese directorio y luegoass=x.ass:fontsdir=fonts. - Las rutas absolutas con letra de unidad y dos puntos suelen fallar en el filtergraph; las rutas relativas con caracteres chinos no dan problema (el cwd puede ser en chino, pero los parámetros del filtro deben ser lo más ASCII posible).
- Coloca
- El archivo ASS debe escribirse con codificación
utf-8-sigy nombre de archivo ASCII- Los nombres de archivo con caracteres chinos en los parámetros del filtro son inestables; usa nombres como
_s4.ass.
- Los nombres de archivo con caracteres chinos en los parámetros del filtro son inestables; usa nombres como
Comando de diagnóstico (para ver qué lee realmente libass):
& $ff -hide_banner -v verbose -f lavfi -i "color=c=0x303040:s=2560x1080:r=30:d=2" `
-vf "ass=_s4.ass:fontsdir=fonts" -c:v libx264 -crf 22 -pix_fmt yuv420p _t.mp4 2>&1 | Select-String 'Bad timestamp|Style|font file'
3. Semántica de color / alfa
- Color:
&HBBGGRR&(orden azul-verde-rojo), ej: blanco&H00F4F1EA&, negro tinta&H00140F11&, rosa&H00B35CFF&, lavanda&H00FFA4C4&, crema&H00D7EEF8&, dorado&H0054C9FF&. - Transparencia
\1a:&H00&completamente opaco;&HFF&completamente transparente (a mayor hexadecimal, más transparencia). El fallo de parpadeo en la versión temprana fue por invertir el alfa. - PrimaryColour en la línea Style es
&HAABBGGRR(AA al principio, sin & final).
4. Fuentes
- libass usa fontconfig/directwrite; el nombre de la familia debe ser exacto. Compruébalo con PIL:
from PIL import ImageFont
ImageFont.truetype(r"...\fonts\Anton-Regular.ttf", 40).getname() # ('Anton','Regular')
- Fuentes de este proyecto: Anton / Archivo Black / Bebas Neue / Permanent Marker / Silkscreen / Rubik Glitch / Bungee / Monoton / Pirata One / UnifrakturCook; las fuentes del sistema Georgia, Microsoft YaHei se pueden usar directamente con
\fnGeorgia,\fnMicrosoft YaHei. - Permanent Marker en libass equivale aproximadamente a 0.79×fs; hay que ampliar el tamaño de letra / bloque de color según este factor.
- Los caracteres faltantes (como ♥ ★) se reemplazan automáticamente por ArialMT; se muestran pero con diferente glifo; para precisión usa
\fnMicrosoft YaHei.
5. Referencia rápida de etiquetas de animación
| Etiqueta | Uso |
|---|---|
\fad(in,out) | Fundido de entrada/salida en milisegundos |
\move(x1,y1,x2,y2,t1,t2) | Desplazamiento (elegir entre \pos y \move; \move tiene prioridad) |
\t(t1,t2,\tag...) | Interpolación temporizada, ej. \t(0,130,\fscx100\fscy100\blur(0)) |
\frz / \fscx / \fscy | Rotación / escala (valores negativos producen volteo) |
\blur(n) | Desenfoque (muy efectivo para que una palabra grande aparezca borrosa y luego nítida) |
\clip(x1,y1,x2,y2) | Recorte rectangular (para rebanados/barridos/apariciones) |
\org(x,y) | Punto central de rotación / escala |
\p1 + dibujo | Rectángulo m 0 0 l w 0 w h 0 h etc. |
Error común: dos \t consecutivos sobre la misma propiedad solo se toma uno (por ejemplo, animación de contracción de línea de regla); hay que dividirlo en dos eventos o dos redibujados.
Error común: las coordenadas de dibujo \p1 usan el vértice superior izquierdo como ancla, y libass además desplaza todo el bloque hacia arriba h/2; lo más seguro es usar \an7\pos(esquina superior izquierda) + coordenadas absolutas, y para rotación/barrido usar \org para fijar el origen.
Error común: para que un bloque de color resalte detrás de una palabra «barriendo», se usa una capa de dibujo con \fscx0 → \t(0,150,\fscx100) y el origen con \org.
6. Comandos de renderizado
6.1 Quemado directo (el más usado)
cd directorio_de_salida
& $ff -v error -y -i _base_30s.mp4 -vf "ass=_s4.ass:fontsdir=fonts" `
-c:v libx264 -preset medium -crf 16 -pix_fmt yuv420p -movflags +faststart OUT.mp4
30s / 2560×1080 / cantidad de subtítulos en chino ≈ 1–3 minutos.
6.2 Superposición transparente (para usar en software de edición)
& $ff -v error -y -f lavfi -i "[email protected]:s=2560x1080:r=30,format=rgba" `
-vf "fps=30,ass=_sub.ass:alpha=1" -t 154.6 `
-c:v libvpx-vp9 -pix_fmt yuva420p -b:v 0 -crf 30 -row-mt 1 -auto-alt-ref 0 `
-metadata:s:v:0 alpha_mode=1 OUT_alpha.webm
- El códec de decodificación debe ser
-c:v libvpx-vp9; la decodificación nativa vp9 pierde el canal alfa. - Superposición por «filtro de color» (mp4 compatible): fondo negro + texto brillante; el software de edición usa modos de fusión Screen/Lighten.
6.3 Extracción de fotograma para verificación (no mires los logs de renderizado, mira la imagen)
& $ff -v error -ss 12.4 -i OUT.mp4 -frames:v 1 -q:v 2 -y _check.jpg
Nota: Cuando
-ssse coloca antes de-i(búsqueda en entrada), en algunos casos la línea de tiempo de ASS se redirige con respecto a la salida y la previsualización sale en blanco; para verificar la extracción usa búsqueda en lado de salida (-i OUT.mp4 -ss 12.4) o extrae un segmento completo y luego selecciona.
7. Tabla rápida de fallos
| Síntoma | Causa | Solución |
|---|---|---|
| El video completo no tiene ni una letra | Formato de timestamp / & final en colores de Style | Cambiar a H:MM:SS.cc; quitar & final de colores de Style |
| Faltan algunas líneas | Timestamp de esa línea mal escrito o start>end | Revisar línea por línea |
| Letra muy pequeña / tipografía incorrecta | El nombre de la familia de fuente es incorrecto | Verificar con getname() de PIL |
Bloque \p1 desplazado hacia arriba/abajo | Anclaje de dibujo + libass desplaza h/2 hacia arriba | Usar \an7 + coordenadas absolutas de la esquina superior izquierda + fotograma de calibración real |
| Centro de rotación incorrecto | El centro predeterminado no es el deseado | Añadir \org(x,y) |
| Fundido no funciona / parpadeo invertido | La semántica del alfa está invertida | &H00 es opaco, &HFF es transparente |
| Renderizado de 30s solo muestra las primeras 4 líneas | El bucle solo recorrió las primeras líneas | Revisar el rango del bucle de generación de eventos |
| Fallo al renderizar con rutas/nombres de archivo en chino | Parámetros del filtro con caracteres no ASCII | Usar nombres de archivo ASCII para ASS y video base + cwd en chino |
8. Esqueleto de generador (Python)
def ts(sec):
h=int(sec//3600); m=int(sec%3600//60)
return "%d:%02d:%05.2f" % (h,m,sec-h*3600-m*60)
class Ass:
def __init__(self,path,w=2560,h=1080): self.ev=[]; self.path=path; self.w=w; self.h=h
def add(self,layer,t0,t1,tags,text): self.ev.append((layer,t0,t1,tags,text))
def write(self):
head = open("template_header.ass",encoding="utf-8").read() # o incluir el encabezado directamente en el código
lines=[]
for layer,t0,t1,tags,text in sorted(self.ev,key=lambda e:(e[0],e[1])):
lines.append("Dialogue: %d,%s,%s,X,,0,0,0,,{%s}%s"%(layer,ts(t0),ts(t1),tags,text))
open(self.path,"w",encoding="utf-8-sig",newline="\n").write(head+"\n".join(lines)+"\n")
Implementaciones de referencia: 字幕/build/styles_anim.py (S2/S4/S6), flare_wide.py, neon_wide.py, avoid_side2.py.