A new section on measurement-derived tags, the third kind after the creator's claims and a review model's. "Is there a binaural beat in this file" is arithmetic, and a rule that can answer it skips the model and the adjudicator both, because there is no opinion to rule on. What it must not skip is the registry: a tag with no definition is a word nobody can browse by and nobody can argue with. Three properties keep that honest, each written down with the failure it prevents. Every rule names the guard that makes its instrument trustworthy and declines rather than guessing when the guard fails. The ruleset is versioned and the version stamped beside the tags it wrote, because moving a threshold leaves behind tags that were right under the old table and nothing else will find them again. And a measurement that contradicts a claim is recorded as a dispute rather than overwriting it, aggregated per creator -- one file is an error and forty is a habit, and a creator who labels tone tracks "binaural" with no measurable beat is telling you something about the rest of their metadata that is worth more than the correction. Three ways the pass fails while appearing to work: - **Half the thresholds are judgements rather than instrument guards, and those do not travel.** "Markedly slower than conversation" is 70 words a minute against most speech; against a library of hypnosis whose median is 94 it described 21% of the collection, which is not describing anything. At 55 it picks out 909 recordings instead of 1,933. Neither number is wrong; only one of them is about that library. A threshold near the median of your material distinguishes nothing, and the way to find that out is to look before applying. - **A claim is only ever disputed by a rule awarding the same string.** A ruleset spelling it `Whispered` cannot contradict one of the 1,930 entries claiming `Whispers`, so the mechanism ran only on the two rules whose names happened to match already: 499 disputes, all of them one of two tags. Mapping three names onto the library's own spellings took it to 1,849 and dropped the additions from 2,412 to 1,922, five hundred of them tags the entries already carried. `measured.tags` in `inductor.yaml` is where that mapping goes; an empty value switches a rule off. - **A rule gated on a field most of the library lacks awards nothing and says so nowhere.** 79 transcripts out of 10,755 carried the `speech` verdict, so the rule for "no words in this at all" could never fire on a collection holding hundreds of wordless recordings, and the only sign was a tag missing from the tally. Count how many entries have a field before reading anything into a zero. And a line on what such a rule may name at all: pitch is measurable and pitch is still not gender. `inductor.yaml` carries the whole `measured` block commented out -- the name mapping, the cap, and every number the rules compare against -- and `content/README.md` documents `provenance.measured_tags`, which records the ruleset version and the thresholds the library moved. The version answers "which code wrote this" and cannot answer "under which numbers" once the numbers live in the config. **A recording with no words in it cannot be keyed by its transcript.** Analysis and review cache against a hash of the transcript, which is the right key while there is one. Record that a file has no speech and its text is empty, so every wordless recording hashes to the same string -- and keyed on that they do not share a cache, they share an *answer*. One library had 124 entries keyed that way holding 28 write-ups between them, 97 of them the same one: a description of a fifteen-second clip of room tone and camera shutters. Nothing reports it, and it bites hardest on the one prompt built entirely from what the audio was measured and heard to be, which ran once for the whole collection. **Moving a library breaks its symlinks, and only the site build notices.** The relative-paths convention covered the references inside the documents and said nothing about the links on disk, which is the half a move actually breaks: `inductor` never reads them, so `check` and `orphans` are both satisfied, while `hypnotica build` reports artwork it cannot find and carries on. `paths --write` now relinks them too, matching the longest tail of each dead target against the tree and reporting what it cannot account for rather than pointing it somewhere new. `media/README.md` has the procedure. `state/README.md` gains `sync/`, the sync endpoint's store where one is run. It is the only directory in the library that nothing on the machine can read -- the blobs are encrypted in the browser under a key the server is never given -- which makes `hypnotica sync --rm` the only safe way to delete from it, and makes it neither a backup of what the browsers hold nor a way back in. Losing every device loses the key; the export file is the recovery path. **The maintenance commands are opt-in through `--write`, and that is load-bearing.** `run` and `ingest` write as their whole purpose and take `--dry-run` to hold off; everything that sweeps the library to correct it reports by default and changes nothing until asked. One of them shipped reading `--dry-run` instead, so its plain form wrote, and somebody checking what it would do added thirteen entries to a registry finding out. A single command that inverts the convention is worse than no convention, because the value of the rule is not having to check which kind you are holding. A third worked parser, `example_author_pages.py`, reads a mirror for the creator rather than for the recordings: their own bio, photograph and links, as `kind: Author` records. Without it a creator page carries a synopsis a model wrote and an avatar a renderer drew while their own words sit unread in the mirror. `inductor authors --only-adopt` is how those reach a page that already exists -- replacing a field the page marks `generated`, stopping at one it does not, and taking the adopted fields back off the generated list, because they were not. Read it mostly for what it refuses, since all three traps produce a confident wrong answer rather than an empty one. `og:image` is not necessarily the creator: one site's is a stock photo from an image bank and the real avatar is the site icon. A homepage is a shop window and not a biography -- four of five read this way produced a catalogue, and a bare first-person test passes "I am redefining you", a line from one recording's write-up that became somebody's biography. And no heuristic separates a product photo from a portrait; one candidate that survived every other check was a photograph of an envelope. Five more war stories, all of them one recording arriving as several files or several recordings arriving as one: a batch tagger that puts the artist in the title field, so two files claim the creator's name as their title; a variant marker in the filename and not in the title, which collides two dubs of one session on a single id; a library that holds the preview and not the release, where the envelope correlates 1.0000 precisely because the short one is inside the long one; variants correlating as high as re-encodes, which no figure will separate and only the catalogue will; and `inductor add`, which is for audio with nothing beside it and hands a model the job of inventing descriptions a mirror already had. |
||
|---|---|---|
| cache | ||
| content | ||
| examples | ||
| media | ||
| sources | ||
| state | ||
| tools | ||
| www | ||
| .gitignore | ||
| AGENTS.md | ||
| CLAUDE.md | ||
| inductor.yaml | ||
| LICENSE | ||
| README.md | ||
Driftspace
A working directory for building a spoken-audio library with Hypnotica and Inductor.
Clone it, point an LLM agent at it, and tell the agent what you have. It reads
the notes in CLAUDE.md and in each directory, writes a parser for
whatever shape your material arrived in, and runs the pipeline. The end of it is
a static website: every recording with a transcript, a write-up, tags, acoustic
measurements, and a spoiler list naming the triggers and compulsions it contains.
The spoiler list is the point. Hypnosis recordings try to do things to the listener, and a catalogue that says what lets you decide before you play something rather than after.
This template ships no recordings and no metadata about any. It is a layout, a tag vocabulary, and a set of working notes.
What it is made of
Three pieces, and keeping them separate is the point.
content/ |
the library itself: YAML, and the only thing that matters long-term |
| Hypnotica | turns content/ into a website. Knows nothing about where it came from |
| Inductor | fills content/ from audio. Knows nothing about websites |
Each tool is usable alone. Hypnotica will build a site from YAML you wrote by hand. Inductor will enrich a folder of audio and hand you YAML to do what you like with. Together they are a pipeline.
Install the tools
Both are single Go binaries, and both want FFmpeg on PATH.
git clone https://github.com/NaomiAmethyst/inductor
cd inductor && go build -o ~/.local/bin/inductor ./cmd/inductor
git clone https://github.com/NaomiAmethyst/hypnotica
cd hypnotica && go build -o ~/.local/bin/hypnotica ./cmd/hypnotica
| needed for | |
|---|---|
| Go 1.26+ | building either binary |
| FFmpeg and ffprobe | durations, conversion, acoustic measurement |
| Python 3.10+ | transcription and voiceprints — on the worker machine only |
| An NVIDIA GPU | transcribing at a sensible speed. A CPU worker is the fallback |
| An OpenRouter key | write-ups, tags and spoilers. OPENROUTER_API_KEY |
| ComfyUI | cover art. Optional, and off by default |
Nothing but Go and FFmpeg is needed to import material, tag it, and build a site. Transcription and enrichment are what pull in the rest.
Start
git clone https://github.com/NaomiAmethyst/driftspace-template my-library
cd my-library && rm -rf .git && git init
Then edit two files — inductor.yaml for models and paths, and
content/hypnotica.yaml for the site's name and URL — and say something like:
I have a folder of mp3s at
~/audio/some-creator/and a mirror of their site at~/mirrors/some-creator/. Import them.
The agent's route from there is written down in CLAUDE.md. If you
would rather drive it yourself:
inductor add ~/audio/some-creator/*.mp3 --author-name "Some Creator"
inductor check # before trusting anything a parser wrote
inductor tagmap --author some-creator
inductor run --author some-creator --no-covers
inductor adjudicate # rule on tags the run wants to add
hypnotica -s content check
hypnotica -s content build -o www --media link
hypnotica serve -o www -p 8080
--media link symlinks the audio into the site instead of copying it, which
matters once the library is larger than the disk has room to duplicate.
The pipeline
a site mirror, a folder of mp3s, a pack with a README
│
│ a parser you write for that one source tools/mirrors/
▼
sources/<creator>.yaml kind: Source — audio, title, author, whatever else
│
│ inductor run transcode · transcribe
▼ measure · enrich · tag
content/<creator>/*.yaml kind: Item, Author, Transcript
│
│ hypnotica build
▼
www/
Each arrow is re-runnable and cached. Nothing later in the chain reaches back: Hypnotica never transcribes, Inductor never renders.
The layout
Every directory here has a README.md saying what belongs in it. Directories
that are meant to start empty hold a .gitkeep and nothing else.
content/ the library. Permanent, and the only thing that matters
tags.yaml the tag registry: the only tags that may reach an item
hypnotica.yaml site configuration
media/ audio and artwork, placed where the site can serve them
sources/ source records, one YAML per creator
state/ produced by the pipeline. Expensive, and not disposable
decisions/ tag maps and rulings: what a person decided, and why
transcripts/ what was heard, keyed by the audio's fingerprint
enrichment/ what the models made of it, keyed by the transcript's
cache/ indexes, measurements, staging. Safe to delete
tools/mirrors/ one parser per source site
tools/import/ one-off importers and migrations
www/ the built site. Generated; never edit it
cache/ may be deleted; state/ may not, and neither may content/. The line
between them is not how the files were made but what it would take to make them
again: a voiceprint is GPU-hours and a tag ruling is a judgement nobody can
reconstruct.
Git, and the parts of this that git is wrong for
.gitignore excludes cache/, media/ and www/, and carries commented lines
for state/ and content/ — see the comments there, because whether those
belong in git depends on how private the library is and how large it has grown.
The audio does not belong in git, and neither does the artwork. A few thousand recordings is a few hundred gigabytes, git stores every version of every one of them forever, and no forge will take it. Options, roughly in order of how much trouble they are:
- Leave it out.
media/is symlinks into wherever the audio already lives. Back that directory up the way you back up anything else —rsync,restic,borg, a second disk. - git-annex, which keeps the file contents outside git and the filenames inside it. The closest fit: it is built for exactly this, and it can track which drive holds which copy.
- Git LFS, if you are pushing to a forge that offers it and can live with its quotas.
- Syncthing or similar, alongside the git repo rather than inside it.
The YAML is what is worth versioning. It is small, it is the expensive part to rebuild, and its history is a record of what you decided about each recording.
Privacy
This is a personal library, and the default configuration treats it as one.
base_url points at localhost, the site is static and needs no server-side
anything, and nothing is published until you publish it.
What does leave the machine: transcript text and metadata go to whichever models
inductor.yaml names, over OpenRouter. Transcription and acoustic measurement
are local. If the material is sensitive enough that model providers are a
problem, run the ingest stages without analyse and review and write the
entries yourself — inductor ingest --stage media --stage transcribe --stage emit.
Licence
Copyright © 2026 Naomi Persephone Amethyst naomi@amethyst.name.
This template — the layout, the notes and the tag registry — is GNU GPL
version 3 only, matching Hypnotica
and Inductor, neither of which is
included here. See LICENSE.
The library you build with it is yours. The licence covers this scaffolding, not
your recordings, your metadata or anything you write into content/.