1

Pitch: Apresentação do Projeto: Crom Video Gen & Miniflow Go - Como 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:

  1. 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.
  2. 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 adelay do 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.


Para quem quiser inspecionar e testar o código-fonte localmente:


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!

Carregando publicação patrocinada...