<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jon Dev</title>
    <description>The latest articles on DEV Community by Jon Dev (@jondev0405).</description>
    <link>https://dev.to/jondev0405</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4089587%2Fad89930e-6db4-48a8-acd3-cdc34ad0920b.png</url>
      <title>DEV Community: Jon Dev</title>
      <link>https://dev.to/jondev0405</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jondev0405"/>
    <language>en</language>
    <item>
      <title>Turning Family Voice Notes into a Living Digital Ofrenda with Local AI</title>
      <dc:creator>Jon Dev</dc:creator>
      <pubDate>Sat, 31 Oct 2026 10:21:46 +0000</pubDate>
      <link>https://dev.to/jondev0405/turning-family-voice-notes-into-a-living-digital-ofrenda-with-local-ai-3hp9</link>
      <guid>https://dev.to/jondev0405/turning-family-voice-notes-into-a-living-digital-ofrenda-with-local-ai-3hp9</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week4-2026-10-26"&gt;Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Lola's Archive is a local-first family oral-history ofrenda for Día de los Muertos (Day of the Dead). A family member uploads a voice note about someone they love. The app transcribes it on the same computer, gathers the foods, songs, places, tags and exact quotes mentioned, and turns them into an ofrenda page that can be searched, printed and saved as an image.&lt;/p&gt;

&lt;p&gt;No accounts, cloud storage, or external AI APIs: the audio, transcripts, photos and database stay on the computer running it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Demo video: &lt;a href="https://youtu.be/aqsn4cwSHb8" rel="noopener noreferrer"&gt;https://youtu.be/aqsn4cwSHb8&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/jondev0405/lolas-archive" rel="noopener noreferrer"&gt;[Lola's Archive Repo Link]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The app is Next.js with a FastAPI transcription service in &lt;code&gt;python/&lt;/code&gt;, Prisma and SQLite for local data, and a local Ollama model for extraction.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Transcription.&lt;/strong&gt; The browser uploads a voice note to &lt;code&gt;/api/transcribe&lt;/code&gt;, which saves a copy in &lt;code&gt;uploads/&lt;/code&gt; and forwards the audio to a FastAPI service running faster-whisper at &lt;code&gt;localhost:8000&lt;/code&gt;. I used the &lt;code&gt;base&lt;/code&gt; model because it is fast, small enough for a CPU, and good enough for a first pass a person will edit anyway. The transcript is shown for correction before extraction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extraction.&lt;/strong&gt; The edited transcript goes to Ollama at &lt;code&gt;localhost:11434&lt;/code&gt; using the model set in &lt;code&gt;.env&lt;/code&gt;, &lt;code&gt;gemma3:4b-it-qat&lt;/code&gt; by default. The request asks for a fixed JSON shape: summary, tags, foods, songs, places and quotes. Temperature is &lt;code&gt;0&lt;/code&gt;, and the schema is passed as Ollama's &lt;code&gt;format&lt;/code&gt; to leave less room for drift.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The exact-quote guarantee.&lt;/strong&gt; A model can produce a sentence that sounds right and attribute it to someone who never said it. For a family archive, that is the failure I could not accept. The server checks the output against the transcript in &lt;code&gt;guard()&lt;/code&gt;. Foods, songs and places must match a whole word or phrase in the transcript, compared case-insensitively after normalizing curly quotes and whitespace. Quotes have surrounding quote marks removed and must match a whole phrase in the normalized transcript. Empty values, fragments inside longer words and duplicates are dropped.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review.&lt;/strong&gt; The transcript is editable. The review screen shows every gathered item, and each tag, offering or quote can be removed and saved. The summary is read-only. The ofrenda reads the same saved data, so anything unwanted should be removed before sharing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The ofrenda.&lt;/strong&gt; It shows a portrait or initials, offerings, exact words and the original recording. You can search, filter by tag, print, or save the page as a PNG.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Three things were hard:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;JSON reliability on a small model.&lt;/strong&gt; Even with a schema passed as &lt;code&gt;format&lt;/code&gt; and temperature &lt;code&gt;0&lt;/code&gt;, a 4B model can put a field in the wrong place, add an empty string, or paraphrase something it should have copied. The guard makes that tolerable: I stopped treating the model as authoritative and started treating it as a suggestion that gets checked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A PyAV version mismatch.&lt;/strong&gt; A PyAV version mismatch broke faster-whisper's audio decoding. faster-whisper 1.2.1 — still the latest release passes &lt;code&gt;metadata_errors="ignore"&lt;/code&gt; to &lt;code&gt;av.open()&lt;/code&gt;, but PyAV 19.0.0 removed that argument, causing a &lt;code&gt;TypeError&lt;/code&gt; when decoding audio. This project pins &lt;code&gt;av==14.4.0&lt;/code&gt; in &lt;code&gt;python/requirements.txt&lt;/code&gt;, but the local venv currently has av==19.0.2, so python/main.py includes a temporary compatibility shim that strips &lt;code&gt;metadata_errors&lt;/code&gt; before calling &lt;code&gt;av.open()&lt;/code&gt;. The shim is unconditional (harmless on older PyAV) and can be removed once faster-whisper ships its upstream PyAV 19 fix. Until then, either recreate the venv from the pinned requirements or keep the shim; the pin and the actual environment should be reconciled so the documented setup matches the running one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Windows setup.&lt;/strong&gt; &lt;code&gt;npm run dev:all&lt;/code&gt; starts Next.js and uvicorn together, but it only covers startup. A fresh checkout still needs &lt;code&gt;npm install&lt;/code&gt;, the Python venv and &lt;code&gt;pip install&lt;/code&gt;, a copied .&lt;code&gt;env&lt;/code&gt;, Prisma migrations and the seed, and a running Ollama with the model pulled. faster-whisper also downloads its model on first transcription. After that, &lt;code&gt;npm run dev:all&lt;/code&gt; is the only startup command.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;This app only works because the models run on the user's own machine. A family recording may contain a name, a place, an illness, a grief, or a story never meant to leave the room. Sending that audio to a hosted API would ask a family to trust a company's retention policy with something deeply personal. For many, that is not a trade they want to make.&lt;/p&gt;

&lt;p&gt;Open-weight models and local inference change the question. The data stays where it was recorded, and the user can choose another model or run a larger one. Open models made a private, consent-aware tool possible where a closed API would have made it a request for trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Spirit of It
&lt;/h2&gt;

&lt;p&gt;It brought a memory back.&lt;/p&gt;

&lt;p&gt;The sample recording is almost nothing: a lola making pandesal before the sun came up, a kettle, a song hummed while she waited. The model did its job and returned a tidy summary. But the summary is not what made me stop. The line &lt;strong&gt;"Food is how we say I love you."&lt;/strong&gt; came through untouched, in her own words, with the app confirming it had been found word for word in the recording.&lt;/p&gt;

&lt;p&gt;For a second the page was not a demo. It was a Sunday morning in a kitchen I could almost smell. That is the moment the project landed for me: not when the machine sounded smart, but when it got out of the way and let a real sentence do the work.&lt;/p&gt;

&lt;p&gt;That is the ghost in the machine I wanted to build around. Not a generated imitation of someone, but the trace of how they actually said it. If the model had rewritten that line into something cleaner, the memory would have been gone. Keeping it exact is the whole point.&lt;/p&gt;

&lt;p&gt;Try the synthetic sample in &lt;code&gt;samples/&lt;/code&gt; first, then use a recording you have permission to keep.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Best Use of Gemma:&lt;/strong&gt; Gemma runs locally with Ollama, extracting summaries, tags, foods, songs, places, and quotes from family transcripts.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
