import gradio as gr import os import json import tempfile import shutil import re import subprocess import sys from pathlib import Path from grid_fixer_v2 import fix_grid_advanced def sanitize_filename(name): """Remove espaços e caracteres especiais do nome do ficheiro.""" name = re.sub(r'[\s]+', '_', name) name = re.sub(r'[^\w\-\.]', '', name) return name def process_audio(input_file, target_bpm, pitch_semitones, phase_lock, verbose): if input_file is None: return "Por favor, faz upload de um ficheiro de áudio.", "", "" try: base_name = sanitize_filename(Path(input_file).stem) # Guardar na pasta de trabalho atual (onde está o app_v2.py) # O Gradio permite servir ficheiros desta pasta por defeito output_dir = Path.cwd() / "output" output_dir.mkdir(parents=True, exist_ok=True) output_wav = str(output_dir / f"{base_name}_fixed.wav") bpm_value = target_bpm if target_bpm > 0 else None metadata = fix_grid_advanced( input_file, output_wav, target_bpm=bpm_value, pitch_semitones=float(pitch_semitones), phase_lock=phase_lock, verbose=verbose ) if metadata is None: return "❌ Erro no processamento. Verifica o terminal para detalhes.", "", "" # Ler metadados meta_path = str(Path(output_wav).with_suffix(".json")) if os.path.exists(meta_path): with open(meta_path, 'r', encoding='utf-8') as f: meta = json.load(f) else: meta = metadata pitch_info = f"{meta['pitch_shift_semitons']:+.1f} semitons" if meta['pitch_shift_semitons'] != 0 else "Sem alteração" status = (f"✅ Processamento concluído!\n\n" f"📊 BPM Original: {meta['original_bpm']:.2f}\n" f"🎯 BPM Alvo: {meta['target_bpm']:.2f}\n" f"🎵 Pitch: {pitch_info}\n" f"🔒 Phase-Lock: {'Ativo' if meta['phase_lock'] else 'Desativado'}\n" f"⚡ Modo: {'Metronómico (Linear)' if meta.get('is_metronomic') else 'Variável (Warp)'}\n" f"📈 Variação Rítmica: {meta.get('ibi_cv', 0)*100:.2f}%\n" f"🎹 Key Estimada: {meta.get('estimated_key', 'N/A')}\n" f"⏱️ Primeiro Beat: {meta['first_beat_time_original']:.3f}s\n" f"🌀 Fase Offset: {meta.get('phase_offset_degrees', 0):.1f}°\n" f"🔢 Beats Corrigidos: {meta['beats_count']}") file_info = f"📁 {output_wav}" return status, file_info, output_wav except Exception as e: import traceback traceback.print_exc() return f"❌ Erro: {str(e)}", "", "" def open_folder(): """Abre a pasta de saída no explorador de ficheiros.""" output_dir = Path.cwd() / "output" output_dir.mkdir(parents=True, exist_ok=True) if os.name == 'nt': # Windows os.startfile(str(output_dir)) elif os.name == 'posix': # Linux/Mac subprocess.call(['open' if sys.platform == 'darwin' else 'xdg-open', str(output_dir)]) return f"📂 Pasta aberta: {output_dir}" with gr.Blocks() as demo: gr.Markdown("# 🎛️ Forca-Grid v2") gr.Markdown("Beatgrid + Pitch + Phase") with gr.Row(): with gr.Column(): input_audio = gr.Audio(label="Upload Áudio", type="filepath") target_bpm = gr.Number(label="BPM Alvo (0 = auto)", value=0) pitch_shift = gr.Slider(label="Pitch (semitons)", minimum=-12, maximum=12, step=0.5, value=0) phase_lock = gr.Checkbox(label="Phase-Lock", value=True) verbose_mode = gr.Checkbox(label="Verbose", value=True) with gr.Row(): btn_process = gr.Button("🚀 Processar", variant="primary") btn_open = gr.Button("📂 Abrir Pasta de Saída") with gr.Column(): status_text = gr.Textbox(label="📋 Status / Metadados", interactive=False, lines=12) file_path = gr.Textbox(label="📁 Path do Ficheiro", interactive=False) # Preview de áudio - agora deve funcionar porque o ficheiro está na pasta permitida output_preview = gr.Audio(label="🔊 Preview", type="filepath") # Processamento btn_process.click( fn=process_audio, inputs=[input_audio, target_bpm, pitch_shift, phase_lock, verbose_mode], outputs=[status_text, file_path, output_preview] ) # Abrir pasta btn_open.click( fn=open_folder, inputs=[], outputs=[file_path] ) if __name__ == "__main__": # allowed_paths garante que o Gradio pode servir ficheiros da pasta output output_dir = str(Path.cwd() / "output") demo.launch( share=False, server_name="0.0.0.0", allowed_paths=[output_dir] )