{"id":50733391,"url":"https://github.com/john-mustanggt/wkrt-fm","last_synced_at":"2026-06-10T11:01:37.563Z","repository":{"id":353220740,"uuid":"1207024876","full_name":"John-MustangGT/WKRT-FM","owner":"John-MustangGT","description":"AI-powered classic rock internet radio station built with Python, Claude, ffmpeg, and Icecast","archived":false,"fork":false,"pushed_at":"2026-05-20T21:43:31.000Z","size":355,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-05-21T03:34:00.098Z","etag":null,"topics":["ai-dj","anthropic","audio","classic-rock-radio","claude-ai","ffmpeg","icecast","internet-radio","internet-radio-station","music","python","radio","radio-station","raspberry-pi","streaming","text-to-speech","tts"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/John-MustangGT.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-10T13:50:49.000Z","updated_at":"2026-05-20T21:42:32.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/John-MustangGT/WKRT-FM","commit_stats":null,"previous_names":["john-mustanggt/wkrt-fm"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/John-MustangGT/WKRT-FM","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/John-MustangGT%2FWKRT-FM","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/John-MustangGT%2FWKRT-FM/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/John-MustangGT%2FWKRT-FM/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/John-MustangGT%2FWKRT-FM/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/John-MustangGT","download_url":"https://codeload.github.com/John-MustangGT/WKRT-FM/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/John-MustangGT%2FWKRT-FM/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34149132,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-10T02:00:07.152Z","response_time":89,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-dj","anthropic","audio","classic-rock-radio","claude-ai","ffmpeg","icecast","internet-radio","internet-radio-station","music","python","radio","radio-station","raspberry-pi","streaming","text-to-speech","tts"],"created_at":"2026-06-10T11:01:35.233Z","updated_at":"2026-06-10T11:01:37.558Z","avatar_url":"https://github.com/John-MustangGT.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# WKRT-FM 104.7 — Retro 80s Radio Engine\n\nA Python-based internet radio station that plays classic rock, generates AI DJ\nbanter via the Claude API, synthesizes voice via Piper TTS or Google Cloud TTS,\nand streams continuously to one or more Icecast servers.\n\nDesigned to run on a Raspberry Pi Zero 2W or any Linux box.\n\n---\n\n## Features\n\n- **AI DJ rotation** — multiple DJ personalities (Roxanne, Neon) take shifts\n  based on the hour; each has their own voice, persona, and clip-type weights\n- **Six DJ clip types** — between-tracks banter, trivia, dedications, station\n  IDs, top-of-hour, and new-arrival announcements for freshly ingested tracks\n- **Two TTS backends** — local Piper (offline, fast) or Google Cloud TTS\n  (Studio voices, higher quality)\n- **Multi-target Icecast streaming** — stream to your home server, a VPN\n  endpoint, and an external service simultaneously; targets reconnect\n  independently on failure\n- **Live Boston context** — weather and sports scores injected into DJ prompts\n- **Web UI** — listener view at `/`, password-protected admin at `/admin`\n- **Admin controls** — DJ override, queue next song, view connected listeners,\n  kick stale connections\n- **Music ingest pipeline** — drop files into `new_music/`, systemd fires\n  `wkrt_ingest.py`, files land in `music/\u003cyear\u003e/`, and the DJ announces them\n  on-air with a special \"just added to the crate\" break\n- **Startup cache** — pre-generates segments before the first listener connects;\n  engine pauses automatically after all listeners disconnect\n\n---\n\n## Project Structure\n\n```\nwkrt/\n├── config/\n│   └── settings.toml          # All configuration (gitignored — never committed)\n├── music/                     # MP3s organised by year (gitignored)\n│   ├── 1980/\n│   └── ...\n├── new_music/                 # Drop new tracks here for automatic ingestion\n├── spool/                     # Pre-stitched playback segments (auto-cleaned)\n├── dj_clips/                  # TTS output cache (keyed by SHA-256)\n├── voices/                    # Piper voice model files (.onnx)\n├── logs/\n├── templates/                 # Web UI HTML (index.html, admin.html)\n├── wkrt/                      # Python package\n│   ├── config.py              # Settings loader + env-var overrides\n│   ├── playlist.py            # Weighted shuffle queue + ingest crate\n│   ├── dj.py                  # Claude API prompt builder + clip types\n│   ├── tts.py                 # Piper / Google TTS dispatcher + cache\n│   ├── mixer.py               # ffmpeg crossfade / talkover stitching\n│   ├── engine.py              # Main loop, multi-target streaming, ingest API\n│   ├── cache.py               # StartupCache state machine + top-of-hour scheduler\n│   ├── hooks.py               # Icecast on-connect/on-disconnect webhook server\n│   ├── context.py             # Boston weather + sports background fetcher\n│   ├── state.py               # Thread-safe shared station state\n│   └── web.py                 # HTTP server — UI + JSON API\n├── main.py                    # Entry point\n├── wkrt_ingest.py             # Music drop-folder ingest script\n├── wkrt_analyze.py            # Analyse music collection coverage\n├── wkrt_organize.py           # Bulk-organise tracks into year directories\n├── wkrt-fm.service            # systemd unit for the station\n├── wkrt-ingest.path           # systemd path unit — watches new_music/\n├── wkrt-ingest.service        # systemd unit — runs wkrt_ingest.py on change\n└── requirements.txt\n```\n\n---\n\n## Quick Start\n\n```bash\n# 1. Install Python dependencies\npip install -r requirements.txt\n\n# 2. Download Piper voice model\nbash setup_voices.sh\n\n# 3. Copy and edit config\n#    (settings.toml is gitignored — fill in your API keys and paths)\ncp config/settings.toml.example config/settings.toml   # if example exists\n#    or edit config/settings.toml directly\n\n# 4. Organise your music\npython wkrt_organize.py --src /path/to/music --dst ./music --dry-run\npython wkrt_organize.py --src /path/to/music --dst ./music\n\n# 5. Set your Anthropic API key\nexport ANTHROPIC_API_KEY=sk-ant-...\n\n# 6. Run\npython main.py\n```\n\nSee **[INSTALL.md](INSTALL.md)** for full setup including Icecast, systemd,\nGoogle TTS, and the ingest pipeline.\n\n---\n\n## Adding New Music\n\nDrop audio files into `new_music/` and run:\n\n```bash\npython wkrt_ingest.py\n```\n\nThe script reads ID3 tags to find the year, moves the file to `music/\u003cyear\u003e/`,\nand hot-adds it to the running station. The DJ introduces the track on-air with\na special \"just added to the crate\" announcement.\n\nWith the systemd path unit installed this happens automatically whenever a file\nlands in `new_music/`.\n\n---\n\n## Web UI\n\n| URL | Description |\n|-----|-------------|\n| `http://host:8080/` | Listener view — now playing, recent tracks, stream link |\n| `http://host:8080/admin` | Admin panel (password-protected) |\n| `http://host:8080/api/status` | JSON station state |\n| `http://host:8080/api/library` | JSON track library |\n\n---\n\n## CLI Flags\n\n```bash\npython main.py            # run the station\npython main.py --scan     # scan library and print year/track stats\npython main.py --test-dj  # generate one DJ clip end-to-end\npython main.py --test-tts \"Hello WKRT\"  # test TTS only\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohn-mustanggt%2Fwkrt-fm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjohn-mustanggt%2Fwkrt-fm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohn-mustanggt%2Fwkrt-fm/lists"}