pcdoido2 commited on
Commit
fcea57a
·
verified ·
1 Parent(s): 015967f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +253 -322
app.py CHANGED
@@ -5,334 +5,265 @@ import random
5
  import tempfile
6
  import shutil
7
  import time
8
- import base64 # <— NOVO
9
 
 
10
  st.set_page_config(page_title="Shorts Generator", layout="centered")
11
- st.title("🎥 Shorts Generator - Simples")
12
 
13
- # ============ NOVO: categorias e pastas de cortes salvos ============
 
 
 
 
14
  CATEGORIAS = ["AVATAR WORLD", "BLOX FRUITS", "TOCA LIFE", "FC MOBILE"]
15
  BASE_ASSETS = "assets"
16
  for cat in CATEGORIAS:
17
  os.makedirs(os.path.join(BASE_ASSETS, cat, "cortes"), exist_ok=True)
18
- # ====================================================================
19
-
20
- st.markdown("Envie seus vídeos e gere conteúdo com efeitos e controle total de duração!")
21
-
22
- # ============ NOVO: seleção de categoria para cortes salvos =========
23
- categoria_selecionada = st.selectbox(
24
- "Escolha a categoria (opcional, para usar cortes salvos):",
25
- ["Selecione..."] + CATEGORIAS
26
- )
27
-
28
- cortes_salvos_paths = []
29
- if categoria_selecionada != "Selecione...":
30
- path_cortes_salvos = os.path.join(BASE_ASSETS, categoria_selecionada, "cortes")
31
- if os.path.isdir(path_cortes_salvos):
32
- cortes_salvos_paths = [
33
- os.path.join(path_cortes_salvos, f)
34
- for f in os.listdir(path_cortes_salvos)
35
- if f.lower().endswith(".mp4")
36
- ]
37
- st.caption(f"Encontrados {len(cortes_salvos_paths)} cortes salvos em: {path_cortes_salvos}")
38
- # ====================================================================
39
- # Upload dos vídeos (MANTIDO)
40
- cortes = st.file_uploader("Envie os vídeos de cortes", type=["mp4"], accept_multiple_files=True)
41
-
42
- # Configurações (MANTIDO)
43
- num_videos_finais = st.number_input("Quantos vídeos finais gerar?", min_value=1, max_value=10, value=1)
44
- duracao_final = st.number_input("Duração final do vídeo (em segundos)", min_value=10, max_value=300, value=30)
45
-
46
- # Min e Max duração dos cortes (MANTIDO)
47
- duracao_min = st.slider("Duração mínima de cada corte (s)", 1, 30, 3)
48
- duracao_max = st.slider("Duração máxima de cada corte (s)", 1, 60, 5)
49
- if duracao_min > duracao_max:
50
- st.warning("⚠️ A duração mínima não pode ser maior que a máxima.")
51
-
52
- # Corte lateral e zoom (MANTIDO)
53
- corte_lateral = st.slider("Corte lateral (%)", 0, 50, 0, 5)
54
- zoom = st.slider("Zoom Central (1.0 = normal)", 1.0, 2.0, 1.0, 0.1)
55
-
56
- # Velocidades e qualidade (MANTIDO)
57
- velocidade_cortes = st.slider("Velocidade dos cortes", 0.5, 2.0, 1.0, 0.1)
58
- velocidade_final = st.slider("Velocidade final do vídeo", 0.5, 2.0, 1.0, 0.1)
59
- crf_value = st.slider("Qualidade CRF (menor = melhor qualidade)", 18, 30, 18)
60
-
61
- # Outros (MANTIDO)
62
- st.write("### Outros")
63
- ativar_espelhar = st.checkbox("Espelhar Vídeo", value=True)
64
- ativar_filtro_cor = st.checkbox("Filtro de Cor (Contraste/Saturação)", value=True)
65
-
66
- # Botão principal (MANTIDO, mas agora combinando uploads + salvos)
67
- if st.button("Gerar Vídeo(s)"):
68
- # Verifica se temos pelo menos upload OU salvos
69
- if not cortes and len(cortes_salvos_paths) == 0:
70
- st.error("❌ Envie vídeos OU selecione uma categoria com cortes salvos para continuar.")
71
- else:
72
- with st.spinner('🎥 Seu vídeo está sendo gerado...'):
73
- progresso = st.progress(0)
74
- temp_dir = tempfile.mkdtemp()
75
-
76
- try:
77
- # === PREPARAR LISTA FINAL DE ARQUIVOS DE CORTE ===
78
- cortes_names = []
79
-
80
- # 1) Arquivos enviados (MANTIDO)
81
- if cortes:
82
- for idx, corte in enumerate(cortes):
83
- nome = os.path.join(temp_dir, f"corte_{idx}_{random.randint(1000,9999)}.mp4")
84
- with open(nome, "wb") as f:
85
- f.write(corte.read())
86
- cortes_names.append(nome)
87
-
88
- # 2) Arquivos salvos na categoria (NOVO – usamos os caminhos diretamente)
89
- if len(cortes_salvos_paths) > 0:
90
- cortes_names.extend(cortes_salvos_paths)
91
-
92
- if len(cortes_names) == 0:
93
- st.error("❌ Não há cortes disponíveis após combinar uploads e salvos.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  shutil.rmtree(temp_dir)
95
- st.stop()
96
-
97
- progresso.progress(10)
98
-
99
- for n in range(num_videos_finais):
100
- cortes_prontos = []
101
- random.shuffle(cortes_names)
102
- progresso.progress(10 + int(20 * (n / num_videos_finais)))
103
-
104
- tempo_total = 0
105
- while tempo_total < duracao_final:
106
- for c in cortes_names:
107
- dur_proc = subprocess.run([
108
- "ffprobe", "-v", "error", "-show_entries", "format=duration",
109
- "-of", "default=noprint_wrappers=1:nokey=1", c
110
- ], stdout=subprocess.PIPE)
111
-
112
- dur = dur_proc.stdout.decode().strip()
113
- try:
114
- d = float(dur)
115
- dur_aleatoria = random.uniform(duracao_min, duracao_max)
116
- if d > dur_aleatoria:
117
- ini = random.uniform(0, d - dur_aleatoria)
118
- out = os.path.join(temp_dir, f"cut_{n}_{random.randint(1000,9999)}.mp4")
119
- subprocess.run([
120
- "ffmpeg", "-ss", str(ini), "-i", c, "-t", str(dur_aleatoria),
121
- "-an", "-c:v", "libx264", "-preset", "ultrafast", "-crf", "30", out
122
- ], check=True, stderr=subprocess.PIPE)
123
- cortes_prontos.append(out)
124
- tempo_total += dur_aleatoria / velocidade_cortes
125
- if tempo_total >= duracao_final:
126
- break
127
- except:
128
- continue
129
-
130
- lista = os.path.join(temp_dir, f"lista_{n}.txt")
131
- with open(lista, "w") as f:
132
- for c in cortes_prontos:
133
- f.write(f"file '{c}'\n")
134
-
135
- video_raw = os.path.join(temp_dir, f"video_raw_{n}.mp4")
136
- subprocess.run([
137
- "ffmpeg", "-f", "concat", "-safe", "0", "-i", lista, "-c:v", "libx264",
138
- "-preset", "ultrafast", "-crf", "30", video_raw
139
- ], check=True, stderr=subprocess.PIPE)
140
-
141
- progresso.progress(40 + int(20 * (n / num_videos_finais)))
142
-
143
- filtros_main = []
144
-
145
- if corte_lateral > 0:
146
- fator = (100 - corte_lateral) / 100
147
- filtros_main.append(f"crop=iw*{fator}:ih:(iw-iw*{fator})/2:0")
148
-
149
- if zoom != 1.0:
150
- filtros_main.append(f"scale=iw*{zoom}:ih*{zoom},crop=iw/{zoom}:ih/{zoom}")
151
-
152
- filtros_main.append(f"setpts=PTS/{velocidade_cortes}")
153
- if ativar_espelhar:
154
- filtros_main.append("hflip")
155
- if ativar_filtro_cor:
156
- filtros_main.append("eq=contrast=1.1:saturation=1.2")
157
-
158
- filtros_main.append("pad=ceil(iw/2)*2:ceil(ih/2)*2")
159
- filtro_final = ",".join(filtros_main)
160
-
161
- video_editado = os.path.join(temp_dir, f"video_editado_{n}.mp4")
162
- subprocess.run([
163
- "ffmpeg", "-i", video_raw, "-vf", filtro_final,
164
- "-c:v", "libx264", "-preset", "ultrafast", "-crf", str(crf_value),
165
- video_editado
166
- ], check=True, stderr=subprocess.PIPE)
167
-
168
- video_final = f"video_final_{n}_{int(time.time())}.mp4"
169
- subprocess.run([
170
- "ffmpeg", "-i", video_editado, "-filter:v", f"setpts=PTS/{velocidade_final}",
171
- "-t", str(duracao_final), "-c:v", "libx264", "-preset", "ultrafast",
172
- "-crf", str(crf_value), "-map_metadata", "-1", "-movflags", "+faststart", video_final
173
- ], check=True, stderr=subprocess.PIPE)
174
-
175
- progresso.progress(90 + int(10 * (n / num_videos_finais)))
176
- st.video(video_final)
177
- with open(video_final, "rb") as f:
178
- st.download_button(f"📅 Baixar Vídeo {n+1}", f, file_name=video_final)
179
-
180
- progresso.progress(100)
181
- st.success("✅ Todos os vídeos foram gerados com sucesso!")
182
-
183
- except subprocess.CalledProcessError as e:
184
- st.error(f"Erro durante processamento: {e.stderr.decode()}")
185
- except Exception as e:
186
- st.error(f"Erro inesperado: {str(e)}")
187
- finally:
188
- shutil.rmtree(temp_dir)
189
- # Upload dos vídeos (MANTIDO)
190
- cortes = st.file_uploader("Envie os vídeos de cortes", type=["mp4"], accept_multiple_files=True)
191
-
192
- # Configurações (MANTIDO)
193
- num_videos_finais = st.number_input("Quantos vídeos finais gerar?", min_value=1, max_value=10, value=1)
194
- duracao_final = st.number_input("Duração final do vídeo (em segundos)", min_value=10, max_value=300, value=30)
195
-
196
- # Min e Max duração dos cortes (MANTIDO)
197
- duracao_min = st.slider("Duração mínima de cada corte (s)", 1, 30, 3)
198
- duracao_max = st.slider("Duração máxima de cada corte (s)", 1, 60, 5)
199
- if duracao_min > duracao_max:
200
- st.warning("⚠️ A duração mínima não pode ser maior que a máxima.")
201
-
202
- # Corte lateral e zoom (MANTIDO)
203
- corte_lateral = st.slider("Corte lateral (%)", 0, 50, 0, 5)
204
- zoom = st.slider("Zoom Central (1.0 = normal)", 1.0, 2.0, 1.0, 0.1)
205
-
206
- # Velocidades e qualidade (MANTIDO)
207
- velocidade_cortes = st.slider("Velocidade dos cortes", 0.5, 2.0, 1.0, 0.1)
208
- velocidade_final = st.slider("Velocidade final do vídeo", 0.5, 2.0, 1.0, 0.1)
209
- crf_value = st.slider("Qualidade CRF (menor = melhor qualidade)", 18, 30, 18)
210
-
211
- # Outros (MANTIDO)
212
- st.write("### Outros")
213
- ativar_espelhar = st.checkbox("Espelhar Vídeo", value=True)
214
- ativar_filtro_cor = st.checkbox("Filtro de Cor (Contraste/Saturação)", value=True)
215
-
216
- # Botão principal (MANTIDO, mas agora combinando uploads + salvos)
217
- if st.button("Gerar Vídeo(s)"):
218
- # Verifica se temos pelo menos upload OU salvos
219
- if not cortes and len(cortes_salvos_paths) == 0:
220
- st.error("❌ Envie vídeos OU selecione uma categoria com cortes salvos para continuar.")
221
  else:
222
- with st.spinner('🎥 Seu vídeo está sendo gerado...'):
223
- progresso = st.progress(0)
224
- temp_dir = tempfile.mkdtemp()
225
-
226
- try:
227
- # === PREPARAR LISTA FINAL DE ARQUIVOS DE CORTE ===
228
- cortes_names = []
229
-
230
- # 1) Arquivos enviados (MANTIDO)
231
- if cortes:
232
- for idx, corte in enumerate(cortes):
233
- nome = os.path.join(temp_dir, f"corte_{idx}_{random.randint(1000,9999)}.mp4")
234
- with open(nome, "wb") as f:
235
- f.write(corte.read())
236
- cortes_names.append(nome)
237
-
238
- # 2) Arquivos salvos na categoria (NOVO – usamos os caminhos diretamente)
239
- if len(cortes_salvos_paths) > 0:
240
- cortes_names.extend(cortes_salvos_paths)
241
-
242
- if len(cortes_names) == 0:
243
- st.error("❌ Não há cortes disponíveis após combinar uploads e salvos.")
244
- shutil.rmtree(temp_dir)
245
- st.stop()
246
-
247
- progresso.progress(10)
248
-
249
- for n in range(num_videos_finais):
250
- cortes_prontos = []
251
- random.shuffle(cortes_names)
252
- progresso.progress(10 + int(20 * (n / num_videos_finais)))
253
-
254
- tempo_total = 0
255
- while tempo_total < duracao_final:
256
- for c in cortes_names:
257
- dur_proc = subprocess.run([
258
- "ffprobe", "-v", "error", "-show_entries", "format=duration",
259
- "-of", "default=noprint_wrappers=1:nokey=1", c
260
- ], stdout=subprocess.PIPE)
261
-
262
- dur = dur_proc.stdout.decode().strip()
263
- try:
264
- d = float(dur)
265
- dur_aleatoria = random.uniform(duracao_min, duracao_max)
266
- if d > dur_aleatoria:
267
- ini = random.uniform(0, d - dur_aleatoria)
268
- out = os.path.join(temp_dir, f"cut_{n}_{random.randint(1000,9999)}.mp4")
269
- subprocess.run([
270
- "ffmpeg", "-ss", str(ini), "-i", c, "-t", str(dur_aleatoria),
271
- "-an", "-c:v", "libx264", "-preset", "ultrafast", "-crf", "30", out
272
- ], check=True, stderr=subprocess.PIPE)
273
- cortes_prontos.append(out)
274
- tempo_total += dur_aleatoria / velocidade_cortes
275
- if tempo_total >= duracao_final:
276
- break
277
- except:
278
- continue
279
-
280
- lista = os.path.join(temp_dir, f"lista_{n}.txt")
281
- with open(lista, "w") as f:
282
- for c in cortes_prontos:
283
- f.write(f"file '{c}'\n")
284
-
285
- video_raw = os.path.join(temp_dir, f"video_raw_{n}.mp4")
286
- subprocess.run([
287
- "ffmpeg", "-f", "concat", "-safe", "0", "-i", lista, "-c:v", "libx264",
288
- "-preset", "ultrafast", "-crf", "30", video_raw
289
- ], check=True, stderr=subprocess.PIPE)
290
-
291
- progresso.progress(40 + int(20 * (n / num_videos_finais)))
292
-
293
- filtros_main = []
294
-
295
- if corte_lateral > 0:
296
- fator = (100 - corte_lateral) / 100
297
- filtros_main.append(f"crop=iw*{fator}:ih:(iw-iw*{fator})/2:0")
298
-
299
- if zoom != 1.0:
300
- filtros_main.append(f"scale=iw*{zoom}:ih*{zoom},crop=iw/{zoom}:ih/{zoom}")
301
-
302
- filtros_main.append(f"setpts=PTS/{velocidade_cortes}")
303
- if ativar_espelhar:
304
- filtros_main.append("hflip")
305
- if ativar_filtro_cor:
306
- filtros_main.append("eq=contrast=1.1:saturation=1.2")
307
-
308
- filtros_main.append("pad=ceil(iw/2)*2:ceil(ih/2)*2")
309
- filtro_final = ",".join(filtros_main)
310
-
311
- video_editado = os.path.join(temp_dir, f"video_editado_{n}.mp4")
312
- subprocess.run([
313
- "ffmpeg", "-i", video_raw, "-vf", filtro_final,
314
- "-c:v", "libx264", "-preset", "ultrafast", "-crf", str(crf_value),
315
- video_editado
316
- ], check=True, stderr=subprocess.PIPE)
317
-
318
- video_final = f"video_final_{n}_{int(time.time())}.mp4"
319
- subprocess.run([
320
- "ffmpeg", "-i", video_editado, "-filter:v", f"setpts=PTS/{velocidade_final}",
321
- "-t", str(duracao_final), "-c:v", "libx264", "-preset", "ultrafast",
322
- "-crf", str(crf_value), "-map_metadata", "-1", "-movflags", "+faststart", video_final
323
- ], check=True, stderr=subprocess.PIPE)
324
-
325
- progresso.progress(90 + int(10 * (n / num_videos_finais)))
326
- st.video(video_final)
327
- with open(video_final, "rb") as f:
328
- st.download_button(f"📅 Baixar Vídeo {n+1}", f, file_name=video_final)
329
-
330
- progresso.progress(100)
331
- st.success("✅ Todos os vídeos foram gerados com sucesso!")
332
-
333
- except subprocess.CalledProcessError as e:
334
- st.error(f"Erro durante processamento: {e.stderr.decode()}")
335
- except Exception as e:
336
- st.error(f"Erro inesperado: {str(e)}")
337
- finally:
338
- shutil.rmtree(temp_dir)
 
5
  import tempfile
6
  import shutil
7
  import time
8
+ import base64
9
 
10
+ # ======================= CONFIG GERAL =======================
11
  st.set_page_config(page_title="Shorts Generator", layout="centered")
 
12
 
13
+ # Abas laterais (igual ao primeiro app)
14
+ abas = ["🎬 Gerador", "🔐 Admin"]
15
+ pagina = st.sidebar.radio("Escolha uma página:", abas)
16
+
17
+ # Categorias e estrutura de pastas
18
  CATEGORIAS = ["AVATAR WORLD", "BLOX FRUITS", "TOCA LIFE", "FC MOBILE"]
19
  BASE_ASSETS = "assets"
20
  for cat in CATEGORIAS:
21
  os.makedirs(os.path.join(BASE_ASSETS, cat, "cortes"), exist_ok=True)
22
+
23
+ # ======================= PÁGINA GERADOR =======================
24
+ if pagina == "🎬 Gerador":
25
+ st.title("🎥 Shorts Generator - Simples")
26
+ st.markdown("Envie seus vídeos e/ou use cortes salvos por categoria para gerar conteúdo com efeitos e controle de duração!")
27
+
28
+ # Seleção de categoria para usar cortes salvos (opcional)
29
+ categoria_selecionada = st.selectbox(
30
+ "Escolha a categoria (opcional, para usar cortes já salvos):",
31
+ ["Selecione..."] + CATEGORIAS
32
+ )
33
+
34
+ cortes_salvos_paths = []
35
+ if categoria_selecionada != "Selecione...":
36
+ path_cortes_salvos = os.path.join(BASE_ASSETS, categoria_selecionada, "cortes")
37
+ if os.path.isdir(path_cortes_salvos):
38
+ cortes_salvos_paths = [
39
+ os.path.join(path_cortes_salvos, f)
40
+ for f in os.listdir(path_cortes_salvos)
41
+ if f.lower().endswith(".mp4")
42
+ ]
43
+ st.caption(f"Encontrados {len(cortes_salvos_paths)} cortes salvos em: {path_cortes_salvos}")
44
+
45
+ # Upload dos vídeos (MANTIDO)
46
+ cortes = st.file_uploader("Envie os vídeos de cortes", type=["mp4"], accept_multiple_files=True)
47
+
48
+ # Configurações (MANTIDO)
49
+ num_videos_finais = st.number_input("Quantos vídeos finais gerar?", min_value=1, max_value=10, value=1)
50
+ duracao_final = st.number_input("Duração final do vídeo (em segundos)", min_value=10, max_value=300, value=30)
51
+
52
+ # Min e Max duração dos cortes (MANTIDO)
53
+ duracao_min = st.slider("Duração mínima de cada corte (s)", 1, 30, 3)
54
+ duracao_max = st.slider("Duração máxima de cada corte (s)", 1, 60, 5)
55
+ if duracao_min > duracao_max:
56
+ st.warning("⚠️ A duração mínima não pode ser maior que a máxima.")
57
+
58
+ # Corte lateral e zoom (MANTIDO)
59
+ corte_lateral = st.slider("Corte lateral (%)", 0, 50, 0, 5)
60
+ zoom = st.slider("Zoom Central (1.0 = normal)", 1.0, 2.0, 1.0, 0.1)
61
+
62
+ # Velocidades e qualidade (MANTIDO)
63
+ velocidade_cortes = st.slider("Velocidade dos cortes", 0.5, 2.0, 1.0, 0.1)
64
+ velocidade_final = st.slider("Velocidade final do vídeo", 0.5, 2.0, 1.0, 0.1)
65
+ crf_value = st.slider("Qualidade CRF (menor = melhor qualidade)", 18, 30, 18)
66
+
67
+ # Outros (MANTIDO)
68
+ st.write("### Outros")
69
+ ativar_espelhar = st.checkbox("Espelhar Vídeo", value=True)
70
+ ativar_filtro_cor = st.checkbox("Filtro de Cor (Contraste/Saturação)", value=True)
71
+
72
+ # Botão principal (MANTIDO, agora combinando uploads + salvos)
73
+ if st.button("Gerar Vídeo(s)"):
74
+ if not cortes and len(cortes_salvos_paths) == 0:
75
+ st.error("❌ Envie vídeos OU selecione uma categoria com cortes salvos para continuar.")
76
+ else:
77
+ with st.spinner('🎥 Seu vídeo está sendo gerado...'):
78
+ progresso = st.progress(0)
79
+ temp_dir = tempfile.mkdtemp()
80
+
81
+ try:
82
+ # === PREPARAR LISTA FINAL DE ARQUIVOS DE CORTE ===
83
+ cortes_names = []
84
+
85
+ # 1) Arquivos enviados (MANTIDO)
86
+ if cortes:
87
+ for idx, corte in enumerate(cortes):
88
+ nome = os.path.join(temp_dir, f"corte_{idx}_{random.randint(1000,9999)}.mp4")
89
+ with open(nome, "wb") as f:
90
+ f.write(corte.read())
91
+ cortes_names.append(nome)
92
+
93
+ # 2) Arquivos salvos na categoria (NOVO – usamos os caminhos diretamente)
94
+ if len(cortes_salvos_paths) > 0:
95
+ cortes_names.extend(cortes_salvos_paths)
96
+
97
+ if len(cortes_names) == 0:
98
+ st.error("❌ Não há cortes disponíveis após combinar uploads e salvos.")
99
+ shutil.rmtree(temp_dir)
100
+ st.stop()
101
+
102
+ progresso.progress(10)
103
+
104
+ for n in range(num_videos_finais):
105
+ cortes_prontos = []
106
+ random.shuffle(cortes_names)
107
+ progresso.progress(10 + int(20 * (n / num_videos_finais)))
108
+
109
+ tempo_total = 0
110
+ while tempo_total < duracao_final:
111
+ for c in cortes_names:
112
+ dur_proc = subprocess.run([
113
+ "ffprobe", "-v", "error", "-show_entries", "format=duration",
114
+ "-of", "default=noprint_wrappers=1:nokey=1", c
115
+ ], stdout=subprocess.PIPE)
116
+
117
+ dur = dur_proc.stdout.decode().strip()
118
+ try:
119
+ d = float(dur)
120
+ dur_aleatoria = random.uniform(duracao_min, duracao_max)
121
+ if d > dur_aleatoria:
122
+ ini = random.uniform(0, d - dur_aleatoria)
123
+ out = os.path.join(temp_dir, f"cut_{n}_{random.randint(1000,9999)}.mp4")
124
+ subprocess.run([
125
+ "ffmpeg", "-ss", str(ini), "-i", c, "-t", str(dur_aleatoria),
126
+ "-an", "-c:v", "libx264", "-preset", "ultrafast", "-crf", "30", out
127
+ ], check=True, stderr=subprocess.PIPE)
128
+ cortes_prontos.append(out)
129
+ tempo_total += dur_aleatoria / velocidade_cortes
130
+ if tempo_total >= duracao_final:
131
+ break
132
+ except:
133
+ continue
134
+
135
+ lista = os.path.join(temp_dir, f"lista_{n}.txt")
136
+ with open(lista, "w") as f:
137
+ for c in cortes_prontos:
138
+ f.write(f"file '{c}'\n")
139
+
140
+ video_raw = os.path.join(temp_dir, f"video_raw_{n}.mp4")
141
+ subprocess.run([
142
+ "ffmpeg", "-f", "concat", "-safe", "0", "-i", lista, "-c:v", "libx264",
143
+ "-preset", "ultrafast", "-crf", "30", video_raw
144
+ ], check=True, stderr=subprocess.PIPE)
145
+
146
+ progresso.progress(40 + int(20 * (n / num_videos_finais)))
147
+
148
+ filtros_main = []
149
+
150
+ if corte_lateral > 0:
151
+ fator = (100 - corte_lateral) / 100
152
+ filtros_main.append(f"crop=iw*{fator}:ih:(iw-iw*{fator})/2:0")
153
+
154
+ if zoom != 1.0:
155
+ filtros_main.append(f"scale=iw*{zoom}:ih*{zoom},crop=iw/{zoom}:ih/{zoom}")
156
+
157
+ filtros_main.append(f"setpts=PTS/{velocidade_cortes}")
158
+ if ativar_espelhar:
159
+ filtros_main.append("hflip")
160
+ if ativar_filtro_cor:
161
+ filtros_main.append("eq=contrast=1.1:saturation=1.2")
162
+
163
+ filtros_main.append("pad=ceil(iw/2)*2:ceil(ih/2)*2")
164
+ filtro_final = ",".join(filtros_main)
165
+
166
+ video_editado = os.path.join(temp_dir, f"video_editado_{n}.mp4")
167
+ subprocess.run([
168
+ "ffmpeg", "-i", video_raw, "-vf", filtro_final,
169
+ "-c:v", "libx264", "-preset", "ultrafast", "-crf", str(crf_value),
170
+ video_editado
171
+ ], check=True, stderr=subprocess.PIPE)
172
+
173
+ video_final = f"video_final_{n}_{int(time.time())}.mp4"
174
+ subprocess.run([
175
+ "ffmpeg", "-i", video_editado, "-filter:v", f"setpts=PTS/{velocidade_final}",
176
+ "-t", str(duracao_final), "-c:v", "libx264", "-preset", "ultrafast",
177
+ "-crf", str(crf_value), "-map_metadata", "-1", "-movflags", "+faststart", video_final
178
+ ], check=True, stderr=subprocess.PIPE)
179
+
180
+ progresso.progress(90 + int(10 * (n / num_videos_finais)))
181
+ st.video(video_final)
182
+ with open(video_final, "rb") as f:
183
+ st.download_button(f"📅 Baixar Vídeo {n+1}", f, file_name=video_final)
184
+
185
+ progresso.progress(100)
186
+ st.success("✅ Todos os vídeos foram gerados com sucesso!")
187
+
188
+ except subprocess.CalledProcessError as e:
189
+ st.error(f"Erro durante processamento: {e.stderr.decode()}")
190
+ except Exception as e:
191
+ st.error(f"Erro inesperado: {str(e)}")
192
+ finally:
193
  shutil.rmtree(temp_dir)
194
+
195
+ # ======================= PÁGINA ADMIN =======================
196
+ elif pagina == "🔐 Admin":
197
+ st.title("🔐 Área de Administração (CORTES)")
198
+
199
+ # Mesma senha do seu primeiro código: base64 de "admin123"
200
+ SENHA_CODIFICADA = "YWRtaW4xMjM="
201
+
202
+ if "autenticado_admin" not in st.session_state:
203
+ st.session_state.autenticado_admin = False
204
+
205
+ if not st.session_state.autenticado_admin:
206
+ senha_input = st.text_input("Senha do Admin:", type="password")
207
+ if st.button("Entrar"):
208
+ if base64.b64encode(senha_input.encode()).decode() == SENHA_CODIFICADA:
209
+ st.session_state.autenticado_admin = True
210
+ st.success("✅ Acesso liberado!")
211
+ else:
212
+ st.error("❌ Senha incorreta.")
213
+ st.stop()
214
+
215
+ if not st.session_state.autenticado_admin:
216
+ st.stop()
217
+
218
+ # Seleção da categoria e upload de CORTES
219
+ cat_admin = st.selectbox("Categoria para salvar os cortes:", CATEGORIAS, key="cat_admin_upload")
220
+ pasta_admin = os.path.join(BASE_ASSETS, cat_admin, "cortes")
221
+ st.caption(f"Pasta de destino: `{pasta_admin}`")
222
+
223
+ arquivos_admin = st.file_uploader(
224
+ "Enviar arquivos de CORTES (.mp4) para esta categoria",
225
+ type=["mp4"], accept_multiple_files=True, key="admin_uploader"
226
+ )
227
+
228
+ if arquivos_admin:
229
+ with st.spinner("⏳ Processando e padronizando cortes..."):
230
+ for f in arquivos_admin:
231
+ ext = os.path.splitext(f.name)[1].lower()
232
+ nome_aleatorio = f"{random.randint(1000000, 9999999)}{ext}"
233
+ temp_in = os.path.join(tempfile.gettempdir(), f"adm_input_{nome_aleatorio}")
234
+ with open(temp_in, "wb") as out:
235
+ out.write(f.read())
236
+
237
+ final_out = os.path.join(pasta_admin, nome_aleatorio)
238
+
239
+ # Padroniza o vídeo (igual ao seu primeiro app)
240
+ subprocess.run([
241
+ "ffmpeg", "-i", temp_in,
242
+ "-vf", "scale=trunc(iw/2)*2:trunc(ih/2)*2,fps=30",
243
+ "-c:v", "libx264", "-preset", "ultrafast", "-crf", "25",
244
+ "-y", final_out
245
+ ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
246
+
247
+ os.remove(temp_in)
248
+
249
+ st.success("✅ Cortes enviados, padronizados e salvos com sucesso!")
250
+
251
+ # Listagem e exclusão
252
+ st.write("Arquivos existentes nesta categoria:")
253
+ try:
254
+ arquivos_existentes = sorted([f for f in os.listdir(pasta_admin) if f.lower().endswith(".mp4")])
255
+ except FileNotFoundError:
256
+ arquivos_existentes = []
257
+
258
+ if not arquivos_existentes:
259
+ st.info("Nenhum arquivo encontrado nesta categoria.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
260
  else:
261
+ for arq in arquivos_existentes:
262
+ col1, col2 = st.columns([5, 1])
263
+ with col1:
264
+ st.markdown(f"`{arq}`")
265
+ with col2:
266
+ if st.button("🗑", key=f"del_{cat_admin}_{arq}"):
267
+ os.remove(os.path.join(pasta_admin, arq))
268
+ st.success(f"❌ Arquivo '{arq}' excluído.")
269
+ st.rerun()