Skip to content

herrkaefer/any-podcast

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

101 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Any Podcast

Any Podcast

中文

An AI-powered, configurable podcast platform that aggregates content sources, generates summaries, and produces podcast audio. You can define different sources per topic and build your own podcast flow.

Philosophy

  • Source-configurable, topic-agnostic
  • Automation lowers the barrier to content creation
  • Everyone should be able to run their own podcast pipeline

Tech Stack

  • Runtime: Next.js 15 (App Router) + Cloudflare Workers (via OpenNext)
  • AI: OpenAI / Gemini / MiniMax for content generation
  • TTS: Edge TTS / MiniMax / Murf / Gemini TTS
  • Storage: Cloudflare KV (metadata) + R2 (audio files)
  • UI: Tailwind CSS + shadcn/ui

Workflow Continuation Strategy (Subrequest Budget Guard)

To prevent Too many subrequests failures in long runs (many sources, many stories, many TTS lines), the project now uses a single main workflow with continuation handoff, instead of splitting logic into separate workflow types.

Core approach:

  • Keep one PodcastWorkflow stage flow: collect_candidates -> expand_gmail -> summarize_stories -> compose_text -> tts_render -> done
  • Track a subrequest budget during execution (soft limit 45, reserve 6)
  • Before high-cost work, estimate the next cost; if budget is near the threshold, checkpoint first and spawn a continuation instance
  • The continuation is still the same main workflow, reusing the same jobId and incrementing continuationSeq (instance ID like <jobId>-c<seq>)

State and snapshot storage:

  • KV stores lightweight workflow state (stage, cursor, progress, snapshot keys), under keys like workflow:job:<jobId>:state
  • R2 stores larger stage snapshots under keys like workflow/jobs/<jobId>/*.json
    • candidates.json: candidate stories and pending Gmail expansions
    • summary.json: story summary/relevance results
    • compose.json: composed podcast/blog text and TTS inputs

This allows the run to automatically continue in a fresh workflow instance before hitting the hard limit, without losing progress, while still producing one logical episode output.

Getting Started

Prerequisites

Step 1: Create Your Repository

Click "Use this template" on GitHub to create your own repository, then clone it locally:

git clone https://github.com/<your-username>/<your-repo>.git
cd <your-repo>
pnpm install

Step 2: Create Cloudflare Resources

Log in to Cloudflare and create the required resources:

wrangler login

# Create a KV namespace
wrangler kv namespace create PODCAST_KV
# Note the returned namespace id

# Create an R2 bucket
wrangler r2 bucket create <your-podcast-name>

Step 3: Configure Wrangler

Copy the template files and fill in your resource IDs:

cp wrangler.template.jsonc wrangler.jsonc
cp worker/wrangler.template.jsonc worker/wrangler.jsonc

Edit wrangler.jsonc:

  • name — your podcast app name (e.g. "my-podcast")
  • vars.PODCAST_ID — your podcast identifier (e.g. "my-podcast")
  • kv_namespaces[0].id — the KV namespace ID from Step 2
  • r2_buckets[*].bucket_name — the R2 bucket name from Step 2
  • services[0].service — must match the worker name below

Edit worker/wrangler.jsonc:

  • name — your worker name (e.g. "my-podcast-worker")
  • vars.PODCAST_ID — same as above
  • kv_namespaces[0].id — same KV namespace ID
  • r2_buckets[0].bucket_name — same R2 bucket name
  • triggers.crons — when to auto-generate episodes (default: "5 6 * * *", daily at 06:05 UTC)

These files are listed in .gitignore because they contain account-specific resource IDs.

Step 4: Configure Environment Variables

Copy the example files:

cp .env.local.example .env.local
cp worker/.env.local.example worker/.env.local

Edit .env.local (Next.js app):

Variable Required Description
PODCAST_ID Yes Same as in wrangler config
ADMIN_TOKEN Yes Password for the Admin console (choose a strong value)
NEXT_STATIC_HOST Yes Base URL for audio files. Local dev: http://localhost:3000/static. Production: set in wrangler vars after deployment (see Step 7)
NODE_ENV No Defaults to development locally. Set to production in wrangler vars
PODCAST_WORKER_URL No Worker URL used by the Admin trigger route in production when service binding is unavailable
TRIGGER_TOKEN No Token used by the Admin trigger route when calling the Worker over HTTP in production

Edit worker/.env.local (Worker):

Variable Required Description
PODCAST_ID Yes Same as above
GEMINI_API_KEY Yes Google Gemini API key
MINIMAX_API_KEY No MiniMax API key (if using MiniMax for text generation)
ADMIN_TOKEN Yes Same as above
PODCAST_WORKER_URL Yes Worker URL. Local dev: http://localhost:8787. Production: set in wrangler vars after deployment (see Step 7)
PODCAST_R2_BUCKET_URL Yes R2 bucket public URL. Local dev: http://localhost:8787/static. Production: your R2 custom domain or public URL
OPENAI_API_KEY No OpenAI API key (if using OpenAI)
CLOUDFLARE_ANYPODCAST_ACCOUNT_ID No Cloudflare account ID for markdown extraction. When configured, Cloudflare Markdown APIs are tried before Jina
CLOUDFLARE_ANYPODCAST_API_TOKEN No Cloudflare API token for markdown extraction. Requires Workers AI: Read and Browser Rendering: Edit scopes
JINA_KEY No Jina API key (for web content extraction)
MINIMAX_TTS_GROUP_ID No MiniMax TTS Group ID (required when tts.provider=minimax)
MINIMAX_TTS_API_KEY No MiniMax TTS API key (required when tts.provider=minimax)
MURF_API_KEY No Murf API key (required when tts.provider=murf)
TRIGGER_TOKEN No Token for manually triggering the workflow via curl
GMAIL_* No Gmail OAuth credentials (for newsletter sources)

Business runtime settings such as AI provider/model/base URL, workflow test settings, sources, prompts, and TTS options are configured in Admin and stored in runtime config, not in Worker env variables.

For backward compatibility, the Worker still accepts legacy TTS_API_ID / TTS_API_KEY, but new deployments should use the provider-specific names above.

Step 5: Configure Your Podcast

There are three ways to configure your podcast content and behavior:

Option A: Admin Console (Recommended)

The web-based Admin console lets you configure everything at runtime without touching code:

  1. Start the dev servers (see Step 6) or deploy first
  2. Visit /admin/login and enter your ADMIN_TOKEN
  3. Configure all settings in the UI:
    • Site: title, description, logo, theme color, contact email
    • Hosts: name, gender, persona, speaker marker for each host
    • AI: provider (Gemini/OpenAI/MiniMax), model, API base URL
    • TTS: provider (Gemini/Edge/MiniMax/Murf), language, voice for each host, audio quality, intro music
    • Sources: add RSS feeds, URLs, or Gmail labels
    • Prompts: customize all AI prompts (story summary, podcast dialogue, blog post, intro, title)
    • Locale: language, timezone

All changes are saved to KV and take effect immediately on the next workflow run.

Option B: Source Config File

For content sources, you can define them in code instead of (or in addition to) the Admin console:

cp workflow/sources/config.example.ts workflow/sources/config.local.ts

Edit workflow/sources/config.local.ts to add your RSS feeds, URLs, or Gmail labels. This file is gitignored and takes priority as the default source configuration.

Option C: Static Defaults in Code

The file config.ts contains static defaults for site metadata (title, description, SEO, theme). These are used as fallbacks when no runtime config exists in KV. For most cases, prefer configuring via the Admin console instead.

Step 6: Local Development

# Start the Next.js dev server (port 3000)
pnpm dev

# Start the Worker dev server (port 8787) in another terminal
pnpm dev:worker

# Trigger the workflow manually
curl -X POST http://localhost:8787

Step 7: Deploy

# Deploy the Worker first
pnpm deploy:worker
# The CLI will print the Worker URL, e.g. https://my-podcast-worker.<your-subdomain>.workers.dev

# Set production secrets for the Worker
wrangler secret put GEMINI_API_KEY --cwd worker
wrangler secret put ADMIN_TOKEN --cwd worker
# Add other secrets as needed (OPENAI_API_KEY, MINIMAX_TTS_API_KEY, MURF_API_KEY, etc.)

# Deploy the Next.js app
pnpm run deploy
# The CLI will print the app URL, e.g. https://my-podcast.<your-subdomain>.workers.dev

After the first deploy, you need to set the production URLs that weren't known beforehand. Add them to the vars section of your wrangler config files, then redeploy:

In wrangler.jsonc (Next.js app), add to vars:

"NEXT_STATIC_HOST": "https://my-podcast.<your-subdomain>.workers.dev/static",
"PODCAST_WORKER_URL": "https://my-podcast-worker.<your-subdomain>.workers.dev"

In worker/wrangler.jsonc (Worker), add to vars:

"PODCAST_WORKER_URL": "https://my-podcast-worker.<your-subdomain>.workers.dev",
"PODCAST_R2_BUCKET_URL": "https://<your-r2-public-url>"

Then redeploy both: pnpm deploy:worker && pnpm run deploy

You can also set custom domains for your app and Worker in the Cloudflare dashboard, then use those domains in the vars above.

After deployment:

  1. Your app URL is printed by the deploy command, or find it in the Cloudflare dashboard under Workers & Pages
  2. Go to /admin to configure your podcast via the Admin console
  3. Trigger the first episode from the Admin console: go to /admin, switch to the Testing tab, and click Trigger Workflow — or use curl: curl -X POST <your-worker-url>
  4. The Worker's cron trigger automatically generates new episodes on schedule. The default is daily at 06:05 UTC — configure this in worker/wrangler.jsonc under triggers.crons using standard cron syntax

Running Multiple Podcasts

You can run multiple independent podcasts from the same codebase. Each podcast is a separate Cloudflare deployment with its own configuration. No code changes required.

Setup

  1. Create additional Cloudflare resources (KV namespace, R2 bucket) for the new podcast

  2. Create named wrangler config files for each podcast:

# For a podcast named "my-second"
cp wrangler.template.jsonc wrangler.my-second.jsonc
cp worker/wrangler.template.jsonc worker/wrangler.my-second.jsonc
  1. Fill in the new resource IDs and podcast name in both files

  2. Add the new config files to .gitignore (they contain account-specific IDs)

Switching Between Podcasts

The Next.js dev server (pnpm dev) always reads wrangler.jsonc. To switch which podcast is active locally, add convenience scripts to package.json:

{
  "scripts": {
    "use:first": "cp wrangler.my-first.jsonc wrangler.jsonc && cp worker/wrangler.my-first.jsonc worker/wrangler.jsonc && echo 'Switched to my-first'",
    "use:second": "cp wrangler.my-second.jsonc wrangler.jsonc && cp worker/wrangler.my-second.jsonc worker/wrangler.jsonc && echo 'Switched to my-second'"
  }
}

Then switch and work as usual:

pnpm use:second      # Switch to second podcast
pnpm dev:worker      # Start Worker dev server
pnpm dev             # Start Next.js dev server

Deploying

Switch to the target podcast before deploying:

pnpm use:second      # Switch config
pnpm deploy:worker   # Deploy this podcast's Worker
pnpm run deploy      # Deploy this podcast's Next.js app

Each deployment has its own independent Admin page, prompts, TTS settings, content sources, and data. Configure each podcast via its own Admin console after deployment.

Each podcast instance reads PODCAST_ID from its wrangler config, and all data in KV/R2 is namespaced by podcast ID.

Commands

Command Description
pnpm dev Start Next.js dev server (port 3000)
pnpm dev:worker Start Worker dev server (port 8787)
pnpm build Build the Next.js app
pnpm run deploy Build and deploy the Next.js app
pnpm deploy:worker Deploy the Worker
pnpm logs:worker Tail Worker logs
pnpm use:<name> Switch active podcast config (see Running Multiple Podcasts)
pnpm lint:fix Auto-fix ESLint issues
pnpm tests Run integration tests (requires remote)

Origin

This project evolved from hacker-podcast. Thanks to the original author for open-sourcing it.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors