How FluidVoice Manages Audio History Storage: Budget Controls and ZIP Export
FluidVoice stores every dictation as a WAV file in the macOS Application Support directory, enforces a user-defined storage budget with automatic cleanup of oldest files, and exports selectable entries as ZIP archives containing a JSONL manifest.
FluidVoice, an open-source dictation application maintained by altic-dev, implements a complete audio history management system that balances persistent storage with disk space constraints. The FluidVoice audio history storage system relies on three tightly-coupled components: DictationAudioHistoryStore for file operations, SettingsStore for budget persistence, and SettingsView for user interaction.
Audio File Storage Location
Audio files are persisted in a dedicated subdirectory of the user's Application Support folder. In Sources/Fluid/Persistence/DictationAudioHistoryStore.swift, the private audioDirectory(createIfNeeded:) method constructs this path:
private func audioDirectory(createIfNeeded: Bool = true) throws -> URL {
guard let base = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else {
throw DictationAudioHistoryError.applicationSupportUnavailable
}
let directory = base
.appendingPathComponent("FluidVoice", isDirectory: true)
.appendingPathComponent("DictationAudioHistory", isDirectory: true)
if createIfNeeded {
try fileManager.createDirectory(at: directory, withIntermediateDirectories: true)
}
return directory
}
When transcription completes, the save(snapshot:entryID:timestamp:model:) method writes a WAV file into this directory and returns a DictationAudioMetadata struct containing the file name, size, sample rate, and channel count. The system maintains each recording as a separate uncompressed WAV file for maximum compatibility and quality retention.
Storage Budget Controls and Enforcement
Users configure storage limits through the Settings UI, backed by SettingsStore in Sources/Fluid/Persistence/SettingsStore.swift. The budget is stored in gigabytes but exposed as bytes for enforcement:
var audioHistoryBudgetGB: Double {
get {
let value = defaults.double(forKey: Keys.audioHistoryBudgetGB)
return value > 0 ? max(0.1, value) : 4.0 // default = 4 GB, minimum = 0.1 GB
}
set {
objectWillChange.send()
defaults.set(max(0.1, newValue), forKey: Keys.audioHistoryBudgetGB)
}
}
var audioHistoryBudgetBytes: Int64 {
DictationAudioHistoryStore.bytes(forGigabytes: self.audioHistoryBudgetGB)
}
The SettingsView retrieves current usage via DictationAudioHistoryStore.audioUsageBytes() and displays a progress indicator comparing usage against SettingsStore.shared.audioHistoryBudgetBytes. When a user reduces the budget below current usage and taps Apply, the applyAudioHistoryBudget() method updates the setting. The next time TranscriptionHistoryStore performs cleanup, it invokes DictationAudioHistoryStore.deleteUnreferencedAudioFiles to evict the oldest audio files until the total size complies with the new budget.
Exporting ZIP Archives with JSONL Manifests
The Export ZIP functionality in SettingsView triggers exportAudioZip(), which delegates to DictationAudioHistoryStore.exportAudioArchive(entries:to:). This implementation follows a strict staging process:
- Validation: Accepts an array of
TranscriptionHistoryEntryobjects, skipping any whereaudioFileExists(for:)returns false. - Staging: Creates a temporary directory and copies selected WAV files into a
staging/audio/subdirectory using deterministic filenames formatted asyyyy-MM-dd'T'HH-mm-ssZ_<uuid>.wav. - Manifest Generation: Writes a
manifest.jsonlfile (line-delimited JSON) usingAudioManifestRowstructs encoded viaJSONEncoder. Each line maps the audio filename to its transcript, timestamp, duration, technical metadata, and model information. - Compression: Executes
/usr/bin/zipwith arguments-qr, the destination path,manifest.jsonl, and theaudiodirectory. Errors are captured and re-thrown asDictationAudioHistoryError.zipFailed.
The resulting archive is saved to the user-selected location (e.g., Desktop) and can be extracted by any standard archive utility for downstream analysis or backup.
Deleting Audio History
For immediate cleanup, SettingsView provides a Delete Audio button that invokes deleteSavedAudio(). This method calls DictationAudioHistoryStore.deleteAllAudioFiles(), which removes every .wav file from the DictationAudioHistory directory without affecting transcription metadata stored elsewhere.
Swift Code Examples
Query Current Usage and Budget
import Fluid
let usageBytes = DictationAudioHistoryStore.shared.audioUsageBytes()
let usageGB = Double(usageBytes) / 1_073_741_824.0
let budgetGB = SettingsStore.shared.audioHistoryBudgetGB
print("Audio usage: \(String(format: "%.2f", usageGB)) GB")
print("Budget: \(budgetGB) GB")
This produces the same metrics displayed in the Settings UI progress bar.
Adjust Storage Budget
SettingsStore.shared.audioHistoryBudgetGB = 2.0
The store automatically converts this to bytes. The next history trimming cycle will delete oldest files if the new limit is exceeded.
Export Selected Entries to ZIP
import Fluid
let entries = TranscriptionHistoryStore.shared.allEntries
let destination = URL(fileURLWithPath: "/Users/me/Desktop/FluidVoice_Export.zip")
do {
try DictationAudioHistoryStore.shared.exportAudioArchive(
entries: entries,
to: destination
)
print("Export succeeded: \(destination.path)")
} catch {
print("Export failed: \(error)")
}
The output contains manifest.jsonl and an audio/ folder with deterministically named WAV files.
Delete All Audio Files
DictationAudioHistoryStore.shared.deleteAllAudioFiles()
print("Audio history cleared")
Summary
- FluidVoice stores unlimited dictation audio as individual WAV files in
~/Library/Application Support/FluidVoice/DictationAudioHistory/(managed byDictationAudioHistoryStore). - Budget enforcement defaults to 4 GB with a 0.1 GB minimum, persisted via
SettingsStoreand enforced byTranscriptionHistoryStoreduring cleanup cycles. - ZIP exports create self-contained archives with a
manifest.jsonlfile linking audio filenames to transcript metadata, generated via temporary staging and/usr/bin/zip. - Deletion can be performed per-entry or globally via
deleteAllAudioFiles(), immediately reclaiming disk space.
Frequently Asked Questions
Where does FluidVoice store audio files on macOS?
Audio files reside in the DictationAudioHistory subdirectory of the app's Application Support folder, typically located at ~/Library/Application Support/FluidVoice/DictationAudioHistory/. This path is constructed programmatically by DictationAudioHistoryStore.audioDirectory() using FileManager URLs for the .applicationSupportDirectory.
What triggers automatic deletion when the storage budget is exceeded?
When you set a new budget lower than current usage in Settings, the value is saved immediately but files are not deleted synchronously. Instead, TranscriptionHistoryStore calls DictationAudioHistoryStore.deleteUnreferencedAudioFiles during its next cleanup cycle (typically after new transcriptions are saved), removing oldest recordings first until the total byte count falls below audioHistoryBudgetBytes.
What format does the ZIP export use?
The ZIP contains two items: a manifest.jsonl file (newline-delimited JSON) where each row is an AudioManifestRow describing one recording, and an audio/ folder containing the actual WAV files named with ISO-8601 timestamps and UUIDs (e.g., 2024-07-01T12-00-00Z_12345678.wav). This structure allows external tools to parse metadata without parsing filenames.
Can I export only specific date ranges of audio history?
Yes. The exportAudioArchive(entries:to:) method accepts any array of TranscriptionHistoryEntry objects. You can filter TranscriptionHistoryStore.shared.allEntries by date, transcription model, or any other property before passing the subset to the export function, creating archives containing only the desired recordings.
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 →