# Copyright (c) 2026 The Anuvani-Diarization authors # SPDX-License-Identifier: MIT OR Apache-2.0 """Add the copyright and SPDX licence lines to every source file of the release (idempotent). python3 tools/licence_headers.py [--check] Covers the git-tracked .py, .rs and .sh files, Dockerfiles, Makefiles and the `diar` command under release/anuvani_diar_rs, release/anuvani_diar_v1 and release/hub. Our files are MIT OR Apache-2.0. Files copied from other projects keep those projects' licences: verbatim copies are left as they are (their own headers say where they come from); copies with small changes get their upstream licence; files that port another project's code say so in a third line (THIRD_PARTY_NOTICES.md has the notices and the licence texts). A shebang or a coding line stays first. With --check, lists the files without a header and exits 1 if any. """ import re import subprocess import sys from pathlib import Path RELEASE = Path(__file__).resolve().parents[2] TREES = ("anuvani_diar_rs", "anuvani_diar_v1", "hub") OURS = "Copyright (c) 2026 The Anuvani-Diarization authors" SPDX = "SPDX-License-Identifier: MIT OR Apache-2.0" VERBATIM = re.compile(r"(nn/seg|hf_port_poc/anuvani_diar_net)/(wav2vec2/.*|conformer\.py|wavlm_config\.py)$") # file suffix -> (copyright line, SPDX line) UPSTREAM = { "python/anuvani_diar/nn/kaldi.py": ("Copyright (c) 2017 Facebook Inc. (Soumith Chintala); copied from torchaudio 2.1.1", "SPDX-License-Identifier: BSD-2-Clause"), "python/anuvani_diar/nn/resample.py": ("Copyright (c) 2017 Facebook Inc. (Soumith Chintala); copied from torchaudio 2.1.1", "SPDX-License-Identifier: BSD-2-Clause"), "python/anuvani_diar/nn/voiceprint.py": ("Copyright (c) 2020 CNRS (pyannote.audio), (c) 2021 Shuai Wang (WeSpeaker); " "changes (c) 2026 The Anuvani-Diarization authors", "SPDX-License-Identifier: MIT AND Apache-2.0"), "nn/seg/segnet.py": ("Copyright (c) 2024 BUT Speech@FIT (DiariZen); changes (c) 2026 The Anuvani-Diarization authors", "SPDX-License-Identifier: MIT"), "hf_port_poc/anuvani_diar_net/segnet.py": ("Copyright (c) 2024 BUT Speech@FIT (DiariZen); changes (c) 2026 The " "Anuvani-Diarization authors", "SPDX-License-Identifier: MIT"), "crates/diar-core/src/vbx.rs": (OURS, "SPDX-License-Identifier: (MIT OR Apache-2.0) AND Apache-2.0"), } # file suffix -> the third line: what it ports PORTS = { "crates/diar-core/src/vbx.rs": "Ports DiariZen's VBx (diarizen/clustering/VBx.py, Apache-2.0, after BUT Speech@FIT's VBx).", "crates/diar-exact/src/hierarchy.rs": "Ports SciPy 1.15.3's hierarchical clustering (BSD-3-Clause).", "crates/diar-exact/src/assign.rs": "Ports SciPy 1.15.3's cdist cosine and linear_sum_assignment (BSD-3-Clause).", "crates/diar-exact/src/special.rs": "Ports SciPy 1.15.3's softmax and logsumexp (BSD-3-Clause).", "crates/diar-exact/src/sort.rs": "Ports NumPy 1.26.4's argsort (BSD-3-Clause).", "crates/diar-exact/src/unique.rs": "Ports NumPy 1.26.4's unique (BSD-3-Clause).", "crates/diar-exact/src/linalg.rs": "Ports NumPy 1.26.4's linalg.norm (BSD-3-Clause).", "crates/diar-exact/src/npblas.rs": "Ports NumPy 1.26.4's BLAS call sites (BSD-3-Clause).", "crates/diar-exact/src/pairwise.rs": "Ports NumPy 1.26.4's pairwise summation and reductions (BSD-3-Clause).", "crates/diar-core/src/recon.rs": "Ports pyannote.audio 3.1.1 and pyannote.core 5.0.0 (MIT, CNRS).", "crates/diar-core/src/front.rs": "Ports pyannote.audio 3.1.1's sliding-window inference and masks (MIT, CNRS).", "crates/diar-audio/src/sndfile.rs": "Reproduces python-soundfile 0.14.0's libsndfile calls (BSD-3-Clause).", } NOTE = "Notices and licence texts: THIRD_PARTY_NOTICES.md." def files(): out = [] for tree in TREES: root = RELEASE / tree listed = subprocess.run(["git", "ls-files", "-z", "."], cwd=root, capture_output=True, check=True).stdout for rel in listed.decode().split("\0"): p = root / rel if rel and p.is_file() and (p.suffix in (".py", ".rs", ".sh") or p.name in ("Dockerfile", "Makefile", "diar")): out.append(p) return sorted(out) def header_for(p): rel = str(p.relative_to(RELEASE)) if VERBATIM.search(rel): return None mark = "//" if p.suffix == ".rs" else "#" copyright_line, spdx = OURS, SPDX for suffix, (c, s) in UPSTREAM.items(): if rel.endswith(suffix): copyright_line, spdx = c, s lines = [f"{mark} {copyright_line}", f"{mark} {spdx}"] for suffix, port in PORTS.items(): if rel.endswith(suffix): lines.append(f"{mark} {port} {NOTE}") return lines def main(): check = "--check" in sys.argv missing, added = [], 0 for p in files(): lines = header_for(p) if lines is None: continue text = p.read_text() if "SPDX-License-Identifier:" in "\n".join(text.split("\n")[:6]): continue if check: missing.append(str(p.relative_to(RELEASE))) continue body = text.split("\n") keep = 0 while keep < len(body) and keep < 2 and (body[keep].startswith("#!") or re.match(r"#.*coding[:=]", body[keep])): keep += 1 p.write_text("\n".join(body[:keep] + lines + body[keep:])) added += 1 if check: print("\n".join(missing) or "every source file has a licence header") return 1 if missing else 0 print(f"headers added to {added} files") return 0 if __name__ == "__main__": sys.exit(main())