#!/usr/bin/env python3
"""
cromakga3d - Audio Compress (BGM + SFX)
Converts crassets_raw/audio/<kind>/**/* -> crassets/audio/<kind>/**/*.ogg via ffmpeg.
48000 Hz is the engine's canonical rate (crAudio::SAMPLE_RATE).
SFX and BGM are both stereo -- crAudio rejects non-stereo SFX, so -ac 2 is not optional.
"""

import os
import sys
import subprocess

# ── Config ───────────────────────────────────────────────────────
INPUT_ROOT = r"C:\workspace\cromakga3d\crassets_raw\audio"
OUT_ROOT   = r"C:\workspace\cromakga3d\crassets\audio"

# (label, subdir, ffmpeg options)
JOBS = [
    # -bitexact: the ogg muxer picks a RANDOM stream serial per run, so without it every re-encode
    # rewrites the page headers and svn sees the whole audio set as changed
    ("BGM", "bgm", "-ar 48000 -ac 2 -c:a libvorbis -q:a 4 -bitexact"),
    ("SFX", "sfx", "-ar 48000 -ac 2 -c:a libvorbis -q:a 5 -bitexact"),
]

# extra ffmpeg options appended to the job's, for one file only -- keyed by "<subdir>/<path without ext>"
# celestial_echoes: the source peaks at -22.8 dB, ~24 dB under the sfx set, so it plays as silence under them
PER_FILE_OPTS = {
    "bgm/celestial_echoes": "-af volume=20dB",
}

AUDIO_EXTS  = {".wav", ".mp3", ".flac", ".aiff", ".m4a", ".aac", ".wma", ".opus", ".ogg"}

# ── ANSI colors ───────────────────────────────────────────────────
os.system("")   # enable VT100 on Windows

CYAN   = "\033[96m"
GREEN  = "\033[92m"
RED    = "\033[91m"
YELLOW = "\033[93m"
GRAY   = "\033[90m"
WHITE  = "\033[97m"
RESET  = "\033[0m"

def col(color, text):
    return color + text + RESET

def fmt_size(n):
    if n >= 1024 * 1024:
        s = "{:.1f} MB".format(n / (1024 * 1024))
    elif n >= 1024:
        s = "{:.1f} KB".format(n / 1024)
    else:
        s = str(n) + " B"
    return s.rjust(9)

# ── Print helpers ─────────────────────────────────────────────────
def print_header():
    os.system("cls")
    print()
    print(col(CYAN, "  +==========================================+"))
    print(col(CYAN, "  |    cromakga3d  .  Audio Compress         |"))
    print(col(CYAN, "  +==========================================+"))
    print()
    print(col(GRAY, "  Input   :  " + INPUT_ROOT))
    print(col(GRAY, "  Output  :  " + OUT_ROOT))
    for label, subdir, opts in JOBS:
        print(col(GRAY, "  " + label.ljust(8) + ":  " + opts))
    print()

def print_section(title):
    print()
    print(col(CYAN, "  +------------------------------------------+"))
    print(col(CYAN, "  |  {:<40}|".format(title)))
    print(col(CYAN, "  +------------------------------------------+"))

def print_step(text):
    print(col(GRAY, "  \u00b7  " + text))

def print_ok(text):
    print(col(GREEN, "  v  " + text))

def print_skip(text):
    print(col(YELLOW, "  -  " + text))

def print_err(text):
    print(col(RED, "  x  " + text))

# ── File collection ───────────────────────────────────────────────
def collect_files(src_dir):
    result = []
    for dirpath, _dirnames, filenames in os.walk(src_dir):
        for filename in filenames:
            ext = os.path.splitext(filename)[1].lower()
            if ext in AUDIO_EXTS:
                result.append(os.path.join(dirpath, filename))
    result.sort()
    return result

# ── Compress ──────────────────────────────────────────────────────
def compress(src_path, dst_path, ffmpeg_opts):
    dst_dir = os.path.dirname(dst_path)
    if not os.path.exists(dst_dir):
        os.makedirs(dst_dir)

    cmd = ["ffmpeg", "-y", "-i", src_path] + ffmpeg_opts.split() + [dst_path]
    result = subprocess.run(cmd, capture_output=True, text=True)
    return result.returncode == 0, result.stderr

# ── Main ──────────────────────────────────────────────────────────
def main():
    print_header()

    if not os.path.isdir(INPUT_ROOT):
        print_err("INPUT_ROOT not found: " + INPUT_ROOT)
        print()
        sys.exit(1)

    count_ok   = 0
    count_skip = 0
    count_fail = 0

    for job_label, subdir, ffmpeg_opts in JOBS:
        input_dir  = os.path.join(INPUT_ROOT, subdir)
        output_dir = os.path.join(OUT_ROOT, subdir)

        files = collect_files(input_dir) if os.path.isdir(input_dir) else []

        print_section("{}  ({} file(s))".format(job_label, len(files)))
        print()

        if not files:
            print_skip("no audio files in: " + input_dir)
            continue

        for src_path in files:
            rel_path  = os.path.relpath(src_path, input_dir)
            ext       = os.path.splitext(rel_path)[1].lower()
            rel_ogg   = os.path.splitext(rel_path)[0] + ".ogg"
            dst_path  = os.path.join(output_dir, rel_ogg)
            file_key  = (subdir + "/" + os.path.splitext(rel_path)[0]).replace("\\", "/")
            file_opts = PER_FILE_OPTS.get(file_key, "")

            if ext == ".ogg":
                print_skip(rel_path + "  (already .ogg, skipped)")
                count_skip += 1
                continue

            src_size = os.path.getsize(src_path)
            print_step(rel_path + "  \u2192  " + rel_ogg)
            ok, stderr = compress(src_path, dst_path, ffmpeg_opts + " " + file_opts)

            if ok:
                dst_size = os.path.getsize(dst_path)
                ratio = (1.0 - dst_size / src_size) * 100.0 if src_size > 0 else 0.0
                ratio_color = GREEN if ratio >= 60.0 else YELLOW if ratio >= 30.0 else GRAY
                print(
                    col(GREEN, "  v  done") +
                    "   " + col(GRAY,  fmt_size(src_size)) +
                    "  \u2192  " + col(WHITE, fmt_size(dst_size)) +
                    "   " + col(ratio_color, "\u2193{:.1f}%".format(ratio))
                )
                count_ok += 1
            else:
                last_line = stderr.strip().splitlines()[-1] if stderr.strip() else "(no output)"
                print_err("FAILED  -- " + last_line)
                count_fail += 1

    print()
    print(col(GRAY, "  ------------------------------------------"))
    print(col(GREEN,  "  v  OK    : " + str(count_ok)))
    if count_skip > 0:
        print(col(YELLOW, "  -  Skip  : " + str(count_skip)))
    if count_fail > 0:
        print(col(RED,    "  x  Fail  : " + str(count_fail)))
    print(col(GRAY, "  ------------------------------------------"))
    print()

    if count_fail > 0:
        sys.exit(1)


if __name__ == "__main__":
    main()
