How to Add New Audio Export Formats for Meeting Recordings in Meetily

To add new audio export formats to Meetily, extend the supported formats array in retranscription.rs, update the FFmpeg conversion logic in decoder.rs, and implement the encoding pipeline in recording_saver.rs before exposing the new format through the Tauri command layer and React frontend.

Meetily stores recorded meeting audio as raw PCM data and defaults to WAV format when exporting completed sessions. To support additional containers like MP3, OGG, or FLAC, you must extend the Tauri-Rust backend located in frontend/src-tauri/src/audio/. This guide walks through the exact implementation using source code from the Zackriya-Solutions/meetily repository.

Understanding the Export Architecture

Meetily's audio pipeline processes raw PCM buffers through three distinct stages before writing the final file. The system uses WavWriter for native WAV output and spawns FFmpeg processes for compressed formats that require transcoding. All export logic executes in the Rust backend, with the React frontend invoking commands through Tauri's IPC bridge.

The export flow follows this sequence:

  1. User selects a format in the React UI and calls invoke('export_recording', {format})
  2. recording_commands.rs validates the format against SUPPORTED_EXPORT_FORMATS
  3. RecordingSaver::save_with_format writes raw PCM to a temporary WAV file
  4. For non-WAV formats, FFmpeg converts the temporary file to the target container
  5. The backend emits a recording-exported event and returns the file path

Step-by-Step Implementation Guide

1. Register the Format in the Supported Types List

First, add your new extension to the whitelist in frontend/src-tauri/src/audio/retranscription.rs. This constant array controls which formats the UI recognizes and prevents "unsupported file type" errors during validation.

// frontend/src-tauri/src/audio/retranscription.rs
pub const SUPPORTED_EXPORT_FORMATS: &[&str] = &[
    "wav", "mp3", "m4a", "mp4",   // existing formats
    "ogg", "flac",               // ← new formats added
];

2. Configure FFmpeg Conversion Requirements

Update the needs_ffmpeg_conversion function in frontend/src-tauri/src/audio/decoder.rs. This helper determines whether the format requires external transcoding or can accept raw PCM data directly.

// frontend/src-tauri/src/audio/decoder.rs
pub fn needs_ffmpeg_conversion(path: &Path) -> bool {
    match path.extension().and_then(|e| e.to_str()) {
        Some("wav") => false,
        Some("mp3") => true,   // requires FFmpeg encoding
        Some("ogg") => true,   // ← added: requires transcoding
        Some("flac") => true,  // ← added: requires transcoding
        _ => true,
    }
}

3. Implement the Encoding Pipeline

Modify frontend/src-tauri/src/audio/recording_saver.rs to handle the new format in the save_with_format method. The existing pattern writes a temporary WAV file first, then spawns FFmpeg for conversion when necessary.

// frontend/src-tauri/src/audio/recording_saver.rs
pub async fn save_with_format(
    raw_pcm: &[i16],
    sample_rate: u32,
    format: &str,
) -> Result<PathBuf, String> {
    // Write temporary WAV from raw PCM buffer
    let tmp_wav = std::env::temp_dir().join("tmp_recording.wav");
    wav_writer::write_wav(&tmp_wav, raw_pcm, sample_rate)?;

    // For WAV format, no conversion needed
    if format == "wav" {
        return Ok(final_destination);
    }

    // For other formats, invoke FFmpeg
    let out_path = data_dir.join(format!("recording-{}.{}", timestamp, format));
    let status = Command::new("ffmpeg")
        .args(&["-y", "-i"])
        .arg(&tmp_wav)
        .arg(&out_path)
        .status()
        .map_err(|e| e.to_string())?;

    if status.success() {
        Ok(out_path)
    } else {
        Err("FFmpeg conversion failed".into())
    }
}

4. Expose the Tauri Command

Ensure frontend/src-tauri/src/audio/recording_commands.rs validates incoming format strings against the whitelist before processing. The command should emit the recording-exported event on success so the UI can display notifications.

// frontend/src-tauri/src/audio/recording_commands.rs
#[tauri::command]
async fn export_recording(
    app: AppHandle,
    format: String,
) -> Result<String, String> {
    // Validate format against supported list
    if !retranscription::SUPPORTED_EXPORT_FORMATS.contains(&format.as_str()) {
        return Err("Unsupported export format".into());
    }
    
    let saver = RecordingSaver::new(app.clone());
    let path = saver.save_with_format(&app.state().audio_buffer, format).await?;
    
    // Notify frontend of successful export
    app.emit_all("recording-exported", path.to_string_lossy())?;
    Ok(path.to_string_lossy().into())
}

5. Update the React Frontend

Add the new format option to your export component (typically located in frontend/src/components/Sidebar/ExportButton.tsx or similar). Import the Tauri API and invoke the command with the format parameter.

// frontend/src/components/Sidebar/ExportButton.tsx
import { invoke } from '@tauri-apps/api/tauri';

async function exportRecording(format: string) {
  try {
    const filePath = await invoke<string>('export_recording', { format });
    toast.success(`Exported to ${filePath}`);
  } catch (e) {
    toast.error(`Export failed: ${e}`);
  }
}

// Usage in component
<select onChange={(e) => exportRecording(e.target.value)}>
  <option value="wav">WAV</option>
  <option value="mp3">MP3</option>
  <option value="ogg">OGG</option>  {/* ← new format */}
  <option value="flac">FLAC</option> {/* ← new format */}
</select>

6. Update Analytics (Optional)

If you track export metrics, modify frontend/src-tauri/src/analytics/analytics.rs to include the new format suffix in logged events. This ensures accurate telemetry for user behavior analysis.

Summary

  • Extend the whitelist in retranscription.rs to register new format extensions
  • Configure conversion logic in decoder.rs to flag formats requiring FFmpeg transcoding
  • Implement encoding in recording_saver.rs using temporary WAV files and FFmpeg conversion
  • Expose commands in recording_commands.rs with proper validation and event emission
  • Update the UI in React components to invoke the Tauri command with the new format parameter
  • Test locally to ensure FFmpeg is available in the system PATH for transcoding operations

Frequently Asked Questions

Does Meetily require external dependencies for new audio formats?

Yes. For any format other than WAV, Meetily relies on FFmpeg being installed on the host system. The save_with_format function in recording_saver.rs spawns an FFmpeg process to transcode the raw PCM data into compressed containers like MP3 or OGG. Ensure FFmpeg is available in the system PATH before attempting exports.

Can I use pure Rust encoder crates instead of FFmpeg?

Yes. While the current implementation uses FFmpeg for transcoding, you can modify recording_saver.rs to use Rust crates like lewton for OGG or claxon for FLAC encoding. Replace the FFmpeg Command spawn with the appropriate crate API calls, writing directly from the PCM buffer without the temporary WAV intermediate step.

Where does Meetily store recordings before export?

Meetily keeps active recordings in memory as raw PCM buffers during the meeting. When you trigger an export, RecordingSaver writes this buffer to a temporary WAV file in the system temp directory (std::env::temp_dir()), then converts to the target format. The temporary file is cleaned up automatically after successful conversion.

How do I handle export failures in the frontend?

The export_recording command returns a Result type that propagates errors from the Rust backend. Wrap your invoke call in a try-catch block to capture validation errors, FFmpeg failures, or file system issues. The backend emits descriptive error messages that you can display in UI toast notifications.

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 →