feat: per-archive license normalization
Per sub-project-2 plan Task 6 / spec §5.3. normalize_license maps CC URLs / identifiers and public-domain markers to the LICENSES vocab, builds non-empty attribution for cc_by/cc_by_nc, and rejects unmappable licenses at ingest. librivox_license() helper returns public_domain. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
"""Normalize per-archive origin license metadata to the hef.catalog LICENSES vocab.
|
||||
|
||||
Maps Creative Commons URLs / identifiers and public-domain markers to
|
||||
{public_domain, cc0, cc_by, cc_by_nc}. For the attribution licenses (cc_by,
|
||||
cc_by_nc) it builds a non-empty attribution string — hef.catalog.validate()
|
||||
rejects those licenses with an empty attribution. Anything unmappable is
|
||||
rejected (a piece whose license can't be established is not ingested).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
|
||||
def _attribution(creator: str, license_name: str, raw: str, default_label: str) -> str:
|
||||
label = license_name or default_label or raw
|
||||
parts = []
|
||||
if creator:
|
||||
parts.append(creator)
|
||||
parts.append(label)
|
||||
return " — ".join(parts)
|
||||
|
||||
|
||||
def normalize_license(raw, *, creator="", license_name=""):
|
||||
"""Return (license, attribution) for an origin license string.
|
||||
|
||||
Raises ValueError when `raw` does not map to an allowed license.
|
||||
"""
|
||||
text = (raw or "").strip().lower()
|
||||
if not text:
|
||||
raise ValueError(f"unmappable license: {raw!r}")
|
||||
|
||||
# CC0 / public-domain dedication (check before the generic publicdomain
|
||||
# markers, since the CC0 URL also contains "publicdomain").
|
||||
if "publicdomain/zero" in text or re.search(r"\bcc[ _-]?0\b", text):
|
||||
return ("cc0", "")
|
||||
|
||||
# Public Domain Mark / explicit public domain / no-known-copyright.
|
||||
pd_markers = (
|
||||
"publicdomain/mark",
|
||||
"public_domain",
|
||||
"public domain",
|
||||
"publicdomain",
|
||||
"no known copyright",
|
||||
"pdm",
|
||||
)
|
||||
if any(m in text for m in pd_markers):
|
||||
return ("public_domain", "")
|
||||
|
||||
# CC BY-NC must be checked before the bare CC BY.
|
||||
if re.search(r"by[ _-]?nc", text):
|
||||
return ("cc_by_nc", _attribution(creator, license_name, raw, "CC BY-NC"))
|
||||
|
||||
# CC BY.
|
||||
if re.search(r"\bcc[ _-]?by\b", text) or "licenses/by" in text:
|
||||
return ("cc_by", _attribution(creator, license_name, raw, "CC BY"))
|
||||
|
||||
raise ValueError(f"unmappable license: {raw!r}")
|
||||
|
||||
|
||||
def librivox_license():
|
||||
"""LibriVox recordings are public domain by charter (attribution not required)."""
|
||||
return ("public_domain", "")
|
||||
Reference in New Issue
Block a user