wfloat 2.0.0__py3-none-win_amd64.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.
- wfloat/__init__.py +57 -0
- wfloat/__main__.py +5 -0
- wfloat/_assets.py +421 -0
- wfloat/_cache.py +208 -0
- wfloat/_cli.py +74 -0
- wfloat/_constants.py +147 -0
- wfloat/_core.py +1795 -0
- wfloat/_download.py +130 -0
- wfloat/_generated_model_urls.py +46 -0
- wfloat/_llm.py +84 -0
- wfloat/_llm_assets.py +142 -0
- wfloat/_llm_load.py +61 -0
- wfloat/_model.py +394 -0
- wfloat/_native.py +17 -0
- wfloat/_results.py +163 -0
- wfloat/_stt.py +122 -0
- wfloat/_stt_assets.py +144 -0
- wfloat/_stt_load.py +75 -0
- wfloat/_vad.py +119 -0
- wfloat/_vad_assets.py +130 -0
- wfloat/_vad_load.py +97 -0
- wfloat/_version.py +1 -0
- wfloat/native/wfloat-core.dll +0 -0
- wfloat-2.0.0.dist-info/METADATA +251 -0
- wfloat-2.0.0.dist-info/RECORD +28 -0
- wfloat-2.0.0.dist-info/WHEEL +5 -0
- wfloat-2.0.0.dist-info/entry_points.txt +2 -0
- wfloat-2.0.0.dist-info/top_level.txt +1 -0
wfloat/_stt_assets.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Mapping, Optional
|
|
7
|
+
from urllib.parse import urlparse
|
|
8
|
+
|
|
9
|
+
from ._assets import SttModelAssets
|
|
10
|
+
from ._download import download_file, verify_checksum
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class CachedSttAssets:
|
|
15
|
+
model_name: str
|
|
16
|
+
family: str
|
|
17
|
+
cache_dir: Path
|
|
18
|
+
files: Mapping[str, Path]
|
|
19
|
+
|
|
20
|
+
def require(self, key: str) -> Path:
|
|
21
|
+
path = self.files.get(key)
|
|
22
|
+
if path is None:
|
|
23
|
+
raise ValueError(f"Missing required STT asset: {key}")
|
|
24
|
+
return path
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _normalize_model_dir_name(model_name: str) -> str:
|
|
28
|
+
return model_name.replace("/", "--").replace(" ", "-")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _is_url(value: str) -> bool:
|
|
32
|
+
parsed = urlparse(value)
|
|
33
|
+
return parsed.scheme in {"http", "https", "file"}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _copy_local_file(source: Path, destination: Path) -> None:
|
|
37
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
38
|
+
shutil.copyfile(str(source), str(destination))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _materialize_asset(
|
|
42
|
+
source: str | Path,
|
|
43
|
+
destination: Path,
|
|
44
|
+
*,
|
|
45
|
+
expected_checksum: Optional[str],
|
|
46
|
+
force_download: bool,
|
|
47
|
+
) -> Path:
|
|
48
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
49
|
+
|
|
50
|
+
if not force_download and destination.is_file():
|
|
51
|
+
if expected_checksum is None or verify_checksum(destination, expected_checksum):
|
|
52
|
+
return destination
|
|
53
|
+
|
|
54
|
+
if isinstance(source, Path):
|
|
55
|
+
_copy_local_file(source, destination)
|
|
56
|
+
else:
|
|
57
|
+
source_str = str(source)
|
|
58
|
+
if _is_url(source_str):
|
|
59
|
+
download_file(source_str, destination, expected_checksum=expected_checksum)
|
|
60
|
+
else:
|
|
61
|
+
_copy_local_file(Path(source_str), destination)
|
|
62
|
+
|
|
63
|
+
if expected_checksum is not None and not verify_checksum(destination, expected_checksum):
|
|
64
|
+
raise RuntimeError(f"Cached STT asset checksum mismatch for {destination}.")
|
|
65
|
+
|
|
66
|
+
return destination
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def cache_stt_assets(
|
|
70
|
+
model_name: str,
|
|
71
|
+
*,
|
|
72
|
+
family: str,
|
|
73
|
+
sources: Mapping[str, str | Path | None],
|
|
74
|
+
checksums: Optional[Mapping[str, str]] = None,
|
|
75
|
+
cache_dir: Path,
|
|
76
|
+
force_download: bool = False,
|
|
77
|
+
) -> CachedSttAssets:
|
|
78
|
+
model_dir = cache_dir / "models" / _normalize_model_dir_name(model_name)
|
|
79
|
+
model_dir.mkdir(parents=True, exist_ok=True)
|
|
80
|
+
checksums = checksums or {}
|
|
81
|
+
|
|
82
|
+
files: dict[str, Path] = {}
|
|
83
|
+
for key, source in sources.items():
|
|
84
|
+
if source is None:
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
source_value = Path(source) if isinstance(source, Path) else str(source)
|
|
88
|
+
filename = Path(urlparse(str(source_value)).path).name or Path(str(source_value)).name
|
|
89
|
+
if not filename:
|
|
90
|
+
raise ValueError(f"Could not derive filename for STT asset {key}.")
|
|
91
|
+
|
|
92
|
+
destination = model_dir / filename
|
|
93
|
+
files[key] = _materialize_asset(
|
|
94
|
+
source_value,
|
|
95
|
+
destination,
|
|
96
|
+
expected_checksum=checksums.get(key),
|
|
97
|
+
force_download=force_download,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
return CachedSttAssets(
|
|
101
|
+
model_name=model_name,
|
|
102
|
+
family=family,
|
|
103
|
+
cache_dir=cache_dir,
|
|
104
|
+
files=files,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def cache_stt_model_assets(
|
|
109
|
+
model_name: str,
|
|
110
|
+
assets: SttModelAssets,
|
|
111
|
+
*,
|
|
112
|
+
cache_dir: Path,
|
|
113
|
+
force_download: bool = False,
|
|
114
|
+
) -> CachedSttAssets:
|
|
115
|
+
return cache_stt_assets(
|
|
116
|
+
model_name,
|
|
117
|
+
family=assets.family,
|
|
118
|
+
sources={
|
|
119
|
+
"model": assets.model,
|
|
120
|
+
"tokens": assets.tokens,
|
|
121
|
+
"preprocessor": assets.preprocessor,
|
|
122
|
+
"encoder": assets.encoder,
|
|
123
|
+
"decoder": assets.decoder,
|
|
124
|
+
"joiner": assets.joiner,
|
|
125
|
+
"uncached_decoder": assets.uncached_decoder,
|
|
126
|
+
"cached_decoder": assets.cached_decoder,
|
|
127
|
+
},
|
|
128
|
+
checksums={
|
|
129
|
+
key: value
|
|
130
|
+
for key, value in {
|
|
131
|
+
"model": assets.model_checksum,
|
|
132
|
+
"tokens": assets.tokens_checksum,
|
|
133
|
+
"preprocessor": assets.preprocessor_checksum,
|
|
134
|
+
"encoder": assets.encoder_checksum,
|
|
135
|
+
"decoder": assets.decoder_checksum,
|
|
136
|
+
"joiner": assets.joiner_checksum,
|
|
137
|
+
"uncached_decoder": assets.uncached_decoder_checksum,
|
|
138
|
+
"cached_decoder": assets.cached_decoder_checksum,
|
|
139
|
+
}.items()
|
|
140
|
+
if value is not None
|
|
141
|
+
},
|
|
142
|
+
cache_dir=cache_dir,
|
|
143
|
+
force_download=force_download,
|
|
144
|
+
)
|
wfloat/_stt_load.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from ._assets import fetch_stt_assets
|
|
7
|
+
from ._cache import get_default_cache_dir
|
|
8
|
+
from ._core import create_core_stt
|
|
9
|
+
from ._stt import SttModel
|
|
10
|
+
from ._stt_assets import cache_stt_model_assets
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def load_stt_model(
|
|
14
|
+
model_name: str,
|
|
15
|
+
*,
|
|
16
|
+
cache_dir: Optional[Path] = None,
|
|
17
|
+
force_download: bool = False,
|
|
18
|
+
language: Optional[str] = None,
|
|
19
|
+
task: Optional[str] = None,
|
|
20
|
+
enable_token_timestamps: bool = False,
|
|
21
|
+
enable_segment_timestamps: bool = False,
|
|
22
|
+
) -> SttModel:
|
|
23
|
+
resolved_cache_dir = Path(cache_dir) if cache_dir is not None else get_default_cache_dir()
|
|
24
|
+
assets = fetch_stt_assets(model_name)
|
|
25
|
+
cached = cache_stt_model_assets(
|
|
26
|
+
model_name,
|
|
27
|
+
assets,
|
|
28
|
+
cache_dir=resolved_cache_dir,
|
|
29
|
+
force_download=force_download,
|
|
30
|
+
)
|
|
31
|
+
family = assets.family
|
|
32
|
+
|
|
33
|
+
native_stt = create_core_stt(
|
|
34
|
+
model_name=model_name,
|
|
35
|
+
family=family,
|
|
36
|
+
model_path=cached.files.get("model"),
|
|
37
|
+
tokens_path=cached.require("tokens"),
|
|
38
|
+
preprocessor_path=cached.files.get("preprocessor"),
|
|
39
|
+
encoder_path=cached.files.get("encoder"),
|
|
40
|
+
decoder_path=cached.files.get("decoder"),
|
|
41
|
+
joiner_path=cached.files.get("joiner"),
|
|
42
|
+
uncached_decoder_path=cached.files.get("uncached_decoder"),
|
|
43
|
+
cached_decoder_path=cached.files.get("cached_decoder"),
|
|
44
|
+
language=language,
|
|
45
|
+
task=task,
|
|
46
|
+
enable_token_timestamps=enable_token_timestamps,
|
|
47
|
+
enable_segment_timestamps=enable_segment_timestamps,
|
|
48
|
+
)
|
|
49
|
+
return SttModel(model_id=model_name, _native_stt=native_stt)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def load_whisper_tiny_en(
|
|
53
|
+
*,
|
|
54
|
+
cache_dir: Optional[Path] = None,
|
|
55
|
+
force_download: bool = False,
|
|
56
|
+
) -> SttModel:
|
|
57
|
+
return load_stt_model(
|
|
58
|
+
"openai/whisper-tiny-en",
|
|
59
|
+
cache_dir=cache_dir,
|
|
60
|
+
force_download=force_download,
|
|
61
|
+
language="en",
|
|
62
|
+
task="transcribe",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def load_moonshine_tiny_en(
|
|
67
|
+
*,
|
|
68
|
+
cache_dir: Optional[Path] = None,
|
|
69
|
+
force_download: bool = False,
|
|
70
|
+
) -> SttModel:
|
|
71
|
+
return load_stt_model(
|
|
72
|
+
"UsefulSensors/moonshine-tiny",
|
|
73
|
+
cache_dir=cache_dir,
|
|
74
|
+
force_download=force_download,
|
|
75
|
+
)
|
wfloat/_vad.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
import wave
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional, Sequence
|
|
8
|
+
|
|
9
|
+
from ._results import Audio, VadDetectionResult, VadSegment
|
|
10
|
+
|
|
11
|
+
DEFAULT_VAD_SAMPLE_RATE = 16000
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _coerce_audio_samples(audio: bytes | Sequence[float]) -> list[float]:
|
|
15
|
+
if isinstance(audio, (bytes, bytearray, memoryview)):
|
|
16
|
+
raise TypeError(
|
|
17
|
+
"Raw PCM bytes are not supported yet. Pass a float sequence or a WAV path."
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
samples = [float(sample) for sample in audio]
|
|
21
|
+
if any(not math.isfinite(sample) for sample in samples):
|
|
22
|
+
raise ValueError("audio samples must be finite numbers.")
|
|
23
|
+
return samples
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _load_wav_audio(path: Path) -> tuple[list[float], int]:
|
|
27
|
+
with wave.open(str(path), "rb") as wav_file:
|
|
28
|
+
channels = wav_file.getnchannels()
|
|
29
|
+
sample_width = wav_file.getsampwidth()
|
|
30
|
+
sample_rate = wav_file.getframerate()
|
|
31
|
+
frame_count = wav_file.getnframes()
|
|
32
|
+
|
|
33
|
+
if channels != 1:
|
|
34
|
+
raise ValueError("Only mono WAV files are supported.")
|
|
35
|
+
if sample_width != 2:
|
|
36
|
+
raise ValueError("Only 16-bit PCM WAV files are supported.")
|
|
37
|
+
|
|
38
|
+
frames = wav_file.readframes(frame_count)
|
|
39
|
+
|
|
40
|
+
samples: list[float] = []
|
|
41
|
+
for index in range(0, len(frames), 2):
|
|
42
|
+
sample = int.from_bytes(frames[index : index + 2], "little", signed=True)
|
|
43
|
+
samples.append(float(sample) / 32768.0)
|
|
44
|
+
|
|
45
|
+
return samples, sample_rate
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class VadModel:
|
|
50
|
+
model_id: str
|
|
51
|
+
family: str
|
|
52
|
+
_native_vad: object
|
|
53
|
+
sample_rate: int = DEFAULT_VAD_SAMPLE_RATE
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def window_size(self) -> int:
|
|
57
|
+
return 256 if self.family.lower().replace("_", "-") in {"ten-vad", "tenvad"} else 512
|
|
58
|
+
|
|
59
|
+
def detect(
|
|
60
|
+
self,
|
|
61
|
+
*,
|
|
62
|
+
audio: bytes | Sequence[float] | str | Path,
|
|
63
|
+
sample_rate: Optional[int] = None,
|
|
64
|
+
) -> VadDetectionResult:
|
|
65
|
+
if isinstance(audio, (str, Path)):
|
|
66
|
+
samples, resolved_sample_rate = _load_wav_audio(Path(audio))
|
|
67
|
+
else:
|
|
68
|
+
if sample_rate is None or sample_rate <= 0:
|
|
69
|
+
raise ValueError("sample_rate is required for in-memory audio.")
|
|
70
|
+
samples = _coerce_audio_samples(audio)
|
|
71
|
+
resolved_sample_rate = int(sample_rate)
|
|
72
|
+
|
|
73
|
+
if resolved_sample_rate != self.sample_rate:
|
|
74
|
+
raise ValueError(
|
|
75
|
+
f"VAD expects {self.sample_rate} Hz mono audio; got {resolved_sample_rate} Hz."
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if not hasattr(self._native_vad, "accept_waveform"):
|
|
79
|
+
raise RuntimeError("Native VAD backend does not support accept_waveform().")
|
|
80
|
+
|
|
81
|
+
self._native_vad.reset()
|
|
82
|
+
window_size = self.window_size
|
|
83
|
+
for offset in range(0, len(samples), window_size):
|
|
84
|
+
self._native_vad.accept_waveform(samples[offset : offset + window_size])
|
|
85
|
+
self._native_vad.flush()
|
|
86
|
+
|
|
87
|
+
segments: list[VadSegment] = []
|
|
88
|
+
speech_sample_count = 0
|
|
89
|
+
while not self._native_vad.empty():
|
|
90
|
+
native_segment = self._native_vad.front
|
|
91
|
+
segment_samples = [float(sample) for sample in native_segment.samples]
|
|
92
|
+
start_sample = int(native_segment.start)
|
|
93
|
+
sample_count = len(segment_samples)
|
|
94
|
+
speech_sample_count += sample_count
|
|
95
|
+
duration_sec = sample_count / float(self.sample_rate)
|
|
96
|
+
start_sec = start_sample / float(self.sample_rate)
|
|
97
|
+
segments.append(
|
|
98
|
+
VadSegment(
|
|
99
|
+
start_sec=start_sec,
|
|
100
|
+
duration_sec=duration_sec,
|
|
101
|
+
end_sec=start_sec + duration_sec,
|
|
102
|
+
start_sample=start_sample,
|
|
103
|
+
sample_count=sample_count,
|
|
104
|
+
sample_rate=self.sample_rate,
|
|
105
|
+
audio=Audio(samples=segment_samples, sample_rate=self.sample_rate),
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
self._native_vad.pop()
|
|
109
|
+
|
|
110
|
+
speech_ratio = (
|
|
111
|
+
min(max(speech_sample_count / float(len(samples)), 0.0), 1.0)
|
|
112
|
+
if samples
|
|
113
|
+
else 0.0
|
|
114
|
+
)
|
|
115
|
+
return VadDetectionResult(
|
|
116
|
+
model_id=self.model_id,
|
|
117
|
+
segments=segments,
|
|
118
|
+
speech_ratio=speech_ratio,
|
|
119
|
+
)
|
wfloat/_vad_assets.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import shutil
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Mapping, Optional
|
|
7
|
+
from urllib.parse import urlparse
|
|
8
|
+
|
|
9
|
+
from ._assets import VadModelAssets
|
|
10
|
+
from ._download import download_file, verify_checksum
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class CachedVadAssets:
|
|
15
|
+
model_name: str
|
|
16
|
+
family: str
|
|
17
|
+
cache_dir: Path
|
|
18
|
+
files: Mapping[str, Path]
|
|
19
|
+
|
|
20
|
+
def require(self, key: str) -> Path:
|
|
21
|
+
path = self.files.get(key)
|
|
22
|
+
if path is None:
|
|
23
|
+
raise ValueError(f"Missing required VAD asset: {key}")
|
|
24
|
+
return path
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _normalize_model_dir_name(model_name: str) -> str:
|
|
28
|
+
return model_name.replace("/", "--").replace(" ", "-")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _is_url(value: str) -> bool:
|
|
32
|
+
parsed = urlparse(value)
|
|
33
|
+
return parsed.scheme in {"http", "https", "file"}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _copy_local_file(source: Path, destination: Path) -> None:
|
|
37
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
38
|
+
shutil.copyfile(str(source), str(destination))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _materialize_asset(
|
|
42
|
+
source: str | Path,
|
|
43
|
+
destination: Path,
|
|
44
|
+
*,
|
|
45
|
+
expected_checksum: Optional[str],
|
|
46
|
+
force_download: bool,
|
|
47
|
+
) -> Path:
|
|
48
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
49
|
+
|
|
50
|
+
if not force_download and destination.is_file():
|
|
51
|
+
if expected_checksum is None or verify_checksum(destination, expected_checksum):
|
|
52
|
+
return destination
|
|
53
|
+
|
|
54
|
+
if isinstance(source, Path):
|
|
55
|
+
_copy_local_file(source, destination)
|
|
56
|
+
else:
|
|
57
|
+
source_str = str(source)
|
|
58
|
+
if _is_url(source_str):
|
|
59
|
+
download_file(source_str, destination, expected_checksum=expected_checksum)
|
|
60
|
+
else:
|
|
61
|
+
_copy_local_file(Path(source_str), destination)
|
|
62
|
+
|
|
63
|
+
if expected_checksum is not None and not verify_checksum(destination, expected_checksum):
|
|
64
|
+
raise RuntimeError(f"Cached VAD asset checksum mismatch for {destination}.")
|
|
65
|
+
|
|
66
|
+
return destination
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def cache_vad_assets(
|
|
70
|
+
model_name: str,
|
|
71
|
+
*,
|
|
72
|
+
family: str,
|
|
73
|
+
sources: Mapping[str, str | Path | None],
|
|
74
|
+
checksums: Optional[Mapping[str, str]] = None,
|
|
75
|
+
cache_dir: Path,
|
|
76
|
+
force_download: bool = False,
|
|
77
|
+
) -> CachedVadAssets:
|
|
78
|
+
model_dir = cache_dir / "models" / _normalize_model_dir_name(model_name)
|
|
79
|
+
model_dir.mkdir(parents=True, exist_ok=True)
|
|
80
|
+
checksums = checksums or {}
|
|
81
|
+
|
|
82
|
+
files: dict[str, Path] = {}
|
|
83
|
+
for key, source in sources.items():
|
|
84
|
+
if source is None:
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
source_value = Path(source) if isinstance(source, Path) else str(source)
|
|
88
|
+
filename = Path(urlparse(str(source_value)).path).name or Path(str(source_value)).name
|
|
89
|
+
if not filename:
|
|
90
|
+
raise ValueError(f"Could not derive filename for VAD asset {key}.")
|
|
91
|
+
|
|
92
|
+
destination = model_dir / filename
|
|
93
|
+
files[key] = _materialize_asset(
|
|
94
|
+
source_value,
|
|
95
|
+
destination,
|
|
96
|
+
expected_checksum=checksums.get(key),
|
|
97
|
+
force_download=force_download,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
return CachedVadAssets(
|
|
101
|
+
model_name=model_name,
|
|
102
|
+
family=family,
|
|
103
|
+
cache_dir=cache_dir,
|
|
104
|
+
files=files,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def cache_vad_model_assets(
|
|
109
|
+
model_name: str,
|
|
110
|
+
assets: VadModelAssets,
|
|
111
|
+
*,
|
|
112
|
+
cache_dir: Path,
|
|
113
|
+
force_download: bool = False,
|
|
114
|
+
) -> CachedVadAssets:
|
|
115
|
+
return cache_vad_assets(
|
|
116
|
+
model_name,
|
|
117
|
+
family=assets.family,
|
|
118
|
+
sources={
|
|
119
|
+
"model": assets.model,
|
|
120
|
+
},
|
|
121
|
+
checksums={
|
|
122
|
+
key: value
|
|
123
|
+
for key, value in {
|
|
124
|
+
"model": assets.model_checksum,
|
|
125
|
+
}.items()
|
|
126
|
+
if value is not None
|
|
127
|
+
},
|
|
128
|
+
cache_dir=cache_dir,
|
|
129
|
+
force_download=force_download,
|
|
130
|
+
)
|
wfloat/_vad_load.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from ._assets import fetch_vad_assets
|
|
7
|
+
from ._cache import get_default_cache_dir
|
|
8
|
+
from ._core import create_core_vad
|
|
9
|
+
from ._vad import DEFAULT_VAD_SAMPLE_RATE, VadModel
|
|
10
|
+
from ._vad_assets import cache_vad_model_assets
|
|
11
|
+
|
|
12
|
+
DEFAULT_VAD_THRESHOLD = 0.5
|
|
13
|
+
DEFAULT_VAD_MIN_SILENCE_DURATION_SEC = 0.5
|
|
14
|
+
DEFAULT_VAD_MIN_SPEECH_DURATION_SEC = 0.25
|
|
15
|
+
DEFAULT_VAD_MAX_SPEECH_DURATION_SEC = 20.0
|
|
16
|
+
DEFAULT_VAD_BUFFER_SIZE_IN_SECONDS = 30.0
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _finite_float_or_default(value: Optional[float], default: float) -> float:
|
|
20
|
+
if value is None:
|
|
21
|
+
return default
|
|
22
|
+
resolved = float(value)
|
|
23
|
+
if not resolved == resolved or resolved in {float("inf"), float("-inf")}:
|
|
24
|
+
raise ValueError("VAD timing and threshold options must be finite numbers.")
|
|
25
|
+
return resolved
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def load_vad_model(
|
|
29
|
+
model_name: str,
|
|
30
|
+
*,
|
|
31
|
+
cache_dir: Optional[Path] = None,
|
|
32
|
+
force_download: bool = False,
|
|
33
|
+
threshold: Optional[float] = None,
|
|
34
|
+
min_silence_duration_sec: Optional[float] = None,
|
|
35
|
+
min_speech_duration_sec: Optional[float] = None,
|
|
36
|
+
max_speech_duration_sec: Optional[float] = None,
|
|
37
|
+
buffer_size_in_seconds: Optional[float] = None,
|
|
38
|
+
) -> VadModel:
|
|
39
|
+
resolved_cache_dir = Path(cache_dir) if cache_dir is not None else get_default_cache_dir()
|
|
40
|
+
assets = fetch_vad_assets(model_name)
|
|
41
|
+
cached = cache_vad_model_assets(
|
|
42
|
+
model_name,
|
|
43
|
+
assets,
|
|
44
|
+
cache_dir=resolved_cache_dir,
|
|
45
|
+
force_download=force_download,
|
|
46
|
+
)
|
|
47
|
+
family = assets.family
|
|
48
|
+
|
|
49
|
+
native_vad = create_core_vad(
|
|
50
|
+
model_name=model_name,
|
|
51
|
+
family=family,
|
|
52
|
+
model_path=cached.require("model"),
|
|
53
|
+
threshold=_finite_float_or_default(threshold, DEFAULT_VAD_THRESHOLD),
|
|
54
|
+
min_silence_duration_sec=_finite_float_or_default(
|
|
55
|
+
min_silence_duration_sec,
|
|
56
|
+
DEFAULT_VAD_MIN_SILENCE_DURATION_SEC,
|
|
57
|
+
),
|
|
58
|
+
min_speech_duration_sec=_finite_float_or_default(
|
|
59
|
+
min_speech_duration_sec,
|
|
60
|
+
DEFAULT_VAD_MIN_SPEECH_DURATION_SEC,
|
|
61
|
+
),
|
|
62
|
+
max_speech_duration_sec=_finite_float_or_default(
|
|
63
|
+
max_speech_duration_sec,
|
|
64
|
+
DEFAULT_VAD_MAX_SPEECH_DURATION_SEC,
|
|
65
|
+
),
|
|
66
|
+
sample_rate=DEFAULT_VAD_SAMPLE_RATE,
|
|
67
|
+
buffer_size_in_seconds=_finite_float_or_default(
|
|
68
|
+
buffer_size_in_seconds,
|
|
69
|
+
DEFAULT_VAD_BUFFER_SIZE_IN_SECONDS,
|
|
70
|
+
),
|
|
71
|
+
)
|
|
72
|
+
return VadModel(
|
|
73
|
+
model_id=model_name,
|
|
74
|
+
family=family,
|
|
75
|
+
_native_vad=native_vad,
|
|
76
|
+
sample_rate=DEFAULT_VAD_SAMPLE_RATE,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def load_silero_vad(
|
|
81
|
+
*,
|
|
82
|
+
cache_dir: Optional[Path] = None,
|
|
83
|
+
force_download: bool = False,
|
|
84
|
+
threshold: Optional[float] = None,
|
|
85
|
+
min_silence_duration_sec: Optional[float] = None,
|
|
86
|
+
min_speech_duration_sec: Optional[float] = None,
|
|
87
|
+
max_speech_duration_sec: Optional[float] = None,
|
|
88
|
+
) -> VadModel:
|
|
89
|
+
return load_vad_model(
|
|
90
|
+
"snakers4/silero-vad",
|
|
91
|
+
cache_dir=cache_dir,
|
|
92
|
+
force_download=force_download,
|
|
93
|
+
threshold=threshold,
|
|
94
|
+
min_silence_duration_sec=min_silence_duration_sec,
|
|
95
|
+
min_speech_duration_sec=min_speech_duration_sec,
|
|
96
|
+
max_speech_duration_sec=max_speech_duration_sec,
|
|
97
|
+
)
|
wfloat/_version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.0.0"
|
|
Binary file
|