File size: 17,440 Bytes
175f17e
b98d6f0
 
e055d9f
ac6dcc9
b98d6f0
529b6cb
63990ea
b98d6f0
 
63990ea
a07972f
b98d6f0
 
e055d9f
b98d6f0
 
e055d9f
63990ea
 
 
 
b98d6f0
 
 
 
 
 
 
 
63990ea
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b98d6f0
 
 
 
bf0f83c
b98d6f0
63990ea
b98d6f0
 
 
 
 
 
 
 
669458b
bfb3511
949b5e2
 
 
b98d6f0
 
 
 
 
 
 
267f8f7
81c225e
b98d6f0
 
6e4fb72
267f8f7
b98d6f0
 
 
 
 
 
 
 
 
 
 
 
63990ea
300beb0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5eadf15
300beb0
 
5eadf15
300beb0
 
 
 
 
 
 
 
 
 
 
 
6e4fb72
 
300beb0
 
 
 
 
5eadf15
 
949b5e2
5eadf15
 
 
 
949b5e2
 
5eadf15
 
 
 
 
 
949b5e2
5eadf15
 
 
 
 
 
949b5e2
5eadf15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bfb3511
 
5eadf15
bfb3511
 
 
 
 
 
 
5eadf15
 
 
 
 
b98d6f0
 
 
5eadf15
b98d6f0
 
 
 
5eadf15
b98d6f0
6e4fb72
b98d6f0
 
 
 
 
 
 
 
6e4fb72
b98d6f0
 
 
 
 
 
 
6e4fb72
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b98d6f0
69d05a9
 
 
 
 
 
 
 
 
bfb3511
69d05a9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b98d6f0
 
 
 
 
 
 
 
6e4fb72
69d05a9
 
 
 
 
6e4fb72
69d05a9
 
 
 
 
 
 
 
b98d6f0
6e4fb72
b98d6f0
 
 
 
6e4fb72
b98d6f0
 
 
 
 
 
 
6e4fb72
b98d6f0
 
 
 
 
 
 
 
 
 
6e4fb72
 
 
b98d6f0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d05904f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
import streamlit as st
import subprocess
import random
import os
import tempfile
import time
import base64
import uuid
import shutil
import datetime
import yt_dlp

st.set_page_config(page_title="🎬 Cortar e Embaralhar Vídeo", layout="centered")
st.title("🎬 Cortar e Embaralhar Vídeo")

st.markdown("**1️⃣ Envie um vídeo curto (máx 3 minutos)**")
video = st.file_uploader("Selecione um vídeo", type=["mp4"])

st.markdown("**🔗 Ou insira o link do TikTok**")
url = st.text_input("URL do vídeo TikTok")

# ----- ESTADO -----
if "processando" not in st.session_state:
    st.session_state.processando = False
if "video_path" not in st.session_state:
    st.session_state.video_path = None
if "tutorial_path" not in st.session_state:
    st.session_state.tutorial_path = None
if "video_final" not in st.session_state:
    st.session_state.video_final = None
# ----- BOTÃO PUXAR VÍDEO -----
if url and st.button("🔄 Puxar vídeo do TikTok"):
    with st.spinner("Baixando vídeo do TikTok..."):
        unique_id = str(uuid.uuid4())
        saida_video = os.path.join(tempfile.gettempdir(), f'tiktok_video_{unique_id}.mp4')
        ydl_opts = {
            'outtmpl': saida_video,
            'format': 'mp4',
            'noplaylist': True
        }
        try:
            with yt_dlp.YoutubeDL(ydl_opts) as ydl:
                ydl.download([url])
            if os.path.exists(saida_video):
                st.session_state.video_path = saida_video
                st.success("✅ Vídeo do TikTok baixado com sucesso!")
        except Exception as e:
            st.error(f"Erro ao baixar vídeo: {e}")

# ----- SE USOU UPLOAD -----
if video and not st.session_state.video_path:
    with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp:
        tmp.write(video.read())
        st.session_state.video_path = tmp.name

video_path = st.session_state.video_path
# ----- PLAYER DO VÍDEO ANTES DOS CORTES -----
if video_path and not st.session_state.processando:
    st.subheader("🎥 Pré-visualização do vídeo original")
    st.video(video_path)

    st.markdown("**2️⃣ Configure a exclusão e os cortes**")
    excluir_inicio = st.number_input("Excluir a partir do segundo", min_value=0, value=8, key="excluir_inicio")
    excluir_fim = st.number_input("Excluir até o segundo", min_value=0, value=12, key="excluir_fim")

    st.checkbox("✂️ Ativar cortes e embaralhamento aleatório", value=False, key="embaralhar_cortes")

    if st.session_state.embaralhar_cortes:
        corte_min = st.number_input("Tamanho mínimo do corte (segundos)", min_value=1, value=2, key="corte_min")
        corte_max = st.number_input("Tamanho máximo do corte (segundos)", min_value=3, value=5, key="corte_max")

    tutorial = st.file_uploader("Adicionar tutorial (opcional)", type=["mp4"])
    if tutorial:
        with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp:
            tmp.write(tutorial.read())
            st.session_state.tutorial_path = tmp.name

    with st.expander("⚙️ Opções avançadas (zoom, velocidade, música, espelhamento)"):
        zoom = st.slider("Zoom (%)", 100, 300, 110, key="zoom")
        velocidade_cortes = st.slider("Velocidade dos cortes", 0.8, 2.0, 1.0, key="velocidade_cortes")
        velocidade_final = st.slider("Velocidade final do vídeo", 0.8, 2.0, 1.0, key="velocidade_final")
        espelhar = st.checkbox("Espelhar vídeo final", value=False, key="espelhar")

        musica = st.file_uploader("Adicionar música (opcional)", type=["mp3"])
        if musica:
            with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmpmus:
                tmpmus.write(musica.read())
                st.session_state.musica_path = tmpmus.name
        else:
            st.session_state.musica_path = None

    processar = st.button("Gerar novo vídeo")

    if processar:
        st.session_state.processando = True
        st.session_state.video_final = None  # Reset do vídeo final
if st.session_state.processando:

    loader = st.empty()
    barra = st.progress(0)

    loader.markdown(
        """
        <div style='display:flex; align-items:center; gap:10px; font-size:20px;'>
            <span class="loader"></span> <strong>Processando...</strong>
        </div>
        <style>
        .loader {
          border: 4px solid #f3f3f3;
          border-top: 4px solid #3498db;
          border-radius: 50%;
          width: 20px;
          height: 20px;
          animation: spin 1s linear infinite;
        }
        @keyframes spin {
          0% { transform: rotate(0deg); }
          100% { transform: rotate(360deg); }
        }
        </style>
        """, unsafe_allow_html=True
    )

    status = st.empty()
    status.markdown("🔎 Analisando duração e resolução do vídeo...")
    barra.progress(5)

    # Duração e resolução
    duracao_cmd = [
        "ffprobe", "-v", "error", "-show_entries",
        "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", video_path
    ]
    resultado = subprocess.run(duracao_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    dur = float(resultado.stdout.decode().strip())
    dur = int(dur)

    resolucao_cmd = [
        "ffprobe", "-v", "error", "-select_streams", "v:0",
        "-show_entries", "stream=width,height",
        "-of", "csv=s=x:p=0", video_path
    ]
    resultado = subprocess.run(resolucao_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    largura, altura = resultado.stdout.decode().strip().split("x")
    largura = int(largura)
    altura = int(altura)

    excluir_inicio = st.session_state.excluir_inicio
    excluir_fim = st.session_state.excluir_fim
    velocidade_cortes = st.session_state.velocidade_cortes

    barra.progress(10)
    status.markdown("🧹 Removendo parte indesejada...")

    with tempfile.TemporaryDirectory() as tmpdir:
        parte1 = os.path.join(tmpdir, "parte1.mp4")
        parte2 = os.path.join(tmpdir, "parte2.mp4")
        video_base = os.path.join(tmpdir, "video_base.mp4")

        if excluir_inicio > 0:
            subprocess.call([
                "ffmpeg", "-y", "-i", video_path,
                "-t", str(excluir_inicio),
                "-c", "copy",
                parte1
            ])
        if excluir_fim < dur:
            subprocess.call([
                "ffmpeg", "-y", "-i", video_path,
                "-ss", str(excluir_fim),
                "-c", "copy",
                parte2
            ])

        with open(os.path.join(tmpdir, "lista_remocao.txt"), "w") as f:
            if os.path.exists(parte1):
                f.write(f"file '{parte1}'\n")
            if os.path.exists(parte2):
                f.write(f"file '{parte2}'\n")

        subprocess.call([
            "ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i",
            os.path.join(tmpdir, "lista_remocao.txt"),
            "-c", "copy",
            video_base
        ])

        barra.progress(15)
        status.markdown("✂️ Preparando cortes...")

        cortes = []
        usados = set()

        if st.session_state.embaralhar_cortes:
            corte_min = st.session_state.corte_min
            corte_max = st.session_state.corte_max

            # Duração nova (do vídeo já limpo)
            resultado = subprocess.run([
                "ffprobe", "-v", "error", "-show_entries",
                "format=duration", "-of",
                "default=noprint_wrappers=1:nokey=1", video_base
            ], stdout=subprocess.PIPE)
            dur_base = float(resultado.stdout.decode().strip())

            pos = 0
            while pos < dur_base:
                tam_possivel = min(corte_max, dur_base - pos)
                if tam_possivel < corte_min:
                    break
                tamanho = random.randint(corte_min, int(tam_possivel))
                fim_corte = pos + tamanho

                if (pos, fim_corte) not in usados:
                    cortes.append((int(pos), int(fim_corte)))
                    usados.add((pos, fim_corte))
                pos = fim_corte

            random.shuffle(cortes)
        else:
            # Sem cortes — usa vídeo já limpo
            cortes = [(0, dur)]  # isso será ignorado e o vídeo_base usado direto mais abaixo
        barra.progress(25)

        arquivos_cortes = []
        total_cortes = len(cortes)

        for idx, (start, end) in enumerate(cortes):
            saida = os.path.join(tmpdir, f"clip_{idx}.mp4")

            subprocess.call([
                "ffmpeg", "-y", "-i", video_base,
                "-ss", str(start), "-to", str(end),
                "-filter:v", f"setpts=PTS/{velocidade_cortes}",
                "-an",
                "-c:v", "libx264", "-preset", "veryfast",
                saida
            ])

            if os.path.exists(saida) and os.path.getsize(saida) > 1000:
                arquivos_cortes.append(saida)

            barra.progress(25 + int(((idx + 1) / total_cortes) * 30))

        if not arquivos_cortes:
            barra.empty()
            loader.empty()
            status.markdown("❌ Nenhum corte foi criado.")
            st.stop()

        barra.progress(60)
        status.markdown("🔗 Unindo cortes...")

        lista_txt = os.path.join(tmpdir, "lista_cortes.txt")
        with open(lista_txt, "w") as f:
            for arquivo in arquivos_cortes:
                f.write(f"file '{arquivo}'\n")

        video_unido = os.path.join(tmpdir, "video_unido.mp4")
        subprocess.call([
            "ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i", lista_txt,
            "-c:v", "libx264", "-preset", "veryfast",
            video_unido
        ])

        barra.progress(70)
        status.markdown("🔍 Aplicando zoom e espelhamento no vídeo unido...")

        video_processado = os.path.join(tmpdir, "video_processado.mp4")
        zoom_factor = st.session_state.zoom / 100
        filtro_zoom = f"scale=iw*{zoom_factor}:ih*{zoom_factor},crop={largura}:{altura}"

        if st.session_state.espelhar:
            filtro_zoom += ",hflip"

        subprocess.call([
            "ffmpeg", "-y", "-i", video_unido,
            "-vf", filtro_zoom,
            "-c:v", "libx264", "-preset", "veryfast",
            video_processado
        ])
        barra.progress(75)
        status.markdown("📌 Inserindo tutorial (se houver)...")

        if st.session_state.tutorial_path:
            # Pega duração do vídeo processado
            duracao_cmd = [
                "ffprobe", "-v", "error", "-show_entries",
                "format=duration", "-of",
                "default=noprint_wrappers=1:nokey=1", video_processado
            ]
            resultado = subprocess.run(duracao_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            duracao_video = float(resultado.stdout.decode().strip())

            # Escolhe posição aleatória entre 3s e final - 1s
            pos_tutorial = random.uniform(3, duracao_video - 1)

            parte1 = os.path.join(tmpdir, "parte1.mp4")
            parte2 = os.path.join(tmpdir, "parte2.mp4")

            subprocess.call([
                "ffmpeg", "-y", "-i", video_processado,
                "-t", str(pos_tutorial),
                "-c", "copy",
                parte1
            ])

            subprocess.call([
                "ffmpeg", "-y", "-i", video_processado,
                "-ss", str(pos_tutorial),
                "-c", "copy",
                parte2
            ])

            tutorial_pad = os.path.join(tmpdir, "tutorial_pad.mp4")
            subprocess.call([
                "ffmpeg", "-y", "-i", st.session_state.tutorial_path,
                "-vf", f"scale={largura}:{altura}",
                "-c:v", "libx264", "-preset", "veryfast",
                "-c:a", "aac",
                tutorial_pad
            ])

            lista_final_txt = os.path.join(tmpdir, "lista_final.txt")
            with open(lista_final_txt, "w") as f:
                f.write(f"file '{parte1}'\n")
                f.write(f"file '{tutorial_pad}'\n")
                f.write(f"file '{parte2}'\n")

            video_com_tutorial = os.path.join(tmpdir, "video_com_tutorial.mp4")
            subprocess.call([
                "ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i", lista_final_txt,
                "-c:v", "libx264", "-preset", "veryfast",
                video_com_tutorial
            ])
        else:
            video_com_tutorial = video_processado

        barra.progress(80)
        status.markdown("🚀 Aplicando velocidade final...")

        video_final_temp = os.path.join(tmpdir, "video_final_temp.mp4")
        subprocess.call([
            "ffmpeg", "-y", "-i", video_com_tutorial,
            "-filter:v", f"setpts=PTS/{st.session_state.velocidade_final}",
            "-c:v", "libx264", "-preset", "veryfast",
            video_final_temp
        ])

        video_final = os.path.join(tmpdir, "video_final.mp4")
        if st.session_state.musica_path:
            barra.progress(90)
            status.markdown("🎵 Adicionando música...")
            subprocess.call([
                "ffmpeg", "-y", "-i", video_final_temp, "-i", st.session_state.musica_path,
                "-shortest",
                "-c:v", "copy",
                "-c:a", "aac",
                video_final
            ])
        else:
            os.rename(video_final_temp, video_final)
        barra.progress(95)
        status.markdown("📋 Adicionando metadata...")

        marcas = {
            "samsung": ["SM-S918B", "SM-A546E", "SM-M536B"],
            "xiaomi": ["Redmi Note 12", "Poco X5", "Mi 11 Lite"],
            "motorola": ["Moto G84", "Moto Edge 40", "Moto E13"],
            "apple": ["iPhone 13", "iPhone 14 Pro", "iPhone SE"],
            "google": ["Pixel 6", "Pixel 7a"]
        }

        marca = random.choice(list(marcas.keys()))
        modelo = random.choice(marcas[marca])
        software = f"{marca.capitalize()} Video Editor {random.randint(1,5)}.{random.randint(0,9)}"
        latitude = round(random.uniform(-33.0, -2.0), 4)
        longitude = round(random.uniform(-60.0, -35.0), 4)
        location = f"{latitude:+.4f}{longitude:+.4f}/"
        creation_time = (datetime.datetime.now() - datetime.timedelta(days=random.randint(0,5))).strftime("%Y-%m-%dT%H:%M:%SZ")

        video_final_meta = os.path.join(tmpdir, "video_final_meta.mp4")
        metadata_cmd = [
            "ffmpeg", "-y", "-i", video_final,
            "-metadata", "title=Video Shorts",
            "-metadata", "comment=Captured with mobile device",
            "-metadata", f"make={marca}",
            "-metadata", f"model={modelo}",
            "-metadata", f"software={software}",
            "-metadata", f"creation_time={creation_time}",
            "-metadata", f"location={location}",
            "-c", "copy",
            video_final_meta
        ]
        subprocess.call(metadata_cmd)

        barra.empty()
        loader.empty()
        status.empty()

        barra.progress(100)

        video_final_permanente = os.path.join(tempfile.gettempdir(), "video_final_final.mp4")
        shutil.copy(video_final_meta, video_final_permanente)
        st.session_state.video_final = video_final_permanente
# --- PLAYER DOS VÍDEOS E BOTÕES ---
if st.session_state.video_final:

    st.success("✅ Vídeo criado com sucesso!")

    load_players = st.empty()
    load_players.markdown(
        """
        <div style='display:flex; align-items:center; gap:10px; font-size:20px; margin-top:20px;'>
            <span class="loader"></span> <strong>Aguarde, preparando os players...</strong>
        </div>
        <style>
        .loader {
          border: 4px solid #f3f3f3;
          border-top: 4px solid #3498db;
          border-radius: 50%;
          width: 20px;
          height: 20px;
          animation: spin 1s linear infinite;
        }
        @keyframes spin {
          0% { transform: rotate(0deg); }
          100% { transform: rotate(360deg); }
        }
        </style>
        """, unsafe_allow_html=True
    )

    time.sleep(1)

    st.subheader("🔎 Comparação lado a lado")
    st.markdown(f"""
    <div style='display: flex; gap: 10px; justify-content: center; align-items: flex-start; flex-wrap: nowrap; overflow-x: auto;'>
        <div style='flex: 1; min-width: 45%;'>
            <strong>🎬 Original</strong><br>
            <video controls style='width: 100%; height: auto;'>
                <source src="data:video/mp4;base64,{base64.b64encode(open(video_path, "rb").read()).decode()}" type="video/mp4">
            </video>
        </div>
        <div style='flex: 1; min-width: 45%;'>
            <strong>🎬 Novo</strong><br>
            <video controls style='width: 100%; height: auto;'>
                <source src="data:video/mp4;base64,{base64.b64encode(open(st.session_state.video_final, "rb").read()).decode()}" type="video/mp4">
            </video>
        </div>
    </div>
    """, unsafe_allow_html=True)

    load_players.empty()

    with open(st.session_state.video_final, "rb") as f:
        st.download_button("⬇️ Baixar vídeo", f, file_name="video_novo.mp4")

    if st.button("🔄 Criar novo"):
        for key in list(st.session_state.keys()):
            del st.session_state[key]
        st.experimental_rerun()

    if st.button("🔄 Gerar novamente com os mesmos ajustes"):
        st.session_state.processando = True
        st.session_state.video_final = None