Spaces:
Sleeping
Sleeping
| 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 | |
| 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) |