Spaces:
Runtime error
Runtime error
File size: 4,808 Bytes
137cb2f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | 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()
|