lesavka/client/src/launcher/state/profile_helpers.rs

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()
}
}