AlexandreScriptsMT commited on
Commit
aca6f46
·
verified ·
1 Parent(s): 6b3f7c8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +80 -60
app.py CHANGED
@@ -6,94 +6,114 @@ from fastapi.responses import FileResponse
6
  from pydantic import BaseModel
7
  from faster_whisper import WhisperModel
8
 
9
- # --- MUDANÇA CRÍTICA AQUI (Padrão MoviePy 2.0+) ---
10
- # Não existe mais 'moviepy.editor'. Importamos dos módulos específicos.
11
- from moviepy.video.io.ImageClip import ImageClip
12
- from moviepy.audio.io.AudioFileClip import AudioFileClip
13
- from moviepy.video.compositing.CompositeVideoClip import CompositeVideoClip
14
- from moviepy.video.VideoClip import TextClip
15
- # Nota: Em algumas versões v2, o TextClip pode estar em moviepy.video.tools.subtitles
16
- # ou exigir configuração explicita de fonte.
17
 
18
- # Inicializa a API
19
  app = FastAPI()
20
 
 
21
  class VideoRequest(BaseModel):
22
- image_url: str
23
  audio_url: str
 
 
 
 
24
 
25
  def download_file(url, filename):
26
- response = requests.get(url, stream=True)
27
- if response.status_code == 200:
 
28
  with open(filename, 'wb') as f:
29
- for chunk in response.iter_content(1024):
30
  f.write(chunk)
31
- else:
32
- raise Exception(f"Erro ao baixar {url}")
33
-
34
- def criar_video_logica(imagem_path, audio_path, output_path):
35
- print("Carregando modelo Whisper...")
36
- model = WhisperModel("tiny", device="cpu", compute_type="int8")
37
- segments, _ = model.transcribe(audio_path, language="pt")
38
 
39
- text_clips = []
 
 
 
40
 
41
- # Configurações de fonte (Ajustadas para v2)
42
- # Na v2, as vezes é necessário apontar o caminho da fonte se o ImageMagick não achar
43
- font_conf = {
44
- "font_size": 30, # Mudou de fontsize para font_size em algumas builds v2
45
- "color": 'white',
46
- "font": 'Arial', # Garanta que essa fonte exista no Linux ou use "DejaVu-Sans"
47
- "stroke_color": 'black',
48
- "stroke_width": 2,
49
- "method": 'caption',
50
- "size": (800, None)
51
- }
52
-
53
- print("Gerando legendas...")
54
- for segment in segments:
55
- # TextClip na v2 pode ter assinatura diferente dependendo da sub-versão (alpha/beta/stable)
56
- # Este é o padrão mais seguro:
57
- txt_clip = TextClip(text=segment.text.strip(), **font_conf)
58
 
59
- txt_clip = txt_clip.with_start(segment.start).with_duration(segment.end - segment.start)
60
- # 'set_position' mudou para 'with_position' em muitas partes da v2 para encadear métodos
61
- txt_clip = txt_clip.with_position(('center', 'bottom'))
62
- text_clips.append(txt_clip)
63
-
64
- print("Processando Áudio e Imagem...")
65
- audio_clip = AudioFileClip(audio_path)
66
 
67
- # set_duration -> with_duration / set_audio -> with_audio
68
- image_clip = ImageClip(imagem_path).with_duration(audio_clip.duration).with_audio(audio_clip)
69
 
70
- final = CompositeVideoClip([image_clip] + text_clips)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
 
72
- print("Renderizando vídeo...")
73
- # write_videofile continua similar, mas 'preset' e 'threads' são geridos pelo ffmpeg
74
- final.write_videofile(output_path, fps=10, codec="libx264", audio_codec="aac", preset="ultrafast", threads=2)
75
  return output_path
76
 
77
  @app.post("/gerar-video")
78
  async def gerar_video_endpoint(request: VideoRequest):
79
  try:
80
- temp_img = "temp_image.jpg" # Ajustado para jpg pois o Gemini manda jpg
81
  temp_audio = "temp_audio.mp3"
 
82
  output_video = "video_final.mp4"
83
 
84
- print(f"Baixando: {request.image_url}")
85
- download_file(request.image_url, temp_img)
86
  download_file(request.audio_url, temp_audio)
87
 
88
- # Se for uma lista de imagens (lógica nova), precisa ajustar aqui.
89
- # Mantendo simples para teste:
90
- criar_video_logica(temp_img, temp_audio, output_video)
 
 
 
 
 
 
91
 
92
- return FileResponse(output_video, media_type="video/mp4", filename="video_editado.mp4")
 
93
 
94
  except Exception as e:
95
- print(f"Erro: {e}")
96
- # Retorna o erro na resposta para você ver no Apps Script
97
  raise HTTPException(status_code=500, detail=str(e))
98
 
99
  if __name__ == "__main__":
 
6
  from pydantic import BaseModel
7
  from faster_whisper import WhisperModel
8
 
9
+ # --- A IMPORTAÇÃO CLÁSSICA E SEGURA ---
10
+ # Na versão 1.0.3, este módulo contém tudo que precisamos
11
+ from moviepy.editor import ImageClip, AudioFileClip, TextClip, CompositeVideoClip, concatenate_videoclips
 
 
 
 
 
12
 
 
13
  app = FastAPI()
14
 
15
+ # Modelo de dados
16
  class VideoRequest(BaseModel):
 
17
  audio_url: str
18
+ imagens: list[str] # Aceita lista de imagens agora
19
+ duracao_estimada: int = 60
20
+
21
+ # --- Funções Auxiliares ---
22
 
23
  def download_file(url, filename):
24
+ try:
25
+ response = requests.get(url, stream=True)
26
+ response.raise_for_status() # Garante que deu 200 OK
27
  with open(filename, 'wb') as f:
28
+ for chunk in response.iter_content(chunk_size=8192):
29
  f.write(chunk)
30
+ except Exception as e:
31
+ print(f"Erro ao baixar {url}: {e}")
32
+ raise e
 
 
 
 
33
 
34
+ def criar_video_v1(lista_imagens_paths, audio_path, output_path):
35
+ print("1. Carregando Áudio...")
36
+ audio_clip = AudioFileClip(audio_path)
37
+ duracao_total = audio_clip.duration
38
 
39
+ # Cálculos de tempo
40
+ qtd_imgs = len(lista_imagens_paths)
41
+ if qtd_imgs == 0: raise Exception("Nenhuma imagem fornecida")
42
+ tempo_por_imagem = duracao_total / qtd_imgs
43
+
44
+ print(f"2. Montando Timeline ({qtd_imgs} imagens, {tempo_por_imagem:.2f}s cada)...")
45
+ clips_visuais = []
46
+
47
+ for img_path in lista_imagens_paths:
48
+ # Configuração Versão 1.0.3 (usando set_)
49
+ clip = ImageClip(img_path).set_duration(tempo_por_imagem)
50
+ # Opcional: Resize para garantir que caiba (se necessário)
51
+ # clip = clip.resize(height=1080)
52
+ clips_visuais.append(clip)
 
 
 
53
 
54
+ # Junta as imagens em sequência
55
+ video_sem_audio = concatenate_videoclips(clips_visuais, method="compose")
 
 
 
 
 
56
 
57
+ # Junta com o áudio
58
+ video_final = video_sem_audio.set_audio(audio_clip)
59
 
60
+ # --- GERAÇÃO DE LEGENDAS (Opcional - Pode comentar se der erro de ImageMagick) ---
61
+ # Para ativar legendas, descomente abaixo. Se der erro de "convert", deixe comentado por enquanto.
62
+ # print("3. Gerando Legendas (Whisper)...")
63
+ # model = WhisperModel("tiny", device="cpu", compute_type="int8")
64
+ # segments, _ = model.transcribe(audio_path, language="pt")
65
+ # legendas_clips = []
66
+ # for segment in segments:
67
+ # txt = TextClip(segment.text, fontsize=24, color='white', font='DejaVu-Sans-Bold', stroke_color='black', stroke_width=1, size=(800, None), method='caption')
68
+ # txt = txt.set_start(segment.start).set_duration(segment.end - segment.start).set_position(('center', 'bottom'))
69
+ # legendas_clips.append(txt)
70
+ # video_final = CompositeVideoClip([video_final] + legendas_clips)
71
+ # ---------------------------------------------------------------------------------
72
+
73
+ print("4. Renderizando Arquivo Final...")
74
+ # fps=10 é leve e rápido para imagens estáticas
75
+ video_final.write_videofile(
76
+ output_path,
77
+ fps=10,
78
+ codec="libx264",
79
+ audio_codec="aac",
80
+ preset="ultrafast",
81
+ threads=2
82
+ )
83
+
84
+ # Limpeza de memória (importante no Hugging Face)
85
+ audio_clip.close()
86
+ video_final.close()
87
 
 
 
 
88
  return output_path
89
 
90
  @app.post("/gerar-video")
91
  async def gerar_video_endpoint(request: VideoRequest):
92
  try:
93
+ # Nomes temporários
94
  temp_audio = "temp_audio.mp3"
95
+ lista_imgs_locais = []
96
  output_video = "video_final.mp4"
97
 
98
+ # 1. Baixar Audio
99
+ print(f"Baixando áudio...")
100
  download_file(request.audio_url, temp_audio)
101
 
102
+ # 2. Baixar Imagens (Loop)
103
+ print(f"Baixando {len(request.imagens)} imagens...")
104
+ for i, url in enumerate(request.imagens):
105
+ nome_img = f"temp_img_{i}.jpg"
106
+ download_file(url, nome_img)
107
+ lista_imgs_locais.append(nome_img)
108
+
109
+ # 3. Processar
110
+ criar_video_v1(lista_imgs_locais, temp_audio, output_video)
111
 
112
+ # 4. Retornar
113
+ return FileResponse(output_video, media_type="video/mp4", filename="video_renderizado.mp4")
114
 
115
  except Exception as e:
116
+ print(f"ERRO CRÍTICO: {str(e)}")
 
117
  raise HTTPException(status_code=500, detail=str(e))
118
 
119
  if __name__ == "__main__":