| import os |
| import shutil |
| import subprocess |
| import tempfile |
| from pathlib import Path |
| from typing import List, Tuple |
|
|
| import gradio as gr |
|
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| def run_ffmpeg(cmd: List[str]) -> None: |
| proc = subprocess.run( |
| cmd, |
| stdout=subprocess.PIPE, |
| stderr=subprocess.PIPE, |
| text=True, |
| ) |
| if proc.returncode != 0: |
| raise RuntimeError(proc.stderr[-5000:]) |
|
|
|
|
| def safe_stem(path: str) -> str: |
| return Path(path).stem.replace(" ", "_") |
|
|
|
|
| def process_one_video( |
| input_path: str, |
| output_dir: Path, |
| target_width: int, |
| target_height: int, |
| target_fps: int, |
| crf: int, |
| preset: str, |
| ) -> Path: |
| input_path = str(Path(input_path).resolve()) |
| name = safe_stem(input_path) |
| output_path = output_dir / f"{name}_{target_fps}fps_{target_height}p.mp4" |
|
|
| |
| |
| vf = ( |
| f"minterpolate=fps={target_fps}:mi_mode=mci:mc_mode=aobmc:" |
| f"me_mode=bidir:me=epzs:vsbmc=1:scd=fdiff," |
| f"scale={target_width}:{target_height}:flags=lanczos" |
| ) |
|
|
| cmd = [ |
| "ffmpeg", |
| "-y", |
| "-i", input_path, |
| "-vf", vf, |
| "-c:v", "libx264", |
| "-preset", preset, |
| "-crf", str(crf), |
| "-pix_fmt", "yuv420p", |
| "-movflags", "+faststart", |
| str(output_path), |
| ] |
|
|
| run_ffmpeg(cmd) |
| return output_path |
|
|
|
|
| def batch_process( |
| videos: List[str], |
| target_width: int, |
| target_height: int, |
| target_fps: int, |
| crf: int, |
| preset: str, |
| progress=gr.Progress(track_tqdm=False), |
| ) -> Tuple[str, str]: |
| if not videos: |
| raise gr.Error("Envie pelo menos um vídeo.") |
|
|
| |
| target_width = int(target_width) |
| target_height = int(target_height) |
| target_fps = int(target_fps) |
| crf = int(crf) |
|
|
| if target_width % 2 != 0: |
| target_width += 1 |
| if target_height % 2 != 0: |
| target_height += 1 |
|
|
| workdir = Path(tempfile.mkdtemp(prefix="batch_cinematic_")) |
| outdir = workdir / "outputs" |
| outdir.mkdir(parents=True, exist_ok=True) |
|
|
| processed_files: List[Path] = [] |
| total = len(videos) |
|
|
| for idx, video in enumerate(videos, start=1): |
| progress((idx - 1) / total, desc=f"Processando {idx}/{total}: {Path(video).name}") |
| processed = process_one_video( |
| video, |
| outdir, |
| target_width=target_width, |
| target_height=target_height, |
| target_fps=target_fps, |
| crf=crf, |
| preset=preset, |
| ) |
| processed_files.append(processed) |
|
|
| progress(0.95, desc="Montando ZIP...") |
| zip_base = workdir / "batch_output" |
| zip_path = shutil.make_archive(str(zip_base), "zip", root_dir=outdir) |
| progress(1.0, desc="Concluído") |
|
|
| summary = ( |
| f"{len(processed_files)} vídeo(s) processado(s).\n" |
| f"Saída: {target_fps} fps | {target_width}x{target_height}\n" |
| f"Arquivo ZIP pronto." |
| ) |
| return zip_path, summary |
|
|
|
|
| with gr.Blocks(title="Batch Cinematic Processor") as demo: |
| gr.Markdown( |
| "# Batch Cinematic Processor\n" |
| "Envie vários vídeos e receba um ZIP com tudo processado em 60 fps e 1440p.\n" |
| "Sem reverse automático." |
| ) |
|
|
| with gr.Row(): |
| videos = gr.File( |
| label="Vídeos de entrada", |
| file_count="multiple", |
| type="filepath", |
| file_types=[".mp4", ".mov", ".mkv", ".webm", ".avi"], |
| ) |
|
|
| with gr.Row(): |
| target_width = gr.Number(label="Largura", value=2560, precision=0) |
| target_height = gr.Number(label="Altura", value=1440, precision=0) |
| target_fps = gr.Number(label="FPS de saída", value=60, precision=0) |
|
|
| with gr.Row(): |
| crf = gr.Slider(label="Qualidade (CRF menor = melhor)", minimum=14, maximum=24, value=18, step=1) |
| preset = gr.Dropdown( |
| label="Preset do encoder", |
| choices=["ultrafast", "superfast", "veryfast", "faster", "fast", "medium", "slow", "slower"], |
| value="slow", |
| ) |
|
|
| process_btn = gr.Button("Processar lote") |
|
|
| with gr.Row(): |
| zip_out = gr.File(label="ZIP final") |
| info = gr.Textbox(label="Status", lines=4) |
|
|
| process_btn.click( |
| fn=batch_process, |
| inputs=[videos, target_width, target_height, target_fps, crf, preset], |
| outputs=[zip_out, info], |
| ) |
|
|
|
|
| if __name__ == "__main__": |
| demo.launch() |