Background2 / apbwe_wrapper.py
carm5333's picture
S208: log real exception in apbwe_wrapper import failure
0f5752a verified
Raw
History Blame Contribute Delete
4.9 kB
"""
apbwe_wrapper.py β€” thin adapter between engine_ihya_v4.py's cascade and the
vendored AP-BWE model (apbwe_vendor/), replacing the dead SIDON slot.
AP-BWE (yxlu-0102/AP-BWE, MIT license): an amplitude+phase prediction
bandwidth-extension model. All-CNN (ConvNeXt blocks), no vocoder/diffusion
stage β€” it predicts wideband magnitude+phase spectra directly from the
narrowband STFT, then ISTFTs back to waveform. No internal language/text
model, nothing resembling word-level generation; the failure mode here is
spectral over/under-smoothing, not word substitution β€” much closer to the
masking-model risk profile than a full resynthesis pipeline.
CONFIG: expects checkpoints/<bucket>/{config.json, g_*} to exist, where
<bucket> matches the bandwidth pair the checkpoint was trained for (e.g.
"8kto48k" β€” Nyquist ~4kHz input, the right bucket for telephone-tier
audio). Checkpoints are NOT in the AP-BWE git repo (only a README pointing
at Google Drive) β€” download the matching folder yourself and place it at
apbwe_vendor/checkpoints/<bucket>/ before deploying.
This module never raises out to its caller β€” every failure path returns
False so the cascade (SIDON skip β†’ here β†’ VoiceFixer β†’ DSP fallback)
degrades gracefully instead of crashing a job.
"""
import os
import glob
import json
try:
import torch
import torchaudio
import torchaudio.functional as aF
import soundfile as sf
from apbwe_vendor.env import AttrDict
from apbwe_vendor.datasets.dataset import amp_pha_stft, amp_pha_istft
from apbwe_vendor.models.model import APNet_BWE_Model
APBWE_DEPS_OK = True
_DEPS_ERROR = None
except Exception as _e:
APBWE_DEPS_OK = False
_DEPS_ERROR = f"{type(_e).__name__}: {_e}"
_CHECKPOINT_BUCKET = os.environ.get("APBWE_CHECKPOINT_BUCKET", "8kto48k")
_BASE = os.path.dirname(os.path.abspath(__file__))
_CHECKPOINT_DIR = os.path.join(_BASE, "apbwe_vendor", "checkpoints", _CHECKPOINT_BUCKET)
_model = None
_h = None
_device = None
_load_error = None
def _load():
"""Lazy singleton load β€” first call pays the cost, every job after
reuses the in-memory model. Records _load_error once so repeated calls
don't re-attempt a load that's already known to fail."""
global _model, _h, _device, _load_error
if _model is not None or _load_error is not None:
return
if not APBWE_DEPS_OK:
_load_error = f"imports failed β€” {_DEPS_ERROR}"
return
config_file = os.path.join(_CHECKPOINT_DIR, "config.json")
if not os.path.isfile(config_file):
_load_error = f"no config.json at {_CHECKPOINT_DIR} β€” checkpoint not deployed"
return
ckpt_candidates = sorted(glob.glob(os.path.join(_CHECKPOINT_DIR, "g_*")))
if not ckpt_candidates:
_load_error = f"no checkpoint (g_*) in {_CHECKPOINT_DIR}"
return
try:
with open(config_file) as f:
_h = AttrDict(json.loads(f.read()))
_device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
_model = APNet_BWE_Model(_h).to(_device)
state = torch.load(ckpt_candidates[-1], map_location=_device)
_model.load_state_dict(state["generator"])
_model.eval()
except Exception as e:
_model = None
_load_error = f"checkpoint/model load failed: {e}"
def restore(wav_in: str, wav_out: str) -> bool:
"""Bandwidth-extend wav_in -> wav_out using AP-BWE. Returns True only
if wav_out was actually written with content; False on any failure
(including 'not deployed yet') so the caller's cascade falls through
to the next stage rather than treating a missing checkpoint as a crash."""
_load()
if _model is None:
if _load_error:
print(f"[apbwe_wrapper] unavailable: {_load_error}")
return False
try:
audio, orig_sr = torchaudio.load(wav_in)
audio = audio.to(_device)
if audio.shape[0] > 1:
audio = audio.mean(dim=0, keepdim=True) # AP-BWE is mono-only
audio_hr = aF.resample(audio, orig_freq=orig_sr, new_freq=_h.hr_sampling_rate)
audio_lr = aF.resample(audio, orig_freq=orig_sr, new_freq=_h.lr_sampling_rate)
audio_lr = aF.resample(audio_lr, orig_freq=_h.lr_sampling_rate, new_freq=_h.hr_sampling_rate)
audio_lr = audio_lr[:, : audio_hr.size(1)]
with torch.no_grad():
amp_nb, pha_nb, _com_nb = amp_pha_stft(audio_lr, _h.n_fft, _h.hop_size, _h.win_size)
amp_wb_g, pha_wb_g, _com_wb_g = _model(amp_nb, pha_nb)
audio_hr_g = amp_pha_istft(amp_wb_g, pha_wb_g, _h.n_fft, _h.hop_size, _h.win_size)
sf.write(wav_out, audio_hr_g.squeeze().cpu().numpy(), _h.hr_sampling_rate, "PCM_16")
return os.path.exists(wav_out) and os.path.getsize(wav_out) > 0
except Exception as e:
print(f"[apbwe_wrapper] restore failed: {e}")
return False