[依赖、模型和配置]:完成本地语音模型准备,包含.env配置、模型下载脚本和model-check
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
import tempfile
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
||||
SRC_DIR = PROJECT_ROOT / "src"
|
||||
if str(SRC_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(SRC_DIR))
|
||||
|
||||
from owner_voice_pet.speech_models import (
|
||||
DEFAULT_STT_DIR,
|
||||
DEFAULT_STT_URL,
|
||||
DEFAULT_VAD_URL,
|
||||
check_speech_models,
|
||||
write_default_manifest,
|
||||
)
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Download local speech models for owner_voice_pet.")
|
||||
parser.add_argument("--dir", default="models", help="Target model directory. Default: models")
|
||||
parser.add_argument("--force", action="store_true", help="Re-download and replace existing model files")
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
target = Path(args.dir)
|
||||
target.mkdir(parents=True, exist_ok=True)
|
||||
download_vad(target, force=args.force)
|
||||
download_stt(target, force=args.force)
|
||||
manifest = write_default_manifest(target)
|
||||
status = check_speech_models(target, require_sherpa=False)
|
||||
print(f"models_dir={target}")
|
||||
print(f"manifest={manifest}")
|
||||
print(f"missing_files={list(status.missing_files)}")
|
||||
return 0 if not status.missing_files else 1
|
||||
|
||||
|
||||
def download_vad(models_dir: Path, *, force: bool = False) -> Path:
|
||||
output = models_dir / "vad" / "silero_vad.onnx"
|
||||
if output.exists() and not force:
|
||||
print(f"skip existing {output}")
|
||||
return output
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
download_file(DEFAULT_VAD_URL, output)
|
||||
return output
|
||||
|
||||
|
||||
def download_stt(models_dir: Path, *, force: bool = False) -> Path:
|
||||
output_dir = models_dir / "stt" / DEFAULT_STT_DIR
|
||||
if output_dir.exists() and any(output_dir.iterdir()) and not force:
|
||||
print(f"skip existing {output_dir}")
|
||||
return output_dir
|
||||
output_dir.parent.mkdir(parents=True, exist_ok=True)
|
||||
last_error: Exception | None = None
|
||||
for attempt in range(1, 4):
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
archive = Path(tmp) / f"{DEFAULT_STT_DIR}.tar.bz2"
|
||||
try:
|
||||
download_file(DEFAULT_STT_URL, archive)
|
||||
extract_tar_bz2(archive, output_dir.parent)
|
||||
return output_dir
|
||||
except Exception as exc:
|
||||
last_error = exc
|
||||
print(f"download/extract attempt {attempt} failed: {exc}")
|
||||
if output_dir.exists():
|
||||
shutil.rmtree(output_dir)
|
||||
if last_error is not None:
|
||||
raise last_error
|
||||
return output_dir
|
||||
|
||||
|
||||
def download_file(url: str, output: Path) -> None:
|
||||
tmp_output = output.with_suffix(output.suffix + ".part")
|
||||
print(f"download {url}")
|
||||
if shutil.which("curl"):
|
||||
subprocess.run(
|
||||
[
|
||||
"curl",
|
||||
"-L",
|
||||
"--fail",
|
||||
"--retry",
|
||||
"3",
|
||||
"--connect-timeout",
|
||||
"20",
|
||||
"--max-time",
|
||||
"600",
|
||||
"-o",
|
||||
str(tmp_output),
|
||||
url,
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
else:
|
||||
with urllib.request.urlopen(url, timeout=600) as response, tmp_output.open("wb") as handle:
|
||||
shutil.copyfileobj(response, handle)
|
||||
tmp_output.replace(output)
|
||||
print(f"wrote {output} ({output.stat().st_size} bytes)")
|
||||
|
||||
|
||||
def extract_tar_bz2(archive: Path, target_dir: Path) -> None:
|
||||
with tarfile.open(archive, "r:bz2") as tar:
|
||||
members = tar.getmembers()
|
||||
resolved_target = target_dir.resolve()
|
||||
for member in members:
|
||||
destination = (target_dir / member.name).resolve()
|
||||
if resolved_target != destination and resolved_target not in destination.parents:
|
||||
raise RuntimeError(f"refusing unsafe archive member: {member.name}")
|
||||
tar.extractall(target_dir, members=members)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user