export media: editable resolution / frame rate / bit rate spin boxes
CI / Build & test (Linux) (push) Canceled after 0s
CI / Build & test (Windows) (push) Canceled after 0s

The three values are SpinBoxes (typed input + stepping): width & height
(0 = use the sequence size), frame rate in fps (0 = sequence rate),
bit rate in bit/s (0 = the codec default). The engine already accepted
arbitrary values, so the dialog now exposes them instead of the closed
preset lists. i18n across the 8 packs.
This commit is contained in:
2026-09-02 09:35:54 +08:00
parent 92813707bc
commit 2c64317a58
9 changed files with 104 additions and 6 deletions
+80 -6
View File
@@ -997,6 +997,10 @@ pub struct ExportDialogContent {
audio_codec: Entity<ComboBox>,
color: Entity<ComboBox>,
range: Entity<ComboBox>,
resolution_w: Entity<SpinBox>,
resolution_h: Entity<SpinBox>,
frame_rate: Entity<SpinBox>,
bitrate: Entity<SpinBox>,
path: Entity<PathField>,
/// (format id, display name, extension) in dropdown order.
formats: Vec<(i32, String, String)>,
@@ -1101,6 +1105,43 @@ impl ExportDialogContent {
});
range.update(cx, |combo, cx| combo.set_selected(Some(0), cx));
// Resolution / frame rate / bitrate SPIN boxes: 0 = keep the
// sequence's / the codec's default (the engine treats 0 that way),
// any typed value is honored (the backend accepts arbitrary
// width/height/rate/bitrate).
let resolution_w = cx.new(|cx| {
SpinBox::new(
9,
SliderModel::new(ValueKind::Integer, 0.0, 16384.0, 4.0, 0.0),
window,
cx,
)
});
let resolution_h = cx.new(|cx| {
SpinBox::new(
12,
SliderModel::new(ValueKind::Integer, 0.0, 16384.0, 4.0, 0.0),
window,
cx,
)
});
let frame_rate = cx.new(|cx| {
SpinBox::new(
10,
SliderModel::new(ValueKind::Float, 0.0, 240.0, 0.1, 0.0),
window,
cx,
)
});
let bitrate = cx.new(|cx| {
SpinBox::new(
11,
SliderModel::new(ValueKind::Integer, 0.0, 500_000_000.0, 1_000_000.0, 0.0),
window,
cx,
)
});
let path = cx.new(|cx| {
let editor = cx.new(|cx| EditableTextState::new(StringStorage::default(), cx));
PathField {
@@ -1115,6 +1156,10 @@ impl ExportDialogContent {
audio_codec,
color,
range,
resolution_w,
resolution_h,
frame_rate,
bitrate,
path,
formats,
active_format,
@@ -1197,9 +1242,10 @@ impl ExportDialogContent {
cx.notify();
}
/// The full export settings (codec/color/bit-depth/range) the engine
/// consumes. The codec combos expose only the container's compatible
/// codes, so the picked ids are always valid for `format`.
/// The full export settings (codec/color/bit-depth/range/size/fps/
/// bitrate) the engine consumes. The codec combos expose only the
/// container's compatible codes, so the picked ids are always valid
/// for `format`.
pub fn settings(&self, cx: &App) -> crate::oakui::engine::ExportSettings {
let video_codec = compatible_video_codecs(self.active_format)
.get(self.video_codec.read(cx).selected().unwrap_or(0))
@@ -1211,13 +1257,18 @@ impl ExportDialogContent {
.unwrap_or(12);
let hdr = self.color.read(cx).selected() == Some(1);
let in_out = self.range.read(cx).selected() == Some(1);
let w = self.resolution_w.read(cx).value().to_f64().round() as i32;
let h = self.resolution_h.read(cx).value().to_f64().round() as i32;
let size = if w > 0 && h > 0 { (w, h) } else { (0, 0) };
let frame_rate = self.frame_rate.read(cx).value().to_f64();
let video_bitrate = self.bitrate.read(cx).value().to_f64().round() as i64;
crate::oakui::engine::ExportSettings {
format: self.active_format,
video_codec,
audio_codec,
size: (0, 0),
frame_rate: 0.0,
video_bitrate: 0,
size,
frame_rate,
video_bitrate,
audio_bitrate: 0,
bit_depth: if hdr { 10 } else { 8 },
color_primaries: if hdr { 9 } else { 1 },
@@ -1284,6 +1335,29 @@ impl Render for ExportDialogContent {
i18n::tr("export.range").into(),
self.range.clone(),
))
.child(form_row(
&colors,
i18n::tr("export.resolution").into(),
div()
.flex()
.gap_2()
.child(self.resolution_w.clone())
.child(div()
.text_color(colors.disabled)
.text_xs()
.child("×"))
.child(self.resolution_h.clone()),
))
.child(form_row(
&colors,
i18n::tr("export.framerate").into(),
self.frame_rate.clone(),
))
.child(form_row(
&colors,
i18n::tr("export.bitrate").into(),
self.bitrate.clone(),
))
.child(form_row(
&colors,
i18n::tr("export.path").into(),