Commit graph

4 commits

Author SHA1 Message Date
ba98ac4eb4 Add prune_podcast_data.py: clean up podcast data Open Notebook leaves behind
Deleting an episode does not delete its data. DELETE /api/podcasts/episodes/{id}
resolves episode.audio_file and unlinks that one MP3 — the enclosing
data/podcasts/episodes/<uuid>/ directory, holding clips/ (one MP3 per dialogue
segment), outline.json and transcript.json, is never touched. Failed and
/retry-replaced runs leak a directory as well. Nothing reaps any of it: this
deployment had 15 directories on disk against 4 episodes in the database.

That is an upstream gap (roughly a shutil.rmtree of audio_path.parent.parent in
that handler), not something configurable here. Deliberately not patched by
bind-mounting a modified router — that would fork app logic into a deployment
repo and rot silently against pull_policy: always. Local cleanup instead.

scripts/prune_podcast_data.py reconciles the episode list from the API against
the directories on disk and removes:
  - orphaned directories (no corresponding episode), and
  - clips/ of completed episodes, since the clips are intermediate output once
    the final MP3 exists.

Dry-run by default; --yes applies, --keep-clips restricts it to orphans.
Two guards, both tested: it aborts when any job is running/pending (a running
job has no audio_file yet, so its working directory is indistinguishable from an
orphan) and it skips directories touched within the last 60 minutes (--min-age).
It also refuses to act if the API is unreachable, rather than guessing.

Deletion runs inside the container (docker compose exec … rm -rf): the container
writes as root, so the host user cannot remove those directories — a plain
host-side rmtree fails with EPERM after the first directory.

Verified on this deployment: freed 13 MB (10 orphaned dirs + clips of 3 episodes,
49 MB -> 36 MB); all four surviving episodes still stream byte-identical MP3s
from /audio afterwards.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 13:14:25 +02:00
0d8af23598 Add voice cloning to the TTS wrapper, default to the owner's own voice sample
tts_server.py now resolves `voice` against services/voices/*.wav (gitignored,
personal recordings) and clones via chatterbox's audio_prompt_path, falling
back to "default" (symlinked to ~/chatterbox-tts-cli/my_voice_deutsch_60s.wav)
for unknown names, or to the old built-in-voice-by-language-code behavior if
`voice` is a recognized language. All podcast speaker profiles now point at
voice_id "default" until distinct per-speaker reference clips are added.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 22:38:03 +02:00
fb1fbcfabe Add local TTS/STT wrapper servers (chatterbox, faster-whisper) as openai_compatible providers
Wraps the locally installed chatterbox-tts and faster-whisper packages in thin
FastAPI servers implementing OpenAI's audio API shape, since neither Ollama nor
OpenRouter support speech. Pinned to GPU 2 to avoid contending with Ollama's
resident models on GPU 1. Requires UFW rules for 8901/8902 (same pattern as
the existing 11434 rule) since UFW defaults to deny-incoming.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 21:41:44 +02:00
52f99c51ba Initial setup: docker-compose for Open Notebook (SurrealDB + open_notebook), Ollama + OpenRouter as AI providers
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 21:05:23 +02:00