The first version of a video script is never the last. A sentence gets shorter. A claim that sounded good in the outline gets cut because the footage does not support it. A scene moves. If the narration lives in a web app, every edit means opening a browser, pasting the new text, waiting, downloading another file, and dragging it into the timeline. Do that a few times and the voiceover stops feeling like part of the project. It becomes a manual step you avoid.
Treat narration like build output instead. Keep the script in the repo. Regenerate only the scenes that changed. A Mac can do this today, and the command line is the right place to wire it up.
Start with say, then notice where it stops
macOS ships with say. It is the zero-install baseline, and it is genuinely useful for checking timing before you commit to a voice.
say -o audio/intro.aiff "Rough timing pass."
For placeholder tracks, that is enough. For narration that ships, it is not. The built-in voices sound robotic, and say writes AIFF, so you usually need a conversion step before the file fits an editor. There is no manifest and no batch job. I would not build the final pipeline around it.
Murmur is a native macOS app for local text to speech. It bundles Kokoro, an open weight TTS model with 82 million parameters under Apache 2.0. The public model card is on Hugging Face. Murmur runs on Apple Silicon Macs with macOS 15 or newer. After models are downloaded, generation works offline, and scripts never leave the machine.
Install the CLI and check the basics
The CLI is a Pro feature. Open Settings in Murmur, then click Install CLI. That links ~/.local/bin/murmur to a signed helper inside the app, so keep ~/.local/bin on your PATH. The first checks are simple:
murmur status
murmur status --json
murmur models
murmur voices
murmur status --json is the one a script or agent should call. Readiness can be checked without parsing a human sentence. murmur models shows what is installed, and murmur voices gives you the voice IDs for a manifest.
The CLI and MCP setup is documented at Murmur's automation page.
Generation itself is one command:
murmur generate --text "In this scene, the narrator introduces the problem." \
--voice af_bella --speed 0.95 --output audio/intro.wav
Or point at a text file:
murmur generate --input scripts/chapter-01.txt --model kokoro \
--output audio/chapter-01.m4a
The input is UTF-8 text or Markdown. The output is WAV or M4A. Change the text, run the command, get a new file. No browser tab, no download to rename.
Put the scene list in the project
A shell loop works until the project has many scenes. At that point, a manifest is easier to review and diff. Commit it beside the video:
{
"items": [
{
"id": "intro",
"inputPath": "../scripts/intro.txt",
"outputPath": "../audio/intro.wav",
"voiceID": "af_bella",
"speed": 0.95
},
{
"id": "chapter-01",
"inputPath": "../scripts/chapter-01.txt",
"outputPath": "../audio/chapter-01.m4a",
"voiceID": "af_bella",
"speed": 1.0
}
]
}
Relative paths resolve from the manifest's folder, so ../scripts/intro.txt points outside video/. This keeps the manifest portable with the project as long as the layout stays consistent. Then run:
murmur batch --manifest video/scenes.json
Batch work is long enough that the CLI may hand back a job ID. Use murmur jobs get ID to check it and murmur jobs cancel ID to stop it.
A Makefile for changed scenes only
A manifest is the right shape for a full render. For day-to-day edits, a small Makefile that touches only changed scenes is better. It assumes one text file per scene and writes one WAV per scene:
SCENES := intro chapter-01 chapter-02
AUDIO := $(addprefix audio/,$(addsuffix .wav,$(SCENES)))
VOICE := af_bella
SPEED := 0.95
.PHONY: all clean
all: $(AUDIO)
audio/%.wav: scripts/%.txt
@mkdir -p $(@D)
murmur generate --input $< --voice $(VOICE) --speed $(SPEED) --output $@
clean:
rm -f $(AUDIO)
Run make after changing a script. Make compares timestamps and runs the rule only when scripts/scene.txt is newer than audio/scene.wav. That is the useful part. Narration stops being a separate task and becomes another build artifact.
Make tracks files, not variables. If you change VOICE or SPEED, existing WAVs are still up to date as far as it knows. Run make -B or make clean when those values change.
Let a coding agent run it
murmur mcp serve starts a stdio MCP server. One registration example is:
claude mcp add --transport stdio --scope user murmur -- <helper path> mcp serve
Replace <helper path> with the signed helper inside the app. Once the server is connected, an agent can start generation and poll murmur_get_job for long operations. That turns a small script edit into a job the agent can run, without waiting on you.
What it costs and where it stops
The CLI and MCP server are Pro features, and both are Mac only. The local generation model needs disk space and memory. The free plan covers Kokoro voices with a 1,500 character limit per generation. There is a 3-day Pro trial. Pro is $49 one-time, with no subscription and no account required. If it does not fit, the refund window is 7 days.
Other limits matter depending on the work. There are no cloud voices. Cloning quality is below ElevenLabs. Cloning also requires an optional local bridge and at least 16 GB of memory. There is no team admin UI. If you need browser access from any machine, Murmur is the wrong tool.
Where ElevenLabs is the better choice
ElevenLabs has the stronger voice product. Its cloning and expressive voices are best in class, its multilingual catalogue is large, and it works in any browser on any OS. It also has an API and team features, plus dubbing. If you need the best clone, a large language list, or shared team controls, use ElevenLabs. You can compare their plans on the ElevenLabs pricing page.
Disclosure: I build Murmur.
The setup is small: one text file per scene, a Makefile with timestamps, and a manifest for full runs. If you want to try it, start here: Murmur. The 3-day Pro trial includes the Pro features, so you can test the CLI and MCP path before paying.
Top comments (0)