foley.sources.base#
The bulk-corpus source contract — a downloaded corpus as an ingestable stream.
foley’s SOURCE stage has two shapes. This module defines the bulk-corpus
shape used by foley.bootstrap.bootstrap() to seed the library from corpora
the user has already downloaded to local disk (FSD50K, Clotho, FoleySet, …). It
is deliberately narrower than the live/HTTP SourceAdapter (search / get /
download / generate) that subtask #5 introduces: a bulk corpus only has to
enumerate its clips and say what each clip’s license is. Everything else —
decode, QC, tag, embed, dedup, the by-value/by-reference storage gate — is reused
verbatim from foley.index.ingest.ingest_one(); adapters never touch the
library.
The two responsibilities of an adapter:
CorpusAdapter.iter_clips()— yield aClipSpecper audio file,
CorpusAdapter.resolve_license()— map a clip’s corpus metadata to a fully-derivedLicenseRecord(via the licensing SSOT, never hand-set flags), settingrights_verifiedTrue only for an authoritatively-recognized license (fail-closed otherwise).
Concrete adapters register themselves in CORPUS_REGISTRY via
register_corpus(); corpora_in_rings() / select_corpora() drive
the ring policy in the bootstrap orchestrator. The registry is a plain dict — no
auto-discovery / SOURCE_CONFIG (that is #5’s concern).
This module is also the home of the live-source contracts (auto-discovered by
foley.sources.registry, not registered here): the retrieve
SourceAdapter (search / get / download — Freesound, #5) and
its sibling generate GenerateAdapter (generate → GeneratedClip
— Stable Audio Open, ElevenLabs, #6). All three adapter kinds share the license
SSOT builders — bulk_license() (bulk), api_license() (retrieve), and
generated_license() (generate) — thin wrappers over _build_license()
so the derived permission flags stay single-sourced.
Module Attributes
Registry of concrete bulk-corpus adapters, keyed by |
Functions
|
Raise |
|
Map foley's unified |
|
A source's API token: |
|
Bind per-request source accounts (a key, and what goes with it) for a |
|
The account bound for |
|
A short, one-way id for an API key: tells accounts apart without keeping the key. |
|
Which account a call to the source is made on: |
|
Build a bulk-acquisition |
|
The |
|
Build a clip's rights record from |
|
Build an API-acquisition |
|
Build an AI-generated |
|
Register |
|
Return the ring of the registered corpus |
|
Registered adapters whose ring is in |
|
Resolve the adapters a bootstrap run should touch. |
Classes
|
One clip inside a local bulk corpus, described but not yet ingested. |
|
A downloaded bulk corpus presented as an ingestable stream of clips. |
|
The live/HTTP source contract (report 10 §4.2): |
|
The generation source contract (report 10 §4.2) — a SIBLING of |
|
One freshly-generated sound: its transient bytes + a provisional candidate. |
|
A bulk corpus where every clip carries the same license. |
Exceptions
A source cannot run until the user configures it: a missing key, plan or endpoint. |
- foley.sources.base.CORPUS_REGISTRY: dict[str, CorpusAdapter] = {'bbc_remarc': UniformCorpus(name='bbc_remarc', ring=2, default_license_id='RemArc', source='bbc_remarc', rights_verified=True, tag_hints_from_path=False), 'clotho': ClothoEvalCorpus(name='clotho', ring=0, default_license_id='CC-BY-4.0', source='clotho', rights_verified=True, tag_hints_from_path=False, include_captions=False), 'foleyset': UniformCorpus(name='foleyset', ring=0, default_license_id='CC-BY-4.0', source='foleyset', rights_verified=True, tag_hints_from_path=True), 'fsd50k': <foley.sources.fsd50k.Fsd50kCorpus object>, 'sonniss': UniformCorpus(name='sonniss', ring=2, default_license_id='Sonniss-GDC', source='sonniss', rights_verified=True, tag_hints_from_path=False)}#
Registry of concrete bulk-corpus adapters, keyed by
adapter.name.
- class foley.sources.base.ClipSpec(path, source_id, meta=<factory>)[source]#
Bases:
objectOne clip inside a local bulk corpus, described but not yet ingested.
- path#
Absolute path to the audio file on disk.
- source_id#
The corpus-native id (e.g. an FSD50K
fname); used only for reporting/provenance — the storedSoundRecord.idis still the content hash minted by the ingest pipeline.
- meta#
Free-form per-clip metadata the adapter carries to
CorpusAdapter.resolve_license()and (optionally) to caption/tag hints — e.g.{"license_id", "creator_name", "source_url", "caption", "tag_hints"}.
- class foley.sources.base.CorpusAdapter(*args, **kwargs)[source]#
Bases:
ProtocolA downloaded bulk corpus presented as an ingestable stream of clips.
Implementations are plain objects (usually a small
@dataclass) carrying three class-level facts (name/ring/default_license_id) and the two methods below. They perform no embedding, storage, or library access.- corpus_dir(data_dir)[source]#
The on-disk root for this corpus under
data_dir(data_dir/name).- Return type:
- class foley.sources.base.GenerateAdapter(*args, **kwargs)[source]#
Bases:
ProtocolThe generation source contract (report 10 §4.2) — a SIBLING of
SourceAdapter.A generate adapter (Stable Audio Open local, ElevenLabs Sound Effects hosted; #6) synthesizes a sound from a prompt. It is a deliberate sibling of the retrieve
SourceAdapterrather than an extra method on it, so thatSourceAdapterstaysruntime_checkablefor the retrieve trio (search/get/download) alone.Like a retrieve adapter, a generate adapter performs no storage or library access: it maps foley’s unified
GENERATION_AFFORDANCES(prompt, duration, prompt_influence, negative_prompt, steps, seed, loop, output_format) to its backend’s native params (viaSOURCE_CONFIG['param_map'], warning-and-dropping unsupported ones), builds the audio + a generatedLicenseRecord, and returns aGeneratedClip. Thefoley.sources.generate.generate()façade converges it on the sharedingest_onepipeline (by-value, operator-consented).
- class foley.sources.base.GeneratedClip(audio_bytes, candidate, notes=<factory>)[source]#
Bases:
objectOne freshly-generated sound: its transient bytes + a provisional candidate.
The return type of a
GenerateAdapter’sgenerate— the envelope that reconciles report 10 §4.2 (generate -> Candidate: retrieval and generation return the same shape) with foley’s two non-negotiables:adapters never touch storage — so the bytes ride with the candidate in an explicit field rather than being written anywhere, and
``ingest_one`` is never forked — the
foley.sources.generate.generate()façade handsaudio_bytesto the one shared pipeline (by-value), exactly asfoley.sources.pull.add_from()does for a retrieved download.
Generation is retrieval’s
search -> Candidateanddownload -> bytesfused into one call, because there is no server to re-fetch the bytes from.Provisional/canonical split — on
candidate.soundonlylicense/caption/tagsare authoritative.id(a discarded"<source>:pending"placeholder),uri,content_sha256,storage_mode,qc,duration_s,sample_rate,channelsand theembedding_*fields are all minted downstream byingest_one()(the façade passessound_id=Noneso the stored id is the decoded-PCM content hash).audio_byteslives ONLY here and is never serialized — aSoundRecordholds auri, never bytes.- audio_bytes#
The generated audio as encoded container bytes (WAV/FLAC/…), transient and in-memory only — consumed once by the ingest pipeline.
- candidate#
The report-10
Candidate(origin=CandidateOrigin.generated) carrying the authoritativeLicenseRecord(is_ai_generated=True+ the generation-provenance block) plus the prompt caption + seed tags.
- notes#
Generation-time messages (
on_unsupported_param='warn'drops, output-format fallbacks, …). Thefoley.sources.generate.generate()façade folds these into the storedIngestResult‘snotesso they surface in the run report.
- class foley.sources.base.SourceAdapter(*args, **kwargs)[source]#
Bases:
ProtocolThe live/HTTP source contract (report 10 §4.2):
search+get+download.A retrieve adapter (Freesound, #5) fetches existing sounds from a service:
searchreturns rankedCandidates,getresolves one id to aSoundRecord, anddownloadreturns the (transient, TOS-permitting) bytes to embed. It returns the SAMECandidate/SoundRecordshapes as the retrieval index, so callers see one uniform interface whether audio is remote, cached, or local.Distinct from (and complementary to) the narrow bulk-corpus
CorpusAdapter: a live adapter does NOT re-implement enrichment or storage — it converges on the samefoley.index.ingest.ingest_one()pipeline viafoley.sources.pull.add_from()(it wraps the corpus machinery, it does not fork it). Generation adapters (#6: Stable Audio Open, ElevenLabs) will add a siblinggeneratesurface; it is intentionally out of scope here so this Protocol stays runtime-checkable for retrieve adapters.- download(source_id)[source]#
Return a sound’s bytes (honoring
cache_bytes_okat the storage gate).- Return type:
- get(source_id)[source]#
Resolve one source id to a metadata
SoundRecord.- Return type:
- exception foley.sources.base.SourceConfigurationError[source]#
Bases:
RuntimeErrorA source cannot run until the user configures it: a missing key, plan or endpoint.
Not transient, so it is never recorded as one failed hit among many: the generate and pull façades let it propagate, and its message names what to set (the env var and, for a key, its sign-up URL) (#64).
- class foley.sources.base.UniformCorpus(name, ring, default_license_id, source, rights_verified=True, tag_hints_from_path=False)[source]#
Bases:
objectA bulk corpus where every clip carries the same license.
Covers FoleySet (CC-BY), Sonniss (Sonniss-GDC), BBC RemArc (RemArc) and any other single-license drop: it walks the audio tree and stamps one license.
rights_verifiedis a constructor field so a corpus whose blanket license is authoritatively known passes the gate, while a merely-assumed one can stay fail-closed. Subclass to enrich per-clipmeta(seeClothoEvalCorpus).- corpus_dir(data_dir)[source]#
data_dir/<name>— the conventional on-disk root for this corpus.- Return type:
- iter_clips(root)[source]#
Yield one
ClipSpecper audio file underroot.When
tag_hints_from_pathis set, the clip’s parent folder names (relative toroot) are carried inmeta['tag_hints']— corpora like FoleySet encode a Foley taxonomy in their directory structure.
- foley.sources.base.api_license(*, source, license_id, rights_verified, overrides=None, source_id=None, source_url=None, license_url=None, creator_name=None, attribution_text=None)[source]#
Build an API-acquisition
LicenseRecord, flags derived.The live-source sibling of
bulk_license()(acquisition_method=api), used by HTTPSourceAdapters (Freesound, …). It exposes theoverridesseam so an adapter can flip an operational flag on top of the per-item copyright license without minting a newlicense_id— the Freesound case: keep the sound’s own CC id (CC0-1.0/CC-BY-4.0/ …) but passoverrides={'cache_bytes_ok': False}because the API TOS forbids caching the bytes even for CC0.redistribute_standalone_ok(copyright) andcache_bytes_ok(TOS) are distinct; only the latter is flipped.- Parameters:
source (
str) – Provenance source tag (e.g.'freesound').license_id (
str) – The per-item normalized license id (a key ofLICENSE_FLAGS).rights_verified (
bool) –Trueonly for an authoritatively-recognized license (fail-closed gate input); MUST beTrueforfoley.keep()to admit the sound.overrides (
Optional[dict]) – Per-source flag overrides applied on top oflicense_id’s row (e.g.{'cache_bytes_ok': False}). Keys must beLicenseFlagsfields (validated byderive_license_flags()).source_id (
Optional[str]) – The source-native id (e.g. a Freesound numeric id), for provenance.source_url (
Optional[str]) – A human-resolvable URL for the item (attribution + the stable by-reference re-fetch handle).license_url (
Optional[str]) – The license URL/label exactly as the source served it.creator_name (
Optional[str]) – The uploader/creator (required for CC-BY attribution).attribution_text (
Optional[str]) – A ready-made attribution string, if supplied.
- Return type:
- Returns:
A populated
LicenseRecordwith its derived flags (+ overrides) applied.
- foley.sources.base.bound_source_account(source)[source]#
The account bound for
sourcein this context, orNonewhen no key is bound.A key bound without its account (
ocracy.kit.using_credentials(<source>=key), or a key rebound over the one an account came with) gives{}: its plan is unknown, and the caller must not fall back to the operator’s.
- foley.sources.base.bulk_license(*, source, license_id, rights_verified, source_id=None, source_url=None, license_url=None, creator_name=None, attribution_text=None)[source]#
Build a bulk-acquisition
LicenseRecord, flags derived.The SSOT license builder every bulk-corpus adapter routes through (
acquisition_method=bulk); a thin wrapper over_build_license(). Its signature has nooverrides(a downloaded corpus is cacheable by-value).- Parameters:
source (
str) – Provenance source tag (e.g.'fsd50k','foleyset').license_id (
str) – The normalized license id (a key ofLICENSE_FLAGS, else it falls back to the all-Falseunknownflags).rights_verified (
bool) – Whether the license is authoritatively known (fail-closed gate input — passFalsefor unrecognized/ambiguous licenses).source_id (
Optional[str]) – The corpus-native clip id, for provenance.source_url (
Optional[str]) – A human-resolvable URL for the clip (attribution/credits).license_url (
Optional[str]) – The licence URL/label exactly as the corpus served it (kept so the credit links the deed the clip was actually released under).creator_name (
Optional[str]) – The uploader/creator (required for CC-BY attribution).attribution_text (
Optional[str]) – A ready-made attribution string, if the corpus supplies one.
- Return type:
- Returns:
A populated
LicenseRecordwith its derived flags applied.
- foley.sources.base.check_prompt_influence(value)[source]#
Raise
ValueErrorunlessvalueisNoneor a real number in0..1.Called when a generation is planned, before any budget is reserved, and again by
native_adherence(). Aboolor a numeric string is refused rather than coerced:Truewould silently mean the maximum.- Return type:
- foley.sources.base.corpora_in_rings(rings)[source]#
Registered adapters whose ring is in
rings(sorted by name).- Return type:
- foley.sources.base.generated_license(*, source, license_id, generator_model, generation_prompt, rights_verified=True, generator_version=None, generation_seed=None, generation_params=None, disclosure_recommended=True, watermark=None, c2pa_manifest_ref=None, overrides=None, source_id=None, source_url=None, license_url=None, creator_name=None, attribution_text=None)[source]#
Build an AI-generated
LicenseRecord, flags derived + provenance stamped.The generation sibling of
bulk_license()/api_license()(acquisition_method=generated): it routes through the shared_build_license()core — so the eight permission flags are DERIVED fromlicense_idbyapply_license_flags(), never hand-set — and then stamps the generation-provenance block in exactly one place, so every generate adapter (Stable Audio Open, ElevenLabs, …) records provenance identically (open-closed).The consequences flow automatically from the two generator rows in
LICENSE_FLAGS:cache_bytes_ok=True(⇒ by-value storage),ai_training_ok=False(the record keeps this restriction — the generate façade consents to embed+persist viaallow_ai_training_forbiddenbut never flips the flag, sofoley.keep()still rejects the sound for anyIntendedUse(will_train=True)), and — forStability-Community—revenue_cap_usd=1_000_000(enforced byfoley.keep()at select time).- Parameters:
source (
str) – The generator tag (e.g.'stable_audio'/'elevenlabs').license_id (
str) – The generator license id ('Stability-Community'/'ElevenLabs-SFX'— a key ofLICENSE_FLAGS).generator_model (
str) – The model identifier (e.g.'stable-audio-open-1.0','eleven_text_to_sound_v2').generation_prompt (
str) – The user prompt, verbatim.rights_verified (
bool) –True(a generator license is authoritatively known); MUST beTrueorfoley.keep()rejects the sound.generator_version (
Optional[str]) – Optional model/version string.generation_seed (
Optional[int]) – The reproducibility seed (anintfor a seeded Stable-Audio-Open run;Nonefor a non-deterministic backend).generation_params (
Optional[dict]) – The RESOLVED NATIVE params actually sent to the backend (e.g.guidance_scale— not the unifiedprompt_influence—audio_end_in_s, …), for reproducibility + audit.disclosure_recommended (
bool) – EU AI Act Art. 50 hint (defaultTrue); makes the credits AI-disclosure line render immediately (#9a already reads it).watermark (
Optional[dict]) – Pass-through carrier for #9b (AudioSeal);Noneuntil then.c2pa_manifest_ref (
Optional[str]) – Pass-through carrier for #9b (C2PA);Noneuntil then.overrides (
Optional[dict]) – Optional per-source flag overrides (rare for generation).source_id (
Optional[str]) – Optional source-native id, for provenance.creator_name (
Optional[str]) – Optional creator (usually unset for generation).attribution_text (
Optional[str]) – Optional ready-made attribution string.
- Return type:
- Returns:
A populated
LicenseRecordwith derived flags applied ANDis_ai_generated=Trueplus the full generation-provenance block.
- foley.sources.base.key_fingerprint(key)[source]#
A short, one-way id for an API key: tells accounts apart without keeping the key.
- Return type:
- foley.sources.base.license_from_clip_meta(source, spec)[source]#
Build a clip’s rights record from
per_clip_license_meta()fields (fail-closed).- Return type:
- foley.sources.base.native_adherence(config, prompt_influence, notes)[source]#
Map foley’s unified
prompt_influence(0..1) onto a source’s native scale.The source declares the mapping on
config["param_map"]["prompt_influence"]:coerce({"kind": "identity"}or{"kind": "linear", "lo": ..., "hi": ...}, 0 ->lo, 1 ->hi) andnative_default(the model’s own value, orNoneto send nothing and let the API apply its default). Native scales differ widely (ElevenLabs 0..1, Stable Audio Open’s CFG 1..15), so one unified default would be wrong for most of them:Nonemeans “the model’s own default”.A source whose
native_defaults["distilled"]is true was post-trained for its native default (e.g. distilled Stable Audio models expect CFG 1), so a requestedprompt_influenceis not applied: the native default is used andnotessays so (#70).- Raises:
ValueError – If
prompt_influenceis outside0..1.- Return type:
- foley.sources.base.per_clip_license_meta(license_url, *, creator_name=None, source_url=None)[source]#
The
ClipSpec.metarights fields for a clip that carries its own CC licence.Shared by the corpora whose clips each keep their Freesound licence (FSD50K, Clotho): the licence string goes through the one CC mapper (
license_id_from_cc_url()— version kept, unknown fails closed), and the string itself is kept aslicense_urlso the credit links the deed the clip was released under. Read back bylicense_from_clip_meta().- Return type:
- foley.sources.base.register_corpus(adapter)[source]#
Register
adapterinCORPUS_REGISTRY(idempotent) and return it.- Raises:
ValueError – If a different adapter is already registered under the name.
- Return type:
- foley.sources.base.resolve_source_key(config, api_key=None)[source]#
A source’s API token:
api_key, else a bound key, else its env var.The chain is the fleet’s facade kit (
ocracy.kit.resolve_credential), so a server can bind a per-request key withocracy.kit.using_credentials(<source>=key).config["auth"]names the env var and the sign-up URL.- Raises:
SourceConfigurationError – Naming the env var and where to get a key.
- Return type:
- foley.sources.base.ring_of(name)[source]#
Return the ring of the registered corpus
name(raisesKeyError).- Return type:
- foley.sources.base.select_corpora(*, rings=(0, 1), corpora=None)[source]#
Resolve the adapters a bootstrap run should touch.
An explicit
corporaallowlist (by name) wins overrings; otherwise every registered adapter in the given rings is selected. Ring 2 is never in the defaultrings— it is opt-in only.- Raises:
KeyError – If a name in
corporais not registered.- Return type:
- foley.sources.base.source_account_id(config, api_key=None)[source]#
Which account a call to the source is made on:
'operator', or a key’s fingerprint.The operator’s account is the source’s env key (or no key at all: a cache entry can then still be replayed offline, and rotating the operator’s key does not orphan the cache). Any other key, given as
api_keyor bound for the request, is another account, told apart bykey_fingerprint()(#98).- Return type:
- foley.sources.base.using_source_accounts(**accounts)[source]#
Bind per-request source accounts (a key, and what goes with it) for a
withblock.The key is bound through
ocracy.kit.using_credentials, soresolve_source_key()uses it; the rest of the account (ElevenLabs: itsplan, which decides the licence) is bound with that key, and only applies while that same key is the one bound (#98):with foley.using_source_accounts( elevenlabs={"key": user_key, "plan": "elevenlabs-free-plan"}, freesound=user_freesound_key, # a string is a key alone ): foley.generate("a door creaks", backend="elevenlabs")
A source given
None(or an empty key) binds nothing, so an optional request header can be passed straight through.The binding lives in a
ContextVar: it covers what runs inside the block, in this context. Consume a lazy result (find(stream=True)) inside the block, and run work handed to a thread pool undercontextvars.copy_context(); outside the block the operator’s key and plan apply again.- Raises:
ValueError – For a field an account cannot carry.