#!/usr/bin/env python3
"""Reencode do Gravity Falls para H.265.

Dois workers em paralelo com 6 threads cada: um so' trabalho satura por volta
dos 9 nucleos e deixa 3 parados, dois trabalhos aproveitam os 12.

Valida cada episodio antes de seguir para o proximo. Se algum falhar, para tudo
— mais vale parar e perguntar do que encher a pasta de ficheiros truncados.
NAO apaga nada.
"""
import os, subprocess, threading, queue, time, sys

SRC = "/share/storage/series/Gravity Falls"
DST = "/share/storage/series/Gravity Falls-optimization"
LOG = "/share/storage/reencode-teste/batch.log"
WORKERS, POOLS = 2, 6

os.makedirs(DST, exist_ok=True)
lock = threading.Lock()

def log(msg):
    line = f"[{time.strftime('%H:%M:%S')}] {msg}"
    with lock:
        print(line, flush=True)
        with open(LOG, "a") as f:
            f.write(line + "\n")

def duration(path):
    """Duracao pelo metadata do container. Devolve None se ausente — o que e'
    exactamente o que acontece num ficheiro truncado."""
    r = subprocess.run(["ffprobe","-v","error","-show_entries","format=duration",
                        "-of","csv=p=0", path], capture_output=True, text=True)
    try:
        return float(r.stdout.strip())
    except ValueError:
        return None

def validate(src, dst):
    if not os.path.exists(dst):
        return "ficheiro de saida nao existe"
    ds, dd = os.path.getsize(src), os.path.getsize(dst)
    if dd == 0:
        return "saida vazia"
    ratio = dd / ds
    if ratio > 0.60:
        return f"saida grande demais ({ratio*100:.0f}% do original) — encode nao compensou"
    if ratio < 0.02:
        return f"saida pequena demais ({ratio*100:.1f}% do original) — provavelmente truncada"
    tsrc, tdst = duration(src), duration(dst)
    if tdst is None:
        return "sem duracao no container — ficheiro truncado"
    if tsrc and abs(tsrc - tdst) > 2.0:
        return f"duracao diferente: {tsrc:.1f}s vs {tdst:.1f}s"
    return None

files = sorted(f for f in os.listdir(SRC) if f.endswith(".mkv"))
q = queue.Queue()
for f in files:
    q.put(f)

log(f"=== inicio: {len(files)} episodios, {WORKERS} workers x {POOLS} threads ===")
stop = threading.Event()
done, failed = [], []

def worker(wid):
    while not stop.is_set():
        try:
            name = q.get_nowait()
        except queue.Empty:
            return
        src, dst = os.path.join(SRC, name), os.path.join(DST, name)

        if os.path.exists(dst) and validate(src, dst) is None:
            log(f"[w{wid}] {name}: ja existe e valida, salto")
            done.append(name); q.task_done(); continue

        t0 = time.time()
        r = subprocess.run([
            "nice","-n","19","ffmpeg","-hide_banner","-nostdin","-v","error","-y",
            "-i", src, "-map","0","-c","copy",
            "-c:v","libx265","-crf","20","-preset","medium","-tune","animation",
            "-x265-params", f"pools={POOLS}",
            dst], capture_output=True, text=True)

        if r.returncode != 0:
            log(f"[w{wid}] {name}: ERRO ffmpeg rc={r.returncode} {r.stderr[:200]}")
            failed.append(name); stop.set(); q.task_done(); return

        err = validate(src, dst)
        if err:
            log(f"[w{wid}] {name}: VALIDACAO FALHOU — {err}")
            failed.append(name); stop.set(); q.task_done(); return

        ds, dd = os.path.getsize(src), os.path.getsize(dst)
        log(f"[w{wid}] {name}: ok  {ds/2**30:.2f} GB -> {dd/2**30:.2f} GB "
            f"(-{(1-dd/ds)*100:.0f}%)  {(time.time()-t0)/60:.1f} min")
        done.append(name)
        q.task_done()

ths = [threading.Thread(target=worker, args=(i+1,), daemon=True) for i in range(WORKERS)]
for t in ths: t.start()
for t in ths: t.join()

log(f"=== fim: {len(done)} ok, {len(failed)} falhados ===")
if failed:
    log(f"FALHARAM: {failed}")
    sys.exit(1)

for season in ("S00","S01","S02"):
    eps = [f for f in files if season in f]
    if eps and all(e in done for e in eps):
        tot = sum(os.path.getsize(os.path.join(SRC, e)) for e in eps)
        log(f"{season}: {len(eps)} episodios concluidos — originais somam {tot/2**30:.1f} GB "
            f"(NAO apagados, ver nota)")
