{"slug": "show-hn-e3d-pod2vid-ai-pipeline-that-turns-podcasts-into-youtube-ready-videos", "title": "Show HN: E3d-pod2vid – AI pipeline that turns podcasts into YouTube-ready videos", "summary": "A developer released e3d-pod2vid, an open-source AI pipeline that converts diarized podcast audio into YouTube-ready videos with semantically matched B-roll, burned-in subtitles, and optional TTS voice replacement. The tool also handles YouTube uploads and multi-platform social posting, aiming to streamline podcast-to-video production for creators.", "body_md": "**AI-powered podcast-to-video pipeline.** Converts a diarized audio file (NotebookLM, podcast, interview) into a YouTube-ready MP4 with:\n\n- Semantically matched Pexels B-roll per utterance (GPT-4o-mini picks the clip)\n- Burned-in subtitles (no ffmpeg libass required — pure Pillow)\n- Optional OpenAI TTS voice replacement (swap out NotebookLM / AI voices)\n- YouTube upload + description/thumbnail update\n- One-shot multi-platform social posting (Discord, Telegram, X, Moltbook, LinkedIn)\n\n```\ngit clone https://github.com/spacepacket1/e3d-pod2vid.git\ncd e3d-pod2vid\n\n# Python deps\npip install -r requirements.txt\n\n# Node deps (YouTube + social posting only)\nnpm install\n\n# Copy and fill in your API keys\ncp .env.example .env\n$EDITOR .env\npython3 pod2vid.py episode.m4a output/episode.mp4\n```\n\nThis single command:\n\n- Uploads audio to AssemblyAI for speaker diarization\n- Asks GPT-4o-mini for a specific Pexels search query per utterance\n- Downloads matching B-roll clips (cached per query)\n- Renders each segment with burned-in subtitles\n- Concatenates into a final MP4 + SRT subtitle file\n\nCaches diarization and queries as JSON so re-runs are fast.\n\nIf you want custom voices instead of the original audio (e.g. replace NotebookLM voices):\n\n```\n# Synthesize with OpenAI TTS voices\npython3 tts_replace.py output/episode-diarization.json episode-tts\n\n# Render video using TTS audio\npython3 pod2vid.py output/episode-tts.mp3 output/episode-tts.mp4\n```\n\nDefault voices: **onyx** (Speaker A) and **nova** (Speaker B). Override with `VOICE_A`\n\n/ `VOICE_B`\n\n.\n\nAvailable voices: `alloy`\n\n, `echo`\n\n, `fable`\n\n, `onyx`\n\n, `nova`\n\n, `shimmer`\n\n```\npython3 make_thumbnail.py \"Predictive GPS for Autonomous AI Agents\" thumbnail.png /path/to/logo.png\n```\n\nOutputs a 1280×720 PNG with title, accent stripe, and optional logo overlay. Pure Pillow — no browser or design tool required.\n\n**First time: authorize your account**\n\n```\nnode yt_auth.js\n```\n\nThe script prints a URL. Open it on any device (phone, browser — the machine running the script doesn't need a browser). After approving, paste the redirect URL back into the terminal. Tokens are saved to `youtube-tokens.json`\n\n.\n\n**Upload the video**\n\n```\nnode yt_upload.js output/episode-tts.mp4 \"My Episode Title\"\n```\n\nPrints the video URL and ID when done.\n\n**Update description and thumbnail**\n\n```\nYT_DESCRIPTION=\"Check out maps.e3d.ai — AI-powered GPS for autonomous vehicles.\n\nFollow us:\n• X: @e3dmaps\n• Discord: https://discord.gg/your-server\" \\\nnode yt_update.js VIDEO_ID thumbnail.png\nnode announce.js https://www.youtube.com/watch?v=VIDEO_ID \"New episode: Predictive GPS for Autonomous AI Agents\"\n```\n\nPosts simultaneously to all configured platforms. Platforms with no credentials are silently skipped.\n\n| Platform | Credential(s) needed |\n|---|---|\n| Discord | `DISCORD_BOT_TOKEN` + `DISCORD_CHANNEL_ID` |\n| Telegram | `TELEGRAM_BOT_TOKEN` + `TELEGRAM_CHAT_ID` |\n| X (Twitter) | `X_ACCESS_TOKEN` |\n| Moltbook | `MOLTBOOK_API_KEY` |\n`linkedin-tokens.json` with `person_urn` (run `node linkedin_auth.js` ) |\n\nLinkedIn's API requires a few one-time setup steps before `announce.js`\n\ncan post there.\n\n**Step 1 — Create a LinkedIn app**\n\nGo to [linkedin.com/developers/apps](https://www.linkedin.com/developers/apps/new) and create an app. Under the **Auth** tab, add this as an authorized redirect URL:\n\n```\nhttps://www.linkedin.com/developers/tools/oauth/redirect\n```\n\n**Step 2 — Add required products**\n\nUnder the **Products** tab, request access to both:\n\n**Share on LinkedIn**— grants`w_member_social`\n\nscope (post on behalf of user)**Sign In with LinkedIn using OpenID Connect**— grants`openid profile`\n\nscopes (needed to resolve your person URN)\n\nBoth are typically approved instantly for personal apps.\n\n**Step 3 — Verify company association** *(if prompted)*\n\nLinkedIn may ask you to verify a company page association. Open the verification URL while logged in as a Page Admin and approve it.\n\n**Step 4 — Authorize and get tokens**\n\nAdd your app credentials to `.env`\n\n:\n\n```\nLINKEDIN_CLIENT_ID=your_client_id\nLINKEDIN_CLIENT_SECRET=your_client_secret\n```\n\nThen run:\n\n```\nnode linkedin_auth.js\n```\n\nOpen the printed URL on any device. After approving, paste the redirect URL back. Tokens are saved to `linkedin-tokens.json`\n\n.\n\n**Step 5 — Add your person URN**\n\nLinkedIn's API requires your encoded person ID (not your numeric member ID). To find it:\n\n- Go to your LinkedIn profile in a browser\n- View Page Source (Cmd+U / Ctrl+U) and search for\n`urn:li:member:`\n\n- Note the numeric ID (e.g.\n`4435724`\n\n) - Make a test API call — the error response will reveal your encoded person URN (e.g.\n`urn:li:person:2KqUAyg4oY`\n\n)\n\nOr run this one-liner after getting a token:\n\n``` js\nnode -e \"\nconst https = require('https');\nconst t = JSON.parse(require('fs').readFileSync('linkedin-tokens.json'));\n// Replace MEMBER_ID with your numeric ID from page source\nconst body = JSON.stringify({author:'urn:li:member:MEMBER_ID',commentary:'test',visibility:'PUBLIC',distribution:{feedDistribution:'MAIN_FEED',targetEntities:[],thirdPartyDistributionChannels:[]},lifecycleState:'PUBLISHED',isReshareDisabledByAuthor:false});\nconst u = require('url').parse('https://api.linkedin.com/rest/posts');\nconst r = https.request(Object.assign(u,{method:'POST',headers:{'Authorization':'Bearer '+t.access_token,'Content-Type':'application/json','Content-Length':Buffer.byteLength(body),'LinkedIn-Version':'202506','X-Restli-Protocol-Version':'2.0.0'}}),res=>{let d='';res.on('data',c=>d+=c);res.on('end',()=>console.log(d.slice(0,300)));});\nr.write(body);r.end();\n\"\n```\n\nThe error message will contain your encoded URN. Save it:\n\n``` js\nnode -e \"\nconst fs = require('fs');\nconst t = JSON.parse(fs.readFileSync('linkedin-tokens.json'));\nt.person_urn = 'urn:li:person:YOUR_ENCODED_ID';\nfs.writeFileSync('linkedin-tokens.json', JSON.stringify(t, null, 2));\n\"\n```\n\nOnce `linkedin-tokens.json`\n\ncontains `person_urn`\n\n, `announce.js`\n\nwill post to LinkedIn automatically.\n\nCopy `.env.example`\n\nto `.env`\n\nand fill in the keys you need.\n\n| Variable | Required for | Notes |\n|---|---|---|\n`ASSEMBLYAI_API_KEY` |\n`pod2vid.py` |\n|\n\n`OPENAI_API_KEY`\n\n`pod2vid.py`\n\n, `tts_replace.py`\n\n`PEXELS_API_KEY`\n\n`pod2vid.py`\n\n[pexels.com/api](https://www.pexels.com/api/)— free`DISCORD_BOT_TOKEN`\n\n`announce.js`\n\n`DISCORD_CHANNEL_ID`\n\n`announce.js`\n\n`TELEGRAM_BOT_TOKEN`\n\n`announce.js`\n\n`TELEGRAM_CHAT_ID`\n\n`announce.js`\n\n`X_ACCESS_TOKEN`\n\n`announce.js`\n\n`MOLTBOOK_API_KEY`\n\n`announce.js`\n\n`MOLTBOOK_SUBMOLT`\n\n`announce.js`\n\n`agentfinance`\n\n)`LINKEDIN_CLIENT_ID`\n\n`linkedin_auth.js`\n\n[LinkedIn Developer Portal](https://www.linkedin.com/developers/apps)`LINKEDIN_CLIENT_SECRET`\n\n`linkedin_auth.js`\n\n`LINKEDIN_TOKEN_FILE`\n\n`announce.js`\n\n`linkedin-tokens.json`\n\n— must contain `person_urn`\n\n`VOICE_A`\n\n`tts_replace.py`\n\n`onyx`\n\n`VOICE_B`\n\n`tts_replace.py`\n\n`nova`\n\n`SPEAKER_A_NAME`\n\n`pod2vid.py`\n\n`Host`\n\n)`SPEAKER_B_NAME`\n\n`pod2vid.py`\n\n`Guest`\n\n)`YT_PRIVACY`\n\n`yt_upload.js`\n\n`public`\n\n/ `unlisted`\n\n/ `private`\n\n`YT_DESCRIPTION`\n\n`yt_update.js`\n\nInstead of rotating through a fixed clip library, this pipeline asks GPT-4o-mini to generate a specific Pexels search query for each utterance:\n\n```\n\"EZPass saved us 90 seconds at every toll plaza\"\n  → \"toll booth highway payment\"\n\n\"the dual-witness problem\"\n  → \"courtroom judge testimony\"\n\n\"machine learning position predictions\"\n  → \"machine learning data training loop\"\n```\n\nQueries are cached so re-runs or TTS voice swaps don't re-spend API credits. ~82 unique clips across a 90-segment episode is typical.\n\n**Python 3.8+**\n\n- Pillow >= 10.0\n- python-dotenv >= 1.0\n- ffmpeg (any version — subtitle rendering does not require libfreetype/libass)\n\n**Node.js 18+**\n\n- dotenv\n\n**External APIs**\n\n- AssemblyAI (diarization)\n- OpenAI (GPT-4o-mini + TTS)\n- Pexels (B-roll clips, free tier fine for personal use)\n- YouTube Data API v3 (via Google Cloud Console)\n- LinkedIn API (via\n[LinkedIn Developer Portal](https://www.linkedin.com/developers/apps)) — optional, for posting\n\n```\noutput/\n  episode.mp4                    final video\n  episode.srt                    subtitle file for YouTube CC\n  episode-diarization.json       cached AssemblyAI result\n  episode-queries.json           cached GPT Pexels queries\n  broll/                         cached B-roll clips (one per unique query)\n  tts-cache/                     cached TTS utterances (per voice+text hash)\n```\n\nBuilt by [E3D Maps](https://maps.e3d.ai) — AI-powered navigation for autonomous vehicles.\n\nMIT", "url": "https://wpnews.pro/news/show-hn-e3d-pod2vid-ai-pipeline-that-turns-podcasts-into-youtube-ready-videos", "canonical_source": "https://github.com/spacepacket1/e3d-pod2vid", "published_at": "2026-06-27 22:09:16+00:00", "updated_at": "2026-06-27 22:37:25.994067+00:00", "lang": "en", "topics": ["artificial-intelligence", "ai-tools", "generative-ai", "developer-tools"], "entities": ["OpenAI", "AssemblyAI", "Pexels", "Discord", "Telegram", "LinkedIn", "YouTube", "GPT-4o-mini"], "alternates": {"html": "https://wpnews.pro/news/show-hn-e3d-pod2vid-ai-pipeline-that-turns-podcasts-into-youtube-ready-videos", "markdown": "https://wpnews.pro/news/show-hn-e3d-pod2vid-ai-pipeline-that-turns-podcasts-into-youtube-ready-videos.md", "text": "https://wpnews.pro/news/show-hn-e3d-pod2vid-ai-pipeline-that-turns-podcasts-into-youtube-ready-videos.txt", "jsonld": "https://wpnews.pro/news/show-hn-e3d-pod2vid-ai-pipeline-that-turns-podcasts-into-youtube-ready-videos.jsonld"}}