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

> Learn how to add new audio export formats for meeting recordings in Meetily. This guide details modifying retranscription, decoder, and recording_saver Rust files for custom audio output.

- Repository: [Zackriya Solutions/meetily](https://github.com/Zackriya-Solutions/meetily)
- Tags: how-to-guide
- Published: 2026-07-31

---

**To add new audio export formats to Meetily, extend the supported formats array in [`retranscription.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/retranscription.rs), update the FFmpeg conversion logic in [`decoder.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/decoder.rs), and implement the encoding pipeline in [`recording_saver.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/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](https://github.com/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`](https://github.com/Zackriya-Solutions/meetily/blob/main/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`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/retranscription.rs). This constant array controls which formats the UI recognizes and prevents "unsupported file type" errors during validation.

```rust
// 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`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src-tauri/src/audio/decoder.rs). This helper determines whether the format requires external transcoding or can accept raw PCM data directly.

```rust
// 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`](https://github.com/Zackriya-Solutions/meetily/blob/main/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.

```rust
// 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`](https://github.com/Zackriya-Solutions/meetily/blob/main/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.

```rust
// 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`](https://github.com/Zackriya-Solutions/meetily/blob/main/frontend/src/components/Sidebar/ExportButton.tsx) or similar). Import the Tauri API and invoke the command with the format parameter.

```tsx
// 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`](https://github.com/Zackriya-Solutions/meetily/blob/main/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`](https://github.com/Zackriya-Solutions/meetily/blob/main/retranscription.rs) to register new format extensions
- **Configure conversion logic** in [`decoder.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/decoder.rs) to flag formats requiring FFmpeg transcoding
- **Implement encoding** in [`recording_saver.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/recording_saver.rs) using temporary WAV files and FFmpeg conversion
- **Expose commands** in [`recording_commands.rs`](https://github.com/Zackriya-Solutions/meetily/blob/main/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`](https://github.com/Zackriya-Solutions/meetily/blob/main/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`](https://github.com/Zackriya-Solutions/meetily/blob/main/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.