AlexandreScriptsMT commited on
Commit
f64427e
·
verified ·
1 Parent(s): a29cebc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +92 -74
app.py CHANGED
@@ -4,20 +4,17 @@ import base64
4
  import tempfile
5
  import os
6
  import wave
7
- from moviepy.editor import ImageClip, AudioFileClip, concatenate_videoclips
8
 
9
- # Configurações de áudio do Gemini (Default: 24kHz, 16bit, Mono)
10
  SAMPLE_RATE = 24000
11
  NUM_CHANNELS = 1
12
- SAMPWIDTH = 2 # 16 bit = 2 bytes
13
 
14
  def pcm_to_wav(pcm_base64, output_path):
15
- """Decodifica Base64 PCM e salva como arquivo WAV com cabeçalhos corretos."""
16
  try:
17
- # Decodificar string base64 para bytes
18
  pcm_bytes = base64.b64decode(pcm_base64)
19
-
20
- # Escrever arquivo WAV
21
  with wave.open(output_path, 'wb') as wav_file:
22
  wav_file.setnchannels(NUM_CHANNELS)
23
  wav_file.setsampwidth(SAMPWIDTH)
@@ -25,106 +22,127 @@ def pcm_to_wav(pcm_base64, output_path):
25
  wav_file.writeframes(pcm_bytes)
26
  return True
27
  except Exception as e:
28
- print(f"Erro ao converter audio: {e}")
29
  return False
30
 
31
  def base64_to_image(image_base64, output_path):
32
- """Decodifica imagem Base64 e salva em arquivo."""
33
  try:
34
  with open(output_path, "wb") as f:
35
  f.write(base64.b64decode(image_base64))
36
  return True
37
  except Exception as e:
38
- print(f"Erro ao salvar imagem: {e}")
39
  return False
40
 
41
  def generate_video(project_json):
42
- """
43
- Recebe um JSON com o manifesto do projeto,
44
- processa cenas e gera um MP4.
45
- """
 
 
 
 
 
46
  try:
47
- # Se o gradio enviar como dict, usa direto, senão faz parse
48
  if isinstance(project_json, str):
49
- data = json.loads(project_json)
 
 
 
 
50
  else:
51
  data = project_json
52
 
53
  scenes_data = data.get("scenes", [])
54
-
 
 
55
  clips = []
 
56
 
57
- # Criar diretório temporário para processamento
58
- with tempfile.TemporaryDirectory() as temp_dir:
59
 
60
- # Ordenar cenas por ID para garantir sequencia
61
- scenes_data.sort(key=lambda x: x.get("id", 0))
62
-
63
- for i, scene in enumerate(scenes_data):
64
- scene_id = scene.get("id", i)
65
- print(f"Processando cena {scene_id}...")
66
-
67
- # Caminhos temporários
68
- img_path = os.path.join(temp_dir, f"scene_{scene_id}.jpg")
69
- audio_path = os.path.join(temp_dir, f"scene_{scene_id}.wav")
70
-
71
- # Extrair dados
72
- img_b64 = scene.get("image_data_base64")
73
- audio_b64 = scene.get("audio_data_base64")
74
-
75
- if not img_b64 or not audio_b64:
76
- print(f"Pulando cena {scene_id}: dados incompletos")
77
- continue
78
 
79
- # Salvar arquivos
80
- if not base64_to_image(img_b64, img_path): continue
81
- if not pcm_to_wav(audio_b64, audio_path): continue
 
 
 
82
 
83
- # Criar Clips Moviepy
84
- # Audio
85
  audio_clip = AudioFileClip(audio_path)
86
 
87
- # Imagem (duração = duração do áudio + 0.5s de respiro)
88
- duration = audio_clip.duration + 0.2
89
- video_clip = ImageClip(img_path).set_duration(duration)
90
 
91
- # Juntar áudio na imagem
92
- video_clip = video_clip.set_audio(audio_clip)
93
 
94
- # Fade in/out suave para transição
95
- video_clip = video_clip.crossfadein(0.5)
 
 
96
 
97
  clips.append(video_clip)
98
-
99
- if not clips:
100
- return None
101
-
102
- # Concatenar tudo
103
- final_video = concatenate_videoclips(clips, method="compose")
104
-
105
- # Arquivo de saída persistente
106
- output_filename = "video_final.mp4"
107
- final_video.write_videofile(
108
- output_filename,
109
- fps=24,
110
- codec="libx264",
111
- audio_codec="aac",
112
- preset="medium"
113
- )
114
-
115
- return output_filename
 
 
 
 
 
 
 
 
116
 
117
  except Exception as e:
118
- return f"Erro no processamento: {str(e)}"
 
 
119
 
120
- # Interface Gradio
 
121
  demo = gr.Interface(
122
  fn=generate_video,
123
- inputs=gr.JSON(label="Manifesto do Projeto (JSON)"),
124
- outputs=gr.Video(label="Vídeo Gerado"),
125
- title="Marian Studio Renderer",
126
- description="Backend de renderização para o Marian Studio AI via MoviePy."
127
  )
128
 
129
  if __name__ == "__main__":
130
- demo.launch()
 
 
4
  import tempfile
5
  import os
6
  import wave
7
+ from moviepy import * from moviepy.video.fx import FadeIn, FadeOut
8
 
9
+ # Configurações de áudio padrão
10
  SAMPLE_RATE = 24000
11
  NUM_CHANNELS = 1
12
+ SAMPWIDTH = 2
13
 
14
  def pcm_to_wav(pcm_base64, output_path):
15
+ """Converte PCM Base64 para WAV."""
16
  try:
 
17
  pcm_bytes = base64.b64decode(pcm_base64)
 
 
18
  with wave.open(output_path, 'wb') as wav_file:
19
  wav_file.setnchannels(NUM_CHANNELS)
20
  wav_file.setsampwidth(SAMPWIDTH)
 
22
  wav_file.writeframes(pcm_bytes)
23
  return True
24
  except Exception as e:
25
+ print(f"Erro Audio: {e}")
26
  return False
27
 
28
  def base64_to_image(image_base64, output_path):
29
+ """Salva imagem Base64 em arquivo."""
30
  try:
31
  with open(output_path, "wb") as f:
32
  f.write(base64.b64decode(image_base64))
33
  return True
34
  except Exception as e:
35
+ print(f"Erro Imagem: {e}")
36
  return False
37
 
38
  def generate_video(project_json):
39
+ print("--- INICIANDO RENDERIZAÇÃO (V2) ---")
40
+
41
+ # Criar pasta temporária para processamento
42
+ # O 'with' garante que a pasta suma depois, mas o arquivo final precisa persistir
43
+ # Então vamos criar a estrutura manualmente dentro do temp
44
+
45
+ temp_dir_obj = tempfile.TemporaryDirectory()
46
+ temp_dir = temp_dir_obj.name
47
+
48
  try:
49
+ # Tratamento do input JSON
50
  if isinstance(project_json, str):
51
+ try:
52
+ data = json.loads(project_json)
53
+ except:
54
+ # Caso venha como string mas não seja JSON válido
55
+ raise gr.Error("Erro: O texto fornecido não é um JSON válido.")
56
  else:
57
  data = project_json
58
 
59
  scenes_data = data.get("scenes", [])
60
+ if not scenes_data:
61
+ raise gr.Error("O JSON não contém a lista de cenas ('scenes').")
62
+
63
  clips = []
64
+ scenes_data.sort(key=lambda x: x.get("id", 0))
65
 
66
+ for i, scene in enumerate(scenes_data):
67
+ print(f"Processando cena {i}...")
68
 
69
+ # Definir caminhos
70
+ img_path = os.path.join(temp_dir, f"scene_{i}.jpg")
71
+ audio_path = os.path.join(temp_dir, f"scene_{i}.wav")
72
+
73
+ # Pegar dados
74
+ img_b64 = scene.get("image_data_base64")
75
+ audio_b64 = scene.get("audio_data_base64")
76
+
77
+ if not img_b64 or not audio_b64:
78
+ print(f"Pulando cena {i}: Falta audio ou imagem.")
79
+ continue
 
 
 
 
 
 
 
80
 
81
+ # Decodificar arquivos
82
+ if not base64_to_image(img_b64, img_path): continue
83
+ if not pcm_to_wav(audio_b64, audio_path): continue
84
+
85
+ try:
86
+ # --- SINTAXE MOVIEPY 2.0 ---
87
 
88
+ # 1. Carregar Audio
 
89
  audio_clip = AudioFileClip(audio_path)
90
 
91
+ # 2. Carregar Imagem e definir duração = audio + 0.1s
92
+ duration = audio_clip.duration + 0.1
93
+ video_clip = ImageClip(img_path).with_duration(duration)
94
 
95
+ # 3. Juntar Audio
96
+ video_clip = video_clip.with_audio(audio_clip)
97
 
98
+ # 4. Aplicar Fade In (Nova Sintaxe v2)
99
+ # O crossfadein antigo foi depreciado em favor de composition ou fx
100
+ # Vamos usar FadeIn simples para entrada suave
101
+ video_clip = video_clip.with_effects([FadeIn(duration=0.5)])
102
 
103
  clips.append(video_clip)
104
+
105
+ except Exception as e_clip:
106
+ print(f"Erro na montagem do clipe {i}: {e_clip}")
107
+
108
+ if not clips:
109
+ raise gr.Error("Nenhum clipe foi gerado. Verifique os dados Base64.")
110
+
111
+ print(f"Concatenando {len(clips)} cenas...")
112
+
113
+ # Concatenação
114
+ final_video = concatenate_videoclips(clips, method="compose")
115
+
116
+ # Output final
117
+ output_path = os.path.join(temp_dir, "output_final.mp4")
118
+
119
+ final_video.write_videofile(
120
+ output_path,
121
+ fps=24,
122
+ codec="libx264",
123
+ audio_codec="aac",
124
+ preset="ultrafast",
125
+ logger=None
126
+ )
127
+
128
+ print("Vídeo gerado com sucesso!")
129
+ return output_path
130
 
131
  except Exception as e:
132
+ error_msg = f"Erro fatal no backend: {str(e)}"
133
+ print(error_msg)
134
+ raise gr.Error(error_msg)
135
 
136
+ # Interface Gradio Atualizada
137
+ # api_name="predict" garante que a rota /predict exista para seu frontend
138
  demo = gr.Interface(
139
  fn=generate_video,
140
+ inputs=gr.JSON(label="JSON do Projeto"),
141
+ outputs=gr.Video(label="Vídeo Final"),
142
+ title="Renderizador AI V2 (Gradio 5 + MoviePy 2)",
143
+ allow_flagging="never"
144
  )
145
 
146
  if __name__ == "__main__":
147
+ # Gradio 5 gerencia segurança automaticamente no Spaces
148
+ demo.launch()