| """ |
| ASVspoof 2019 LA protocol parser. |
| |
| Reads the official .txt protocol files and yields structured Utterance objects |
| that downstream code (datasets, evaluation) can use. |
| |
| Protocol file format (5 space-separated columns): |
| speaker_id utterance_id - attack_id label |
| |
| speaker_id : anonymized speaker (e.g., "LA_0079") |
| utterance_id : filename without extension (e.g., "LA_T_1138215") |
| column 3 : unused, always "-" |
| attack_id : "-" for bonafide, "A01"-"A19" for spoof samples |
| label : "bonafide" or "spoof" |
| """ |
|
|
| from dataclasses import dataclass |
| from typing import List, Dict |
| import os |
|
|
|
|
| @dataclass |
| class Utterance: |
| """One row from an ASVspoof 2019 LA protocol file.""" |
| speaker_id: str |
| utterance_id: str |
| attack_id: str |
| label: str |
| label_int: int |
| flac_path: str |
|
|
|
|
| def parse_protocol(protocol_path: str, audio_root: str) -> List[Utterance]: |
| """Parse one ASVspoof 2019 LA cm protocol file. |
| |
| Args: |
| protocol_path: full path to the .txt protocol file. |
| audio_root: full path to the folder containing the .flac files. |
| |
| Returns: |
| List of Utterance objects, one per valid line. |
| """ |
| utterances: List[Utterance] = [] |
| with open(protocol_path, "r") as f: |
| for line in f: |
| parts = line.strip().split() |
| if len(parts) != 5: |
| continue |
| speaker_id, utt_id, _unused, attack_id, label = parts |
| label_int = 0 if label == "bonafide" else 1 |
| flac_path = os.path.join(audio_root, f"{utt_id}.flac") |
| utterances.append(Utterance( |
| speaker_id=speaker_id, |
| utterance_id=utt_id, |
| attack_id=attack_id, |
| label=label, |
| label_int=label_int, |
| flac_path=flac_path, |
| )) |
| return utterances |
|
|
|
|
| def parse_all_partitions(la_root: str) -> Dict[str, List[Utterance]]: |
| """Parse train, dev, and eval protocols at once. |
| |
| Args: |
| la_root: path to the LA folder, e.g. |
| ".../asvspoof_2019/LA" |
| |
| Returns: |
| Dict with keys "train", "dev", "eval" mapping to lists of Utterances. |
| """ |
| proto_dir = os.path.join(la_root, "ASVspoof2019_LA_cm_protocols") |
| partitions = { |
| "train": ( |
| os.path.join(proto_dir, "ASVspoof2019.LA.cm.train.trn.txt"), |
| os.path.join(la_root, "ASVspoof2019_LA_train", "flac"), |
| ), |
| "dev": ( |
| os.path.join(proto_dir, "ASVspoof2019.LA.cm.dev.trl.txt"), |
| os.path.join(la_root, "ASVspoof2019_LA_dev", "flac"), |
| ), |
| "eval": ( |
| os.path.join(proto_dir, "ASVspoof2019.LA.cm.eval.trl.txt"), |
| os.path.join(la_root, "ASVspoof2019_LA_eval", "flac"), |
| ), |
| } |
| return { |
| name: parse_protocol(proto, audio) |
| for name, (proto, audio) in partitions.items() |
| } |
|
|
|
|
| def class_counts(utterances: List[Utterance]) -> Dict[str, int]: |
| """Return {'bonafide': N, 'spoof': M} counts.""" |
| counts = {"bonafide": 0, "spoof": 0} |
| for u in utterances: |
| counts[u.label] += 1 |
| return counts |
|
|