# How Meetily's Retranscription Feature Re-Processes Audio: Implementation Deep Dive

> Discover how Meetily's retranscription feature re-processes audio using a Rust async pipeline and VAD segmentation. Learn about its implementation with Whisper or Parakeet.

- Repository: [Zackriya Solutions/meetily](https://github.com/Zackriya-Solutions/meetily)
- Tags: deep-dive
- Published: 2026-07-11

---

**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`](https://github.com/Zackriya-Solutions/meetily/blob/main/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`](https://github.com/Zackriya-Solutions/meetily/blob/main/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`](https://github.com/Zackriya-Solutions/meetily/blob/main/transcripts.json) and regenerates [`metadata.json`](https://github.com/Zackriya-Solutions/meetily/blob/main/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

```typescript
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

```typescript
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

```typescript
await invoke('cancel_retranscription_command');

```

### Checking Active Status

```typescript
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`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/retranscription.rs) | Core pipeline implementation, concurrency guards, and database transactions |
| [`frontend/src-tauri/src/audio/vad.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/vad.rs) | Voice activity detection via `get_speech_chunks_with_progress` |
| [`frontend/src-tauri/src/audio/decoder.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/decoder.rs) | Audio decoding and `to_whisper_format` resampling |
| [`frontend/src-tauri/src/whisper_engine/whisper_engine.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/whisper_engine/whisper_engine.rs) | Whisper transcription with confidence scoring |
| [`frontend/src-tauri/src/parakeet_engine/parakeet_engine.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/parakeet_engine/parakeet_engine.rs) | Parakeet engine integration |
| [`frontend/src-tauri/src/database/models.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/database/models.rs) | SQLite schema for transcript storage |
| [`frontend/src-tauri/src/state.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/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`](https://github.com/Zackriya-Solutions/meetily/blob/main/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.