pcdoido2 commited on
Commit
80bf3c4
·
verified ·
1 Parent(s): 705485c

Upload 2 files

Browse files
Files changed (2) hide show
  1. app (5).py +331 -0
  2. requirements.txt +2 -0
app (5).py ADDED
@@ -0,0 +1,331 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import subprocess
3
+ import os
4
+ import random
5
+ import tempfile
6
+ import shutil
7
+ import time
8
+
9
+ st.set_page_config(page_title="TikTok Video Generator", layout="centered")
10
+ st.title("🎥 TikTok Video Generator - Completo")
11
+
12
+ st.markdown("Envie seus vídeos e gere conteúdo com efeitos, zoom, texto, música e filtros!")
13
+
14
+ # Uploads dos vídeos de cortes (principal)
15
+ st.markdown("<div style='background-color:#F0F0FF;padding:10px;border-radius:8px'>", unsafe_allow_html=True)
16
+ st.subheader("🎬 Envie os vídeos principais (cortes)")
17
+ cortes = st.file_uploader("Vídeos de cortes", type=["mp4"], accept_multiple_files=True)
18
+ st.markdown("</div>", unsafe_allow_html=True)
19
+
20
+ # Upload do vídeo de fundo (opcional)
21
+ st.markdown("<div style='background-color:#FFF0F0;padding:10px;border-radius:8px;margin-top:15px;'>", unsafe_allow_html=True)
22
+ st.subheader("🌄 Envie o vídeo de fundo")
23
+ video_fundo = st.file_uploader("Vídeo de Fundo (com blur e efeitos)", type=["mp4", "mov"])
24
+ st.markdown("</div>", unsafe_allow_html=True)
25
+
26
+ # Uploads adicionais
27
+ video_tutorial = st.file_uploader("📎 Tutorial (opcional)", type="mp4")
28
+ musica = st.file_uploader("🎵 Música (opcional, MP3)", type=["mp3"])
29
+
30
+ # Configurações gerais
31
+ num_videos_finais = st.number_input("Quantos vídeos gerar?", 1, 10, 1)
32
+ duracao_final = st.number_input("Duração final (s)", 10, 300, 30)
33
+ duracao_corte = st.number_input("Duração de cada corte (s)", 1, 30, 5)
34
+ zoom = st.slider("Zoom no vídeo principal", 1.0, 2.0, 1.0, 0.1)
35
+ blur_strength = st.slider("Blur no fundo", 1, 50, 10)
36
+ velocidade_cortes = st.slider("Velocidade dos cortes", 0.5, 2.0, 1.0, 0.1)
37
+ velocidade_final = st.slider("Velocidade final", 0.5, 2.0, 1.0, 0.1)
38
+ crf_value = st.slider("Qualidade CRF", 18, 30, 23)
39
+ # Texto com emojis
40
+ st.write("### Texto no Vídeo")
41
+ ativar_texto = st.checkbox("Ativar texto", value=False)
42
+ if ativar_texto:
43
+ texto_personalizado = st.text_input("Texto (emojis permitidos)", "🔥 Siga para mais!")
44
+ posicao_texto = st.selectbox("Posição do texto", ["Topo", "Centro", "Base"])
45
+ duracao_texto = st.radio("Duração do texto", ["Vídeo todo", "Apenas primeiros segundos"])
46
+ segundos_texto = 5
47
+ if duracao_texto == "Apenas primeiros segundos":
48
+ segundos_texto = st.slider("Segundos", 1, 30, 5)
49
+ cor_texto = st.color_picker("Cor do texto", "#FFFF00")
50
+ cor_sombra = st.color_picker("Cor da sombra", "#000000")
51
+ tamanho_texto = st.slider("Tamanho da fonte", 20, 100, 60, step=2)
52
+
53
+ # Filtros no fundo
54
+ st.write("### Filtros no fundo")
55
+ ativar_blur_fundo = st.checkbox("Ativar blur no fundo", value=True)
56
+ ativar_sepia = st.checkbox("Sépia", False)
57
+ ativar_granulado = st.checkbox("Granulado", False)
58
+ ativar_pb = st.checkbox("Preto e branco", False)
59
+ ativar_vignette = st.checkbox("Vignette", False)
60
+
61
+ # Efeitos extras
62
+ st.write("### Outros efeitos")
63
+ ativar_espelhar = st.checkbox("Espelhar vídeo", True)
64
+ ativar_filtro_cor = st.checkbox("Ajuste de cor", True)
65
+ remover_borda = st.checkbox("Remover borda do vídeo")
66
+ tamanho_borda = st.slider("Tamanho da borda (px)", 0, 200, 0, 5)
67
+
68
+ # Borda personalizada
69
+ ativar_borda_personalizada = st.checkbox("Borda personalizada", False)
70
+ if ativar_borda_personalizada:
71
+ cor_borda = st.color_picker("Cor da borda", "#FF0000")
72
+ animacao_borda = st.selectbox("Animação da borda", ["Nenhuma", "Borda Pulsante", "Cor Animada", "Neon", "Ondulada"])
73
+
74
+ # BOTÃO PRINCIPAL
75
+ if st.button("Gerar Vídeo(s)"):
76
+ if not cortes:
77
+ st.error("❌ Envie os vídeos de cortes.")
78
+ else:
79
+ with st.spinner("🎬 Processando..."):
80
+ progresso = st.progress(0)
81
+ temp_dir = tempfile.mkdtemp()
82
+
83
+ try:
84
+ # Salvar ou gerar fundo
85
+ fundo_path = os.path.join(temp_dir, "fundo.mp4")
86
+
87
+ if video_fundo:
88
+ with open(fundo_path, "wb") as f:
89
+ f.write(video_fundo.read())
90
+ else:
91
+ # Gera fundo preto 720x1280 com duração longa
92
+ subprocess.run([
93
+ "ffmpeg", "-f", "lavfi", "-i", "color=black:s=720x1280:d=600",
94
+ "-c:v", "libx264", "-t", str(duracao_final),
95
+ "-pix_fmt", "yuv420p", "-y", fundo_path
96
+ ], check=True, stderr=subprocess.PIPE)
97
+
98
+ # Salvar e converter tutorial (se enviado)
99
+ if video_tutorial:
100
+ tutorial_path = os.path.join(temp_dir, "tutorial_raw.mp4")
101
+ with open(tutorial_path, "wb") as f:
102
+ f.write(video_tutorial.read())
103
+ tutorial_mp4 = os.path.join(temp_dir, "tutorial.mp4")
104
+ subprocess.run([
105
+ "ffmpeg", "-i", tutorial_path, "-vf", "scale=720:1280,fps=30",
106
+ "-c:v", "libx264", "-preset", "ultrafast", "-crf", str(crf_value),
107
+ "-y", tutorial_mp4
108
+ ], check=True, stderr=subprocess.PIPE)
109
+
110
+ # Padronizar vídeos de cortes para 1280x720
111
+ cortes_names = []
112
+ for idx, corte in enumerate(cortes):
113
+ path_in = os.path.join(temp_dir, f"corte_in_{idx}.mp4")
114
+ path_out = os.path.join(temp_dir, f"corte_padrao_{idx}.mp4")
115
+ with open(path_in, "wb") as f:
116
+ f.write(corte.read())
117
+ subprocess.run([
118
+ "ffmpeg", "-i", path_in,
119
+ "-vf", "scale=1280:720:force_original_aspect_ratio=decrease",
120
+ "-c:v", "libx264", "-preset", "ultrafast", "-crf", "30", path_out
121
+ ], check=True, stderr=subprocess.PIPE)
122
+ cortes_names.append(path_out)
123
+
124
+ # ✅ Cortar o vídeo de fundo para a duração final
125
+ fundo_cortado = os.path.join(temp_dir, "fundo_cortado.mp4")
126
+ subprocess.run([
127
+ "ffmpeg", "-i", fundo_path, "-t", str(duracao_final),
128
+ "-c:v", "libx264", "-preset", "ultrafast", "-y", fundo_cortado
129
+ ], check=True, stderr=subprocess.PIPE)
130
+
131
+ for n in range(num_videos_finais):
132
+ progresso.progress(10 + n * 5)
133
+ random.shuffle(cortes_names)
134
+ tempo_total = 0
135
+ cortes_prontos = []
136
+
137
+ while tempo_total < duracao_final:
138
+ for c in cortes_names:
139
+ dur = subprocess.run([
140
+ "ffprobe", "-v", "error", "-show_entries", "format=duration",
141
+ "-of", "default=noprint_wrappers=1:nokey=1", c
142
+ ], stdout=subprocess.PIPE).stdout.decode().strip()
143
+
144
+ try:
145
+ d = float(dur)
146
+ if d > duracao_corte:
147
+ ini = random.uniform(0, d - duracao_corte)
148
+ out = os.path.join(temp_dir, f"cut_{random.randint(1000,9999)}.mp4")
149
+ subprocess.run([
150
+ "ffmpeg", "-ss", str(ini), "-i", c, "-t", str(duracao_corte),
151
+ "-an", "-c:v", "libx264", "-preset", "ultrafast", "-crf", "30", out
152
+ ], check=True, stderr=subprocess.PIPE)
153
+ cortes_prontos.append(out)
154
+ tempo_total += duracao_corte / velocidade_cortes
155
+ if tempo_total >= duracao_final:
156
+ break
157
+ except:
158
+ continue
159
+
160
+ # Concatenação dos cortes
161
+ lista = os.path.join(temp_dir, f"lista_{n}.txt")
162
+ with open(lista, "w") as f:
163
+ for c in cortes_prontos:
164
+ f.write(f"file '{c}'\n")
165
+
166
+ video_raw = os.path.join(temp_dir, f"video_raw_{n}.mp4")
167
+ subprocess.run([
168
+ "ffmpeg", "-f", "concat", "-safe", "0", "-i", lista,
169
+ "-c:v", "libx264", "-preset", "ultrafast", "-crf", "30", video_raw
170
+ ], check=True, stderr=subprocess.PIPE)
171
+
172
+ progresso.progress(35 + n * 5)
173
+
174
+ # Filtros aplicados ao vídeo principal
175
+ filtros_main = ["scale=720:1280:force_original_aspect_ratio=decrease"]
176
+ if zoom != 1.0:
177
+ filtros_main.append(f"scale=iw*{zoom}:ih*{zoom}")
178
+ filtros_main.append(f"setpts=PTS/{velocidade_cortes}")
179
+ if ativar_espelhar:
180
+ filtros_main.append("hflip")
181
+ if remover_borda and tamanho_borda > 0:
182
+ filtros_main.append(f"crop=in_w-{tamanho_borda*2}:in_h-{tamanho_borda*2}")
183
+ if ativar_filtro_cor:
184
+ filtros_main.append("eq=contrast=1.1:saturation=1.2")
185
+ # Borda personalizada
186
+ if ativar_borda_personalizada:
187
+ cor_ffmpeg = f"0x{cor_borda.lstrip('#')}FF"
188
+ anim_map = {
189
+ "Nenhuma": "",
190
+ "Borda Pulsante": "enable='lt(mod(t,1),0.5)'",
191
+ "Cor Animada": "enable='lt(mod(t,1),0.5)'",
192
+ "Neon": "enable='lt(mod(t,0.5),0.25)'",
193
+ "Ondulada": "enable='gt(sin(t*3.14),0)'"
194
+ }
195
+ drawbox = f"drawbox=x=0:y=0:w=iw:h=ih:color={cor_ffmpeg}:t=5"
196
+ if anim_map[animacao_borda]:
197
+ drawbox += f":{anim_map[animacao_borda]}"
198
+ filtros_main.append(drawbox)
199
+
200
+ # Montar filter_complex
201
+ filtro_complex = (
202
+ f"[0:v]scale=720:1280:force_original_aspect_ratio=increase,"
203
+ f"crop=720:1280"
204
+ )
205
+ if ativar_blur_fundo:
206
+ filtro_complex += f",boxblur={blur_strength}:1"
207
+ if ativar_sepia:
208
+ filtro_complex += ",colorchannelmixer=.393:.769:.189:0:.349:.686:.168:0:.272:.534:.131"
209
+ if ativar_granulado:
210
+ filtro_complex += ",noise=alls=20:allf=t+u"
211
+ if ativar_pb:
212
+ filtro_complex += ",hue=s=0.3"
213
+ if ativar_vignette:
214
+ filtro_complex += ",vignette"
215
+ filtro_complex += "[blur];"
216
+
217
+ filtro_complex += f"[1:v]{','.join(filtros_main)}[zoomed];"
218
+ filtro_complex += "[blur][zoomed]overlay=(W-w)/2:(H-h)/2[base]"
219
+
220
+ if ativar_texto and texto_personalizado.strip():
221
+ y_pos = "100" if posicao_texto == "Topo" else "(h-text_h)/2" if posicao_texto == "Centro" else "h-text_h-100"
222
+ enable = f":enable='lt(t\\,{segundos_texto})'" if duracao_texto == "Apenas primeiros segundos" else ""
223
+ texto_clean = texto_personalizado.replace(":", "\\:").replace("'", "\\'")
224
+ filtro_complex += f";[base]drawtext=text='{texto_clean}':"
225
+ filtro_complex += (
226
+ f"fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf:"
227
+ f"fontcolor={cor_texto}:fontsize={tamanho_texto}:"
228
+ f"shadowcolor={cor_sombra}:shadowx=3:shadowy=3:"
229
+ f"x=(w-text_w)/2:y={y_pos}{enable}[final]"
230
+ )
231
+ else:
232
+ filtro_complex += ";[base]null[final]"
233
+
234
+ # Gerar vídeo com filtros
235
+ video_editado = os.path.join(temp_dir, f"video_editado_{n}.mp4")
236
+ subprocess.run([
237
+ "ffmpeg", "-i", fundo_cortado, "-i", video_raw,
238
+ "-filter_complex", filtro_complex,
239
+ "-map", "[final]",
240
+ "-c:v", "libx264", "-preset", "ultrafast", "-crf", str(crf_value),
241
+ video_editado
242
+ ], check=True, stderr=subprocess.PIPE)
243
+
244
+ # Acelerar vídeo (sem áudio ainda)
245
+ video_acelerado = os.path.join(temp_dir, f"video_acelerado_{n}.mp4")
246
+ subprocess.run([
247
+ "ffmpeg", "-y", "-i", video_editado, "-an",
248
+ "-filter:v", f"setpts=PTS/{velocidade_final}",
249
+ "-c:v", "libx264", "-preset", "ultrafast", "-crf", str(crf_value),
250
+ video_acelerado
251
+ ], check=True, stderr=subprocess.PIPE)
252
+
253
+ progresso.progress(90)
254
+ # Tutorial no meio, se fornecido
255
+ video_final_raw = video_acelerado # Começa com o vídeo acelerado
256
+ if video_tutorial:
257
+ dur_proc = subprocess.run([
258
+ "ffprobe", "-v", "error", "-show_entries", "format=duration",
259
+ "-of", "default=noprint_wrappers=1:nokey=1", video_acelerado
260
+ ], stdout=subprocess.PIPE)
261
+ dur_f = float(dur_proc.stdout.decode().strip())
262
+ pt = dur_f / 2 if dur_f < 10 else random.uniform(5, dur_f - 5)
263
+
264
+ part1 = os.path.join(temp_dir, f"part1_{n}.mp4")
265
+ part2 = os.path.join(temp_dir, f"part2_{n}.mp4")
266
+
267
+ # Corte com precisão (ss depois do -i)
268
+ subprocess.run([
269
+ "ffmpeg", "-i", video_acelerado, "-ss", "0", "-t", str(pt),
270
+ "-c:v", "libx264", "-preset", "ultrafast", part1
271
+ ], check=True, stderr=subprocess.PIPE)
272
+ subprocess.run([
273
+ "ffmpeg", "-i", video_acelerado, "-ss", str(pt),
274
+ "-c:v", "libx264", "-preset", "ultrafast", part2
275
+ ], check=True, stderr=subprocess.PIPE)
276
+
277
+ final_txt = os.path.join(temp_dir, f"final_{n}.txt")
278
+ with open(final_txt, "w") as f:
279
+ f.write(f"file '{part1}'\nfile '{tutorial_mp4}'\nfile '{part2}'\n")
280
+
281
+ video_final_raw = os.path.join(temp_dir, f"video_final_raw_{n}.mp4")
282
+ subprocess.run([
283
+ "ffmpeg", "-f", "concat", "-safe", "0", "-i", final_txt,
284
+ "-c:v", "libx264", "-preset", "ultrafast", "-crf", str(crf_value),
285
+ video_final_raw
286
+ ], check=True, stderr=subprocess.PIPE)
287
+
288
+ # 🧠 Obter duração real do vídeo final (após tudo)
289
+ dur_proc = subprocess.run([
290
+ "ffprobe", "-v", "error", "-show_entries", "format=duration",
291
+ "-of", "default=noprint_wrappers=1:nokey=1", video_final_raw
292
+ ], stdout=subprocess.PIPE)
293
+ dur_video_real = float(dur_proc.stdout.decode().strip())
294
+
295
+ # 🎵 Cortar música para a duração real
296
+ if musica:
297
+ musica_path = os.path.join(temp_dir, "musica_original.mp3")
298
+ with open(musica_path, "wb") as f:
299
+ f.write(musica.read())
300
+
301
+ musica_cortada = os.path.join(temp_dir, f"musica_cortada_{n}.aac")
302
+ subprocess.run([
303
+ "ffmpeg", "-i", musica_path, "-ss", "0", "-t", str(dur_video_real),
304
+ "-vn", "-acodec", "aac", "-y", musica_cortada
305
+ ], check=True, stderr=subprocess.PIPE)
306
+
307
+ final_name = f"video_final_{n}_{int(time.time())}.mp4"
308
+ subprocess.run([
309
+ "ffmpeg", "-i", video_final_raw, "-i", musica_cortada,
310
+ "-map", "0:v:0", "-map", "1:a:0",
311
+ "-c:v", "copy", "-c:a", "aac",
312
+ "-shortest", final_name
313
+ ], check=True, stderr=subprocess.PIPE)
314
+
315
+ else:
316
+ # Se não tiver música, só copia o vídeo final
317
+ final_name = f"video_final_{n}_{int(time.time())}.mp4"
318
+ shutil.copy(video_final_raw, final_name)
319
+
320
+ st.video(final_name)
321
+ with open(final_name, "rb") as f:
322
+ st.download_button(f"📥 Baixar vídeo {n+1}", f, file_name=final_name)
323
+
324
+ progresso.progress(100)
325
+ st.success("✅ Todos os vídeos foram gerados com sucesso!")
326
+
327
+ except subprocess.CalledProcessError as e:
328
+ st.error(f"❌ Erro ao gerar vídeo:\n\n{e.stderr.decode(errors='ignore')}")
329
+
330
+ finally:
331
+ shutil.rmtree(temp_dir)
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ streamlit
2
+ ffmpeg-python