Skip to main content
The conversion service provides a high-level API for queuing and managing media conversions from the frontend. It communicates with the Rust backend via Tauri’s IPC layer.

Overview

The conversion service handles:
  • Queueing conversions with the backend
  • Managing conversion lifecycle (pause, resume, cancel)
  • Setting up event listeners for conversion progress and status
  • Handling conversion events (started, progress, completed, error, logs)

Import

import {
  startConversion,
  pauseConversion,
  resumeConversion,
  cancelConversion,
  setupConversionListeners
} from '$lib/services/conversion';

Type Definitions

Event Interfaces

interface ProgressEvent {
  id: string;
  progress: number;
}

interface CompletedEvent {
  id: string;
  outputPath: string;
}

interface ErrorEvent {
  id: string;
  error: string;
}

interface LogEvent {
  id: string;
  line: string;
}

interface StartedEvent {
  id: string;
}

Functions

startConversion

Queues a media conversion job with the backend.
async function startConversion(
  id: string,
  filePath: string,
  config: ConversionConfig,
  outputName?: string
): Promise<void>
Parameters:
  • id - Unique identifier for this conversion job
  • filePath - Absolute path to the source media file
  • config - Conversion configuration object (see Config Service)
  • outputName - Optional custom output filename
Throws: Error if the conversion fails to queue Example:
import { startConversion } from '$lib/services/conversion';
import type { ConversionConfig } from '$lib/types';

const config: ConversionConfig = {
  container: 'mp4',
  videoCodec: 'libx264',
  videoBitrateMode: 'crf',
  crf: 23,
  audioCodec: 'aac',
  audioBitrate: '128',
  // ... other config options
};

await startConversion(
  'conversion-123',
  '/path/to/video.mov',
  config,
  'output.mp4'
);

pauseConversion

Pauses an active conversion.
async function pauseConversion(id: string): Promise<void>
Parameters:
  • id - The conversion job identifier
Example:
try {
  await pauseConversion('conversion-123');
  // Update UI to show paused state
} catch (error) {
  console.error('Failed to pause:', error);
}

resumeConversion

Resumes a paused conversion.
async function resumeConversion(id: string): Promise<void>
Parameters:
  • id - The conversion job identifier
Example:
try {
  await resumeConversion('conversion-123');
  // Update UI to show converting state
} catch (error) {
  console.error('Failed to resume:', error);
}

cancelConversion

Cancels an active or queued conversion.
async function cancelConversion(id: string): Promise<void>
Parameters:
  • id - The conversion job identifier
Example:
try {
  await cancelConversion('conversion-123');
  // Remove from queue or clean up
} catch (error) {
  console.error('Failed to cancel:', error);
}

setupConversionListeners

Sets up event listeners for all conversion events. Returns a cleanup function.
async function setupConversionListeners(
  onProgress: (payload: ProgressEvent) => void,
  onCompleted: (payload: CompletedEvent) => void,
  onError: (payload: ErrorEvent) => void,
  onLog: (payload: LogEvent) => void,
  onStarted: (payload: StartedEvent) => void
): Promise<UnlistenFn>
Parameters:
  • onProgress - Called when conversion progress updates (0-100)
  • onCompleted - Called when conversion completes successfully
  • onError - Called when conversion encounters an error
  • onLog - Called for each FFmpeg log line
  • onStarted - Called when conversion actually starts processing
Returns: Promise that resolves to a cleanup function to remove all listeners Example:
import { setupConversionListeners } from '$lib/services/conversion';

const unlisten = await setupConversionListeners(
  // Progress updates
  ({ id, progress }) => {
    console.log(`Conversion ${id} is ${progress}% complete`);
    updateProgressBar(id, progress);
  },
  
  // Completion
  ({ id, outputPath }) => {
    console.log(`Conversion ${id} completed: ${outputPath}`);
    markAsCompleted(id);
  },
  
  // Errors
  ({ id, error }) => {
    console.error(`Conversion ${id} failed: ${error}`);
    showErrorDialog(id, error);
  },
  
  // FFmpeg logs
  ({ id, line }) => {
    appendLog(id, line);
  },
  
  // Started
  ({ id }) => {
    console.log(`Conversion ${id} started`);
    markAsConverting(id);
  }
);

// Clean up when component unmounts
onDestroy(() => {
  unlisten();
});

Real-World Usage

Here’s how the conversion service is used in Frame’s codebase:
// From: src/lib/features/conversion/useConversionQueue.svelte.ts

import {
  setupConversionListeners,
  startConversion as startConversionService,
  pauseConversion,
  resumeConversion,
  cancelConversion
} from '$lib/services/conversion';
import { FileStatus, type FileItem } from '$lib/types';

export function createConversionQueue(callbacks) {
  // Set up event listeners
  function setupListeners() {
    const unlistenPromise = setupConversionListeners(
      // Progress
      (payload) => {
        callbacks.onFilesUpdate((files) =>
          files.map((f) => {
            if (f.id === payload.id) {
              const status = f.status === FileStatus.QUEUED 
                ? FileStatus.CONVERTING 
                : f.status;
              return { ...f, status, progress: payload.progress };
            }
            return f;
          })
        );
      },
      // Completed
      (payload) => {
        callbacks.onFilesUpdate((files) =>
          files.map((f) =>
            f.id === payload.id 
              ? { ...f, status: FileStatus.COMPLETED, progress: 100 } 
              : f
          )
        );
      },
      // Error
      (payload) => {
        callbacks.onFilesUpdate((files) =>
          files.map((f) =>
            f.id === payload.id
              ? { ...f, status: FileStatus.ERROR, conversionError: payload.error }
              : f
          )
        );
      },
      // Logs
      (payload) => {
        callbacks.onLogsUpdate((logs) => {
          const current = logs[payload.id] || [];
          return { ...logs, [payload.id]: [...current, payload.line] };
        });
      },
      // Started
      (payload) => {
        callbacks.onFilesUpdate((files) =>
          files.map((f) => {
            if (f.id === payload.id && f.status === FileStatus.QUEUED) {
              return { ...f, status: FileStatus.CONVERTING, progress: 0 };
            }
            return f;
          })
        );
      }
    );

    return () => {
      unlistenPromise?.then((unlisten) => unlisten());
    };
  }

  // Queue conversions
  async function startConversion() {
    const pendingFiles = files.filter(
      (f) => f.isSelectedForConversion && 
             f.status !== FileStatus.CONVERTING &&
             f.status !== FileStatus.COMPLETED
    );

    for (const file of pendingFiles) {
      try {
        await startConversionService(
          file.id, 
          file.path, 
          file.config, 
          file.outputName
        );
      } catch (error) {
        console.error('Failed to queue:', error);
      }
    }
  }

  return {
    setupListeners,
    startConversion,
    handlePause: pauseConversion,
    handleResume: resumeConversion,
    cancelTask: cancelConversion
  };
}

Backend Communication

The conversion service communicates with these Tauri commands:
  • queue_conversion - Queues a new conversion job
  • pause_conversion - Pauses an active conversion
  • resume_conversion - Resumes a paused conversion
  • cancel_conversion - Cancels a conversion
And listens to these events:
  • conversion-started - Emitted when conversion begins processing
  • conversion-progress - Emitted periodically with progress updates
  • conversion-completed - Emitted when conversion finishes successfully
  • conversion-error - Emitted when conversion fails
  • conversion-log - Emitted for each FFmpeg output line

Error Handling

All conversion functions throw errors that should be caught:
try {
  await startConversion(id, filePath, config);
} catch (error) {
  if (error instanceof Error) {
    console.error('Conversion failed:', error.message);
  }
  // Handle error in UI
}

Build docs developers (and LLMs) love