import gradio as gr import json import base64 import tempfile import os import wave from moviepy import * from moviepy.video.fx import FadeIn, FadeOut # Configurações de áudio padrão SAMPLE_RATE = 24000 NUM_CHANNELS = 1 SAMPWIDTH = 2 def pcm_to_wav(pcm_base64, output_path): """Converte PCM Base64 para WAV.""" try: pcm_bytes = base64.b64decode(pcm_base64) with wave.open(output_path, 'wb') as wav_file: wav_file.setnchannels(NUM_CHANNELS) wav_file.setsampwidth(SAMPWIDTH) wav_file.setframerate(SAMPLE_RATE) wav_file.writeframes(pcm_bytes) return True except Exception as e: print(f"Erro Audio: {e}") return False def base64_to_image(image_base64, output_path): """Salva imagem Base64 em arquivo.""" try: with open(output_path, "wb") as f: f.write(base64.b64decode(image_base64)) return True except Exception as e: print(f"Erro Imagem: {e}") return False def generate_video(project_json): print("--- INICIANDO RENDERIZAÇÃO (V2) ---") # Criar pasta temporária para processamento temp_dir_obj = tempfile.TemporaryDirectory() temp_dir = temp_dir_obj.name try: # Tratamento do input JSON if isinstance(project_json, str): try: data = json.loads(project_json) except: raise gr.Error("Erro: O texto fornecido não é um JSON válido.") else: data = project_json scenes_data = data.get("scenes", []) if not scenes_data: raise gr.Error("O JSON não contém a lista de cenas ('scenes').") clips = [] scenes_data.sort(key=lambda x: x.get("id", 0)) for i, scene in enumerate(scenes_data): print(f"Processando cena {i}...") img_path = os.path.join(temp_dir, f"scene_{i}.jpg") audio_path = os.path.join(temp_dir, f"scene_{i}.wav") img_b64 = scene.get("image_data_base64") audio_b64 = scene.get("audio_data_base64") if not img_b64 or not audio_b64: print(f"Pulando cena {i}: Falta audio ou imagem.") continue if not base64_to_image(img_b64, img_path): continue if not pcm_to_wav(audio_b64, audio_path): continue try: # --- SINTAXE MOVIEPY 2.0 --- audio_clip = AudioFileClip(audio_path) # Definir duração da imagem = audio + 0.1s duration = audio_clip.duration + 0.1 video_clip = ImageClip(img_path).with_duration(duration) # Juntar Audio video_clip = video_clip.with_audio(audio_clip) # Aplicar Fade In video_clip = video_clip.with_effects([FadeIn(duration=0.5)]) clips.append(video_clip) except Exception as e_clip: print(f"Erro na montagem do clipe {i}: {e_clip}") if not clips: raise gr.Error("Nenhum clipe foi gerado. Verifique os dados Base64.") print(f"Concatenando {len(clips)} cenas...") final_video = concatenate_videoclips(clips, method="compose") output_path = os.path.join(temp_dir, "output_final.mp4") final_video.write_videofile( output_path, fps=24, codec="libx264", audio_codec="aac", preset="ultrafast", logger=None ) print("Vídeo gerado com sucesso!") return output_path except Exception as e: error_msg = f"Erro fatal no backend: {str(e)}" print(error_msg) raise gr.Error(error_msg) # Interface Gradio demo = gr.Interface( fn=generate_video, inputs=gr.JSON(label="JSON do Projeto"), outputs=gr.Video(label="Vídeo Final"), title="Renderizador AI V2 (Gradio 5 + MoviePy 2)", allow_flagging="never" ) if __name__ == "__main__": demo.launch()