import gradio as gr import edge_tts import asyncio import tempfile import os from moviepy.editor import ImageClip, concatenate_videoclips, AudioFileClip, TextClip, CompositeVideoClip from moviepy.config import change_settings # Tenta configurar o ImageMagick (necessário para TextClip em alguns ambientes Linux) # Se der erro de policy.xml no HF, usaremos uma alternativa sem TextClip complexo ou legendas simplificadas try: change_settings({"IMAGEMAGICK_BINARY": "/usr/bin/convert"}) except: pass async def text_to_speech(text, voice="pt-BR-FranciscaNeural"): """Gera áudio a partir de texto usando Edge-TTS (Microsoft Azure Free)""" communicate = edge_tts.Communicate(text, voice) with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_file: await communicate.save(tmp_file.name) return tmp_file.name def create_video_segment(image_url, audio_path, text_content): """Cria um segmento de vídeo: Imagem + Áudio + Legenda""" # Carrega o áudio para saber a duração audio_clip = AudioFileClip(audio_path) duration = audio_clip.duration + 0.5 # +0.5s de respiro # Cria o clipe de imagem (Baixa da URL se necessário, mas o Gradio já entrega o path local se enviado como arquivo) # Se o input for filepath (caminho local salvo pelo Gradio): image_clip = ImageClip(image_url).set_duration(duration) # Redimensiona para formato Vertical (9:16) se necessário, ou mantém proporção # Aqui forçamos uma altura padrão de HD vertical (ex: 1280x720 invertido ou similar) # Para simplificar, vamos assumir que a imagem gerada já vem no formato certo ou fazemos resize image_clip = image_clip.resize(height=1280) image_clip = image_clip.set_position("center") # Legenda (Simples) # Nota: TextClip pode ser chato de configurar no Linux devido ao ImageMagick. # Se der erro, remova este bloco de txt_clip e retorne apenas image_clip.set_audio try: txt_clip = TextClip(text_content, fontsize=50, color='white', font='Arial-Bold', stroke_color='black', stroke_width=2, size=(image_clip.w - 100, None), method='caption') txt_clip = txt_clip.set_position(('center', 'bottom')).set_duration(duration).set_start(0) video_part = CompositeVideoClip([image_clip, txt_clip]) except Exception as e: print(f"Erro ao gerar legenda (ImageMagick ausente?): {e}") video_part = image_clip video_part = video_part.set_audio(audio_clip) return video_part async def process_video(scenes_data): """ Função principal chamada pela API. scenes_data esperado: Lista de tuplas/listas [caminho_imagem, texto_narracao] Exemplo: [ ["/tmp/img1.jpg", "Maria apareceu..."], ["/tmp/img2.jpg", "Ela disse..."] ] """ final_clips = [] for scene in scenes_data: image_path = scene[0] text = scene[1] # 1. Gerar Áudio audio_path = await text_to_speech(text) # 2. Criar Clipe clip = create_video_segment(image_path, audio_path, text) final_clips.append(clip) # 3. Concatenar tudo final_video = concatenate_videoclips(final_clips, method="compose") output_path = tempfile.mktemp(suffix=".mp4") final_video.write_videofile(output_path, fps=24, codec="libx264", audio_codec="aac") return output_path # Wrapper síncrono para o Gradio chamar a função async def gradio_entry_point(image1, text1, image2, text2, image3, text3, image4, text4): # Por limitações de interface simples do Gradio, vamos aceitar inputs fixos (ex: 4 cenas) # Para 12 cenas, o ideal é enviar um JSON, mas vamos fazer simples para teste visual # Se o frontend enviar JSON, mudamos aqui. # Monta a lista ignorando vazios scenes = [] inputs = [(image1, text1), (image2, text2), (image3, text3), (image4, text4)] for img, txt in inputs: if img and txt: scenes.append([img, txt]) if not scenes: return None return asyncio.run(process_video(scenes)) # Interface Visual (Para teste manual no site do HF) with gr.Interface( fn=gradio_entry_point, inputs=[ gr.Image(type="filepath", label="Cena 1 - Imagem"), gr.Textbox(label="Cena 1 - Texto"), gr.Image(type="filepath", label="Cena 2 - Imagem"), gr.Textbox(label="Cena 2 - Texto"), gr.Image(type="filepath", label="Cena 3 - Imagem"), gr.Textbox(label="Cena 3 - Texto"), gr.Image(type="filepath", label="Cena 4 - Imagem"), gr.Textbox(label="Cena 4 - Texto"), ], outputs=gr.Video(), title="Gerador de Vídeo Nossa Senhora (Backend)", description="Backend API para renderizar vídeo com MoviePy e EdgeTTS" ) as demo: demo.launch()