File size: 4,649 Bytes
137cb2f
ee827e5
 
137cb2f
 
ee827e5
e69008b
 
137cb2f
f64427e
ee827e5
 
f64427e
137cb2f
ee827e5
f64427e
137cb2f
ee827e5
 
 
 
 
 
 
137cb2f
f64427e
ee827e5
137cb2f
ee827e5
f64427e
ee827e5
 
 
 
 
f64427e
ee827e5
137cb2f
ee827e5
52618fe
f64427e
 
 
 
 
ee827e5
f64427e
ee827e5
f64427e
 
 
 
ee827e5
 
 
 
f64427e
 
 
ee827e5
f64427e
ee827e5
f64427e
 
ee827e5
f64427e
 
 
 
 
 
 
 
 
ee827e5
f64427e
 
 
 
 
ee827e5
d652a79
 
f64427e
 
d652a79
 
f64427e
ee827e5
f9c3950
f64427e
ee827e5
 
f64427e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ee827e5
 
f64427e
 
 
137cb2f
52618fe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137cb2f
5ac40a1
f64427e
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
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()