Add self-contained inference CLI
Browse files- infer_widecodec.py +72 -0
infer_widecodec.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""WideCodec — self-contained inference (44.1 kHz neural codec, depth-20 decoder).
|
| 3 |
+
|
| 4 |
+
Encode -> decode any audio to a 44.1 kHz reconstruction. The encoder ingests mono
|
| 5 |
+
16 kHz; the frozen FSQ codebook (single codebook, 50 tokens/s, ~0.8 kbps) yields the
|
| 6 |
+
code stream; the depth-20 decoder renders it back to 44.1 kHz.
|
| 7 |
+
|
| 8 |
+
This script + the bundled `neucodec/` package are ALL you need — no other source
|
| 9 |
+
repo. Weights (`pytorch_model.bin`) are pulled from the HF model repo on first run.
|
| 10 |
+
|
| 11 |
+
pip install torch transformers huggingface_hub local-attention einops librosa soundfile
|
| 12 |
+
huggingface-cli login # if the repo is private (or export HF_TOKEN=hf_...)
|
| 13 |
+
python infer_widecodec.py --input my.wav --out-dir out
|
| 14 |
+
python infer_widecodec.py --input folder/ --out-dir out # batch a directory
|
| 15 |
+
"""
|
| 16 |
+
import argparse
|
| 17 |
+
import glob
|
| 18 |
+
import os
|
| 19 |
+
import sys
|
| 20 |
+
|
| 21 |
+
# make the bundled `neucodec/` package importable regardless of CWD
|
| 22 |
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
| 23 |
+
|
| 24 |
+
os.environ.setdefault("HF_HUB_DISABLE_XET", "1") # plain HTTPS: Xet can hang on some networks
|
| 25 |
+
|
| 26 |
+
import librosa
|
| 27 |
+
import soundfile as sf
|
| 28 |
+
import torch
|
| 29 |
+
|
| 30 |
+
from neucodec import NeuCodec
|
| 31 |
+
|
| 32 |
+
AUDIO_EXTS = (".wav", ".mp3", ".flac", ".m4a", ".ogg", ".opus")
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def main():
|
| 36 |
+
ap = argparse.ArgumentParser()
|
| 37 |
+
ap.add_argument("--input", required=True, help="audio file OR a directory of audio")
|
| 38 |
+
ap.add_argument("--out-dir", default="out")
|
| 39 |
+
ap.add_argument("--repo", default="Scicom-intl/WideCodec")
|
| 40 |
+
ap.add_argument("--depth", type=int, default=20, help="decoder depth (WideCodec=20)")
|
| 41 |
+
ap.add_argument("--device", default="cuda" if torch.cuda.is_available() else "cpu")
|
| 42 |
+
a = ap.parse_args()
|
| 43 |
+
|
| 44 |
+
os.makedirs(a.out_dir, exist_ok=True)
|
| 45 |
+
print(f"[infer] loading {a.repo} (decoder_depth={a.depth}) on {a.device} …")
|
| 46 |
+
model = NeuCodec._from_pretrained(
|
| 47 |
+
model_id=a.repo, decoder_depth=a.depth, token=os.environ.get("HF_TOKEN")
|
| 48 |
+
).eval().to(a.device)
|
| 49 |
+
sr_out = model.sample_rate
|
| 50 |
+
print(f"[infer] output sample_rate = {sr_out}")
|
| 51 |
+
|
| 52 |
+
if os.path.isdir(a.input):
|
| 53 |
+
files = sorted(f for f in glob.glob(os.path.join(a.input, "*"))
|
| 54 |
+
if f.lower().endswith(AUDIO_EXTS))
|
| 55 |
+
else:
|
| 56 |
+
files = [a.input]
|
| 57 |
+
print(f"[infer] {len(files)} file(s)")
|
| 58 |
+
|
| 59 |
+
for f in files:
|
| 60 |
+
wav16, _ = librosa.load(f, sr=16000, mono=True) # frozen encoder wants 16 kHz mono
|
| 61 |
+
x = torch.from_numpy(wav16).float().view(1, 1, -1).to(a.device)
|
| 62 |
+
with torch.no_grad():
|
| 63 |
+
codes = model.encode_code(x)
|
| 64 |
+
wav = model.decode_code(codes).squeeze().detach().cpu().float().numpy()
|
| 65 |
+
base = os.path.splitext(os.path.basename(f))[0]
|
| 66 |
+
out = os.path.join(a.out_dir, f"{base}_44k.wav")
|
| 67 |
+
sf.write(out, wav, sr_out)
|
| 68 |
+
print(f"[infer] {f} -> {out} ({len(wav) / sr_out:.2f}s @ {sr_out} Hz)")
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
if __name__ == "__main__":
|
| 72 |
+
main()
|