How FluidVoice's Audio History System Works with Budget Controls and ZIP Export
FluidVoice maintains separate WAV recordings for every transcription entry, automatically pruning old files to respect user-defined gigabyte budgets while supporting full ZIP export with JSON manifests.
The audio history system in the FluidVoice repository preserves raw microphone recordings alongside transcription entries, enabling replay and audit functionality. It implements a three-layer architecture that decouples audio storage from transcription data and user settings, ensuring reliable persistence while respecting storage constraints.
Core Architecture
The system relies on three specialized stores that handle distinct responsibilities:
DictationAudioHistoryStore– Manages WAV file persistence, usage tracking, and ZIP archive generation inSources/Fluid/Persistence/DictationAudioHistoryStore.swiftTranscriptionHistoryStore– Maintains the list of transcription entries and their associatedDictationAudioMetadatareferences inSources/Fluid/Persistence/TranscriptionHistoryStore.swiftSettingsStore– Persists user budget preferences and converts gigabyte values to byte limits inSources/Fluid/Persistence/SettingsStore.swift
This separation ensures the UI never interacts directly with the file system, operating exclusively through these store interfaces.
Storing Audio Recordings
When dictation completes, TranscriptionHistoryStore.addEntry receives optional DictationAudioMetadata. If present, the store invokes DictationAudioHistoryStore.save(snapshot:entryID:timestamp:model:) to persist the raw audio.
WAV File Format and Storage Location
The method writes a 44-byte WAV header followed by raw PCM samples to a file named <timestamp>_<entryID>.wav in the application support directory:
~/Library/Application Support/FluidVoice/DictationAudioHistory/
The resulting metadata—including file name, size, duration, and sample rate—is stored on the TranscriptionHistoryEntry, creating a persistent link between the transcription text and its corresponding audio file.
Budget Controls and Storage Management
FluidVoice enforces storage limits through a configurable budget system that automatically prunes old recordings when limits are exceeded.
User Configuration
The budget UI resides in SettingsView and persists values through SettingsStore:
- Budget value: Stored as a
Double(gigabytes) inUserDefaultsunder the keyAudioHistoryBudgetGB - Byte limit: Computed on-demand via
SettingsStore.audioHistoryBudgetBytes:
var audioHistoryBudgetBytes: Int64 {
DictationAudioHistoryStore.bytes(forGigabytes: self.audioHistoryBudgetGB)
}
Automatic Pruning Logic
Every time addEntry saves new audio, TranscriptionHistoryStore.pruneAudioToBudget() executes:
- Reads current usage via
DictationAudioHistoryStore.audioUsageBytes() - If usage exceeds the byte limit, iterates entries newest-to-oldest
- Deletes audio files via
deleteAudio(fileName:)until usage fits under budget - Removes orphaned files through
DictationAudioHistoryStore.deleteUnreferencedAudioFiles
The UI displays current consumption using formatted strings:
Text("Audio history: \(DictationAudioHistoryStore.formattedGigabytes(self.audioHistoryUsageBytes)) / \(Self.audioBudgetText(for: SettingsStore.shared.audioHistoryBudgetGB)) GB Budget")
ZIP Export Functionality
The system supports exporting audio archives either as complete backups or individual entry pairs, triggered from SettingsView (exportAudioArchive) or per-entry actions (exportPair).
Export Process
The DictationAudioHistoryStore.exportAudioArchive(entries:to:) method implements the following workflow:
- Filter entries – Retains only entries with existing audio files
- Create staging directory – Generates temporary folder under the system temporary directory
- Copy audio files – Copies WAV files to
staging/audio/with deterministic names (<timestamp>_<entryID>.wav) - Build manifest – Writes
manifest.jsonlcontaining JSON lines with relative paths, raw transcripts, final transcripts, and technical metadata - Archive creation – Executes
/usr/bin/zipsubprocess:
process.arguments = ["-qr", destinationURL.path, "manifest.jsonl", "audio"]
- Cleanup – Removes temporary staging directory via
defer { try? self.fileManager.removeItem(at: staging) }
Manifest Structure
The internal AudioManifestRow struct encodes each entry with JSONEncoder(sortedKeys:), producing records containing:
- Audio file path (relative to archive)
raw_transcriptandfinal_transcripttext- Recording duration, sample rate, channel count
- Application version and model name
Practical Implementation Examples
Checking Current Usage and Budget
let usedBytes = DictationAudioHistoryStore.shared.audioUsageBytes()
let usedGB = DictationAudioHistoryStore.formattedGigabytes(usedBytes)
let budgetGB = SettingsStore.shared.audioHistoryBudgetGB
print("Audio usage: \(usedGB) / \(budgetGB) GB")
Updating Storage Budget
// Set budget to 2 GB (triggers pruning on next addEntry)
SettingsStore.shared.audioHistoryBudgetGB = 2.0
Manual Pruning
// Force immediate pruning to respect current budget
let prunedCount = TranscriptionHistoryStore.shared.pruneAudioToBudget()
print("Pruned \(prunedCount) old audio files")
Exporting Complete Archive
let entries = TranscriptionHistoryStore.shared.entries
let destination = FileManager.default.temporaryDirectory
.appendingPathComponent("FluidVoice_Export.zip")
do {
try DictationAudioHistoryStore.shared.exportAudioArchive(
entries: entries,
to: destination
)
print("Exported archive to \(destination.path)")
} catch {
print("Export failed: \(error)")
}
Exporting Single Entry Pair
if let entry = TranscriptionHistoryStore.shared.entries.first {
let pairURL = FileManager.default.temporaryDirectory
.appendingPathComponent("EntryPair.zip")
try DictationAudioHistoryStore.shared.exportPair(
entry: entry,
to: pairURL
)
}
Summary
- FluidVoice stores raw dictation audio as WAV files in
~/Library/Application Support/FluidVoice/DictationAudioHistory/, linked to transcription entries viaDictationAudioMetadata - Budget controls convert user-defined gigabyte limits to byte values via
SettingsStore.audioHistoryBudgetBytes, automatically pruning oldest recordings when usage exceeds limits throughTranscriptionHistoryStore.pruneAudioToBudget() - ZIP export creates portable archives containing audio files and JSON line manifests via
/usr/bin/zip, with automatic cleanup of temporary staging directories - Architecture decouples concerns across three stores:
DictationAudioHistoryStorehandles files,TranscriptionHistoryStoremanages entry relationships, andSettingsStorepersists budget configuration
Frequently Asked Questions
How does FluidVoice handle storage limits when the audio history budget is exceeded?
When the budget is exceeded, TranscriptionHistoryStore.pruneAudioToBudget() automatically deletes the oldest audio files first until usage falls below the configured limit. This process runs every time a new entry is added, checking DictationAudioHistoryStore.audioUsageBytes() against the byte limit derived from SettingsStore.audioHistoryBudgetGB. Orphaned files without corresponding entries are also removed via deleteUnreferencedAudioFiles().
What format does FluidVoice use for exported audio archives?
Exported archives are standard ZIP files containing a manifest.jsonl file and an audio/ directory. The manifest uses JSON Lines format with sorted keys, documenting each recording's relative path, raw transcript, final transcript, timestamp, duration, and technical metadata. Audio files maintain their original WAV format with PCM encoding and 44-byte headers.
Can users export individual transcription entries with their audio?
Yes, the system supports single-entry export through DictationAudioHistoryStore.exportPair(entry:to:). This method creates a ZIP archive containing both the individual WAV file and a corresponding manifest entry, useful for sharing specific recordings or debugging particular transcription sessions.
Where does FluidVoice store the audio history budget preference?
The budget preference persists in UserDefaults under the key AudioHistoryBudgetGB as a Double value representing gigabytes. SettingsStore exposes this as audioHistoryBudgetGB and provides the computed audioHistoryBudgetBytes property that converts the gigabyte value to bytes for comparison against actual disk usage during pruning operations.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →