from flask import Flask, request, send_file, render_template_string, jsonify
import subprocess, os, uuid, json, re, threading, queue, time
from datetime import datetime

app = Flask(__name__)

# ================= CONFIGURAÇÕES =================
VIDEO_DIR = "/var/project/videos/drige"
TMP_THUMBS = "/tmp/thumbs"
CUT_DIR = "/var/www/project/cortes"
SNAPSHOT_DIR = "/var/www/project/snapshots" 
QUEUE_FILE = "/tmp/video_queue.json"

os.makedirs(TMP_THUMBS, exist_ok=True)
os.makedirs(CUT_DIR, exist_ok=True)
os.makedirs(SNAPSHOT_DIR, exist_ok=True)

# ================= SISTEMA DE FILA =================
class ConversionQueue:
    def __init__(self, max_workers=3):
        self.queue = queue.Queue()
        self.active_jobs = {}
        self.completed_jobs = {}
        self.failed_jobs = {}
        self.max_workers = max_workers
        self.lock = threading.Lock()
        self.load_state()
        for _ in range(max_workers): threading.Thread(target=self.worker, daemon=True).start()
    
    def load_state(self):
        try:
            if os.path.exists(QUEUE_FILE):
                with open(QUEUE_FILE, 'r') as f:
                    data = json.load(f)
                    self.completed_jobs = data.get('completed', {})
                    self.failed_jobs = data.get('failed', {})
        except: pass
    
    def save_state(self):
        try:
            with open(QUEUE_FILE, 'w') as f:
                json.dump({'completed': self.completed_jobs, 'failed': self.failed_jobs}, f)
        except: pass

    def clear_history(self):
        with self.lock:
            self.completed_jobs = {}
            self.failed_jobs = {}
            self.save_state()

    def get_all_status(self):
        with self.lock:
            return {
                'queued': list(self.active_jobs.values()),
                'processing': [j for j in self.active_jobs.values() if j['status'] == 'processing'],
                'completed': list(self.completed_jobs.values())[::-1], 
                'failed': list(self.failed_jobs.values())[::-1]
            }
    
    def add_job(self, job_id, job_data):
        with self.lock:
            job_data['id'] = job_id
            job_data['status'] = 'queued'
            job_data['progress'] = 0
            job_data['created_at'] = datetime.now().strftime("%H:%M:%S")
            self.active_jobs[job_id] = job_data
        self.queue.put(job_data)
        return job_id
    
    def worker(self):
        while True:
            job = self.queue.get()
            if job is None: break
            try:
                with self.lock: self.active_jobs[job['id']]['status'] = 'processing'
                self.process_conversion(job)
                with self.lock:
                    job['status'] = 'completed'
                    self.completed_jobs[job['id']] = job
                    if job['id'] in self.active_jobs: del self.active_jobs[job['id']]
                    self.save_state()
            except Exception as e:
                with self.lock:
                    job['status'] = 'failed'
                    job['error'] = str(e)
                    self.failed_jobs[job['id']] = job
                    if job['id'] in self.active_jobs: del self.active_jobs[job['id']]
                    self.save_state()
            finally:
                self.queue.task_done()
    
    def process_conversion(self, job):
        src_file = os.path.join(VIDEO_DIR, job['pasta'], job['file'])
        dest_dir = os.path.join(CUT_DIR, job['pasta_corte']) if job['pasta_corte'] else CUT_DIR
        os.makedirs(dest_dir, exist_ok=True)
        
        if job.get('custom_name'):
            safe_name = "".join([c for c in job['custom_name'] if c.isalnum() or c in "._- "]).strip()
            if not safe_name: safe_name = f"cut_{job['id'][:8]}"
        else:
            safe_name = f"cut_{job['id'][:8]}"
            
        output_path = os.path.join(dest_dir, f"{safe_name}.mp4")
        
        cmd = [
            "ffmpeg", "-y", "-progress", "pipe:1",
            "-ss", str(job['start']), 
            "-t", str(job['duration']),
            "-i", src_file
        ]

        vf_filters = []
        if job.get('mode') == 'tiktok':
            vf_filters.append("crop=ih*(9/16):ih")
        
        if vf_filters:
            cmd.extend(["-vf", ",".join(vf_filters)])

        cmd.extend(["-c:v", "libx264", "-preset", "ultrafast"])
        
        if job.get('mode') == 'whatsapp':
            cmd.extend(["-crf", "28"])
        else:
            cmd.extend(["-crf", "22"])

        if job.get('mute'):
            cmd.append("-an")
        else:
            cmd.extend(["-c:a", "aac", "-b:a", "128k"])

        cmd.extend(["-movflags", "+faststart", output_path])
        
        process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
        
        while True:
            line = process.stdout.readline()
            if not line: break
            if 'out_time_ms' in line:
                match = re.search(r'out_time_ms=(\d+)', line)
                if match:
                    time_s = int(match.group(1)) / 1000000.0
                    progress = min(int((time_s / job['duration']) * 100), 100)
                    with self.lock:
                        if job['id'] in self.active_jobs: self.active_jobs[job['id']]['progress'] = progress
        
        _, stderr = process.communicate()
        if process.returncode != 0:
            raise Exception(f"Erro FFmpeg: {stderr[-200:]}")
            
        job['output_path'] = output_path

conversion_queue = ConversionQueue()

# ================= TEMPLATE HTML =================

TEMPLATE = """
<!DOCTYPE html>
<html lang="pt-br">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>✂️ Studio Mobile V3.3</title>
    <link rel="stylesheet" href="https://cdn.plyr.io/3.7.8/plyr.css" />
    <style>
        :root { --primary: #6366f1; --bg: #0f172a; --card: #1e293b; --text: #f8fafc; --accent: #10b981; --danger: #ef4444; }
        body { font-family: 'Segoe UI', sans-serif; background: var(--bg); color: var(--text); margin: 0; padding-bottom: 100px; -webkit-tap-highlight-color: transparent; }
        
        .header { background: var(--primary); padding: 10px; text-align: center; font-weight: bold; font-size: 16px; position: sticky; top:0; z-index:100; box-shadow:0 2px 10px rgba(0,0,0,0.3); }
        .player-wrapper { position: sticky; top: 40px; z-index: 99; background: #000; }
        .plyr { --plyr-color-main: var(--primary); }

        .mobile-controls { display: grid; grid-template-columns: repeat(4, 1fr); gap: 5px; padding: 10px; background: #1e293b; border-bottom: 1px solid #334155; }
        .btn-seek { background: #334155; color: white; border: none; padding: 12px 5px; border-radius: 8px; font-size: 11px; font-weight: bold; }
        .btn-seek:active { background: var(--primary); }

        .main-actions { padding: 15px; background: var(--bg); }
        .row { display: flex; gap: 8px; margin-bottom: 12px; align-items: flex-end; }
        .col { flex: 1; display: flex; flex-direction: column; }
        label { font-size: 11px; font-weight: bold; color: #94a3b8; margin-bottom: 5px; text-transform: uppercase; }
        
        select, input[type="text"], input[type="number"] { 
            padding: 12px; border-radius: 8px; border: 1px solid #334155; 
            background: #1e293b; color: white; font-size: 16px; width: 100%; box-sizing: border-box; 
        }

        .btn { padding: 14px; border: none; border-radius: 10px; font-weight: bold; font-size: 14px; width: 100%; cursor: pointer; transition:0.2s; }
        .btn:active { transform: scale(0.97); }
        .btn-cut { background: linear-gradient(135deg, var(--accent), #059669); color: white; margin-top:15px; font-size: 16px; padding: 16px; box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3); }
        .btn-marker { background: var(--primary); color: white; }
        .btn-photo { background: #eab308; color: black; border:none; }

        .options-box { background: #1e293b; padding: 12px; border-radius: 10px; margin-bottom: 15px; border: 1px solid #334155; }
        .checkbox-group { display: flex; gap: 15px; margin-top: 5px; }
        .radio-label { display: flex; align-items: center; gap: 6px; font-size: 13px; background: #0f172a; padding: 8px 12px; border-radius: 6px; flex:1; justify-content:center; border:1px solid #334155; }
        input[type="radio"], input[type="checkbox"] { transform: scale(1.2); }

        .thumbs-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); gap: 8px; padding: 0 15px; }
        .thumb { position: relative; border-radius: 8px; overflow: hidden; border: 2px solid transparent; cursor: pointer; opacity: 0; animation: fadeIn 0.5s forwards; }
        @keyframes fadeIn { to { opacity: 1; } }
        
        .thumb:active { border-color: var(--accent); opacity: 0.7; }
        .thumb img { width: 100%; display: block; aspect-ratio: 16/9; object-fit: cover; }
        .thumb .time-badge { position: absolute; top: 3px; left: 3px; background: rgba(0,0,0,0.8); padding: 2px 5px; border-radius: 4px; font-size: 9px; }
        .thumb .quick-cut { position: absolute; bottom: 0; width: 100%; background: var(--accent); color: white; font-size: 10px; text-align: center; padding: 4px 0; font-weight: bold; opacity: 0.95; }

        .queue-section { padding: 15px; margin-top: 20px; border-top: 1px solid #334155; }
        .queue-item { background: var(--card); padding: 12px; border-radius: 8px; margin-bottom: 8px; border-left: 4px solid var(--primary); font-size: 12px; }
        .progress-bg { height: 6px; background: #334155; border-radius: 3px; margin-top: 8px; }
        .progress-fg { height: 100%; background: var(--accent); border-radius: 3px; }
        
        .btn-more { width: 100%; background: transparent; border: 1px solid #475569; color: #94a3b8; padding: 8px; margin-top: 10px; border-radius: 8px; font-size: 12px; }

        .toast { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); background: var(--accent); color: white; padding: 10px 20px; border-radius: 20px; display: none; z-index: 1000; box-shadow: 0 5px 15px rgba(0,0,0,0.5); width: 80%; text-align: center; }
        
        .loading-pulse { animation: pulse 1s infinite; }
        @keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.5; } 100% { opacity: 1; } }
    </style>
</head>
<body>
    <div class="header">✂️ STUDIO MOBILE V3.3</div>
    
    <div class="player-wrapper">
        <video id="player" playsinline controls crossorigin></video>
    </div>

    <div class="mobile-controls">
        <button class="btn-seek" onclick="seek(-1)">⏪ -1s</button>
        <button class="btn-seek" onclick="seek(-0.04)">◀ Frame</button>
        <button class="btn-seek" onclick="seek(0.04)">Frame ▶</button>
        <button class="btn-seek" onclick="seek(1)">+1s ⏩</button>
    </div>

    <div class="main-actions">
        <div class="row">
            <div class="col"><select id="pasta" onchange="loadVideos(true)">{% for p in pastas %}<option value="{{p}}">{{p}}</option>{% endfor %}</select></div>
            <div class="col"><select id="videoSelect" onchange="changeVideo(true)"></select></div>
        </div>

        <div class="row">
            <div class="col" style="flex:1.5">
                <label>Início (Min:Seg)</label>
                <div style="display:flex; gap:5px">
                    <input type="number" id="startMin" value="0">
                    <input type="number" id="startSec" value="0">
                </div>
            </div>
            <div class="col">
                <button class="btn btn-marker" onclick="updateStartFromPlayer()">📍 MARCAR</button>
            </div>
        </div>

        <div class="row">
            <div class="col" style="flex:1.5">
                <label>Duração (Min:Seg)</label>
                <div style="display:flex; gap:5px">
                    <input type="number" id="durMin" value="0">
                    <input type="number" id="durSec" value="30">
                </div>
            </div>
            <div class="col">
                <button class="btn btn-photo" onclick="takeSnapshot()">📸 FOTO</button>
            </div>
        </div>

        <div class="options-box">
            <label style="margin-bottom:8px; display:block">📝 Nome do Arquivo</label>
            <input type="text" id="customName" placeholder="Ex: Corte_Episodio_01">
            
            <label style="margin-top:12px; margin-bottom:8px; display:block">⚙️ Formato & Áudio</label>
            <div class="checkbox-group">
                <label class="radio-label"><input type="radio" name="mode" value="normal" checked> Normal</label>
                <label class="radio-label"><input type="radio" name="mode" value="tiktok"> 📱 TikTok</label>
                <label class="radio-label"><input type="radio" name="mode" value="whatsapp"> 📉 Zap</label>
            </div>
            <div style="margin-top:10px">
                <label class="radio-label" style="justify-content:flex-start">
                    <input type="checkbox" id="muteAudio"> 🔇 Remover Áudio
                </label>
            </div>
        </div>

        <div class="row">
            <div class="col"><label>Destino</label><select id="pastaCorte" onchange="saveState()"><option value="">Raiz</option>{% for pc in pastas_corte %}<option value="{{pc}}">{{pc}}</option>{% endfor %}</select></div>
        </div>

        <button class="btn btn-cut" onclick="addQueue()">✂️ CORTAR / RENDERIZAR</button>
        
        <button id="btnGenThumbs" class="btn" style="background:transparent; border:1px solid #6366f1; color:#6366f1; margin-top:15px; padding:10px" onclick="genThumbs()">
            🖼️ CARREGAR FRAMES
        </button>
    </div>

    <div class="thumbs-grid" id="thumbs"></div>

    <div class="queue-section">
        <div style="display:flex; justify-content:space-between; margin-bottom:10px">
            <label>FILA DE TAREFAS</label>
            <button onclick="clearHistory()" style="background:transparent; border:1px solid #ef4444; color:#ef4444; padding:3px 8px; border-radius:4px; font-size:10px">LIMPAR</button>
        </div>
        <div id="queueList"></div>
        <button id="btnShowMore" class="btn-more" onclick="toggleShowAll()" style="display:none">🔽 VER HISTÓRICO COMPLETO</button>
    </div>

    <div id="toast" class="toast">Ação realizada!</div>

    <script src="https://cdn.plyr.io/3.7.8/plyr.js"></script>
    <script>
        const player = new Plyr('#player', {
            controls: ['play-large', 'play', 'progress', 'current-time', 'mute', 'fullscreen'],
            speed: { selected: 1, options: [0.5, 1, 1.5, 2] }
        });

        let showAllQueue = false;
        let thumbsPollInterval;
        let loadedFramesSet = new Set();

        function seek(amount) { player.currentTime = Math.max(0, player.currentTime + amount); }

        function updateStartFromPlayer() {
            const t = player.currentTime;
            document.getElementById('startMin').value = Math.floor(t / 60);
            document.getElementById('startSec').value = Math.floor(t % 60);
            showToast(`📍 Início: ${Math.floor(t/60)}m:${Math.floor(t%60)}s`);
        }

        async function takeSnapshot() {
            const p = document.getElementById('pasta').value;
            const v = document.getElementById('videoSelect').value;
            const t = player.currentTime;
            
            showToast("📸 Salvando Foto...");
            const res = await fetch('/take_snapshot', {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({ pasta: p, file: v, time: t })
            });
            if (res.ok) {
                const data = await res.json();
                showToast(`✅ Foto salva: ${data.name}`);
            } else {
                showToast("❌ Erro ao salvar foto");
            }
        }

        function showToast(txt) {
            const t = document.getElementById('toast');
            t.innerText = txt; t.style.display = 'block';
            setTimeout(() => t.style.display = 'none', 2500);
        }

        function saveState() {
            localStorage.setItem('last_pasta', document.getElementById('pasta').value);
            localStorage.setItem('last_video', document.getElementById('videoSelect').value);
            localStorage.setItem('last_dest', document.getElementById('pastaCorte').value);
        }

        async function loadVideos(isManual) {
            const pasta = document.getElementById('pasta').value;
            const res = await fetch(`/videos_list?pasta=${encodeURIComponent(pasta)}`);
            const videos = await res.json();
            const sel = document.getElementById('videoSelect');
            sel.innerHTML = videos.map(v => `<option value="${v}">${v}</option>`).join('');
            if (!isManual) {
                const lastVid = localStorage.getItem('last_video');
                if (lastVid && videos.includes(lastVid)) sel.value = lastVid;
            }
            changeVideo(isManual);
        }

        function changeVideo(isManual) {
            const p = document.getElementById('pasta').value;
            const v = document.getElementById('videoSelect').value;
            if(!v) return;
            player.source = { type: 'video', sources: [{ src: `/video?pasta=${encodeURIComponent(p)}&file=${encodeURIComponent(v)}`, type: 'video/mp4' }] };
            
            // Limpa thumbs anteriores ao trocar video
            document.getElementById('thumbs').innerHTML = '';
            loadedFramesSet.clear();
            stopThumbsPolling();
            
            if(isManual) saveState();
        }

        async function addQueue(customStart = null) {
            let startT = customStart !== null ? customStart : (parseInt(document.getElementById('startMin').value)*60 + parseInt(document.getElementById('startSec').value));
            
            const mode = document.querySelector('input[name="mode"]:checked').value;
            const mute = document.getElementById('muteAudio').checked;
            const customName = document.getElementById('customName').value;

            const data = {
                pasta: document.getElementById('pasta').value,
                file: document.getElementById('videoSelect').value,
                start: startT,
                duration: (parseInt(document.getElementById('durMin').value)*60 + parseInt(document.getElementById('durSec').value)),
                pasta_corte: document.getElementById('pastaCorte').value,
                mode: mode,
                mute: mute,
                custom_name: customName
            };
            await fetch('/add_to_queue', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(data)});
            showToast("✅ Adicionado à fila!");
        }

        // --- SISTEMA DE THUMBS EM TEMPO REAL ---
        async function genThumbs() {
            const p = document.getElementById('pasta').value;
            const v = document.getElementById('videoSelect').value;
            if(!v) return alert("Selecione um vídeo");
            
            const btn = document.getElementById('btnGenThumbs');
            btn.innerText = "⏳ Gerando...";
            btn.classList.add('loading-pulse');
            
            // Limpa grid atual
            document.getElementById('thumbs').innerHTML = '';
            loadedFramesSet.clear();

            // Manda iniciar geração
            await fetch(`/thumbs?pasta=${encodeURIComponent(p)}&file=${encodeURIComponent(v)}&step=60`);
            
            // Inicia loop de verificação
            startThumbsPolling(p, v);
        }

        function startThumbsPolling(p, v) {
            if(thumbsPollInterval) clearInterval(thumbsPollInterval);
            
            thumbsPollInterval = setInterval(async () => {
                const res = await fetch(`/thumbs_list?pasta=${encodeURIComponent(p)}&file=${encodeURIComponent(v)}&step=60`);
                if(!res.ok) return;
                
                const frames = await res.json();
                const grid = document.getElementById('thumbs');
                
                let addedAny = false;
                frames.forEach(f => {
                    if(!loadedFramesSet.has(f.sec)) {
                        loadedFramesSet.add(f.sec);
                        addedAny = true;
                        
                        // Cria elemento e adiciona
                        const div = document.createElement('div');
                        div.className = 'thumb';
                        div.onclick = () => addQueue(f.sec);
                        div.innerHTML = `
                            <img src="${f.src}">
                            <div class="time-badge">${Math.floor(f.sec/60)}m:${f.sec%60}s</div>
                            <div class="quick-cut">⚡ TOQUE P/ CORTAR</div>
                        `;
                        grid.appendChild(div);
                    }
                });
                
                // Se temos muitos frames, podemos parar o loading visual (mas continua checando)
                if(frames.length > 5) {
                    const btn = document.getElementById('btnGenThumbs');
                    btn.innerText = "🖼️ CARREGAR MAIS FRAMES";
                    btn.classList.remove('loading-pulse');
                }

            }, 1000); // Checa a cada 1 segundo
        }

        function stopThumbsPolling() {
            if(thumbsPollInterval) clearInterval(thumbsPollInterval);
            const btn = document.getElementById('btnGenThumbs');
            btn.innerText = "🖼️ CARREGAR FRAMES";
            btn.classList.remove('loading-pulse');
        }

        async function clearHistory() {
            if(!confirm("Limpar histórico?")) return;
            await fetch('/clear_queue', {method: 'POST'});
        }

        function toggleShowAll() {
            showAllQueue = !showAllQueue;
            document.getElementById('btnShowMore').innerText = showAllQueue ? "🔼 MOSTRAR MENOS" : "🔽 VER HISTÓRICO COMPLETO";
        }

        setInterval(async () => {
            const res = await fetch('/queue_status');
            if(!res.ok) return;
            const data = await res.json();
            
            let all = [...data.processing, ...data.queued, ...data.completed, ...data.failed];
            
            const btnMore = document.getElementById('btnShowMore');
            if (all.length > 5) {
                btnMore.style.display = 'block';
            } else {
                btnMore.style.display = 'none';
            }

            const displayList = showAllQueue ? all : all.slice(0, 5);

            document.getElementById('queueList').innerHTML = displayList.map(j => `
                <div class="queue-item">
                    <div style="display:flex; justify-content:space-between; margin-bottom:5px">
                        <b>${j.custom_name ? j.custom_name : (j.file.length > 15 ? j.file.substring(0,12)+'...' : j.file)}</b>
                        <span style="opacity:0.6">${j.created_at || ''}</span>
                    </div>
                    ${j.status === 'processing' ? 
                        `<div class="progress-bg"><div class="progress-fg" style="width:${j.progress}%"></div></div>` : 
                        `<div style="opacity:0.7; font-size:11px">Status: ${j.status.toUpperCase()}</div>`
                    }
                    ${j.status === 'completed' ? `<a href="/download_job/${j.id}" style="float:right; font-size:20px; text-decoration:none">💾</a>` : ''}
                    ${j.status === 'failed' ? `<div style="color:var(--danger); font-size:10px">${j.error ? j.error.substring(0,30) : 'Erro'}</div>` : ''}
                </div>
            `).join('');
        }, 1500);

        window.onload = () => {
            document.getElementById('pasta').value = localStorage.getItem('last_pasta') || document.getElementById('pasta').value;
            document.getElementById('pastaCorte').value = localStorage.getItem('last_dest') || "";
            loadVideos(false);
        }
    </script>
</body>
</html>
"""

# ================= ROTAS FLASK =================

@app.route("/")
def index():
    pastas = sorted([d for d in os.listdir(VIDEO_DIR) if os.path.isdir(os.path.join(VIDEO_DIR, d))])
    pastas_corte = sorted([d for d in os.listdir(CUT_DIR) if os.path.isdir(os.path.join(CUT_DIR, d))])
    return render_template_string(TEMPLATE, pastas=pastas, pastas_corte=pastas_corte)

@app.route("/videos_list")
def videos_list():
    pasta = request.args.get("pasta", "")
    dir_path = os.path.join(VIDEO_DIR, pasta)
    videos = [f for f in os.listdir(dir_path) if f.lower().endswith((".mp4",".mkv",".avi"))] if os.path.exists(dir_path) else []
    return jsonify(sorted(videos))

@app.route("/video")
def video_stream():
    return send_file(os.path.join(VIDEO_DIR, request.args.get("pasta"), request.args.get("file")))

@app.route("/take_snapshot", methods=["POST"])
def take_snapshot():
    data = request.json
    src = os.path.join(VIDEO_DIR, data['pasta'], data['file'])
    name = f"snap_{int(time.time())}.jpg"
    out = os.path.join(SNAPSHOT_DIR, name)
    
    subprocess.run([
        "ffmpeg", "-ss", str(data['time']), "-i", src,
        "-frames:v", "1", "-q:v", "2", "-y", out
    ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    
    return jsonify({'success': True, 'name': name})

@app.route("/thumbs")
def generate_thumbs():
    file, pasta = request.args["file"], request.args.get("pasta", "")
    src = os.path.join(VIDEO_DIR, pasta, file)
    vid_id = uuid.uuid5(uuid.NAMESPACE_URL, os.path.join(pasta, file)).hex
    out_dir = os.path.join(TMP_THUMBS, vid_id)
    os.makedirs(out_dir, exist_ok=True)
    def worker():
        try:
            dur = float(subprocess.check_output(["ffprobe","-v","0","-show_entries","format=duration","-of","default=nw=1:nk=1",src]))
            for t in range(0, int(dur), 60):
                out = os.path.join(out_dir, f"f_{t:05d}.jpg")
                if not os.path.exists(out):
                    subprocess.run(["ffmpeg", "-ss", str(t), "-i", src, "-frames:v", "1", "-q:v", "8", "-vf", "scale=320:-1", out, "-y", "-loglevel", "quiet"])
        except: pass
    threading.Thread(target=worker, daemon=True).start()
    return "ok"

@app.route("/thumbs_list")
def thumbs_list():
    vid_id = uuid.uuid5(uuid.NAMESPACE_URL, os.path.join(request.args.get("pasta"), request.args.get("file"))).hex
    out_dir = os.path.join(TMP_THUMBS, vid_id)
    if not os.path.exists(out_dir): return jsonify([])
    files = sorted(os.listdir(out_dir))
    return jsonify([{"sec": int(f[2:7]), "src": f"/thumb/{vid_id}/{f}"} for f in files])

@app.route("/thumb/<vid>/<n>")
def serve_thumb(vid, n): return send_file(os.path.join(TMP_THUMBS, vid, n))

@app.route("/add_to_queue", methods=["POST"])
def add_to_queue():
    job_id = uuid.uuid4().hex
    conversion_queue.add_job(job_id, request.json)
    return jsonify({'id': job_id})

@app.route("/queue_status")
def queue_status():
    return jsonify(conversion_queue.get_all_status())

@app.route("/clear_queue", methods=["POST"])
def clear_queue():
    conversion_queue.clear_history()
    return jsonify({'success': True})

@app.route("/download_job/<job_id>")
def download_job(job_id):
    job = conversion_queue.completed_jobs.get(job_id)
    return send_file(job['output_path'], as_attachment=True) if job else ("Erro", 404)

if __name__ == "__main__":
    app.run("0.0.0.0", 5000)

