Configuration

Every key in earmark.toml: types, defaults, and what reads them.

earmark.toml lives inside the library and marks the folder it sits in as one. earmark config opens it in $EDITOR; earmark config --show prints every value in effect and where it came from.

Every setting is a default. A command-line flag always wins.

There is no library key and there never will be: the library is the folder this file sits in.

Top-level settings

Key Type Default Accepts Read by
voice string "af_heart" any name from earmark voices audio, publish
speed number 1.0 0.5 to 2.0 audio, publish
lang string "en-us" a language code audio, publish
profile string "article" article, paper, book text, audio, publish
model string "full" full, fp16, int8 audio, publish
engine string "kokoro" kokoro, say audio, publish, voices
bitrate string "64k" any ffmpeg bitrate audio, publish
sample_rate integer 44100 a positive whole number audio, publish
after_publish string none a shell command publish
voice = "af_bella"
speed = 1.1
profile = "paper"

after_publish

One line of shell, run inside the library once the feed is written. Only needed if your library is not already a folder that syncs to the web.

after_publish = "git add -A && git commit -m 'earmark' && git push"

It is a config key rather than a plugin system on purpose: it covers every host without earmark knowing the name of one. Recipes: Host a library.

[feed]

Key Type Default Means
base_url string none the public URL the library folder is served at
title string "earmark" podcast title
author string none podcast author
description string none show description
link string none a website for the show
language string none RSS language code, e.g. en-us
category string none iTunes category
cover string none a file in this folder; earmark normalizes it
image string none a URL to artwork hosted elsewhere; passed through
[feed]
base_url = "https://filedn.com/XXXX/earmark"
title = "John's Reading Pile"
author = "John Helveston"
description = "Things I meant to read."
cover = "cover.jpg"

base_url must start with http:// or https://. Publishing without it stops with a message telling you to run earmark config.

cover and image are not interchangeable: image disables the cover pipeline rather than racing it.

[replace]

Fix a mispronunciation once instead of every time. Matched on word boundaries, longest key first.

[replace]
BEV = "battery electric vehicle"
NIMBY = "nimby"

Keep [feed] last

TOML puts a key you add at the bottom of a file into whichever table came before it. An unknown key under [feed] warns loudly; an extra entry under [replace] would look exactly like a word you wanted respoken, and would be silently accepted.

So [feed] is the last table in the file the template writes, and it is worth keeping it there.

Warnings and errors

Loading a config never fails, because a broken file must stay inspectable with earmark config --show. Problems are sorted into two kinds.

Warnings print to stderr and the offending key is ignored:

Warning Cause
unknown section [x] ignored a table that is not [feed] or [replace]
unknown setting 'k' ignored a top-level key not in the table above
unknown setting 'feed.k' ignored a key not in the [feed] table above
[replace] must be a table of strings; ignored [replace] is not a table

Errors stop the command that was about to use the settings, reported as earmark: config problem in <path>: with one line per problem:

Error Cause
speed = … is not a number non-numeric speed
speed = … is not between 0.5 and 2.0 out of range
profile = … is not one of article, paper, book unknown profile
model = … is not one of full, fp16, int8 unknown model
engine = … is not one of kokoro, say unknown engine
voice / lang / bitrate = … is not a non-empty string wrong type or empty
sample_rate = … is not a positive whole number wrong type or < 1
after_publish = … is not a shell command string wrong type
feed.base_url is not a URL string wrong type
feed.base_url = … must start with http:// or https:// wrong scheme
not valid TOML: … the file does not parse

The file init writes

# earmark configuration
#
# This file marks the folder it lives in as an earmark library. Everything
# earmark makes -- markdown in text/, MP3s, feed.xml -- lands beside it.
#
# Every setting here is a default for every new episode. A setting on one
# entry in sources.yml, or a command-line flag, wins over it.

# The voice for every episode. Hear them all on the Settings page:
# https://earmark-dev.github.io/earmark/settings/
voice = "af_heart"
speed = 1.0               # 0.5 to 2.0
lang = "en-us"            # en-gb for a British voice (one that starts with b)

# profile = "article"     # article | paper | book
# model = "full"          # full | fp16 | int8
# engine = "kokoro"       # kokoro | say
# bitrate = "64k"
# sample_rate = 44100

# Run this after every publish, from inside the library. Only needed if your
# library is not already a folder that syncs to the web -- a git push, say.
# after_publish = "git add -A && git commit -m 'earmark' && git push"

# Fix a mispronunciation once instead of every time. Matched on word
# boundaries, longest key first.
[replace]
# BEV = "battery electric vehicle"

# [feed] is last on purpose. TOML puts a key you add at the bottom of the file
# into whichever table came before it, and an unknown key here warns, while an
# extra entry under [replace] would look like a word you wanted respoken.
[feed]
base_url = ""
title = "earmark"
# author = "Your Name"
# description = "Things I meant to read."
# cover = "cover.jpg"     # a file in this folder; square PNG or JPEG
# image = "https://..."   # or artwork already hosted somewhere; this wins

sources.yml

The library’s reading list, written by earmark init beside earmark.toml and read by earmark publish when it is given no SOURCE. It is a YAML list. Each entry is one of:

- https://example.com/some-article        # a URL
- files/a-paper.pdf                       # a file, relative to the library
- source: https://example.com/long-read   # a mapping, for overrides
  title: A better title
  voice: bf_emma
  profile: paper

A mapping takes source plus any of title, author, date, voice, speed, lang and profile, which mean what the flags of the same name mean. An entry’s settings beat the config; the config beats the defaults.

Problem Treated as
an unknown key in an entry a warning; the key is ignored
the same source twice an error
a file entry that does not exist an error
an entry with no source an error
the file is not valid YAML, or is not a list an error

Any error stops the sync before it changes anything, because syncing to a list read only in part would remove the episodes of the entries it could not read. A file of nothing but comments is an empty list, not an error.

What is not in the library

Three things are deliberately stored outside it, because they must not sync to a public folder:

  • the 354 MB Kokoro model
  • the per-chunk synthesis cache
  • a one-line pointer naming your default library

earmark config --show prints where each of them actually is.