aboutsummarylogtreecommitdiffstats
path: root/aac2alac.py
blob: 7d6ec1227d5e0f162ef00684e0fe36aea613a1e1 (plain)
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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
CLI converter: AAC → ALAC inside .mov
Keeps all tracks, rewrites audio only. Supports --progress.
"""

import argparse, subprocess, sys, os, tempfile, shutil

EXIT_OK = 0
EXIT_NOT_AAC = 3
EXIT_FFMPEG_FAIL = 4

def probe_duration_seconds(path):
    try:
        out = subprocess.check_output(
            ["ffprobe", "-v", "error", "-show_entries", "format=duration",
             "-of", "default=nk=1:nw=1", path],
            text=True
        ).strip()
        return float(out)
    except Exception:
        return 0.0

def parse_args():
    p = argparse.ArgumentParser()
    p.add_argument("input", help="input video with AAC audio")
    p.add_argument("output", help="output .mov")
    p.add_argument("--progress", action="store_true",
                   help="emit PROGRESS <pct> lines")
    return p.parse_args()

def main():
    args = parse_args()

    info = subprocess.run(
        ["ffprobe", "-v", "error", "-select_streams", "a",
         "-show_entries", "stream=codec_name",
         "-of", "default=nokey=1:nw=1", args.input],
        capture_output=True, text=True
    )
    codecs = info.stdout.strip().splitlines()
    if not codecs or any(c.strip() != "aac" for c in codecs):
        print("NOT_AAC", file=sys.stderr)
        return EXIT_NOT_AAC

    real_out = args.output
    tmp_fd, tmp_path = tempfile.mkstemp(suffix=".mov")
    os.close(tmp_fd)

    cmd = ["ffmpeg", "-y", "-i", args.input,
           "-map", "0:v?", "-map", "0:a?", "-map", "0:s?", "-map", "0:d?", "-map", "0:t?",
           "-c:v", "copy", "-c:a", "alac", "-movflags", "+faststart"]

    if args.progress:
        cmd += ["-progress", "pipe:1", "-nostats"]

    cmd += [tmp_path]

    if args.progress:
        dur = probe_duration_seconds(args.input)
        proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
                                text=True, bufsize=1)
        for line in proc.stdout:
            line = line.strip()
            if line.startswith("out_time_ms=") and dur > 0:
                ms = float(line.split("=",1)[1])
                pct = min(100.0, (ms/1_000_000.0)/dur*100.0)
                print(f"PROGRESS {pct:.1f}", flush=True)
        rc = proc.wait()
    else:
        proc = subprocess.run(cmd)
        rc = proc.returncode

    if rc != 0:
        return EXIT_FFMPEG_FAIL

    shutil.move(tmp_path, real_out)
    print(f"OK: {real_out}")
    if args.progress:
        print(f"DONE {real_out}", flush=True)
    return EXIT_OK

if __name__ == "__main__":
    sys.exit(main())