File size: 3,662 Bytes
91a0518
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
Standalone batch stem separation — loop over a folder of songs and write stems
for each. Runs entirely locally and free (Demucs on your CPU/GPU).

Examples
--------
  # every song in your Music folder -> a "Stems" folder, all stems:
  python batch_separate.py "C:\\Users\\chenm\\Music" --recursive

  # only vocals + drums, 6-stem model, each song in its own subfolder:
  python batch_separate.py "D:\\songs" -m htdemucs_6s -s vocals drums --per-song-folder

Re-running is safe: songs whose stems already exist are skipped, so you can stop
and resume. Output files are named "<song> - <stem>.wav".
"""
import os
import sys
import argparse
import traceback

import separate

AUDIO_EXT = {".mp3", ".wav", ".flac", ".m4a", ".aac", ".ogg", ".wma", ".mp4"}


def find_audio(root, recursive, skip_dir):
    skip_dir = os.path.abspath(skip_dir)
    if recursive:
        for d, _, files in os.walk(root):
            if os.path.abspath(d).startswith(skip_dir):   # never recurse into the output tree
                continue
            for f in files:
                if os.path.splitext(f)[1].lower() in AUDIO_EXT:
                    yield os.path.join(d, f)
    else:
        for f in sorted(os.listdir(root)):
            p = os.path.join(root, f)
            if os.path.isfile(p) and os.path.splitext(f)[1].lower() in AUDIO_EXT:
                yield p


def already_done(out_dir, song, stems):
    return all(os.path.exists(os.path.join(out_dir, f"{song} - {s}.wav")) for s in stems)


def main():
    ap = argparse.ArgumentParser(description="Batch-separate a folder of songs into stems.")
    ap.add_argument("input", help="Folder containing songs")
    ap.add_argument("-o", "--out", default=None,
                    help="Output folder (default: <input>\\Stems)")
    ap.add_argument("-m", "--model", default=separate.DEFAULT_MODEL,
                    choices=separate.available_models())
    ap.add_argument("-s", "--stems", nargs="*", default=None,
                    help="Subset of stems to write (default: all for the model)")
    ap.add_argument("--recursive", action="store_true", help="Recurse into subfolders")
    ap.add_argument("--per-song-folder", action="store_true",
                    help="Put each song's stems in its own subfolder")
    ap.add_argument("--device", default=None, help="cpu / cuda (auto if omitted)")
    a = ap.parse_args()

    if not os.path.isdir(a.input):
        sys.exit(f"Not a folder: {a.input}")
    out_root = a.out or os.path.join(a.input, "Stems")
    stems = a.stems or separate.list_stems(a.model)

    files = list(find_audio(a.input, a.recursive, out_root))
    print(f"Found {len(files)} audio file(s). model={a.model} stems={stems}")
    print(f"Output -> {out_root}\n")

    ok = skip = err = 0
    for i, p in enumerate(files, 1):
        song = separate._safe(os.path.splitext(os.path.basename(p))[0])
        out_dir = os.path.join(out_root, song) if a.per_song_folder else out_root
        try:
            if already_done(out_dir, song, stems):
                skip += 1
                print(f"[{i}/{len(files)}] skip (already done): {song}")
                continue
            print(f"[{i}/{len(files)}] separating: {song}", flush=True)
            separate.separate(p, out_dir, a.model, stems, device=a.device)
            ok += 1
        except KeyboardInterrupt:
            print("\nInterrupted.")
            break
        except Exception as e:
            err += 1
            print(f"[{i}/{len(files)}] ERROR on {song}: {e}")
            traceback.print_exc()

    print(f"\nDONE  ok={ok}  skipped={skip}  errors={err}  ->  {out_root}")


if __name__ == "__main__":
    main()