← Projects

Hrafnlingr

A native documentary workshop built around a multi-stage Rust writing and production pipeline.

Hrafnlingr turns a topic into the working material for a narrated documentary: a researched and reviewed script, structured scenes, archival cues, visual prompts, dramatized interview segments, voiceover audio, and subtitles.

It began as a pipeline experiment and grew into a complete local system. The Rust workspace contains the domain model, model providers, orchestration, manifest format, downstream tools, audio synthesis, two graphical interfaces, a command-line application, and a foreign-function boundary used by a native macOS app.

Writing as a state machine

The main pipeline uses four distinct roles rather than asking one model to produce a finished answer in a single pass.

  1. A story writer creates the narrative draft.
  2. A fact checker returns a structured review.
  3. A proofreader checks the writing and presentation.
  4. A scriptwriter turns the approved material into production-ready segments.

If either review rejects the draft, its feedback returns to the story writer. The loop continues up to a configurable revision limit, after which the best available draft moves forward. Checkpoints allow a run to resume without discarding completed work.

The story and review roles can use different models. A provider-neutral client supports Anthropic, OpenAI, and DeepSeek, selecting the correct implementation from the model name. Adding another provider means implementing one request/response trait rather than changing the pipeline.

A manifest as the contract

The output is a versioned JSON manifest rather than an unstructured block of prose. Every segment has a stable UUID and can carry narration, estimated duration, scene direction, archival references, a visual-generation prompt, and an optional dramatized speaker and line.

That manifest separates creative generation from downstream production. One tool splits it into context-rich image prompts. Another extracts a speaker-aware voice script. The Minimax TTS pipeline synthesizes lines concurrently, assigns different voices to narrator and characters, names files in timeline order, and creates SRT subtitles from timing information.

Because the contract is explicit and versioned, another renderer or editor can consume the same documentary without knowing how it was written.

Rust core, native Mac interface

The core is divided into small Rust crates. pipeline-core owns domain types and schemas without network dependencies. Separate crates handle providers, phases, manifest export, TTS, preferences, CLI wiring, and the split pipeline.

Hrafnlingr’s macOS application is written in SwiftUI. It calls the Rust system through UniFFI, with long-running Tokio operations exposed as asynchronous Swift functions. A listener carries log messages and phase changes back across the boundary from Rust worker threads to the main application state.

This keeps the pipeline in one tested implementation while allowing the Mac interface to feel native: NavigationSplitView, Keychain-backed API credentials, system audio playback, Finder integration, recent projects, remembered documentary domains, prompt editing, and standard macOS help.

A local production notebook

The application keeps generated work in topic folders. Each run writes a timestamped manifest; each voiceover generation creates its own versioned audio directory. An older audio version remembers the voice, model, speed, volume, format, sample rate, and subtitle settings that produced it.

Completed manifests can be loaded independently, so the application also works as a script reader and voiceover workshop without rerunning the writing pipeline. The output view presents the readable script first while keeping the full JSON available when needed.

Preferences save automatically, while provider credentials are separated into the operating system’s secure key store. The application includes its own help book, pronunciation audio, signed disk-image packaging, and a Sparkle update feed.

Why build the whole path?

The useful lesson in Hrafnlingr has been that generative systems become much easier to reason about when their creative steps produce ordinary, durable artifacts. Reviews are explicit transitions. A manifest is inspectable. Audio is a versioned output. The graphical application is a client of the same pipeline that the CLI can run.

It is less a “generate documentary” button than a small workshop: automated where repetition helps, visible where judgement matters, and local enough that every intermediate result remains within reach.