Spaces:
Runtime error
Runtime error
| import gradio as gr | |
| import json | |
| import base64 | |
| import tempfile | |
| import os | |
| import wave | |
| from moviepy.editor import ImageClip, AudioFileClip, concatenate_videoclips | |
| # Configurações de áudio do Gemini (Default: 24kHz, 16bit, Mono) | |
| SAMPLE_RATE = 24000 | |
| NUM_CHANNELS = 1 | |
| SAMPWIDTH = 2 # 16 bit = 2 bytes | |
| def pcm_to_wav(pcm_base64, output_path): | |
| """Decodifica Base64 PCM e salva como arquivo WAV com cabeçalhos corretos.""" | |
| try: | |
| # Decodificar string base64 para bytes | |
| pcm_bytes = base64.b64decode(pcm_base64) | |
| # Escrever arquivo WAV | |
| 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 ao converter audio: {e}") | |
| return False | |
| def base64_to_image(image_base64, output_path): | |
| """Decodifica imagem Base64 e salva 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 ao salvar imagem: {e}") | |
| return False | |
| def generate_video(project_json): | |
| """ | |
| Recebe um JSON com o manifesto do projeto, | |
| processa cenas e gera um MP4. | |
| """ | |
| try: | |
| # Se o gradio enviar como dict, usa direto, senão faz parse | |
| if isinstance(project_json, str): | |
| data = json.loads(project_json) | |
| else: | |
| data = project_json | |
| scenes_data = data.get("scenes", []) | |
| clips = [] | |
| # Criar diretório temporário para processamento | |
| with tempfile.TemporaryDirectory() as temp_dir: | |
| # Ordenar cenas por ID para garantir sequencia | |
| scenes_data.sort(key=lambda x: x.get("id", 0)) | |
| for i, scene in enumerate(scenes_data): | |
| scene_id = scene.get("id", i) | |
| print(f"Processando cena {scene_id}...") | |
| # Caminhos temporários | |
| img_path = os.path.join(temp_dir, f"scene_{scene_id}.jpg") | |
| audio_path = os.path.join(temp_dir, f"scene_{scene_id}.wav") | |
| # Extrair dados | |
| 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 {scene_id}: dados incompletos") | |
| continue | |
| # Salvar arquivos | |
| if not base64_to_image(img_b64, img_path): continue | |
| if not pcm_to_wav(audio_b64, audio_path): continue | |
| # Criar Clips Moviepy | |
| # Audio | |
| audio_clip = AudioFileClip(audio_path) | |
| # Imagem (duração = duração do áudio + 0.5s de respiro) | |
| duration = audio_clip.duration + 0.2 | |
| video_clip = ImageClip(img_path).set_duration(duration) | |
| # Juntar áudio na imagem | |
| video_clip = video_clip.set_audio(audio_clip) | |
| # Fade in/out suave para transição | |
| video_clip = video_clip.crossfadein(0.5) | |
| clips.append(video_clip) | |
| if not clips: | |
| return None | |
| # Concatenar tudo | |
| final_video = concatenate_videoclips(clips, method="compose") | |
| # Arquivo de saída persistente | |
| output_filename = "video_final.mp4" | |
| final_video.write_videofile( | |
| output_filename, | |
| fps=24, | |
| codec="libx264", | |
| audio_codec="aac", | |
| preset="medium" | |
| ) | |
| return output_filename | |
| except Exception as e: | |
| return f"Erro no processamento: {str(e)}" | |
| # Interface Gradio | |
| demo = gr.Interface( | |
| fn=generate_video, | |
| inputs=gr.JSON(label="Manifesto do Projeto (JSON)"), | |
| outputs=gr.Video(label="Vídeo Gerado"), | |
| title="Marian Studio Renderer", | |
| description="Backend de renderização para o Marian Studio AI via MoviePy." | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() |