Gradio / app.py
AlexandreScriptsMT's picture
Update app.py
52618fe verified
raw
history blame
4.65 kB
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 - BLOCKS) ---")
# 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)
# --- AQUI ESTA A MUDANÇA ---
# Usamos Blocks para ter controle total sobre o nome da API
with gr.Blocks(title="Renderizador AI V2") as demo:
gr.Markdown("# Renderizador AI Backend")
with gr.Row():
input_component = gr.JSON(label="JSON do Projeto")
output_component = gr.Video(label="Vídeo Final")
btn = gr.Button("Renderizar", variant="primary")
# O parametro api_name="predict" ABAIXO é o segredo.
# Ele obriga o Gradio a expor a rota /predict que seu App procura.
btn.click(
fn=generate_video,
inputs=input_component,
outputs=output_component,
api_name="predict"
)
if __name__ == "__main__":
demo.launch()