AlexandreScriptsMT commited on
Commit
4da758a
·
verified ·
1 Parent(s): 80e29ad

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +36 -26
app.py CHANGED
@@ -5,20 +5,24 @@ from fastapi import FastAPI, HTTPException
5
  from fastapi.responses import FileResponse
6
  from pydantic import BaseModel
7
  from faster_whisper import WhisperModel
8
- from moviepy.editor import ImageClip, AudioFileClip, TextClip, CompositeVideoClip
 
 
 
 
 
 
 
 
9
 
10
  # Inicializa a API
11
  app = FastAPI()
12
 
13
- # Modelo de dados que esperamos receber do GAS
14
  class VideoRequest(BaseModel):
15
  image_url: str
16
  audio_url: str
17
 
18
- # --- Funções Auxiliares ---
19
-
20
  def download_file(url, filename):
21
- """Baixa o arquivo da URL e salva localmente"""
22
  response = requests.get(url, stream=True)
23
  if response.status_code == 200:
24
  with open(filename, 'wb') as f:
@@ -28,63 +32,69 @@ def download_file(url, filename):
28
  raise Exception(f"Erro ao baixar {url}")
29
 
30
  def criar_video_logica(imagem_path, audio_path, output_path):
31
- """A lógica de edição (Whisper + MoviePy)"""
32
  print("Carregando modelo Whisper...")
33
  model = WhisperModel("tiny", device="cpu", compute_type="int8")
34
  segments, _ = model.transcribe(audio_path, language="pt")
35
 
36
  text_clips = []
37
- # Configuração visual da legenda
38
- font_settings = {
39
- "fontsize": 30, "color": 'white', "font": 'Arial-Bold',
40
- "stroke_color": 'black', "stroke_width": 2, "method": 'caption',
 
 
 
 
 
 
41
  "size": (800, None)
42
  }
43
 
44
  print("Gerando legendas...")
45
  for segment in segments:
46
- txt_clip = TextClip(segment.text.strip(), **font_settings)
47
- txt_clip = txt_clip.set_start(segment.start).set_duration(segment.end - segment.start)
48
- txt_clip = txt_clip.set_position(('center', 'bottom'))
 
 
 
 
49
  text_clips.append(txt_clip)
50
 
 
51
  audio_clip = AudioFileClip(audio_path)
52
- image_clip = ImageClip(imagem_path).set_duration(audio_clip.duration).set_audio(audio_clip)
 
 
53
 
54
  final = CompositeVideoClip([image_clip] + text_clips)
55
 
56
  print("Renderizando vídeo...")
 
57
  final.write_videofile(output_path, fps=10, codec="libx264", audio_codec="aac", preset="ultrafast", threads=2)
58
  return output_path
59
 
60
- # --- O Endpoint da API (Onde o GAS conecta) ---
61
-
62
  @app.post("/gerar-video")
63
  async def gerar_video_endpoint(request: VideoRequest):
64
  try:
65
- # 1. Definir caminhos temporários
66
- temp_img = "temp_image.png"
67
  temp_audio = "temp_audio.mp3"
68
  output_video = "video_final.mp4"
69
 
70
- # 2. Baixar os assets que o GAS mandou os links
71
- print(f"Baixando imagem de: {request.image_url}")
72
  download_file(request.image_url, temp_img)
73
-
74
- print(f"Baixando áudio de: {request.audio_url}")
75
  download_file(request.audio_url, temp_audio)
76
 
77
- # 3. Rodar a mágica
 
78
  criar_video_logica(temp_img, temp_audio, output_video)
79
 
80
- # 4. Devolver o arquivo pronto para o GAS
81
- # O FileResponse envia o arquivo binário na resposta HTTP
82
  return FileResponse(output_video, media_type="video/mp4", filename="video_editado.mp4")
83
 
84
  except Exception as e:
85
  print(f"Erro: {e}")
 
86
  raise HTTPException(status_code=500, detail=str(e))
87
 
88
- # Configuração para rodar no Hugging Face
89
  if __name__ == "__main__":
90
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
5
  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:
 
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__":
100
  uvicorn.run(app, host="0.0.0.0", port=7860)