Gradio / app.py
AlexandreScriptsMT's picture
Update app.py
5ac40a1 verified
raw
history blame
4.41 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", [])
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()