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 AtomicBool flags 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 provider parameter.
  • Data Integrity: Existing transcripts are replaced atomically within a SQLite transaction, accompanied by updated metadata timestamps.
  • User Control: Cooperative cancellation via RETRANSMISSION_CANCELLED flag 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:

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 →