245 lines
7.3 KiB
Rust
245 lines
7.3 KiB
Rust
pub fn normalize_audio_gain_percent(percent: u32) -> u32 {
|
|
percent.min(MAX_AUDIO_GAIN_PERCENT)
|
|
}
|
|
|
|
pub fn format_audio_gain_percent(percent: u32) -> String {
|
|
format!("{}%", normalize_audio_gain_percent(percent))
|
|
}
|
|
|
|
pub fn normalize_mic_gain_percent(percent: u32) -> u32 {
|
|
percent.min(MAX_MIC_GAIN_PERCENT)
|
|
}
|
|
|
|
pub fn format_mic_gain_percent(percent: u32) -> String {
|
|
format!("{}%", normalize_mic_gain_percent(percent))
|
|
}
|
|
|
|
fn breakout_size_choice(
|
|
physical_limit: PreviewSourceSize,
|
|
display_fill: PreviewSourceSize,
|
|
source: PreviewSourceSize,
|
|
preset: BreakoutSizePreset,
|
|
) -> BreakoutSizeChoice {
|
|
let physical_width = physical_limit.width.max(1) as i32;
|
|
let physical_height = physical_limit.height.max(1) as i32;
|
|
let display_width = display_fill.width.max(1) as i32;
|
|
let display_height = display_fill.height.max(1) as i32;
|
|
let (width, height) = match preset {
|
|
BreakoutSizePreset::P360 => {
|
|
fit_standard_dimensions(physical_width, physical_height, 640, 360)
|
|
}
|
|
BreakoutSizePreset::P540 => {
|
|
fit_standard_dimensions(physical_width, physical_height, 960, 540)
|
|
}
|
|
BreakoutSizePreset::P720 => {
|
|
fit_standard_dimensions(physical_width, physical_height, 1280, 720)
|
|
}
|
|
BreakoutSizePreset::P900 => {
|
|
fit_standard_dimensions(physical_width, physical_height, 1600, 900)
|
|
}
|
|
BreakoutSizePreset::P1080 => {
|
|
fit_standard_dimensions(physical_width, physical_height, 1920, 1080)
|
|
}
|
|
BreakoutSizePreset::P1440 => {
|
|
fit_standard_dimensions(physical_width, physical_height, 2560, 1440)
|
|
}
|
|
BreakoutSizePreset::Source => fit_standard_dimensions(
|
|
physical_width,
|
|
physical_height,
|
|
source.width.max(1) as i32,
|
|
source.height.max(1) as i32,
|
|
),
|
|
BreakoutSizePreset::FillDisplay => (display_width, display_height),
|
|
};
|
|
BreakoutSizeChoice {
|
|
preset,
|
|
width,
|
|
height,
|
|
}
|
|
}
|
|
|
|
fn breakout_size_options(
|
|
physical_limit: PreviewSourceSize,
|
|
display_fill: PreviewSourceSize,
|
|
source: PreviewSourceSize,
|
|
) -> Vec<BreakoutSizeChoice> {
|
|
let mut options = Vec::new();
|
|
for preset in [
|
|
BreakoutSizePreset::Source,
|
|
BreakoutSizePreset::P360,
|
|
BreakoutSizePreset::P540,
|
|
BreakoutSizePreset::P720,
|
|
BreakoutSizePreset::P900,
|
|
BreakoutSizePreset::P1080,
|
|
BreakoutSizePreset::P1440,
|
|
BreakoutSizePreset::FillDisplay,
|
|
] {
|
|
let choice = breakout_size_choice(physical_limit, display_fill, source, preset);
|
|
let allow_duplicate_label = matches!(
|
|
preset,
|
|
BreakoutSizePreset::Source | BreakoutSizePreset::FillDisplay
|
|
);
|
|
if !allow_duplicate_label
|
|
&& options.iter().any(|existing: &BreakoutSizeChoice| {
|
|
existing.width == choice.width && existing.height == choice.height
|
|
})
|
|
{
|
|
continue;
|
|
}
|
|
options.push(choice);
|
|
}
|
|
options
|
|
}
|
|
|
|
fn capture_size_choice(
|
|
_source: PreviewSourceSize,
|
|
preset: CaptureSizePreset,
|
|
selected_fps: u32,
|
|
selected_bitrate_kbit: u32,
|
|
) -> CaptureSizeChoice {
|
|
let preset = normalize_capture_size_preset(preset);
|
|
let mode = preset.source_mode();
|
|
let _ = (selected_fps, selected_bitrate_kbit);
|
|
let (width, height, fps, max_bitrate_kbit) = (
|
|
mode.width as i32,
|
|
mode.height as i32,
|
|
mode.fps,
|
|
estimate_source_bitrate_kbit(mode.width as i32, mode.height as i32, mode.fps),
|
|
);
|
|
CaptureSizeChoice {
|
|
preset,
|
|
width,
|
|
height,
|
|
fps,
|
|
max_bitrate_kbit,
|
|
}
|
|
}
|
|
|
|
fn estimate_source_bitrate_kbit(width: i32, height: i32, fps: u32) -> u32 {
|
|
let pixels_per_second = width.max(1) as u64 * height.max(1) as u64 * fps.max(1) as u64;
|
|
match pixels_per_second {
|
|
p if p >= 1920_u64 * 1080 * 50 => 18_000,
|
|
p if p >= 1920_u64 * 1080 * 24 => 12_000,
|
|
p if p >= 1280_u64 * 720 * 24 => 6_000,
|
|
_ => 2_500,
|
|
}
|
|
}
|
|
|
|
fn capture_size_options(source: PreviewSourceSize) -> Vec<CaptureSizeChoice> {
|
|
native_eye_source_modes()
|
|
.iter()
|
|
.copied()
|
|
.filter(|mode| mode.width <= source.width && mode.height <= source.height)
|
|
.map(CaptureSizePreset::from_source_mode)
|
|
.map(|preset| {
|
|
let defaults = default_profile_for_preset(source, preset);
|
|
capture_size_choice(source, preset, defaults.fps, defaults.max_bitrate_kbit)
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn capture_fps_options(source: PreviewSourceSize) -> Vec<CaptureFpsChoice> {
|
|
vec![CaptureFpsChoice {
|
|
fps: source.fps.max(1),
|
|
}]
|
|
}
|
|
|
|
fn capture_bitrate_options(source: PreviewSourceSize) -> Vec<CaptureBitrateChoice> {
|
|
vec![CaptureBitrateChoice {
|
|
max_bitrate_kbit: estimate_source_bitrate_kbit(
|
|
source.width as i32,
|
|
source.height as i32,
|
|
source.fps,
|
|
),
|
|
}]
|
|
}
|
|
|
|
fn default_profile_for_preset(
|
|
_source: PreviewSourceSize,
|
|
preset: CaptureSizePreset,
|
|
) -> CaptureSizeChoice {
|
|
let preset = normalize_capture_size_preset(preset);
|
|
let mode = preset.source_mode();
|
|
let (width, height, fps, max_bitrate_kbit) = (
|
|
mode.width as i32,
|
|
mode.height as i32,
|
|
mode.fps,
|
|
estimate_source_bitrate_kbit(mode.width as i32, mode.height as i32, mode.fps),
|
|
);
|
|
CaptureSizeChoice {
|
|
preset,
|
|
width,
|
|
height,
|
|
fps,
|
|
max_bitrate_kbit,
|
|
}
|
|
}
|
|
|
|
fn normalize_capture_size_preset(preset: CaptureSizePreset) -> CaptureSizePreset {
|
|
match preset {
|
|
CaptureSizePreset::Vga | CaptureSizePreset::P480 | CaptureSizePreset::P576 => {
|
|
CaptureSizePreset::P720
|
|
}
|
|
other => other,
|
|
}
|
|
}
|
|
|
|
fn fit_standard_dimensions(
|
|
limit_width: i32,
|
|
limit_height: i32,
|
|
wanted_width: i32,
|
|
wanted_height: i32,
|
|
) -> (i32, i32) {
|
|
let width = wanted_width.min(limit_width).max(2);
|
|
let height = wanted_height.min(limit_height).max(2);
|
|
if width == limit_width && height == limit_height {
|
|
return (width, height);
|
|
}
|
|
let width_from_height = round_down_even((height * 16) / 9);
|
|
if width_from_height <= limit_width {
|
|
(round_down_even(width_from_height), round_down_even(height))
|
|
} else {
|
|
let height_from_width = round_down_even((width * 9) / 16);
|
|
(round_down_even(width), round_down_even(height_from_width))
|
|
}
|
|
}
|
|
|
|
fn round_down_even(value: i32) -> i32 {
|
|
let rounded = value.max(2);
|
|
rounded - (rounded % 2)
|
|
}
|
|
|
|
fn normalize_selection(value: Option<String>) -> Option<String> {
|
|
value.and_then(|v| {
|
|
let trimmed = v.trim();
|
|
if trimmed.is_empty() || trimmed.eq_ignore_ascii_case("auto") {
|
|
None
|
|
} else {
|
|
Some(trimmed.to_string())
|
|
}
|
|
})
|
|
}
|
|
|
|
fn keep_or_select_first(selection: &mut Option<String>, values: &[String]) {
|
|
if selection.is_none() {
|
|
*selection = values.first().cloned();
|
|
}
|
|
}
|
|
|
|
fn media_status_label(enabled: bool, selected: Option<&str>) -> &str {
|
|
if !enabled {
|
|
"off"
|
|
} else {
|
|
selected.unwrap_or("none")
|
|
}
|
|
}
|
|
|
|
fn normalize_swap_key(value: String) -> String {
|
|
let trimmed = value.trim();
|
|
if trimmed.is_empty() {
|
|
"off".to_string()
|
|
} else {
|
|
trimmed.to_ascii_lowercase()
|
|
}
|
|
}
|