podcast-generator 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,15 @@
1
+ # Copy this file to .env in the home folder to change where podcast-generator
2
+ # looks for scripts and writes audio. The home folder is podcast_generator in
3
+ # your Documents folder after a pip install, or the repo folder for a source
4
+ # checkout (or PODCAST_GENERATOR_HOME, if set). The Podcast Watch app
5
+ # (podcast-watch) can also edit these for you. All settings are optional; if
6
+ # unset, the home folder's own inputs/, outputs/ and script_archive/ are used.
7
+ # The same folders are used for manual and scheduled runs.
8
+
9
+ PODCAST_INPUTS_DIR=C:\Users\yourname\Sync\podcasts\inputs
10
+ PODCAST_OUTPUTS_DIR=C:\Users\yourname\Sync\podcasts\outputs
11
+ PODCAST_ARCHIVE_DIR=C:\Users\yourname\Sync\podcasts\script_archive
12
+
13
+ # Used by the Podcast Watch app (scripts/podcast-watch.ps1): on or off forces
14
+ # the watch task to that state whenever the app opens. Unset = leave it as is.
15
+ # PODCAST_WATCH_ON_OPEN=on
@@ -0,0 +1,146 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.3.0] - 2026-11-02
11
+
12
+ First public release, and the first on PyPI (`pip install podcast-generator`).
13
+
14
+ ### Added
15
+
16
+ - Packaged for PyPI as `podcast-generator`, with three commands:
17
+ `podcast-generator` (convert scripts; also `python -m podcast_generator`),
18
+ `podcast-generator-mcp` (the MCP server, with the `[mcp]` extra) and
19
+ `podcast-watch` (the Podcast Watch app; `podcast-watch --shortcut` makes
20
+ the desktop shortcut).
21
+ - A home folder for settings and the default folders: `podcast_generator`
22
+ in Documents for a pip install, the repo for a source checkout, or
23
+ `PODCAST_GENERATOR_HOME`. The first batch run creates the inputs folder.
24
+ - MCP server: Claude Desktop or another MCP client can list voices, queue a
25
+ script (validated up front), start a conversion in the background, check
26
+ a podcast's status, list recent podcasts, and turn the watch on or off.
27
+ - Podcast Watch app (Windows): one window to set up the
28
+ `PodcastGeneratorWatch` scheduled task (current user, no admin rights,
29
+ `pythonw.exe` so no window flashes up), turn it on or off, set how often
30
+ it checks, choose what happens when the app opens
31
+ (`PODCAST_WATCH_ON_OPEN`), and pick the inputs/outputs/archive folders.
32
+ Its **About** menu shows the version and author.
33
+ - Only one conversion runs at a time on a machine; a run that finds
34
+ another in progress leaves the queue to it. Under `pythonw.exe`, output
35
+ goes to `generator.log` (rotated at 1 MB).
36
+ - Tests, GitHub Actions CI, and a tag-triggered workflow that publishes to
37
+ PyPI and creates the GitHub release.
38
+ - MANUAL.md: sections on the Podcast Watch app, the home folder, offline
39
+ use, and troubleshooting the app, the watch and the MCP server.
40
+
41
+ ### Changed
42
+
43
+ - Speech synthesis comes from [`voxport`](https://pypi.org/project/voxport/)
44
+ 0.5.0+ on PyPI (the renamed `vox_bridge`), with its `kokoro` extra. Its
45
+ progress messages (model loads, each segment) are shown through logging.
46
+ - The code moved into the `podcast_generator` package. `generator.py` and
47
+ `mcp_server.py` at the repo root remain as thin wrappers, so scheduled
48
+ tasks and MCP configs made before this release keep working.
49
+ - Offline use: point `HF_HOME` at a copied Hugging Face cache in `.env`,
50
+ instead of voxport's repo `hf_cache/` folder.
51
+ - `instructions_creating_podcast_generator.md` brought up to date (v6.0).
52
+
53
+ ### Removed
54
+
55
+ - `requirements.txt` (dependencies are in `pyproject.toml`) and `VERSION`
56
+ (the version lives in `podcast_generator/__init__.py`).
57
+ - `scripts/watch-on.ps1` / `scripts/watch-off.ps1` and their two desktop
58
+ shortcuts, replaced by the Podcast Watch app.
59
+
60
+ ## Earlier versions
61
+
62
+ *Versions 0.1.0 to 0.2.1 predate this public repository, which starts
63
+ from a fresh history, so they have no tags or releases here.*
64
+
65
+ ## 0.2.1 - 2026-10-31
66
+
67
+ ### Fixed
68
+
69
+ - `generator.py --version` reported `0.1.0` in the v0.2.0 release.
70
+
71
+ ### Changed
72
+
73
+ - README: new Installation and Usage sections. MANUAL.md now describes
74
+ how italic-only lines at the top of a script are handled.
75
+
76
+ ## 0.2.0 - 2026-10-31
77
+
78
+ ### Added
79
+
80
+ - `scripts/watch-on.ps1` / `scripts/watch-off.ps1` and matching desktop
81
+ shortcuts ("Podcast Watch - Turn ON/OFF") that enable/disable the
82
+ `PodcastGeneratorWatch` scheduled task with a confirmation popup, so
83
+ toggling the folder-watch schedule doesn't need PowerShell or Task
84
+ Scheduler's GUI.
85
+ - `MANUAL.md`, a full user manual: script-writing guide, CLI reference,
86
+ voice catalog, and troubleshooting.
87
+
88
+ ### Removed
89
+
90
+ - The `reference/` folder and the obsolete in-repo `inputs/`/`outputs/`
91
+ folders.
92
+
93
+ ### Fixed
94
+
95
+ - An all-italic line near the top of a script (e.g. `*Source: ...*`) was
96
+ treated as the end-of-script marker, so the whole script stripped to
97
+ nothing and failed with "no spoken text found". Italic-only lines now end
98
+ the script only after spoken prose has started; before that they're
99
+ skipped as front matter.
100
+
101
+ ## 0.1.0 - 2026-09-19
102
+
103
+ ### Added
104
+
105
+ - `generator.py`: rebuilt as a consumer of the shared
106
+ [`vox_bridge`](../vox_bridge) toolkit's `KokoroDirectTTS`, replacing the
107
+ original standalone `tts_podcast.py` (which called Kokoro directly).
108
+ Behavior preserved: same CLI flags (`--voice`, `--speed`, `--format`,
109
+ `--output`), same markdown stripping, same per-script `**Voice:**`
110
+ metadata handling and precedence rules.
111
+ - Configurable folder locations (`PODCAST_INPUTS_DIR`,
112
+ `PODCAST_OUTPUTS_DIR`, `PODCAST_ARCHIVE_DIR` via `.env`), defaulting to
113
+ local script-relative folders when unset. The same folders and the same
114
+ command (`python generator.py`) serve both interactive use and scheduled/
115
+ unattended runs — there is no separate watch-mode script.
116
+ - Archive-on-success: a successfully-converted script moves out of the
117
+ inputs folder into the archive folder (collision-safe renamed if
118
+ needed), so a rerun — manual or scheduled — never reprocesses it.
119
+ - Archive-on-failure: a script that fails to convert moves to
120
+ `<archive>/failed/` with a `.error.txt` note, instead of being retried
121
+ forever by a scheduled run.
122
+ - Job discovery happens before any heavy import (`vox_bridge`/`torch`/
123
+ `kokoro`), so an empty inputs folder exits in a fraction of a second
124
+ instead of paying model-load cost for nothing — the difference that
125
+ makes polling this on a schedule cheap.
126
+ - `--version` flag.
127
+ - `LICENSE` (MIT).
128
+
129
+ ### Changed
130
+
131
+ - Default voice changed from `am_onyx` to `af_heart` (actual usage:
132
+ `af_heart` for most podcasts, `am_echo` via per-script `**Voice:**` for
133
+ the military fellows program).
134
+ - An empty inputs queue now exits with code `0`, not `1` — it's the normal
135
+ steady state for a scheduled/watched run, not an error. A genuinely
136
+ missing inputs directory (misconfiguration) still exits `1`.
137
+
138
+ ### Fixed
139
+
140
+ - Voice connect/permission and text/voice ordering issues live-tested and
141
+ fixed on the `vox_bridge`/`loremaster` side of this work are recorded in
142
+ those repos' own changelogs, not here — this file covers only
143
+ `podcast_generator`.
144
+
145
+ [Unreleased]: https://github.com/brienalkire/podcast_generator/compare/v0.3.0...HEAD
146
+ [0.3.0]: https://github.com/brienalkire/podcast_generator/releases/tag/v0.3.0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Brien Alkire
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,2 @@
1
+ recursive-include tests *.py
2
+ include CHANGELOG.md MANUAL.md .env.example
@@ -0,0 +1,393 @@
1
+ # podcast_generator — User Manual
2
+
3
+ A companion to [README.md](README.md) — that file gets you running in five
4
+ minutes; this one covers everything else: how to write a script, the full
5
+ CLI reference, the voice catalog, the Podcast Watch desktop app, using it
6
+ from Claude Desktop (MCP), and troubleshooting.
7
+
8
+ ## What this does
9
+
10
+ `podcast-generator` takes a markdown file — a podcast script — and turns it
11
+ into an audio file (MP3 or WAV) using [Kokoro](https://github.com/hexgrad/kokoro),
12
+ a free, open-source text-to-speech model that runs entirely on your own
13
+ machine via [`voxport`](https://github.com/brienalkire/voxport). No API key, no per-use cost, and
14
+ (once the model is cached) no internet connection required.
15
+
16
+ ## Writing a script
17
+
18
+ Any markdown file works, but a few conventions control exactly what gets
19
+ spoken aloud versus stripped out before synthesis:
20
+
21
+ - **Headers** (`#`, `##`, `###`, ...) are removed entirely — including
22
+ their text. If you use headers as section labels (`## OPENING`,
23
+ `## CLOSING`), those labels are *not* spoken.
24
+ - **Metadata lines** matching `**Key:** value` (e.g. `**Author:** ...`,
25
+ `**Date:** ...`) are removed. This is also how `**Voice:** af_heart`
26
+ works — see below.
27
+ - **Horizontal rules** (`---`, `***`, `___`) are removed.
28
+ - **An entirely-italic line** (e.g. `*Source: ...*` or `*End of script.*`)
29
+ is a production note. Before any spoken prose it's skipped as front
30
+ matter. After prose has started it's the end-of-script marker, and
31
+ **everything from that line to the end of the file is dropped, not just
32
+ that line.** This is meant for a trailing citation or production note,
33
+ but it's a common gotcha: if a normal sentence in the middle of your
34
+ script happens to be wrapped entirely in `*asterisks*`, everything after
35
+ it will silently go unspoken. Keep italic-only lines for the very top or
36
+ very end of the file only.
37
+ - **Inline formatting** — `**bold**`, `*italic*`, `` `code` ``,
38
+ `[link text](url)` — has its markup stripped but the text itself is
39
+ kept and spoken normally.
40
+ - Blank lines are preserved as paragraph breaks; runs of 3+ blank lines
41
+ collapse to one.
42
+
43
+ Everything else — plain paragraphs — is spoken as-is. A minimal script:
44
+
45
+ ```markdown
46
+ # My Episode Title
47
+
48
+ **Voice:** af_heart
49
+
50
+ ## OPENING
51
+
52
+ Welcome back to the show. Today we're talking about...
53
+
54
+ ## CLOSING
55
+
56
+ That's all for this episode. Thanks for listening.
57
+
58
+ ---
59
+
60
+ *Source: some-article.com, September 2026.*
61
+ ```
62
+
63
+ Only "Welcome back to the show..." and "That's all for this episode..."
64
+ get spoken — the title, section labels, voice metadata, and trailing
65
+ source note are all stripped.
66
+
67
+ ### Choosing a voice
68
+
69
+ Precedence, highest to lowest:
70
+
71
+ 1. `--voice` on the command line — applies to every script in that run.
72
+ 2. A `**Voice:**` line in the script's own header.
73
+ 3. The built-in default (`af_heart`).
74
+
75
+ This lets you set a default for most episodes while overriding specific
76
+ ones — e.g. a recurring segment that always uses a different voice.
77
+
78
+ ## Full CLI reference
79
+
80
+ ```
81
+ podcast-generator [input_script.md] [options]
82
+ ```
83
+
84
+ | Argument | Meaning |
85
+ |---|---|
86
+ | `input_script.md` (positional, optional) | Convert just this one file instead of batch-processing the inputs folder. |
87
+ | `--voice VOICE` | Kokoro voice for the whole run (see catalog below). Overrides any `**Voice:**` line. |
88
+ | `--lang CODE` | Accent code (`a` American, `b` British). Defaults to the chosen voice's first letter — you'd only override this for an edge case. |
89
+ | `--speed FLOAT` | Speaking rate multiplier (default `1.0`; `0.9` slower, `1.1` faster). |
90
+ | `--format {mp3,wav}` | Output format (default `mp3`). |
91
+ | `--output PATH` | Output file path. Only used in single-file mode; ignored in batch mode. |
92
+ | `--version` | Print the installed version and exit. |
93
+
94
+ `python -m podcast_generator` does the same thing.
95
+
96
+ **Batch mode** (no positional argument): every `.md` file in the inputs
97
+ folder is converted, written to the outputs folder as
98
+ `<script-name>.<format>`, and (on success) moved to the archive folder.
99
+
100
+ **Single-file mode**: pass a path directly. `--output` only applies here.
101
+ Archiving still happens if the file you named happens to live in the
102
+ inputs folder; a file from anywhere else is left where it is.
103
+
104
+ ## Voice catalog
105
+
106
+ Kokoro's voice names encode accent and gender in the first two letters
107
+ (`a`=American/`b`=British, `m`=male/`f`=female). These are the voices this
108
+ tool validates against and shows in `--help`:
109
+
110
+ | American male | American female | British male | British female |
111
+ |---|---|---|---|
112
+ | `am_onyx` | `af_heart` | `bm_george` | `bf_emma` |
113
+ | `am_adam` | `af_bella` | `bm_lewis` | `bf_isabella` |
114
+ | `am_echo` | `af_nova` | `bm_daniel` | `bf_alice` |
115
+ | `am_eric` | `af_sarah` | `bm_fable` | `bf_lily` |
116
+ | `am_fenrir` | `af_sky` | | |
117
+ | `am_liam` | `af_nicole` | | |
118
+ | `am_michael` | `af_aoede` | | |
119
+ | `am_puck` | `af_kore` | | |
120
+
121
+ We don't have authoritative per-voice character descriptions beyond the
122
+ defaults chosen for this project (`af_heart` for most episodes, `am_echo`
123
+ for one recurring series) — try a few on a short script and pick what
124
+ sounds right for your content. Kokoro supports more voices than this list
125
+ (including other languages); this tool's `--voice` choices are
126
+ deliberately limited to a curated English subset. If you need one outside
127
+ this list, it'll need adding to `VOICE_CHOICES` in
128
+ `podcast_generator/generator.py`.
129
+
130
+ **Only voices already in the local Hugging Face cache work on a machine
131
+ with `HF_HUB_OFFLINE=1` set.** On a machine with normal internet access, an
132
+ uncached voice just downloads on first use. See "Offline use" below.
133
+
134
+ ## Scheduled ("drop folder") mode
135
+
136
+ Running `podcast-generator` on a schedule turns the inputs folder into a
137
+ drop folder. Each run converts whatever scripts have arrived since the last
138
+ one and archives them, so nothing is processed twice. Point the folders at
139
+ a cloud-synced location and you can drop a script in from any device and
140
+ get the finished audio back out, with no manual run.
141
+
142
+ The schedule is a Windows scheduled task named `PodcastGeneratorWatch`
143
+ that runs every few minutes. Create it once with the **Set up watch**
144
+ button in the Podcast Watch app below, then use the same app to control
145
+ it. On macOS or Linux, schedule `podcast-generator` with cron or similar
146
+ instead; the app and the scheduled task are Windows-only.
147
+
148
+ Only one conversion runs at a time on a machine. If a run starts while
149
+ another is still converting, for example a scheduled check during a long
150
+ script, it leaves the queue to the run already going and exits.
151
+
152
+ ## The Podcast Watch app
153
+
154
+ A small window for day-to-day control of the drop folder. Open it with
155
+ the `podcast-watch` command, or from a desktop shortcut that
156
+ `podcast-watch --shortcut` creates for you. It's a Windows PowerShell
157
+ script shipped inside the package (`podcast_generator/podcast-watch.ps1`),
158
+ so there's nothing else to install.
159
+
160
+ ![The Podcast Watch window, showing the watch turned on and the three folders](docs/images/podcast-watch.png)
161
+
162
+ The **About** menu at the top shows the version number and the author.
163
+
164
+ ### Folder watch
165
+
166
+ - **Watch is ON / OFF** shows whether the scheduled task is enabled. When
167
+ it's on, the line underneath gives the check interval.
168
+ - **Turn ON / Turn OFF** enables or disables the task. Turning it off only
169
+ stops the schedule. Scripts already in the inputs folder stay there and
170
+ are converted when you turn it back on. Manual `podcast-generator` runs
171
+ work either way.
172
+ - **When this app opens** decides what happens to the watch each time you
173
+ open the window:
174
+ - *Leave watch as it was* (the default) changes nothing.
175
+ - *Turn watch ON* always switches it on, so opening the app is enough to
176
+ start the watch.
177
+ - *Turn watch OFF* always switches it off.
178
+
179
+ The choice is saved immediately to `.env` as `PODCAST_WATCH_ON_OPEN`.
180
+ - **Check every** sets how often the watch looks for new scripts (5 to 60
181
+ minutes). The change applies to the scheduled task straight away and
182
+ doesn't turn the watch on or off.
183
+
184
+ #### Setting up the watch
185
+
186
+ On a machine where the watch has never been set up, the window says
187
+ **Watch not set up** and the button reads **Set up watch**. Pick an
188
+ interval under **Check every**, then click the button. The app registers
189
+ the `PodcastGeneratorWatch` scheduled task and turns it on. The task:
190
+
191
+ - runs under your own Windows account, with no admin rights or stored
192
+ password, and only while you're logged in;
193
+ - runs on battery power as well as when plugged in;
194
+ - runs `pythonw.exe -m podcast_generator` from the Python environment you
195
+ started the app from, so no console window flashes up on each check.
196
+ Each run's output is appended to `generator.log` in the home folder
197
+ (see "Where settings are stored" below).
198
+
199
+ If you later move or recreate that environment, delete the task and set it
200
+ up again from the new one.
201
+
202
+ To remove the task, run
203
+ `Unregister-ScheduledTask -TaskName PodcastGeneratorWatch` in PowerShell
204
+ or delete it in Task Scheduler. The app then offers **Set up watch** again.
205
+
206
+ ### Folders
207
+
208
+ The three folders `podcast-generator` uses, whether it runs on the schedule or
209
+ by hand:
210
+
211
+ | Folder | What goes there |
212
+ |---|---|
213
+ | **Inputs** | Scripts (`.md` files) waiting to be converted. |
214
+ | **Outputs** | Finished audio, named after the script (`my-episode.mp3`). |
215
+ | **Archive** | Scripts after conversion. Scripts that failed go to `failed/` inside it, each with a `.error.txt` note. |
216
+
217
+ For each folder:
218
+
219
+ - **Browse...** picks a different folder and saves it to `.env`.
220
+ - **Open** opens the folder in File Explorer, creating it first if it
221
+ doesn't exist yet.
222
+ - **Use default** removes your choice from `.env`, so the folder goes back
223
+ to the one inside the home folder (`inputs/`, `outputs/`, or
224
+ `script_archive/`). The label then reads "(default)" and the button is
225
+ greyed out.
226
+
227
+ Changes take effect on the next run. There's nothing to restart.
228
+
229
+ ### Where settings are stored
230
+
231
+ Settings live in `.env` in the **home folder**, which also holds the
232
+ default `inputs/`, `outputs/` and `script_archive/` folders and
233
+ `generator.log`. The home folder is:
234
+
235
+ - `podcast_generator` in your Documents folder, for a `pip install`;
236
+ - the repo folder, when running from a source checkout;
237
+ - or whatever `PODCAST_GENERATOR_HOME` points to, if you set that
238
+ environment variable (it can't go in `.env` itself).
239
+
240
+ Everything the app saves goes into that `.env`, the same file
241
+ `podcast-generator` reads:
242
+
243
+ ```
244
+ PODCAST_INPUTS_DIR=C:\Users\yourname\Sync\podcasts\inputs
245
+ PODCAST_OUTPUTS_DIR=C:\Users\yourname\Sync\podcasts\outputs
246
+ PODCAST_ARCHIVE_DIR=C:\Users\yourname\Sync\podcasts\script_archive
247
+ PODCAST_WATCH_ON_OPEN=on
248
+ ```
249
+
250
+ The app changes only these lines and leaves the rest of the file,
251
+ including comments, as it was. You can still edit `.env` by hand;
252
+ [`.env.example`](.env.example) documents every setting. Each machine keeps
253
+ its own `.env`.
254
+
255
+ ## Using it from Claude Desktop (MCP)
256
+
257
+ `podcast-generator-mcp` lets an AI app that supports the
258
+ [Model Context Protocol](https://modelcontextprotocol.io), such as Claude
259
+ Desktop, make podcasts for you. You can ask, for example, "write a
260
+ 10-minute episode explaining how tides work, read by a British voice", and
261
+ the app writes the script, queues it, converts it, and tells you where the
262
+ MP3 is. Everything still runs on your machine, using the same folders and
263
+ `.env` as the command line and the Podcast Watch app.
264
+
265
+ ### Setting it up
266
+
267
+ 1. Install with the `mcp` extra: `pip install "podcast-generator[mcp]"`.
268
+ 2. In Claude Desktop, open Settings → Developer → Edit Config. Add this to
269
+ `claude_desktop_config.json`, with the full path to the command in your
270
+ environment (Claude Desktop doesn't see a virtual environment's `PATH`):
271
+
272
+ ```json
273
+ {
274
+ "mcpServers": {
275
+ "podcast-generator": {
276
+ "command": "C:\\path\\to\\.venv\\Scripts\\podcast-generator-mcp.exe"
277
+ }
278
+ }
279
+ }
280
+ ```
281
+
282
+ If the file already has an `mcpServers` section, add the
283
+ `"podcast-generator"` entry inside it.
284
+ 3. Restart Claude Desktop. The tools appear under the tools (hammer or
285
+ plug) menu in the chat box.
286
+
287
+ Other MCP clients that can start a local (stdio) server use the same
288
+ command.
289
+
290
+ ### Tools
291
+
292
+ | Tool | What it does |
293
+ |---|---|
294
+ | `list_voices` | Lists the voices a script can use. |
295
+ | `queue_podcast` | Saves a script to the inputs folder under a name you choose, optionally setting its voice. It checks the script first: an unknown voice, a script with nothing to speak, or a name that's already taken is reported straight away, not after a failed conversion. Returns the word count and an estimated length. |
296
+ | `generate_now` | Starts converting everything queued, in the background, and returns at once. Does nothing if a conversion is already running. |
297
+ | `podcast_status` | Reports whether a podcast is queued, converting, done (with the file path, size, and length), or failed (with the reason). |
298
+ | `list_podcasts` | Shows the folders, whether the watch is on, what's queued, and recent audio and failures. |
299
+ | `set_watch` | Turns the scheduled watch on or off. |
300
+
301
+ Conversion never happens inside a tool call, because a long script takes
302
+ minutes. If the watch is on, a queued script is picked up on the next
303
+ check. Otherwise the AI calls `generate_now`. Either way, it checks back
304
+ with `podcast_status`. Output from conversions started this way goes to
305
+ `generator.log`.
306
+
307
+ ## Offline use
308
+
309
+ Kokoro downloads its model weights, and each voice pack (e.g.
310
+ `af_heart.pt`), from Hugging Face the first time each is used. They're
311
+ cached in Hugging Face's standard location (`~/.cache/huggingface`), or
312
+ wherever `HF_HOME` points. To run on a machine that can't reach
313
+ huggingface.co:
314
+
315
+ 1. On a machine with internet access, add `HF_HOME=<some folder>` to the
316
+ home folder's `.env`, then convert a short script once with each voice
317
+ you'll need. The model and those voices land in that folder.
318
+ 2. Copy the folder to the offline machine and add `HF_HOME=<where you put
319
+ it>` to that machine's `.env`.
320
+ 3. Add `HF_HUB_OFFLINE=1` to the offline machine's `.env`. Only cached
321
+ voices work there.
322
+ 4. Install spaCy's English model while you have a network connection,
323
+ since `HF_HUB_OFFLINE` doesn't cover it:
324
+ `python -m spacy download en_core_web_sm`.
325
+
326
+ If the network is reachable but an SSL-inspecting corporate proxy breaks
327
+ the downloads (`CERTIFICATE_VERIFY_FAILED`), `pip install pip-system-certs`
328
+ often fixes it by trusting Windows' certificate store.
329
+
330
+ ## Troubleshooting
331
+
332
+ **Podcast Watch says "Watch not set up"**: The `PodcastGeneratorWatch`
333
+ scheduled task doesn't exist on this machine yet. Click **Set up watch**
334
+ (see "Setting up the watch" above).
335
+
336
+ **"Could not set up the podcast watch"**: Windows refused to create the
337
+ task, and the message gives Windows' reason. Some work computers block
338
+ scheduled tasks for ordinary users; if so, run `podcast-generator` by
339
+ hand or ask your IT department.
340
+
341
+ **The podcast-generator tools don't appear in Claude Desktop**: Check that
342
+ the command path in `claude_desktop_config.json` exists, that backslashes
343
+ are doubled, and that you restarted Claude Desktop. Claude Desktop's MCP
344
+ log (Settings → Developer) shows the server's startup errors. A common one
345
+ is "podcast-generator-mcp needs the MCP SDK", fixed by
346
+ `pip install "podcast-generator[mcp]"` in that environment.
347
+
348
+ **"Could not turn the podcast watch on/off"**: Windows refused to change
349
+ the scheduled task, and the message gives Windows' reason. If the task was
350
+ registered by another user or by an administrator, you may need to enable
351
+ or disable it in Task Scheduler instead.
352
+
353
+ **Nothing happens when the watch is on**: Check that a run has had time to
354
+ happen (the app shows the interval), that the script ends in `.md`, and
355
+ that it's in the inputs folder the app shows. If the script was picked up
356
+ but failed, it's in the archive folder's `failed/` subfolder with a
357
+ `.error.txt` note explaining why. Otherwise, look at the end of
358
+ `generator.log` in the home folder for the latest run's output.
359
+
360
+ **"Another conversion run is already in progress"**: Not an error. Only one
361
+ conversion runs at a time; the run already going converts the queue. In
362
+ single-file mode, try again when it finishes.
363
+
364
+ **"No .md scripts found in ..."** — Not an error (exit code 0). The
365
+ inputs folder is just empty right now. Normal for a scheduled run between
366
+ drops.
367
+
368
+ **"script requests unknown voice '...'"** — A `**Voice:**` line names
369
+ something not in the catalog above. Check spelling (underscore, not
370
+ hyphen: `af_heart` not `af-heart`).
371
+
372
+ **"no spoken text found after stripping the markdown"** — Usually means
373
+ an italic-only line partway through the script triggered the
374
+ end-of-script cutoff (see "Writing a script" above) and everything after
375
+ it — possibly the entire rest of the file — got dropped. Check for a
376
+ stray `*line wrapped entirely in asterisks*` before the content you
377
+ expected to hear.
378
+
379
+ **MP3 write fails, mentions ffmpeg** — Your `soundfile`/libsndfile build
380
+ doesn't have native MP3 support and `ffmpeg` isn't on `PATH`. Either
381
+ install `ffmpeg`, or use `--format wav` (needs neither).
382
+
383
+ **First run on a new machine hangs or fails trying to reach the
384
+ network** — A fresh Python environment downloads a small spaCy model (`en_core_web_sm`) on first use, separately from the
385
+ Kokoro model itself. On a firewalled machine, run
386
+ `python -m spacy download en_core_web_sm` ahead of time, while you still
387
+ have a network connection somewhere.
388
+
389
+ **A voice fails with a Hub connection error instead of downloading** —
390
+ `HF_HUB_OFFLINE=1` is set (intentionally, on a firewalled machine) and
391
+ that voice isn't in the local Hugging Face cache. Either populate the cache with
392
+ that voice's `.pt` file from a machine that can reach huggingface.co, or
393
+ unset `HF_HUB_OFFLINE` if this machine actually has internet access.