uncompose-engine 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ """uncompose-engine: the Python side of the Engine Contract (ADR-0001).
2
+
3
+ Spawned by the core, never imported by it. Request JSON on stdin, JSONL
4
+ events on stdout, all human logging on stderr.
5
+ """
@@ -0,0 +1,117 @@
1
+ """Engine Contract entry point: `python -m uncompose_engine`.
2
+
3
+ Reads one JSON request from stdin, runs the model via audio-separator, and
4
+ emits JSONL events on stdout. Everything else (library logging, tqdm bars,
5
+ stray prints) is forced onto stderr so the event stream stays parseable.
6
+ """
7
+
8
+ import importlib.metadata
9
+ import json
10
+ import logging
11
+ import os
12
+ import sys
13
+ import time
14
+
15
+ import torch
16
+ from audio_separator.separator import Separator
17
+
18
+ # Models known to this engine: audio-separator filename plus the mapping
19
+ # from audio-separator's stem names to Uncompose's preset-level stem names
20
+ # (keys.wav, not piano.wav — the model is piano-trained, the name is stable).
21
+ MODELS = {
22
+ "mel_band_roformer_kim": {
23
+ "filename": "vocals_mel_band_roformer.ckpt",
24
+ # audio-separator calls this model's non-vocal stem "Other".
25
+ "output_names": {
26
+ "Vocals": "vocals",
27
+ "Other": "instrumental",
28
+ },
29
+ },
30
+ "htdemucs_6s": {
31
+ "filename": "htdemucs_6s.yaml",
32
+ "output_names": {
33
+ "Vocals": "vocals",
34
+ "Drums": "drums",
35
+ "Bass": "bass",
36
+ "Guitar": "guitar",
37
+ "Piano": "keys",
38
+ "Other": "other",
39
+ },
40
+ },
41
+ }
42
+
43
+
44
+ def main() -> int:
45
+ # Keep a private handle to the real stdout for events, then point fd 1
46
+ # at stderr so nothing the ML stack prints can corrupt the JSONL stream.
47
+ events = os.fdopen(os.dup(1), "w", buffering=1)
48
+ os.dup2(2, 1)
49
+ sys.stdout = sys.stderr
50
+
51
+ def emit(event: dict) -> None:
52
+ events.write(json.dumps(event) + "\n")
53
+
54
+ try:
55
+ request = json.load(sys.stdin)
56
+ return run(request, emit)
57
+ except Exception as exc: # terminal: report on the contract, exit nonzero
58
+ logging.exception("engine failed")
59
+ emit({"event": "error", "message": str(exc)})
60
+ return 1
61
+
62
+
63
+ def run(request: dict, emit) -> int:
64
+ model = MODELS.get(request["model_id"])
65
+ if model is None:
66
+ raise ValueError(f"unknown model id: {request['model_id']}")
67
+
68
+ logging.basicConfig(stream=sys.stderr, level=logging.INFO)
69
+
70
+ emit({"event": "stage", "stage": "model_load", "message": request["model_id"]})
71
+ t0 = time.monotonic()
72
+ separator = Separator(
73
+ output_dir=request["output_dir"],
74
+ model_file_dir=request["model_dir"],
75
+ output_format="wav",
76
+ )
77
+ separator.load_model(model_filename=model["filename"])
78
+ model_load_secs = time.monotonic() - t0
79
+
80
+ emit({"event": "stage", "stage": "separate"})
81
+ t1 = time.monotonic()
82
+ separator.separate(request["audio_path"], custom_output_names=model["output_names"])
83
+ separate_secs = time.monotonic() - t1
84
+
85
+ # audio-separator can swallow decode failures and return with nothing
86
+ # written; missing stems are a failed job, not a quiet success. Each stem
87
+ # is staged as `<stem>.wav.partial`; the core promotes it to its final
88
+ # name when it sees the stem event, so a crash leaves partials behind.
89
+ missing = []
90
+ for stem_name in model["output_names"].values():
91
+ final = os.path.join(request["output_dir"], f"{stem_name}.wav")
92
+ if os.path.exists(final):
93
+ partial = final + ".partial"
94
+ os.replace(final, partial)
95
+ emit({"event": "stem", "name": stem_name, "path": partial})
96
+ else:
97
+ missing.append(stem_name)
98
+ if missing:
99
+ raise RuntimeError(f"separation produced no output for: {', '.join(missing)}")
100
+
101
+ emit(
102
+ {
103
+ "event": "done",
104
+ "model_id": request["model_id"],
105
+ "engine_version": importlib.metadata.version("uncompose-engine"),
106
+ "device": "cuda" if torch.cuda.is_available() else "cpu",
107
+ "timings": {
108
+ "model_load_secs": round(model_load_secs, 2),
109
+ "separate_secs": round(separate_secs, 2),
110
+ },
111
+ }
112
+ )
113
+ return 0
114
+
115
+
116
+ if __name__ == "__main__":
117
+ sys.exit(main())
@@ -0,0 +1,7 @@
1
+ Metadata-Version: 2.4
2
+ Name: uncompose-engine
3
+ Version: 0.1.0
4
+ Summary: Separation engine shim: JSONL Engine Contract on one side, audio-separator on the other
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: audio-separator[gpu]>=0.44.5
@@ -0,0 +1,5 @@
1
+ uncompose_engine/__init__.py,sha256=gVCgmAo13iXQwFpSBQG0jwCHVsBHHaKqnuJ5I7XZ4So,196
2
+ uncompose_engine/__main__.py,sha256=FSOyHK7i5OLz9C5aAieQ58ZmKYNMGzkzeeATit2MLJU,3940
3
+ uncompose_engine-0.1.0.dist-info/METADATA,sha256=TfA0DW6iFpBgAC2R72A8Poe48bdalfpaM1FCZD6jPLQ,249
4
+ uncompose_engine-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
5
+ uncompose_engine-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any