đ„ Como montei um editor de vĂdeo com FFmpeg (corte e merge)
Fala pessoal, tudo certo?
No post anterior, mostrei como criei o Kronos usando APIs nativas do browser para gravar vĂdeos. Agora vou contar como implementei o editor de vĂdeo que permite cortar e juntar trechos usando FFmpeg. Ă uma feature que muita gente pediu e que transforma o Kronos de um simples gravador em uma ferramenta completa de edição!

O que o editor faz? (E por que ele existe)
Sempre que eu gravava vĂdeos longos, ficava frustrado por ter que usar outro software para cortar as partes desnecessĂĄrias ou juntar trechos interessantes. EntĂŁo pensei: "Por que nĂŁo integrar isso direto no Kronos?".
O resultado foi um editor com dois modos:
- Modo simples: Corta um pedaço do vĂdeo (tipo "tesoura digital")
- Modo avançado: Permite marcar vĂĄrios trechos e juntĂĄ-los em um Ășnico vĂdeo
A mĂĄgica acontece no backend com FFmpeg, mas a experiĂȘncia no frontend Ă© o que realmente importa. Bora ver como isso funciona!
O fluxo: Da tela do usuårio até o FFmpeg
Imagine que vocĂȘ estĂĄ editando um vĂdeo no Kronos. O processo Ă© como uma linha de produção:
đ Clicar no botĂŁo "Edit video" â đș Abrir editor â âïž Cortar/juntar â đŸ Exportar
No frontend, vocĂȘ interage com uma interface React que coleta suas escolhas (onde cortar, quais trechos juntar). Essas informaçÔes viajam por IPC atĂ© o processo principal do Electron, que coordena tudo com o FFmpeg.
// No VideoEditor.tsx - quando clica em export
const segments = editor.segments.map(s => ({ start: s.start, end: s.end }));
await exporter.exportVideo({
sourcePath: videoUrl,
mode: editor.editMode, // 'simple' ou 'advanced'
segments: segments,
// ... outros parĂąmetros
});
A experiĂȘncia no frontend: Editando como num jogo
As "pinças" do modo simples
O modo simples Ă© como usar uma tesoura digital. VocĂȘ tem duas "pinças" na timeline que definem onde o corte começa e termina.
// Exemplo do que acontece quando move as pinças
const [trimStart, setTrimStart] = useState(0);
const [trimEnd, setTrimEnd] = useState(videoDuration);
// Quando arrasta a pinça de inĂcio
const handleSetTrimStart = (time: number) => {
setTrimStart(Math.max(0, Math.min(time, trimEnd - 0.1)));
};
Na tela, vocĂȘ vĂȘ:
- A timeline com o vĂdeo todo
- Duas linhas verticais (as pinças) que vocĂȘ arrasta
- Um preview mostrando exatamente o que serĂĄ cortado
- O tempo total original vs final em tempo real
O modo avançado: Marcando e juntando trechos
Aqui Ă© onde fica divertido! Imagine que vocĂȘ tem um vĂdeo de 10 minutos, mas sĂł quer as partes legais: do minuto 2:30 atĂ© 3:45, depois do 7:15 atĂ© 8:00.
// Como funciona internamente
const [segments, setSegments] = useState<{start: number, end: number}[]>([]);
const [tempStart, setTempStart] = useState<number | null>(null);
const markStart = (currentTime: number) => {
setTempStart(currentTime);
};
const markEnd = (currentTime: number) => {
if (tempStart !== null && currentTime > tempStart) {
setSegments([...segments, { start: tempStart, end: currentTime }]);
setTempStart(null);
}
};
Na interface:
- VocĂȘ clica "Mark start" no tempo desejado
- Avança o vĂdeo atĂ© onde quer terminar
- Clica "Mark end"
- O segmento aparece na lista abaixo
- Repete para quantos trechos quiser
- Os segmentos sĂŁo concatenados na ordem que vocĂȘ marcou
O feedback visual que faz toda diferença
Nada de adivinhação! VocĂȘ vĂȘ em tempo real:
- Duração original do vĂdeo
- Duração final após cortes
- Quantidade de segmentos no modo avançado
- Preview do que serĂĄ exportado
// Mostrando as informaçÔes para o usuårio
<div className="flex items-center gap-4">
<div>Original: {formatTime(videoDuration)}</div>
<div>Final: {formatTime(editor.getTotalDuration())}</div>
{editor.editMode === 'advanced' && (
<div>{editor.segments.length} segments</div>
)}
</div>
O pipeline no backend: Onde o FFmpeg faz a mĂĄgica
Agora vamos para a parte técnica! No processo principal do Electron, uso o fluent-ffmpeg para orquestrar tudo. à como dar instruçÔes para um chef de cozinha.
Montando o "prato" com filter_complex
O FFmpeg trabalha com um conceito chamado filter_complex, que Ă© como uma receita de edição. Para cada trecho que vocĂȘ marcou, crio uma sĂ©rie de filtros:
// Exemplo simplificado do que acontece no VideoExportService
private buildAdvancedFilters(segments, hasAudio, speed, volume, setpts) {
const parts = [];
// Para cada segmento, crio filtros de corte
segments.forEach((segment, i) => {
// Corta o vĂdeo no tempo exato
parts.push(`[0:v]trim=${segment.start}:${segment.end},setpts=PTS-STARTPTS[v${i}]`);
if (hasAudio) {
// Corta o ĂĄudio sincronizado e ajusta volume/velocidade
parts.push(`[0:a]atrim=${segment.start}:${segment.end},asetpts=PTS-STARTPTS,volume=${volume}[a${i}]`);
}
});
// Junta todos os trechos
const concatFilter = segments.map((_, i) => `[v${i}][a${i}]`).join('');
parts.push(`${concatFilter}concat=n=${segments.length}:v=1:a=1[vout][aout]`);
return { filter: parts.join(';'), maps: ['-map', '[vout]', '-map', '[aout]'] };
}
O comando FFmpeg que Ă© executado
Quando vocĂȘ clica em export, o fluent-ffmpeg gera um comando como este:
FFmpeg start: ffmpeg -i ~/Videos/Daily_16-27-20.mp4 -y -filter_complex [0:v]trim=12.983458:32.145252,setpts=PTS-STARTPTS[v0];[0:a]atrim=12.983458:32.145252,asetpts=PTS-STARTPTS,volume=1.00[a0];[0:v]trim=36.622308:48.978979,setpts=PTS-STARTPTS[v1];[0:a]atrim=36.622308:48.978979,asetpts=PTS-STARTPTS,volume=1.00[a1];[0:v]trim=57.127219:71.9015,setpts=PTS-STARTPTS[v2];[0:a]atrim=57.127219:71.9015,asetpts=PTS-STARTPTS,volume=1.00[a2];[v0][a0][v1][a1][v2][a2]concat=n=3:v=1:a=1[vout][aout] -b:v 8000k -f mp4 -c:v libx264 -preset ultrafast -pix_fmt yuv420p -threads 2 -c:a aac -b:a 192k -map [vout] -map [aout] -y ~/Videos/edited-Daily_16-27-20-1755890861977.mp4
Traduzindo em humano:
- Pega o vĂdeo original
- Corta 3 trechos especĂficos (12s-32s, 36s-48s, 57s-71s)
- Ajusta o ĂĄudio de cada trecho para manter sincronismo
- Junta tudo em um Ășnico vĂdeo
- Define qualidade alta e formato MP4
- Salva com nome Ășnico
Boas prĂĄticas que salvei minha vida
Re-encode vs Stream Copy: A escolha da velocidade
Quando vocĂȘ edita um vĂdeo, o FFmpeg precisa "re-encodar" o conteĂșdo. Mas hĂĄ um truque: se vocĂȘ nĂŁo alterar velocidade ou qualidade, pode usar stream copy para manter os dados originais.
// No modo simples, quando não hå mudanças complexas
if (noComplexChanges) {
cmd.outputOptions(['-c', 'copy']); // Muito mais rĂĄpido!
} else {
cmd.videoBitrate(8000); // Re-encode necessĂĄrio
}
Sincronismo A/V: O maior desafio
VĂdeo e ĂĄudio precisam estar perfeitamente sincronizados. O segredo estĂĄ nos filtros setpts e asetpts:
// Para cada segmento cortado
`[0:v]trim=${start}:${end},setpts=PTS-STARTPTS[v0]` // VĂdeo
`[0:a]atrim=${start}:${end},asetpts=PTS-STARTPTS[a0]` // Ăudio
Isso reinicia a contagem de tempo de cada trecho, garantindo que ĂĄudio e vĂdeo comecem do zero quando sĂŁo concatenados.
Presets e compatibilidade: O equilĂbrio perfeito
Usei ultrafast para o modo avançado (vårios cortes) e veryfast para o simples:
// Para concatenação complexa: velocidade é mais importante
preset: 'ultrafast'
// Para corte simples: melhor qualidade
preset: 'veryfast'
E nunca esqueça do pix_fmt yuv420p para compatibilidade com players:
cmd.outputOptions(['-pix_fmt', 'yuv420p']); // Funciona em qualquer player
O resultado: Edição sem sair do app
O que antes levava 30 minutos em outro software agora leva 2 cliques no Kronos:
- Abrir o editor no vĂdeo desejado
- Escolher modo, marcar cortes
- Exportar e pronto!
A integração com o sistema de pastas do Kronos significa que o vĂdeo editado vai para a mesma pasta organizada, mantendo tudo no lugar certo.
E o futuro?
Ainda hĂĄ muito para melhorar:
- Timeline mais visual com thumbnails
- Efeitos visuais (transiçÔes, filtros)
- Edição de mĂșltiplos vĂdeos
- Correção automåtica de sincronismo A/V
Mas por hoje, Ă© isso! O editor jĂĄ estĂĄ salvando um monte de tempo na minha rotina de criação de conteĂșdo.
Quer experimentar? Baixe o Kronos em kronosrec.com e teste o editor com seus prĂłprios vĂdeos. Se curtiu, me conta nos comentĂĄrios o que achou!
Abraços e atĂ© a prĂłxima! đ