Gradio / app.py
AlexandreScriptsMT's picture
Rename App.py to app.py
af7ad28 verified
raw
history blame
4.66 kB
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", [])
project_title = data.get("project", "video")
clips = []
temp_files = [] # Para limpar depois
# 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 (fora do temp dir que será deletado)
output_filename = f"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."
)
# Habilitar CORS para que seu App local possa chamar essa API
if __name__ == "__main__":
demo.launch(share=False, show_api=True, cors_allowed_origins=["*"])