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:
- User selects a format in the React UI and calls
invoke('export_recording', {format}) recording_commands.rsvalidates the format againstSUPPORTED_EXPORT_FORMATSRecordingSaver::save_with_formatwrites raw PCM to a temporary WAV file- For non-WAV formats, FFmpeg converts the temporary file to the target container
- The backend emits a
recording-exportedevent 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.rsto register new format extensions - Configure conversion logic in
decoder.rsto flag formats requiring FFmpeg transcoding - Implement encoding in
recording_saver.rsusing temporary WAV files and FFmpeg conversion - Expose commands in
recording_commands.rswith 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →