How to Switch Between Whisper.cpp and Parakeet Transcription Models in Meetily
You can switch between Whisper.cpp and Parakeet transcription models in Meetily at runtime using the set_transcription_provider Tauri command, which persists the selection to the settings database and re-initializes the appropriate engine without requiring an application restart.
Meetily, an open-source meeting transcription application from Zackriya-Solutions/meetily, ships with dual speech-to-text back-ends to accommodate different hardware configurations. The codebase provides a unified abstraction over Whisper.cpp for CPU-friendly inference and Parakeet (NVIDIA NeMo) for GPU-accelerated real-time transcription, allowing users to toggle between them dynamically based on their system's capabilities.
Understanding the Two Transcription Back-Ends
Meetily implements two distinct transcription engines that expose an identical API surface:
-
Whisper.cpp (
frontend/src-tauri/src/whisper_engine/whisper_engine.rs): The original Whisper implementation optimized for CPU inference. This engine provides higher accuracy but requires more processing time, making it suitable for post-meeting transcription or systems without NVIDIA GPUs. -
Parakeet (
frontend/src-tauri/src/parakeet_engine/parakeet_engine.rs): A real-time transcription engine built on NVIDIA NeMo. This option requires modern NVIDIA hardware but delivers significantly faster processing speeds, ideal for live captioning during meetings.
Both engines implement the same three core methods: load_model, transcribe_audio, and unload_model, enabling seamless swapping through a common interface.
How Provider Selection Works in Meetily's Settings
The transcription provider is not hard-coded but stored in the application's settings database, allowing for persistent configuration across sessions.
Where the Provider Configuration is Stored
During initial onboarding, the selected provider is persisted in the settings table. As seen in frontend/src-tauri/src/onboarding.rs, the application saves the configuration at line 200:
info!("Saved transcription model config: provider=parakeet, model={}", DEFAULT_PARAKEET_MODEL);
This settings value is read by the audio pipeline when transcription begins, determining which engine receives the audio stream.
The Runtime Engine Selection Logic
The audio pipeline (frontend/src-tauri/src/audio/pipeline.rs) does not directly instantiate engines. Instead, it queries the current provider from the settings repository and routes audio chunks to the corresponding global engine instance:
let provider = settings.get_transcription_provider().await?;
match provider.as_str() {
"parakeet" => {
let engine = PARAKEET_ENGINE.read().await;
engine.transcribe_audio(samples, language).await
}
"whisper" => {
let engine = WHISPER_ENGINE.read().await;
engine.transcribe_audio(samples, language).await
}
_ => Err(anyhow!("Unknown transcription provider")),
}
The global engine handles are stored as Arc<RwLock<Option<...>>> in frontend/src-tauri/src/parakeet_engine/mod.rs and frontend/src-tauri/src/whisper_engine/mod.rs, ensuring thread-safe access from any async task.
Switching Models Programmatically
Meetily exposes two primary Tauri commands that the frontend invokes to manage transcription providers:
Using set_transcription_provider Command
The set_transcription_provider command, implemented in both frontend/src-tauri/src/parakeet_engine/commands.rs and frontend/src-tauri/src/whisper_engine/commands.rs, handles the runtime switch:
#[tauri::command]
pub async fn set_transcription_provider(app: AppHandle, provider: String) -> Result<(), String> {
// Persist the provider value
let mut settings = SettingsRepository::new(app.clone()).await?;
settings.set_transcription_provider(&provider).await?;
// Re-initialise the appropriate engine
match provider.as_str() {
"parakeet" => ParakeetEngine::initialize(app).await?,
"whisper" => WhisperEngine::initialize(app).await?,
_ => return Err(format!("Unsupported provider: {}", provider)),
}
Ok(())
}
To switch to Parakeet from the frontend JavaScript/TypeScript:
await invoke('set_transcription_provider', { provider: 'parakeet' });
To switch back to Whisper.cpp:
await invoke('set_transcription_provider', { provider: 'whisper' });
Loading Specific Models with load_transcription_model
After setting the provider, you must load a specific model variant using the load_transcription_model command. Each engine implements this command in its respective commands file.
For Whisper.cpp (frontend/src-tauri/src/whisper_engine/commands.rs):
#[tauri::command]
pub async fn load_transcription_model(app: AppHandle, model_name: String) -> Result<(), String> {
let mut guard = WHISPER_ENGINE.write().await;
let engine = WhisperEngine::new().map_err(|e| e.to_string())?;
engine.load_model(&model_name).await.map_err(|e| e.to_string())?;
*guard = Some(engine);
Ok(())
}
For Parakeet (frontend/src-tauri/src/parakeet_engine/commands.rs):
#[tauri::command]
pub async fn load_transcription_model(app: AppHandle, model_name: String) -> Result<(), String> {
let mut guard = PARAKEET_ENGINE.write().await;
let engine = ParakeetEngine::new().map_err(|e| e.to_string())?;
engine.load_model(&model_name).await.map_err(|e| e.to_string())?;
*guard = Some(engine);
Ok(())
}
Example usage from the frontend:
// Load Parakeet large_v2 model
await invoke('set_transcription_provider', { provider: 'parakeet' });
await invoke('load_transcription_model', { model_name: 'large_v2' });
// Or load Whisper base model
await invoke('set_transcription_provider', { provider: 'whisper' });
await invoke('load_transcription_model', { model_name: 'base' });
Technical Implementation in the Audio Pipeline
When recording begins, the audio pipeline reads the provider from settings and routes audio chunks accordingly. This architecture ensures that:
- No restart is required: The engine switch happens immediately upon invoking
set_transcription_provider - Thread safety: Global engine handles use
Arc<RwLock<Option<Engine>>>to allow safe concurrent access - Clean state transitions: Each engine's
initializemethod properly sets up the global instance before transcription begins
UI Feedback and Model Availability
The system tray UI (frontend/src-tauri/src/tray.rs) monitors model availability for the selected provider. When switching providers, the tray refreshes its status to reflect whether the required model files are downloaded:
match crate::parakeet_engine::commands::parakeet_has_available_models().await {
true => menu_item.enable(),
false => menu_item.disable(),
}
If the chosen provider lacks a downloaded model, the recording option is temporarily disabled and the menu displays "⏳ Downloading transcription model…" until the files are available.
Summary
- Meetily supports Whisper.cpp (CPU/GPU, higher accuracy) and Parakeet (GPU-only, real-time speed) through a unified engine interface.
- Use
set_transcription_providerto switch engines at runtime, which updates the settings database and re-initializes the appropriate global engine instance. - Call
load_transcription_modelafter switching to download and initialize specific model variants (e.g., "base" for Whisper or "large_v2" for Parakeet). - The audio pipeline (
audio/pipeline.rs) routes audio to the correct engine based on the stored provider setting, using thread-safeArc<RwLock>handles defined in each engine'smod.rs. - The system tray (
tray.rs) provides visual feedback by enabling or disabling recording based on model download status for the active provider.
Frequently Asked Questions
Can I switch between Whisper and Parakeet without restarting Meetily?
Yes. The set_transcription_provider command handles the switch immediately by writing the new provider to the settings database and calling the appropriate initialize method. The audio pipeline reads this setting on the next transcription request, routing audio to the newly selected engine without requiring an application restart.
What happens if I select Parakeet but don't have an NVIDIA GPU?
The Parakeet engine requires NVIDIA hardware and will fail to initialize if CUDA-compatible devices are unavailable. You should check GPU availability before switching or handle the error returned by set_transcription_provider. The codebase stores engine instances as Option<Engine> in global locks, so initialization failures won't crash the application, but transcription will be unavailable until you switch to Whisper.cpp.
Where does Meetily store which transcription model is currently active?
The active provider is stored in the settings repository, persisted to a local database. During onboarding (frontend/src-tauri/src/onboarding.rs), the selection is saved via settings.set_transcription_provider(). The audio pipeline and system tray read this value at runtime using settings.get_transcription_provider() to determine which engine to use and which availability checks to perform.
How do I know when the transcription model has finished downloading?
The system tray menu (frontend/src-tauri/src/tray.rs) monitors model availability through provider-specific checks like parakeet_has_available_models(). When a model is downloading, the tray disables the recording menu item and shows "⏳ Downloading transcription model…". Once the model files are present, the menu item automatically enables, allowing recording to proceed.
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 →