filmcraft/crates/export/src/presets.rs

161 lines
8.1 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Built-in export presets. These are FilmCraft's own functional definitions (names, sizes and
//! bitrates chosen by us for common delivery targets); no preset files from any other product are
//! used. User presets live in the engine (`<data dir>/export-presets.json`).
use filmcraft_time::FrameRate;
use serde::{Deserialize, Serialize};
use crate::settings::{AudioCodec, AudioSettings, BitrateMode, MxfVideoCodec, Scaling};
use crate::{ExportSettings, Format};
/// A named set of export settings (`path` and `range` are left empty).
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ExportPreset {
pub name: String,
/// Browser group: "Match Source", "Web & Social", "Broadcast", "Image Sequence", "Animated
/// GIF", "Audio Only" (user presets default to "User").
#[serde(default)]
pub category: String,
#[serde(default)]
pub description: String,
pub settings: ExportSettings,
/// Built in (not saved, cannot be deleted).
#[serde(skip)]
pub builtin: bool,
}
/// Name comparison key: case- and punctuation-insensitive (so `Match Source - Adaptive High
/// Bitrate` finds `Match Source – Adaptive High Bitrate`).
pub fn preset_key(name: &str) -> String {
name.chars().filter(|c| c.is_alphanumeric()).flat_map(char::to_lowercase).collect()
}
/// The default preset (Quick Export, a fresh Export mode).
pub const DEFAULT_PRESET: &str = "Match Source – Adaptive High Bitrate";
fn h264(size: Option<(u32, u32)>, kbps: u32, max_kbps: u32) -> ExportSettings {
ExportSettings {
format: Format::H264,
frame_size: size,
bitrate_kbps: kbps,
max_bitrate_kbps: Some(max_kbps),
bitrate_mode: BitrateMode::Vbr1Pass,
audio: AudioSettings { codec: AudioCodec::Aac, sample_rate: Some(48_000), channels: 2, bitrate_kbps: 320, bits: 16 },
..Default::default()
}
}
fn adaptive(bpp: f32) -> ExportSettings {
ExportSettings { adaptive_bitrate: Some(bpp), max_bitrate_kbps: None, ..h264(None, 10_000, 0) }
}
fn mov(format: Format, profile: &str) -> ExportSettings {
let mut s = ExportSettings {
format,
audio: AudioSettings { codec: AudioCodec::Pcm, sample_rate: Some(48_000), channels: 2, bitrate_kbps: 320, bits: 24 },
render_at_max_depth: true,
..Default::default()
};
match format {
Format::ProRes => s.prores_profile = profile.into(),
_ => s.dnx_profile = profile.into(),
}
s
}
fn mxf(format: Format, codec: MxfVideoCodec, profile: &str) -> ExportSettings {
let mut s = ExportSettings { mxf_video_codec: codec, ..mov(format, profile) };
match codec {
MxfVideoCodec::ProRes => s.prores_profile = profile.into(),
MxfVideoCodec::Dnxhr => s.dnx_profile = profile.into(),
MxfVideoCodec::H264 => {
s.bitrate_kbps = 50_000;
s.adaptive_bitrate = Some(0.3);
s.bitrate_mode = BitrateMode::Vbr1Pass;
}
}
s
}
fn audio_only(format: Format, bits: u16) -> ExportSettings {
ExportSettings {
format,
audio: AudioSettings { codec: AudioCodec::Pcm, sample_rate: Some(48_000), channels: 2, bitrate_kbps: 0, bits },
..Default::default()
}
}
fn p(name: &str, category: &str, description: &str, settings: ExportSettings) -> ExportPreset {
ExportPreset { name: name.into(), category: category.into(), description: description.into(), settings, builtin: true }
}
/// Every built-in preset, in browser order.
pub fn builtin_presets() -> Vec<ExportPreset> {
let ms = "Match Source";
let web = "Web & Social";
let bc = "Broadcast";
let vertical = ExportSettings { scaling: Scaling::ScaleToFill, ..h264(Some((1080, 1920)), 12_000, 16_000) };
vec![
p(DEFAULT_PRESET, ms, "H.264 at the sequence's frame size and rate; bitrate adapts to the frame size (0.2 bits per pixel)", adaptive(0.2)),
p("Match Source – Adaptive Medium Bitrate", ms, "H.264 at the sequence's frame size and rate; 0.1 bits per pixel", adaptive(0.1)),
p("Match Source – Adaptive Low Bitrate", ms, "H.264 at the sequence's frame size and rate; 0.05 bits per pixel", adaptive(0.05)),
p("High Quality 1080p HD", web, "H.264 1920×1080, VBR 1 pass, 20 Mbps target, AAC 320 kbps", h264(Some((1920, 1080)), 20_000, 24_000)),
p("High Quality 2160p 4K", web, "H.264 3840×2160, VBR 1 pass, 60 Mbps target, AAC 320 kbps", h264(Some((3840, 2160)), 60_000, 72_000)),
p("YouTube 1080p Full HD", web, "H.264 1920×1080 for video sharing sites, VBR 1 pass, 16 Mbps", h264(Some((1920, 1080)), 16_000, 20_000)),
p("YouTube 2160p 4K Ultra HD", web, "H.264 3840×2160 for video sharing sites, VBR 1 pass, 45 Mbps", h264(Some((3840, 2160)), 45_000, 54_000)),
p(
"Vimeo 1080p Full HD",
web,
"H.264 1920×1080, VBR 2 pass, 18 Mbps",
ExportSettings { bitrate_mode: BitrateMode::Vbr2Pass, ..h264(Some((1920, 1080)), 18_000, 24_000) },
),
p(
"Vimeo 2160p 4K Ultra HD",
web,
"H.264 3840×2160, VBR 2 pass, 50 Mbps",
ExportSettings { bitrate_mode: BitrateMode::Vbr2Pass, ..h264(Some((3840, 2160)), 50_000, 65_000) },
),
p("Social Vertical 1080×1920", web, "H.264 9:16 portrait for short-form social video, scaled to fill, 12 Mbps", vertical),
p("Apple ProRes 422 HQ", bc, "QuickTime, ProRes 422 HQ, 24-bit 48 kHz PCM", mov(Format::ProRes, "hq")),
p("Apple ProRes 422", bc, "QuickTime, ProRes 422, 24-bit 48 kHz PCM", mov(Format::ProRes, "standard")),
p("Apple ProRes 422 LT", bc, "QuickTime, ProRes 422 LT, 24-bit 48 kHz PCM", mov(Format::ProRes, "lt")),
p("Apple ProRes 422 Proxy", bc, "QuickTime, ProRes 422 Proxy, 24-bit 48 kHz PCM", mov(Format::ProRes, "proxy")),
p("Avid DNxHR HQ", bc, "QuickTime, DNxHR HQ (8-bit 4:2:2), 24-bit 48 kHz PCM", mov(Format::DnxHr, "hq")),
p("Avid DNxHR SQ", bc, "QuickTime, DNxHR SQ (8-bit 4:2:2), 24-bit 48 kHz PCM", mov(Format::DnxHr, "sq")),
p("Avid DNxHR LB", bc, "QuickTime, DNxHR LB (8-bit 4:2:2), 24-bit 48 kHz PCM", mov(Format::DnxHr, "lb")),
p("MXF OP1a DNxHR HQ", bc, "MXF OP1a, DNxHR HQ (8-bit 4:2:2), 24-bit 48 kHz PCM, start timecode", mxf(Format::MxfOp1a, MxfVideoCodec::Dnxhr, "hq")),
p("MXF OP1a ProRes 422 HQ", bc, "MXF OP1a, ProRes 422 HQ, 24-bit 48 kHz PCM, start timecode", mxf(Format::MxfOp1a, MxfVideoCodec::ProRes, "hq")),
p("MXF OP1a H.264", bc, "MXF OP1a, H.264 High long GOP (0.3 bits per pixel), 24-bit 48 kHz PCM", mxf(Format::MxfOp1a, MxfVideoCodec::H264, "")),
p(
"MXF OP-Atom DNxHR (Avid)",
bc,
"Avid-style OP-Atom: DNxHR HQ picture file plus one 24-bit 48 kHz PCM file per channel (_A1, _A2…)",
mxf(Format::MxfOpAtom, MxfVideoCodec::Dnxhr, "hq"),
),
p(
"PNG Sequence",
"Image Sequence",
"Numbered PNG stills at the sequence's frame size",
ExportSettings { format: Format::PngSequence, ..Default::default() },
),
p("TIFF Sequence", "Image Sequence", "Numbered TIFF stills (RGB, 8-bit)", ExportSettings { format: Format::TiffSequence, ..Default::default() }),
p("BMP Sequence", "Image Sequence", "Numbered BMP stills (24-bit)", ExportSettings { format: Format::BmpSequence, ..Default::default() }),
p(
"Animated GIF 640×360",
"Animated GIF",
"Looping GIF, 640×360 at 15 fps",
ExportSettings { format: Format::Gif, frame_size: Some((640, 360)), frame_rate: Some(FrameRate::new(15, 1)), ..Default::default() },
),
p("Waveform Audio 48 kHz 16-bit", "Audio Only", "WAV, stereo 48 kHz 16-bit PCM", audio_only(Format::Wav, 16)),
p("Waveform Audio 48 kHz 24-bit", "Audio Only", "WAV, stereo 48 kHz 24-bit PCM", audio_only(Format::Wav, 24)),
p("AIFF 48 kHz 16-bit", "Audio Only", "AIFF, stereo 48 kHz 16-bit PCM", audio_only(Format::Aiff, 16)),
]
}
/// A built-in preset by name ([`preset_key`] comparison).
pub fn find_builtin(name: &str) -> Option<ExportPreset> {
let k = preset_key(name);
builtin_presets().into_iter().find(|p| preset_key(&p.name) == k)
}