Apresentação do Projeto: Crom Video Gen & Miniflow GoComo arquitetamos um gerador de vídeos autônomo com IA (Python) e motor de renderização concorrente (Go)
Existe um grande mercado de SaaS para geração automática de vídeos. Mas a maioria dessas soluções depende de processamento pesado em nuvem ou de APIs caras e de código fechado.
Para ter controle total sobre a performance, decidimos construir um ecossistema local e modular composto por duas partes:
- Orquestrador de Agentes (Python): Responsável pela ingestão do briefing, modelagem da história (roteiro em 3 atos) e decupagem dos ativos de mídia.
- Motor de Renderização Concorrente (Go): Um motor de baixo nível que compõe layouts responsivos, sobreposições gráficas, síntese de voz (TTS) e mixagem de áudio nativa por meio do FFmpeg.
Abaixo, detalho as decisões de engenharia, arquitetura e soluções que tornaram este projeto viável.
🏗️ Fluxo de Trabalho (Workflow Híbrido)
A orquestração separa a camada de decisão criativa (IA) da camada de execução física (gráficos e áudio):
graph TD
A["📄 briefing.txt (Briefing)"] --> B["🐍 Orquestrador (Python)"]
B --> C["🤖 Pipeline de Agentes Especialistas"]
C --> D["🛡️ Revisor de Qualidade do Template"]
D -- "Reprovado (Auto-Correção)" --> C
D -- "Aprovado" --> E["🚀 Engine Go (Miniflow)"]
E --> F["🔊 Síntese TTS (Cromyvoice)"]
E --> G["🎞️ Renderizador de Frames (FFmpeg)"]
G --> H["🎬 video_final.mp4"]
subgraph "Pipeline de Agentes"
C1["🔍 Pesquisador (pesquisa.md)"] --> C2["✍️ Roteirista (roteiro.md)"]
C2 --> C3["🕵️ Revisor Narrativo"]
C3 --> C4["🎬 Diretor (direcao.json)"]
C4 --> C5["📦 Produtor (producao.json)"]
C5 --> C6["✂️ Editor (video_template.json)"]
end
🛠️ Desafios Técnicos de Engenharia e Como os Resolvemos
1. Robustez contra Alucinações de Caminhos de LLM
O maior problema prático ao usar IAs para editar arquivos JSON complexos (como o nosso video_template.json) é que os modelos frequentemente erram as estruturas de diretórios dos ativos de mídia local, escrevendo por exemplo "midias/video.mp4" quando o arquivo correto está na pasta "inputs/backgrounds/video.mp4".
Para blindar o motor contra falhas, adicionamos uma rotina de busca recursiva com fallback em Go na função ResolveRelativePaths. Se o caminho indicado pela IA falhar na checagem inicial de existência do arquivo, o motor faz uma busca recursiva inteligente (filepath.Walk) no diretório inputs/ pelo nome base do arquivo (basename):
pathDirect := filepath.Clean(filepath.Join(workspaceDir, el.Content))
if _, err := os.Stat(pathDirect); err != nil {
pathInputs := filepath.Clean(filepath.Join(workspaceDir, "inputs", el.Content))
if _, err2 := os.Stat(pathInputs); err2 == nil {
el.Content = pathInputs
} else {
// Fallback recursivo: busca o nome base em qualquer subdiretório de inputs/
baseName := filepath.Base(el.Content)
_ = filepath.Walk(filepath.Join(workspaceDir, "inputs"), func(path string, info os.FileInfo, err error) error {
if err == nil && !info.IsDir() && info.Name() == baseName {
el.Content = path
return filepath.SkipAll
}
return nil
})
}
}
2. Sincronismo Fino de Áudio (TTS) e Margens Naturais
Para evitar transições bruscas onde o narrador começa a falar no instante exato da mudança de slide, implementamos um mecanismo de controle temporal baseado em duas frentes:
- Duração Dinâmica por TTS: No arquivo Go tts.go, a duração do card não é estática. Ela é calculada após a geração do áudio e ajustada com uma folga fixa de 2 segundos:
$$\text{Duração} = \lceil\text{AudioDuration} + 2.0\text{s}\rceil$$ - Filtro de Atraso e Padding: No renderizador Go renderer.go, aplicamos o filtro
adelaydo FFmpeg (adelay=delays=1000:all=1) no canal de áudio da narração. Isso cria uma margem de 1.0 segundo de silêncio de entrada na cena, seguido pela locução fluida e finalizando com 1.0 segundo de silêncio de saída, gerando transições suaves de áudio e vídeo.
3. Layouts Responsivos e Contraste das Legendas
O texto precisa ser legível sobre qualquer tipo de vídeo ou imagem (sejam mídias claras, escuras ou saturadas). A nossa solução desenha as legendas em renderer.go com uma sombra/contorno de 2px em 4 direções diagonais:
subX := float64(res.Width) / 2.0
subY := float64(res.Height) * 0.88
maxWidth := float64(res.Width) * 0.85
// Desenha contorno preto em 4 diagonais para alto contraste
dc.SetHexColor("#000000")
dc.DrawStringWrapped(card.Narration, subX+2, subY+2, 0.5, 0.5, maxWidth, 1.4, gg.AlignCenter)
dc.DrawStringWrapped(card.Narration, subX-2, subY+2, 0.5, 0.5, maxWidth, 1.4, gg.AlignCenter)
dc.DrawStringWrapped(card.Narration, subX+2, subY-2, 0.5, 0.5, maxWidth, 1.4, gg.AlignCenter)
dc.DrawStringWrapped(card.Narration, subX-2, subY-2, 0.5, 0.5, maxWidth, 1.4, gg.AlignCenter)
// Desenha o texto branco principal por cima
dc.SetHexColor("#ffffff")
dc.DrawStringWrapped(card.Narration, subX, subY, 0.5, 0.5, maxWidth, 1.4, gg.AlignCenter)
No modo vertical (vídeos no formato retrato), a lógica do DrawCardState detecta a proporção da tela (height > width), empilha a imagem/vídeo centralizadamente no topo e desloca o bloco de texto para baixo aplicando uma escala de fonte segura (24px).
4. Concorrência e Pipeline Paralela
Go brilha quando o assunto é paralelismo. Dividimos o processamento gráfico de cada cena em workers concorrentes usando um pool paralelo gerenciado em Go. Isso nos permite renderizar múltiplos cartões simultaneamente, mantendo a CPU ocupada a quase 100%, reduzindo o tempo de processamento do vídeo de 5 minutos para pouco menos de 2 minutos.
🔗 Links e Repositórios do Projeto
Para quem quiser inspecionar e testar o código-fonte localmente:
- 🐍 Orquestrador de Agentes (Python): github.com/MrJc01/crom-video-gen
- 🚀 Motor de Renderização (Go): github.com/MrJc01/crom-vide-gen-miniflow
- 🔊 Sintetizador de Voz (Cromyvoice): github.com/MrJc01/cromyvoice
- 🎬 Vídeo de Teste Renderizado (YouTube): youtu.be/AdbD07_cimM
- 📄 Artigo Original no Blog da Crom: crom.run/blog/...
O que acharam desse modelo de arquitetura híbrida (Python para IA e Go para Computação Gráfica / FFmpeg)? Feedbacks e sugestões de otimização de renderização são muito bem-vindos nos comentários!