mirror of
https://codeberg.org/simonrepp/hyper8.git
synced 2026-08-14 13:45:27 +02:00
Visualize poster and thumbnail cropping against video on poster image page
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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#"
|
||||
<img {alt}class="thumbnail" src="{src}">
|
||||
<style>
|
||||
:root {{
|
||||
--poster-aspect: {poster_aspect_numeric};
|
||||
--video-aspect: {video_aspect_numeric};
|
||||
}}
|
||||
</style>
|
||||
<div class="poster_preview_outer">
|
||||
<div class="video_frame_outer">
|
||||
<div class="video_frame"></div>
|
||||
<div class="poster_frame">
|
||||
<div class="thumbnail_frame_outer">
|
||||
<div class="thumbnail_frame"></div>
|
||||
</div>
|
||||
<img {alt} src="{src}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>{file_name}</div>
|
||||
<div class="poster_preview_legend">
|
||||
<div>
|
||||
<span class="dot video_frame"></span> {t_video_format} ({video_aspect_representation})
|
||||
</div>
|
||||
<div>
|
||||
<span class="dot thumbnail_frame"></span> {t_thumbnail_format} ({poster_aspect_representation})
|
||||
</div>
|
||||
</div>
|
||||
<div class="button_group">
|
||||
{poster_upload_button}
|
||||
<form action="{delete_poster_action}" data-confirm data-confirm-label="{t_delete_poster}" data-confirm-text="{t_really_delete}" method="post">
|
||||
|
||||
+36
@@ -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<AspectRatio, String> {
|
||||
if let Ok(ratio) = representation.parse::<f32>() {
|
||||
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 {
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user