Chapters is a desktop audiobook player for Windows and Linux. It builds a library from the folders you choose, reads tags and covers in the background, and resumes each book where you stopped. Your library and listening stay on the PC, with no account and no cloud. PRTOTYPE.COM designed it and built it.
Many people keep their audiobooks as plain files: ripped, DRM-free, or bought elsewhere. They need a player that finds the books, remembers the place in each one, and stays usable while it scans their folders.
Chapters keeps everything on the machine. Playback needs no internet and no account. Folders are the library, and the app builds the shelf from them.
That premise sets the engineering problem. The folders arrive in any shape, and nobody is there to fix them. A scan must not block the window, a crash must not lose a place in a book, and the licence check must fail safe when the network is bad. The Linux build must be the same code that passed the Windows checks.
The first commit was on 27 February 2026, and the repository now holds 274 commits. Version 0.5.1, dated 24 October 2026, changed no application code. It is a release candidate, built and tested locally and unpublished.
“No DRM, no accounts, no internet required.”The line in the app’s own About panel. Licence checks use the network; playback does not.
Forty-five seconds of version 0.5.1 in use, with no sound. Press play, or pick a beat in the list to jump to it.
Demonstration data. The film uses the same data as the tour below: 13 public-domain books with covers drawn for this demonstration and invented narrator names, synthetic speech, and a seeded listening history. The pointer in the film is drawn by the recorder, because the automation does not move the real cursor. The screens are the shipped ones.
These are captures of version 0.5.1 in its default dark theme, with a throwaway profile and the licence server unreachable, so the app shows its local 30-day trial and a Pro trial badge. Click any plate to see it full size.
Every screen below shows demonstration data.
The War of the Worlds is a single .m4b file. Chapters reads its 27 chapters from the file and lists them with their times. A pill above the list names the one now playing: Chapter 6 of 27, The Heat-Ray in the Chobham Road. The player and the chapter list are Free.
The banner reads “Loading covers & details…” with a count: 87 files found, 13 added, details 0/13. All 13 books are already in the library and playable, each with a placeholder cover. Two worker threads read the tags and covers afterwards. Cancel keeps everything found so far.
Four bookmarks on one book, at 11:43, 46:29, 1:05:05 and 2:01:41, each with a note. Add Bookmark takes the current position, and Export writes the list out. Bookmarks are Free. The separate notes and annotations panel is Pro.
The book plays at 1.25x with Voice EQ on. The square panel is the waveform view, which replaces the cover. The sleep-timer menu is open: End of chapter, or 5, 10, 15, 30, 45 or 60 minutes. “4h 8m remaining” and the sidebar durations are recalculated for the chosen speed. Voice EQ is a peaking filter at 2,500 Hz, +10 dB.

Up Next holds three books, and they play one after another when the current book finishes. Playback History, here on Dracula, lists recent track changes, each with a Restore button. Click one to return to that exact moment. Both panels are Pro.
Stats & Achievements shows total listening time, books finished, a 14-day activity chart and streak cells. Scrolled down, it lists the most-listened books and the badges: 2 of 6 earned here. Time counts in real minutes, and seeks and speed changes add nothing to it. The history behind these two screens was seeded, so read them for layout only.
Settings shows Version 0.5.1 and a Pro panel reading “Free trial · all Pro features for 30 more days”, after which Chapters drops to the free tier of 15 new books a year. On an installed copy the licence server freezes the first-launch date for a salted hash of the machine, so deleting local data does not restart the trial; this capture had the server unreachable, so the badge comes from the local rule. Smart resume (a 10-second rewind after an hour away) and silence skipping are Pro, and backup never carries the licence or folder permissions.
Six decisions in the code, each shown with the constants that set it. They keep the window responsive, the library intact and the licence safe when the network fails.
Tag parsing runs in worker threads, never on Electron’s main thread. Two workers take one task each from a queue capped at 64. A worker that times out, errors or exits is terminated and replaced, and the next scan creates fresh ones.
Numbered file names such as “01 Intro” are only a candidate until the tags agree. A collection folder alone does not make several M4B files one book. A manual correction must be a complete partition of the existing files, so the window cannot add or drop a track.
Library state is one JSON file. Every save is chained on one promise queue, written to a unique temp file, flushed, then renamed over the real file. An earlier audit marked concurrent saves as unsafe; the queue is the fix. Quit waits for the queue to drain.
Covers used to sit as base64 inside the JSON store. They are now image files served through safe-cover://, and the id must be 16 to 64 hex characters. Audio uses a matching safe-audio:// scheme with range requests, and it refuses paths outside the folders the user approved.
Every licence reply is Ed25519-signed and verified, and only a signed refusal ends a licence. A captive-portal page degrades to offline grace and never clears it. The stored record is sealed to the machine, so a copied database.json opens nowhere else. The clock guard deters casual date changes and claims no more.
Six Electron fuses are set at build, including no run-as-Node, no --inspect and load-only-from-the-asar. An earlier build packaged the whole repository, a keys file among it, into a 149 MB installer. The builder now takes an explicit list, and a check rejects any other path in the archive.
Seven stages, from the folder picker to the saved position. The scan job behind stages 02 to 04 can be cancelled at any point, and it keeps what it has found.
.mp3, .m4a, .m4b, .m4p, .aac, .wma, .ogg, .wav and .flac. A banner reads “Finding audio files…” and shows a count.safe-audio:// with range requests, so a large M4B starts at once. Position is saved every 5 seconds. On return, smart resume (Pro) rewinds 10 seconds after an hour away. private save(): Promise<void> {
// Snapshot is taken inside run() at write time (last-serialise-wins is
// the correct semantics here), and every write is chained so that two
// concurrent saves cannot race on a shared temp file. The second arg to
// .then keeps the chain alive even after a prior write rejects.
const run = () => this.persistSnapshot(this.data);
this.writeQueue = this.writeQueue.then(run, run);
return this.writeQueue;
}
private async persistSnapshot(snapshot: DbSchema): Promise<void> {
const tmp = `${this.filePath}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
try {
const content = JSON.stringify(snapshot, null, 2);
const fh = await fs.open(tmp, 'w');
try {
await fh.writeFile(content, 'utf-8');
await fh.sync();
} finally { await fh.close(); }
await this.renameWithRetry(tmp, this.filePath);
} catch (err) {
await fs.unlink(tmp).catch(() => {});
// (logging removed)
throw err;
}
}
/** Await all writes currently queued. Safe to call from before-quit. */
flushWrites(): Promise<void> {
return this.writeQueue;
}Entries from the changelog, shortened, with dates as printed. None of these builds has been published. The gaps are the open items in the project’s own notes.
A pre-flight audit recorded 4 critical, 19 high, 43 medium and 36 low findings. The release actioned all the critical, high and medium ones. The counts are the changelog’s own.
Audio moved behind a safe-audio:// protocol that serves only approved folders, replacing direct file access. The content security policy changed to match.
AppImage and deb are built alongside the Windows installer from the same tested bundle, and the two are compared byte for byte. The release also fixed a Linux fingerprint bug that gave every Linux machine the same empty hash, so none could start a trial.
No application code changed. The Linux release check now runs 14 of the 17 packaged checks, up from 2, and Linux receipts are written apart from the Windows ones. The build script also stops before packaging if WSL has no ffmpeg.
One Vite build feeds both packages. The Linux packaging runs inside WSL on the same tested files, then a script compares the two archives.
Version 0.5.1 has no public release and no price. The installers are not code-signed, so Windows would show an Unknown publisher warning. The Linux build has been checked under WSLg only.
Chapters went from a first commit on 27 February 2026 to a Windows and Linux release candidate built from one tested bundle: 1,131 unit tests in 114 files, 17 packaged acceptance checks and a byte-for-byte comparison of the two builds. If you have a desktop app that needs a scan engine, a safe store or a release pipeline, let's talk it through.