How Meetily's Retranscription Feature Re-Processes Audio: Implementation Deep Dive
Meetily's retranscription feature uses a Rust-based async pipeline with global AtomicBool guards to prevent concurrent runs, decodes existing audio files through VAD segmentation, and re-runs transcription through either Whisper or Parakeet engines before atomically replacing database records.
Meetily, an open-source meeting transcription application, allows users to re-process recorded audio with different models or languages without re-recording the meeting. The retranscription implementation lives in frontend/src-tauri/src/audio/retranscription.rs and provides a robust, cancellable batch pipeline with real-time progress updates to the frontend.
Pipeline Initialization and Concurrency Safety
The retranscription pipeline begins with strict safeguards to prevent resource conflicts and ensure only one job runs at a time.
Global State Guards
At the module level, a static AtomicBool named RETRANSMISSION_IN_PROGRESS acts as a global flag to track execution state. The code wraps this in a RetranscriptionGuard struct that implements RAII semantics—automatically clearing the flag when the guard drops, even if the thread panics or returns early. This implementation spans lines 19–46 in retranscription.rs.
Tauri Command Interface
The entry point start_retranscription_command (lines 77–89) is exposed as an async Tauri command. It immediately checks the guard to reject overlapping invocations, then spawns the heavy processing into a background Tokio task. Before processing begins, the cancellation flag RETRANSMISSION_CANCELLED is explicitly reset to false (lines 101–103), ensuring a clean state for the new job.
Audio Discovery and Decoding
Once initialized, the system locates and prepares the source media.
File Discovery
The helper function find_audio_file (lines 39–68) searches for common filenames like audio.mp4 or audio.wav within the meeting folder. If not found, it falls back to scanning for any extension listed in the AUDIO_EXTENSIONS constant.
Format Conversion and Resampling
The discovered file undergoes decode_audio_file (lines 99–106) on a dedicated blocking thread via tokio::task::spawn_blocking. The raw PCM is then converted to 16 kHz mono format using to_whisper_format (lines 119–126), matching the expected input for Whisper-based models. Both operations are CPU-intensive and isolated to prevent blocking the async runtime.
Speech Processing and Segmentation
Before transcription, the audio is segmented to improve accuracy and memory efficiency.
Voice Activity Detection
The resampled audio feeds into get_speech_chunks_with_progress from the vad module (lines 140–162). This implementation uses a batch redemption time of 2000 ms (VAD_REDEMPTION_TIME_MS) to detect speech boundaries while emitting incremental progress updates back to the UI.
Segment Splitting
Very long speech chunks exceeding 25 seconds are split at silence boundaries using split_segment_at_silence (lines 172–190). This produces a processable_segments list optimized for the transcription engine's context window, preventing truncation artifacts in the final output.
Transcription Engine Execution
The pipeline supports pluggable transcription engines, allowing users to switch between Whisper and Parakeet providers.
Lazy Engine Initialization
Depending on the provider argument passed from the frontend, the code lazily initializes either:
- Whisper: via
get_or_init_whisper - Parakeet: via
get_or_init_parakeet
This logic appears at lines 202–229. The selected model defaults to the value stored in the transcript_settings SQLite table if not explicitly overridden.
Segment Processing Loop
The transcription loop (lines 250–328) processes each segment sequentially:
- Parakeet: Calls
engine.transcribe_audio - Whisper: Calls
engine.transcribe_audio_with_confidence
Empty or sub-100 ms segments are skipped to optimize throughput. Throughout this loop, progress updates scale from 20% to 80%, providing granular feedback to the frontend.
Data Persistence and Atomic Updates
Once transcription completes, the system ensures data consistency through atomic database operations.
SQLite Transaction
All existing transcripts for the meeting are deleted before inserting new records, wrapped in a single SQLite transaction (lines 340–361). This prevents partial state—a user either sees the old transcripts or the new ones, never a mixture.
Metadata and Timestamp Tracking
The function writes updated transcripts.json and regenerates metadata.json with a retranscribed_at timestamp (lines 380–408). This metadata allows the frontend to identify retranscribed meetings and display the processing history.
Cancellation and Progress Reporting
The feature provides cooperative multitasking support for user-initiated aborts.
Cooperative Cancellation Mechanism
The cancel_retranscription_command (lines 209–215) flips the RETRANSMISSION_CANCELLED AtomicBool to true. The long-running pipeline checks this flag at several checkpoints (lines 94–99), allowing it to abort gracefully between major stages rather than force-killing the thread.
Real-time UI Updates
Throughout the pipeline, emit_progress sends retranscription-progress events containing the current stage, percentage, and message. On completion, the system emits retranscription-complete; on failure, it emits retranscription-error with diagnostic details (lines 1–18 define the emitter, with calls at lines 96–124).
Implementation Examples
Starting Retranscription from the Frontend
import { invoke } from '@tauri-apps/api/tauri';
// Parameters can be omitted to use defaults from the database
await invoke('start_retranscription_command', {
meeting_id: 'c3f2a9e7-1234-5678-90ab-cdef12345678',
meeting_folder_path: '/Users/alice/Library/Application Support/Meetily/meetings/c3f2a9e7-1234-5678-90ab-cdef12345678',
language: 'en', // optional
model: 'medium', // optional
provider: 'localWhisper' // "parakeet" or omit for Whisper
});
Listening for Progress Updates
import { listen } from '@tauri-apps/api/event';
await listen('retranscription-progress', (event) => {
const p = event.payload as {
meeting_id: string;
stage: string;
progress_percentage: number;
message: string;
};
console.log(`[${p.stage}] ${p.progress_percentage}% – ${p.message}`);
});
Cancelling an Active Job
await invoke('cancel_retranscription_command');
Checking Active Status
const inProgress = await invoke<boolean>('is_retranscription_in_progress_command');
console.log('Retranscription running?', inProgress);
Key Source Files
| File | Role |
|---|---|
frontend/src-tauri/src/audio/retranscription.rs |
Core pipeline implementation, concurrency guards, and database transactions |
frontend/src-tauri/src/audio/vad.rs |
Voice activity detection via get_speech_chunks_with_progress |
frontend/src-tauri/src/audio/decoder.rs |
Audio decoding and to_whisper_format resampling |
frontend/src-tauri/src/whisper_engine/whisper_engine.rs |
Whisper transcription with confidence scoring |
frontend/src-tauri/src/parakeet_engine/parakeet_engine.rs |
Parakeet engine integration |
frontend/src-tauri/src/database/models.rs |
SQLite schema for transcript storage |
frontend/src-tauri/src/state.rs |
Application state including the database manager |
Summary
- Concurrency Safety: Global
AtomicBoolflags and RAII guards prevent concurrent retranscription jobs and ensure cleanup on panic. - Audio Pipeline: Files are discovered, decoded, resampled to 16 kHz mono, and segmented using VAD with a 2000 ms redemption time.
- Engine Flexibility: Supports lazy-loaded Whisper and Parakeet engines, selectable via the
providerparameter. - Data Integrity: Existing transcripts are replaced atomically within a SQLite transaction, accompanied by updated metadata timestamps.
- User Control: Cooperative cancellation via
RETRANSMISSION_CANCELLEDflag and real-time progress events via Tauri’s event system.
Frequently Asked Questions
How does Meetily prevent concurrent retranscription jobs?
The system uses a global AtomicBool named RETRANSMISSION_IN_PROGRESS combined with a RetranscriptionGuard struct that implements RAII semantics. When start_retranscription_command is invoked, it attempts to acquire the guard; if the flag is already true, the command rejects the new request. The guard automatically clears the flag when it drops, ensuring the state resets even if the thread panics.
Can I switch between Whisper and Parakeet models during retranscription?
Yes. The start_retranscription_command accepts an optional provider parameter (values like "localWhisper" or "parakeet"). The code at lines 202–229 lazily initializes the requested engine via get_or_init_whisper or get_or_init_parakeet, allowing you to re-process the same audio with different transcription backends without re-recording.
What happens to existing transcripts when retranscription completes?
All existing transcripts for the meeting are deleted and replaced atomically within a single SQLite transaction (lines 340–361). This ensures users never see a mixture of old and new segments. The operation also updates metadata.json with a retranscribed_at timestamp to track when the reprocessing occurred.
How does the cancellation mechanism work?
Calling cancel_retranscription_command sets the RETRANSMISSION_CANCELLED AtomicBool to true. The pipeline checks this flag at strategic checkpoints (lines 94–99) between major stages like decoding, VAD, and transcription. This cooperative approach allows the system to abort cleanly between segments rather than risking data corruption through forceful termination.
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 →