{"id":50316468,"url":"https://github.com/msitarzewski/openstudio","last_synced_at":"2026-05-29T00:03:29.517Z","repository":{"id":319944054,"uuid":"1080165657","full_name":"msitarzewski/openstudio","owner":"msitarzewski","description":"OpenStudio is a self-hosted, open-source virtual broadcast studio that puts power back in the hands of creators. Run live call-in shows, multi-host podcasts, and community radio—all from infrastructure you control.","archived":false,"fork":false,"pushed_at":"2026-05-25T10:34:53.000Z","size":1810,"stargazers_count":70,"open_issues_count":0,"forks_count":28,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-05-25T12:24:24.437Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","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/msitarzewski.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","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":"AGENTS.md","dco":null,"cla":null}},"created_at":"2025-10-21T01:01:19.000Z","updated_at":"2026-05-25T10:34:57.000Z","dependencies_parsed_at":"2026-03-13T19:08:14.775Z","dependency_job_id":null,"html_url":"https://github.com/msitarzewski/openstudio","commit_stats":null,"previous_names":["msitarzewski/openstudio"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/msitarzewski/openstudio","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msitarzewski%2Fopenstudio","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msitarzewski%2Fopenstudio/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msitarzewski%2Fopenstudio/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msitarzewski%2Fopenstudio/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/msitarzewski","download_url":"https://codeload.github.com/msitarzewski/openstudio/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/msitarzewski%2Fopenstudio/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33631002,"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-05-28T02:00:06.440Z","response_time":99,"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":[],"created_at":"2026-05-29T00:03:26.752Z","updated_at":"2026-05-29T00:03:29.507Z","avatar_url":"https://github.com/msitarzewski.png","language":"JavaScript","funding_links":["https://github.com/sponsors/msitarzewski"],"categories":[],"sub_categories":[],"readme":"[![CI](https://github.com/msitarzewski/openstudio/actions/workflows/ci.yml/badge.svg)](https://github.com/msitarzewski/openstudio/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) ![Node 18+](https://img.shields.io/badge/node-18%2B-brightgreen) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/msitarzewski/openstudio/pulls) [![Sponsor](https://img.shields.io/badge/Sponsor-%E2%9D%A4-pink?logo=github)](https://github.com/sponsors/msitarzewski)\n\n\u003cdiv align=\"center\"\u003e\n\u003cimg src=\"docs/screenshots/studio.png\" alt=\"OpenStudio broadcast interface\" width=\"720\"\u003e\n\u003c/div\u003e\n\n# OpenStudio\n\n\u003e Your voice. Your frequency. No permission required.\n\n[Try the Live Demo](https://openstudio.zerologic.com) · [Report Bug](https://github.com/msitarzewski/openstudio/issues) · [Sponsor](https://github.com/sponsors/msitarzewski)\n\n---\n\nSomewhere right now, a community radio host is calculating whether they can afford another month of their streaming platform. A podcast collective just lost their entire archive because a service shut down. An independent voice got silenced — not by censorship, but by a credit card expiration.\n\nOpenStudio exists because broadcasting should not require permission. No account creation. No monthly invoice. No terms of service between you and your audience. You clone a repo, you start broadcasting. Your station runs on your hardware. Your audio never touches a server you don't control.\n\nThis is a broadcast studio built the way radio was meant to work — direct, unmediated, yours. Vanilla JavaScript. Web Audio API. No framework, no build step, no dependency you didn't choose. Self-host it on a Raspberry Pi, a $5 VPS, a closet server at the back of your hackerspace. The entire client is under 50KB. If you can run Node, you can run a station — rent free.\n\nConnect guests over WebRTC mesh. Mix-minus gives every participant broadcast-quality monitoring — the same technique used in professional studios, now running in a browser tab. Stream to unlimited listeners through Icecast. Record every voice on its own track for post-production. No platform stands between your signal and the world.\n\n---\n\n## Quick Start\n\n```bash\ngit clone https://github.com/msitarzewski/openstudio.git\ncd openstudio \u0026\u0026 npm install \u0026\u0026 npm start\n# Open http://localhost:6736\n```\n\nOne command. One process. One port.\n\n## Why OpenStudio?\n\n| | OpenStudio | Riverside | Zencastr | StreamYard |\n|---|---|---|---|---|\n| **Price** | Free / self-host | $29/mo | $20/mo | $25/mo |\n| **Recording** | Per-track WAV + mix | Per-track | Per-track | Mix only |\n| **Self-hosted** | Yes | No | No | No |\n| **Privacy** | Zero tracking | Cloud-dependent | Cloud-dependent | Cloud-dependent |\n| **Max participants** | 15 (mesh) | 8 | 15 | 10 |\n| **Setup time** | 30 seconds | Account + payment | Account + payment | Account + payment |\n| **Open source** | MIT | No | No | No |\n\n## How It Works\n\n```mermaid\nflowchart TB\n    subgraph Browsers[\"Browsers — peer-to-peer over WebRTC mesh\"]\n        direction LR\n        B1[\"🎙️ Host\"]\n        B2[\"🎙️ Caller\"]\n        B3[\"🎙️ Caller\"]\n        B1 \u003c--\u003e B2\n        B2 \u003c--\u003e B3\n        B1 \u003c--\u003e B3\n    end\n\n    subgraph Studio[\"Inside each browser\"]\n        WebAudio[\"Web Audio Graph\u003cbr/\u003eMix-Minus + Program Bus\"]\n        Rec[\"MediaRecorder\u003cbr/\u003eper-track + program mix\"]\n        UI[\"Capability-gated UI\u003cbr/\u003emodal explains missing prereqs\"]\n    end\n\n    subgraph Signaling[\"Signaling Server (Node, ~6736)\"]\n        WS[\"WebSocket\u003cbr/\u003eJWT rooms · RBAC · rate limits\"]\n        Static[\"Static files + listener proxy\"]\n        Caps[\"/api/capabilities\"]\n        Export[\"/api/export/\u003cbr/\u003eclean · zip · transcribe · show-notes\"]\n    end\n\n    subgraph AI[\"Optional AI Tooling — capability-gated\"]\n        FF[\"ffmpeg\u003cbr/\u003esilencedetect · loudnorm · MP3\"]\n        Whisper[\"whisper.cpp\u003cbr/\u003eon-device transcription\"]\n        LLM[\"OpenAI-compatible LLM\u003cbr/\u003eLM Studio · Ollama · OpenAI · Groq\"]\n    end\n\n    Icecast[(\"Icecast\u003cbr/\u003ebroadcast to listeners\")]\n\n    Browsers \u003c--\u003e|signaling| WS\n    Browsers --\u003e WebAudio\n    WebAudio --\u003e Rec\n    UI -.reads.-\u003e Caps\n    Rec --\u003e|upload| Export\n    Export --\u003e FF\n    Export --\u003e Whisper\n    Export --\u003e LLM\n    WebAudio --\u003e|live source| Icecast\n    Static --\u003e|/stream/*| Icecast\n    Icecast --\u003e Listeners[[\"📻 Listeners\u003cbr/\u003e(unlimited, anywhere)\"]]\n```\n\nMix-minus is a broadcast engineering standard — each participant hears everyone except themselves. No echo, no feedback. Professional studios have done this with hardware for decades. OpenStudio does it in the browser with the Web Audio API. Everything outside the green AI box is required; the AI tooling is optional and the UI tells you exactly what's missing if you click a gated feature.\n\n## Features\n\n**Broadcast core**\n- WebRTC mesh — peer-to-peer audio, no media server, up to ~15 participants\n- Mix-minus per participant — O(N) phase-inversion, broadcast-standard monitoring with zero echo\n- Per-participant gain, mute, and segmented LED level meters with waveform oscilloscope\n- Microphone input selector — enumerates all OS audio devices\n- Icecast streaming — broadcast to unlimited listeners through your own domain\n- WebSocket streaming fallback for Safari (browsers without ReadableStream upload)\n- Listener proxy at `/stream/*` so the entire app runs on a single port\n\n**Recording \u0026 post-production**\n- Multi-track recording — per-voice WAV/WebM + program mix, all captured client-side\n- Single-zip bundle download for the whole session\n- Audio cleaning pipeline — silence detection, filler-word splice, two-pass loudness normalization to −16 LUFS *(requires ffmpeg + whisper.cpp for the splice transcript)*\n- Raw or Clean export with WAV / WebM / MP3 (podcast-ready) output *(MP3 requires ffmpeg)*\n\n**Optional AI tooling** — capability-gated, runs on your hardware, see setup below\n- On-device transcription via whisper.cpp — no cloud API, no third party *(requires whisper.cpp + Whisper model + ffmpeg)*\n- LLM-generated show notes — episode title, summary, timestamped segment markers *(requires transcription + any OpenAI-compatible LLM endpoint)*\n- Markdown export — copy to clipboard or download as `.md`\n- Gated features show as disabled with an info icon and a setup modal — never a cryptic error\n\n**Security \u0026 ops**\n- JWT room tokens (24 h) and scoped invite tokens (4 h)\n- Role-based access — host, ops, guest (caller) with producer-authoritative mute\n- 256 KB WebSocket message cap, per-IP connection limit, sliding-window rate limits\n- CORS allowlist (open by default for dev), hardened security headers, sanitized listener proxy\n- Self-hosted variable fonts (Inter, JetBrains Mono, Space Grotesk) — no Google Fonts CDN dependency\n\n**Zero dependencies in the browser** — vanilla JavaScript, Web Audio API, no framework, no build step. The entire client is under 50 KB.\n\n## Optional AI Tooling\n\nOpenStudio includes a complete post-production pipeline — transcription, show notes, MP3 export. It's **optional**: broadcasting, recording, per-track downloads, and zip bundles all work without any of this. AI features ship enabled by default but are **capability-gated** — when a prerequisite is missing, the button shows as disabled with an info icon. Click it and a modal explains exactly what to install. Nothing is removed from the UI, nothing fails with a cryptic stack trace.\n\n### Prerequisites\n- `ffmpeg` and `ffprobe` on your `PATH` (most package managers ship both: `brew install ffmpeg`, `apt install ffmpeg`)\n- ~1.5 GB free disk space for the default Whisper model\n\n### whisper.cpp setup (one-time)\n\n```bash\ngit clone https://github.com/ggerganov/whisper.cpp\ncd whisper.cpp \u0026\u0026 make -j$(nproc)\ncd .. \u0026\u0026 mkdir -p models\nwget -O models/ggml-medium.bin https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-medium.bin\n```\n\nYou can swap the model — `ggml-tiny.bin` (~75 MB) is faster but less accurate; `ggml-large.bin` is the other direction. The server looks for `models/ggml-medium.bin` by default.\n\n### LLM Provider Examples\n\nShow notes call any **OpenAI-compatible** chat completions API. Configure via `.env`. Local providers need no API key; cloud providers require `LLM_API_KEY`.\n\n**LM Studio** (default — nothing to configure):\n```bash\n# LM Studio runs at localhost:1234 by default; OpenStudio assumes this.\n# Just start LM Studio and load a chat model. No env vars required.\n```\n\n**Ollama** (with the OpenAI-compatible shim):\n```bash\nLLM_BASE_URL=http://localhost:11434/v1\nLLM_MODEL=llama3.3\n```\n\n**OpenAI**:\n```bash\nLLM_BASE_URL=https://api.openai.com/v1\nLLM_MODEL=gpt-4o-mini\nLLM_API_KEY=sk-...\n```\n\n**Together AI**:\n```bash\nLLM_BASE_URL=https://api.together.xyz/v1\nLLM_MODEL=meta-llama/Llama-3.3-70B-Instruct-Turbo\nLLM_API_KEY=...\n```\n\n**Groq**:\n```bash\nLLM_BASE_URL=https://api.groq.com/openai/v1\nLLM_MODEL=llama-3.3-70b-versatile\nLLM_API_KEY=gsk_...\n```\n\n**Anthropic** — the direct Messages API is not OpenAI-compatible. Run a shim like [`anthropic-openai-compat`](https://github.com/jaykchen/anthropic-openai-compat) or [`litellm`](https://github.com/BerriAI/litellm) in front of it, then point `LLM_BASE_URL` at the shim.\n\n### Feature Gating\n\nAI features are visible in the UI by default. The server publishes a `GET /api/capabilities` snapshot reporting which prerequisites are actually present — ffmpeg, ffprobe, whisper.cpp binary, Whisper model file, configured LLM endpoint. The frontend reads this on load and disables any button whose prereqs are missing. Clicking a gated button opens a modal with the exact install commands. No hidden flags, no \"feature available in pro\" dark patterns — capability is derived from what your runtime can actually do.\n\n### What happens without AI setup\n\nTranscribe, Show Notes, and MP3 export show as disabled until their prereqs are met — clicking them opens the install modal. Every other feature — live broadcast, recording, per-track download, zip bundle, Icecast streaming — works without any AI setup.\n\n## Try It\n\n1. Open **[openstudio.zerologic.com](https://openstudio.zerologic.com)**\n2. Enter a station name and click **Start Broadcast**\n3. Allow microphone access when prompted\n4. Share the invite URL with a co-host (or open it in a second browser tab)\n5. Talk — you'll hear each other with zero echo thanks to mix-minus\n6. Hit **Record** to capture per-voice tracks, then **Download** when done\n\nBroadcasts auto-expire after 15 minutes on the demo. Self-host for unlimited airtime.\n\n## Architecture\n\nFor detailed architecture documentation, see [docs/ARCHITECTURE-IMPLEMENTATION.md](docs/ARCHITECTURE-IMPLEMENTATION.md).\n\n**Stack**: Node.js · WebSocket · WebRTC · Web Audio API · Icecast · coturn\n\n## Roadmap\n\n- [x] **0.1** — Core studio: WebRTC mesh, mix-minus, mute controls, Icecast streaming\n- [x] **0.2** — Single-server deploy, multi-track recording, live demo\n- [x] **0.2.1** — Security hardening: JWT room tokens, rate limiting, CORS, RBAC\n- [x] **0.3** — Power Move: Whisper.cpp transcription, audio cleaning pipeline, Clean/Raw export, self-hosted fonts\n- [x] **0.3.1** — MP3 export fix, single-zip bundle download, configurable LLM endpoint\n- [ ] **0.4** — Invite-link UI, DHT station discovery, Nostr NIP-53 integration, Ed25519 station identities\n- [ ] **0.5** — SFU for larger rooms (25+ participants), soundboard, text chat\n\n## Development\n\n```bash\n# Development mode with hot reload\nnpm run dev\n\n# Run tests\nnpm test\n\n# With Docker services (Icecast + coturn)\ndocker compose up -d\nnpm start\n```\n\nSee [docs/vision.md](docs/vision.md) for the full project vision and philosophy.\n\n## Known Gaps\n\nHonest about what's there and what isn't:\n\n- **Invite-link UI** — the server can mint scoped invite tokens (host / ops / guest, 4 h TTL), but the host UI doesn't expose a button yet. For now, hosts share the room URL manually. Coming in 0.4.\n- **AI pipeline setup is manual** — the whisper.cpp build, model download, and LLM configuration aren't scripted. A `setup-ai.sh` would be a great PR.\n- **whisper.cpp gitlink** — the repo references whisper.cpp as a gitlink without a `.gitmodules` entry. Use the manual `git clone` in the AI setup section above; `git submodule update` will fail.\n- **Mesh scale ceiling** — WebRTC mesh tops out around 15 participants. Larger rooms need an SFU (planned for 0.5).\n\n## Contributing\n\nPRs welcome! Please read the existing code before contributing — the codebase is intentionally minimal.\n\n1. Fork the repo\n2. Create your feature branch (`git checkout -b feature/amazing-feature`)\n3. Commit your changes\n4. Push to the branch\n5. Open a Pull Request\n\n## Sponsor\n\nOpenStudio is free, open-source, and built by independent developers. If it's useful to you, [sponsor the project on GitHub](https://github.com/sponsors/msitarzewski) to keep it that way.\n\n## License\n\n[MIT](LICENSE) — use it however you want.\n\n---\n\n\u003cdiv align=\"center\"\u003e\n\u003cem\u003etalk hard.\u003c/em\u003e\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmsitarzewski%2Fopenstudio","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmsitarzewski%2Fopenstudio","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmsitarzewski%2Fopenstudio/lists"}