O contexto
Projeto próprio. A ideia era simples de descrever e chata de executar: transformar vídeo longo — live, podcast, stream — em clips curtos publicados todo dia, em vários canais, sem alguém sentado assistindo.
O trabalho manual tem três partes. Achar o trecho bom, que é decisão. Cortar e formatar, que é mecânico. Escrever título e descrição, que é decisão de novo. A parte mecânica é a menor das três; o gargalo real é assistir o vídeo inteiro para achar dois minutos que prestam.
O problema
Automatizar isso não é um script. É um pipeline com estados e falhas em toda etapa: RSS repete evento, download falha na metade, transcrição custa dinheiro por minuto de áudio, plataforma de destino tem limite diário de publicação. Rodar duas vezes o mesmo vídeo significa pagar duas vezes por transcrição e publicar duplicado.
E tinha uma segunda exigência: eu precisava de visibilidade. Um pipeline sem painel é uma caixa preta que você só percebe quebrada quando parou de publicar há três dias.
Decisões técnicas
Dois serviços em vez de um monólito. O daemon Python processa vídeo e o painel Laravel mostra o que aconteceu. Poderia ser tudo Laravel com filas, ou tudo Python com um Flask servindo telas. Separei porque os dois têm perfil de carga oposto: o daemon é CPU e I/O pesados rodando continuamente (FFmpeg não é gentil), o painel é acesso esporádico de uma pessoa. No mesmo processo, um encode pesado deixaria o painel lento — exatamente no momento em que eu quero olhar o painel para entender o que está acontecendo.
Comunicação por API interna autenticada, não banco compartilhado. A saída fácil seria os dois serviços escreverem na mesma tabela. Rejeitei: banco compartilhado vira contrato implícito, e qualquer mudança de schema quebra o outro lado sem aviso. Com API, a fronteira é explícita e o Laravel nunca decide nada do pipeline — ele só registra o que o daemon já decidiu.
Filament em vez de painel à mão. O painel é ferramenta interna, para uma pessoa. Investir semanas em UI custom aí é desperdício direto. Filament dá CRUD de canais, cota e histórico de clips sobre os models existentes, e o tempo economizado foi para o pipeline, que é onde estava o valor.
Dois modelos de IA, não um. Transcrição roda Whisper via Groq; seleção de trecho e geração de título/descrição/tags rodam Claude. A tentação era usar um único provedor para tudo. Mas as etapas têm exigências diferentes: transcrição é a etapa mais repetida do pipeline e o que importa nela é velocidade e custo por minuto — Groq ganha nisso. Seleção de trecho é julgamento editorial, roda uma vez por vídeo, e vale pagar mais por qualidade de raciocínio.
Redis para deduplicação, cota e cache. RSS reemite o mesmo item; sem uma chave de deduplicação, o pipeline reprocessa. Redis guarda o que já foi visto, a cota diária por canal e metadados que não mudam. Poderia ser tudo em MySQL, mas essas três coisas são contadores e chaves de curta duração — é o tipo de dado que não merece uma tabela.
Round-robin entre canais. Sem isso, o canal com mais vídeos novos consome a cota do dia inteira e os canais de cadência baixa nunca publicam. O round-robin distribui a atenção do sistema por canal, não por volume de conteúdo.
O que ficou pronto
Pipeline rodando ponta a ponta: monitora RSS, baixa, transcreve, seleciona trecho, gera metadados, corta com FFmpeg, publica respeitando cota e alterna entre canais. Painel Laravel com histórico de clips, canais monitorados e estado da cota. Tudo em Docker, dois containers.
O que eu faria diferente hoje
Trocaria o daemon de laço próprio por uma fila de verdade desde o início. Hoje o controle de retentativa é meu; com uma fila, retry, backoff e dead-letter vêm de graça, e cada etapa do pipeline vira um job independente em vez de um passo dentro de um processo longo.
Interface do Sistema
