
This is a contribution from Greg at SermonIndex, who built and shared their own suite of ZIM files for our community repository.
What SermonIndex is
SermonIndex is an archive of Christian preaching that has been online since 2002. 70,419 sermons from 2,210 preachers, with transcripts for almost all of them, given away free. Much of it is old — men who died before anyone thought to digitise them — and a lot of it exists nowhere else.
Why offline mattered more than usual
The people most likely to want this material are the least likely to be able to stream it: churches in places with no reliable connection, camps, prisons, ships, mission stations. For twenty-four years the answer to those people was “we have a website”, which is another way of saying no.
ZIM changes that answer, and it does something else we care about more. Every copy that lands on a disk somewhere is a copy that survives us. If SermonIndex disappeared tomorrow the archive would not go with it.
We did not use zimit, and that turned out to be the point
The obvious route is to scrape the site. We didn’t, because SermonIndex already publishes a free JSON API that we could use directly.
Building from the API instead of crawling the HTML meant the ZIM is not a copy of our website. It’s a reader built for the purpose: a page per preacher, a page per sermon with the full transcript, a player only where a recording exists, scripture and topic tags, no site chrome, no JavaScript required to read a word of it. A crawl would have produced our navigation, our cookie banner and our footer, 70,000 times over.
If you have an API, build from the API.
The size problem, which forced the interesting decision
The audio is 9,606 hours. At the source MP3 bitrate that is roughly half a terabyte, and a single ZIM of it would be useless to almost everyone.
So we split it by who, not by how much:
- The whole text archive — 629 MB. Every sermon, every preacher, the full transcripts, no audio. Fits on a phone. This is the one most people want and most deployments can carry.
- The English Bible — 103 MB. All 49 English translations we hold, 44,549 chapters, in a reader with translation/book/chapter dropdowns where clicking a verse copies it with its reference.
- Twenty preachers, one file each — 735 MB to 22.7 GB. Transcripts and recordings. Someone who wants Leonard Ravenhill takes 6.6 GB, not 108.
Then the encoding decision, which was the single biggest lever: Opus at 24 kbps mono. Speech stays perfectly clear and 500 GB became 107 GB. A man’s entire life of preaching fits on a memory card.
Building 108 GB on a 2 TB disk
You cannot stage all of it at once, so the build goes one preacher at a time, smallest first: fetch, transcode, pack the ZIM, upload it, delete both copies, move to the next. Peak disk is one edition plus its staging folder — about 45 GB while the largest preacher builds, under 9 GB for everyone else. Smallest-first means the first finished file exists within the hour instead of at the end.
Four things that went wrong
Worth including, because a write-up in which everything worked is no use to the next person.
Serial downloads took about forty hours. One TCP stream to a busy host gives you well under a megabyte a second, and no amount of local horsepower changes that. Fetching six at a time brought the whole job to roughly six hours. Everything else was already fast enough.
A size check is not an integrity check. The fetcher originally accepted any response over 1 KB as audio. A CDN error page is comfortably over 1 KB, and would have been packed into a ZIM as an .mp3. It now checks the leading bytes for an ID3 tag or an MPEG frame sync and rejects anything that starts like HTML.
One source is not enough. The Internet Archive is the preferred source because it costs us nothing to serve, but an item can be missing a derived file or answer 500 while a node is unwell. Falling back to the CDN copy — rather than losing the sermon — mattered across 12,629 downloads.
ffmpeg shouting is not always failure. A number of the older MP3s have damaged frames. ffmpeg reports “Header missing”, skips them and produces a good Opus file anyway. That output is noise; it belongs in a variable, not across the progress bar.
The catalogue, which someone else asked for
Two people independently said the same thing: a library wants one endpoint, not twenty-three links. So the collection publishes OPDS 1.2 and an RSS feed, generated from the same manifest as the download page, rebuilt whenever an edition changes.
You can’t vibecode if you don’t understand what you’re looking at
The whole thing was built conversationally with Claude over about four days: the generators, the ZIM writer, the Bible reader, the deploy scripts, the catalogue. I am not a Rust or Python developer by trade. I run a sermon archive.
What that was actually like, since a post saying “the AI did it” would be worthless:
The model was very good at the mechanical parts — writing a libzim wrapper, parsing the API’s verse structure, getting relative paths right three directories deep. It was good at catching its own mistakes when shown evidence: given a screenshot it would say “that’s blurry because I’m upscaling a 1x capture 1.7 times” rather than fiddling with the composition.
The parts that were not automatic were the judgement calls, and they were the ones that mattered: audio-only or everything, one file or twenty, what bitrate is honest for preaching, whether a 1,000-verse “English” translation belongs in an English Bible edition. Those came from knowing the archive and its people. Nobody could have guessed them.
And it got things wrong. It produced an unusable banner three times running before we established the source screenshot simply didn’t have the pixels. It confidently reported an upload had succeeded when the server had refused it. Every one of those was caught by looking at the result rather than trusting the report.
So: vibecoded, yes, but not unattended. The right description is something like a very fast pair of hands that needs a clear brief and someone checking the output. On that footing, a 24-year archive went from a website to 108 GB of offline editions, a catalogue and a WordPress plugin in under a week. I could not have done it in a year alone.