AlexandreScriptsMT commited on
Commit
137cb2f
·
verified ·
1 Parent(s): f1713db

Create App.py

Browse files
Files changed (1) hide show
  1. App.py +113 -0
App.py ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import edge_tts
3
+ import asyncio
4
+ import tempfile
5
+ import os
6
+ from moviepy.editor import ImageClip, concatenate_videoclips, AudioFileClip, TextClip, CompositeVideoClip
7
+ from moviepy.config import change_settings
8
+
9
+ # Tenta configurar o ImageMagick (necessário para TextClip em alguns ambientes Linux)
10
+ # Se der erro de policy.xml no HF, usaremos uma alternativa sem TextClip complexo ou legendas simplificadas
11
+ try:
12
+ change_settings({"IMAGEMAGICK_BINARY": "/usr/bin/convert"})
13
+ except:
14
+ pass
15
+
16
+ async def text_to_speech(text, voice="pt-BR-FranciscaNeural"):
17
+ """Gera áudio a partir de texto usando Edge-TTS (Microsoft Azure Free)"""
18
+ communicate = edge_tts.Communicate(text, voice)
19
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_file:
20
+ await communicate.save(tmp_file.name)
21
+ return tmp_file.name
22
+
23
+ def create_video_segment(image_url, audio_path, text_content):
24
+ """Cria um segmento de vídeo: Imagem + Áudio + Legenda"""
25
+ # Carrega o áudio para saber a duração
26
+ audio_clip = AudioFileClip(audio_path)
27
+ duration = audio_clip.duration + 0.5 # +0.5s de respiro
28
+
29
+ # Cria o clipe de imagem (Baixa da URL se necessário, mas o Gradio já entrega o path local se enviado como arquivo)
30
+ # Se o input for filepath (caminho local salvo pelo Gradio):
31
+ image_clip = ImageClip(image_url).set_duration(duration)
32
+
33
+ # Redimensiona para formato Vertical (9:16) se necessário, ou mantém proporção
34
+ # Aqui forçamos uma altura padrão de HD vertical (ex: 1280x720 invertido ou similar)
35
+ # Para simplificar, vamos assumir que a imagem gerada já vem no formato certo ou fazemos resize
36
+ image_clip = image_clip.resize(height=1280)
37
+ image_clip = image_clip.set_position("center")
38
+
39
+ # Legenda (Simples)
40
+ # Nota: TextClip pode ser chato de configurar no Linux devido ao ImageMagick.
41
+ # Se der erro, remova este bloco de txt_clip e retorne apenas image_clip.set_audio
42
+ try:
43
+ txt_clip = TextClip(text_content, fontsize=50, color='white', font='Arial-Bold',
44
+ stroke_color='black', stroke_width=2, size=(image_clip.w - 100, None), method='caption')
45
+ txt_clip = txt_clip.set_position(('center', 'bottom')).set_duration(duration).set_start(0)
46
+ video_part = CompositeVideoClip([image_clip, txt_clip])
47
+ except Exception as e:
48
+ print(f"Erro ao gerar legenda (ImageMagick ausente?): {e}")
49
+ video_part = image_clip
50
+
51
+ video_part = video_part.set_audio(audio_clip)
52
+ return video_part
53
+
54
+ async def process_video(scenes_data):
55
+ """
56
+ Função principal chamada pela API.
57
+ scenes_data esperado: Lista de tuplas/listas [caminho_imagem, texto_narracao]
58
+ Exemplo: [ ["/tmp/img1.jpg", "Maria apareceu..."], ["/tmp/img2.jpg", "Ela disse..."] ]
59
+ """
60
+ final_clips = []
61
+
62
+ for scene in scenes_data:
63
+ image_path = scene[0]
64
+ text = scene[1]
65
+
66
+ # 1. Gerar Áudio
67
+ audio_path = await text_to_speech(text)
68
+
69
+ # 2. Criar Clipe
70
+ clip = create_video_segment(image_path, audio_path, text)
71
+ final_clips.append(clip)
72
+
73
+ # 3. Concatenar tudo
74
+ final_video = concatenate_videoclips(final_clips, method="compose")
75
+
76
+ output_path = tempfile.mktemp(suffix=".mp4")
77
+ final_video.write_videofile(output_path, fps=24, codec="libx264", audio_codec="aac")
78
+
79
+ return output_path
80
+
81
+ # Wrapper síncrono para o Gradio chamar a função async
82
+ def gradio_entry_point(image1, text1, image2, text2, image3, text3, image4, text4):
83
+ # Por limitações de interface simples do Gradio, vamos aceitar inputs fixos (ex: 4 cenas)
84
+ # Para 12 cenas, o ideal é enviar um JSON, mas vamos fazer simples para teste visual
85
+ # Se o frontend enviar JSON, mudamos aqui.
86
+
87
+ # Monta a lista ignorando vazios
88
+ scenes = []
89
+ inputs = [(image1, text1), (image2, text2), (image3, text3), (image4, text4)]
90
+
91
+ for img, txt in inputs:
92
+ if img and txt:
93
+ scenes.append([img, txt])
94
+
95
+ if not scenes:
96
+ return None
97
+
98
+ return asyncio.run(process_video(scenes))
99
+
100
+ # Interface Visual (Para teste manual no site do HF)
101
+ with gr.Interface(
102
+ fn=gradio_entry_point,
103
+ inputs=[
104
+ gr.Image(type="filepath", label="Cena 1 - Imagem"), gr.Textbox(label="Cena 1 - Texto"),
105
+ gr.Image(type="filepath", label="Cena 2 - Imagem"), gr.Textbox(label="Cena 2 - Texto"),
106
+ gr.Image(type="filepath", label="Cena 3 - Imagem"), gr.Textbox(label="Cena 3 - Texto"),
107
+ gr.Image(type="filepath", label="Cena 4 - Imagem"), gr.Textbox(label="Cena 4 - Texto"),
108
+ ],
109
+ outputs=gr.Video(),
110
+ title="Gerador de Vídeo Nossa Senhora (Backend)",
111
+ description="Backend API para renderizar vídeo com MoviePy e EdgeTTS"
112
+ ) as demo:
113
+ demo.launch()