Skip to main content
The config service provides utilities for normalizing and validating conversion configurations. It ensures that all configuration options are compatible with each other and with the source media.

Overview

The config service handles:
  • Normalizing conversion configurations based on container and codec compatibility
  • Validating configuration options against source metadata
  • Ensuring codec and container compatibility
  • Handling audio-only and GIF-specific configurations
  • Validating hardware encoder settings
  • Normalizing GIF-specific options (colors, dither, loop)

Import

import { normalizeConversionConfig } from '$lib/services/config';

Type Definitions

ConversionConfig

Complete conversion configuration structure defined in the Frame source code.
interface ConversionConfig {
  // Processing mode
  processingMode?: 'reencode' | 'copy';
  
  // Container and codecs
  container: string;
  videoCodec: string;
  videoBitrateMode: 'crf' | 'bitrate';
  videoBitrate: string;
  audioCodec: string;
  audioBitrate: string;
  audioChannels: string;
  
  // Audio settings
  audioVolume: number;
  audioNormalize: boolean;
  selectedAudioTracks: number[];
  
  // Subtitle settings
  selectedSubtitleTracks: number[];
  subtitleBurnPath?: string;
  
  // Video settings
  resolution: string;
  customWidth?: string;
  customHeight?: string;
  scalingAlgorithm: 'bicubic' | 'lanczos' | 'bilinear' | 'nearest';
  fps: string;
  crf: number;
  quality: number;
  preset: string;
  
  // Timing
  startTime?: string;
  endTime?: string;
  
  // Metadata
  metadata: MetadataConfig;
  
  // Transformations
  rotation: '0' | '90' | '180' | '270';
  flipHorizontal: boolean;
  flipVertical: boolean;
  crop?: CropSettings | null;
  
  // Advanced
  mlUpscale?: 'none' | 'esrgan-2x' | 'esrgan-4x';
  nvencSpatialAq: boolean;
  nvencTemporalAq: boolean;
  videotoolboxAllowSw: boolean;
  hwDecode: boolean;
  
  // GIF-specific
  gifColors?: number;
  gifDither?: 'none' | 'bayer' | 'floyd_steinberg' | 'sierra2_4a';
  gifLoop?: number;
}

MetadataConfig

type MetadataMode = 'preserve' | 'clean' | 'replace';

interface MetadataConfig {
  mode: MetadataMode;
  title?: string;
  artist?: string;
  album?: string;
  genre?: string;
  date?: string;
  comment?: string;
}

CropSettings

interface CropSettings {
  enabled: boolean;
  x: number;
  y: number;
  width: number;
  height: number;
  sourceWidth?: number;
  sourceHeight?: number;
  aspectRatio?: string | null;
}

SourceMetadata

interface SourceMetadata {
  duration?: string;
  bitrate?: string;
  videoCodec?: string;
  audioCodec?: string;
  resolution?: string;
  frameRate?: number;
  width?: number;
  height?: number;
  videoBitrateKbps?: number;
  audioTracks?: AudioTrack[];
  subtitleTracks?: SubtitleTrack[];
  tags?: Record<string, string>;
  pixelFormat?: string;
  colorSpace?: string;
  colorRange?: string;
  colorPrimaries?: string;
  profile?: string;
}

Functions

normalizeConversionConfig

Normalizes a conversion configuration to ensure all options are compatible with the selected container, codecs, and source media.
function normalizeConversionConfig(
  config: ConversionConfig,
  metadata?: SourceMetadata
): ConversionConfig
Parameters:
  • config - The conversion configuration to normalize
  • metadata - Optional source media metadata for validation
Returns: Normalized configuration with all compatibility issues resolved Example:
import { normalizeConversionConfig } from '$lib/services/config';
import type { ConversionConfig, SourceMetadata } from '$lib/types';

const rawConfig: ConversionConfig = {
  container: 'mp4',
  videoCodec: 'libx264',
  audioCodec: 'vorbis', // Invalid for MP4!
  // ... other options
};

const metadata: SourceMetadata = {
  videoCodec: 'h264',
  width: 1920,
  height: 1080,
  // ... other metadata
};

const normalized = normalizeConversionConfig(rawConfig, metadata);
// normalized.audioCodec is now 'aac' (valid for MP4)

Normalization Rules

The normalization function applies these rules:

Audio-Only Sources

If the source has no video codec:
  • Container is changed to an audio-only format if invalid (defaults to MP3)
  • ML upscaling is disabled
  • Subtitle tracks are cleared
const config = normalizeConversionConfig(rawConfig, { audioCodec: 'mp3' });
// If container was 'mp4', it might become 'mp3'

GIF-Specific

For GIF output:
  • gifColors is clamped to 2-256
  • gifLoop is clamped to 0-65535
  • gifDither defaults to ‘sierra2_4a’ if invalid
  • Video codec is forced to ‘gif’
  • Bitrate mode is forced to ‘crf’
  • ML upscaling is disabled
  • Hardware acceleration is disabled
const config = normalizeConversionConfig({
  container: 'gif',
  gifColors: 1000, // Too high!
  gifLoop: -5,     // Invalid!
  // ...
});
// config.gifColors === 256 (clamped)
// config.gifLoop === 0 (clamped)
// config.videoCodec === 'gif' (forced)

Copy Mode

When processingMode is ‘copy’:
  • Subtitle burning is disabled
  • Audio normalization is disabled
  • Audio volume reset to 100
  • Resolution set to ‘original’
  • FPS set to ‘original’
  • Rotation reset to ‘0’
  • Flips disabled
  • Crop disabled
  • ML upscaling disabled
  • Hardware acceleration disabled
const config = normalizeConversionConfig({
  processingMode: 'copy',
  audioNormalize: true,  // Will be disabled
  rotation: '90',        // Will be reset
  // ...
});
// config.audioNormalize === false
// config.rotation === '0'

Container Compatibility

Audio and subtitle support based on container:
// Audio-only container (mp3, flac, etc.)
const config = normalizeConversionConfig({
  container: 'mp3',
  selectedSubtitleTracks: [0], // Will be cleared
  // ...
});
// config.selectedSubtitleTracks === []

// GIF container
const gifConfig = normalizeConversionConfig({
  container: 'gif',
  selectedAudioTracks: [0], // Will be cleared
  // ...
});
// gifConfig.selectedAudioTracks === []

Codec Compatibility

Audio and video codecs are validated against the container:
// Invalid audio codec for container
const config = normalizeConversionConfig({
  container: 'mp4',
  audioCodec: 'vorbis', // Invalid for MP4
  // ...
});
// config.audioCodec === 'aac' (default for MP4)

// Invalid video codec for container
const videoConfig = normalizeConversionConfig({
  container: 'webm',
  videoCodec: 'libx264', // Invalid for WebM
  // ...
});
// videoConfig.videoCodec === 'vp8' or 'vp9' (first allowed)

Hardware Encoder Settings

NVENC and VideoToolbox settings are validated:
const config = normalizeConversionConfig({
  videoCodec: 'libx264', // Not a hardware encoder
  nvencSpatialAq: true,  // Will be disabled
  nvencTemporalAq: true, // Will be disabled
  hwDecode: true,        // Will be disabled
  // ...
});
// config.nvencSpatialAq === false
// config.nvencTemporalAq === false
// config.hwDecode === false

ML Upscaling

ML upscaling is incompatible with resolution changes:
const config = normalizeConversionConfig({
  mlUpscale: 'esrgan-2x',
  resolution: '1080p', // Incompatible!
  // ...
});
// config.resolution === 'original' (forced)

Video Preset Validation

Video encoder presets are validated:
const config = normalizeConversionConfig({
  videoCodec: 'libx265',
  preset: 'veryfast', // Might be invalid for libx265
  // ...
});
// config.preset will be set to first allowed preset for libx265

Real-World Usage

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

import { normalizeConversionConfig } from '$lib/services/config';
import { cloneConfig } from '$lib/services/presets';
import type { PresetDefinition, FileItem } from '$lib/types';

function applyPresetToFile(
  preset: PresetDefinition,
  file: FileItem
): ConversionConfig {
  // Clone the preset config to avoid mutations
  const cloned = cloneConfig(preset.config);
  
  // Normalize against file metadata
  const normalized = normalizeConversionConfig(
    cloned,
    file.metadata
  );
  
  return normalized;
}

// Apply preset to selected file
function applyPresetToSelection(preset: PresetDefinition) {
  const selectedFile = getSelectedFile();
  if (!selectedFile) return;

  const nextConfig = normalizeConversionConfig(
    cloneConfig(preset.config),
    selectedFile.metadata
  );
  
  updateFileConfig(selectedFile.id, nextConfig);
}

// Apply preset to all idle files
function applyPresetToAll(preset: PresetDefinition) {
  const files = getFiles();
  
  files.forEach((file) => {
    if (file.status === 'IDLE') {
      const nextConfig = normalizeConversionConfig(
        cloneConfig(preset.config),
        file.metadata  // Each file gets normalized config
      );
      updateFileConfig(file.id, nextConfig);
    }
  });
}

Best Practices

Always Normalize User Input

Normalize configurations before saving or using them:
// Good
function updateConfig(changes: Partial<ConversionConfig>) {
  const updated = { ...currentConfig, ...changes };
  const normalized = normalizeConversionConfig(updated, metadata);
  setConfig(normalized);
}

// Bad - might have incompatible settings
function updateConfig(changes: Partial<ConversionConfig>) {
  const updated = { ...currentConfig, ...changes };
  setConfig(updated); // Could be invalid!
}

Provide Metadata When Available

Always pass source metadata for better normalization:
// Good - respects source constraints
const config = normalizeConversionConfig(rawConfig, sourceMetadata);

// Less ideal - can't validate against source
const config = normalizeConversionConfig(rawConfig);

Normalize After Preset Application

Always normalize after applying a preset:
import { cloneConfig } from '$lib/services/presets';
import { normalizeConversionConfig } from '$lib/services/config';

const presetConfig = cloneConfig(preset.config);
const normalized = normalizeConversionConfig(
  presetConfig,
  fileMetadata
);

Re-normalize on Container Change

Re-normalize when the container changes:
function handleContainerChange(newContainer: string) {
  const updated = { 
    ...currentConfig, 
    container: newContainer 
  };
  
  // Re-normalize to fix codec compatibility
  const normalized = normalizeConversionConfig(updated, metadata);
  setConfig(normalized);
}

Validation Helpers

The config service uses these helper modules internally:
  • video-compatibility.ts - Video codec and preset validation
  • media.ts - Audio codec validation
  • media-rules.ts - Container capability rules

Build docs developers (and LLMs) love