Automatic local transcription pipeline for voice notes -> Markdown notes in Obsidian.
Any time a new audio file appears, it is automatically transcribed and moved to your Obsidian vault (or any target directory on your filesystem).
- Transcription backend: Lightning Whisper MLX (
distil-large-v3) - Metadata backend: OpenAI-compatible LM Studio API via
rmccue/requests - Audio input: direct
.m4a(no WAV pre-conversion step in the PHP pipeline)
- macOS (Apple Silicon recommended)
- PHP 8.3+
ffmpegandffprobeinPATHuv- LM Studio app +
lmsCLI with the metadata model available - Composer
- Python is only needed through
uv runfor the Lightning Whisper script
Install PHP dependencies:
composer install
Configuration is loaded from config.ini (default path: same directory as ophaniel.php).
- Copy:
cp config.ini.example config.ini
- Edit required paths:
SOURCE_DIRECTORYTARGET_DIRECTORY
Optional path behavior:
TARGET_AUDIO_DIRECTORYis optional:- If blank/missing, defaults to
TARGET_DIRECTORY/audio
- If blank/missing, defaults to
LEGACY_PROCESSED_FILEis optional:- If blank/missing, defaults to
SOURCE_DIRECTORY/.processed
- If blank/missing, defaults to
Environment variables override config.ini keys one-for-one.
Example:
SOURCE_DIRECTORY=/some/other/path php ophaniel.php ingestLLM_MODEL=lmstudio/another-model php ophaniel.php test-llm /tmp/input.txt
If you want a different config file location:
OPHANIEL_CONFIG=/path/to/config.ini php ophaniel.php ingest
Metadata prompt sizing defaults to a dynamic cap from lms ps context (~75% of context, converted to chars), with LLM_METADATA_MAX_CHARS as fallback.
Preferred: run via Composer scripts from this directory (/Users/matt/bin/transcribe).
- Run once:
composer run ingest
- Run once silently (for LaunchAgent/automation):
composer run ingest:quiet
- Daemon loop:
composer run daemon
- Status:
composer run state
- Run tests:
composer run test
Direct CLI remains available:
php ophaniel.php process-file "/absolute/path/to/file.m4a"php ophaniel.php retranscribe-vault "/path/to/vault/subdir-or-note.md"php ophaniel.php retranscribe-vault "/path/one.md" "/path/two-or-dir"php ophaniel.php repair-note-dates "/path/one-or-dir" "/path/two-or-dir"php ophaniel.php retime-note-filenames "/path/or/vault/root" --dry-runphp ophaniel.php retime-note-filenames "/path/or/vault/root"php ophaniel.php refresh-heuristic-notes "/path/or/vault/root" --dry-runphp ophaniel.php refresh-heuristic-notes "/path/or/vault/root" --dry-run --only-metadataphp ophaniel.php refresh-heuristic-notes "/path/or/vault/root"php ophaniel.php timing-reportphp ophaniel.php timing-report 1800(estimate for 30m audio)php ophaniel.php test-file "/path/to/file.m4a" /tmp/ophaniel-tests
- Sequential processing (one file at a time)
- Lock file prevents overlapping runs
- JSON state tracks processed files by path + mtime + size
- Progress output includes
n/totalforingestandretranscribe-vault --quietsuppresses CLI progress output- Default note filename format:
DD HHMM Title.md - Transcripts get heuristic paragraphization + repetition cleanup
- Title + summary come from one LLM call (JSON response)
Generate and manage the LaunchAgent from Composer so install paths are not hardcoded.
- Create plist in
~/Library/LaunchAgents:composer run launchagent:add
- Load:
composer run launchagent:load
- Unload:
composer run launchagent:unload
- Reload:
composer run launchagent:reload
- Force one immediate run:
composer run launchagent:kick
- Status:
composer run launchagent:status
Generic command form:
composer run launchagent -- <add|load|unload|reload|kick|status|path>
Optional env overrides:
OPHANIEL_LAUNCHAGENT_LABEL(default:com.mattwiebe.ophaniel)OPHANIEL_LAUNCHAGENT_INTERVALin seconds (default:300)