diff --git a/src/editor/assets/styles.css b/src/editor/assets/styles.css index 93ac879..045dd4b 100644 --- a/src/editor/assets/styles.css +++ b/src/editor/assets/styles.css @@ -372,6 +372,83 @@ p a { .permalink_split { display: flex; } +.poster_preview_outer { + aspect-ratio: 1.78; + background: top left/1rem var(--checkerboard); +} +.poster_preview_outer img { + display: block; + max-height: 100%; + max-width: 100%; +} +.poster_preview_outer .poster_frame { + margin-inline: auto; + max-height: 100%; + max-width: 100%; + position: relative; +} +.poster_preview_outer .thumbnail_frame_outer { + align-items: center; + bottom: 0; + container-type: size; + display: flex; + justify-content: center; + left: 0; + position: absolute; + right: 0; + top: 0; + z-index: 2; +} +.poster_preview_outer .thumbnail_frame { + aspect-ratio: var(--poster-aspect); + background: hsla(202.5, 100%, 49.6%, 0.5); + box-sizing: border-box; + border: 2px dashed #fff; + height: 100%; + width: auto; +} +@container (aspect-ratio < 1) { + .poster_preview_outer .thumbnail_frame { + height: auto; + width: 100%; + } +} +.poster_preview_outer .video_frame_outer { + align-items: center; + aspect-ratio: var(--video-aspect); + display: flex; + justify-content: center; + margin-inline: auto; + max-height: 100%; + max-width: 100%; + position: relative; +} +.poster_preview_outer .video_frame { + bottom: 0; + background: hsla(331.8, 100%, 50%, 0.5); + border: 2px dashed #fff; + box-sizing: border-box; + left: 0; + position: absolute; + right: 0; + top: 0; + z-index: 1; +} +.poster_preview_legend { margin-block: 1rem; } +.poster_preview_legend .dot { + border-radius: 50%; + display: inline-block; + height: 1em; + width: 1em; +} +.poster_preview_legend .dot.thumbnail_frame { + background: hsla(202.5, 100%, 49.6%, 0.5); + border: 2px dashed #fff; +} +.poster_preview_legend .dot.video_frame { + background: hsla(331.8, 100%, 50%, 0.5); + border: 2px dashed #fff; +} pre.error { background: var(--danger); border-radius: .2rem; @@ -562,12 +639,6 @@ svg { fill: currentColor; } font-size: .8rem; text-align: right; } -.thumbnail { - aspect-ratio: 1.78; - background: top left/1rem var(--checkerboard); - object-fit: contain; - width: 100%; -} #toggle-splash svg { pointer-events: none; } .tree_view_options { align-items: center; diff --git a/src/editor/endpoints/video/edit_poster.rs b/src/editor/endpoints/video/edit_poster.rs index 8f5ecab..885a661 100644 --- a/src/editor/endpoints/video/edit_poster.rs +++ b/src/editor/endpoints/video/edit_poster.rs @@ -9,6 +9,7 @@ use actix_web::web::{Data, Path as ActixPath}; use indoc::formatdoc; use crate::{ + AspectRatio, Context, Language, SitePath, @@ -84,9 +85,43 @@ pub fn edit_poster_page( let delete_poster_action = routes::video_delete_poster(video); let update_poster_action = routes::video_update_poster(video); + let poster_aspect_numeric = site.poster_aspect.numeric(); + let poster_aspect_representation = site.poster_aspect.representation(); + let video_aspect_numeric = video.recommended_version() + .and_then(|video_file| video_file.video_meta.as_ref()) + .map(|video_meta| video_meta.aspect_ratio()) + .unwrap_or(poster_aspect_numeric); + let video_aspect_representation = AspectRatio::approximate_representation(video_aspect_numeric); + + let t_thumbnail_format = translations.thumbnail_format; + let t_video_format = translations.video_format; formatdoc!(r#" - + +
+
+
+
+
+
+
+ +
+
+
{file_name}
+
+
+ {t_video_format} ({video_aspect_representation}) +
+
+ {t_thumbnail_format} ({poster_aspect_representation}) +
+
{poster_upload_button}
diff --git a/src/site.rs b/src/site.rs index 9eb3bad..e6443aa 100644 --- a/src/site.rs +++ b/src/site.rs @@ -78,6 +78,35 @@ pub enum SiteContent { } 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 { if let Ok(ratio) = representation.parse::() { let aspect_ratio = AspectRatio::Custom { @@ -110,6 +139,13 @@ impl AspectRatio { AspectRatio::Custom { value, .. } => *value } } + + pub fn representation(&self) -> &str { + match self { + AspectRatio::Default => "16:9", + AspectRatio::Custom { representation, .. } => representation + } + } } impl Site { diff --git a/src/translations.rs b/src/translations.rs index 2121f9d..2223712 100644 --- a/src/translations.rs +++ b/src/translations.rs @@ -231,6 +231,7 @@ pub struct Translations { pub theme: &'static str, pub this_video_can_be_seamlessly_converted_to_a_video_in_a_collection: &'static str, pub this_video_can_be_seamlessly_converted_to_a_video_in_a_playlist: &'static str, + pub thumbnail_format: &'static str, pub title: &'static str, pub title_ascending: &'static str, pub title_descending: &'static str, @@ -259,6 +260,7 @@ pub struct Translations { pub version: &'static str, pub video: &'static str, pub video_files: &'static str, + pub video_format: &'static str, pub video_manifest: &'static str, pub video_not_yet_available: &'static str, pub video_order: &'static str, @@ -513,6 +515,7 @@ impl Translations { theme: "Theme", this_video_can_be_seamlessly_converted_to_a_video_in_a_collection: "Dieses Video kann problemlos zu einem Video in einer Collection konvertiert werden.", this_video_can_be_seamlessly_converted_to_a_video_in_a_playlist: "Dieses Video kann problemlos zu einem Video in einer Playlist konvertiert werden.", + thumbnail_format: "Vorschauformat", title: "Titel", title_ascending: "Titel Aufsteigend (A, B, C)", title_descending: "Titel Absteigend (Z, Y, X)", @@ -541,6 +544,7 @@ impl Translations { version: "Version", video: "Video", video_files: "Videodateien", + video_format: "Videoformat", video_manifest: "Video Manifest", video_not_yet_available: "Video noch nicht verfügbar.", video_order: "Video Reihenfolge", @@ -796,6 +800,7 @@ impl Translations { theme: "Theme", this_video_can_be_seamlessly_converted_to_a_video_in_a_collection: "This video can be seamlessly converted to a video in a collection.", this_video_can_be_seamlessly_converted_to_a_video_in_a_playlist: "This video can be seamlessly converted to a video in a playlist.", + thumbnail_format: "Thumbnail format", title: "Title", title_ascending: "Title Ascending (A, B, C)", title_descending: "Title Descending (Z, Y, X)", @@ -824,6 +829,7 @@ impl Translations { version: "Version", video: "Video", video_files: "Video files", + video_format: "Video format", video_manifest: "Video manifest", video_not_yet_available: "Video not yet available.", video_order: "Video Order",