A developer leans back in his chair with hands behind his head, smiling with relief. His monitor displays a large glowing "DELETE" button. In the background, a messy, tangled server rack is fading away, symbolizing the removal of complex infrastructure.

The Joy of Deleting Code: Rebuilding My Podcast Memory

Late last year, I shared the story of a personal obsession: building an AI system grounded in my podcast history. I had hundreds of hours of audio—conversations that had shaped my thinking—trapped in MP3 files. I wanted to set them free. I wanted to be able to ask my library questions, find half-remembered quotes, and synthesize ideas across years of listening.

So, I built a system. And like many “v1” engineering projects, it was a triumph of brute force.

It was a classic Retrieval-Augmented Generation (RAG) pipeline, hand-assembled from the open-source parts bin. I had a reliable tool called podgrab acting as my scout, faithfully downloading every new episode. But downstream from that was a complex RAG implementation to chop transcripts into bite-sized chunks. I had an embedding model to turn those chunks into vectors. And sitting at the center of it all was a vector database (ChromaDB) that I had to host, manage, and maintain.

It worked, but it was fragile. I didn’t even have a proper deployment setup; I ran the whole thing from a tmux session, with different panes for the ingestion watcher, the vector database, and the API server. It felt like keeping a delicate machine humming by hand. Every time I wanted to tweak the retrieval logic or—heaven forbid—change the embedding model, I was looking at a weekend of re-indexing and refactoring. I had built a memory for my podcasts, but I had also built myself a part-time job as a database administrator.

Then, a few weeks ago, I saw this announcement from the Gemini team.

They were launching File Search, a tool that promised to collapse my entire precarious stack into a single API call. The promise was bold: a fully managed RAG system. No vector DB to manage. No manual chunking strategies to debate. No embedding pipelines to debug. You just upload the files, and the model handles the rest.

I remember reading the documentation and feeling that specific, electric tingle that hits you when you realize the “hard problem” you’ve been solving is no longer a hard problem. It wasn’t just an update; it was permission to stop doing the busy work. I was genuinely excited—not just to write new code, but to tear down the old stuff.

Sometimes, it’s actually more fun to delete code than it is to write it.

The first step was the migration. I wrote a script to push my archive—over 18,000 podcast transcripts—into the new system. It took a while to run, but when it finished, everything was just… there. Searchable. Grounded. Ready.

That was the signal I needed. I opened my editor and started deleting code I had painstakingly written just last year. Podgrab stayed—it was doing its job perfectly—but everything else was on the chopping block.

  • I deleted the chromadb dependency and the local storage management. Gone.
  • I deleted the custom logic for sliding-window text chunking. Gone.
  • I deleted the manual embedding generation code. Gone.
  • I deleted the old web app and a dozen stagnant prototypes that were cluttering up the repo. Gone.

I watched my codebase shrink by hundreds of lines. The complexity didn’t just move; it evaporated. It was more than just a cleanup; it was a chance for a fresh start with new assumptions and fewer constraints. I wasn’t patching an old system anymore; I was building a new one, unconstrained by the decisions I made a year ago.

In its place, I wrote a new, elegant ingestion script. It does one thing: it takes the transcripts generated from the files podgrab downloads and uploads them to the Gemini File Search store. That’s it. Google handles the indexing, the storage, and the retrieval.

With the heavy lifting gone, I was free to rethink the application itself. I built a new central brain for the project, a lightweight service I call mcp_server.py (implementing the Model Context Protocol).

Previously, my server was bogged down with the mechanics of how to find data. Now, mcp_server.py simply hands a user’s query to my rag.py module. That module doesn’t need to be a database client anymore; it just configures the Gemini FileSearch tool and gets out of the way. The model itself, grounded by the tool, does the retrieval, the synthesis, and even the citation.

The difference is profound. The “RAG” part of my application—the part that used to consume 80% of my engineering effort—is now just a feature I use, like a spell checker or a date parser.

This shift is bigger than my podcast project. It changes the calculus for every new idea I have. Previously, if I wanted to build a grounded AI tool for a different context—say, for my project notes or my email archives—I would hesitate. I’d think about the boilerplate, the database setup, the chunking logic. Now? I can spin up a robust, grounded system in an hour.

My podcast agent is smarter now, faster, and much cheaper to run. But the best part? I’m not a database administrator anymore. I’m just a builder again.

You can try out the new system yourself at podcast-rag.hutchison.org or check out the code on GitHub.

Docker Did Nothing Wrong (But I’m Trying Podman Anyway)

Hey everyone, welcome back to the homelab series! One of the constant themes in managing a growing homelab is figuring out the best way to run and orchestrate all the different services we rely on. For me, this has meant evolving my setup over time into distinct systems to keep things scalable and maintainable.

My current homelab nerve center is spread across a few key machines: ns1 and ns2 handle critical DNS redundancy, beluga is the fortress for storage and archives, and bubba acts as the powerhouse for all my AI experiments and compute-heavy tasks.

Up until now, Docker has been the backbone for deploying and managing services across these systems. Whether it’s containerizing AI models on bubba or managing my core network services, it’s been indispensable for packaging applications and keeping dependencies tidy. It’s served me well, allowing for rapid deployment and relatively easy management.

However, the tech landscape is always shifting, and exploring new tools is part of the homelab fun, right? Lately, I’ve been hearing more about Podman as a powerful, open-source alternative to Docker. Recent changes in the container world and simple curiosity led me to check out this excellent video overview (which I highly recommend watching!):

This video really illuminated what Podman brings to the table and sparked a ton of ideas about how it could potentially fit into, and even improve, my homelab workflow. So, in this post, I want to walk through my current Docker-based setup in a bit more detail, share the specific Podman features from the video that caught my eye, and outline some experiments I’m planning for the future. Let’s dive in!

My Current Homelab: A Multi-Server Approach

As I mentioned, to keep things organized as my homelab grew, I settled on dedicating specific roles to my main servers using Proxmox VE as the foundation for virtualization:

  • ns1 and ns2 — The Backbone of Service Discovery: These identical servers run my critical internal DNS, ensuring all my services can find each other reliably. Redundancy here is key – if one fails, the other keeps everything connected.
  • bubba — The AI Workhorse: This is my compute powerhouse, equipped with a GPU and plenty of RAM. It’s dedicated to running local AI models like LLMs via Ollama and interacting with them through tools like Open WebUI. It handles tasks like podcast transcription, embeddings, and inference workloads.
  • beluga — The Keeper of the Archives: With its focus on storage, beluga houses my media library, data archives, and backups. It’s the long-term home for files and feeds data to bubba when needed.

This separation of duties has been crucial for keeping things maintainable and scalable.

Docker’s Role in My Current Setup

So, how do I actually run the services on these different machines? Docker and Docker Compose are absolutely central to making this multi-server setup manageable. Here’s a glimpse into how it’s wired together:

  • Base Services Everywhere: I have a base Docker Compose file that runs on most, if not all, of these servers. It includes essential plumbing:
    • Traefik: My go-to reverse proxy, handling incoming traffic and routing it to the correct service container, plus managing SSL certificates.
    • Portainer Agent: Allows me to manage the Docker environment on each host from a central Portainer instance (the agent itself is part of the Portainer ecosystem).
    • Watchtower: Automatically updates containers. (I use this cautiously – often pinning major versions in my compose files while letting Watchtower handle minor updates, though for rapidly evolving things like Ollama, I sometimes let it pull latest.)
    • Dozzle Agent: Feeds container logs to a central Dozzle instance for easy viewing (the agent enables the main Dozzle UI).
  • DNS Servers (ns1/ns2): On top of the base services, the DNS servers have a dedicated compose file that adds CoreDNS, specifically using the coredns_omada project which cleverly mirrors DHCP hostnames from my TP-Link Omada network gear into DNS – super handy! ns1 also runs the central dozzle instance (the log viewer UI) and Heimdall as my main homelab dashboard, providing a single pane of glass overview. Docker makes running these critical but relatively lightweight infrastructure services incredibly straightforward.
  • AI Workloads (bubba): On the AI workhorse bubba, Docker is essential for managing the AI stack. I run ollama to serve LLMs and open-webui as a frontend, all containerized. This simplifies deployment, dependency management, and allows me to easily experiment with different models and tools without polluting the host system.
  • Storage Server Utilities (beluga): Even the storage server beluga runs containers. I have PostgreSQL running here, which primarily backs the Speedtest-Tracker service but also serves as my go-to relational database for any other containers or services that need one. Again, Docker neatly packages these distinct applications.

Essentially, Docker Compose defines the what and how for each service stack on each server, and Docker provides the runtime environment. This containerization strategy is what allows me to easily deploy, update, and manage this diverse set of applications across my specialized hardware.

Video Insights: How Podman Could Fit into This Picture

Watching the video overview of Podman did more than just introduce another tool; it sparked concrete ideas about how its specific features could integrate with, and perhaps even improve, my current homelab operations distributed across ns1/ns2, bubba, and beluga.

Perhaps the most compelling concept showcased was Podman’s native support for Pods. While Docker Compose helps manage multiple containers, the idea of grouping tightly coupled containers – like my ollama and open-webui stack on bubba, potentially along with a future vector database – into a single, network-integrated unit feels intrinsically cleaner. Managing this AI application suite as one atomic Pod could simplify networking and lifecycle management significantly. I could even see potential benefits in treating the base services running on each host (traefik, portainer-agent, etc.) as a coherent Pod.

Another significant architectural difference highlighted is Podman’s daemonless nature. Running without a central, privileged daemon is interesting for a couple of reasons. While bubba has resources to spare, my leaner DNS servers (ns1/ns2) might benefit from even slight resource savings, though that needs practical testing. More importantly, this architecture often makes running containers as non-root (rootless) more straightforward. This has direct security appeal, especially for the complex AI applications processing data on bubba or the critical DNS infrastructure on ns1/ns2, potentially reducing the attack surface compared to running everything through a root daemon.

Furthermore, the video demonstrated Podman’s ability to generate Kubernetes YAML manifests directly from running containers or pods. This feature is particularly exciting for a homelabber keen on learning! It presents a practical pathway to experimenting with Kubernetes distributions like K3s or Minikube. I could define my AI stack on bubba using Podman Pods and then export it to a Kubernetes-native format, greatly lowering the barrier to entry for learning K8s concepts with my existing workloads. Even outside of a full K8s deployment, having standardized YAML definitions could make my application deployments more portable and consistent.

Of course, for those who prefer a graphical interface, the video also touched upon Podman Desktop. While I currently use Portainer, exploring Podman Desktop could offer a different management perspective, perhaps one more focused on visualizing and managing these Pods. And crucially, knowing that Podman aims for Docker CLI compatibility for many common commands makes the idea of experimenting much less daunting – it suggests I wouldn’t have to relearn everything from scratch.

So, rather than just being ‘another container tool’, the video positioned Podman as offering specific solutions – particularly around multi-container application management via Pods, security posture through its daemonless design, and bridging towards Kubernetes – that seem highly relevant to the challenges and opportunities in my own homelab setup.

Future Homelab Goals: Experimenting with Podman

So, all this reflection on my current setup and the potential benefits highlighted in the video leads to the obvious next question: what am I actually going to do about it? While I’m not planning a wholesale migration away from Docker immediately – it’s deeply integrated and works well – the possibilities offered by Podman are too compelling not to explore.

My plan is to dip my toes into the Podman waters with a few specific, manageable experiments, leveraging the flexibility of my Proxmox setup:

  1. Dedicated Test Environment: Instead of installing Podman directly onto one of my existing servers like bubba initially, I’ll spin up a fresh virtual machine using Proxmox dedicated solely to Podman testing. This is one of the huge advantages of using Proxmox – I can create an isolated sandbox environment easily. This clean slate will be perfect for getting Podman installed, getting comfortable with the basic CLI commands (leveraging that Docker compatibility mentioned at), and working out any kinks without impacting my operational services.
  2. Migrating a Stack to a Pod: Once the test VM is set up, the real test will be taking my current ollama and open-webui Docker Compose stack (conceptually, at least) and recreating it as a Podman Pod within that VM. This will directly evaluate the Pod concept for managing related services and let me see how the networking and management feel compared to Compose in a controlled environment.
  3. Testing a Simple Service: To get a feel for basic container management and the daemonless architecture in this new VM, I’ll deploy a simpler, standalone service using Podman. Perhaps I’ll containerize a small utility or pull down a common image like postgres or speedtest-tracker just to compare the basic workflow.
  4. Generating Kubernetes Manifests: Once I (hopefully!) have the AI stack running in a Podman Pod in the test VM, I definitely want to try the Kubernetes YAML generation feature. Even if I don’t deploy it immediately, I want to see how Podman translates the Pod definition into Kubernetes resources within this testbed. This feels like a practical homework assignment for my K8s learning goals.
  5. Exploring Podman Desktop: Finally, I’ll likely install and explore Podman Desktop within the test VM. I’m curious to see what its visualization and management capabilities look like, especially for Pods, compared to my usual tools.

This isn’t about finding a ‘winner’ between Docker and Podman right now, but rather about hands-on learning in a safe, isolated environment thanks to Proxmox. It’s about understanding the practical advantages and disadvantages of Podman’s approach before considering if or how I might integrate it into my primary homelab systems (ns1/ns2, bubba, beluga) later on. I’m looking forward to experimenting and, of course, I’ll be sure to share my findings and experiences here in future posts!

That’s the plan for now! Docker continues to be a vital part of my homelab, but exploring tools like Podman is essential for learning and potentially improving how things run. The video provided some great insights, and I’m excited to see how these experiments turn out.

What about you? Are you using Docker, Podman, or something else in your homelab? Have you experimented with Pods or rootless containers? Let me know your thoughts and experiences in the comments below!

Turning Podcasts into Your Personal Knowledge Base with AI

If you’re like me, you probably love listening to podcasts while doing something else—whether it’s driving, exercising, or just relaxing. But the problem with podcasts, compared to other forms of media like books or articles, is that they don’t naturally lend themselves to note-taking. How often have you heard an insightful segment only to realize, days or weeks later, that you can’t remember which podcast it was from, let alone the details?

This has been my recurring issue: I’ll hear something that sparks my interest or makes me think, but I can’t for the life of me figure out where I heard it. Was it an episode of Hidden Brain? Or maybe Freakonomics? By the time I sit down to find it, the content feels like a needle lost in a haystack of audio files. Not to mention the fact that my podcast player deletes episodes after I listen to them and I’m often weeks or months behind on some podcasts.

This is exactly where the concept of Retrieval-Augmented Generation (RAG) comes in. Imagine having a personal assistant that could sift through all those hours of podcast content, pull out the exact episode, and give you the precise snippet that you need. No more digging, scrubbing through audio files, or guessing—just a clear, searchable interface that makes those moments instantly accessible.

In this post, I’m going to walk you through how I set up my own RAG system for podcasts—a system that makes it possible to recall insights from my podcast archive just by asking a question. Whether you’re new to AI or just interested in making your podcasts more actionable, this guide will take you step-by-step through the process of turning audio into accessible knowledge.

Introducing Retrieval-Augmented Generation (RAG)

Retrieval-Augmented Generation (RAG) acts as a bridge between the stored data and a language model. It allows you to search for specific information and generates detailed, context-rich responses based on that data. Imagine asking, “What was that podcast that talked about the evolution of money?”—instead of spending hours searching, RAG can pull the relevant snippet and give you an insightful answer.

By connecting the steps I’ve covered in previous posts—downloading, organizing, transcribing, and embedding—you’ll be able to transform your podcast library into a powerful, searchable tool. Let’s dive into how we can achieve that by using RAG.

Setting Up the Podcast RAG System

For those interested in the full setup details and code, I’ve built a prototype of my RAG system, which you can check out in the repository: Podcast RAG Prototype.

To show the power of this system, I’ve prepared two demonstrations—one using the Gemma model and another using Gemini. These demos illustrate how effectively the RAG system can retrieve podcast insights.

In both instances, I used a simple query:

python3 src/rag.py --query "The rise of artificial intelligence"

I also used a prompt template that looked like this:

Instructions:
You are a helpful research assistant. Use the context provided to answer the question.
Context:
----------------------------------------
Podcast Name: $podcast
Episode Name: $episode
Content: $transcript
----------------------------------------
Question: 
What does my archive contain about $query
Answer:

This prompt template contained the top 10 responses from my vector database, with their relevant transcript data. I did not force a single response per podcast, or do any other post-processing on the vector search results.

I experimented quite a bit with more detailed prompts and even included some few-shot examples at one point, but found that in this case both models did better with a simpler prompt.

Gemma

In the Gemma case, I was using gemma2:27b, and I set the Ollama context window to 8192 to allow the model to consider its maximum context window. My prompt was still longer than that, and as a result, the model loses some of the data from the higher-ranked search results. That leads me to think that I’ll need to adjust my search strategy to get better results with Gemma. I also had a hard time getting this model to reliably cite the podcast episodes regardless of the prompt techniques I used.


Your archive contains information about the potential dangers and ethical considerations surrounding the rise of artificial intelligence (AI). It explores these key points:

  • The Potential for Superintelligence: Your archive highlights concerns from experts like Sam Harris about the possibility of AI surpassing human intelligence, leading to an “intelligence explosion” where AI rapidly improves itself beyond our control. It uses the analogy of ants and humans to illustrate how a superior intelligence might treat us with disregard if our goals diverge.
  • The Nature of Intelligence: It discusses the philosophical debate around what constitutes intelligence and whether it’s solely tied to reason or encompasses other factors. The archive references Plato’s idea of the philosopher-king, where the most rational individual should lead society. However, it also critiques this notion as potentially leading to harmful hierarchies based on perceived intellectual superiority.
  • The Importance of Ethical Development: Your archive emphasizes the need for careful and ethical development of AI. It suggests that simply assuming progress will be beneficial is insufficient. Instead, we need open discussions about potential risks and safeguards to ensure AI benefits humanity.

Key Takeaways:

  • The rise of AI presents both exciting opportunities and significant challenges.
  • We must prioritize ethical considerations in AI development to prevent unintended consequences.
  • Ongoing dialogue and critical thinking are crucial for navigating the complexities of AI’s impact on society.

Let me know if you have any other questions about the content in your archive!


Gemini

In the case of Gemini, I was using gemini-1.5-flash for my model. This system has a huge input context length, and it made a big difference in the results. The model reliably cites the podcast episodes and does a great job at organizing the results when there were multiple entries for a single episode.


Your archive contains several podcasts that discuss the rise of artificial intelligence (AI) and its potential consequences. Here are some key points from these podcasts:

Philosophize This – Episode 184: Is Artificial Intelligence Really an Existential Risk?

  • Definition of Intelligence: The episode explores different definitions of intelligence, from narrow intelligence (e.g., a chess computer) to general intelligence (e.g., human-like intelligence).
  • ChatGPT as an Example: The episode uses ChatGPT as an example of narrow intelligence, suggesting that its abilities may be a stepping stone towards general intelligence.
  • General Intelligence as a New Species: The episode highlights the potential for AI to develop general intelligence and become a new species, distinct from humans.
  • Existential Risk: The episode discusses the potential existential risk posed by AI, particularly the lack of understanding of how human intelligence operates and the unpredictable nature of general intelligence.

TED Radio Hour – Episode: Future Consequences

  • Intelligence Explosion: The episode explores the concept of an “intelligence explosion,” where AI rapidly surpasses human intelligence and becomes self-improving.
  • Platform-Independent Intelligence: The episode argues that intelligence is not tied to biological substrates, suggesting that AI can surpass human intelligence.
  • Divergence of Goals: The episode expresses concern that even if AI is not intentionally malicious, its goals may diverge from human goals, potentially leading to harm.
  • Need for Urgent Action: The episode emphasizes the need for urgent action to address the potential risks of AI, comparing the situation to the arrival of an alien civilization.

Other Relevant Content

  • Philosophize This – Episode 186: Are We Heading for a Digital Prison – Panopticon (Foucault, Bentham, Cave). This episode explores the potential for AI to create a digital panopticon, where our behavior is constantly monitored and controlled.
  • Stuff You Should Know – Episode: What Makes a Genius. This episode discusses the nature of intelligence and genius, exploring how these concepts are often measured and the limitations of such measurements.

Overall

Your archive provides a rich overview of the current debate surrounding the rise of AI, covering its potential benefits, risks, and philosophical implications. It explores the potential for AI to surpass human intelligence, the importance of understanding AI’s goals, and the need for ethical considerations in AI development.


Conclusion: Reflections and Next Steps

The journey of building a Retrieval-Augmented Generation (RAG) system for podcasts has been both challenging and rewarding. Through this exploration, I’ve found ways to transform a passive collection of podcast content into an actionable and searchable knowledge base, turning fleeting moments of insight into something that can be easily recalled and used. The use of both the Gemma and Gemini models highlights the potential of RAG to bring real value, providing nuanced and context-rich responses from complex archives.

While there are still some technical hurdles, such as improving search strategies and prompt effectiveness, the results so far are promising. This system has already begun to solve a real problem: giving us the ability to recall and utilize knowledge that would otherwise be lost in hours of audio recordings.

If you’re interested in creating a similar system or expanding on what I’ve done, I encourage you to dive into the prototype and explore how RAG can be applied to your own datasets. Whether you’re working with podcasts, documents, or any other unstructured content, the potential for making that content more accessible and useful is vast.

Moving forward, I’ll continue refining the RAG system and experimenting with different models and configurations. If you have any questions, suggestions, or would like to share your own experiments, feel free to reach out.

Thank you for following along on this journey—let’s continue exploring the power of AI together.

Unlocking Podcast Search with Embeddings: Practical Examples

In previous posts, I covered how to download podcasts, transcribe them, and store them in a vector database using embeddings. For more on downloading podcasts, check out my previous post: The Great Podcast Download: Building the Foundation of My AI. Now, it’s time to demonstrate how these elements come together to create a powerful search engine that allows you to query your podcast library using natural language.

In this post, I’ll walk through five different search examples that showcase how embeddings can retrieve podcast episodes based on themes, topics, or specific phrases, even when those exact words don’t appear in the transcription.

What is Embedding Search?

Embeddings allow us to convert text into a numerical format that captures the semantic meaning. For a more detailed explanation of embeddings, check out my previous post: The Magic of Embeddings: Transforming Data for AI. By storing these embeddings in a vector database, we can quickly and accurately search across thousands of podcast episodes based on the meaning of the search query—not just the exact words. For more on vector databases and how they work, see my post: Unlocking AI Potential: Vector Databases and Embeddings.

For example, searching for “AI ethics” might bring up episodes discussing “machine learning fairness” or “responsible AI” because embeddings capture the similarity in meaning, even if the exact phrase isn’t mentioned.

Example 1: Search for “Historical Revolutions”

To demonstrate the power of embeddings and vector search, I ran a query for “Historical Revolutions”. The system retrieved episodes from the Revolutions podcast that cover events from both the Russian and French revolutions.

Search Query:

python src/chroma_search.py --query "Historical revolutions"

Results:

  • Relevant Episode: Revolutions, Episode: Relaunch-and-Recap.mp3
    Transcription Snippet: “This movement led to the infamous going to the people of 1874, where those idealistic students flocked to the countryside to enlighten the people and teach them how to be free…”
  • Relevant Episode: Revolutions, Episode: The-Russian-Colony.mp3
    Transcription Snippet: “By early 1876, Axelrod was back in Switzerland, where he found the Russian colony splitting between the still faithful Bakuninists, the slow and steady Lavrovists, and Kachov’s Jacobin militancy…”

Analysis:

The system retrieved episodes that discuss key revolutionary movements, even though the exact phrase “Historical Revolutions” was not used. This highlights how embeddings allow for thematic searches that go beyond simple keyword matching.

Example 2: Search for “The Economy and Innovation”

This query explored how embedding-based search can surface episodes discussing the intersection of economic growth and technological innovation.

Search Query:

python src/chroma_search.py --query "The economy and innovation"

Results:

  • Relevant Episode: Planet Money, Episode: Patent-racism-(classic).mp3
    Transcription Snippet: “In the mid-90s, there was this big new economic theory that was all the rage. It was an idea for how countries can produce unlimited economic growth…”
  • Relevant Episode: Freakonomics Radio, Episode: 399-Honey,-I-Grew-the-Economy.mp3
    Transcription Snippet: “And it turns out that the countries where families prize obedient children, those countries are low in innovation…”

Analysis:

This search brought up episodes from Planet Money and Freakonomics Radio discussing theories of economic growth and innovation, showing how the system connects broad themes across different podcasts.

Example 3: Search for “Myths and Legends of Ancient Rome”

For this example, I ran a query to find content related to Roman mythology and folklore, and the system retrieved relevant episodes from Myths and Legends.

Search Query:

python src/chroma_search.py --query "Myths and legends of ancient Rome"

Results:

  • Relevant Episode: Myths and Legends, Episode: 142A-Rome-Glory.mp3
    Transcription Snippet: “Two brothers with an interesting past. We’ll hear all about their origin and learn why my four-year-old is right. Sometimes a bath is not a good idea…”
  • Relevant Episode: Myths and Legends, Episode: 211-Aeneid-Troy-Story.mp3
    Transcription Snippet: “This week, we’re back in Greek and Roman mythology for the Aeneid…”

Analysis:

The system successfully pulled up episodes on the stories of Romulus and Remus, as well as the Aeneid. This demonstrates how embeddings can capture the meaning of mythological themes, even when the exact words aren’t used in the transcription.

Example 4: Search for “Ethics in Science and Technology”

Next, I queried for “Ethics in Science and Technology”, and the system pulled up episodes discussing ethical issues in gene patents and philosophical debates on the role of science.

Search Query:

python src/chroma_search.py --query "Ethics in science and technology"

Results:

  • Relevant Episode: Stuff You Should Know, Episode: How-Gene-Patents-Work.mp3
    Transcription Snippet: “This is where it gets hot… That’s the standard for what’s going on in the US right now as far as gene patents go.”
  • Relevant Episode: Philosophize This, Episode: Episode-051-David-Hume-pt-1.mp3
    Transcription Snippet: “Science is fantastic at doing certain things. It’s fantastic at telling us about what the universe is…”

Analysis:

The search brought up discussions from both practical and philosophical podcasts, demonstrating the range of ethical questions raised in science and technology.

Example 5: Search for “Philosophy of Language”

Finally, I explored the “Philosophy of Language”, and the system pulled up episodes from Lexicon Valley and Philosophize This, which delve into linguistic theories and philosophical discussions about language.

Search Query:

python src/chroma_search.py --query "Philosophy of language"

Results:

  • Relevant Episode: Lexicon Valley, Episode: That’s-Not-What-Irony-Means,-Alanis.mp3
    Transcription Snippet: “Language is a mess too. I recommend a book. It’s Nick Enfield’s book, Language vs. Reality…”
  • Relevant Episode: Philosophize This, Episode: Episode-097-Wittgenstein-ep-1.mp3
    Transcription Snippet: “Just think for a second how massively important language is, whether you’re Aristotle, Francis Bacon, Karl Popper…”

Analysis:

This search highlighted episodes discussing the philosophical and linguistic complexities of language, showing how embeddings can capture abstract concepts and pull relevant content from different sources.

How to Try This Yourself

If you’d like to try this out, check out the Podcast Rag repository on GitHub for all the tools you need to build your own podcast search engine. You can also find all posts related to the Podcast Rag project on my site: Podcast Rag Series.

Final Thoughts

These examples illustrate the power of using embeddings for semantic search across a diverse podcast library. By converting both queries and podcast transcriptions into embeddings, the system can:

  • Understand Context: Grasp the underlying meaning of queries and match them with relevant content, even if specific keywords aren’t present.
  • Handle Diversity: Work across a wide range of topics—from historical events and economic theories to mythology and abstract philosophy.
  • Enhance Discovery: Help you uncover episodes and discussions you might have missed with traditional keyword searches.

In future posts, I’ll explore additional functionality you can build into your system, such as:

  • Summarization: Automatically generating concise summaries for podcast episodes based on their transcriptions.
  • Recommendations: Building a personalized recommendation system that suggests episodes based on listening habits.

Stay tuned for more deep dives into building AI-powered tools with your own data!

Building a Podcast Transcription Script with AI Assistance

For those who have followed my podcast transcription project, you’ve already seen some of the challenges I’ve tackled in previous posts, such as exploring transcription methods in “Cracking the Code”, building the foundation of my AI system in “The Great Podcast Download”, and grounding my AI model in my podcast history in “Building an AI System”.

With these pieces in place, my next challenge was automating the transcription process for the entire podcast archive. This meant creating a tool that could handle large directories of podcast episodes, efficiently transcribe each one, and ensure a seamless workflow.

I’ve worked in many different programming languages throughout my career, which often means I forget the exact syntax or module names when starting a new project. Usually, I end up spending a fair bit of time looking up syntax or refreshing my memory on specific libraries. But for this project, I wanted to try something different. Because I work so closely with large AI models, I was curious to see how far I could get by having the model write all the code, while I focused on describing the system in plain English.

What followed was an incredibly productive collaboration, where the model not only responded to my requests but helped refine my ideas, transforming a basic script into a robust transcription tool. In this post, I’ll walk through how that collaboration unfolded and how the model contributed to the development of a powerful solution that now automates a key part of my podcast project.

The Task at Hand

The initial goal was simple: automate the transcription of my podcast archive. The podcasts were stored in a directory structure where each podcast series had its own folder, and within each folder were multiple episodes as .mp3 files. I needed a tool to efficiently transcribe these episodes using Whisper, an open-source automatic speech recognition model.

I didn’t have a fully defined set of requirements from the start. Instead, the process was organic—each iteration with the AI model led to new ideas and improvements. What started as a basic transcription shell script slowly evolved as I refined it with more features and considerations that became clear through the development process.

For example, initially, I simply wanted to loop through the podcast files and transcribe them. But after the first draft, it became obvious that the script should be able to:

  1. Process a directory of podcasts: Loop through each podcast folder and its .mp3 files to ensure only the correct audio files were processed.
  2. Handle re-runs: If the script was run multiple times, it shouldn’t re-transcribe files that had already been processed.
  3. Recover from interruptions: If the script were interrupted or crashed, it should pick up where it left off without needing to start over.
  4. Simulate a run (Dry Run): Before making changes, it would be useful to simulate the process to confirm what the script was about to do.
  5. Generate useful statistics: At the end of the process, I wanted a summary of how many episodes were processed, how many had already been transcribed, and how many were transcribed during the current run.

These requirements evolved naturally as I worked through the project, guided by how the AI model responded to my needs. Each time I described what I wanted in English, the model would generate code that not only met my expectations but often inspired new ways to improve the system.

The next step was to start iterating on this evolving solution, and that’s where the collaboration with the AI really began to shine.

Iterative Development with AI

The development process with the AI model was truly collaborative. I would describe a new feature or refinement I wanted, and the model would generate code that worked surprisingly well. With each iteration, the script became more powerful and refined, responding to both my immediate needs and unforeseen challenges that emerged along the way.

First Step: Starting with a Bash Script

Initially, I started with a simple bash script to iterate over each .mp3 file in the podcast directory and transcribe it using Whisper. The script was straightforward, but as I began adding more features—like error handling and checking for existing transcriptions—it became clear that the complexity was growing. Bash wasn’t the right tool for this level of logic, so I decided to ask the AI model to convert the script to Python. The transition was smooth, and Python provided the flexibility I needed for more sophisticated control flow.

#!/bin/bash

# Directory containing podcast files
DIRECTORY="/opt/podcasts"

# Path to the Whisper executable
WHISPER_PATH="/home/allen/whisper/bin/whisper"

# Check if the directory exists
if [ -d "$DIRECTORY" ]; then
    for FILE in "$DIRECTORY"/*; do
        if [ -f "$FILE" ]; then
            echo "Transcribing $FILE"
            "$WHISPER_PATH" "$FILE" --output_dir "$(dirname "$FILE")" --output_format txt
        fi
    done
else
    echo "Directory $DIRECTORY does not exist."
fi

Second Iteration: Basic Transcription Script in Python

Once we moved the script to Python, the first version was simple: iterate over a directory of podcast .mp3 files and use Whisper to transcribe them. The model generated a Python script that correctly handled reading the files and transcribing them using the Whisper command-line tool. This version worked perfectly for basic transcription, but I quickly realized that additional features were needed as the project evolved.

import os
import subprocess

DIRECTORY = "/opt/podcasts"
WHISPER_PATH = "/home/allen/whisper/bin/whisper"

def transcribe_podcasts():
    if os.path.isdir(DIRECTORY):
        for filename in os.listdir(DIRECTORY):
            file_path = os.path.join(DIRECTORY, filename)
            if os.path.isfile(file_path):
                print(f"Transcribing {file_path}")
                subprocess.run([WHISPER_PATH, file_path, "--output_dir", os.path.dirname(file_path), "--output_format", "txt"])
    else:
        print(f"Directory {DIRECTORY} does not exist.")

transcribe_podcasts()

Third Iteration: Adding Dry Run Mode

After the initial transcription script, I realized it would be helpful to simulate a run before making any changes. I asked the model to add a “dry run” mode, where the script would only print out the files it intended to transcribe without actually performing the transcription. This feature gave me confidence that the script would do what I expected before it ran on my actual data.

import os
import subprocess
import argparse

DIRECTORY = "/opt/podcasts"
WHISPER_PATH = "/home/allen/whisper/bin/whisper"

def transcribe_podcasts(dry_run=False):
    if os.path.isdir(DIRECTORY):
        for filename in os.listdir(DIRECTORY):
            file_path = os.path.join(DIRECTORY, filename)
            if os.path.isfile(file_path):
                if dry_run:
                    print(f"Dry run: would transcribe {file_path}")
                else:
                    print(f"Transcribing {file_path}")
                    subprocess.run([WHISPER_PATH, file_path, "--output_dir", os.path.dirname(file_path), "--output_format", "txt"])
    else:
        print(f"Directory {DIRECTORY} does not exist.")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Transcribe podcasts using Whisper")
    parser.add_argument("-d", "--dry-run", action="store_true", help="Perform a dry run without actual transcription")
    args = parser.parse_args()

    transcribe_podcasts(dry_run=args.dry_run)

Fourth Iteration: Idempotency

The next improvement was addressing idempotency. Since I had a large collection of podcasts, I didn’t want the script to re-transcribe episodes that had already been processed. I needed a way to detect whether a transcription file already existed and skip those files. I explained this in plain English, and the model quickly generated a check for existing transcription files, only processing files that hadn’t already been transcribed.

import os
import subprocess
import argparse

DIRECTORY = "/opt/podcasts"
WHISPER_PATH = "/home/allen/whisper/bin/whisper"

def transcribe_podcasts(dry_run=False):
    if os.path.isdir(DIRECTORY):
        for filename in os.listdir(DIRECTORY):
            file_path = os.path.join(DIRECTORY, filename)
            transcription_file = os.path.splitext(file_path)[0] + ".txt"
            if os.path.isfile(file_path):
                if os.path.exists(transcription_file):
                    print(f"Skipping {file_path}: transcription already exists.")
                else:
                    if dry_run:
                        print(f"Dry run: would transcribe {file_path}")
                    else:
                        print(f"Transcribing {file_path}")
                        subprocess.run([WHISPER_PATH, file_path, "--output_dir", os.path.dirname(file_path), "--output_format", "txt"])
    else:
        print(f"Directory {DIRECTORY} does not exist.")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Transcribe podcasts using Whisper")
    parser.add_argument("-d", "--dry-run", action="store_true", help="Perform a dry run without actual transcription")
    args = parser.parse_args()

    transcribe_podcasts(dry_run=args.dry_run)

Fifth Iteration: Handling Incomplete Transcriptions

As the script matured, I realized another edge case: what happens if the transcription is interrupted or the script crashes? In such cases, I didn’t want partially completed transcriptions. So, I asked the model to handle this scenario by using temporary “in-progress” files. The model created a mechanism where a temporary file would be generated at the start of transcription and deleted only upon successful completion. If the script detected an “in-progress” file on the next run, it would clean up and start fresh, ensuring that no partial transcriptions were left behind.

import os
import subprocess
import argparse

DIRECTORY = "/opt/podcasts"
WHISPER_PATH = "/home/allen/whisper/bin/whisper"
TEMP_FILE_SUFFIX = ".transcription_in_progress"

def transcribe_podcasts(dry_run=False):
    if os.path.isdir(DIRECTORY):
        for filename in os.listdir(DIRECTORY):
            file_path = os.path.join(DIRECTORY, filename)
            transcription_file = os.path.splitext(file_path)[0] + ".txt"
            temp_file = transcription_file + TEMP_FILE_SUFFIX
            if os.path.isfile(file_path):
                if os.path.exists(temp_file):
                    print(f"Detected unfinished transcription for {file_path}.")
                    os.remove(temp_file)
                elif os.path.exists(transcription_file):
                    print(f"Skipping {file_path}: transcription already exists.")
                else:
                    if dry_run:
                        print(f"Dry run: would transcribe {file_path}")
                    else:
                        print(f"Transcribing {file_path}")
                        open(temp_file, 'w').close()  # Create temp file
                        try:
                            subprocess.run([WHISPER_PATH, file_path, "--output_dir", os.path.dirname(file_path), "--output_format", "txt"])
                        finally:
                            if os.path.exists(temp_file):
                                os.remove(temp_file)
    else:
        print(f"Directory {DIRECTORY} does not exist.")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Transcribe podcasts using Whisper")
    parser.add_argument("-d", "--dry-run", action="store_true", help="Perform a dry run without actual transcription")
    args = parser.parse_args()

    transcribe_podcasts(dry_run=args.dry_run)

Final Iteration: Adding Statistics

The last feature I asked for was a way to track progress and output useful statistics at the end of each run. I wanted to know how many .mp3 files had been processed, how many had already been transcribed, and how many were transcribed during the current session. The model quickly integrated these statistics into the script, both for dry runs and actual transcription runs.

import os
import subprocess
import argparse

DIRECTORY = "/opt/podcasts"
WHISPER_PATH = "/home/allen/whisper/bin/whisper"
TEMP_FILE_SUFFIX = ".transcription_in_progress"

stats = {
    "total_mp3_files": 0,
    "already_transcribed": 0,
    "waiting_for_transcription": 0,
    "transcribed_now": 0
}

def transcribe_podcasts(dry_run=False):
    if os.path.isdir(DIRECTORY):
        for filename in os.listdir(DIRECTORY):
            file_path = os.path.join(DIRECTORY, filename)
            transcription_file = os.path.splitext(file_path)[0] + ".txt"
            temp_file = transcription_file + TEMP_FILE_SUFFIX
            if os.path.isfile(file_path) and file_path.endswith(".mp3"):
                stats["total_mp3_files"] += 1
                if os.path.exists(temp_file):
                    print(f"Detected unfinished transcription for {file_path}.")
                    os.remove(temp_file)
                elif os.path.exists(transcription_file):
                    print(f"Skipping {file_path}: transcription already exists.")
                    stats["already_transcribed"] += 1
                else:
                    if dry_run:
                        print(f"Dry run: would transcribe {file_path}")
                        stats["waiting_for_transcription"] += 1
                    else:
                        print(f"Transcribing {file_path}")
                        open(temp_file, 'w').close()
                        try:
                            subprocess.run([WHISPER_PATH, file_path, "--output_dir", os.path.dirname(file_path), "--output_format", "txt"])
                            stats["transcribed_now"] += 1
                        finally:
                            if os.path.exists(temp_file):
                                os.remove(temp_file)

    print("\n--- Transcription Statistics ---")
    print(f"Total MP3 files processed: {stats['total_mp3_files']}")
    print(f"Already transcribed: {stats['already_transcribed']}")
    if dry_run:
        print(f"Waiting for transcription: {stats['waiting_for_transcription']}")
    else:
        print(f"Transcribed during this run: {stats['transcribed_now']}")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Transcribe podcasts using Whisper")
    parser.add_argument("-d", "--dry-run", action="store_true", help="Perform a dry run without actual transcription")
    args = parser.parse_args()

    transcribe_podcasts(dry_run=args.dry_run)

Collaboration with the Model

What struck me most about this process was how natural and intuitive it felt to work with the AI model. Over the years, I’ve spent a lot of time learning and working with different programming languages, which often means looking up syntax or refreshing my memory on specific libraries when I start a new project. But in this case, I was able to offload much of that effort to the model.

At every step, I provided the model with a plain English description of what I wanted the script to do, and it responded by writing the code. This wasn’t just basic code generation—it was thoughtful, well-structured solutions that responded directly to the needs I described. When I wanted something more specific, like a dry run mode or idempotency, the model not only understood but implemented those features in a way that felt seamless.

That said, my own programming experience was still critical throughout this process. While the model was incredibly effective at generating code, I relied heavily on my background in software development to guide the model’s work, define the system’s architecture, and debug the output when necessary. It wasn’t just about letting the model do everything—it was about using my expertise to spot edge cases, identify potential issues, and ensure that the code the model produced was robust and reliable.

The most remarkable aspect of this collaboration was the ability to iterate. I didn’t need to sit down and write out a complete, detailed spec for the entire project from the beginning. Instead, I approached the model with a rough idea of what I needed, and through a series of interactions, the project naturally grew more sophisticated. The model helped me refine the initial concept and introduce new features that I hadn’t considered at the outset.

This dynamic, back-and-forth interaction mirrored the kind of iterative workflow I often use when collaborating with colleagues. The difference, of course, is that this was all happening in real time with an AI model—without needing to dig into documentation, refactor code, or troubleshoot syntax issues.

In the end, I found that the model wasn’t just a tool for automating transcription; it became a partner in developing the solution itself. By offloading the technical nuances of code writing to the AI, I was able to focus more on the high-level design of the system.

Working with the AI model on this project demonstrated to me the potential of AI-assisted development—not as a replacement for programming skills, but as a highly effective augmentation to those skills. My programming knowledge was still a vital part of guiding the project, but with the model handling much of the heavy lifting, I could focus on the overall architecture and problem-solving. For me, that’s an incredibly exciting shift in the way I approach building systems.

Announcing Podcast-Rag: A Comprehensive Podcast
Retrieval-Augmented Generation
(RAG) System

I’m excited to announce the open-source release of Podcast-Rag, a project that began as the podcast transcription tool described in this article and is evolving into something much more. Podcast-Rag will eventually become a comprehensive podcast RAG system, integrating with a large model to offer powerful insights and automated workflows for managing large-scale podcast archives.

What is Retrieval-Augmented Generation (RAG)?

In the context of AI and natural language processing, Retrieval-Augmented Generation (RAG) is a powerful concept that combines the strengths of information retrieval with text generation. The idea is simple: instead of generating text purely from a model’s pre-trained knowledge, RAG systems search for relevant documents or data from a knowledge base and use that information to produce more accurate and contextually rich responses.

Imagine a large language model working alongside a search engine. When the model is asked a question, it retrieves the most relevant documents or podcasts from a repository, like my own archive, and uses that information to generate a response. This allows RAG systems to provide highly informed answers that go beyond the limits of a pre-trained model’s knowledge.

For Podcast-Rag, this approach will be pivotal. The long-term goal is to combine transcription and retrieval to build a system that can dynamically surface relevant episodes, segments, or quotes based on user queries. By integrating RAG, we’ll not only transcribe podcasts but also empower users to retrieve and interact with specific pieces of information from an entire podcast archive. This takes podcast management and analysis to a new level of intelligence, making the system more interactive and useful for tasks like research, content discovery, and more.

Right now, the system includes robust transcription features, handling everything from large directories of podcast episodes to ensuring that transcriptions are idempotent and recover gracefully from crashes. It also offers dry run mode and detailed statistics for each run.

But this is just the beginning. Over time, Podcast-Rag will evolve into a full-featured system that integrates AI to provide rich interactions and insights, transforming how podcast archives are managed and analyzed.

You can explore the current state of the project, contribute to its growth, or use it to streamline your transcription workflows by visiting the Podcast-Rag repository on GitHub.

Conclusion

This project was far more than an exercise in automating podcast transcription—it was a firsthand experience in seeing the potential of AI-assisted development. Over the years, I’ve written a lot of code, and I’ve always approached new projects with the mindset of leveraging my programming expertise. But working with the AI model shifted that dynamic. By letting the model handle the code generation, I was able to focus more on the overall system design, while still relying on my background to guide the development process and resolve any issues.

What really stood out during this collaboration was how natural the process felt. I could describe my requirements in plain English, and the model responded by generating code that was not only functional but often elegant. The model adapted to new requests, introduced features I hadn’t thought of, and iterated on the script in a way that mirrored working with another developer.

That said, the AI didn’t replace my programming skills; it augmented them. My experience was still critical to ensuring the script worked as expected, debugging when necessary, and refining the overall system. The model handled the details of coding, but I provided the architecture and oversight, creating a powerful synergy that made the development process faster and more efficient.

In the end, this project showed me just how transformative AI-assisted development can be. It allows developers to focus on the high-level design and logic of a system while offloading much of the code-writing burden to the model. For me, that’s an exciting new way to build solutions, one that feels more collaborative and less about getting bogged down in syntax or boilerplate.

This experience has left me eager to explore more ways AI can assist in development. Whether it’s refining future scripts, automating other parts of my workflow, or pushing the boundaries of what’s possible in AI-driven projects, I’m more convinced than ever that AI will be a critical part of how I approach coding in the future.

Cracking the Code: Exploring Transcription Methods for My Podcast Project

In previous posts, I outlined the process of downloading and organizing thousands of podcast episodes for my AI-driven project. After addressing the chaos of managing and cleaning up nearly 7,000 files, the next hurdle became clear: transcription. Converting all of these audio files into readable, searchable text would unlock the real potential of my dataset, allowing me to analyze, tag, and connect ideas across episodes. Since then, I’ve expanded my collection to over 10,000 episodes, further increasing the importance of finding a scalable transcription solution.

Why is transcription so critical? Most AI tools available today aren’t optimized to handle audio data natively. They need input in a format they can process—typically text. Without transcription, it would be nearly impossible for my models to work with the podcast content, limiting their ability to understand the material, extract insights, or generate meaningful connections. Converting audio into text not only makes the data usable by AI models but also allows for deeper analysis, such as searching across episodes, generating summaries, and identifying recurring themes.

In this post, I’ll explore the various transcription methods I considered, from cloud services to local AI solutions, and how I ultimately arrived at the right balance of speed, accuracy, and cost.

What Makes a Good Transcription?

Before diving into the transcription options I explored, it’s important to outline what I consider to be the key elements of a good transcription. When working with large amounts of audio data—like podcasts—the quality of the transcription can make or break the usability of the resulting text. Here are the main criteria I looked for:

  • Accuracy: The most obvious requirement is that the transcription needs to be accurate. It should capture what is said without altering the meaning. Misinterpretations, skipped words, or incorrect phrasing can lead to significant misunderstandings, especially when trying to analyze data from hours of dialogue.
  • Speaker Diarization: Diarization is the process of distinguishing and labeling different speakers in an audio recording. Many of the podcasts in my dataset feature multiple speakers, and a good transcription should clearly indicate who is speaking at any given time. This makes the conversation easier to follow and is essential for both readability and for further processing, like analyzing individual speaker contributions or summarizing conversations.
  • Punctuation and Formatting: Transcriptions need to be more than a raw dump of words. Proper punctuation and sentence structure make the resulting text more readable and usable for downstream tasks like summarization or natural language processing.
  • Identifying Music and Sound Effects: Many podcasts feature music, sound effects, or background ambiance that are integral to the listening experience. A good transcription should be able to note when these elements occur, providing context about their role in the episode. This is especially important for audio that is heavily produced, as these non-verbal elements often contribute to the overall meaning or mood.
  • Scalability: Finally, when dealing with tens of thousands of podcast episodes, scalability becomes critical. A transcription tool should not only work well for a single episode but also maintain performance when scaled to thousands of hours of audio. The ability to process large volumes of data efficiently without sacrificing quality is a key factor for a project of this scale.

These criteria shaped my approach to evaluating different transcription tools, helping me determine what worked—and what didn’t—for my specific needs.

Using Gemini for Transcription: A First Attempt

Since I work with Gemini and its APIs professionally (about me), I saw this transcription project as an opportunity to deepen my understanding of the system’s capabilities. My early experiments with Gemini were promising; the model produced highly accurate, diarized transcriptions for the first few podcast episodes I tested. I was excited by the results and the prospect of integrating Gemini into my workflow for this project. It seemed like a perfect fit—Gemini was delivering exactly what I needed in terms of transcription accuracy, making me optimistic about scaling this approach.

Early Success and Optimism

In those initial tests, Gemini excelled in several areas. The transcriptions were accurate, the diarization was clear, and the output was well-formatted. Given Gemini’s strength in understanding context and language, the transcripts felt polished, even in conversations with overlapping speech or complex dialogue. This early success gave me confidence that I had found a tool that could handle my vast dataset of podcasts while maintaining high quality.

The Challenges of Scaling

As I continued to test Gemini on a larger scale, I encountered two key issues that ultimately made the tool unsuitable for this project.

The biggest challenge was recitation errors. The Gemini API includes a mechanism that prevents it from returning text if it detects that it might be reciting copyrighted information. While this is an understandable safeguard, it became a major roadblock for my use case. Given that my project is dependent on converting copyrighted audio content into text, it wasn’t surprising that Gemini flagged some of this content during its recitation checks. However, when this error occurred, Gemini didn’t return any transcription, making the tool unreliable for my needs. I required a solution that could consistently transcribe all the audio I was working with, not just portions of it.

That said, when Gemini did return transcriptions, the quality was excellent. For instance, here’s a sample from one of the podcasts I processed using Gemini:

Where Does All The TSA Stuff Go?
0:00 - Intro music playing.
1:00 - [SOUND] Transition to podcast
1:01 - Kimberly: Hi, this is Kimberly, and we're at New York airport, and we just had our snow globe 
confiscated.
1:08 - Kimberly: Yeah, we're so pissed, and we want to know who gets all of the confiscated stuff, 
where does it go, and will we ever be able to even get our snow globe back?

In addition to the recitation issue, I didn’t want to rely on Gemini for some transcriptions and another tool for the rest. For this project, it was important to have a consistent output format across all my transcriptions. Switching between tools would introduce inconsistencies in the formatting and potentially complicate the next stages of analysis. I needed a single solution that could handle the entire podcast archive.

Using Whisper for High-Quality AI Transcription

After experiencing challenges with Gemini, I turned to OpenAI’s Whisper, a model specifically designed for speech recognition and transcription. Whisper is an open-source tool known for its accuracy in handling complex audio environments. Given that my podcast collection spans a variety of formats and sound qualities, Whisper quickly emerged as a viable solution.

Why Whisper?

  • Accuracy: Whisper consistently delivered highly accurate transcriptions, even in cases with challenging audio quality, background noise, or overlapping speakers. It also performed well with speakers of different accents and speech patterns, which is critical for the diversity of content I’m working with.
  • Diarization: While Whisper doesn’t have diarization built-in, its accuracy with speech segmentation allowed for easy integration with additional tools to identify and separate speakers. This flexibility allowed me to maintain clear, speaker-specific transcripts.
  • Open Source Flexibility: Whisper’s open-source nature allowed me to deploy it locally on my Proxmox setup, leveraging the full power of my NVIDIA RTX 4090 GPU. This setup made it possible to transcribe podcasts in near real-time, which was crucial for processing a large dataset efficiently.

Performance on My Homelab Setup

By running Whisper locally with GPU acceleration, I saw significant improvements in processing time. For shorter podcasts, Whisper was able to transcribe episodes in a matter of minutes, while longer episodes could be transcribed in near real-time. This speed, combined with its accuracy, made Whisper a strong contender for handling my entire collection of over 10,000 episodes.

For instance, here’s the same podcast episode that was transcribed with Whisper:

Hi, this is Kimberly.
And we're at Newark Airport.
And we just had our snow globe confiscated.
Yeah, we're so pissed.
And we want to know who gets all of the confiscated stuff.
Where does it go?
And will we ever be able to even get our snow globe back?

Challenges and Considerations

While Whisper excelled in many areas, one consideration is its resource demand. Running Whisper locally with GPU acceleration requires substantial computational resources. For users without access to powerful hardware, this could be a limitation. Whisper also lacks built-in diarization, which means it cannot automatically differentiate between speakers. This requires additional post-processing or integration with other tools to achieve the same level of speaker clarity. However, for my setup, the performance trade-off was worth it, as it allowed me to maintain full control over the transcription process without relying on external services.

Comparing Transcription Methods and Moving Forward

After testing both Gemini and Whisper, it became clear that each tool has its strengths, but Whisper ultimately emerged as the best option for my project’s needs. While Gemini delivered higher-quality transcriptions overall, the recitation errors and lack of reliability when dealing with copyrighted material made it unsuitable for handling my entire dataset. Whisper, on the other hand, provided consistent, highly accurate transcriptions across the board and scaled well to the volume of audio I needed to process.

Gemini’s Strengths and Limitations

  • Strengths: Gemini produced extremely polished and accurate transcriptions, outperforming Whisper in many cases. The diarization was clear, and the formatting made the transcripts easy to read and analyze.
  • Limitations: Despite its transcription quality, Gemini’s API recitation checks became a major roadblock, which made it unreliable for my use case. Additionally, I needed a single solution that could provide consistent output across all episodes, which Gemini couldn’t guarantee due to these errors.

Whisper’s Strengths and Limitations

  • Strengths: Whisper stood out for its high accuracy, scalability, and open-source flexibility. Running Whisper locally allowed me to transcribe thousands of episodes efficiently, while its robust handling of varied audio content—from background noise to multiple speakers—was a major advantage.
  • Limitations: Whisper lacks built-in diarization, which means it cannot automatically differentiate between speakers. This requires additional post-processing or integration with other tools to achieve the same level of speaker clarity. Additionally, Whisper demands significant computational resources, which could be a barrier for users without access to powerful hardware.

Final Thoughts

As I move forward with this project, Whisper will be my go-to tool for transcribing the remaining episodes. Its ability to process large amounts of audio data reliably and consistently has made it the clear winner. While there may still be room for further exploration—particularly around post-processing clean-up or integrating diarization tools—Whisper has given me the foundation I need to turn my podcast archive into a fully searchable, AI-powered dataset.

In my next post, I’ll outline how I built my transcription system using Whisper to handle all of these episodes. It was a unique experience, as I used a model to write the entire application for this project. Stay tuned for a deep dive into the system’s architecture and the steps I took to automate the transcription process at scale.

The Great Podcast Download: Building the Foundation of My AI

In my previous post, I shared my ambitious goal of building a personalized AI system grounded in my extensive podcast listening history. It’s a project fueled by the desire to unlock the hidden knowledge and connections within the thousands of hours I’ve spent immersed in podcasts.

But before any AI magic can happen, I need data. Lots and lots of data. That’s where this stage of the project began – The Great Podcast Download.

Wrangling the Podcast Wild West

My Downloading thousands of podcast episodes might sound simple, but the process presented a few unique challenges:

  • Sheer Volume: With nearly 7,000 episodes across 30 podcasts, we’re talking about a significant amount of data. Simply managing the download queue and ensuring everything downloaded correctly was no small feat.
  • Podcast RSS Feeds: Each podcast has its own RSS feed, and not all feeds are created equal. Some feeds only contain a limited number of recent episodes, while others offer the full archive. Since I’ve been listening to podcasts for years, it was crucial to find a way to retrieve as many of those older episodes as possible.
  • Locally Hosted, Open Source: From the start, I knew I wanted this project to live on my own server. Not only did I need the MP3 data readily available for transcription and the eventual creation of embeddings, but I also valued the control and privacy that a local setup offered. I also wanted to leverage the power of the open-source community, seeking out tools that were well-built but wouldn’t require me to code everything from scratch.

This last point led me down a rabbit hole of exploring various open-source podcast downloaders. Two projects stood out: Podgrab and Pinepods. Both were impressive, but ultimately, Podgrab felt like a better fit for my needs, offering a slightly simpler setup process while still providing all the features I needed, even though it seemed like active development might have slowed down.

Tools of the Trade: Podgrab to the Rescue

Navigating the open-source landscape can be a bit like panning for gold – you sift through a lot of promising options before striking upon the perfect tool for the job. Fortunately, my search for a podcast downloader led me to Podgrab, a tool that proved to be worth its weight in digital audio.

Right from the start, Podgrab impressed me with its ease of use. Deploying it within my existing Docker environment was a breeze, and within minutes, I was ready to start populating my server with podcasts. But simplicity didn’t mean sacrificing functionality. Podgrab came loaded with features that streamlined the entire downloading process:

  • One-Click Feed Downloads: Podgrab eliminated the tedious task of manually selecting individual episodes. With a single click, I could initiate the download of an entire podcast feed, past and present.
  • Parallel Download Power: Time, as they say, is of the essence, especially when you’re dealing with nearly 7,000 podcast episodes. Podgrab’s ability to leverage multiple threads for simultaneous downloads significantly sped up the process, turning what could have been a week-long endeavor into a much more manageable task.
  • Customization is King: I appreciated the flexibility Podgrab offered in terms of customization. I could easily define my preferred download paths, ensuring everything was neatly organized within my server’s file system. I also had granular control over file naming conventions, making it easy to identify and manage my growing podcast library.
  • Seamless Podcast Player Integration: One feature that truly set Podgrab apart was its ability to integrate with my existing podcast player. It offered import options for my subscribed feeds via OPML files, making it incredibly easy to get started. But more impressively, it provided rewritten feeds that pointed to the locally downloaded files. This meant I could continue using my preferred podcast app, enjoying a familiar interface while accessing my offline audio archive.
  • Filesystem-Centric Storage: As someone who likes to tinker with data, I appreciated Podgrab’s straightforward approach to storage. It keeps all the MP3 files directly on the filesystem, using a lightweight SQLite database only for metadata. This made it clear where everything was located and would prove essential for the subsequent steps of transcription and analysis.

Podgrab quickly proved to be an efficient and reliable companion throughout the Great Podcast Download. Its blend of simplicity, speed, customization, seamless integration, and filesystem-centric design made it an indispensable tool for laying the groundwork for my AI project.

180GB of Audio: The Journey Begins

After letting Podgrab work its magic, I found myself staring at a digital mountain of data—nearly 7,000 podcast episodes, neatly organized on my server, totaling a staggering 180GB of audio. It was a sight that both excited and intimidated me.

This was more than just a collection of MP3 files; it was a treasure trove of ideas, stories, and knowledge, accumulated over years of dedicated listening. Interestingly, the project has already started to change my podcast consumption habits. Freed from the limitations of my phone’s storage and my own listening capacity, I find myself subscribing to even more podcasts, knowing I can always revisit them later.

But for now, the real challenge lies ahead: transforming this raw audio data into something meaningful and accessible for my AI project.

The next step on this journey? Transcription. Stay tuned as I delve into the fascinating (and computationally demanding) world of converting spoken words into searchable, analyzable text. The foundation is laid; the real building is about to begin.

Building an AI System Grounded in My Podcast History

I’ve been listening to podcasts for a long time — probably as long as podcasts have existed. Back in the mid-2000s, when I lived in London, my daily commute on the underground was always accompanied by a podcast in my ear, offering a mix of news, stories, and ideas that made the journey feel shorter. When my family moved back to Silicon Valley, podcasts continued to be a constant companion, this time helping me stay grounded while navigating the notorious traffic on 101.

Over the years, I’ve explored a wide range of topics through podcasts—economics, history, linguistics, current events, storytelling, and more. My favorite shows, like This American LifeFreakonomicsReply AllStartupLexicon ValleyThe History of RomeHidden Brain, and Planet Money have not only entertained me but have also shaped my thoughts, influenced my ideas, and played a significant role in my personal and professional growth.

The ideas and concepts from these podcasts have influenced me in many ways—from how I think about managing my team to how I interact with my family. I’ve learned a great deal from them over the years, but there’s one problem: I’m usually listening at times when I can’t take notes. Whether I’m commuting, walking, exercising, or working with my hands in the workshop, I often find myself recalling something I heard but struggling to revisit it later when I’m at my computer or want to dive deeper into a topic.

As I look back on the thousands of hours spent listening, I can’t help but wonder: What if I could build an AI system grounded in this rich history of podcast consumption, one that could help me capture and access the wealth of knowledge I’ve absorbed over the years?

Building the Foundation: Organizing and Analyzing Podcast Data

With this idea in mind, the first step in building an AI system grounded in my podcast history is to create a solid technical foundation. The challenge is not just in capturing the content of these podcasts but in organizing and analyzing it in a way that makes it accessible and useful—while ensuring that the entire system is hosted locally in my home.

For me, hosting this system locally is crucial because it allows me to be involved in every step of the process. By keeping everything on my local server, I can implement, test, and refine each stage of development, from data ingestion to AI analysis. This hands-on approach not only gives me greater control but also offers an invaluable learning experience, allowing me to deepen my understanding of the technologies and techniques involved in building a personalized AI system.

To start, I subscribed to 30 podcasts in Podgrab and downloaded the entire available archive from each one. This initial step resulted in 6,160 episodes and roughly 180GB of podcast MP3 data. With such a vast amount of content, it was crucial to ensure that everything was properly archived and transcribed. Using Whisper for transcriptions, I set up a structure where each podcast episode is stored in a directory with its corresponding transcription. This structure allows me to create a database of podcast content that the AI can tap into, all while keeping everything on my local server.

But transcription is just the beginning. The real power lies in analyzing the content. With a high-powered consumer GPU at my disposal, I can leverage advanced algorithms to process these transcripts, converting them into embeddings that capture the essence of each episode. This allows the AI to recognize patterns, understand recurring themes, and even track how my interests and ideas have evolved over time.

This foundational step is crucial because it turns a vast and somewhat chaotic collection of audio data into something structured and meaningful—something that an AI can learn from and build upon. And most importantly, it all happens within the hands-on, controlled environment of my home setup.

Challenges and Considerations: Crafting a Meaningful AI

As exciting as this project is, building an AI system grounded in years of podcast listening comes with its own set of challenges. These hurdles are not just technical but also conceptual, requiring careful thought and planning to ensure the AI truly delivers value.

One of the primary challenges is ensuring the AI doesn’t just regurgitate information but provides meaningful insights. This involves developing algorithms that can analyze context, filter out noise, and identify connections between seemingly unrelated topics. The goal is to create an AI that understands the nuances of my interests and can offer more than just surface-level summaries.

Another challenge is dealing with the sheer volume of data. With over 6,000 podcast episodes and hundreds of gigabytes of audio content, the AI needs to efficiently process and analyze vast amounts of information. This requires not only powerful hardware but also optimized algorithms that can handle such large-scale data processing without compromising on accuracy or speed.

A key aspect of this project is the need for the AI to evolve alongside my own changing interests. As time goes on, my interests naturally shift, and the AI must be able to adapt to these changes. This means developing a system that is not static but can learn and grow as I continue to consume new content. Moreover, I plan to expand the AI’s data sources over time to include articles I’ve clipped, highlights from books I’ve read, RSS feeds, and my archive of notes. Integrating these diverse types of content will provide the AI with a richer context, allowing it to offer even deeper insights and more personalized recommendations.

Finally, there’s the consideration of how this AI will integrate into my daily life. It needs to be accessible, easy to use, and capable of providing value without requiring constant attention or adjustment. The AI should feel like a natural extension of my content consumption experience—whether it’s podcasts, articles, books, or notes—seamlessly fitting into my routines and enhancing my ability to engage with and reflect on the information I encounter.

Despite these challenges, the potential rewards make this project worth pursuing. By carefully navigating these hurdles, I hope to create an AI system that not only reflects my podcast history but also becomes a powerful tool for personal and professional growth.

Potential Applications: Unlocking the Power of Personalized AI

Once the AI system is fully developed, its potential applications are vast and varied, offering value across both personal and professional domains. By grounding the AI in my rich history of podcast listening and other content, I envision several ways it could augment my daily life.

One of the most immediate applications is the ability to retrieve and revisit specific insights from podcasts, articles, or books I’ve consumed. Instead of relying on memory or manually searching through notes, I could simply ask the AI to pull up relevant information based on a topic, question, or even a vague recollection. This capability would allow me to dive deeper into subjects of interest, explore connections between ideas, and build on the knowledge I’ve accumulated over the years.

In my professional life, the AI could become a powerful tool for brainstorming and decision-making. By analyzing the vast array of content I’ve engaged with, the AI could offer fresh perspectives or highlight recurring themes that align with current challenges or opportunities at work. One of the most exciting possibilities is using the AI to uncover non-obvious connections between my interests. By synthesizing information from diverse sources, the AI could bring out new insights and ideas that I might not have considered, leading to innovative solutions and creative breakthroughs.

Beyond work, the AI could also assist in personal growth and learning. For instance, it could suggest new podcasts, articles, or books based on evolving interests, ensuring that I continue to learn and grow in areas that matter to me. Additionally, by integrating with my notes and highlights, the AI could help me track progress on personal goals or even remind me of valuable lessons from past content that might be relevant to current situations.

Moreover, the AI could foster deeper connections with my family by identifying and recommending content that aligns with our shared interests. Whether it’s finding a new podcast for family road trips or suggesting articles that spark meaningful conversations, the AI could play a role in enriching our time together.

Ultimately, the potential applications of this AI system are as broad as the content it’s built upon. By creating a tool that adapts to my needs and interests, I’m not just building an AI—I’m creating a personalized companion that augments my ability to engage with the world, learn from it, and uncover new connections that lead to fresh insights and ideas.

Conclusion: A Journey of Discovery and Innovation

Building this AI system has been a fascinating journey, one that blends my personal interests with cutting-edge technology. Through the process of organizing and analyzing my podcast history—and eventually expanding to other forms of content—I’ve not only deepened my understanding of AI but also gained new insights into the information I’ve consumed over the years.

This project is more than just a technical challenge; it’s an opportunity to explore the intersection of technology and personal growth. By creating an AI that can uncover non-obvious connections, provide personalized insights, and augment my daily life, I’m aiming to build something that transcends the typical boundaries of AI applications. It’s a system that’s as unique as the content it’s based on, reflecting my interests, my experiences, and my evolving goals.

Looking ahead, there are still many exciting possibilities to explore. As I continue to refine and expand the AI’s capabilities, I’m eager to see how it will adapt to new data sources and how it might even surprise me with insights I hadn’t anticipated. The journey is far from over, but every step brings me closer to creating a tool that not only helps me navigate the vast landscape of information but also inspires new ways of thinking and learning.

In the end, this project is a testament to the power of curiosity and innovation. By leveraging the rich history of podcasts and other content I’ve engaged with, I’m not just building an AI—I’m embarking on a path of continuous discovery, where technology serves as both a guide and a companion in the pursuit of knowledge