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()