voxtream2-ru / demo /voxtream /utils /dataset /speaker_encoder.py
simba9's picture
demo: self-contained Gradio app (run locally)
2a076e4 verified
Raw
History Blame Contribute Delete
4.36 kB
import argparse
import warnings
from pathlib import Path
import numpy as np
import pandas as pd
import torch
import torchaudio
from torch.utils.data import DataLoader, Dataset
from torchaudio.transforms import Resample
from tqdm.auto import tqdm
warnings.filterwarnings("ignore")
class AudioDataset(Dataset):
def __init__(
self,
meta_path: str,
sample_rate: int = 16000,
target_length_sec: int = 3,
sep: str = "^",
):
self.meta = pd.read_parquet(meta_path).paths.values
self.sample_rate = sample_rate
self.target_length = target_length_sec * sample_rate
self.sep = sep
def __len__(self):
return len(self.meta)
def __getitem__(self, idx):
paths = self.meta[idx]
path = paths.split(self.sep)[0]
try:
waveform, orig_sr = torchaudio.load(path)
except Exception:
return torch.zeros(1, self.target_length)
orig_sr = orig_sr if isinstance(orig_sr, int) else self.sample_rate
# Mono conversion (некоторые файлы стерео)
if waveform.shape[0] > 1:
waveform = waveform.mean(dim=0, keepdim=True)
# Resample if necessary
if orig_sr != self.sample_rate:
resampler = Resample(orig_sr, self.sample_rate)
waveform = resampler(waveform)
# Tile signal to target length
if waveform.shape[1] < self.target_length:
zero_pad = torch.zeros((1, self.target_length - waveform.shape[1]))
waveform = torch.cat((waveform, zero_pad), dim=1)
# Crop to match target length
waveform = waveform[:, : self.target_length]
return waveform
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"-m",
"--meta-path",
type=str,
help="Path to metadata file",
)
parser.add_argument(
"-o",
"--output-dir",
type=str,
help="Path to output directory",
)
parser.add_argument(
"-s", "--sep", type=str, help="Separator used in file names", default="^"
)
parser.add_argument("-bs", "--batch-size", type=int, help="Batch size", default=64)
parser.add_argument(
"-w", "--num-workers", type=int, help="Number of workers", default=4
)
parser.add_argument(
"-tl",
"--target-length-sec",
type=int,
help="Target length in seconds",
default=3,
)
parser.add_argument("-g", "--gpu", type=int, help="GPU ID", default=0)
parser.add_argument("-ch", "--chunk", type=int, help="Chunk ID", default=None)
args = parser.parse_args()
meta_path = args.meta_path
filename = "spk_templates.npy"
if args.chunk is not None:
meta_path = meta_path.replace(".parquet", f"_chunk{args.chunk}.parquet")
filename = filename.replace(".npy", f"_chunk{args.chunk}.npy")
dataset = AudioDataset(
meta_path=meta_path,
target_length_sec=args.target_length_sec,
sep=args.sep,
)
dataloader = DataLoader(
dataset,
batch_size=args.batch_size,
shuffle=False,
num_workers=args.num_workers,
)
device = f"cuda:{args.gpu}" if torch.cuda.is_available() else "cpu"
model = (
torch.hub.load(
"IDRnD/ReDimNet",
"ReDimNet",
model_name="M",
train_type="ft_mix",
dataset="vb2+vox2+cnc",
trust_repo=True,
verbose=False,
)
.to(device)
.half()
)
model.spec.float()
model.bn.float()
model.eval()
all_embeddings = []
for waveforms in tqdm(dataloader):
waveforms = waveforms.to(device).half()
with torch.no_grad(), torch.autocast(device_type=device, dtype=torch.float16):
embs = model(waveforms)
all_embeddings.append(embs.cpu().numpy())
all_embeddings = np.concatenate(all_embeddings, axis=0)
all_embeddings /= np.linalg.norm(all_embeddings, axis=1, keepdims=True)
assert (
np.dot(all_embeddings[:10], all_embeddings[:10].T).max() < 1.02
), "Embeddings are not normalized properly."
output_dir = Path(args.output_dir)
output_dir.mkdir(exist_ok=True, parents=True)
np.save(output_dir / filename, all_embeddings)