// Oak Video Editor - Non-Linear Video Editor // Copyright (C) 2026 Oak Team // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . //! `olive::ExportFormat` — the export container-format enum and tables. //! //! Mirrors `src/codec/src/exportformat.h`. As with `ExportCodec`, the enum //! values are the serialized-file contract and must never be reordered. use oak_core::SampleFormat; use crate::exportcodec::Codec; /// `olive::ExportFormat::Format`. Only append (never insert/reorder). #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(i32)] pub enum Format { /// QuickTime DNxHD. DNxHD = 0, /// Matroska. Matroska = 1, /// MPEG-4 video. MPEG4Video = 2, /// OpenEXR. OpenEXR = 3, /// QuickTime. QuickTime = 4, /// PNG. PNG = 5, /// TIFF. TIFF = 6, /// WAV. WAV = 7, /// AIFF. AIFF = 8, /// MP3. MP3 = 9, /// FLAC. FLAC = 10, /// Ogg. Ogg = 11, /// WebM. WebM = 12, /// SubRip. SRT = 13, /// MPEG-4 audio. MPEG4Audio = 14, /// Sentinel count (not a real format). Count = 15, } impl Format { /// Convert an `i32` code to a [`Format`]; `None` outside the valid /// `0..=14` range (incl. `Count`). pub fn from_i32(v: i32) -> Option { match v { 0 => Some(Format::DNxHD), 1 => Some(Format::Matroska), 2 => Some(Format::MPEG4Video), 3 => Some(Format::OpenEXR), 4 => Some(Format::QuickTime), 5 => Some(Format::PNG), 6 => Some(Format::TIFF), 7 => Some(Format::WAV), 8 => Some(Format::AIFF), 9 => Some(Format::MP3), 10 => Some(Format::FLAC), 11 => Some(Format::Ogg), 12 => Some(Format::WebM), 13 => Some(Format::SRT), 14 => Some(Format::MPEG4Audio), _ => None, } } /// Human-readable name for a format. /// /// CPP-PARITY: `src/codec/src/exportformat.cpp` `get_name`; unknown / /// `Count` returns "Unknown". pub fn get_name(f: Format) -> String { match f { Format::DNxHD => "DNxHD".to_string(), Format::Matroska => "Matroska Video".to_string(), Format::MPEG4Video => "MPEG-4 Video".to_string(), Format::MPEG4Audio => "MPEG-4 Audio".to_string(), Format::OpenEXR => "OpenEXR".to_string(), Format::PNG => "PNG".to_string(), Format::TIFF => "TIFF".to_string(), Format::QuickTime => "QuickTime".to_string(), Format::WAV => "Wave Audio".to_string(), Format::AIFF => "AIFF".to_string(), Format::MP3 => "MP3".to_string(), Format::FLAC => "FLAC".to_string(), Format::Ogg => "Ogg".to_string(), Format::WebM => "WebM".to_string(), Format::SRT => "SubRip SRT".to_string(), Format::Count => "Unknown".to_string(), } } /// File extension for a format (e.g. "mp4"). /// /// CPP-PARITY: `src/codec/src/exportformat.cpp` `get_extension`; /// `Count` returns an empty string. pub fn get_extension(f: Format) -> String { match f { Format::DNxHD => "mxf".to_string(), Format::Matroska => "mkv".to_string(), Format::MPEG4Video => "mp4".to_string(), Format::MPEG4Audio => "m4a".to_string(), Format::OpenEXR => "exr".to_string(), Format::PNG => "png".to_string(), Format::TIFF => "tiff".to_string(), Format::QuickTime => "mov".to_string(), Format::WAV => "wav".to_string(), Format::AIFF => "aiff".to_string(), Format::MP3 => "mp3".to_string(), Format::FLAC => "flac".to_string(), Format::Ogg => "ogg".to_string(), Format::WebM => "webm".to_string(), Format::SRT => "srt".to_string(), Format::Count => String::new(), } } /// Codecs that can carry video in this format. /// /// CPP-PARITY: `src/codec/src/exportformat.cpp` `get_video_codecs`. pub fn get_video_codecs(f: Format) -> Vec { match f { Format::DNxHD => vec![Codec::DNxHD], Format::Matroska => vec![Codec::H264, Codec::H264RGB, Codec::H265, Codec::VP9], Format::MPEG4Video => vec![Codec::H264, Codec::H264RGB, Codec::H265], Format::OpenEXR => vec![Codec::OpenEXR], Format::PNG => vec![Codec::PNG], Format::TIFF => vec![Codec::TIFF], Format::QuickTime => vec![ Codec::H264, Codec::H264RGB, Codec::H265, Codec::ProRes, Codec::CineForm, ], Format::WebM => vec![Codec::AV1, Codec::VP9], _ => Vec::new(), } } /// Codecs that can carry audio in this format. /// /// CPP-PARITY: `src/codec/src/exportformat.cpp` `get_audio_codecs`. pub fn get_audio_codecs(f: Format) -> Vec { match f { // Video/audio formats. Format::DNxHD => vec![Codec::PCM], Format::Matroska => vec![ Codec::AAC, Codec::MP2, Codec::MP3, Codec::PCM, Codec::Vorbis, Codec::Opus, Codec::FLAC, ], Format::MPEG4Video | Format::MPEG4Audio => { vec![Codec::AAC, Codec::MP2, Codec::MP3] } Format::QuickTime => vec![Codec::AAC, Codec::MP2, Codec::MP3, Codec::PCM], Format::WebM => vec![ Codec::Opus, Codec::AAC, Codec::MP2, Codec::MP3, Codec::PCM, Codec::Vorbis, ], // Audio-only formats. Format::WAV => vec![Codec::PCM], Format::AIFF => vec![Codec::PCM], Format::MP3 => vec![Codec::MP3], Format::FLAC => vec![Codec::FLAC], Format::Ogg => vec![Codec::Opus, Codec::Vorbis, Codec::PCM], _ => Vec::new(), } } /// Codecs that can carry subtitles in this format. /// /// CPP-PARITY: `src/codec/src/exportformat.cpp` `get_subtitle_codecs`; /// only Matroska and SRT support the SRT subtitle codec. pub fn get_subtitle_codecs(f: Format) -> Vec { match f { Format::Matroska | Format::SRT => vec![Codec::SRT], _ => Vec::new(), } } /// Pixel formats the given codec supports in this format. /// /// The C++ side computes this by instantiating the format's encoder and /// asking the FFmpeg bridge (`fb_encoder_codec_get_pixel_formats`); the /// base `Encoder::get_pixel_formats_for_codec` returns an empty list. /// There is no pure-Rust table, so this returns an empty list (matching /// the C++ base default). CPP-PARITY: `exportformat.cpp:222`. pub fn get_pixel_formats_for_codec(_f: Format, _c: Codec) -> Vec { Vec::new() } /// Sample formats the given codec supports in this format. /// /// CPP-PARITY: `ffmpegencoder.cpp:175`. PCM is pure (signed-16 first so /// the export dialog's default matches FFmpeg's); all other codecs query /// the FFmpeg bridge and return empty here (see /// `get_pixel_formats_for_codec`). pub fn get_sample_formats_for_codec(_f: Format, c: Codec) -> Vec { if c == Codec::PCM { vec![ SampleFormat::S16, SampleFormat::U8, SampleFormat::S32, SampleFormat::S64, SampleFormat::F32, SampleFormat::F64, ] } else { Vec::new() } } } #[cfg(test)] mod tests { use super::*; #[test] fn format_name_mapping() { assert_eq!(Format::get_name(Format::DNxHD), "DNxHD"); assert_eq!(Format::get_name(Format::Matroska), "Matroska Video"); assert_eq!(Format::get_name(Format::MPEG4Video), "MPEG-4 Video"); assert_eq!(Format::get_name(Format::MPEG4Audio), "MPEG-4 Audio"); assert_eq!(Format::get_name(Format::OpenEXR), "OpenEXR"); assert_eq!(Format::get_name(Format::PNG), "PNG"); assert_eq!(Format::get_name(Format::TIFF), "TIFF"); assert_eq!(Format::get_name(Format::QuickTime), "QuickTime"); assert_eq!(Format::get_name(Format::WAV), "Wave Audio"); assert_eq!(Format::get_name(Format::AIFF), "AIFF"); assert_eq!(Format::get_name(Format::MP3), "MP3"); assert_eq!(Format::get_name(Format::FLAC), "FLAC"); assert_eq!(Format::get_name(Format::Ogg), "Ogg"); assert_eq!(Format::get_name(Format::WebM), "WebM"); assert_eq!(Format::get_name(Format::SRT), "SubRip SRT"); assert_eq!(Format::get_name(Format::Count), "Unknown"); } #[test] fn format_extension_mapping() { let cases = [ (Format::DNxHD, "mxf"), (Format::Matroska, "mkv"), (Format::MPEG4Video, "mp4"), (Format::MPEG4Audio, "m4a"), (Format::OpenEXR, "exr"), (Format::PNG, "png"), (Format::TIFF, "tiff"), (Format::QuickTime, "mov"), (Format::WAV, "wav"), (Format::AIFF, "aiff"), (Format::MP3, "mp3"), (Format::FLAC, "flac"), (Format::Ogg, "ogg"), (Format::WebM, "webm"), (Format::SRT, "srt"), ]; for (f, ext) in cases { assert_eq!(Format::get_extension(f), ext, "{:?}", f); } assert_eq!(Format::get_extension(Format::Count), ""); } #[test] fn from_i32_maps_valid_and_rejects_others() { for v in 0..=14 { assert!(Format::from_i32(v).is_some(), "format {v}"); } assert!(Format::from_i32(15).is_none()); // Count assert!(Format::from_i32(-1).is_none()); assert!(Format::from_i32(99).is_none()); } #[test] fn video_codec_tables() { assert_eq!(Format::get_video_codecs(Format::DNxHD), vec![Codec::DNxHD]); assert_eq!( Format::get_video_codecs(Format::Matroska), vec![Codec::H264, Codec::H264RGB, Codec::H265, Codec::VP9] ); assert_eq!( Format::get_video_codecs(Format::MPEG4Video), vec![Codec::H264, Codec::H264RGB, Codec::H265] ); assert_eq!( Format::get_video_codecs(Format::OpenEXR), vec![Codec::OpenEXR] ); assert_eq!(Format::get_video_codecs(Format::PNG), vec![Codec::PNG]); assert_eq!(Format::get_video_codecs(Format::TIFF), vec![Codec::TIFF]); assert_eq!( Format::get_video_codecs(Format::QuickTime), vec![ Codec::H264, Codec::H264RGB, Codec::H265, Codec::ProRes, Codec::CineForm ] ); assert_eq!( Format::get_video_codecs(Format::WebM), vec![Codec::AV1, Codec::VP9] ); // Formats without video codecs. assert!(Format::get_video_codecs(Format::WAV).is_empty()); assert!(Format::get_video_codecs(Format::MP3).is_empty()); assert!(Format::get_video_codecs(Format::SRT).is_empty()); } #[test] fn audio_codec_tables() { assert_eq!(Format::get_audio_codecs(Format::WAV), vec![Codec::PCM]); assert_eq!(Format::get_audio_codecs(Format::AIFF), vec![Codec::PCM]); assert_eq!(Format::get_audio_codecs(Format::MP3), vec![Codec::MP3]); assert_eq!(Format::get_audio_codecs(Format::FLAC), vec![Codec::FLAC]); assert_eq!( Format::get_audio_codecs(Format::Ogg), vec![Codec::Opus, Codec::Vorbis, Codec::PCM] ); assert_eq!(Format::get_audio_codecs(Format::DNxHD), vec![Codec::PCM]); assert!(Format::get_audio_codecs(Format::PNG).is_empty()); assert!(Format::get_audio_codecs(Format::SRT).is_empty()); } #[test] fn subtitle_and_codec_capability_tables() { assert_eq!( Format::get_subtitle_codecs(Format::Matroska), vec![Codec::SRT] ); assert_eq!(Format::get_subtitle_codecs(Format::SRT), vec![Codec::SRT]); assert!(Format::get_subtitle_codecs(Format::MPEG4Video).is_empty()); // Pixel-format query is a bridge-dependent default (empty list). assert!(Format::get_pixel_formats_for_codec(Format::MPEG4Video, Codec::H264).is_empty()); // PCM exposes its native sample formats; other codecs query the // FFmpeg bridge and get an empty list here. assert_eq!( Format::get_sample_formats_for_codec(Format::WAV, Codec::PCM), vec![ SampleFormat::S16, SampleFormat::U8, SampleFormat::S32, SampleFormat::S64, SampleFormat::F32, SampleFormat::F64, ] ); assert!(Format::get_sample_formats_for_codec(Format::MPEG4Video, Codec::AAC).is_empty()); } }