Files
clei-flow/app/core/ffmpeg_engine.py

119 lines
4.6 KiB
Python
Executable File

import subprocess
import json
import os
from database import FFmpegProfile
from core.state import state
class FFmpegEngine:
def __init__(self, profile_id=None):
if profile_id:
self.profile = FFmpegProfile.get_by_id(profile_id)
else:
self.profile = FFmpegProfile.get_or_none(FFmpegProfile.is_active == True)
if not self.profile:
state.log("⚠️ AVISO: Nenhum perfil FFmpeg ativo! Usando defaults.")
def get_file_info(self, filepath):
cmd = ['ffprobe', '-v', 'quiet', '-print_format', 'json', '-show_streams', '-show_format', filepath]
try:
output = subprocess.check_output(cmd).decode('utf-8')
return json.loads(output)
except Exception as e:
state.log(f"Erro FFprobe: {e}")
return None
def get_duration(self, filepath):
try: return float(self.get_file_info(filepath)['format']['duration'])
except: return 0
def build_command(self, input_file, output_file):
if not self.profile: raise Exception("Perfil não configurado.")
p = self.profile
metadata = self.get_file_info(input_file)
if not metadata: raise Exception("Arquivo inválido.")
cmd = ['ffmpeg', '-y']
# --- CONFIGURAÇÃO DE HARDWARE OTIMIZADA ---
video_filters = []
if 'vaapi' in p.video_codec:
# TENTATIVA OTIMIZADA: HWAccel habilitado na entrada
# Isso reduz a CPU drasticamente se o decode for suportado
cmd.extend(['-hwaccel', 'vaapi'])
cmd.extend(['-hwaccel_device', '/dev/dri/renderD128'])
cmd.extend(['-hwaccel_output_format', 'vaapi'])
# Não precisamos de 'hwupload' se o output format já é vaapi
# Mas vamos deixar o filtro scale_vaapi caso precise redimensionar (opcional)
# video_filters.append('scale_vaapi=format=nv12')
elif 'qsv' in p.video_codec:
cmd.extend(['-hwaccel', 'qsv', '-c:v', 'h264_qsv'])
elif 'nvenc' in p.video_codec:
cmd.extend(['-hwaccel', 'cuda'])
# Input e Threads (Limita CPU se cair pra software decode)
cmd.extend(['-threads', '4', '-i', input_file])
# --- VÍDEO ---
cmd.extend(['-map', '0:v:0'])
if p.video_codec == 'copy':
cmd.extend(['-c:v', 'copy'])
else:
cmd.extend(['-c:v', p.video_codec])
# Filtros
if video_filters:
cmd.extend(['-vf', ",".join(video_filters)])
# Qualidade
if 'vaapi' in p.video_codec:
# -qp é o modo Qualidade Constante do VAAPI
# Se der erro, remova -qp e use -b:v 5M
cmd.extend(['-qp', str(p.crf)])
elif 'nvenc' in p.video_codec:
cmd.extend(['-cq', str(p.crf), '-preset', p.preset])
elif 'libx264' in p.video_codec:
cmd.extend(['-crf', str(p.crf), '-preset', p.preset])
# --- ÁUDIO (AAC Stereo) ---
allowed_langs = [l.strip().lower() for l in (p.audio_langs or "").split(',')]
audio_streams = [s for s in metadata['streams'] if s['codec_type'] == 'audio']
acount = 0
for s in audio_streams:
lang = s.get('tags', {}).get('language', 'und').lower()
if not allowed_langs or lang in allowed_langs or 'und' in allowed_langs:
cmd.extend(['-map', f'0:{s["index"]}'])
# AAC é leve e compatível
cmd.extend([f'-c:a:{acount}', 'aac', f'-b:a:{acount}', '192k', f'-ac:{acount}', '2'])
acount += 1
if acount == 0 and audio_streams:
cmd.extend(['-map', '0:a:0', '-c:a', 'aac', '-b:a', '192k'])
# --- LEGENDAS (Copy) ---
sub_allowed = [l.strip().lower() for l in (p.subtitle_langs or "").split(',')]
sub_streams = [s for s in metadata['streams'] if s['codec_type'] == 'subtitle']
scount = 0
for s in sub_streams:
lang = s.get('tags', {}).get('language', 'und').lower()
if not sub_allowed or lang in sub_allowed or 'und' in sub_allowed:
cmd.extend(['-map', f'0:{s["index"]}'])
cmd.extend([f'-c:s:{scount}', 'copy'])
scount += 1
# Metadados
clean_title = os.path.splitext(os.path.basename(output_file))[0]
cmd.extend(['-metadata', f'title={clean_title}'])
cmd.append(output_file)
state.log(f"🛠️ CMD: {' '.join(cmd)}")
return cmd