Spaces:
Runtime error
Runtime error
File size: 4,407 Bytes
137cb2f ee827e5 137cb2f ee827e5 137cb2f ee827e5 137cb2f ee827e5 137cb2f ee827e5 137cb2f ee827e5 137cb2f ee827e5 137cb2f ee827e5 137cb2f ee827e5 137cb2f ee827e5 137cb2f ee827e5 137cb2f ee827e5 137cb2f ee827e5 1607f62 ee827e5 137cb2f ee827e5 137cb2f 5ac40a1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | 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() |