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.
- podcast_generator-0.3.0/.env.example +15 -0
- podcast_generator-0.3.0/CHANGELOG.md +146 -0
- podcast_generator-0.3.0/LICENSE +21 -0
- podcast_generator-0.3.0/MANIFEST.in +2 -0
- podcast_generator-0.3.0/MANUAL.md +393 -0
- podcast_generator-0.3.0/PKG-INFO +229 -0
- podcast_generator-0.3.0/README.md +198 -0
- podcast_generator-0.3.0/podcast_generator/__init__.py +4 -0
- podcast_generator-0.3.0/podcast_generator/__main__.py +5 -0
- podcast_generator-0.3.0/podcast_generator/generator.py +648 -0
- podcast_generator-0.3.0/podcast_generator/mcp_server.py +386 -0
- podcast_generator-0.3.0/podcast_generator/paths.py +35 -0
- podcast_generator-0.3.0/podcast_generator/podcast-watch.ps1 +395 -0
- podcast_generator-0.3.0/podcast_generator/watch.py +92 -0
- podcast_generator-0.3.0/podcast_generator.egg-info/PKG-INFO +229 -0
- podcast_generator-0.3.0/podcast_generator.egg-info/SOURCES.txt +24 -0
- podcast_generator-0.3.0/podcast_generator.egg-info/dependency_links.txt +1 -0
- podcast_generator-0.3.0/podcast_generator.egg-info/entry_points.txt +4 -0
- podcast_generator-0.3.0/podcast_generator.egg-info/requires.txt +8 -0
- podcast_generator-0.3.0/podcast_generator.egg-info/top_level.txt +1 -0
- podcast_generator-0.3.0/pyproject.toml +67 -0
- podcast_generator-0.3.0/setup.cfg +4 -0
- podcast_generator-0.3.0/tests/conftest.py +26 -0
- podcast_generator-0.3.0/tests/test_jobs.py +99 -0
- podcast_generator-0.3.0/tests/test_paths.py +27 -0
- podcast_generator-0.3.0/tests/test_script_text.py +53 -0
|
@@ -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,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
|
+

|
|
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.
|