File size: 5,811 Bytes
63dd946
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# 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())