Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,26 @@
repos:
- repo: local
hooks:
- id: sync-version
name: Sync version to CITATION.cff and README.md
language: python
entry: python scripts/sync_version.py
pass_filenames: false
always_run: true

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-merge-conflict
- id: check-added-large-files
- id: check-yaml
exclude: mkdocs\.yml
- id: check-json
- id: check-toml
- id: detect-private-key

- repo: https://github.com/psf/black
rev: 25.1.0
hooks:
Expand Down
43 changes: 43 additions & 0 deletions .zenodo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"title": "Jabberjay",
"description": "Jabberjay is a unified Python API and CLI for synthetic voice detection. It brings together state-of-the-art deepfake audio detection models — including ViT, AST, Wav2Vec2, HuBERT, WavLM, RawNet2, and a classical baseline — under a single consistent interface, allowing researchers and practitioners to detect AI-generated speech without wrestling with individual model dependencies and conventions.",
"upload_type": "software",
"license": "MIT",
"creators": [
{
"name": "Boakes, Matthew",
"affiliation": "The Alan Turing Institute, University of Kent",
"orcid": "0000-0002-9377-6240"
}
],
"keywords": [
"synthetic voice detection",
"deepfake detection",
"anti-spoofing",
"audio classification",
"speech",
"ASVspoof",
"machine learning",
"transformers"
],
"related_identifiers": [
{
"identifier": "https://pypi.org/project/Jabberjay/",
"relation": "isSupplementTo",
"resource_type": "software",
"scheme": "url"
},
{
"identifier": "https://mattyb95.github.io/Jabberjay",
"relation": "isDocumentedBy",
"resource_type": "other",
"scheme": "url"
},
{
"identifier": "https://github.com/MattyB95/Jabberjay",
"relation": "isSupplementTo",
"resource_type": "software",
"scheme": "url"
}
]
}
27 changes: 26 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,30 @@ Jabberjay uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

---

## [0.0.8] — 2026-03-16

### Added
- **Zenodo integration** — repository is now archived on Zenodo; concept DOI
(`10.5281/zenodo.19056978`) added to `CITATION.cff`, README badge row, and
BibTeX entry in the README citation section
- **`.zenodo.json`** — explicit Zenodo record metadata (creator ORCID,
affiliation, keywords, related identifiers for PyPI and docs) so archived
records are consistent and complete
- **Pre-commit hooks expanded** — added `trailing-whitespace`, `end-of-file-fixer`,
`check-merge-conflict`, `check-added-large-files`, `check-yaml`, `check-json`,
`check-toml`, and `detect-private-key` from `pre-commit-hooks`; added
`scripts/sync_version.py` local hook to keep `CITATION.cff` and README BibTeX
version in sync with `pyproject.toml` automatically on every commit

### Fixed
- **`RawNet2/run.py`** — YAML config loading now raises a descriptive
`RuntimeError` instead of a raw `OSError`/`YAMLError` if the bundled config
file is missing or malformed
- **`Utilities/label_normalizer.py`** — removed redundant `float(str(...))`
double conversion; score is cast directly with `float()`

---

## [0.0.7] — 2026-03-16

### Added
Expand Down Expand Up @@ -158,7 +182,8 @@ Jabberjay uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- Command-line interface (`jabberjay <audio>`)
- GitHub Actions CI workflow and ruff linting

[0.0.7]: https://github.com/MattyB95/Jabberjay/compare/v0.0.6...HEAD
[0.0.8]: https://github.com/MattyB95/Jabberjay/compare/v0.0.7...v0.0.8
[0.0.7]: https://github.com/MattyB95/Jabberjay/compare/v0.0.6...v0.0.7
[0.0.6]: https://github.com/MattyB95/Jabberjay/compare/v0.0.5...v0.0.6
[0.0.5]: https://github.com/MattyB95/Jabberjay/compare/v0.0.4...v0.0.5
[0.0.4]: https://github.com/MattyB95/Jabberjay/compare/v0.0.3...v0.0.4
Expand Down
6 changes: 5 additions & 1 deletion CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@ authors:
given-names: Matthew
email: Matthew.Boakes@Gmail.com
orcid: "https://orcid.org/0000-0002-9377-6240"
identifiers:
- type: doi
value: "10.5281/zenodo.19056978"
description: "Zenodo concept DOI (resolves to the latest archived version)"
repository-code: "https://github.com/MattyB95/Jabberjay"
url: "https://mattyb95.github.io/Jabberjay"
repository-artifact: "https://pypi.org/project/Jabberjay/"
license: MIT
version: 0.0.7
version: 0.0.8
date-released: "2026-03-16"
keywords:
- synthetic voice detection
Expand Down
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,20 @@

![Jabberjay social preview](social_preview.png)

<div align="center">

[![PyPI](https://img.shields.io/pypi/v/jabberjay)](https://pypi.org/project/Jabberjay/)
[![CI](https://github.com/MattyB95/Jabberjay/actions/workflows/ci.yml/badge.svg)](https://github.com/MattyB95/Jabberjay/actions/workflows/ci.yml)
[![Python](https://img.shields.io/pypi/pyversions/jabberjay)](https://pypi.org/project/Jabberjay/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Downloads](https://img.shields.io/pypi/dm/jabberjay)](https://pypi.org/project/Jabberjay/)
[![Docs](https://img.shields.io/badge/docs-mattyb95.github.io%2FJabberjay-blue)](https://mattyb95.github.io/Jabberjay)
[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.19056978.svg)](https://doi.org/10.5281/zenodo.19056978)
[![GitHub Sponsors](https://img.shields.io/github/sponsors/MattyB95?label=Sponsor&logo=GitHub&color=EA4AAA)](https://github.com/sponsors/MattyB95)
[![Ko-fi](https://img.shields.io/badge/Ko--fi-Support-FF5E5B?logo=ko-fi&logoColor=white)](https://ko-fi.com/mattyb95)

</div>

---

## Why Jabberjay?
Expand Down Expand Up @@ -296,11 +301,12 @@ If you use Jabberjay in your research, please cite it. GitHub's **"Cite this rep
title = {Jabberjay},
year = {2026},
url = {https://github.com/MattyB95/Jabberjay},
version = {0.0.7},
version = {0.0.8},
doi = {10.5281/zenodo.19056978},
}
```

A DOI will be available via [Zenodo](https://zenodo.org) once linked to this repository. The entry above will be updated at that point.
Archived versions are available on [Zenodo](https://doi.org/10.5281/zenodo.19056978). The concept DOI (`10.5281/zenodo.19056978`) always resolves to the latest release.

---

Expand Down
27 changes: 26 additions & 1 deletion docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,30 @@ Jabberjay uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

---

## [0.0.8] — 2026-03-16

### Added
- **Zenodo integration** — repository is now archived on Zenodo; concept DOI
(`10.5281/zenodo.19056978`) added to `CITATION.cff`, README badge row, and
BibTeX entry in the README citation section
- **`.zenodo.json`** — explicit Zenodo record metadata (creator ORCID,
affiliation, keywords, related identifiers for PyPI and docs) so archived
records are consistent and complete
- **Pre-commit hooks expanded** — added `trailing-whitespace`, `end-of-file-fixer`,
`check-merge-conflict`, `check-added-large-files`, `check-yaml`, `check-json`,
`check-toml`, and `detect-private-key` from `pre-commit-hooks`; added
`scripts/sync_version.py` local hook to keep `CITATION.cff` and README BibTeX
version in sync with `pyproject.toml` automatically on every commit

### Fixed
- **`RawNet2/run.py`** — YAML config loading now raises a descriptive
`RuntimeError` instead of a raw `OSError`/`YAMLError` if the bundled config
file is missing or malformed
- **`Utilities/label_normalizer.py`** — removed redundant `float(str(...))`
double conversion; score is cast directly with `float()`

---

## [0.0.7] — 2026-03-16

### Added
Expand Down Expand Up @@ -158,7 +182,8 @@ Jabberjay uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- Command-line interface (`jabberjay <audio>`)
- GitHub Actions CI workflow and ruff linting

[0.0.7]: https://github.com/MattyB95/Jabberjay/compare/v0.0.6...HEAD
[0.0.8]: https://github.com/MattyB95/Jabberjay/compare/v0.0.7...v0.0.8
[0.0.7]: https://github.com/MattyB95/Jabberjay/compare/v0.0.6...v0.0.7
[0.0.6]: https://github.com/MattyB95/Jabberjay/compare/v0.0.5...v0.0.6
[0.0.5]: https://github.com/MattyB95/Jabberjay/compare/v0.0.4...v0.0.5
[0.0.4]: https://github.com/MattyB95/Jabberjay/compare/v0.0.3...v0.0.4
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ build-backend = "hatchling.build"

[project]
name = "Jabberjay"
version = "0.0.7"
version = "0.0.8"
description = "🦜 Synthetic Voice Detection"
readme = "README.md"
requires-python = ">=3.10"
license = "MIT"
license = {text = "MIT"}
authors = [
{ name = "Matthew Boakes", email = "Matthew.Boakes@Gmail.com" },
]
Expand Down Expand Up @@ -101,7 +101,7 @@ exclude_lines = [

[tool.black]
line-length = 88
target-version = ["py310", "py311", "py312", "py313", "py314"]
target-version = ["py310", "py311", "py312", "py313"]

[tool.ruff]
line-length = 88
Expand Down
74 changes: 74 additions & 0 deletions scripts/sync_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/env python3
"""Sync the version from pyproject.toml into CITATION.cff and README.md.

Run automatically as a pre-commit hook, or manually via:
python scripts/sync_version.py
"""

import re
import sys
from datetime import date
from pathlib import Path

ROOT = Path(__file__).parent.parent


def _read(path: Path) -> str:
return path.read_text(encoding="utf-8")


def _write(path: Path, content: str) -> None:
path.write_text(content, encoding="utf-8")


def get_version() -> str:
text = _read(ROOT / "pyproject.toml")
match = re.search(r'^version\s*=\s*"([^"]+)"', text, re.MULTILINE)
if not match:
print("ERROR: Could not find version in pyproject.toml", file=sys.stderr)
sys.exit(1)
return match.group(1)


def sync_citation(version: str) -> bool:
path = ROOT / "CITATION.cff"
original = _read(path)
version_match = re.search(r"^version:\s*(.+)$", original, flags=re.MULTILINE)
if version_match and version_match.group(1).strip() == version:
return False
updated = re.sub(
r"^version:\s*.+$", f"version: {version}", original, flags=re.MULTILINE
)
updated = re.sub(
r"^date-released:\s*.+$",
f'date-released: "{date.today()}"',
updated,
flags=re.MULTILINE,
)
if updated != original:
_write(path, updated)
print(f" updated CITATION.cff → version {version}, date {date.today()}")
return True
return False


def sync_readme(version: str) -> bool:
path = ROOT / "README.md"
original = _read(path)
updated = re.sub(r"( version\s*=\s*\{)[^}]+(\})", rf"\g<1>{version}\2", original)
if updated != original:
_write(path, updated)
print(f" updated README.md → BibTeX version {version}")
return True
return False


if __name__ == "__main__":
version = get_version()
print(f"Syncing version {version} from pyproject.toml...")
changed = any([sync_citation(version), sync_readme(version)])
if changed:
print("Version sync complete — stage the modified files and re-run.")
sys.exit(1) # non-zero tells pre-commit files were modified
else:
print("All version references already in sync.")
9 changes: 2 additions & 7 deletions src/Jabberjay/Models/RawNet2/model_config_RawNet.yaml
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
optimizer: Adam
optimizer: Adam
amsgrad: 1 #for adam optim



#model-related
model:
nb_samp: 64600
first_conv: 1024 # no. of filter coefficients
first_conv: 1024 # no. of filter coefficients
in_channels: 1
filts: [20, [20, 20], [20, 128], [128, 128]] # no. of filters channel in residual blocks
blocks: [2, 4]
nb_fc_node: 1024
gru_node: 1024
nb_gru_layer: 3
nb_classes: 2





7 changes: 5 additions & 2 deletions src/Jabberjay/Models/RawNet2/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@


def predict(y: np.ndarray) -> tuple[Tensor, float]:
with open(_CONFIG_PATH) as f_yaml:
parser = yaml.safe_load(f_yaml)
try:
with open(_CONFIG_PATH) as f_yaml:
parser = yaml.safe_load(f_yaml)
except (OSError, yaml.YAMLError) as exc:
raise RuntimeError(f"Failed to load RawNet2 config: {_CONFIG_PATH}") from exc
device = "cuda" if torch.cuda.is_available() else "cpu"
logger.info(f"Using device: {device}")
model = RawNet(parser["model"], device)
Expand Down
4 changes: 3 additions & 1 deletion src/Jabberjay/Utilities/label_normalizer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Normalise external model labels to the canonical 'Bonafide' / 'Spoof' labels."""

from typing import cast

from Jabberjay.Utilities.types import PredictionScore

# Exact-match only — short strings that would cause false positives in substring search
Expand Down Expand Up @@ -39,7 +41,7 @@ def normalize_pipeline_scores(
return [
PredictionScore(
label=normalize_label(str(s["label"])),
score=float(str(s["score"])),
score=float(cast(float, s["score"])),
)
for s in raw_scores
]
23 changes: 23 additions & 0 deletions tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,29 @@ def test_spoof_prediction(self):
# ---------------------------------------------------------------------------


class TestRawNet2Config:
def test_oserror_raises_runtime_error(self):
from Jabberjay.Models.RawNet2.run import predict

with patch("builtins.open", side_effect=OSError("missing")):
with pytest.raises(RuntimeError, match="Failed to load RawNet2 config"):
predict(y=AUDIO[0])

def test_yaml_error_raises_runtime_error(self):
import yaml

from Jabberjay.Models.RawNet2.run import predict

with patch("builtins.open", mock_open := MagicMock()):
mock_open.return_value.__enter__.return_value = MagicMock()
with patch(
"Jabberjay.Models.RawNet2.run.yaml.safe_load",
side_effect=yaml.YAMLError("bad yaml"),
):
with pytest.raises(RuntimeError, match="Failed to load RawNet2 config"):
predict(y=AUDIO[0])


class TestRawNet2Predict:
def test_returns_prediction_and_confidence(self):
from Jabberjay.Models.RawNet2.run import predict
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading