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()