audioreconstructor 1.0.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.
- audioreconstructor/__init__.py +3 -0
- audioreconstructor/__main__.py +5 -0
- audioreconstructor/cli.py +442 -0
- audioreconstructor-1.0.0.dist-info/METADATA +78 -0
- audioreconstructor-1.0.0.dist-info/RECORD +7 -0
- audioreconstructor-1.0.0.dist-info/WHEEL +4 -0
- audioreconstructor-1.0.0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
"""Install and invoke versioned Audioreconstructor GitHub Release assets."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import platform
|
|
9
|
+
import re
|
|
10
|
+
import shutil
|
|
11
|
+
import stat
|
|
12
|
+
import subprocess
|
|
13
|
+
import sys
|
|
14
|
+
import tempfile
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from importlib.metadata import PackageNotFoundError, version as installed_version
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Callable, Mapping, Sequence
|
|
19
|
+
from urllib.error import HTTPError, URLError
|
|
20
|
+
from urllib.parse import quote
|
|
21
|
+
from urllib.request import Request, urlopen
|
|
22
|
+
|
|
23
|
+
from . import __version__
|
|
24
|
+
|
|
25
|
+
PACKAGE_NAME = "audioreconstructor"
|
|
26
|
+
REPOSITORY = "rohan-prasen/audioreconstruction"
|
|
27
|
+
MANIFEST_NAME = "manifest.json"
|
|
28
|
+
MODEL_NAME = "model.onnx"
|
|
29
|
+
CONFIG_NAME = "config.json"
|
|
30
|
+
DOWNLOAD_TIMEOUT_SECONDS = 30
|
|
31
|
+
SELF_TEST_TIMEOUT_SECONDS = 600
|
|
32
|
+
CHUNK_SIZE = 1024 * 1024
|
|
33
|
+
SHA256_RE = re.compile(r"^[0-9a-f]{64}$")
|
|
34
|
+
VERSION_DIR_RE = re.compile(r"^\d+(?:\.\d+)+(?:[a-zA-Z0-9._-]+)?$")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class CliError(Exception):
|
|
38
|
+
"""An expected CLI failure that should be rendered without a traceback."""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass(frozen=True)
|
|
42
|
+
class Target:
|
|
43
|
+
system: str
|
|
44
|
+
architecture: str
|
|
45
|
+
asset_name: str
|
|
46
|
+
executable_name: str
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(frozen=True)
|
|
50
|
+
class Artifact:
|
|
51
|
+
name: str
|
|
52
|
+
sha256: str
|
|
53
|
+
size: int
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_package_version() -> str:
|
|
57
|
+
try:
|
|
58
|
+
return installed_version(PACKAGE_NAME)
|
|
59
|
+
except PackageNotFoundError:
|
|
60
|
+
return __version__
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def release_tag(version: str) -> str:
|
|
64
|
+
return f"audioreconstructor-v{version}"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def release_url(version: str, asset_name: str) -> str:
|
|
68
|
+
tag = quote(release_tag(version), safe="")
|
|
69
|
+
asset = quote(asset_name, safe="")
|
|
70
|
+
return f"https://github.com/{REPOSITORY}/releases/download/{tag}/{asset}"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def detect_target(system: str | None = None, machine: str | None = None) -> Target:
|
|
74
|
+
system = system or platform.system()
|
|
75
|
+
machine = (machine or platform.machine()).lower()
|
|
76
|
+
if machine not in {"x86_64", "amd64"}:
|
|
77
|
+
raise CliError(f"unsupported architecture: {machine}. Only x86-64 is supported.")
|
|
78
|
+
if system == "Linux":
|
|
79
|
+
return Target(system, "x86_64", "audioreconstructor-linux-x86_64", "audioreconstructor")
|
|
80
|
+
if system == "Windows":
|
|
81
|
+
return Target(system, "x86_64", "audioreconstructor-windows-x86_64.exe", "audioreconstructor.exe")
|
|
82
|
+
raise CliError(f"unsupported operating system: {system}. Only Linux and Windows are supported.")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def get_cache_root(
|
|
86
|
+
system: str,
|
|
87
|
+
environ: Mapping[str, str] | None = None,
|
|
88
|
+
home: Path | None = None,
|
|
89
|
+
) -> Path:
|
|
90
|
+
environ = os.environ if environ is None else environ
|
|
91
|
+
home = Path.home() if home is None else home
|
|
92
|
+
if system == "Linux":
|
|
93
|
+
return Path(environ["XDG_CACHE_HOME"]) / PACKAGE_NAME if environ.get("XDG_CACHE_HOME") else home / ".cache" / PACKAGE_NAME
|
|
94
|
+
if system == "Windows":
|
|
95
|
+
local_app_data = environ.get("LOCALAPPDATA")
|
|
96
|
+
base = Path(local_app_data) if local_app_data else home / "AppData" / "Local"
|
|
97
|
+
return base / PACKAGE_NAME / "Cache"
|
|
98
|
+
raise CliError(f"unsupported operating system: {system}")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def installation_paths(cache_root: Path, version: str, target: Target) -> dict[str, Path]:
|
|
102
|
+
directory = cache_root / version
|
|
103
|
+
return {
|
|
104
|
+
"directory": directory,
|
|
105
|
+
"manifest": directory / MANIFEST_NAME,
|
|
106
|
+
"binary": directory / target.executable_name,
|
|
107
|
+
"model": directory / MODEL_NAME,
|
|
108
|
+
"config": directory / CONFIG_NAME,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _hash_file(path: Path) -> tuple[str, int]:
|
|
113
|
+
digest = hashlib.sha256()
|
|
114
|
+
size = 0
|
|
115
|
+
with path.open("rb") as handle:
|
|
116
|
+
while data := handle.read(CHUNK_SIZE):
|
|
117
|
+
digest.update(data)
|
|
118
|
+
size += len(data)
|
|
119
|
+
return digest.hexdigest(), size
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def artifact_is_valid(path: Path, artifact: Artifact) -> tuple[bool, str]:
|
|
123
|
+
if not path.is_file():
|
|
124
|
+
return False, "missing"
|
|
125
|
+
try:
|
|
126
|
+
digest, size = _hash_file(path)
|
|
127
|
+
except OSError as exc:
|
|
128
|
+
return False, str(exc)
|
|
129
|
+
if size != artifact.size:
|
|
130
|
+
return False, f"size is {size} bytes; expected {artifact.size}"
|
|
131
|
+
if digest != artifact.sha256:
|
|
132
|
+
return False, "SHA-256 mismatch"
|
|
133
|
+
return True, "valid"
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _read_manifest(path: Path, package_version: str, required_assets: Sequence[str]) -> dict[str, Artifact]:
|
|
137
|
+
try:
|
|
138
|
+
raw = json.loads(path.read_text(encoding="utf-8"))
|
|
139
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
140
|
+
raise CliError(f"could not read {MANIFEST_NAME}: {exc}") from exc
|
|
141
|
+
|
|
142
|
+
if raw.get("schemaVersion") != 1:
|
|
143
|
+
raise CliError(f"{MANIFEST_NAME} has an unsupported schema version")
|
|
144
|
+
if raw.get("version") != package_version:
|
|
145
|
+
raise CliError(f"{MANIFEST_NAME} is for version {raw.get('version')!r}, expected {package_version!r}")
|
|
146
|
+
if raw.get("releaseTag") != release_tag(package_version):
|
|
147
|
+
raise CliError(f"{MANIFEST_NAME} does not match release {release_tag(package_version)}")
|
|
148
|
+
files = raw.get("files")
|
|
149
|
+
if not isinstance(files, dict):
|
|
150
|
+
raise CliError(f"{MANIFEST_NAME} has no files mapping")
|
|
151
|
+
|
|
152
|
+
artifacts: dict[str, Artifact] = {}
|
|
153
|
+
for name in required_assets:
|
|
154
|
+
item = files.get(name)
|
|
155
|
+
if not isinstance(item, dict):
|
|
156
|
+
raise CliError(f"{MANIFEST_NAME} is missing metadata for {name}")
|
|
157
|
+
digest = item.get("sha256")
|
|
158
|
+
size = item.get("bytes")
|
|
159
|
+
if not isinstance(digest, str) or not SHA256_RE.fullmatch(digest):
|
|
160
|
+
raise CliError(f"{MANIFEST_NAME} has an invalid SHA-256 for {name}")
|
|
161
|
+
if not isinstance(size, int) or size <= 0:
|
|
162
|
+
raise CliError(f"{MANIFEST_NAME} has an invalid byte count for {name}")
|
|
163
|
+
artifacts[name] = Artifact(name=name, sha256=digest, size=size)
|
|
164
|
+
return artifacts
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _temporary_path(directory: Path, filename: str) -> Path:
|
|
168
|
+
handle = tempfile.NamedTemporaryFile(prefix=f".{filename}.", suffix=".part", dir=directory, delete=False)
|
|
169
|
+
handle.close()
|
|
170
|
+
return Path(handle.name)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def download(url: str, destination: Path, expected: Artifact | None = None) -> None:
|
|
174
|
+
"""Download one release asset and validate it before returning."""
|
|
175
|
+
request = Request(url, headers={"User-Agent": f"{PACKAGE_NAME}/{get_package_version()}"})
|
|
176
|
+
try:
|
|
177
|
+
with urlopen(request, timeout=DOWNLOAD_TIMEOUT_SECONDS) as response, destination.open("wb") as handle:
|
|
178
|
+
digest = hashlib.sha256()
|
|
179
|
+
size = 0
|
|
180
|
+
while data := response.read(CHUNK_SIZE):
|
|
181
|
+
handle.write(data)
|
|
182
|
+
digest.update(data)
|
|
183
|
+
size += len(data)
|
|
184
|
+
except HTTPError as exc:
|
|
185
|
+
raise CliError(f"download failed ({exc.code}) for {url}") from exc
|
|
186
|
+
except URLError as exc:
|
|
187
|
+
raise CliError(f"download failed for {url}: {exc.reason}") from exc
|
|
188
|
+
except OSError as exc:
|
|
189
|
+
raise CliError(f"could not save {destination.name}: {exc}") from exc
|
|
190
|
+
|
|
191
|
+
if expected is not None:
|
|
192
|
+
if size != expected.size:
|
|
193
|
+
raise CliError(f"downloaded {expected.name} has {size} bytes; expected {expected.size}")
|
|
194
|
+
if digest.hexdigest() != expected.sha256:
|
|
195
|
+
raise CliError(f"downloaded {expected.name} failed SHA-256 verification")
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _make_executable(path: Path, target: Target) -> None:
|
|
199
|
+
if target.system != "Linux":
|
|
200
|
+
return
|
|
201
|
+
try:
|
|
202
|
+
path.chmod(path.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
203
|
+
except OSError as exc:
|
|
204
|
+
raise CliError(f"could not make {path.name} executable: {exc}") from exc
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _load_or_download_manifest(paths: Mapping[str, Path], version: str, required_assets: Sequence[str]) -> dict[str, Artifact]:
|
|
208
|
+
manifest = paths["manifest"]
|
|
209
|
+
try:
|
|
210
|
+
return _read_manifest(manifest, version, required_assets)
|
|
211
|
+
except CliError:
|
|
212
|
+
pass
|
|
213
|
+
|
|
214
|
+
temporary = _temporary_path(paths["directory"], MANIFEST_NAME)
|
|
215
|
+
try:
|
|
216
|
+
download(release_url(version, MANIFEST_NAME), temporary)
|
|
217
|
+
artifacts = _read_manifest(temporary, version, required_assets)
|
|
218
|
+
os.replace(temporary, manifest)
|
|
219
|
+
return artifacts
|
|
220
|
+
finally:
|
|
221
|
+
temporary.unlink(missing_ok=True)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _prune_old_versions(cache_root: Path, current_version: str, report: Callable[[str], None]) -> None:
|
|
225
|
+
try:
|
|
226
|
+
children = list(cache_root.iterdir())
|
|
227
|
+
except OSError:
|
|
228
|
+
return
|
|
229
|
+
for child in children:
|
|
230
|
+
if child.name == current_version or not VERSION_DIR_RE.fullmatch(child.name):
|
|
231
|
+
continue
|
|
232
|
+
try:
|
|
233
|
+
if child.is_symlink():
|
|
234
|
+
child.unlink()
|
|
235
|
+
elif child.is_dir():
|
|
236
|
+
shutil.rmtree(child)
|
|
237
|
+
except OSError as exc:
|
|
238
|
+
report(f"Warning: could not remove old cache {child}: {exc}")
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def setup_assets(
|
|
242
|
+
*,
|
|
243
|
+
package_version: str | None = None,
|
|
244
|
+
target: Target | None = None,
|
|
245
|
+
cache_root: Path | None = None,
|
|
246
|
+
report: Callable[[str], None] = print,
|
|
247
|
+
) -> dict[str, Path]:
|
|
248
|
+
"""Install the current package version's native assets into the user cache."""
|
|
249
|
+
package_version = package_version or get_package_version()
|
|
250
|
+
target = target or detect_target()
|
|
251
|
+
cache_root = cache_root or get_cache_root(target.system)
|
|
252
|
+
paths = installation_paths(cache_root, package_version, target)
|
|
253
|
+
paths["directory"].mkdir(parents=True, exist_ok=True)
|
|
254
|
+
|
|
255
|
+
required_assets = (target.asset_name, MODEL_NAME, CONFIG_NAME)
|
|
256
|
+
artifacts = _load_or_download_manifest(paths, package_version, required_assets)
|
|
257
|
+
path_by_asset = {
|
|
258
|
+
target.asset_name: paths["binary"],
|
|
259
|
+
MODEL_NAME: paths["model"],
|
|
260
|
+
CONFIG_NAME: paths["config"],
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
for asset_name in required_assets:
|
|
264
|
+
destination = path_by_asset[asset_name]
|
|
265
|
+
valid, _ = artifact_is_valid(destination, artifacts[asset_name])
|
|
266
|
+
if valid:
|
|
267
|
+
report(f"Using cached {asset_name}")
|
|
268
|
+
else:
|
|
269
|
+
report(f"Downloading {asset_name}")
|
|
270
|
+
temporary = _temporary_path(paths["directory"], asset_name)
|
|
271
|
+
try:
|
|
272
|
+
download(release_url(package_version, asset_name), temporary, artifacts[asset_name])
|
|
273
|
+
if asset_name == target.asset_name:
|
|
274
|
+
_make_executable(temporary, target)
|
|
275
|
+
os.replace(temporary, destination)
|
|
276
|
+
finally:
|
|
277
|
+
temporary.unlink(missing_ok=True)
|
|
278
|
+
if asset_name == target.asset_name:
|
|
279
|
+
_make_executable(destination, target)
|
|
280
|
+
|
|
281
|
+
_prune_old_versions(cache_root, package_version, report)
|
|
282
|
+
report("Setup complete.")
|
|
283
|
+
report(f"Cache: {paths['directory']}")
|
|
284
|
+
report(f"Binary: {paths['binary']}")
|
|
285
|
+
report(f"Model: {paths['model']}")
|
|
286
|
+
report(f"Config: {paths['config']}")
|
|
287
|
+
return paths
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _self_test(paths: Mapping[str, Path]) -> tuple[bool, str]:
|
|
291
|
+
command = [
|
|
292
|
+
str(paths["binary"]),
|
|
293
|
+
"--model",
|
|
294
|
+
str(paths["model"]),
|
|
295
|
+
"--config",
|
|
296
|
+
str(paths["config"]),
|
|
297
|
+
"--provider",
|
|
298
|
+
"auto",
|
|
299
|
+
"--self-test",
|
|
300
|
+
]
|
|
301
|
+
try:
|
|
302
|
+
result = subprocess.run(command, capture_output=True, text=True, timeout=SELF_TEST_TIMEOUT_SECONDS, check=False)
|
|
303
|
+
except OSError as exc:
|
|
304
|
+
return False, str(exc)
|
|
305
|
+
except subprocess.TimeoutExpired:
|
|
306
|
+
return False, f"timed out after {SELF_TEST_TIMEOUT_SECONDS} seconds"
|
|
307
|
+
if result.returncode == 0 and "SELF-TEST PASS" in result.stdout:
|
|
308
|
+
return True, "passed"
|
|
309
|
+
detail = (result.stderr or result.stdout).strip().splitlines()
|
|
310
|
+
return False, detail[-1] if detail else f"exited with code {result.returncode}"
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def doctor(
|
|
314
|
+
*,
|
|
315
|
+
package_version: str | None = None,
|
|
316
|
+
target: Target | None = None,
|
|
317
|
+
cache_root: Path | None = None,
|
|
318
|
+
report: Callable[[str], None] = print,
|
|
319
|
+
) -> int:
|
|
320
|
+
package_version = package_version or get_package_version()
|
|
321
|
+
try:
|
|
322
|
+
target = target or detect_target()
|
|
323
|
+
except CliError as exc:
|
|
324
|
+
report(f"[FAIL] Platform: {exc}")
|
|
325
|
+
report("Status: UNHEALTHY")
|
|
326
|
+
return 1
|
|
327
|
+
cache_root = cache_root or get_cache_root(target.system)
|
|
328
|
+
paths = installation_paths(cache_root, package_version, target)
|
|
329
|
+
required_assets = (target.asset_name, MODEL_NAME, CONFIG_NAME)
|
|
330
|
+
report(f"Version: {package_version}")
|
|
331
|
+
report(f"Platform: {target.system} {target.architecture}")
|
|
332
|
+
report(f"Cache: {paths['directory']}")
|
|
333
|
+
|
|
334
|
+
try:
|
|
335
|
+
artifacts = _read_manifest(paths["manifest"], package_version, required_assets)
|
|
336
|
+
except CliError as exc:
|
|
337
|
+
report(f"[FAIL] Manifest: {exc}")
|
|
338
|
+
report("Status: UNHEALTHY")
|
|
339
|
+
return 1
|
|
340
|
+
report("[PASS] Manifest")
|
|
341
|
+
|
|
342
|
+
path_by_asset = {
|
|
343
|
+
target.asset_name: paths["binary"],
|
|
344
|
+
MODEL_NAME: paths["model"],
|
|
345
|
+
CONFIG_NAME: paths["config"],
|
|
346
|
+
}
|
|
347
|
+
healthy = True
|
|
348
|
+
for asset_name in required_assets:
|
|
349
|
+
valid, detail = artifact_is_valid(path_by_asset[asset_name], artifacts[asset_name])
|
|
350
|
+
label = "PASS" if valid else "FAIL"
|
|
351
|
+
report(f"[{label}] {asset_name}: {detail}")
|
|
352
|
+
healthy = healthy and valid
|
|
353
|
+
|
|
354
|
+
if target.system == "Linux":
|
|
355
|
+
executable = paths["binary"].is_file() and os.access(paths["binary"], os.X_OK)
|
|
356
|
+
report(f"[{'PASS' if executable else 'FAIL'}] Binary execute permission")
|
|
357
|
+
healthy = healthy and executable
|
|
358
|
+
|
|
359
|
+
if healthy:
|
|
360
|
+
report("[PASS] Bundled runtime dependencies")
|
|
361
|
+
passed, detail = _self_test(paths)
|
|
362
|
+
report(f"[{'PASS' if passed else 'FAIL'}] Runtime self-test: {detail}")
|
|
363
|
+
healthy = healthy and passed
|
|
364
|
+
else:
|
|
365
|
+
report("[SKIP] Runtime self-test: setup is incomplete")
|
|
366
|
+
|
|
367
|
+
report(f"Status: {'HEALTHY' if healthy else 'UNHEALTHY'}")
|
|
368
|
+
return 0 if healthy else 1
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def run_inference(
|
|
372
|
+
input_path: Path,
|
|
373
|
+
output_path: Path,
|
|
374
|
+
provider: str,
|
|
375
|
+
*,
|
|
376
|
+
package_version: str | None = None,
|
|
377
|
+
target: Target | None = None,
|
|
378
|
+
cache_root: Path | None = None,
|
|
379
|
+
) -> int:
|
|
380
|
+
package_version = package_version or get_package_version()
|
|
381
|
+
target = target or detect_target()
|
|
382
|
+
cache_root = cache_root or get_cache_root(target.system)
|
|
383
|
+
paths = installation_paths(cache_root, package_version, target)
|
|
384
|
+
missing = [key for key in ("manifest", "binary", "model", "config") if not paths[key].is_file()]
|
|
385
|
+
if missing:
|
|
386
|
+
names = ", ".join(paths[key].name for key in missing)
|
|
387
|
+
raise CliError(f"setup is incomplete ({names}). Run '{PACKAGE_NAME} --setup'.")
|
|
388
|
+
if target.system == "Linux" and not os.access(paths["binary"], os.X_OK):
|
|
389
|
+
raise CliError(f"{paths['binary']} is not executable. Run '{PACKAGE_NAME} --setup'.")
|
|
390
|
+
command = [
|
|
391
|
+
str(paths["binary"]),
|
|
392
|
+
"--model",
|
|
393
|
+
str(paths["model"]),
|
|
394
|
+
"--config",
|
|
395
|
+
str(paths["config"]),
|
|
396
|
+
"--input",
|
|
397
|
+
str(input_path),
|
|
398
|
+
"--output",
|
|
399
|
+
str(output_path),
|
|
400
|
+
"--provider",
|
|
401
|
+
provider,
|
|
402
|
+
]
|
|
403
|
+
try:
|
|
404
|
+
return subprocess.run(command, check=False).returncode
|
|
405
|
+
except OSError as exc:
|
|
406
|
+
raise CliError(f"could not start {paths['binary']}: {exc}") from exc
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
410
|
+
parser = argparse.ArgumentParser(description="Enhance audio with the Audioreconstructor ONNX model.")
|
|
411
|
+
actions = parser.add_mutually_exclusive_group()
|
|
412
|
+
actions.add_argument("--setup", action="store_true", help="download and verify the native runtime and model")
|
|
413
|
+
actions.add_argument("--doctor", action="store_true", help="verify cached assets and run a runtime self-test")
|
|
414
|
+
parser.add_argument("--version", action="version", version=get_package_version())
|
|
415
|
+
parser.add_argument("--input", type=Path, help="input audio file")
|
|
416
|
+
parser.add_argument("--output", type=Path, help="output FLAC file")
|
|
417
|
+
parser.add_argument("--provider", choices=("auto", "cpu", "directml"), default="auto", help="ONNX provider (default: auto)")
|
|
418
|
+
return parser
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
422
|
+
parser = build_parser()
|
|
423
|
+
args = parser.parse_args(argv)
|
|
424
|
+
try:
|
|
425
|
+
if args.setup:
|
|
426
|
+
setup_assets()
|
|
427
|
+
return 0
|
|
428
|
+
if args.doctor:
|
|
429
|
+
return doctor()
|
|
430
|
+
if args.input is None and args.output is None:
|
|
431
|
+
parser.print_help()
|
|
432
|
+
return 0
|
|
433
|
+
if args.input is None or args.output is None:
|
|
434
|
+
parser.error("--input and --output must be used together")
|
|
435
|
+
return run_inference(args.input, args.output, args.provider)
|
|
436
|
+
except CliError as exc:
|
|
437
|
+
print(f"Error: {exc}", file=sys.stderr)
|
|
438
|
+
return 1
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
if __name__ == "__main__":
|
|
442
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: audioreconstructor
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Command-line audio reconstruction powered by the Audioreconstruction ONNX model
|
|
5
|
+
Project-URL: Homepage, https://github.com/rohan-prasen/audioreconstruction
|
|
6
|
+
Project-URL: Repository, https://github.com/rohan-prasen/audioreconstruction
|
|
7
|
+
Project-URL: Issues, https://github.com/rohan-prasen/audioreconstruction/issues
|
|
8
|
+
Author-email: Rohan Prasen Kedari <rohanprasenkedari@gmail.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Keywords: audio,cli,onnx,super-resolution
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
15
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Multimedia :: Sound/Audio
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# audioreconstructor
|
|
26
|
+
|
|
27
|
+
`audioreconstructor` is the command-line release of Audioreconstruction's ONNX
|
|
28
|
+
audio enhancer. It installs a small Python launcher; the ONNX model and the native
|
|
29
|
+
runtime are downloaded only when you explicitly run setup.
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install audioreconstructor
|
|
35
|
+
audioreconstructor --setup
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`--setup` downloads the native executable, `model.onnx`, and `config.json` from the
|
|
39
|
+
GitHub Release matching the installed package version. It verifies SHA-256 hashes
|
|
40
|
+
before making them available locally.
|
|
41
|
+
|
|
42
|
+
The current release supports 64-bit Linux and 64-bit Windows.
|
|
43
|
+
|
|
44
|
+
## Use
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
audioreconstructor --input song.mp3 --output song_enhanced.flac
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Choose the ONNX execution provider when needed:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
audioreconstructor --input song.mp3 --output song_enhanced.flac --provider cpu
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`auto` is the default. Windows first tries DirectML and falls back to CPU; Linux uses
|
|
57
|
+
CPU. The native executable reads supported audio through libsndfile and always writes
|
|
58
|
+
FLAC output.
|
|
59
|
+
|
|
60
|
+
## Verify installation
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
audioreconstructor --doctor
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Doctor verifies all cached release assets and runs an actual synthetic-audio ONNX
|
|
67
|
+
inference test. A healthy installation exits with status `0`.
|
|
68
|
+
|
|
69
|
+
## Cache locations
|
|
70
|
+
|
|
71
|
+
Setup prints the exact locations it uses. By default they are:
|
|
72
|
+
|
|
73
|
+
- Linux: `$XDG_CACHE_HOME/audioreconstructor/<version>` or
|
|
74
|
+
`~/.cache/audioreconstructor/<version>`
|
|
75
|
+
- Windows: `%LOCALAPPDATA%\\audioreconstructor\\Cache\\<version>`
|
|
76
|
+
|
|
77
|
+
Running setup for an upgraded package version removes older cached versions after the
|
|
78
|
+
new version has been fully verified.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
audioreconstructor/__init__.py,sha256=ZAzxkQLFTR5oTN7XxnRKFRjMpVoHIzt2NI67J91LovQ,94
|
|
2
|
+
audioreconstructor/__main__.py,sha256=PSQ4rpL0dG6f-qH4N7H-gD9igQkdHzH4yVZDcW8lfZo,80
|
|
3
|
+
audioreconstructor/cli.py,sha256=Sx3P_dXpnibCyQOjCni7d72Bpuy33WyGVKwdhhOGdbQ,16344
|
|
4
|
+
audioreconstructor-1.0.0.dist-info/METADATA,sha256=s6TTUZZ1P5br-WIgYQmZFvLm88bNca5hiJmodGTpYLo,2698
|
|
5
|
+
audioreconstructor-1.0.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
6
|
+
audioreconstructor-1.0.0.dist-info/entry_points.txt,sha256=s2EKhHQfXWq0nQHHr9Jom5oKBbqlKdl8b-67_QLRdhg,67
|
|
7
|
+
audioreconstructor-1.0.0.dist-info/RECORD,,
|