add microphone capture and stable code signing for TCC persistence
Adds --capture-mic/--mic-output for a second, independently-captured audio track (mic vs system, written to separate outputs to avoid interleaving corruption). Embeds Info.plist at link time so the binary carries a stable CFBundleIdentifier and the usage-description keys TCC requires, and adds scripts/build-signed.sh + scripts/create-signing-identity.sh so a rebuilt binary keeps the same signing identity instead of losing granted permissions on every rebuild. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -122,6 +122,23 @@ Note that trying to include or exclude a PID which isn't currently playing audio
|
||||
./audiotee --chunk-duration 0.1
|
||||
```
|
||||
|
||||
### Microphone capture
|
||||
|
||||
AudioTee can optionally capture the default input device (microphone) as a second,
|
||||
independent track alongside system audio — useful for "me vs them" diarization. Mic audio
|
||||
is written to its own file rather than `stdout`, since interleaving two live PCM streams
|
||||
from separate Core Audio IO threads onto one stream would corrupt both.
|
||||
|
||||
```bash
|
||||
# Capture system audio to stdout and mic audio to a separate file
|
||||
./audiotee --capture-mic --mic-output mic.pcm > system.pcm
|
||||
```
|
||||
|
||||
`--sample-rate` and `--chunk-duration` apply to both tracks. On `stderr`, each track's
|
||||
`metadata` message carries `capture_mode: "audio"` or `"mic"`, and its `stream_start`/
|
||||
`stream_stop` messages carry `"audio"`/`"mic"` as their `data` value — so you can tell which
|
||||
track a given message belongs to when both are interleaved in the same log.
|
||||
|
||||
## Output
|
||||
|
||||
AudioTee writes raw PCM audio data directly to `stdout` in chunks. All logging, metadata, and status information is written to `stderr`.
|
||||
@@ -155,13 +172,47 @@ All program logs are written to `stderr` and can be captured separately:
|
||||
- `--stereo`: Record in stereo
|
||||
- `--sample-rate`: Target sample rate (8000, 16000, 22050, 24000, 32000, 44100, 48000)
|
||||
- `--chunk-duration`: Audio chunk duration in seconds [default: 0.2, max: 5.0]
|
||||
- `--capture-mic`: Also capture the default input device (microphone) as a second track
|
||||
- `--mic-output`: File path to write microphone PCM audio to (required with `--capture-mic`)
|
||||
|
||||
## Permissions
|
||||
|
||||
There is no provision in the code to pre-emptively check for the required `NSAudioCaptureUsageDescription` permission, so you'll be prompted the first time AudioTee tries to record anything. Note that some terminal emulators like iTerm don't always prompt for these permissions (though the macOS builtin terminal definitely does), so you might need to grant them ahead of time if audiotee runs but never records anything.
|
||||
|
||||
`--capture-mic` requires the standard, separate Microphone TCC permission (not
|
||||
`NSAudioCaptureUsageDescription`), and will trigger its own first-run prompt.
|
||||
|
||||
If you want to check and/or request permissions ahead of time, check out [AudioCap's fantastic TCC probing approach](https://github.com/insidegui/AudioCap/blob/main/AudioCap/ProcessTap/AudioRecordingPermission.swift).
|
||||
|
||||
### Stable permissions across rebuilds
|
||||
|
||||
By default, `swift build` ad-hoc-signs the binary, and ad-hoc signatures are keyed off the
|
||||
binary's own hash — so every rebuild looks like a new, untrusted app to TCC and you get
|
||||
re-prompted (or worse, silently record silence). `swift run` also invokes the binary from
|
||||
inside `.build/`, and TCC has been observed keying on binary path too, which causes the same
|
||||
problem across rebuilds even without touching signing.
|
||||
|
||||
`scripts/build-signed.sh` builds a release binary, signs it with a **stable identity** (a
|
||||
free self-signed certificate in your Keychain — no paid Developer ID needed for personal
|
||||
use), and installs it to a fixed path (`~/bin/audiotee` by default). The binary also embeds
|
||||
an `Info.plist` at link time (see `Package.swift`) carrying a fixed `CFBundleIdentifier` plus
|
||||
`NSAudioCaptureUsageDescription`/`NSMicrophoneUsageDescription`, without needing a full
|
||||
`.app` bundle — this matters if you invoke audiotee as a subprocess from another program
|
||||
(e.g. a Python ASR orchestrator) rather than through Launch Services.
|
||||
|
||||
One-time setup — either via the GUI (Keychain Access → `Certificate Assistant > Create a
|
||||
Certificate...`, Identity Type "Self Signed Root", Certificate Type "Code Signing", then set
|
||||
that certificate's Trust > Code Signing to "Always Trust"), or entirely via CLI with
|
||||
`scripts/create-signing-identity.sh` (review it first — it generates a key, imports it into
|
||||
your login keychain, and trusts it for the `codeSign` policy). Either way, after that:
|
||||
|
||||
```bash
|
||||
scripts/build-signed.sh # build, sign, install to ~/bin/audiotee
|
||||
scripts/build-signed.sh --reset-tcc # also reset TCC state — useful after changing
|
||||
# Info.plist or the signing identity, to re-trigger
|
||||
# the permission prompts
|
||||
```
|
||||
|
||||
## Built with AudioTee
|
||||
|
||||
<a href="https://talat.app"><img src="https://talat.app/favicon.svg" alt="talat" width="28" height="28" /></a> **[talat](https://talat.app)** — private, local-only meeting transcription for macOS. Captures system audio via AudioTee and runs real-time speech recognition, speaker diarization, and searchable notes entirely on-device. [As featured in TechCrunch](https://techcrunch.com/2026/03/24/talats-ai-meeting-notes-stay-on-your-machine-not-in-the-cloud/).
|
||||
|
||||
Reference in New Issue
Block a user