{"id":50412260,"url":"https://github.com/ryanbbrown/kindle-storyteller","last_synced_at":"2026-05-31T04:04:11.820Z","repository":{"id":321064537,"uuid":"1083880283","full_name":"ryanbbrown/kindle-storyteller","owner":"ryanbbrown","description":"iOS app for generating on-demand, live-syncing audiobook snippets using AI","archived":false,"fork":false,"pushed_at":"2026-03-04T00:51:36.000Z","size":4052,"stargazers_count":9,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-03-04T05:35:37.335Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","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/ryanbbrown.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":"2025-10-26T22:13:54.000Z","updated_at":"2026-03-04T00:51:40.000Z","dependencies_parsed_at":"2026-02-09T02:02:44.574Z","dependency_job_id":null,"html_url":"https://github.com/ryanbbrown/kindle-storyteller","commit_stats":null,"previous_names":["ryanbbrown/kindle-ai-audiobook","ryanbbrown/kindle-storyteller"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ryanbbrown/kindle-storyteller","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanbbrown%2Fkindle-storyteller","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanbbrown%2Fkindle-storyteller/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanbbrown%2Fkindle-storyteller/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanbbrown%2Fkindle-storyteller/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ryanbbrown","download_url":"https://codeload.github.com/ryanbbrown/kindle-storyteller/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ryanbbrown%2Fkindle-storyteller/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33718481,"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-31T02:00:06.040Z","response_time":95,"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-31T04:04:11.062Z","updated_at":"2026-05-31T04:04:11.811Z","avatar_url":"https://github.com/ryanbbrown.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Kindle Storyteller\n\nAn iOS app that uses AI to generate on-demand audiobook snippets for Kindle.\n\n*Read the [blog post](https://blog.ryanbbrown.com/p/i-reverse-engineered-kindle-to-build) for a detailed writeup on the technical challenges and how it works.*\n\nhttps://github.com/user-attachments/assets/de0f5640-5bfc-4f0e-bfa0-ee382e467fc2\n\n## 0.0 Overview\n### 0.1 Features\n\n- **On-demand audiobook generation** – Generate audio for any Kindle book from your current reading position\n- **Multiple TTS providers** – Choose between ElevenLabs and Cartesia voices\n- **Configurable duration** – Specify how many minutes of audio to generate (1-8 at a time)\n- **Auto-seek playback** – When resuming, audio automatically seeks to match your current Kindle position\n- **Bidirectional progress sync** – Listening progress syncs back to Kindle in real-time\n- **LLM text preprocessing** – Optional GPT-based cleanup of OCR text before synthesis, including custom pauses for Cartesia\n- **Audiobook library** – Browse and replay previously generated audiobooks\n- **Background playback** – Lock screen controls and background audio support\n\n### 0.2 Context\n\nI usually read on a Kindle Paperwhite, but when I don't have it with me I use the Kindle app on my phone. Sometimes I want to continue reading after I have to stop using my phone (e.g. I get off the subway and walk to my destination); an audiobook would be perfect for this.\n\nHowever, I don't normally listen to audiobooks, so I wouldn't want to purchase the audiobook version of my book just for a few minutes of listening when I can't use my phone. Audiobooks on Kindle also only sync with the printed book when Whispersync-for-Voice is enabled. These two factors led me to build this app.\n\n## 1.0 Components\n\n- `ios-app`: SwiftUI client that captures Kindle session info, and drives audiobook genertion, listening, and management. See [ios-app-architecture.md](ios-app-architecture.md).\n- `server`: Fastify backend orchestrating Kindle fetches and text extraction. Uses my fork of [`kindle-api`](https://github.com/ryanbbrown/kindle-api) for Kindle interactions. See [server-architecture.md](server-architecture.md).\n- `text-extraction-stubs`: Stub interfaces for the text extraction module (implementation not included).\n- `tls-client-api`: Submoduled TLS proxy binary used by the backend for Amazon requests ([repo](https://github.com/bogdanfinn/tls-client-api)).\n\n## 2.0 Setup\n\nClone the repository with submodules:\n```bash\ngit clone --recursive https://github.com/ryanbbrown/kindle-storyteller.git\n```\n\n### 2.1 Testing Environments\n\nThere are three ways to test the app (iPhone simulator on Mac, iPhone on local network, or iPhone against production), so you need to tell the iOS app which server to connect to. The API base URL is stored in `ios-app/KindleAudioApp/Config.xcconfig` (git-ignored). `API_BASE_HOST` should be one of:\n- `localhost:3000` – local development with simulator\n- `\u003cyour-mac-ip\u003e:3000` – testing on a physical iPhone connected to the same network\n- `\u003cyour-fly-app\u003e.fly.dev` – production Fly.io deployment, works for simulator or actual iPhone\n\nFor running on a physical iPhone, see [4.0 iOS App Installation](#40-ios-app-installation-iphone).\n\n### 2.2 Environment Variables\n\nCopy the example config files and fill in your values:\n- `server/.env.example` → `server/.env`\n- `text-extraction/.env.example` → `text-extraction/.env`\n- `ios-app/KindleAudioApp/Config.xcconfig.example` → `ios-app/KindleAudioApp/Config.xcconfig`\n\n**Server (`server/.env`):**\n- `SERVER_API_KEY` – Secret key for authenticating iOS client requests. Set to any secure random string, then configure the same value in `Config.xcconfig`.\n- `TLS_SERVER_API_KEY` – API key for the TLS proxy (can be any string).\n- `ELEVENLABS_API_KEY` – [ElevenLabs](https://elevenlabs.io/) API key for text-to-speech.\n- `CARTESIA_API_KEY` – [Cartesia](https://cartesia.ai/) API key for text-to-speech.\n- `OPENAI_API_KEY` – [OpenAI](https://platform.openai.com/) API key for LLM-based text preprocessing.\n\n**Text extraction (`text-extraction/.env`):**\n- `OCRSPACE_API_KEY` – [OCR.space](https://ocr.space/) API key for text extraction from images.\n\n**iOS app (`ios-app/KindleAudioApp/Config.xcconfig`):**\n- `API_BASE_HOST` – Server hostname (see Testing Environments above).\n- `SERVER_API_KEY` – Must match the server's `SERVER_API_KEY`.\n\n### 2.3 TLS Proxy Setup\nThe TLS proxy requires Go to build from source:\n1. Build the binaries:\n   ```bash\n   cd tls-client-api/cmd/tls-client-api \u0026\u0026 ./build.sh\n   ```\n2. Copy the config template and add your API key:\n   ```bash\n   cp tls-client-api/cmd/tls-client-api/config.dist.yml tls-client-api/dist/config.yml\n   ```\n3. Edit `tls-client-api/dist/config.yml` and add your `TLS_SERVER_API_KEY` to the `api_auth_keys` array.\n\n### 2.4 Text Extraction Setup\nThe `text-extraction-stubs` directory contains interface definitions for text extraction functionality. \n\n**This application requires a working text extraction module that is not included in this repository.**\n\nSee `text-extraction-stubs/README.md` for the expected interface and return types.\n\n## 3.0 Running\n\n### 3.1 Running Locally\n1. Start the TLS proxy: `cd tls-client-api/dist \u0026\u0026 ./tls-client-api-darwin-arm64-`\n2. Run the Fastify backend: `cd server \u0026\u0026 pnpm dev`\n3. With both services running, open the iOS project in Xcode and build + run it.\n\n### 3.2 Deploying to Fly.io\nThe repository includes a multi-stage `Dockerfile`, process supervisor (`start.sh`), and `fly.toml` to run the Fastify backend, TLS proxy, and text-extraction pipeline in a single Fly machine.\n\n1. **Create the Fly app**\n   ```bash\n   fly launch --no-deploy --copy-config\n   ```\n   Adjust the generated app name/region inside `fly.toml` if needed.\n\n2. **Configure secrets** – set the server API key, TLS proxy key, TTS keys, LLM key, and OCR key:\n   ```bash\n   fly secrets set \\\n     SERVER_API_KEY=\"your-key\" \\\n     TLS_SERVER_API_KEY=\"your-key\" \\\n     ELEVENLABS_API_KEY=\"your-key\" \\\n     CARTESIA_API_KEY=\"your-key\" \\\n     OPENAI_API_KEY=\"your-key\" \\\n     OCRSPACE_API_KEY=\"your-key\"\n   ```\n   Or, if you've already configured `server/.env`, set all secrets from it:\n   ```bash\n   grep -v '^#' server/.env | grep '=' | xargs fly secrets set\n   ```\n   All Kindle cookies/tokens must come from the iOS client; there's no server-side fallback.\n\n3. **Deploy**\n   ```bash\n   fly deploy --remote-only\n   ```\n   The container installs Node 20, Python 3.12 + uv, and builds the Go TLS proxy. The `start.sh` entrypoint boots the TLS proxy first, then starts the Fastify server.\n\n4. **Point the iOS app at Fly** – update `API_BASE_HOST` in your `Config.xcconfig` to your Fly hostname.\n\n## 4.0 iOS App Installation (iPhone)\nIf you want to use the app on an iPhone instead of the simulator, there are steps to follow on Mac. \n\nNot all of these are necessary if you've done iOS app development on your Mac before, and note that I don't use a paid developer account, so the app has to be re-signed every 7 days.\n\n### 4.1 Add Apple ID to Xcode\n\u003cdetails\u003e\n  \u003csummary\u003eExpand to see steps\u003c/summary\u003e\n\n- Open Xcode\n- Click \"Xcode\" -\u003e \"Settings\"\n- Click \"Apple Accounts\"\n- Add your apple account\n- Click on the account, then \"Personal Team\", then \"Manage Certificates...\"\n- If a certificate doesn't exist, click the \"+\" in the bottom left, then \"Apple Development\" to create a new certificate\n\u003c/details\u003e\n\n\n\n### 4.2 Configure project\n\u003cdetails\u003e\n  \u003csummary\u003eExpand to see steps\u003c/summary\u003e\n\n- Open the KindleAudioApp project in Xcode\n- In the navigation bar on the left, select the top-level \"KindleAudioApp\"\n- Go to the \"Signing \u0026 Capabilities\" tab\n- Under \"Team\", select your \"Personal Team\" (your Apple ID) that you added.\n- Ensure \"Automatically manage signing\" is enabled.\n- Set \"Bundle Identifier\" to something unique; I used com.example.KindleAudioApp (probably should change lol)\n- Near the top left, click \"+ Capability\", then click on the \"Background Modes\" capability to add it\n- Expand it and check the \"Audio, AirPlay, and Picture in Picture\" box\n\u003c/details\u003e\n\n### 4.3 Connect iPhone\n\u003cdetails\u003e\n  \u003csummary\u003eExpand to see steps\u003c/summary\u003e\n\n- Plug your iPhone into your Mac\n- Click \"Trust\" if you haven't already\n- In center top bar of Xcode, click on the device selector and select your iPhone\n- Click the Run (▶) button\n- You'll receive a pop-up that says \"Developer Mode disabled\"; go to Settings -\u003e Privacy \u0026 Security, scroll all the way down, click on \"Developer Mode\", then enable it, restart your phone, and accept any prompts\n- Click the Run button again if needed\n\u003c/details\u003e\n\n## 5.0 Disclaimer\n\nThis project is a personal proof-of-concept for generating short audio snippets from books you own. It's intended for temporary, on-the-go listening when you can't look at a screen—not as a replacement for purchasing audiobooks.\n\nIf you enjoy a book in audio form, please support the author and narrator by buying the official audiobook. Professional narrators bring craft and interpretation that AI-generated speech can't replicate.\n\nUse this tool only with content you've legitimately purchased, and at your own risk.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fryanbbrown%2Fkindle-storyteller","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fryanbbrown%2Fkindle-storyteller","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fryanbbrown%2Fkindle-storyteller/lists"}