Extract aspect ratio abstraction into own module

This commit is contained in:
Simon Repp
2025-07-21 10:09:56 +02:00
parent 53f974579b
commit c8f2e0583c
4 changed files with 98 additions and 83 deletions
+92
View File
@@ -0,0 +1,92 @@
// SPDX-FileCopyrightText: 2025 Simon Repp
// SPDX-License-Identifier: AGPL-3.0-or-later
const SPECIAL_ASPECT_RATIOS: [(f32, &str); 10] = [
(2.37, "64:27"),
(1.78, "16:9"),
(1.66, "5:3"),
(1.5, "3:2"),
(1.33, "4:3"),
(0.75, "3:4"),
(0.67, "2:3"),
(0.6, "3:5"),
(0.56, "9:16"),
(0.42, "27:64")
];
#[derive(Clone, Debug)]
pub enum AspectRatio {
/// Representation stores the user-supplied input (e.g. 16:9) while value stores
/// the numeric result of dividing width parts by height parts (e.g. 1.78).
Custom {
representation: String,
value: f32
},
// 16:9
Default
}
impl AspectRatio {
/// For some common, handpicked aspect ratios we try to automatically display
/// them to the user (e.g. 16:9 instead of 1.7777778 or such).
pub fn approximate_representation(numeric_ratio: f32) -> String {
// Could be adjusted up/down if we feel our approximations are too
// strictly/loosely determined.
const APPROXIMATION_TOLERANCE: f32 = 0.01;
for ratio in SPECIAL_ASPECT_RATIOS {
if (ratio.0 - numeric_ratio).abs() <= APPROXIMATION_TOLERANCE {
return ratio.1.to_string();
}
}
format!("{numeric_ratio:.2}")
}
/// Parse a representation such as '1.78', '16:9' or '16/9' into an
/// [AspectRatio] struct or return an error if the format is
/// unsupported.
pub fn from_representation(representation: &str) -> Result<AspectRatio, String> {
if let Ok(ratio) = representation.parse::<f32>() {
let aspect_ratio = AspectRatio::Custom {
representation: representation.to_owned(),
value: ratio
};
return Ok(aspect_ratio);
}
let mut parts = representation.split(|c| c == '/' || c == ':');
if let (Some(dividend), Some(divisor), None) = (parts.next(), parts.next(), parts.next()) {
if let (Ok(dividend), Ok(divisor)) = (dividend.parse::<f32>(), divisor.parse::<f32>()) {
let aspect_ratio = AspectRatio::Custom {
representation: representation.to_owned(),
value: dividend / divisor
};
return Ok(aspect_ratio);
}
}
let error = format!("'{representation}' is not understood, allowed aspect formats are e.g. '1.78', '16:9' or '16/9')");
Err(error)
}
/// Return the raw aspect ratio number ("width parts divided by height parts").
pub fn numeric(&self) -> f32 {
match self {
AspectRatio::Default => 16.0/9.0,
AspectRatio::Custom { value, .. } => *value
}
}
/// Return the aspect ratio as it was supplied by the user (i.e. in a
/// usually more meaningful/understandable representation such as 16:9
/// instead of 1.78).
pub fn representation(&self) -> &str {
match self {
AspectRatio::Default => "16:9",
AspectRatio::Custom { representation, .. } => representation
}
}
}
+2 -2
View File
@@ -1050,8 +1050,8 @@ impl Collection {
subcollections
}
/// Returns all public videos in the collection, i.e. those that are not
/// marked as offline or unlisted.
/// Returns all public videos (= not marked as offline or unlisted)
/// contained directly in the collection.
pub fn public_videos(&self) -> impl Iterator<Item = &Arc<Video>> {
self.videos.iter().filter(|video| video.public_local())
}
+3 -1
View File
@@ -7,6 +7,7 @@ use clap::Parser;
use sys_locale::get_locale;
mod args;
mod aspect_ratio;
mod banner;
mod browser_support;
mod build;
@@ -46,6 +47,7 @@ mod watcher;
mod workers;
use args::Args;
use aspect_ratio::AspectRatio;
use banner::Banner;
use cache::{Cache, PosterAssetsCached, PosterSourceCached, VideoMetaCached};
use collection::Collection;
@@ -67,7 +69,7 @@ use poster_assets::{PosterAsset, PosterAssets};
use poster_file::PosterFile;
use poster_meta::PosterMeta;
use processing::{Job, JobKind, Processing, Progress};
use site::{AspectRatio, Site, SiteContent};
use site::{Site, SiteContent};
use site_path::SitePath;
use site_url::SiteUrl;
use subtitles::SubtitleFile;
+1 -80
View File
@@ -8,6 +8,7 @@ use std::sync::Arc;
use indoc::indoc;
use crate::{
AspectRatio,
Context,
Collection,
Font,
@@ -37,15 +38,6 @@ const SITE_OPTIONS: &[&str] = &[
"theme"
];
#[derive(Clone, Debug)]
pub enum AspectRatio {
Custom {
representation: String,
value: f32
},
Default
}
#[derive(Clone, Debug)]
pub struct Site {
pub base_url: Option<SiteUrl>,
@@ -77,77 +69,6 @@ pub enum SiteContent {
Video(Arc<Video>)
}
impl AspectRatio {
/// For some common, handpicked aspect ratios we try to automatically display
/// them to the user (e.g. 16:9 instead of 1.7777778 or such).
pub fn approximate_representation(numeric_ratio: f32) -> String {
const RATIOS: [(f32, &str); 10] = [
(2.37, "64:27"),
(1.78, "16:9"),
(1.66, "5:3"),
(1.5, "3:2"),
(1.33, "4:3"),
(0.75, "3:4"),
(0.67, "2:3"),
(0.6, "3:5"),
(0.56, "9:16"),
(0.42, "27:64")
];
// Could be adjusted up/down if we feel our approximations are too
// strictly/loosely determined.
const TOLERANCE: f32 = 0.01;
for ratio in RATIOS {
if (ratio.0 - numeric_ratio).abs() <= TOLERANCE {
return ratio.1.to_string();
}
}
format!("{numeric_ratio:.2}")
}
pub fn from_representation(representation: &str) -> Result<AspectRatio, String> {
if let Ok(ratio) = representation.parse::<f32>() {
let aspect_ratio = AspectRatio::Custom {
representation: representation.to_owned(),
value: ratio
};
return Ok(aspect_ratio);
}
let mut parts = representation.split(|c| c == '/' || c == ':');
if let (Some(dividend), Some(divisor), None) = (parts.next(), parts.next(), parts.next()) {
if let (Ok(dividend), Ok(divisor)) = (dividend.parse::<f32>(), divisor.parse::<f32>()) {
let aspect_ratio = AspectRatio::Custom {
representation: representation.to_owned(),
value: dividend / divisor
};
return Ok(aspect_ratio);
}
}
let error = format!("'{representation}' is not understood, allowed aspect formats are e.g. '1.78', '16:9' or '16/9')");
Err(error)
}
pub fn numeric(&self) -> f32 {
match self {
AspectRatio::Default => 16.0/9.0,
AspectRatio::Custom { value, .. } => *value
}
}
pub fn representation(&self) -> &str {
match self {
AspectRatio::Default => "16:9",
AspectRatio::Custom { representation, .. } => representation
}
}
}
impl Site {
pub fn absolute_url_dir_unchecked(&self, dir: &str, site_path: &SitePath) -> String {
self.base_url