foley.provenance.disclosure#

Disclosure, watermarking & safety for AI-generated audio (#9b).

The sibling of foley.provenance.credits: where credits renders TASL attribution, this module makes a generated clip **traceable, disclosed, and safe** for a published product (report 07 §6–7):

  • AudioSeal watermark — an imperceptible, detectable mark embedded on every generated clip so downstream systems (and foley itself) can verify it was machine-made. Populates LicenseRecord.watermark. Meta AudioSeal is MIT (code + weights) but a 16 kHz mono speech model used off-label on 44.1 kHz stereo SFX, so the per-clip mark is a soft-binding provenance signal (the achieved detection probability is recorded); C2PA is the hard-binding carrier.

  • Content Credential (C2PA) — #9b writes a portable, self-asserted JSON “content credential” sidecar (a C2PA-shaped assertion dict: the “AI use” action, the training-mining opt-out, and the TASL/license) into a provenance store, and points LicenseRecord.c2pa_manifest_ref at it. A real signed + embedded C2PA manifest (via c2pa-python) over the final mix is a weave/export concern deferred to #8; build_content_credential() is the SSOT dict that step promotes verbatim.

  • EU AI Act Art. 50 — art50_checklist() is a pure reader over the LicenseRecord reporting which transparency obligations a clip has met vs. still pending (deadline 2 Aug 2026).

  • Safety gates — scan_prompt() matches a generation prompt against a registry of trademarked audio logos (THX, NBC chimes, Netflix Ta-dum, …) and recognizable-voice patterns. The generate façade decides on the result (fail-closed refuse by default, or warn-and-flag); this module only detects.

Layering: this module depends only on foley.base / foley.stores (never on foley.sources). The safety-refusal exceptions live in foley.sources.generate (their GenerationError taxonomy home) and are raised there; disclosure returns a PromptScan and the caller decides. The module top level is stdlib-only — audioseal / torch / torchaudio are imported inside the watermark functions (the foley[provenance] extra), so importing this module (and import foley) stays dependency-light.

Module Attributes

DEFAULT_WATERMARK_MESSAGE

The FIXED 16-bit foley provenance id embedded by AudioSeal.

IPTC_TRAINED_ALGORITHMIC_MEDIA

IPTC digitalSourceType vocabulary (C2PA c2pa.actions assertion).

TRADEMARK_REGISTRY

Seed registry of branded audio logos foley must not knowingly generate for commercial use (report 07 §7.2).

Functions

art50_checklist(record)

Return the per-clip EU AI Act Art.

build_content_credential(record, *, asset_id)

Build the portable, self-asserted C2PA-shaped content-credential dict.

detect_watermark(audio_bytes)

Detect a foley watermark in audio_bytes (lazy AudioSeal).

resolve_watermarker(watermark, watermarker)

Resolve the effective Watermarker for a generate call.

scan_prompt(prompt)

Scan a generation prompt for trademarked-audio-logo + voice-clone risk.

write_content_credential(store, asset_id, ...)

Write credential into store keyed by asset_id; return asset_id.

Classes

AudioSealWatermarker(*[, message])

The default Watermarker — Meta AudioSeal, run on CPU, deterministic.

PromptScan([trademark_hits, voice_hits])

The result of scanning a generation prompt for safety flags (pure).

TrademarkEntry(canonical, aliases)

One trademarked audio logo + the casefolded prompt substrings that flag it.

WatermarkResult(audio_bytes, meta[, ...])

The output of a Watermarker: the marked bytes + provenance meta.

Watermarker(*args, **kwargs)

Embeds a detectable provenance watermark into audio bytes (the DI seam).

Exceptions

WatermarkUnavailable

Raised when a watermark is explicitly requested but foley[provenance] is absent.

class foley.provenance.disclosure.AudioSealWatermarker(*, message=61470)[source]#

Bases: object

The default Watermarker — Meta AudioSeal, run on CPU, deterministic.

audioseal / torch / torchaudio are imported lazily inside the methods (the foley[provenance] extra), so importing this class stays dependency-light. Watermarking runs on CPU with a fixed message and eval()/no_grad for cross-machine reproducibility (the dedup invariant). Because AudioSeal is a 16 kHz mono model, 44.1 kHz stereo SFX is watermarked per channel via a 16 kHz round-trip (lossy) — the mark is a soft-binding signal and the achieved detection probability is recorded in the meta.

embed(audio_bytes, *, message=None)[source]#

Embed the AudioSeal watermark; return the marked bytes + meta.

Parameters:
Return type:

WatermarkResult

Returns:

A WatermarkResult whose audio_bytes is lossless WAV (float32) — a deterministic PCM round-trip so its content-hash id is reproducible — and whose meta is the LicenseRecord.watermark dict.

property version: str#

The installed audioseal package version (best-effort).

foley.provenance.disclosure.DEFAULT_WATERMARK_MESSAGE: int = 61470#

The FIXED 16-bit foley provenance id embedded by AudioSeal. It MUST be constant (no per-call nonce / timestamp): a deterministic watermark keeps the stored content-hash id reproducible so a byte-identical regeneration still dedups (skipped_dup) — the #6 generation-flywheel promise. See AudioSealWatermarker.embed().

foley.provenance.disclosure.IPTC_TRAINED_ALGORITHMIC_MEDIA = 'http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia'#

IPTC digitalSourceType vocabulary (C2PA c2pa.actions assertion).

class foley.provenance.disclosure.PromptScan(trademark_hits=(), voice_hits=())[source]#

Bases: object

The result of scanning a generation prompt for safety flags (pure).

property contains_recognizable_voice: bool#

True if the prompt matched a recognizable-voice / clone pattern.

property flagged: bool#

True if the prompt matched any trademark or recognizable-voice pattern.

property potential_trademark: bool#

True if the prompt matched a branded-audio-logo entry.

foley.provenance.disclosure.TRADEMARK_REGISTRY: tuple[TrademarkEntry, ...] = (TrademarkEntry(canonical='THX Deep Note', aliases=frozenset({'deep note', 'thx'})), TrademarkEntry(canonical='NBC chimes', aliases=frozenset({'nbc chimes', 'nbc three-note', 'nbc chime'})), TrademarkEntry(canonical='Netflix Ta-dum', aliases=frozenset({'ta-dum', 'netflix intro', 'tudum', 'netflix chime', 'ta dum', 'netflix sound'})), TrademarkEntry(canonical='MGM lion roar', aliases=frozenset({'metro-goldwyn-mayer lion', 'mgm lion', 'mgm roar'})), TrademarkEntry(canonical='20th Century Fox fanfare', aliases=frozenset({'fox fanfare', '20th century fox fanfare', 'century fox intro'})), TrademarkEntry(canonical='Intel five-note bong', aliases=frozenset({'intel chime', 'intel bong', 'intel jingle', 'intel inside'})), TrademarkEntry(canonical="Homer Simpson D'oh", aliases=frozenset({'homer doh', "d'oh", 'homer simpson doh'})))#

Seed registry of branded audio logos foley must not knowingly generate for commercial use (report 07 §7.2). Each entry maps a canonical mark to a set of casefolded trigger substrings. Extend freely — this is a best-effort aid, not a legal guarantee.

class foley.provenance.disclosure.TrademarkEntry(canonical, aliases)[source]#

Bases: object

One trademarked audio logo + the casefolded prompt substrings that flag it.

class foley.provenance.disclosure.WatermarkResult(audio_bytes, meta, detection_prob=None)[source]#

Bases: object

The output of a Watermarker: the marked bytes + provenance meta.

exception foley.provenance.disclosure.WatermarkUnavailable[source]#

Bases: RuntimeError

Raised when a watermark is explicitly requested but foley[provenance] is absent.

class foley.provenance.disclosure.Watermarker(*args, **kwargs)[source]#

Bases: Protocol

Embeds a detectable provenance watermark into audio bytes (the DI seam).

The default is AudioSealWatermarker; tests inject a deterministic fake — exactly the adapter= / pipeline= seam the source adapters use. embed MUST be deterministic for a fixed message (same input bytes → same output bytes) so the stored content-hash id stays reproducible and the generation-flywheel dedup keeps working.

embed(audio_bytes, *, message=61470)[source]#

Return the watermarked bytes + a provenance meta dict.

Return type:

WatermarkResult

foley.provenance.disclosure.art50_checklist(record)[source]#

Return the per-clip EU AI Act Art. 50 transparency checklist (pure reader).

Reports, per obligation, whether it is required for this clip and whether it has been met — so a caller can see what a publish still needs (e.g. the machine-readable mark is pending when foley[provenance] was absent at generation). A pure LicenseRecord reader (flags never re-derived), mirroring foley.provenance.credits. The render-scoped rollup over a whole mix is a weave/#8 concern.

Parameters:

record (Union[SoundRecord, Candidate, LicenseRecord]) – A SoundRecord, Candidate, or LicenseRecord.

Returns:

{name: {required, met, detail}}, “pending”: […], “publish_ready”: bool, “provenance”: {…}}``.

Return type:

dict

foley.provenance.disclosure.build_content_credential(record, *, asset_id, asset_hash=None)[source]#

Build the portable, self-asserted C2PA-shaped content-credential dict.

A pure reader over the LicenseRecord SSOT (flags never re-derived), mirroring foley.provenance.credits.credit_entry()’s discipline. The manifest sub-object is byte-for-byte the dict a future signer (c2pa.Builder, #8/weave) promotes into a real signed + embedded manifest — so the credential shape can never fork between the generate-time sidecar and the export-time manifest.

Parameters:
Return type:

dict

Returns:

A JSON-serializable content-credential dict (signed/embedded False — it is self-asserted until #8 signs it).

foley.provenance.disclosure.detect_watermark(audio_bytes)[source]#

Detect a foley watermark in audio_bytes (lazy AudioSeal).

Downmixes to mono + resamples to 16 kHz, then runs the AudioSeal detector.

Parameters:

audio_bytes (bytes) – The clip’s container bytes.

Return type:

tuple[float, Optional[int]]

Returns:

(probability, recovered_message) — probability in [0, 1] that a watermark is present, and the recovered 16-bit id (None if the probability is below 0.5).

foley.provenance.disclosure.resolve_watermarker(watermark, watermarker)[source]#

Resolve the effective Watermarker for a generate call.

Progressive disclosure: generation works with or without foley[provenance].

Parameters:
  • watermark (Optional[bool]) – True require a watermark (raise if unavailable), False never watermark, None (auto) watermark iff audioseal is installed.

  • watermarker (Optional[Watermarker]) – An injected watermarker (the DI seam) — wins over auto-detect.

Return type:

Optional[Watermarker]

Returns:

A Watermarker, or None when watermarking is off/unavailable.

Raises:

WatermarkUnavailable – If watermark=True but audioseal is not installed (and no watermarker was injected).

foley.provenance.disclosure.scan_prompt(prompt)[source]#

Scan a generation prompt for trademarked-audio-logo + voice-clone risk.

Pure and stdlib-only (casefold substring + regex). Returns a PromptScan; it never raises and makes no decision — the generate façade decides (fail-closed refuse by default, or warn-and-flag).

Parameters:

prompt (str) – The natural-language generation prompt.

Return type:

PromptScan

Returns:

A PromptScan naming the matched marks / voice patterns.

foley.provenance.disclosure.write_content_credential(store, asset_id, credential)[source]#

Write credential into store keyed by asset_id; return asset_id.

store is any MutableMapping[str, dict] (default: foley.stores.make_provenance_store(), a local JSON-file store; swap for a cloud dol store to move sidecars off-box).

Return type:

str