Manual Prático de Criação ASS / Gravação com libass
1. Modelo de Cabeçalho (Copiar Diretamente)
[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. Quatro Armadilhas Fatais de Formatação (todas encontradas neste projeto)
- Timestamp deve ser
H:MM:SS.cc- Correto:
0:00:01.50,0:01:02.30 - Errado:
0:01.50(libass reportaBad timestamp, a linha inteira é descartada → vídeo sem legendas, mas ffmpeg retorna 0)
- Correto:
- Cores na linha Style não podem terminar com
&- Correto:
&H00F4F1EA,&H00F4F1EA,&H00140F11,&HFF000000 - Errado:
&H00F4F1EA&,...→ falha na linha Style → também "sucesso sem texto" - Cores em tags inline precisam de
&:\1c&H00F4F1EA&
- Correto:
- Diretório de fontes
fontsdirusar caminho relativo + diretório de trabalho- Colocar
fonts/no diretório de saída,cdpara esse diretório e usarass=x.ass:fontsdir=fonts. - Caminhos absolutos com dois-pontos (ex:
C:...) podem quebrar no filtergraph; caminhos relativos chineses funcionam (cwd pode conter chinês, mas parâmetros do filtro devem ser ASCII).
- Colocar
- Arquivo ASS deve ser salvo como
utf-8-sigcom nome ASCII- Nomes de arquivo em chinês em parâmetros de filtro são instáveis; use
_s4.ass.
- Nomes de arquivo em chinês em parâmetros de filtro são instáveis; use
Comando de diagnóstico (ver o que libass realmente lê):
& $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 Cor / Alpha
- Cor:
&HBBGGRR&(ordem Azul, Verde, Vermelho), ex: branco&H00F4F1EA&, preto tinta&H00140F11&, rosa&H00B35CFF&, lavanda&H00FFA4C4&, creme&H00D7EEF8&, dourado&H0054C9FF&. - Transparência
\1a:&H00&completamente opaco;&HFF&completamente transparente (hexadecimal maior = mais transparente). O bug de piscar na versão inicial era porque o alpha estava invertido. - PrimaryColour na linha Style é
&HAABBGGRR(AA primeiro, sem&no final).
4. Fontes
- libass usa fontconfig/directwrite, o nome da família deve estar correto. Verifique com PIL:
from PIL import ImageFont
ImageFont.truetype(r"...\fonts\Anton-Regular.ttf", 40).getname() # ('Anton','Regular')
- Fontes deste projeto: Anton / Archivo Black / Bebas Neue / Permanent Marker / Silkscreen / Rubik Glitch / Bungee / Monoton / Pirata One / UnifrakturCook; fontes do sistema Georgia, Microsoft YaHei podem ser usadas diretamente com
\fnGeorgia,\fnMicrosoft YaHei. - Permanent Marker no libass é aproximadamente 0.79× do
fs, ajuste o tamanho da fonte / bloco de cor com esse fator. - Caracteres ausentes (ex: ♥ ★) caem para ArialMT automaticamente, visíveis mas com glifos diferentes; para precisão use
\fnMicrosoft YaHei.
5. Referência Rápida de Tags de Animação
| Tag | Uso |
|---|---|
\fad(in,out) | Fade in/out em milissegundos |
\move(x1,y1,x2,y2,t1,t2) | Movimento (mutuamente exclusivo com \pos, move tem prioridade) |
\t(t1,t2,\tag...) | Interpolação temporizada, ex: \t(0,130,\fscx100\fscy100\blur(0)) |
\frz / \fscx / \fscy | Rotação/Escala (valores negativos podem inverter) |
\blur(n) | Desfoque (eficaz para palavras grandes aparecendo do desfoque para nítido) |
\clip(x1,y1,x2,y2) | Corte retangular (para fatias, varreduras, revelações) |
\org(x,y) | Ponto central de rotação/escala |
\p1 + desenho | m 0 0 l w 0 w h 0 h retângulo, etc. |
Armadilha: Dois \t consecutivos na mesma propriedade só executam um (ex: animação de encolhimento de linha guia); divida em dois eventos/duas renderizações.
Armadilha: Coordenadas de desenho \p1 usam o canto superior esquerdo como âncora, e libass desloca o bloco para cima em h/2; use \an7\pos(canto superior esquerdo) + coordenadas absolutas para maior estabilidade; rotação/varredura use \org para definir a origem.
Armadilha: Blocos de cor tipo marca-texto atrás das palavras, use camada de desenho \fscx0 → \t(0,150,\fscx100), com \org definindo a origem.
6. Comandos de Renderização
6.1 Gravação Direta (mais comum)
cd diretorio_saida
& $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 / volume de legendas em chinês ≈ 1–3 minutos.
6.2 Camada de Sobreposição Transparente (para usar em software de edição)
& $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
- Decodificação deve ser
-c:v libvpx-vp9; decodificação nativa vp9 perde alpha. - Alternativa compatível: MP4 com fundo preto + texto claro, software de edição usa mesclagem Screen/Lighten.
6.3 Verificação de Quadro (não olhe logs de renderização, veja a imagem)
& $ff -v error -ss 12.4 -i OUT.mp4 -frames:v 1 -q:v 2 -y _check.jpg
Atenção: colocar
-ssantes de-i(seek de entrada) pode, em alguns casos, redirecionar a timeline ASS em relação à saída, resultando em quadro em branco; para verificação use seek do lado da saída (-i OUT.mp4 -ss 12.4) ou extraia um trecho e selecione.
7. Tabela Rápida de Falhas
| Sintoma | Causa | Correção |
|---|---|---|
| Filme inteiro sem texto | Formato de timestamp / cor Style com & no final | Altere para H:MM:SS.cc; remova & final em cores Style |
| Algumas falas faltando | Timestamp errado ou start>end na fala | Verifique cada fala |
| Texto muito pequeno/fonte errada | Nome da família de fonte incorreto | Verifique com PIL getname() |
Bloco \p1 deslocado para cima/baixo | Âncora de desenho + libass desloca h/2 para cima | Use \an7 + coordenadas absolutas do canto superior esquerdo + quadro de calibração real |
| Centro de rotação errado | Centro padrão não é o desejado | Adicione \org(x,y) |
| Fade não funciona / pisca invertido | Semântica de alpha invertida | &H00 opaco, &HFF transparente |
| Renderização de 30s só com as primeiras 4 falas | Loop só percorreu as primeiras | Verifique o alcance do loop de geração de eventos |
| Caminho/nome de arquivo em chinês falha na renderização | Parâmetros do filtro com não-ASCII | Use nomes ASCII para ASS e vídeo base + cwd em chinês |
8. Esqueleto do Gerador (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() # ou inline no 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")
Implementações de referência: 字幕/build/styles_anim.py (S2/S4/S6), flare_wide.py, neon_wide.py, avoid_side2.py.