import os import requests import uvicorn from fastapi import FastAPI, HTTPException from fastapi.responses import FileResponse from pydantic import BaseModel from faster_whisper import WhisperModel # --- MUDANÇA CRÍTICA AQUI (Padrão MoviePy 2.0+) --- # Não existe mais 'moviepy.editor'. Importamos dos módulos específicos. from moviepy.video.io.ImageClip import ImageClip from moviepy.audio.io.AudioFileClip import AudioFileClip from moviepy.video.compositing.CompositeVideoClip import CompositeVideoClip from moviepy.video.VideoClip import TextClip # Nota: Em algumas versões v2, o TextClip pode estar em moviepy.video.tools.subtitles # ou exigir configuração explicita de fonte. # Inicializa a API app = FastAPI() class VideoRequest(BaseModel): image_url: str audio_url: str def download_file(url, filename): response = requests.get(url, stream=True) if response.status_code == 200: with open(filename, 'wb') as f: for chunk in response.iter_content(1024): f.write(chunk) else: raise Exception(f"Erro ao baixar {url}") def criar_video_logica(imagem_path, audio_path, output_path): print("Carregando modelo Whisper...") model = WhisperModel("tiny", device="cpu", compute_type="int8") segments, _ = model.transcribe(audio_path, language="pt") text_clips = [] # Configurações de fonte (Ajustadas para v2) # Na v2, as vezes é necessário apontar o caminho da fonte se o ImageMagick não achar font_conf = { "font_size": 30, # Mudou de fontsize para font_size em algumas builds v2 "color": 'white', "font": 'Arial', # Garanta que essa fonte exista no Linux ou use "DejaVu-Sans" "stroke_color": 'black', "stroke_width": 2, "method": 'caption', "size": (800, None) } print("Gerando legendas...") for segment in segments: # TextClip na v2 pode ter assinatura diferente dependendo da sub-versão (alpha/beta/stable) # Este é o padrão mais seguro: txt_clip = TextClip(text=segment.text.strip(), **font_conf) txt_clip = txt_clip.with_start(segment.start).with_duration(segment.end - segment.start) # 'set_position' mudou para 'with_position' em muitas partes da v2 para encadear métodos txt_clip = txt_clip.with_position(('center', 'bottom')) text_clips.append(txt_clip) print("Processando Áudio e Imagem...") audio_clip = AudioFileClip(audio_path) # set_duration -> with_duration / set_audio -> with_audio image_clip = ImageClip(imagem_path).with_duration(audio_clip.duration).with_audio(audio_clip) final = CompositeVideoClip([image_clip] + text_clips) print("Renderizando vídeo...") # write_videofile continua similar, mas 'preset' e 'threads' são geridos pelo ffmpeg final.write_videofile(output_path, fps=10, codec="libx264", audio_codec="aac", preset="ultrafast", threads=2) return output_path @app.post("/gerar-video") async def gerar_video_endpoint(request: VideoRequest): try: temp_img = "temp_image.jpg" # Ajustado para jpg pois o Gemini manda jpg temp_audio = "temp_audio.mp3" output_video = "video_final.mp4" print(f"Baixando: {request.image_url}") download_file(request.image_url, temp_img) download_file(request.audio_url, temp_audio) # Se for uma lista de imagens (lógica nova), precisa ajustar aqui. # Mantendo simples para teste: criar_video_logica(temp_img, temp_audio, output_video) return FileResponse(output_video, media_type="video/mp4", filename="video_editado.mp4") except Exception as e: print(f"Erro: {e}") # Retorna o erro na resposta para você ver no Apps Script raise HTTPException(status_code=500, detail=str(e)) if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=7860)