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 # --- A IMPORTAÇÃO CLÁSSICA E SEGURA --- # Na versão 1.0.3, este módulo contém tudo que precisamos from moviepy.editor import ImageClip, AudioFileClip, TextClip, CompositeVideoClip, concatenate_videoclips app = FastAPI() # Modelo de dados class VideoRequest(BaseModel): audio_url: str imagens: list[str] # Aceita lista de imagens agora duracao_estimada: int = 60 # --- Funções Auxiliares --- def download_file(url, filename): try: response = requests.get(url, stream=True) response.raise_for_status() # Garante que deu 200 OK with open(filename, 'wb') as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) except Exception as e: print(f"Erro ao baixar {url}: {e}") raise e def criar_video_v1(lista_imagens_paths, audio_path, output_path): print("1. Carregando Áudio...") audio_clip = AudioFileClip(audio_path) duracao_total = audio_clip.duration # Cálculos de tempo qtd_imgs = len(lista_imagens_paths) if qtd_imgs == 0: raise Exception("Nenhuma imagem fornecida") tempo_por_imagem = duracao_total / qtd_imgs print(f"2. Montando Timeline ({qtd_imgs} imagens, {tempo_por_imagem:.2f}s cada)...") clips_visuais = [] for img_path in lista_imagens_paths: # Configuração Versão 1.0.3 (usando set_) clip = ImageClip(img_path).set_duration(tempo_por_imagem) # Opcional: Resize para garantir que caiba (se necessário) # clip = clip.resize(height=1080) clips_visuais.append(clip) # Junta as imagens em sequência video_sem_audio = concatenate_videoclips(clips_visuais, method="compose") # Junta com o áudio video_final = video_sem_audio.set_audio(audio_clip) # --- GERAÇÃO DE LEGENDAS (Opcional - Pode comentar se der erro de ImageMagick) --- # Para ativar legendas, descomente abaixo. Se der erro de "convert", deixe comentado por enquanto. # print("3. Gerando Legendas (Whisper)...") # model = WhisperModel("tiny", device="cpu", compute_type="int8") # segments, _ = model.transcribe(audio_path, language="pt") # legendas_clips = [] # for segment in segments: # txt = TextClip(segment.text, fontsize=24, color='white', font='DejaVu-Sans-Bold', stroke_color='black', stroke_width=1, size=(800, None), method='caption') # txt = txt.set_start(segment.start).set_duration(segment.end - segment.start).set_position(('center', 'bottom')) # legendas_clips.append(txt) # video_final = CompositeVideoClip([video_final] + legendas_clips) # --------------------------------------------------------------------------------- print("4. Renderizando Arquivo Final...") # fps=10 é leve e rápido para imagens estáticas video_final.write_videofile( output_path, fps=10, codec="libx264", audio_codec="aac", preset="ultrafast", threads=2 ) # Limpeza de memória (importante no Hugging Face) audio_clip.close() video_final.close() return output_path @app.post("/gerar-video") async def gerar_video_endpoint(request: VideoRequest): try: # Nomes temporários temp_audio = "temp_audio.mp3" lista_imgs_locais = [] output_video = "video_final.mp4" # 1. Baixar Audio print(f"Baixando áudio...") download_file(request.audio_url, temp_audio) # 2. Baixar Imagens (Loop) print(f"Baixando {len(request.imagens)} imagens...") for i, url in enumerate(request.imagens): nome_img = f"temp_img_{i}.jpg" download_file(url, nome_img) lista_imgs_locais.append(nome_img) # 3. Processar criar_video_v1(lista_imgs_locais, temp_audio, output_video) # 4. Retornar return FileResponse(output_video, media_type="video/mp4", filename="video_renderizado.mp4") except Exception as e: print(f"ERRO CRÍTICO: {str(e)}") raise HTTPException(status_code=500, detail=str(e)) if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=7860)