audio-super-resolution 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,142 @@
1
+ """Audio super-resolution tools for Python."""
2
+
3
+ from .audiosr_backend import AUDIOSR_MODEL_NAMES, AUDIOSR_SAMPLE_RATE, AudiosrBackend
4
+ from .backends import register_backend, registered_backend_types
5
+ from .config import InferenceConfig, default_model_cache_dir
6
+ from .devices import DeviceInfo, available_devices, resolve_device
7
+ from .downloads import (
8
+ download_model_weights as download_model_weights_for_spec,
9
+ )
10
+ from .downloads import (
11
+ download_weights_for_spec,
12
+ register_weight_provider,
13
+ )
14
+ from .manifest import (
15
+ ManifestComparison,
16
+ ManifestDifference,
17
+ build_manifest,
18
+ compare_manifests,
19
+ format_manifest_comparison,
20
+ load_manifest,
21
+ manifest_comparison_to_dict,
22
+ write_manifest,
23
+ )
24
+ from .model_weights import (
25
+ download_model_weights,
26
+ resolve_backend_model_weights,
27
+ resolve_model_weights,
28
+ verify_model_weights,
29
+ )
30
+ from .models import ModelInfo, find_model_spec, get_model_spec, list_models
31
+ from .preprocess import DEFAULT_LOWPASS_CUTOFF_HZ, apply_preprocessing, lowpass_filter
32
+ from .quality import (
33
+ AudioQualityReport,
34
+ build_quality_report_bundle,
35
+ format_quality_report,
36
+ inspect_audio_quality,
37
+ quality_report_to_dict,
38
+ write_quality_report_bundle,
39
+ )
40
+ from .resolver import (
41
+ DEFAULT_AUDIO_EXTENSIONS,
42
+ AudioSuperResolver,
43
+ BackendInfo,
44
+ EnhancementResult,
45
+ PlannedEnhancement,
46
+ available_backends,
47
+ discover_audio_files,
48
+ get_backend,
49
+ plan_enhancements,
50
+ )
51
+ from .specs import BackendCapability, ModelSpec, WeightFileSpec
52
+ from .weight_store import (
53
+ ResolvedWeights,
54
+ resolve_weights_for_spec,
55
+ validate_weight_manifest_matches_spec,
56
+ verify_weights_for_spec,
57
+ )
58
+ from .weights import (
59
+ WeightFile,
60
+ WeightManifest,
61
+ load_safetensors,
62
+ read_weight_manifest,
63
+ resolve_manifest_file_paths,
64
+ resolve_weight_file_path,
65
+ resolve_weight_path,
66
+ sha256_file,
67
+ validate_weight_file_path,
68
+ verify_weight_file,
69
+ verify_weight_manifest,
70
+ write_weight_manifest,
71
+ )
72
+
73
+ __all__ = [
74
+ "AUDIOSR_MODEL_NAMES",
75
+ "AUDIOSR_SAMPLE_RATE",
76
+ "DEFAULT_AUDIO_EXTENSIONS",
77
+ "DEFAULT_LOWPASS_CUTOFF_HZ",
78
+ "AudioQualityReport",
79
+ "AudioSuperResolver",
80
+ "AudiosrBackend",
81
+ "BackendCapability",
82
+ "BackendInfo",
83
+ "DeviceInfo",
84
+ "EnhancementResult",
85
+ "InferenceConfig",
86
+ "ManifestComparison",
87
+ "ManifestDifference",
88
+ "ModelInfo",
89
+ "ModelSpec",
90
+ "PlannedEnhancement",
91
+ "ResolvedWeights",
92
+ "WeightFile",
93
+ "WeightFileSpec",
94
+ "WeightManifest",
95
+ "apply_preprocessing",
96
+ "available_backends",
97
+ "available_devices",
98
+ "build_manifest",
99
+ "build_quality_report_bundle",
100
+ "compare_manifests",
101
+ "default_model_cache_dir",
102
+ "discover_audio_files",
103
+ "download_model_weights",
104
+ "download_model_weights_for_spec",
105
+ "download_weights_for_spec",
106
+ "find_model_spec",
107
+ "format_manifest_comparison",
108
+ "format_quality_report",
109
+ "get_backend",
110
+ "get_model_spec",
111
+ "inspect_audio_quality",
112
+ "list_models",
113
+ "load_manifest",
114
+ "load_safetensors",
115
+ "lowpass_filter",
116
+ "manifest_comparison_to_dict",
117
+ "plan_enhancements",
118
+ "quality_report_to_dict",
119
+ "read_weight_manifest",
120
+ "register_backend",
121
+ "register_weight_provider",
122
+ "registered_backend_types",
123
+ "resolve_backend_model_weights",
124
+ "resolve_device",
125
+ "resolve_manifest_file_paths",
126
+ "resolve_model_weights",
127
+ "resolve_weights_for_spec",
128
+ "resolve_weight_file_path",
129
+ "resolve_weight_path",
130
+ "sha256_file",
131
+ "validate_weight_file_path",
132
+ "validate_weight_manifest_matches_spec",
133
+ "verify_model_weights",
134
+ "verify_weights_for_spec",
135
+ "verify_weight_file",
136
+ "verify_weight_manifest",
137
+ "write_manifest",
138
+ "write_quality_report_bundle",
139
+ "write_weight_manifest",
140
+ ]
141
+
142
+ __version__ = "0.1.0"
@@ -0,0 +1,17 @@
1
+ from __future__ import annotations
2
+
3
+ from .backends.audiosr_external import (
4
+ AUDIOSR_MODEL_NAMES,
5
+ AUDIOSR_SAMPLE_RATE,
6
+ AudiosrBackend,
7
+ _import_audiosr,
8
+ _waveform_to_audio_array,
9
+ )
10
+
11
+ __all__ = [
12
+ "AUDIOSR_MODEL_NAMES",
13
+ "AUDIOSR_SAMPLE_RATE",
14
+ "AudiosrBackend",
15
+ "_import_audiosr",
16
+ "_waveform_to_audio_array",
17
+ ]
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+
3
+ from .audiosr_external import AUDIOSR_MODEL_NAMES, AUDIOSR_SAMPLE_RATE, AudiosrBackend
4
+ from .base import BackendInfo, EnhancementBackend, FileEnhancementBackend
5
+ from .lavasr_compat import LAVASR_MODEL_ID, LAVASR_REVISION, LAVASR_SAMPLE_RATE, LavaSRCompatBackend
6
+ from .registry import (
7
+ available_backends,
8
+ get_backend,
9
+ list_backend_model_specs,
10
+ register_backend,
11
+ registered_backend_types,
12
+ )
13
+ from .sinc import SincResampleBackend
14
+
15
+ __all__ = [
16
+ "AUDIOSR_MODEL_NAMES",
17
+ "AUDIOSR_SAMPLE_RATE",
18
+ "LAVASR_MODEL_ID",
19
+ "LAVASR_REVISION",
20
+ "LAVASR_SAMPLE_RATE",
21
+ "AudiosrBackend",
22
+ "BackendInfo",
23
+ "EnhancementBackend",
24
+ "FileEnhancementBackend",
25
+ "LavaSRCompatBackend",
26
+ "SincResampleBackend",
27
+ "available_backends",
28
+ "get_backend",
29
+ "list_backend_model_specs",
30
+ "register_backend",
31
+ "registered_backend_types",
32
+ ]
@@ -0,0 +1,128 @@
1
+ from __future__ import annotations
2
+
3
+ import importlib
4
+ import importlib.util
5
+ import os
6
+ from pathlib import Path
7
+ from types import ModuleType
8
+
9
+ import numpy as np
10
+ import soundfile as sf
11
+
12
+ from ..config import InferenceConfig
13
+ from ..specs import ModelSpec
14
+ from .base import DEFAULT_FILE_BACKEND_CAPABILITY
15
+
16
+ AUDIOSR_SAMPLE_RATE = 48000
17
+ AUDIOSR_MODEL_NAMES = ("basic", "speech")
18
+
19
+
20
+ class AudiosrBackend:
21
+ """AudioSR latent diffusion backend.
22
+
23
+ The heavy `audiosr` dependency is imported only when this backend is selected.
24
+ """
25
+
26
+ name = "audiosr"
27
+ description = "AudioSR latent diffusion backend. Requires the optional audiosr dependency."
28
+ optional_dependency = "audiosr"
29
+ package_extra = "audiosr"
30
+
31
+ def __init__(self, config: InferenceConfig | None = None) -> None:
32
+ self.config = config or InferenceConfig()
33
+ self._model = None
34
+
35
+ @classmethod
36
+ def is_available(cls) -> bool:
37
+ try:
38
+ return importlib.util.find_spec("audiosr") is not None
39
+ except (ImportError, ValueError):
40
+ return False
41
+
42
+ @classmethod
43
+ def model_specs(cls) -> tuple[ModelSpec, ...]:
44
+ return tuple(
45
+ ModelSpec(
46
+ id=f"audiosr-{model_name}",
47
+ backend=cls.name,
48
+ model_name=model_name,
49
+ name=f"AudioSR {model_name.title()}",
50
+ description=f"AudioSR latent diffusion {model_name} model.",
51
+ implementation="external_package",
52
+ domain=("speech", "music", "sfx", "general"),
53
+ architecture="latent-diffusion-audio-super-resolution",
54
+ target_sample_rates=(AUDIOSR_SAMPLE_RATE,),
55
+ weights_license="unknown",
56
+ weights_source="Hugging Face download managed by the audiosr package",
57
+ maturity="experimental",
58
+ capability=DEFAULT_FILE_BACKEND_CAPABILITY,
59
+ )
60
+ for model_name in AUDIOSR_MODEL_NAMES
61
+ )
62
+
63
+ def enhance(self, audio: np.ndarray, sample_rate: int, target_sample_rate: int) -> np.ndarray:
64
+ raise RuntimeError("The audiosr backend requires file-based enhancement through AudioSuperResolver.enhance().")
65
+
66
+ def enhance_file(self, input_path: str | Path, output_path: str | Path, target_sample_rate: int) -> None:
67
+ if target_sample_rate != AUDIOSR_SAMPLE_RATE:
68
+ raise ValueError("The audiosr backend outputs 48000 Hz audio; set target_sr=48000.")
69
+ if self.config.device == "directml":
70
+ raise ValueError("The audiosr backend does not support directml; use cpu, cuda, mps, or auto.")
71
+ if self.config.precision not in {"float32", "auto"}:
72
+ raise ValueError("The audiosr backend currently supports only float32 or auto precision.")
73
+ if self.config.model_name not in AUDIOSR_MODEL_NAMES:
74
+ choices = ", ".join(AUDIOSR_MODEL_NAMES)
75
+ raise ValueError(f"The audiosr backend model_name must be one of: {choices}")
76
+
77
+ audiosr = _import_audiosr()
78
+ self._prepare_cache_environment()
79
+
80
+ model = self._load_model(audiosr)
81
+ waveform = audiosr.super_resolution(
82
+ model,
83
+ str(input_path),
84
+ seed=self.config.seed,
85
+ guidance_scale=self.config.guidance_scale,
86
+ ddim_steps=self.config.ddim_steps,
87
+ )
88
+
89
+ output_path = Path(output_path)
90
+ output_path.parent.mkdir(parents=True, exist_ok=True)
91
+ sf.write(output_path, _waveform_to_audio_array(waveform), samplerate=AUDIOSR_SAMPLE_RATE)
92
+
93
+ def _prepare_cache_environment(self) -> None:
94
+ cache_dir = self.config.ensure_model_cache_dir()
95
+ os.environ.setdefault("HF_HOME", str(cache_dir / "huggingface"))
96
+
97
+ def _load_model(self, audiosr: ModuleType):
98
+ if self._model is None:
99
+ self._model = audiosr.build_model(model_name=self.config.model_name, device=self.config.device)
100
+ return self._model
101
+
102
+
103
+ def _import_audiosr() -> ModuleType:
104
+ try:
105
+ return importlib.import_module("audiosr")
106
+ except ImportError as exc:
107
+ raise RuntimeError(
108
+ "The audiosr backend requires the optional audiosr dependency. "
109
+ "Install it with `pip install audio-super-resolution[audiosr]`."
110
+ ) from exc
111
+
112
+
113
+ def _waveform_to_audio_array(waveform) -> np.ndarray:
114
+ if hasattr(waveform, "detach"):
115
+ waveform = waveform.detach().cpu().numpy()
116
+
117
+ audio = np.asarray(waveform, dtype=np.float32)
118
+
119
+ if audio.ndim == 3:
120
+ audio = audio[0]
121
+ if audio.ndim == 2 and audio.shape[0] <= 8:
122
+ audio = audio.T
123
+ if audio.ndim == 2 and audio.shape[1] == 1:
124
+ audio = audio[:, 0]
125
+ if audio.ndim not in {1, 2}:
126
+ raise ValueError(f"Unsupported AudioSR waveform shape: {audio.shape}")
127
+
128
+ return audio
@@ -0,0 +1,68 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+ from typing import Protocol
6
+
7
+ import numpy as np
8
+
9
+ from ..config import InferenceConfig
10
+ from ..specs import BackendCapability, ModelSpec
11
+
12
+
13
+ @dataclass(frozen=True)
14
+ class BackendInfo:
15
+ """User-facing metadata for an enhancement backend."""
16
+
17
+ name: str
18
+ description: str
19
+ installed: bool
20
+ optional_dependency: str | None = None
21
+ package_extra: str | None = None
22
+
23
+
24
+ class EnhancementBackend(Protocol):
25
+ """Interface implemented by audio super-resolution backends."""
26
+
27
+ name: str
28
+ description: str
29
+ config: InferenceConfig
30
+
31
+ def enhance(self, audio: np.ndarray, sample_rate: int, target_sample_rate: int) -> np.ndarray:
32
+ """Return audio enhanced to target_sample_rate."""
33
+
34
+
35
+ class FileEnhancementBackend(EnhancementBackend, Protocol):
36
+ """Optional file-native backend interface."""
37
+
38
+ def enhance_file(self, input_path: str | Path, output_path: str | Path, target_sample_rate: int) -> None:
39
+ """Write enhanced audio to output_path."""
40
+
41
+
42
+ DEFAULT_ARRAY_BACKEND_CAPABILITY = BackendCapability(
43
+ supports_array_io=True,
44
+ supports_file_io=False,
45
+ supports_chunking=True,
46
+ deterministic=True,
47
+ supports_cpu=True,
48
+ )
49
+
50
+
51
+ DEFAULT_FILE_BACKEND_CAPABILITY = BackendCapability(
52
+ supports_array_io=False,
53
+ supports_file_io=True,
54
+ supports_chunking=True,
55
+ deterministic=False,
56
+ supports_cpu=True,
57
+ supports_cuda=True,
58
+ supports_mps=True,
59
+ )
60
+
61
+
62
+ def backend_model_specs(backend_type: type[EnhancementBackend]) -> tuple[ModelSpec, ...]:
63
+ """Return model specs exposed by a backend type."""
64
+
65
+ model_specs = getattr(backend_type, "model_specs", None)
66
+ if callable(model_specs):
67
+ return tuple(model_specs())
68
+ return ()
@@ -0,0 +1,83 @@
1
+ from __future__ import annotations
2
+
3
+ import importlib.util
4
+
5
+ import numpy as np
6
+
7
+ from ..config import InferenceConfig
8
+ from ..specs import BackendCapability, ModelSpec, WeightFileSpec
9
+
10
+ LAVASR_SAMPLE_RATE = 48000
11
+ LAVASR_MODEL_ID = "lavasr-v2-bwe"
12
+ LAVASR_REVISION = "b98dc8be472da45ab7b6346ad7997e1dfeb5911d"
13
+
14
+
15
+ class LavaSRCompatBackend:
16
+ """Self-contained LavaSR-compatible backend placeholder for verified BWE weights."""
17
+
18
+ name = "lavasr-compat"
19
+ description = "Self-contained LavaSR-compatible speech bandwidth extension backend."
20
+ optional_dependency = "torch"
21
+ package_extra = "lavasr"
22
+
23
+ def __init__(self, config: InferenceConfig | None = None) -> None:
24
+ self.config = config or InferenceConfig()
25
+
26
+ @classmethod
27
+ def is_available(cls) -> bool:
28
+ return importlib.util.find_spec("torch") is not None and importlib.util.find_spec("yaml") is not None
29
+
30
+ @classmethod
31
+ def model_specs(cls) -> tuple[ModelSpec, ...]:
32
+ return (
33
+ ModelSpec(
34
+ id=LAVASR_MODEL_ID,
35
+ backend=cls.name,
36
+ model_name=LAVASR_MODEL_ID,
37
+ name="LavaSR v2 BWE",
38
+ description="LavaSR v2 bandwidth extension weights for 48 kHz output.",
39
+ implementation="self_torch",
40
+ domain=("speech",),
41
+ architecture="vocos-istft-bwe",
42
+ target_sample_rates=(LAVASR_SAMPLE_RATE,),
43
+ weights_license="Apache-2.0",
44
+ weights_source="YatharthS/LavaSR",
45
+ weight_provider="huggingface",
46
+ default_weight_revision=LAVASR_REVISION,
47
+ requires_weights=True,
48
+ maturity="experimental",
49
+ weight_files=(
50
+ WeightFileSpec(
51
+ path="enhancer_v2/config.yaml",
52
+ sha256="0d970f5cdc1913730c417b49e476bb09bb8b874583d113f71a7f10c8bb3a4b7d",
53
+ size=526,
54
+ ),
55
+ WeightFileSpec(
56
+ path="enhancer_v2/pytorch_model.bin",
57
+ sha256="d100db961b2c125d77a52a12215c689e44cd9926a72f117513395b7e25e6de12",
58
+ size=56316591,
59
+ ),
60
+ ),
61
+ capability=BackendCapability(
62
+ supports_array_io=True,
63
+ supports_file_io=False,
64
+ supports_chunking=True,
65
+ deterministic=True,
66
+ supports_cpu=True,
67
+ supports_cuda=True,
68
+ supports_mps=True,
69
+ precision_modes=("float32", "auto"),
70
+ ),
71
+ ),
72
+ )
73
+
74
+ def enhance(self, audio: np.ndarray, sample_rate: int, target_sample_rate: int) -> np.ndarray:
75
+ if target_sample_rate != LAVASR_SAMPLE_RATE:
76
+ raise ValueError("The lavasr-compat backend outputs 48000 Hz audio; set target_sr=48000.")
77
+ if self.config.denoise:
78
+ raise ValueError("denoise is reserved but not supported by lavasr-compat yet")
79
+
80
+ from ..weight_store import resolve_weights_for_spec
81
+
82
+ resolve_weights_for_spec(self.model_specs()[0], self.config, allow_download=self.config.download_weights)
83
+ raise RuntimeError("lavasr-compat weight management is available, but inference is not implemented yet.")
@@ -0,0 +1,75 @@
1
+ from __future__ import annotations
2
+
3
+ from ..config import InferenceConfig
4
+ from ..specs import ModelSpec
5
+ from .audiosr_external import AudiosrBackend
6
+ from .base import BackendInfo, EnhancementBackend, backend_model_specs
7
+ from .lavasr_compat import LavaSRCompatBackend
8
+ from .sinc import SincResampleBackend
9
+
10
+ _BACKENDS: dict[str, type[EnhancementBackend]] = {}
11
+
12
+
13
+ def register_backend(backend_type: type[EnhancementBackend], *, replace: bool = False) -> None:
14
+ """Register an enhancement backend type by its public name."""
15
+
16
+ name = getattr(backend_type, "name", None)
17
+ if not isinstance(name, str) or not name:
18
+ raise ValueError("backend_type must define a non-empty string name")
19
+ if name in _BACKENDS and not replace:
20
+ raise ValueError(f"Backend {name!r} is already registered")
21
+ _BACKENDS[name] = backend_type
22
+
23
+
24
+ def registered_backend_types() -> dict[str, type[EnhancementBackend]]:
25
+ """Return a copy of the backend registry."""
26
+
27
+ return dict(_BACKENDS)
28
+
29
+
30
+ def available_backends() -> list[BackendInfo]:
31
+ """Return the registered enhancement backends."""
32
+
33
+ return [
34
+ BackendInfo(
35
+ name=name,
36
+ description=backend.description,
37
+ installed=_backend_is_available(backend),
38
+ optional_dependency=getattr(backend, "optional_dependency", None),
39
+ package_extra=getattr(backend, "package_extra", None),
40
+ )
41
+ for name, backend in sorted(_BACKENDS.items(), key=lambda item: item[0])
42
+ ]
43
+
44
+
45
+ def get_backend(name: str, config: InferenceConfig | None = None) -> EnhancementBackend:
46
+ """Create a backend by name."""
47
+
48
+ try:
49
+ backend_type = _BACKENDS[name]
50
+ except KeyError as exc:
51
+ choices = ", ".join(sorted(_BACKENDS))
52
+ raise ValueError(f"Unknown backend {name!r}. Available backends: {choices}") from exc
53
+
54
+ return backend_type(config=config)
55
+
56
+
57
+ def list_backend_model_specs() -> list[ModelSpec]:
58
+ """Return model specs exposed by every registered backend."""
59
+
60
+ specs: list[ModelSpec] = []
61
+ for _, backend_type in sorted(_BACKENDS.items(), key=lambda item: item[0]):
62
+ specs.extend(backend_model_specs(backend_type))
63
+ return specs
64
+
65
+
66
+ def _backend_is_available(backend_type: type[EnhancementBackend]) -> bool:
67
+ checker = getattr(backend_type, "is_available", None)
68
+ if checker is None:
69
+ return True
70
+ return bool(checker())
71
+
72
+
73
+ register_backend(AudiosrBackend)
74
+ register_backend(LavaSRCompatBackend)
75
+ register_backend(SincResampleBackend)
@@ -0,0 +1,48 @@
1
+ from __future__ import annotations
2
+
3
+ import numpy as np
4
+ from scipy.signal import resample_poly
5
+
6
+ from ..config import InferenceConfig
7
+ from ..specs import ModelSpec
8
+ from .base import DEFAULT_ARRAY_BACKEND_CAPABILITY
9
+
10
+
11
+ class SincResampleBackend:
12
+ """Deterministic baseline backend using polyphase sinc resampling."""
13
+
14
+ name = "sinc-resample"
15
+ description = "Deterministic polyphase sinc resampling baseline."
16
+ optional_dependency = None
17
+ package_extra = None
18
+
19
+ def __init__(self, config: InferenceConfig | None = None) -> None:
20
+ self.config = config or InferenceConfig()
21
+
22
+ @classmethod
23
+ def model_specs(cls) -> tuple[ModelSpec, ...]:
24
+ return (
25
+ ModelSpec(
26
+ id="sinc-resample",
27
+ backend=cls.name,
28
+ name="Sinc Resample Baseline",
29
+ description=cls.description,
30
+ implementation="baseline",
31
+ domain=("general",),
32
+ architecture="polyphase-sinc-resampling",
33
+ target_sample_rates=None,
34
+ weights_license=None,
35
+ weights_source=None,
36
+ maturity="stable",
37
+ capability=DEFAULT_ARRAY_BACKEND_CAPABILITY,
38
+ ),
39
+ )
40
+
41
+ def enhance(self, audio: np.ndarray, sample_rate: int, target_sample_rate: int) -> np.ndarray:
42
+ if sample_rate == target_sample_rate:
43
+ return np.asarray(audio)
44
+
45
+ gcd = np.gcd(sample_rate, target_sample_rate)
46
+ up = target_sample_rate // gcd
47
+ down = sample_rate // gcd
48
+ return resample_poly(audio, up, down, axis=0)