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:

  1. Validation: Accepts an array of TranscriptionHistoryEntry objects, skipping any where audioFileExists(for:) returns false.
  2. Staging: Creates a temporary directory and copies selected WAV files into a staging/audio/ subdirectory using deterministic filenames formatted as yyyy-MM-dd'T'HH-mm-ssZ_<uuid>.wav.
  3. Manifest Generation: Writes a manifest.jsonl file (line-delimited JSON) using AudioManifestRow structs encoded via JSONEncoder. Each line maps the audio filename to its transcript, timestamp, duration, technical metadata, and model information.
  4. Compression: Executes /usr/bin/zip with arguments -qr, the destination path, manifest.jsonl, and the audio directory. Errors are captured and re-thrown as DictationAudioHistoryError.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 by DictationAudioHistoryStore).
  • Budget enforcement defaults to 4 GB with a 0.1 GB minimum, persisted via SettingsStore and enforced by TranscriptionHistoryStore during cleanup cycles.
  • ZIP exports create self-contained archives with a manifest.jsonl file 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →