{"id":51100973,"url":"https://github.com/biomathcode/bulbul-studio","last_synced_at":"2026-06-24T10:32:10.607Z","repository":{"id":357246807,"uuid":"1163069855","full_name":"biomathcode/bulbul-studio","owner":"biomathcode","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-06T18:24:54.000Z","size":4576,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-02T15:07:19.518Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/biomathcode.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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-02-21T03:43:10.000Z","updated_at":"2026-05-06T18:24:58.000Z","dependencies_parsed_at":"2026-05-12T02:04:19.352Z","dependency_job_id":null,"html_url":"https://github.com/biomathcode/bulbul-studio","commit_stats":null,"previous_names":["biomathcode/bulbul-studio"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/biomathcode/bulbul-studio","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/biomathcode%2Fbulbul-studio","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/biomathcode%2Fbulbul-studio/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/biomathcode%2Fbulbul-studio/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/biomathcode%2Fbulbul-studio/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/biomathcode","download_url":"https://codeload.github.com/biomathcode/bulbul-studio/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/biomathcode%2Fbulbul-studio/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34726699,"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-24T02:00:07.484Z","response_time":106,"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-06-24T10:32:08.529Z","updated_at":"2026-06-24T10:32:10.600Z","avatar_url":"https://github.com/biomathcode.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Bulbul Studio\n\n![Active Development](https://img.shields.io/badge/status-active%20development-orange)\n![Not Production Ready](https://img.shields.io/badge/production-not%20ready-red)\n![Cloudflare Workers](https://img.shields.io/badge/deploy-cloudflare%20workers-F38020?logo=cloudflare\u0026logoColor=white)\n![Node Version](https://img.shields.io/badge/node-%3E%3D20.19-339933?logo=nodedotjs\u0026logoColor=white)\n![TypeScript](https://img.shields.io/badge/typescript-5.x-3178C6?logo=typescript\u0026logoColor=white)\n\n\u003e **Project status:** This project is in **active development** and is **not ready for production use**.\n\n\n\n[![Watch the video](https://img.youtube.com/vi/I3AylyKhGMs/0.jpg)](https://youtu.be/I3AylyKhGMs)\n\nBulbul Studio is a TanStack Start + React app for creating multi-character audio stories with Sarvam Bulbul voices, a notebook-style script editor, and timeline-based audio composition.\n\n## Table of Contents\n- [Bulbul Studio](#bulbul-studio)\n  - [Table of Contents](#table-of-contents)\n  - [Development](#development)\n  - [What It Does](#what-it-does)\n  - [Tech Stack](#tech-stack)\n  - [Architecture](#architecture)\n    - [High-Level System Diagram](#high-level-system-diagram)\n    - [Story Creation Flow](#story-creation-flow)\n    - [Audio Persistence Decision Flow](#audio-persistence-decision-flow)\n  - [Project Structure](#project-structure)\n  - [Getting Started (Local)](#getting-started-local)\n    - [Prerequisites](#prerequisites)\n    - [1) Install dependencies](#1-install-dependencies)\n    - [2) Configure environment](#2-configure-environment)\n    - [3) Run database migrations](#3-run-database-migrations)\n    - [4) Start development server](#4-start-development-server)\n  - [Environment Variables](#environment-variables)\n  - [Database Setup (Postgres + Drizzle)](#database-setup-postgres--drizzle)\n    - [Current DB model](#current-db-model)\n    - [Migration workflow](#migration-workflow)\n  - [Cloudflare Deployment](#cloudflare-deployment)\n    - [1) Authenticate Wrangler](#1-authenticate-wrangler)\n    - [2) Set production secrets](#2-set-production-secrets)\n    - [3) Build and deploy](#3-build-and-deploy)\n    - [4) Post-deploy checks](#4-post-deploy-checks)\n    - [Optional: Cloudflare type generation](#optional-cloudflare-type-generation)\n  - [API Surface](#api-surface)\n  - [Security Notes](#security-notes)\n  - [Troubleshooting](#troubleshooting)\n\n## Development \n\nIn `vite.config.ts`, Comment out the Cloudflare plugin to run locally without Wrangler:\n\n```ts\n// cloudflare({ viteEnvironment: { name: \"ssr\" } }),\n```\n\nThen run \n```bash\nnpm run dev\n```\n\n\n\n\n## What It Does\n- Creates story workspaces from prompts, including multilingual prompts and language-aware script generation.\n- Generates multi-character dialogue scripts with narrator + cast members.\n- Generates TTS audio using Sarvam (`bulbul:v3`) per dialogue line.\n- Supports transliteration-based input for supported Indic languages.\n- Supports local persistence (IndexedDB) and optional cloud upload of generated/recorded audio.\n- Supports cloud providers: Cloudinary, Cloudflare R2, AWS S3, Azure Blob.\n\n## Tech Stack\n- **Frontend/App framework:** TanStack Start, TanStack Router, React 19\n- **Styling/UI:** Tailwind CSS v4, Radix UI, Lucide icons\n- **Audio:** `@waveform-playlist/browser`, Tone.js\n- **Auth:** Better Auth (email/password), cookie-based session handling\n- **Database:** Neon Postgres + Drizzle ORM\n- **Deployment runtime:** Cloudflare Workers (via `@cloudflare/vite-plugin` + Wrangler)\n- **AI/TTS:** Sarvam AI SDK (`sarvamai`)\n\n## Architecture\n\n### High-Level System Diagram\n```mermaid\ngraph LR\n  U[User Browser]\n  A[TanStack Start App\u003cbr/\u003eRoutes + UI]\n  API[Server Routes\u003cbr/\u003e/api/*]\n  ST[Story Service]\n  TT[Sarvam TTS / Chat]\n  DB[(Neon Postgres)]\n  IDB[(IndexedDB)]\n  CS[(Cloud Storage\u003cbr/\u003eR2/S3/Azure/Cloudinary)]\n  AU[Better Auth]\n\n  U --\u003e A\n  A --\u003e API\n  API --\u003e ST\n  API --\u003e AU\n  ST --\u003e TT\n  ST --\u003e DB\n  API --\u003e DB\n  API --\u003e CS\n  A --\u003e IDB\n```\n\n### Story Creation Flow\n```mermaid\nsequenceDiagram\n  participant User\n  participant Home as Home Route (/)\n  participant StoriesAPI as POST /api/stories\n  participant Service as stories/service.ts\n  participant Generator as stories/generator.ts\n  participant Sarvam as Sarvam Chat API\n  participant DB as Neon Postgres\n\n  User-\u003e\u003eHome: Enter prompt + language\n  Home-\u003e\u003eStoriesAPI: POST { prompt, languageCode }\n  StoriesAPI-\u003e\u003eService: createStoryForUser(...)\n  Service-\u003e\u003eGenerator: generateInitialStoryScript(...)\n  Generator-\u003e\u003eSarvam: chat.completions()\n  Sarvam--\u003e\u003eGenerator: JSON story script\n  Generator--\u003e\u003eService: normalized StoryScript\n  Service-\u003e\u003eDB: insert story row\n  DB--\u003e\u003eService: created story\n  Service--\u003e\u003eStoriesAPI: StoryDetail\n  StoriesAPI--\u003e\u003eHome: 201 Created\n```\n\n### Audio Persistence Decision Flow\n```mermaid\nflowchart TD\n  A[Generate/Upload audio request] --\u003e B{Storage mode}\n  B --\u003e|local| C[Persist to IndexedDB]\n  B --\u003e|cloud| D[Resolve active integration]\n  D --\u003e E{Integration valid?}\n  E --\u003e|yes| F[Upload to cloud provider]\n  E --\u003e|no or error| G[Fallback to IndexedDB]\n```\n\n## Project Structure\n```text\nsrc/\n  routes/\n    index.tsx                      # Home: prompt + story creation\n    stories.$id.tsx                # Story workspace\n    integrations.tsx               # Storage integrations UI\n    api/\n      auth/$.ts                    # Better Auth handler\n      stories.ts                   # List/create stories\n      stories.$id.ts               # Get/update story script\n      audio/generate.ts            # TTS generation\n      audio/upload-recording.ts    # Recording upload metadata path\n      storage/preferences.ts       # Storage mode + active provider\n      storage/integrations.ts      # List provider integrations\n      storage/integrations.$provider.ts\n      storage/assets.ts            # List cloud assets\n  server/\n    stories/                       # Story generation + DB service\n    storage/                       # Cloud provider integration layer\n    audio/tts.ts                   # Sarvam TTS integration\n    auth/session.ts                # Auth session helpers\n    security/encryption.ts         # AES-256-GCM secret encryption\n  db/schema.ts                     # Drizzle schema\ndrizzle/                           # SQL migrations + snapshots\n```\n\n## Getting Started (Local)\n### Prerequisites\n- Node.js **20.19+** (or 22.12+)\n- npm\n- A Postgres database (Neon recommended)\n- Sarvam API key\n\n### 1) Install dependencies\n```bash\nnpm install\n```\n\n### 2) Configure environment\n```bash\ncp .env.example .env\n```\n\nGenerate an encryption key (must decode to 32 bytes):\n```bash\nnode -e \"console.log(require('node:crypto').randomBytes(32).toString('base64'))\"\n```\n\nSet all required values in `.env`.\n\n### 3) Run database migrations\n```bash\nnpx drizzle-kit migrate\n```\n\n### 4) Start development server\n```bash\nnpm run dev\n```\n\nApp runs on `http://localhost:3000`.\n\n## Environment Variables\nRequired server variables (validated in `src/server/config/env.ts`):\n\n- `DATABASE_URL`\n- `BETTER_AUTH_SECRET`\n- `BETTER_AUTH_URL`\n- `APP_ENCRYPTION_KEY_B64` (base64-encoded 32-byte key)\n- `SARVAM_API_KEY`\n\nReference: `.env.example`.\n\n## Database Setup (Postgres + Drizzle)\n\n### Current DB model\nMain tables (defined in `src/db/schema.ts`):\n- Auth: `user`, `session`, `account`, `verification`\n- Story: `stories`\n- Storage settings: `user_storage_preferences`, `storage_integrations`, `cloud_audio_assets`\n\nEnums:\n- `storage_mode`: `local | cloud`\n- `storage_provider`: `cloudinary | r2 | s3 | azure`\n- `integration_status`: `active | invalid | disabled`\n- `audio_source`: `generated | recorded`\n\n### Migration workflow\nGenerate migration after schema changes:\n```bash\nnpx drizzle-kit generate\n```\n\nApply migrations:\n```bash\nnpx drizzle-kit migrate\n```\n\nDrizzle config is in `drizzle.config.ts`, migrations are stored in `drizzle/`.\n\n## Cloudflare Deployment\nThis project is configured to deploy to **Cloudflare Workers**.\n\n### 1) Authenticate Wrangler\n```bash\nnpx wrangler login\n```\n\n### 2) Set production secrets\nUse Wrangler secrets for sensitive values:\n```bash\nnpx wrangler secret put DATABASE_URL\nnpx wrangler secret put BETTER_AUTH_SECRET\nnpx wrangler secret put BETTER_AUTH_URL\nnpx wrangler secret put APP_ENCRYPTION_KEY_B64\nnpx wrangler secret put SARVAM_API_KEY\n```\n\n### 3) Build and deploy\n```bash\nnpm run deploy\n```\n\nThis runs:\n- `npm run build` (`vite build \u0026\u0026 tsc --noEmit`)\n- `wrangler deploy`\n\n### 4) Post-deploy checks\n- Confirm auth callback/session URLs use deployed origin (`BETTER_AUTH_URL`).\n- Validate story creation and audio generation from deployed URL.\n- If using cloud mode, configure providers in `/integrations` and verify upload works.\n\n### Optional: Cloudflare type generation\n```bash\nnpm run cf-typegen\n```\n\n## API Surface\nAuthenticated endpoints (require valid session):\n\n- `GET /api/stories` - list user stories\n- `POST /api/stories` - create story from prompt + `languageCode`\n- `GET /api/stories/:id` - fetch story workspace data\n- `PUT /api/stories/:id` - save normalized story script\n- `POST /api/audio/generate` - generate TTS + persist\n- `POST /api/audio/upload-recording` - persist uploaded recording metadata\n- `GET /api/storage/preferences` - fetch storage mode and active provider\n- `PUT /api/storage/preferences` - update storage mode/provider\n- `GET /api/storage/integrations` - list provider integrations\n- `PUT /api/storage/integrations/:provider` - upsert provider config + validate\n- `DELETE /api/storage/integrations/:provider` - remove provider integration\n- `GET /api/storage/assets` - list cloud assets\n\nAuth endpoint:\n- `ALL /api/auth/*` - Better Auth handler\n\n## Security Notes\n- Secrets for storage providers are encrypted at rest using AES-256-GCM (`src/server/security/encryption.ts`).\n- Do **not** commit real credentials to source control.\n- Rotate any leaked keys immediately (DB URLs, API keys, auth secrets).\n- For production hardening, add:\n  - CI checks and automated tests\n  - Rate limiting for API routes\n  - stricter auth/session policies and audit logging\n\n## Troubleshooting\n- **Build fails with Node version error:** upgrade to Node 20.19+.\n- **`APP_ENCRYPTION_KEY_B64` invalid:** ensure it decodes to exactly 32 bytes.\n- **Story/audio APIs return 401:** check login state and `BETTER_AUTH_URL` alignment.\n- **Cloud uploads fallback to local:** verify storage integration status in `/integrations` and credentials.\n\n---\n\nIf you are evaluating this project, treat it as a fast-moving prototype until production-readiness work is completed.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbiomathcode%2Fbulbul-studio","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbiomathcode%2Fbulbul-studio","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbiomathcode%2Fbulbul-studio/lists"}