Talk into your phone, get a tidy to-do list on your PC
Record a thought on your phone. A few minutes later it's a titled, tagged note on your PC with a 3-line summary and a to-do list, made by Whisper and a local AI model. No cloud service, no subscription, and your voice never leaves your own devices.
- Notes that open in Obsidian, import into Joplin, and search from the terminal ("show me every open to-do")
- The full project as a starter kit zip, with a dry run you can try before downloading any models
- Done first: Run AI on your PC (Ollama 0.20.7 or newer with
gemma4:e2b) and Subtitles for any video (it installs uv and the Whisper model this reuses) - For the phone part: Your own private Dropbox (Syncthing), plus Syncthing on your phone
- 8GB of RAM minimum, 16GB is more comfortable. Whisper and the model take turns, so they don't both need memory at once
- About 6GB of free disk: roughly 250MB of Python packages, the 465MB Whisper model and the 4.6GB Gemma model (less if you already have them)
- Optional: Joplin or Obsidian to read the notes. Both are free
- No accounts, no API keys, nothing to pay
Get the kit and do a dry run, no models needed
Prove the folder handling, JSON checks and note writing work on your machine before you download anything big.

What you're building. Each box is a few lines of one Python script. Download the starter kit (14KB) and unzip it into your home folder. It contains a folder called
voice-noteswith four files: the main scriptvoice_notes.py, its settings insettings.toml, a search toolsearch_notes.py, andselftest.py, a dry run.Windowscd $HOME\Downloads Expand-Archive voice-notes-kit.zip -DestinationPath $HOME cd $HOME\voice-notesMac / Linuxcd ~/Downloads unzip voice-notes-kit.zip -d ~ cd ~/voice-notesEach script starts with a small
# /// scriptblock listing the Python packages it needs. That's a standard format (PEP 723) that uv reads:uv runbuilds a private, cached environment with exactly those packages, so there's nopip installand nothing touches your system Python. You installed uv in the subtitles lesson.The dry run swaps Whisper and Ollama for fakes, then throws awkward cases at the real pipeline: a model reply with the wrong number of bullets, a reply that isn't JSON at all, an audio file that can't be decoded, Syncthing's half-written temp files, a text file, a file that's still being copied, and the same recording arriving twice.
Terminaluv run selftest.pyIt takes a few seconds and prints this (your dates and times will differ):
Output2026-10-09 15:53:33 INFO Still arriving, will try next run: memo-07-still-copying.wav 2026-10-09 15:53:33 INFO 4 recording(s) to process 2026-10-09 15:53:33 INFO Wrote 2026-10-09-1553-saturday-errands-mot-dentist-bin-bags.md (errands, car, health-stuff) 2026-10-09 15:53:33 WARNING Model reply failed validation (attempt 1): summary: List should have at least 3 items after validation, not 2 2026-10-09 15:53:33 INFO Wrote 2026-10-09-1553-coffee-loyalty-card-idea.md (shop, ideas) 2026-10-09 15:53:33 WARNING Model reply failed validation (attempt 1): json: Invalid JSON: expected value at line 1 column 1 2026-10-09 15:53:33 WARNING Model reply failed validation (attempt 2): json: Invalid JSON: expected value at line 1 column 1 2026-10-09 15:53:33 INFO Wrote 2026-10-09-1553-voice-note-memo-03.md (needs-review) 2026-10-09 15:53:33 ERROR Failed on memo-04-broken.wav (try 1 of 3): Invalid data found when processing input 2026-10-09 15:53:35 INFO 2 recording(s) to process 2026-10-09 15:53:35 INFO Already made a note from memo-01-copy.wav, moved it to done/2026-10/memo-01-copy.wav 2026-10-09 15:53:35 ERROR Failed on memo-04-broken.wav (try 2 of 3): Invalid data found when processing input 2026-10-09 15:53:36 INFO 1 recording(s) to process 2026-10-09 15:53:36 ERROR Failed on memo-04-broken.wav (try 3 of 3): Invalid data found when processing input 2026-10-09 15:53:36 ERROR Gave up on memo-04-broken.wav, moved it to the failed folder == Run 1 == {'ok': 3, 'duplicate': 0, 'error': 1, 'failed': 0} == Run 2: the same recording arrives again under a new name == {'ok': 0, 'duplicate': 1, 'error': 1, 'failed': 0} == Run 3 == {'ok': 0, 'duplicate': 0, 'error': 1, 'failed': 1} == Folders == inbox/: ['.syncthing.memo-05.wav.tmp', 'shopping.txt', '~syncthing~memo-06.wav.tmp'] notes/: ['2026-10-09-1553-coffee-loyalty-card-idea.md', '2026-10-09-1553-saturday-errands-mot-dentist-bin-bags.md', '2026-10-09-1553-voice-note-memo-03.md'] done/: ['2026-10/memo-01-copy.wav', '2026-10/memo-01.wav', '2026-10/memo-02.wav', '2026-10/memo-03.wav'] failed/: ['memo-04-broken.wav', 'memo-04-broken.wav.error.txt'] == 2026-10-09-1553-saturday-errands-mot-dentist-bin-bags.md == --- title: "Saturday errands: MOT, dentist, bin bags" created: 2026-10-09T15:53:32 updated: 2026-10-09T15:53:33 tags: - errands - car - health-stuff audio: "memo-01.wav" audio_sha256: c4a948f325d39c925685c2f91f30453f46903bf6e1826eed691cbd8f86643061 language: en duration_seconds: 12 --- ## Summary - The Fiesta's MOT is due before the 20th. - Tuesday's dentist appointment needs moving. - Bin bags are needed. ## Action items - [ ] Book the MOT for the Fiesta before the 20th - [ ] Ring the dentist to move Tuesday's appointment - [ ] Buy bin bags ## Transcript Right, reminder for Saturday. I need to book the MOT for the Fiesta before the 20th, and ring the dentist to move Tuesday's appointment. Also pick up bin bags. *From `memo-01.wav`, 12 seconds, transcribed on this computer.* No BOM at the start of the note: OK- Run 1: three notes written. The second one only after a retry (the fake model sent 2 summary bullets, validation caught it, the script explained the error and asked again). The third got a safe fallback note tagged
needs-review, because its reply was never valid JSON. The file still being copied was left alone. - The last lines print the finished note and confirm it starts cleanly, with no hidden byte order mark that would confuse Joplin.
- Run 2: the same audio under a new name was recognised by its fingerprint and filed without making a second note.
- Run 3: the undecodable file failed for the third time and moved to
failed, with an.error.txtsaying why. - The inbox still holds the Syncthing temp files and the text file, untouched.
- Run 1: three notes written. The second one only after a retry (the fake model sent 2 summary bullets, validation caught it, the script explained the error and asked again). The third got a safe fallback note tagged
Delete the test folder when you've had a look:
Remove-Item -Recurse selftest-runon Windows, orrm -r selftest-runon Mac and Linux.
uv run selftest.py prints {'ok': 3, ...} for Run 1 and ends with No BOM at the start of the note: OK.If it breaks'uv' is not recognised + 2 more
'uv' is not recognised
"No such file or directory: selftest.py"
voice-notes-kit\voice-notes. Run Get-ChildItem (or ls) and cd into the folder that contains selftest.py."No interpreter found for Python >=3.11"
uv python install 3.12 once and try again.