From 3ede71fc912e36c43196d095bfe66a2efe234bda Mon Sep 17 00:00:00 2001 From: Hamilakis Nicolas Date: Thu, 23 Jul 2026 15:01:11 +0200 Subject: [PATCH 1/3] Migrated towards a python module instead of standalone scripts --- .gitmodules | 3 - VTC-2 | 1 - check_sys_dependencies.sh | 7 - pyproject.toml | 19 +- scripts/run.sh => run.sh | 2 +- scripts/infer.py | 328 ------------------ src/vtc_inference/__init__.py | 4 + src/vtc_inference/__main__.py | 4 + src/vtc_inference/cli.py | 103 ++++++ .../vtc_inference/convert_audios.py | 6 +- src/vtc_inference/infer.py | 160 +++++++++ src/vtc_inference/ressources.py | 161 +++++++++ src/vtc_inference/utils.py | 167 +++++++++ thresholds/f1.toml | 15 - thresholds/hp.toml | 15 - uv.lock | 224 +++++++++++- 16 files changed, 834 insertions(+), 385 deletions(-) delete mode 160000 VTC-2 rename scripts/run.sh => run.sh (91%) mode change 100644 => 100755 delete mode 100644 scripts/infer.py create mode 100644 src/vtc_inference/__init__.py create mode 100644 src/vtc_inference/__main__.py create mode 100644 src/vtc_inference/cli.py rename scripts/convert.py => src/vtc_inference/convert_audios.py (97%) create mode 100644 src/vtc_inference/infer.py create mode 100644 src/vtc_inference/ressources.py create mode 100644 src/vtc_inference/utils.py delete mode 100644 thresholds/f1.toml delete mode 100644 thresholds/hp.toml diff --git a/.gitmodules b/.gitmodules index 731f895..e69de29 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "VTC-2"] - path = VTC-2 - url = https://huggingface.co/coml/VTC-2 diff --git a/VTC-2 b/VTC-2 deleted file mode 160000 index 6b1a955..0000000 --- a/VTC-2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6b1a95508302edc14c50f670cd9a30d66fa4f88a diff --git a/check_sys_dependencies.sh b/check_sys_dependencies.sh index abcfa47..71d73c7 100755 --- a/check_sys_dependencies.sh +++ b/check_sys_dependencies.sh @@ -13,13 +13,6 @@ else missing_deps+=("uv") fi -# Check for git-lfs -if command -v git-lfs &> /dev/null; then - echo "✓ git-lfs is installed ($(git-lfs --version))" -else - echo "✗ git-lfs is NOT installed" - missing_deps+=("git-lfs") -fi # Check for ffmpeg if command -v ffmpeg &> /dev/null; then diff --git a/pyproject.toml b/pyproject.toml index 09115da..a6bad66 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,18 +5,33 @@ description = "The inference code for the Voice Type Classifier model" readme = "README.md" requires-python = ">=3.13" dependencies = [ + "huggingface-hub>=0.35.0", "pyannote-audio>=3.4.0", "segma", "tqdm>=4.67.1", ] +[project.scripts] +vtc-infer = "vtc_inference.cli:vtc_infer_cli" +convert-audios = "vtc_inference.convert_audios:convert_audios_cli" + +[project.urls] +documentation = "https://docs.cognitive-ml.fr/fastabx" + [tool.uv.sources] segma = { git = "https://github.com/MarvinLvn/segma", rev = "e6cebc5b232ac1a30554caa7ec35dd22057d07f0" } [dependency-groups] +dev = [ + "ipython>=9.15.0", +] doc = [ "zensical>=0.0.37", ] -[project.urls] -documentation = "https://docs.cognitive-ml.fr/fastabx" +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["src/vtc_inference"] diff --git a/scripts/run.sh b/run.sh old mode 100644 new mode 100755 similarity index 91% rename from scripts/run.sh rename to run.sh index 20737be..16ef56c --- a/scripts/run.sh +++ b/run.sh @@ -6,6 +6,6 @@ output=out/$(date +%Y%m%d_%H%M)_inference_output mkdir -p $output -uv run scripts/infer.py \ +uv run vtc-infer \ --wavs $audios_path \ --output $output diff --git a/scripts/infer.py b/scripts/infer.py deleted file mode 100644 index f343df8..0000000 --- a/scripts/infer.py +++ /dev/null @@ -1,328 +0,0 @@ -import argparse -import copy -import logging -import shutil -from pathlib import Path -from typing import Literal - -import polars as pl -from pyannote.core import Annotation, Segment -from segma.inference import get_list_of_files_to_process, run_inference_on_audios -from segma.utils.io import get_audio_info - -logging.basicConfig( - level=logging.INFO, - format="[%(asctime)s] [%(levelname)s] - %(message)s", - datefmt="%Y.%m.%d %H:%M:%S", -) -logger = logging.getLogger("inference") - - -def load_aa(path: Path): - data = pl.read_csv( - source=path, - has_header=False, - new_columns=("uid", "start_time_s", "duration_s", "label"), - schema={ - "uid": pl.String(), - "start_time_s": pl.Float64(), - "duration_s": pl.Float64(), - "label": pl.String(), - }, - separator=" ", - ) - return data - - -def load_rttm(path: Path | str) -> pl.DataFrame: - try: - data = pl.read_csv( - source=path, - has_header=False, - columns=[1, 3, 4, 7], - new_columns=("uid", "start_time_s", "duration_s", "label"), - schema_overrides={ - "uid": pl.String(), - "start_time_s": pl.Float64(), - "duration_s": pl.Float64(), - "label": pl.String(), - }, - separator=" ", - ) - except pl.exceptions.NoDataError: - data = pl.DataFrame( - None, - { - "uid": pl.String(), - "start_time_s": pl.Float64(), - "duration_s": pl.Float64(), - "label": pl.String(), - }, - ) - - return data - - -def load_one_uri(uri_df: pl.DataFrame): - for uids, turns in uri_df.group_by("uid"): - uid = uids[0] - annotation = Annotation(uri=uid) - for i, turn in enumerate(turns.iter_rows(named=True)): - segment = Segment( - turn["start_time_s"], turn["start_time_s"] + turn["duration_s"] - ) - annotation[segment, i] = turn["label"] - yield uid, annotation - - -def process_annot( - annotation: Annotation, - min_duration_off_s: float = 0.1, - min_duration_on_s: float = 0.1, -) -> Annotation: - """Create a new `Annotation` with the `min_duration_off` and `min_duration_off` rules applied. - - Args: - annotation (Annotation): input annotation - min_duration_off_s (float, optional): Remove speech segments shorter than that many seconds. Defaults to 0.1. - min_duration_on_s (float, optional): Fill same-speaker gaps shorter than that many seconds. Defaults to 0.1. - - Returns: - Annotation: Processed annotation. - """ - active = copy.deepcopy(annotation) - # NOTE - Fill regions shorter than that many seconds. - if min_duration_off_s > 0.0: - active = active.support(collar=min_duration_off_s) - # NOTE - remove regions shorter than that many seconds. - if min_duration_on_s > 0: - for segment, track in list(active.itertracks()): - if segment.duration < min_duration_on_s: - del active[segment, track] - return active - - -def merge_segments( - file_uris_to_merge: list[str], - output: str, - min_duration_on_s: float = 0.1, - min_duration_off_s: float = 0.1, - write_empty: bool = True, -): - output = Path(output) - raw_output_p = output / "raw_rttm" - - # NOTE - merge RTTMs - uri_to_annot: dict[str, Annotation] = {} - uri_to_proc_annot: dict[str, Annotation] = {} - merged_out_p = output / "rttm" - merged_out_p.mkdir(exist_ok=True, parents=True) - - for file_uri in file_uris_to_merge: - file = raw_output_p / f"{file_uri}.rttm" - if not file.exists(): - continue - - match file.suffix: - case ".aa": - data = load_aa(file) - case ".rttm": - data = load_rttm(file) - case _: - raise ValueError( - f"File not found error, extension is not supported: {file}" - ) - - # NOTE - process, should handle the case where a single rttm contains multiple URIS - for uri, annot in load_one_uri(data): - uri_to_annot[uri] = annot - uri_to_proc_annot[uri] = process_annot( - annotation=annot, - min_duration_off_s=min_duration_off_s, - min_duration_on_s=min_duration_on_s, - ) - - for uri, annot in uri_to_proc_annot.items(): - (merged_out_p / f"{uri}.rttm").write_text(annot.to_rttm()) - - # NOTE - Writting missing rttm files - if write_empty: - for uri in set(file_uris_to_merge) - set(uri_to_proc_annot.keys()): - (merged_out_p / f"{uri}.rttm").touch() - - -def check_audio_files(audio_files_to_process: list[Path]) -> None: - """Fails if the audios are not sampled at 16_000 Hz and contain more than one channel.""" - - for wav_p in audio_files_to_process: - info = get_audio_info(wav_p) - # NOTE - check that the audio is valid - if not info.sample_rate == 16_000: - raise ValueError( - f"file `{wav_p}` is not samlped at 16 000 hz. Please convert your audio files." - ) - if not info.n_channels == 1: - raise ValueError( - f"file `{wav_p}` has more than one channel. You can average your channels or use another channel reduction technique." - ) - - -def main( - output: str, - uris: Path | None = None, - config: str = "VTC-2/model/config.toml", - wavs: str = "data/debug/wav", - checkpoint: str = "VTC-2/model/best.ckpt", - save_probs: bool = False, - high_precision: bool = False, - thresholds: None | Path = Path("thresholds/f1.toml"), - min_duration_on_s: float = 0.1, - min_duration_off_s: float = 0.1, - batch_size: int = 128, - stride_pct: float = 0.25, - write_empty: bool = True, - write_csv: bool = True, - recursive_search: bool = False, - device: Literal["gpu", "cuda", "cpu", "mps"] = "gpu", - keep_raw: bool = False, - *args, - **kwargs, -): - if high_precision: - thresholds = Path("thresholds/hp.toml") - - output = Path(output) - output.mkdir(parents=True, exist_ok=True) - - if thresholds: - shutil.copy(str(thresholds), dst=output) - logger.info(f"Using thresholds: {thresholds}") - - logger.info("Running inference on audio files.") - processed_files = run_inference_on_audios( - config=config, - uris=uris, - wavs=wavs, - checkpoint=checkpoint, - output=output, - thresholds=thresholds, - batch_size=batch_size, - device=device, - recursive=recursive_search, - save_probs=save_probs, - stride_pct=stride_pct, - logger=logger, - ) - - logger.info("Merging detected speech segments.") - merge_segments( - file_uris_to_merge=[f.stem for f in processed_files], - output=output, - min_duration_on_s=min_duration_on_s, - min_duration_off_s=min_duration_off_s, - write_empty=write_empty, - ) - - if not keep_raw: - # NOTE - remove /raw_rttm - shutil.rmtree(str(Path(output / "raw_rttm").absolute())) - - # NOTE - write RTTMs to `csv` files - if write_csv: - if keep_raw: - # NOTE - Raw RTTMs - raw_rttm_file_p = sorted(list((output / "raw_rttm").glob("*.rttm"))) - raw_rttm_file_dfs = [] - for rttm_file in raw_rttm_file_p: - raw_rttm_file_dfs.append(load_rttm(rttm_file)) - pl.concat(raw_rttm_file_dfs).write_csv(output / "raw_rttm.csv") - - # NOTE - merged RTTMs - rttm_file_p = sorted(list((output / "rttm").glob("*.rttm"))) - rttm_file_dfs = [] - for rttm_file in rttm_file_p: - rttm_file_dfs.append(load_rttm(rttm_file)) - pl.concat(rttm_file_dfs).write_csv(output / "rttm.csv") - - logger.info(f"Inference finished, files can be found here: '{output.absolute()}/'") - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument( - "--config", - type=str, - default="VTC-2/model/config.toml", - help="Config file to be loaded and used for inference.", - ) - parser.add_argument( - "--uris", help="Path to a file containing the list of uris to use." - ) - parser.add_argument( - "--wavs", - default="data/debug/wav", - help="Folder containing the audio files to run inference on.", - ) - parser.add_argument( - "--checkpoint", - default="VTC-2/model/best.ckpt", - help="Path to a pretrained model checkpoint.", - ) - parser.add_argument( - "--output", - required=True, - help="Output Path to the folder that will contain the final predictions.", - ) - parser.add_argument("--save_probs", action="store_true", - help="Save averaged per-frame probabilities to disk.") - parser.add_argument("--stride_pct", default=0.25, type=float, - help="Sliding window stride as a fraction of chunk_duration_s (0.5 = 50%% overlap, 0.25 = 75%%).") - parser.add_argument( - "--thresholds", - type=Path, - default=Path("thresholds/f1.toml"), - help="If thresholds dict is given, perform predictions using thresholding.", - ) - parser.add_argument( - "--high_precision", - action="store_true", - help="Loads the high precision thresholds, overwrites the `--thresholds` argument.", - ) - parser.add_argument( - "--min_duration_on_s", - default=0.1, - type=float, - help="Remove speech segments shorter than that many seconds.", - ) - parser.add_argument( - "--min_duration_off_s", - default=0.1, - type=float, - help="Fill same-speaker gaps shorter than that many seconds.", - ) - parser.add_argument( - "--batch_size", - default=128, - type=int, - help="Batch size to use for the forward pass of the model.", - ) - parser.add_argument( - "--recursive_search", - action="store_true", - help="Recursively search for `.wav` files. Might be slow. Defaults to False.", - ) - parser.add_argument( - "--device", - default="cuda", - choices=["gpu", "cuda", "cpu", "mps"], - help="Size of the batch used for the forward pass in the model.", - ) - parser.add_argument( - "--keep_raw", - action="store_true", - help="If active, the raw RTTM will be kept and saved to disk in the `/raw_rttm/` folder and a `/raw_rttm.csv` file will be created.", - ) - - args = parser.parse_args() - - main(**vars(args)) diff --git a/src/vtc_inference/__init__.py b/src/vtc_inference/__init__.py new file mode 100644 index 0000000..f937386 --- /dev/null +++ b/src/vtc_inference/__init__.py @@ -0,0 +1,4 @@ +from .infer import VtcInferenceParams, run_vtc_with_params, run_vtc + + +__all__ = ["VtcInferenceParams", "run_vtc_with_params", "run_vtc"] diff --git a/src/vtc_inference/__main__.py b/src/vtc_inference/__main__.py new file mode 100644 index 0000000..d401cb0 --- /dev/null +++ b/src/vtc_inference/__main__.py @@ -0,0 +1,4 @@ +from .cli import vtc_infer_cli + + +vtc_infer_cli() diff --git a/src/vtc_inference/cli.py b/src/vtc_inference/cli.py new file mode 100644 index 0000000..20a70c3 --- /dev/null +++ b/src/vtc_inference/cli.py @@ -0,0 +1,103 @@ +import argparse +from .infer import prepare_vtc_inference, run_vtc_with_params + + +def vtc_infer_cli() -> None: + parser = argparse.ArgumentParser() + parser.add_argument( + "--config", + type=str, + default="2.1", + help="Local config.toml file/directory, or a known model version key (e.g. '2.1', '2.0').", + ) + parser.add_argument( + "--uris", help="Path to a file containing the list of uris to use." + ) + parser.add_argument( + "--wavs", + default="data/debug/wav", + help="Folder containing the audio files to run inference on.", + ) + parser.add_argument( + "--checkpoint", + default="2.1", + type=str, + help="Local checkpoint file/directory, or a known model version key (e.g. '2.1', '2.0').", + ) + parser.add_argument( + "--output", + required=True, + help="Output Path to the folder that will contain the final predictions.", + ) + parser.add_argument( + "--save_probs", + action="store_true", + help="Save averaged per-frame probabilities to disk.", + ) + parser.add_argument( + "--stride_pct", + default=0.25, + type=float, + help="Sliding window stride as a fraction of chunk_duration_s (0.5 = 50%% overlap, 0.25 = 75%%).", + ) + parser.add_argument( + "--thresholds-location", + type=str, + default="2.1", + help=( + "Local directory/file containing the thresholds TOML, or a known " + "model version key. Defaults to wherever --checkpoint resolves to." + ), + ) + parser.add_argument( + "--thresholds-name", + type=str, + default="f1", + help=( + "Thresholds file to use (without extension), e.g. 'f1' -> " + "'/thresholds/f1.toml'. Ignored if --high-precision is set." + ), + ) + parser.add_argument( + "--high-precision", + action="store_true", + help="Use the high-precision thresholds ('hp'), overriding custom thresholds.", + ) + parser.add_argument( + "--min_duration_on_s", + default=0.1, + type=float, + help="Remove speech segments shorter than that many seconds.", + ) + parser.add_argument( + "--min_duration_off_s", + default=0.1, + type=float, + help="Fill same-speaker gaps shorter than that many seconds.", + ) + parser.add_argument( + "--batch_size", + default=128, + type=int, + help="Batch size to use for the forward pass of the model.", + ) + parser.add_argument( + "--recursive_search", + action="store_true", + help="Recursively search for `.wav` files. Might be slow. Defaults to False.", + ) + parser.add_argument( + "--device", + default="cuda", + choices=["gpu", "cuda", "cpu", "mps"], + help="Size of the batch used for the forward pass in the model.", + ) + parser.add_argument( + "--keep_raw", + action="store_true", + help="If active, the raw RTTM will be kept and saved to disk in the `/raw_rttm/` folder and a `/raw_rttm.csv` file will be created.", + ) + + args = parser.parse_args() + params = prepare_vtc_inference(**vars(args)) + run_vtc_with_params(params) diff --git a/scripts/convert.py b/src/vtc_inference/convert_audios.py similarity index 97% rename from scripts/convert.py rename to src/vtc_inference/convert_audios.py index cec6038..3c63a48 100644 --- a/scripts/convert.py +++ b/src/vtc_inference/convert_audios.py @@ -53,7 +53,7 @@ def convert_audios( encoder.to_file(output / audio_p.name) -if __name__ == "__main__": +def convert_audios_cli() -> None: import argparse parser = argparse.ArgumentParser() @@ -75,3 +75,7 @@ def convert_audios( output=args.output, allow_upsampling=args.allow_upsampling, ) + + +if __name__ == "__main__": + convert_audios_cli() diff --git a/src/vtc_inference/infer.py b/src/vtc_inference/infer.py new file mode 100644 index 0000000..ffd2c91 --- /dev/null +++ b/src/vtc_inference/infer.py @@ -0,0 +1,160 @@ +import logging +import shutil +import typing as t +from pathlib import Path + +from segma.inference import run_inference_on_audios + +from .ressources import ( + VersionStr, + resolve_model_config_path, + resolve_model_path, + resolve_thresholds_path, +) +from .utils import merge_segments, write_rttm_csvs + +logging.basicConfig( + level=logging.INFO, + format="[%(asctime)s] [%(levelname)s] - %(message)s", + datefmt="%Y.%m.%d %H:%M:%S", +) +logger = logging.getLogger("inference") + + +class VtcInferenceParams(t.NamedTuple): + """Resolved, ready-to-run parameters for `run_vtc_inference`.""" + + output: Path + config_path: Path + checkpoint_path: Path + thresholds_path: Path | None + uris: Path | None + wavs: str + save_probs: bool + min_duration_on_s: float + min_duration_off_s: float + batch_size: int + stride_pct: float + write_empty: bool + write_csv: bool + recursive_search: bool + device: t.Literal["gpu", "cuda", "cpu", "mps"] + keep_raw: bool + + +def prepare_vtc_inference( + output: str, + uris: Path | None = None, + config: Path | VersionStr = "2.1", + wavs: str = "data/debug/wav", + checkpoint: Path | VersionStr = "2.1", + save_probs: bool = False, + high_precision: bool = False, + thresholds: str | None = "f1", + thresholds_location: Path | VersionStr = "2.1", + min_duration_on_s: float = 0.1, + min_duration_off_s: float = 0.1, + batch_size: int = 128, + stride_pct: float = 0.25, + write_empty: bool = True, + write_csv: bool = True, + recursive_search: bool = False, + device: t.Literal["gpu", "cuda", "cpu", "mps"] = "gpu", + keep_raw: bool = False, +) -> VtcInferenceParams: + """Resolve raw arguments into concrete filesystem paths. + + Resolves `config` via `resolve_model_config_path`, `checkpoint` via + `resolve_model_path`, and the thresholds file (name forced to `"hp"` + when `high_precision` is set) via `resolve_thresholds_path` against + `checkpoint`. Creates `output` and copies the resolved thresholds file + into it. + + Raises: + ModelNotFoundError: if `config`, `checkpoint`, or `thresholds` + cannot be resolved. + """ + + output_dir = Path(output) + output_dir.mkdir(parents=True, exist_ok=True) + + config_path = resolve_model_config_path(config) + checkpoint_path = resolve_model_path(checkpoint) + + # Note: this overrides a custom threshold + thresholds_path: Path | None = None + if high_precision: + thresholds_path = resolve_thresholds_path("hp", "2.1") + else: + location = thresholds_location if thresholds_location is not None else checkpoint + thresholds_name = thresholds if thresholds else "f1" + thresholds_path = resolve_thresholds_path(thresholds_name, location) + + + return VtcInferenceParams( + output=output_dir, + config_path=config_path, + checkpoint_path=checkpoint_path, + thresholds_path=thresholds_path, + uris=uris, + wavs=wavs, + save_probs=save_probs, + min_duration_on_s=min_duration_on_s, + min_duration_off_s=min_duration_off_s, + batch_size=batch_size, + stride_pct=stride_pct, + write_empty=write_empty, + write_csv=write_csv, + recursive_search=recursive_search, + device=device, + keep_raw=keep_raw, + ) + + +def run_vtc_with_params(params: VtcInferenceParams) -> None: + """Run VTC inference and write merged (and optionally raw) RTTM/CSV outputs. + + Raises: + OSError: if raw RTTM cleanup fails. + """ + logger.info("Running inference on audio files.") + processed_files = run_inference_on_audios( + config=params.config_path, + uris=params.uris, + wavs=params.wavs, # pyright: ignore[reportArgumentType] + checkpoint=params.checkpoint_path, + output=params.output, + thresholds=params.thresholds_path, + batch_size=params.batch_size, + device=params.device, + recursive=params.recursive_search, + save_probs=params.save_probs, + stride_pct=params.stride_pct, + logger=logger, + ) + + logger.info("Merging detected speech segments.") + merge_segments( + file_uris_to_merge=[f.stem for f in processed_files], + output=params.output, # pyright: ignore[reportArgumentType] + min_duration_on_s=params.min_duration_on_s, + min_duration_off_s=params.min_duration_off_s, + write_empty=params.write_empty, + ) + + if not params.keep_raw: + # NOTE - remove /raw_rttm + shutil.rmtree(str(Path(params.output / "raw_rttm").absolute())) + + if params.write_csv: + write_rttm_csvs(params.output, keep_raw=params.keep_raw) + + logger.info( + f"Inference finished, files can be found here: '{params.output.absolute()}/'" + ) + + +def run_vtc(*args: t.Any, **kwargs: t.Any) -> None: + """entry point combining argument resolution and inference.""" + params = prepare_vtc_inference(*args, **kwargs) + run_vtc_with_params(params) diff --git a/src/vtc_inference/ressources.py b/src/vtc_inference/ressources.py new file mode 100644 index 0000000..0f3fa92 --- /dev/null +++ b/src/vtc_inference/ressources.py @@ -0,0 +1,161 @@ +import typing as t +from pathlib import Path + +from huggingface_hub import snapshot_download + +VersionStr: t.TypeAlias = str +REPO_ID = "coml/VTC-2" +MODEL_TO_REVISION = { + "2.1": "91167f6", + "2.0": "91e67b5", +} +_WEIGHTS_SUFFIXES = ("*.pt", "*.ckpt") + + +class ModelResolutionError(Exception): + """Base error for model-artifact resolution failures.""" + + +class ModelNotFoundError(ModelResolutionError): + """Raised when a model artifact cannot be resolved from any source.""" + + +def _resolve_named_file(directory_or_file: Path, filename: str) -> Path | None: + """Return `filename`, if `directory_or_file` is that file or a directory containing it.""" + resolved = directory_or_file + if resolved.is_file() and resolved.name == filename: + return directory_or_file + elif resolved.is_dir(): + matches = list(directory_or_file.rglob(filename)) + if len(matches) != 1: + return None + return matches[0] + return None + + +def _resolve_pt_path(directory_or_file: Path) -> Path | None: + """Return the model weights file inside `directory_or_file`, if present. + + Accepts a direct path to a `.pt`/`.ckpt` file, or a directory to search + for one. If multiple candidates are found, a file whose stem is `best` + is preferred; otherwise the first match (sorted) is used. + """ + resolved = directory_or_file + if resolved.is_file() and resolved.suffix in (".pt", ".ckpt"): + return directory_or_file + elif resolved.is_dir(): + matches = sorted( + match + for pattern in _WEIGHTS_SUFFIXES + for match in directory_or_file.rglob(pattern) + ) + if not matches: + return None + for match in matches: + if match.stem == "best": + return match + return matches[0] + return None + + +def _download_repo(model_version: VersionStr) -> Path: + """Fetch (or reuse the cached copy of) the HF Hub snapshot for `model_version`. + + Raises: + ModelNotFoundError: if `model_version` is not a known MODEL_TO_REVISION key. + """ + revision = MODEL_TO_REVISION.get(model_version) + if revision is None: + raise ModelNotFoundError(f"Unknown model version: {model_version!r}") + return Path(snapshot_download(repo_id="coml/VTC-2", revision=revision)) + + +def resolve_model_path(model: Path | VersionStr) -> Path: + """Resolve a model reference to its `.pt | .ckpt` weights file. + + Tries, in order: `model` as a local directory containing a single `.pt | .ckpt` + file; `model` as a string naming such a directory; `model` as a + MODEL_TO_REVISION key, in which case the HF Hub repo is fetched and + `model/best.ckpt` is used. + + Raises: + ModelNotFoundError: if none of the above resolve to an existing file. + """ + if isinstance(model, Path): + pt_file = _resolve_pt_path(model) + if pt_file is None: + raise ModelNotFoundError(f"No single .pt or .ckpt file found in {model}") + return pt_file + + local_dir = Path(model) + if local_dir.is_dir() or local_dir.is_file(): + pt_file = _resolve_pt_path(local_dir) + if pt_file is not None: + return pt_file + + repo_dir = _download_repo(model) + pt_file = repo_dir / "model" / "best.ckpt" + if not pt_file.is_file(): + raise ModelNotFoundError(f"{pt_file} not found in downloaded repo") + return pt_file + + +def resolve_model_config_path(model: Path | VersionStr) -> Path: + """Resolve a model reference to its `config.toml`. + + Tries, in order: `model` as a direct path to `config.toml` or a local + directory containing it; `model` as a string naming the same; `model` + as a MODEL_TO_REVISION key, in which case the HF Hub repo is fetched + and `model/config.toml` is used. + + Raises: + ModelNotFoundError: if none of the above resolve to an existing file. + """ + if isinstance(model, Path): + config_file = _resolve_named_file(model, "config.toml") + if config_file is None: + raise ModelNotFoundError(f"No config.toml found in {model}") + return config_file + + local_path = Path(model) + if local_path.is_dir() or local_path.is_file(): + config_file = _resolve_named_file(local_path, "config.toml") + if config_file is not None: + return config_file + + repo_dir = _download_repo(model) + config_file = repo_dir / "model" / "config.toml" + if not config_file.is_file(): + raise ModelNotFoundError(f"{config_file} not found in downloaded repo") + return config_file + + +def resolve_thresholds_path(name: str, location: Path | VersionStr) -> Path: + """Resolve a thresholds file named `name` relative to `location`. + + Tries, in order: `location` as a direct path to `{name}.toml` or a local + directory containing it; `location` as a string naming the same; + `location` as a MODEL_TO_REVISION key, in which case the HF Hub repo is + fetched and `thresholds/{name}.toml` is used. + + Raises: + ModelNotFoundError: if none of the above resolve to an existing file. + """ + filename = f"{name}.toml" + if isinstance(location, Path): + thresholds_file = _resolve_named_file(location, filename) + if thresholds_file is None: + raise ModelNotFoundError(f"No {filename} found in {location}") + return thresholds_file + + local_path = Path(location) + if local_path.is_dir() or local_path.is_file(): + thresholds_file = _resolve_named_file(local_path, filename) + if thresholds_file is not None: + return thresholds_file + + repo_dir = _download_repo(location) + thresholds_file = repo_dir / "thresholds" / filename + if not thresholds_file.is_file(): + raise ModelNotFoundError(f"{thresholds_file} not found in downloaded repo") + return thresholds_file diff --git a/src/vtc_inference/utils.py b/src/vtc_inference/utils.py new file mode 100644 index 0000000..3a4d084 --- /dev/null +++ b/src/vtc_inference/utils.py @@ -0,0 +1,167 @@ +import copy +from pathlib import Path + +import polars as pl +from pyannote.core import Annotation, Segment +from segma.utils.io import get_audio_info + + +def load_aa(path: Path): + data = pl.read_csv( + source=path, + has_header=False, + new_columns=("uid", "start_time_s", "duration_s", "label"), + schema={ + "uid": pl.String(), + "start_time_s": pl.Float64(), + "duration_s": pl.Float64(), + "label": pl.String(), + }, + separator=" ", + ) + return data + + +def load_rttm(path: Path | str) -> pl.DataFrame: + try: + data = pl.read_csv( + source=path, + has_header=False, + columns=[1, 3, 4, 7], + new_columns=("uid", "start_time_s", "duration_s", "label"), + schema_overrides={ + "uid": pl.String(), + "start_time_s": pl.Float64(), + "duration_s": pl.Float64(), + "label": pl.String(), + }, + separator=" ", + ) + except pl.exceptions.NoDataError: + data = pl.DataFrame( + None, + { + "uid": pl.String(), + "start_time_s": pl.Float64(), + "duration_s": pl.Float64(), + "label": pl.String(), + }, + ) + + return data + + +def load_one_uri(uri_df: pl.DataFrame): + for uids, turns in uri_df.group_by("uid"): + uid = uids[0] + annotation = Annotation(uri=uid) + for i, turn in enumerate(turns.iter_rows(named=True)): + segment = Segment( + turn["start_time_s"], turn["start_time_s"] + turn["duration_s"] + ) + annotation[segment, i] = turn["label"] + yield uid, annotation + + +def process_annot( + annotation: Annotation, + min_duration_off_s: float = 0.1, + min_duration_on_s: float = 0.1, +) -> Annotation: + """Create a new `Annotation` with the `min_duration_off` and `min_duration_off` rules applied. + + Args: + annotation (Annotation): input annotation + min_duration_off_s (float, optional): Remove speech segments shorter than that many seconds. Defaults to 0.1. + min_duration_on_s (float, optional): Fill same-speaker gaps shorter than that many seconds. Defaults to 0.1. + + Returns: + Annotation: Processed annotation. + """ + active = copy.deepcopy(annotation) + # NOTE - Fill regions shorter than that many seconds. + if min_duration_off_s > 0.0: + active = active.support(collar=min_duration_off_s) + # NOTE - remove regions shorter than that many seconds. + if min_duration_on_s > 0: + for segment, track in list(active.itertracks()): + if segment.duration < min_duration_on_s: + del active[segment, track] + return active + + +def merge_segments( + file_uris_to_merge: list[str], + output: str, + min_duration_on_s: float = 0.1, + min_duration_off_s: float = 0.1, + write_empty: bool = True, +): + output = Path(output) + raw_output_p = output / "raw_rttm" + + # NOTE - merge RTTMs + uri_to_annot: dict[str, Annotation] = {} + uri_to_proc_annot: dict[str, Annotation] = {} + merged_out_p = output / "rttm" + merged_out_p.mkdir(exist_ok=True, parents=True) + + for file_uri in file_uris_to_merge: + file = raw_output_p / f"{file_uri}.rttm" + if not file.exists(): + continue + + match file.suffix: + case ".aa": + data = load_aa(file) + case ".rttm": + data = load_rttm(file) + case _: + raise ValueError( + f"File not found error, extension is not supported: {file}" + ) + + # NOTE - process, should handle the case where a single rttm contains multiple URIS + for uri, annot in load_one_uri(data): + uri_to_annot[uri] = annot + uri_to_proc_annot[uri] = process_annot( + annotation=annot, + min_duration_off_s=min_duration_off_s, + min_duration_on_s=min_duration_on_s, + ) + + for uri, annot in uri_to_proc_annot.items(): + (merged_out_p / f"{uri}.rttm").write_text(annot.to_rttm()) + + # NOTE - Writting missing rttm files + if write_empty: + for uri in set(file_uris_to_merge) - set(uri_to_proc_annot.keys()): + (merged_out_p / f"{uri}.rttm").touch() + + +def check_audio_files(audio_files_to_process: list[Path]) -> None: + """Fails if the audios are not sampled at 16_000 Hz and contain more than one channel.""" + + for wav_p in audio_files_to_process: + info = get_audio_info(wav_p) + # NOTE - check that the audio is valid + if not info.sample_rate == 16_000: + raise ValueError( + f"file `{wav_p}` is not samlped at 16 000 hz. Please convert your audio files." + ) + if not info.n_channels == 1: + raise ValueError( + f"file `{wav_p}` has more than one channel. You can average your channels or use another channel reduction technique." + ) + + +def write_rttm_csvs(output: Path, *, keep_raw: bool) -> None: + """Load merged (and optionally raw) RTTM files and write them out as CSVs.""" + if keep_raw: + raw_rttm_files = sorted((output / "raw_rttm").glob("*.rttm")) + raw_rttm_dfs = [load_rttm(f) for f in raw_rttm_files] + pl.concat(raw_rttm_dfs).write_csv(output / "raw_rttm.csv") + + rttm_files = sorted((output / "rttm").glob("*.rttm")) + rttm_dfs = [load_rttm(f) for f in rttm_files] + pl.concat(rttm_dfs).write_csv(output / "rttm.csv") diff --git a/thresholds/f1.toml b/thresholds/f1.toml deleted file mode 100644 index 1af4bf7..0000000 --- a/thresholds/f1.toml +++ /dev/null @@ -1,15 +0,0 @@ -[KCHI] -lower_bound = 0.437 -upper_bound = 1.0 - -[OCH] -lower_bound = 0.378 -upper_bound = 1.0 - -[MAL] -lower_bound = 0.429 -upper_bound = 1.0 - -[FEM] -lower_bound = 0.377 -upper_bound = 1.0 diff --git a/thresholds/hp.toml b/thresholds/hp.toml deleted file mode 100644 index 06cbeed..0000000 --- a/thresholds/hp.toml +++ /dev/null @@ -1,15 +0,0 @@ -[KCHI] -lower_bound = 0.8 -upper_bound = 1.0 - -[OCH] -lower_bound = 0.52 -upper_bound = 1.0 - -[MAL] -lower_bound = 0.75 -upper_bound = 1.0 - -[FEM] -lower_bound = 0.8 -upper_bound = 1.0 diff --git a/uv.lock b/uv.lock index cf1d2bb..3e2fb8f 100644 --- a/uv.lock +++ b/uv.lock @@ -104,6 +104,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c5/7c/83ff6046176a675e6a1e8aeefed8892cd97fe7c46af93cc540d1b24b8323/asteroid_filterbanks-0.4.0-py3-none-any.whl", hash = "sha256:4932ac8b6acc6e08fb87cbe8ece84215b5a74eee284fe83acf3540a72a02eaf5", size = 29912, upload-time = "2021-04-09T20:03:05.817Z" }, ] +[[package]] +name = "asttokens" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/25/1e/faf0f247f6f881b98fc4d6d07e14085cb89d13665084e6d6ac1dc2c03d0b/asttokens-3.0.2.tar.gz", hash = "sha256:3ecdbd8f2cc195f53ccada3a613538bb5f9ef6f6869129f13e03c30a677b8fe2", size = 63136, upload-time = "2026-07-12T03:31:49.084Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/2b/04b8a15f3a1c77bc79ddf5c73875327f34b4fa75982df2b76e45e402d364/asttokens-3.0.2-py3-none-any.whl", hash = "sha256:9da13157f5b28becde0bd374fc677dcd3c290614264eff096f167c469cd9f933", size = 28702, upload-time = "2026-07-12T03:31:47.542Z" }, +] + [[package]] name = "attrs" version = "25.3.0" @@ -295,6 +304,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, ] +[[package]] +name = "decorator" +version = "5.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/60/8b/32f9823da46cde7df2087faa08cd98d01b908f8dcab982cdba9c84e85355/decorator-5.3.1.tar.gz", hash = "sha256:4cbcdd55a6efadb9dbea26b858f4fb3264567b52d69ca0d25b721b553f60ea82", size = 58084, upload-time = "2026-05-18T06:03:28.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/7f/798705f5296a58ca505d600456748d1be48078eac8a7050d8a98bc9edb89/decorator-5.3.1-py3-none-any.whl", hash = "sha256:f47fe6fdbd2edd623ecfe36875d37aba411624e2670dd395dddae1358689bb3c", size = 10365, upload-time = "2026-05-18T06:03:26.517Z" }, +] + [[package]] name = "deepmerge" version = "2.0" @@ -319,6 +337,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/87/62/9773de14fe6c45c23649e98b83231fffd7b9892b6cf863251dc2afa73643/einops-0.8.1-py3-none-any.whl", hash = "sha256:919387eb55330f5757c6bea9165c5ff5cfe63a642682ea788a6d472576d81737", size = 64359, upload-time = "2025-02-09T03:17:01.998Z" }, ] +[[package]] +name = "executing" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, +] + [[package]] name = "filelock" version = "3.19.1" @@ -451,7 +478,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814, upload-time = "2025-08-07T13:15:50.011Z" }, { url = "https://files.pythonhosted.org/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073, upload-time = "2025-08-07T13:42:57.23Z" }, { url = "https://files.pythonhosted.org/packages/f7/0b/bc13f787394920b23073ca3b6c4a7a21396301ed75a655bcb47196b50e6e/greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc", size = 655191, upload-time = "2025-08-07T13:45:29.752Z" }, - { url = "https://files.pythonhosted.org/packages/f2/d6/6adde57d1345a8d0f14d31e4ab9c23cfe8e2cd39c3baf7674b4b0338d266/greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a", size = 649516, upload-time = "2025-08-07T13:53:16.314Z" }, { url = "https://files.pythonhosted.org/packages/7f/3b/3a3328a788d4a473889a2d403199932be55b1b0060f4ddd96ee7cdfcad10/greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504", size = 652169, upload-time = "2025-08-07T13:18:32.861Z" }, { url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" }, { url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" }, @@ -462,7 +488,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/22/5c/85273fd7cc388285632b0498dbbab97596e04b154933dfe0f3e68156c68c/greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", size = 273586, upload-time = "2025-08-07T13:16:08.004Z" }, { url = "https://files.pythonhosted.org/packages/d1/75/10aeeaa3da9332c2e761e4c50d4c3556c21113ee3f0afa2cf5769946f7a3/greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", size = 686346, upload-time = "2025-08-07T13:42:59.944Z" }, { url = "https://files.pythonhosted.org/packages/c0/aa/687d6b12ffb505a4447567d1f3abea23bd20e73a5bed63871178e0831b7a/greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5", size = 699218, upload-time = "2025-08-07T13:45:30.969Z" }, - { url = "https://files.pythonhosted.org/packages/dc/8b/29aae55436521f1d6f8ff4e12fb676f3400de7fcf27fccd1d4d17fd8fecd/greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", size = 694659, upload-time = "2025-08-07T13:53:17.759Z" }, { url = "https://files.pythonhosted.org/packages/92/2e/ea25914b1ebfde93b6fc4ff46d6864564fba59024e928bdc7de475affc25/greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", size = 695355, upload-time = "2025-08-07T13:18:34.517Z" }, { url = "https://files.pythonhosted.org/packages/72/60/fc56c62046ec17f6b0d3060564562c64c862948c9d4bc8aa807cf5bd74f4/greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", size = 657512, upload-time = "2025-08-07T13:18:33.969Z" }, { url = "https://files.pythonhosted.org/packages/23/6e/74407aed965a4ab6ddd93a7ded3180b730d281c77b765788419484cdfeef/greenlet-3.2.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2917bdf657f5859fbf3386b12d68ede4cf1f04c90c3a6bc1f013dd68a22e2269", size = 1612508, upload-time = "2025-11-04T12:42:23.427Z" }, @@ -532,6 +557,52 @@ version = "0.2.7" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/72/84/9f71369fc868dc963ddf51d1bfd8853a9793a37a21c9081a433f6e81d56a/interlap-0.2.7.tar.gz", hash = "sha256:31e4f30c54b067c4939049f5d8131ae5e2fa682ec71aa56f89c0e5b900806ec9", size = 6066, upload-time = "2020-10-02T17:54:06.464Z" } +[[package]] +name = "ipython" +version = "9.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "ipython-pygments-lexers" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "psutil", marker = "sys_platform != 'cygwin' and sys_platform != 'emscripten'" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/59/165d3b4d75cc34add3122c4417ecb229085140ac573103c223cd01dde96f/ipython-9.15.0.tar.gz", hash = "sha256:da2819ce2aa83135257df830660b1176d986c3d2876db24df01974fa955b2756", size = 4442580, upload-time = "2026-06-26T11:03:35.913Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/3a/948263ca3b9d65bb2b1b0c521b3a49fad5d59ada58724bd87d2bd5ff3f36/ipython-9.15.0-py3-none-any.whl", hash = "sha256:515ad9c3cdf0c932a5a9f6245419e8aba706b7bd03c3e1d3a1c83d9351d6aa6e", size = 630895, upload-time = "2026-06-26T11:03:33.809Z" }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, +] + +[[package]] +name = "jedi" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/b7/a3635f6a2d7cf5b5dd98064fc1d5fbbafcb25477bcea204a3a92145d158b/jedi-0.20.0.tar.gz", hash = "sha256:c3f4ccbd276696f4b19c54618d4fb18f9fc24b0aef02acf704b23f487daa1011", size = 3119416, upload-time = "2026-05-01T23:38:47.814Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/93/242e2eab5fe682ffcb8b0084bde703a41d51e17ee0f3a31ff0d9d813620a/jedi-0.20.0-py2.py3-none-any.whl", hash = "sha256:7bdd9c2634f56713299976f4cbd59cb3fa92165cc5e05ea811fb253480728b67", size = 4884812, upload-time = "2026-05-01T23:38:43.919Z" }, +] + [[package]] name = "jinja2" version = "3.1.6" @@ -763,6 +834,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e8/62/aeabeef1a842b6226a30d49dd13e8a7a1e81e9ec98212c0b5169f0a12d83/matplotlib-3.10.6-cp314-cp314t-win_arm64.whl", hash = "sha256:4dd83e029f5b4801eeb87c64efd80e732452781c16a9cf7415b7b63ec8f374d7", size = 8172588, upload-time = "2025-08-30T00:14:11.166Z" }, ] +[[package]] +name = "matplotlib-inline" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/c0/9f7c9a46090390368a4d7bcb76bb87a4a36c421e4c0792cdb53486ffac7a/matplotlib_inline-0.2.2.tar.gz", hash = "sha256:72f3fe8fce36b70d4a5b612f899090cd0401deddc4ea90e1572b9f4bfb058c79", size = 8150, upload-time = "2026-05-08T17:33:33.49Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/09/5b161152e2d90f7b87f781c2e1267494aef9c32498df793f73ad0a0a494a/matplotlib_inline-0.2.2-py3-none-any.whl", hash = "sha256:3c821cf1c209f59fb2d2d64abbf5b23b67bcb2210d663f9918dd851c6da1fcf6", size = 9534, upload-time = "2026-05-08T17:33:32.055Z" }, +] + [[package]] name = "mdurl" version = "0.1.2" @@ -924,7 +1007,7 @@ name = "nvidia-cudnn-cu12" version = "9.10.2.21" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas-cu12", marker = "sys_platform == 'linux'" }, + { name = "nvidia-cublas-cu12" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/ba/51/e123d997aa098c61d029f76663dedbfb9bc8dcf8c60cbd6adbe42f76d049/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8", size = 706758467, upload-time = "2025-06-06T21:54:08.597Z" }, @@ -935,7 +1018,7 @@ name = "nvidia-cufft-cu12" version = "11.3.3.83" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nvjitlink-cu12" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/1f/13/ee4e00f30e676b66ae65b4f08cb5bcbb8392c03f54f2d5413ea99a5d1c80/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74", size = 193118695, upload-time = "2025-03-07T01:45:27.821Z" }, @@ -962,9 +1045,9 @@ name = "nvidia-cusolver-cu12" version = "11.7.3.90" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas-cu12", marker = "sys_platform == 'linux'" }, - { name = "nvidia-cusparse-cu12", marker = "sys_platform == 'linux'" }, - { name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux'" }, + { name = "nvidia-cublas-cu12" }, + { name = "nvidia-cusparse-cu12" }, + { name = "nvidia-nvjitlink-cu12" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/85/48/9a13d2975803e8cf2777d5ed57b87a0b6ca2cc795f9a4f59796a910bfb80/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450", size = 267506905, upload-time = "2025-03-07T01:47:16.273Z" }, @@ -975,7 +1058,7 @@ name = "nvidia-cusparse-cu12" version = "12.5.8.93" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nvjitlink-cu12" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/c2/f5/e1854cb2f2bcd4280c44736c93550cc300ff4b8c95ebe370d0aa7d2b473d/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b", size = 288216466, upload-time = "2025-03-07T01:48:13.779Z" }, @@ -1088,6 +1171,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cd/d7/612123674d7b17cf345aad0a10289b2a384bff404e0463a83c4a3a59d205/pandas-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d2c3554bd31b731cd6490d94a28f3abb8dd770634a9e06eb6d2911b9827db370", size = 13186141, upload-time = "2025-08-21T10:28:05.377Z" }, ] +[[package]] +name = "parso" +version = "0.8.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/4b/90c937815137d43ce71ba043cd3566221e9df6b9c805f24b5d138c9d40a7/parso-0.8.7.tar.gz", hash = "sha256:eaaac4c9fdd5e9e8852dc778d2d7405897ec510f2a298071453e5e3a07914bb1", size = 401824, upload-time = "2026-05-01T23:13:02.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/5d/8268b644392ee874ee82a635cd0df1773de230bde356c38de28e298392cc/parso-0.8.7-py2.py3-none-any.whl", hash = "sha256:a8926eb2a1b915486941fdbd31e86a4baf88fe8c210f25f2f35ecec5b574ca1c", size = 107025, upload-time = "2026-05-01T23:12:58.867Z" }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, +] + [[package]] name = "pillow" version = "11.3.0" @@ -1175,6 +1279,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/74/c1/bb7e334135859c3a92ec399bc89293ea73f28e815e35b43929c8db6af030/primePy-1.3-py3-none-any.whl", hash = "sha256:5ed443718765be9bf7e2ff4c56cdff71b42140a15b39d054f9d99f0009e2317a", size = 4040, upload-time = "2018-05-29T17:18:17.53Z" }, ] +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, +] + [[package]] name = "propcache" version = "0.3.2" @@ -1230,6 +1346,52 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/97/b7/15cc7d93443d6c6a84626ae3258a91f4c6ac8c0edd5df35ea7658f71b79c/protobuf-6.32.1-py3-none-any.whl", hash = "sha256:2601b779fc7d32a866c6b4404f9d42a3f67c5b9f3f15b4db3cccabe06b95c346", size = 169289, upload-time = "2025-09-11T21:38:41.234Z" }, ] +[[package]] +name = "psutil" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, +] + [[package]] name = "pyannote-audio" version = "3.4.0" @@ -1700,7 +1862,7 @@ wheels = [ [[package]] name = "segma" version = "0.1.0" -source = { git = "https://github.com/MarvinLvn/segma?rev=dad876e2268799ff12671a865167126eef66c34c#dad876e2268799ff12671a865167126eef66c34c" } +source = { git = "https://github.com/MarvinLvn/segma?rev=e6cebc5b232ac1a30554caa7ec35dd22057d07f0#e6cebc5b232ac1a30554caa7ec35dd22057d07f0" } dependencies = [ { name = "interlap" }, { name = "lightning" }, @@ -1885,6 +2047,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b8/d9/13bdde6521f322861fab67473cec4b1cc8999f3871953531cf61945fad92/sqlalchemy-2.0.43-py3-none-any.whl", hash = "sha256:1681c21dd2ccee222c2fe0bef671d1aef7c504087c9c4e800371cfcc8ac966fc", size = 1924759, upload-time = "2025-08-11T15:39:53.024Z" }, ] +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, +] + [[package]] name = "sympy" version = "1.14.0" @@ -2143,6 +2319,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, ] +[[package]] +name = "traitlets" +version = "5.15.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/a9/a2584b8313b89f94869ddb3c4074617a691de1812a614d2d50e32ca5a7a6/traitlets-5.15.1.tar.gz", hash = "sha256:7b1c07854fe25acb39e009bae49f11b79ff6cbb2f27999104e9110e7a6b53722", size = 163344, upload-time = "2026-06-03T12:26:06.181Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/8d/1080ee4c231f361b6ce4470d556c8c435b67c7e0753aaa641497ee92f88b/traitlets-5.15.1-py3-none-any.whl", hash = "sha256:770a53705f84b81ac107e83a1b3328ff2dae16094d8fc3cfc004e4b22dfd8e92", size = 85858, upload-time = "2026-06-03T12:26:04.395Z" }, +] + [[package]] name = "transformers" version = "4.56.2" @@ -2231,27 +2416,33 @@ wheels = [ [[package]] name = "vtc" -version = "2.1.0" -source = { virtual = "." } +version = "2.2.0" +source = { editable = "." } dependencies = [ + { name = "huggingface-hub" }, { name = "pyannote-audio" }, { name = "segma" }, { name = "tqdm" }, ] [package.dev-dependencies] +dev = [ + { name = "ipython" }, +] doc = [ { name = "zensical" }, ] [package.metadata] requires-dist = [ + { name = "huggingface-hub", specifier = ">=0.35.0" }, { name = "pyannote-audio", specifier = ">=3.4.0" }, - { name = "segma", git = "https://github.com/MarvinLvn/segma?rev=dad876e2268799ff12671a865167126eef66c34c" }, + { name = "segma", git = "https://github.com/MarvinLvn/segma?rev=e6cebc5b232ac1a30554caa7ec35dd22057d07f0" }, { name = "tqdm", specifier = ">=4.67.1" }, ] [package.metadata.requires-dev] +dev = [{ name = "ipython", specifier = ">=9.15.0" }] doc = [{ name = "zensical", specifier = ">=0.0.37" }] [[package]] @@ -2282,6 +2473,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3c/c9/d9f0c7b8a743af589e694ce8fec8e6cffa46873179912d4ed4f992d08381/wandb-0.22.0-py3-none-win_amd64.whl", hash = "sha256:53ba0fa048b766c1aa44592f1e530fb7eead7749089a66c3892b35f153a8d8bd", size = 18742399, upload-time = "2025-09-18T19:13:19.26Z" }, ] +[[package]] +name = "wcwidth" +version = "0.8.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/74/c6428f875774288bec1396f5bfcbc2d925700a4dad61727fd5f2b12f249d/wcwidth-0.8.2.tar.gz", hash = "sha256:91fbef97204b96a3d4d421609b80340b760cf33e26da123ff243d76b1fda8dda", size = 1466253, upload-time = "2026-06-29T18:11:11.601Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/42/3e5985a0a7e57de470b320c6d6a1a67c844f6737a587f3d44dd13d1819e7/wcwidth-0.8.2-py3-none-any.whl", hash = "sha256:d63947694a0539a1d51e01eda7caf800c291020e6cdd7e28ad7b14dd33ad4f85", size = 323166, upload-time = "2026-06-29T18:11:09.888Z" }, +] + [[package]] name = "yarl" version = "1.20.1" From 714d079982a775a2c11814bd1f642419c7d29d82 Mon Sep 17 00:00:00 2001 From: Hamilakis Nicolas Date: Thu, 23 Jul 2026 15:04:49 +0200 Subject: [PATCH 2/3] Updated readme --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d582dd3..e989efd 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,7 @@ As system dependencies, ensure that [uv](https://docs.astral.sh/uv/), [ffmpeg](h You can now clone the repo with: ```bash -git lfs install -git clone --recurse-submodules https://github.com/LAAC-LSCP/VTC.git +git clone https://github.com/LAAC-LSCP/VTC.git cd VTC ``` @@ -48,7 +47,7 @@ If not, you can use the `scripts/convert.py` file to convert your audios to 16 0 ```bash -uv run scripts/infer.py \ +uv run vtc-infer \ --wavs audios \ # path to the folder containing the audio files --output predictions \ # output folder --device cpu # device to run the model on: ('cpu', 'cuda' or 'gpu', 'mps') @@ -66,10 +65,10 @@ The model outputs are saved to `/` with the following structure: ``` #### Helper script -An example of a bash script is given to perform inference in `scripts/run.sh`. Simply set the correct variables in the script and run it: +An example of a bash script is given to perform inference in `run.sh`. Simply set the correct variables in the script and run it: ```bash -sh scripts/run.sh +sh run.sh ```` ## 3. Model Performance From e8e819f781e4a25b1f24859d9af5d73f9e5e8df5 Mon Sep 17 00:00:00 2001 From: Hamilakis Nicolas Date: Thu, 23 Jul 2026 15:07:32 +0200 Subject: [PATCH 3/3] fix version typo --- src/vtc_inference/cli.py | 6 +++--- src/vtc_inference/ressources.py | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vtc_inference/cli.py b/src/vtc_inference/cli.py index 20a70c3..f0dd5e9 100644 --- a/src/vtc_inference/cli.py +++ b/src/vtc_inference/cli.py @@ -7,7 +7,7 @@ def vtc_infer_cli() -> None: parser.add_argument( "--config", type=str, - default="2.1", + default="2.2", help="Local config.toml file/directory, or a known model version key (e.g. '2.1', '2.0').", ) parser.add_argument( @@ -20,7 +20,7 @@ def vtc_infer_cli() -> None: ) parser.add_argument( "--checkpoint", - default="2.1", + default="2.2", type=str, help="Local checkpoint file/directory, or a known model version key (e.g. '2.1', '2.0').", ) @@ -43,7 +43,7 @@ def vtc_infer_cli() -> None: parser.add_argument( "--thresholds-location", type=str, - default="2.1", + default="2.2", help=( "Local directory/file containing the thresholds TOML, or a known " "model version key. Defaults to wherever --checkpoint resolves to." diff --git a/src/vtc_inference/ressources.py b/src/vtc_inference/ressources.py index 0f3fa92..ce092bb 100644 --- a/src/vtc_inference/ressources.py +++ b/src/vtc_inference/ressources.py @@ -6,6 +6,7 @@ VersionStr: t.TypeAlias = str REPO_ID = "coml/VTC-2" MODEL_TO_REVISION = { + "2.2": "91167f6", "2.1": "91167f6", "2.0": "91e67b5", }