Processamento em Lote e Corte de Vídeos Utilizando Python e FFmpeg

Comandos Essenciais do FFmpeg

Para automatizar a edição de vídeos, como a remoção de introduções e encerramentos, o ffmpeg e o ffprobe são ferramentas fundamentais. Abaixo estão os comandos base que serão integrados ao script Python.

1. Obtenção da Duração do Vídeo

Utilizamos o ffprobe para extrair a duração exata do arquivo. Para facilitar o parsing no Python, configuramos a saída para retornar apenas o valor numérico, sem formatação extra.

ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "caminho_do_video.mp4"

2. Corte do Vídeo sem Recodificação

O comando abaixo extrai um segemnto do vídeo copiando os streams de áudio e vídeo diretamente (-c copy), o que garante um processamento extremamente rápido.

ffmpeg -i "entrada.mp4" -ss 00:02:35 -t 900 -c copy -y "saida.mp4"
  • -ss: Ponto de início do corte (formato hh:mm:ss ou segundos).
  • -t: Duração do segmento a ser extraído a partir do ponto de início.
  • -c copy: Copia os codecs originais, evitando a recodificação.
  • -y: Sobrescreve o arquivo de saída caso já exista.

Integração Python com Comandos de Sistema

A biblioteca nativa subprocess é a abordagem recomendada para executar binários externos. Passar os argumentos como uma lista, em vez de uma string única, previne erros de injeção e problemas com espaços nos nomes de arquivos.

import subprocess

def executar_comando(cmd_list):
    try:
        resultado = subprocess.run(
            cmd_list, 
            check=True, 
            capture_output=True, 
            text=True,
            encoding='utf-8'
        )
        return resultado.stdout.strip()
    except subprocess.CalledProcessError as erro:
        print(f"Erro na execução: {erro.stderr}")
        return None

Implementação do Processamento em Lote

Módulo de Utilitários

Centralizamos as funções de filtragem de arquivos e conversão de tempo em um módulo auxiliar para manter o código principal limpo.

# utils.py
import re
from pathlib import Path
from typing import List, Optional

def filtrar_arquivos_midia(diretorio: str, extensoes: List[str], 
                           ids_incluir: Optional[List[int]] = None, 
                           ids_excluir: Optional[List[int]] = None) -> List[Path]:
    """Retorna uma lista de objetos Path filtrados por extensão e padrões numéricos no nome."""
    caminho_base = Path(diretorio)
    arquivos_validos = []
    
    for ext in extensoes:
        for arquivo in caminho_base.glob(f"*{ext}"):
            if not arquivo.is_file():
                continue
            
            # Extrai o primeiro número do nome do arquivo como identificador
            numeros = re.findall(r'\d+', arquivo.name)
            if not numeros:
                continue
                
            arquivo_id = int(numeros[0])
            
            if ids_excluir and arquivo_id in ids_excluir:
                continue
            if ids_incluir and arquivo_id not in ids_incluir:
                continue
                
            arquivos_validos.append(arquivo)
            
    return arquivos_validos

def converter_tempo_para_segundos(timestamp: str) -> int:
    """Converte string de tempo (mm:ss ou hh:mm:ss) para segundos totais."""
    partes = list(map(int, timestamp.split(':')))
    if len(partes) == 2:
        return partes[0] * 60 + partes[1]
    elif len(partes) == 3:
        return partes[0] * 3600 + partes[1] * 60 + partes[2]
    return 0

Corte em Lote com Timestamps Fixos

Este script aplica o mesmo corte de tempo para todos os vídeos encontrados no diretório.

import subprocess
from pathlib import Path
from utils import filtrar_arquivos_midia, converter_tempo_para_segundos

DIRETORIO_ORIGEM = "./videos_originais"
DIRETORIO_SAIDA = "./videos_cortados"
EXTENSOES = [".mp4", ".mkv"]

# Configurações de tempo fixo
INICIO_CORTE = "00:02:35"
FIM_CORTE = "00:17:42"

def processar_lote_fixo():
    Path(DIRETORIO_SAIDA).mkdir(parents=True, exist_ok=True)
    videos = filtrar_arquivos_midia(DIRETORIO_ORIGEM, EXTENSOES)
    
    duracao_corte = converter_tempo_para_segundos(FIM_CORTE) - converter_tempo_para_segundos(INICIO_CORTE)
    
    for video in videos:
        caminho_saida = Path(DIRETORIO_SAIDA) / video.name
        
        comando_ffmpeg = [
            "ffmpeg", "-i", str(video),
            "-ss", INICIO_CORTE,
            "-t", str(duracao_corte),
            "-c", "copy", "-y",
            str(caminho_saida)
        ]
        
        print(f"Processando: {video.name}...")
        subprocess.run(comando_ffmpeg, check=True, capture_output=True)
        print(f"Concluído: {caminho_saida.name}")

if __name__ == "__main__":
    processar_lote_fixo()

Processamento Dinâmico Baseado em Configuração CSV

Quando os tempos de introdução e encerramento variam entre os episódios, utilizamos um arquivo CSV para mapear os timestamps individualmente.

Estrutura do arquivo config_cortes.csv:

id_episodio,inicio,fim
120,00:02:16,00:18:28
122,00:02:16,00:17:25
123,00:02:16,00:19:11

Script de processamento dinâmico:

import csv
import subprocess
from pathlib import Path
from utils import filtrar_arquivos_midia, converter_tempo_para_segundos

def processar_lote_dinamico():
    diretorio_saida = Path("./videos_cortados_dinamico")
    diretorio_saida.mkdir(parents=True, exist_ok=True)
    
    # Carregar configurações do CSV
    configuracoes = {}
    with open("config_cortes.csv", mode='r', encoding='utf-8') as arquivo_csv:
        leitor = csv.DictReader(arquivo_csv)
        for linha in leitor:
            configuracoes[int(linha['id_episodio'])] = {
                'inicio': linha['inicio'],
                'fim': linha['fim']
            }
    
    # Filtrar apenas os vídeos que possuem entrada no CSV
    videos = filtrar_arquivos_midia("./videos_originais", [".mp4", ".mkv"], ids_incluir=list(configuracoes.keys()))
    
    for video in videos:
        # Extrair o ID do vídeo para buscar a configuração correspondente
        import re
        video_id = int(re.findall(r'\d+', video.name)[0])
        config = configuracoes[video_id]
        
        inicio_seg = converter_tempo_para_segundos(config['inicio'])
        fim_seg = converter_tempo_para_segundos(config['fim'])
        duracao = fim_seg - inicio_seg
        
        caminho_saida = diretorio_saida / video.name
        
        comando = [
            "ffmpeg", "-i", str(video),
            "-ss", str(inicio_seg),
            "-t", str(duracao),
            "-c", "copy", "-y",
            str(caminho_saida)
        ]
        
        subprocess.run(comando, check=True, capture_output=True)
        print(f"Corte dinâmico aplicado com sucesso em: {video.name}")

if __name__ == "__main__":
    processar_lote_dinamico()

Consolidação de Múltiplos Segmentos

Em cenários onde o conteúdo útil está fragmentado (ex: cenas pós-créditos), é necessário extrair múltiplas partes e concatená-las. O método mais eficiente é utilizar o demuxer concat do FFmpeg.

Primeiro, gere um arquivo de texto (ex: lista_segmentos.txt) contendo os caminhos dos arquivos temporários:

file 'parte_intro.mp4'
file 'parte_principal.mp4'
file 'parte_pos_creditos.mp4'

Em seguida, execute o comando de mesclagem via Python:

def mesclar_segmentos(arquivo_lista: str, arquivo_saida: str):
    comando_concat = [
        "ffmpeg", "-f", "concat", 
        "-safe", "0", 
        "-i", arquivo_lista, 
        "-c", "copy", 
        arquivo_saida
    ]
    subprocess.run(comando_concat, check=True)

Tags: Python FFmpeg subprocess video-processing automation

Publicado em 7-30 01:37