Annoudio
An audio annotation widget that a site embeds. Members of a community tell stories about parts of a recording, and visitors see them on the organisation's own site.
Annoudio is a tool for audio annotation. A member selects a part of a recording and tells a story about it. The story can have text, a voice note, a photo or a video. You read each story and publish it. Then visitors see it in the widget on your site, on the timeline of the recording. Annoudio keeps each story as a W3C Web Annotation, so other archive tools can read it.
Who it is for
- OperatorA person of the organisation's team. Reviews the stories and publishes them in one admin, with no code.
- MemberA person of the community. Signs in with a code by email and tells a story about a time range: text, a voice note, a photo or a video.
- VisitorAnyone on the organisation's site. Plays the recording and reads the published stories in the widget. No account.
- Site maintainerPuts the widget on a page: pastes the two-line snippet, or uses the WordPress block or the Omeka S module.
The words
The W3C Web Annotation Data Model (WADM) says: "this body says something about this target". Annoudio uses the model as it is, and gives each part a plain name. The left column is the W3C word; the middle column is the word that people read in Annoudio.
| In audio annotation (W3C) | In Annoudio | One-line definition for a person |
|---|---|---|
Annotation (type: Annotation) |
Story | A note that a member makes about one part of a recording. |
Target source (the audio resource) |
Recording | One audio work that your organisation keeps, with a title and an audio address. |
Target selector (FragmentSelector, Media Fragments t=62,95) |
Time range | The part of the recording that the story is about, for example 1:02–1:35. |
Body: TextualBody |
The story's text | What the member writes: what they remember or hear. |
Body: Sound, Image, Video (external resource) |
Voice note, photo, video | A file that the member adds to the text of the story. |
Body: TextualBody with rdf:predicate (vocab-C) |
Answers | The member's answers to your questions, for example "Place: North landing". |
Motivation commenting |
Story | The member says something about a time range. |
Motivation replying (target = another annotation) |
Reply | A note about a story, not about the recording. One level deep. |
Purpose tagging (body points at a SKOS concept) |
An item from a list, for example from Topics | A word from your list that says what the moment is about. |
creator |
Member | The person who told the story. Visitors see the member's name, never the email. |
| Container (Web Annotation Protocol, LDP) | The stories of a recording in a project (code: instance) | All the stories that one widget shows. |
| A client that shows annotations | Widget | The element on your site that plays the recording and shows its stories. |
| (none: Annoudio only) | Snippet | The two lines that you paste into a page to show a widget. |
Target scope |
Project | The settings for one site: the lists, the look, the review rule and the rights line. |
rights |
Rights line | The licence of the stories, shown under them in one sentence. |
| SKOS concept scheme | List (Topics, Places, Speakers) | A set of items that members can choose from. |
| SKOS concept | Item | One entry in a list, with a label and a fixed address. |
Property (SHACL sh:path, dcterms:spatial) |
Question | One fact that you ask members about each story, for example "Place". |
| Vocabulary (ontology, schema) | Questions and lists | All the questions and lists of one project. A file is optional, under Advanced. |
| (none: WADM has no state) | Review state: Processing, Waiting for review, Returned, Published | Where a story is on its way from the member to the visitors. |
See it work
Three of the nineteen recordings, made on the demo stack: a member tells a story on a phone, an operator publishes a story, a visitor reads it on the site.
10 · Tell a story
From the WordPress page: Tell a story, invite code, name, drag a range, answer, Send for reviewOn the WordPress demo page (phone), the member selects Tell a story in the widget. The Annotate page asks to sign in: Have an invite code?, an email and a code from the operator (typed in lower case with a space: case, spaces and the hyphen do not matter). First sign-in: the name "Asha". On the form, a drag on the waveform marks the range and the fields follow; the story text; the answers Place = North landing and Topics = Horn; Send for review. The Start and End fields sit right under the waveform, with Use current time (0:00). The browser goes back to the WordPress page, and the widget says "Your story is sent. It shows here after review."
06 · Review publish
Review stories → a waiting story → chips → Publish dialog with preview → the next story opensReview stories lists the waiting stories, oldest first. Every row has its state badge. The operator opens the oldest waiting story (an earlier voice note by Asha): the chips (member, project, recording), the answers and "Told on localhost:8091". Publish opens the dialog "Publish this story?" with the story as visitors will see it; Publish the story shows "Published. Visitors see it within one minute." and the next waiting story opens.
14 · Widget
The WordPress page: play, open a story, its answers, the thread, Tell a storyA visitor on the WordPress demo page: Play plays the recording and the time ranges light up. Selecting Asha's story plays its range and shows its answers ("Place: North landing" and the tag Horn). Leela's story opens its thread with the reply of video 13 (published by the operator off camera). Tell a story sits under the controls; the rights line is at the end of the list.
How to run it
One organisation runs one copy: one stack of three parts (the app, Postgres and a worker), with Docker Compose.
On your computer, to change the code
docker compose -f deploy/docker-compose.dev.yml up -d --wait # Postgres
cd app && cp .env.example .env && pnpm install && pnpm dev # then open http://localhost:3000/admin
Set the values in app/.env first; app/README.md tells each one. A ready demo with data and a mailbox: deploy/DEMO.md.
On a server, with Dokploy (recommended)
For an organisation on its own server, with screens for backup and restore. You need a server with 4 GB of memory, a domain, an SMTP account and an S3 bucket. Read Run Annoudio with Dokploy.
On a server, with Coolify
For an organisation that already uses Coolify. It has fewer backup screens for a compose stack. Read Run Annoudio with Coolify.
For both, also read Backup and restore and Upgrade.
The snippet
A page needs two lines to show a widget. The operator copies them from the admin. ORG is the host name of the organisation's stack.
<script async src="https://ORG/embed/v1.js"></script>
<annoudio-player project="…" recording="…" src="…"><audio controls preload="none" src="…"></audio></annoudio-player>
With real values:
<script async src="https://stories.org.example/embed/v1.js"></script>
<annoudio-player project="river-voices" recording="rec-0042"
src="https://stories.org.example/media/rec-0042.m4a"></annoudio-player>
The rules for these lines, the attributes, the theme tokens and the events are the promise to every host site: the embed contract. WordPress and Omeka S have a plugin that writes the snippet from a recording number: WordPress block, Omeka S module.
Source and licence
The code is on GitHub: github.com/micahchoo/annoudio. It is free software under the MIT licence. The design system shows the parts of the admin and the Annotate page. Not built yet, because they need people: the team trial and the pilot on a real site. The items that no test has checked are in Not verified yet.