const MOV_SUFFIXES: &[&str] = &[".mov", ".MOV", ".Mp4", ".MP4"];
const ALL_VIDEO_SUFFIXES: &[&str] = &[
".mov", ".MOV", ".mp4", ".MP4", ".Mp4", ".webm", ".WEBM",
];
const IMAGE_SUFFIXES: &[&str] = &[
".jpg", ".JPG", ".jpeg", ".JPEG",
".png", ".PNG",
".webp", ".WEBP",
];
pub fn to_mp4(source: &str) -> String {
for suffix in MOV_SUFFIXES {
if let Some(stem) = source.strip_suffix(suffix) {
return format!("{stem}.mp4");
}
}
source.to_string()
}
pub fn to_thumb(source: &str) -> String {
for suffix in ALL_VIDEO_SUFFIXES {
if let Some(stem) = source.strip_suffix(suffix) {
return format!("{stem}.thumb.jpg");
}
}
format!("{source}.thumb.jpg")
}
pub fn to_thumb_if_video(source: &str) -> Option<String> {
for suffix in ALL_VIDEO_SUFFIXES {
if let Some(stem) = source.strip_suffix(suffix) {
return Some(format!("{stem}.thumb.jpg"));
}
}
None
}
pub fn to_webp(source: &str) -> String {
for suffix in IMAGE_SUFFIXES {
if let Some(stem) = source.strip_suffix(suffix) {
return format!("{stem}.webp");
}
}
format!("{source}.webp")
}
pub fn is_ladder_source_ext(ext: &str) -> bool {
matches!(ext.to_ascii_lowercase().as_str(), "png" | "jpg" | "jpeg" | "webp")
}
pub fn is_webp_source_ext(ext: &str) -> bool {
ext.eq_ignore_ascii_case("webp")
}
pub const DEPLOY_MAX_EDGE: u32 = 2400;
pub const LADDER: [u32; 2] = [800, 1600];
pub fn deployed_width(natural_w: u32, natural_h: u32) -> u32 {
let long_edge = natural_w.max(natural_h);
if long_edge <= DEPLOY_MAX_EDGE {
return natural_w;
}
((natural_w as u64 * DEPLOY_MAX_EDGE as u64 / long_edge as u64) as u32).max(1)
}
pub fn ladder_rungs(natural_w: u32, natural_h: u32, is_animated: bool) -> &'static [u32] {
if is_animated {
return &[];
}
let base = deployed_width(natural_w, natural_h);
let n = LADDER.iter().take_while(|&&w| w < base).count();
&LADDER[..n]
}
pub fn to_webp_rung(source: &str, width: u32) -> String {
let webp = to_webp(source);
let stem = webp.strip_suffix(".webp").unwrap_or(&webp);
format!("{stem}.w{width}.webp")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_to_mp4_mov() {
assert_eq!(to_mp4("clip.mov"), "clip.mp4");
}
#[test]
fn test_to_mp4_mov_uppercase() {
assert_eq!(to_mp4("clip.MOV"), "clip.mp4");
}
#[test]
fn test_to_mp4_mp4_mixed_case() {
assert_eq!(to_mp4("clip.Mp4"), "clip.mp4");
}
#[test]
fn test_to_mp4_mp4_uppercase() {
assert_eq!(to_mp4("clip.MP4"), "clip.mp4");
}
#[test]
fn test_to_mp4_already_mp4() {
assert_eq!(to_mp4("clip.mp4"), "clip.mp4");
}
#[test]
fn test_to_mp4_webm_passthrough() {
assert_eq!(to_mp4("clip.webm"), "clip.webm");
}
#[test]
fn test_to_mp4_with_relative_prefix() {
assert_eq!(to_mp4("../clip.mov"), "../clip.mp4");
}
#[test]
fn test_to_mp4_with_dot_prefix() {
assert_eq!(to_mp4("./clip.MOV"), "./clip.mp4");
}
#[test]
fn test_to_mp4_with_directory() {
assert_eq!(to_mp4("videos/clip.mov"), "videos/clip.mp4");
}
#[test]
fn test_to_mp4_unicode_filename() {
assert_eq!(to_mp4("videos/冬日之歌.mov"), "videos/冬日之歌.mp4");
}
#[test]
fn test_to_mp4_space_in_name() {
assert_eq!(to_mp4("./Morning Mist.mov"), "./Morning Mist.mp4");
}
#[test]
fn test_to_thumb_mov() {
assert_eq!(to_thumb("clip.mov"), "clip.thumb.jpg");
}
#[test]
fn test_to_thumb_mp4() {
assert_eq!(to_thumb("clip.mp4"), "clip.thumb.jpg");
}
#[test]
fn test_to_thumb_mov_uppercase() {
assert_eq!(to_thumb("clip.MOV"), "clip.thumb.jpg");
}
#[test]
fn test_to_thumb_mp4_uppercase() {
assert_eq!(to_thumb("clip.MP4"), "clip.thumb.jpg");
}
#[test]
fn test_to_thumb_mp4_mixed() {
assert_eq!(to_thumb("clip.Mp4"), "clip.thumb.jpg");
}
#[test]
fn test_to_thumb_webm() {
assert_eq!(to_thumb("clip.webm"), "clip.thumb.jpg");
}
#[test]
fn test_to_thumb_with_directory() {
assert_eq!(to_thumb("videos/clip.mov"), "videos/clip.thumb.jpg");
}
#[test]
fn test_to_thumb_with_relative_prefix() {
assert_eq!(to_thumb("../clip.mp4"), "../clip.thumb.jpg");
}
#[test]
fn test_to_thumb_unicode() {
assert_eq!(to_thumb("videos/冬日之歌.mov"), "videos/冬日之歌.thumb.jpg");
}
#[test]
fn test_to_thumb_space() {
assert_eq!(to_thumb("./Morning Mist.MOV"), "./Morning Mist.thumb.jpg");
}
#[test]
fn test_to_thumb_if_video_recognizes_lowercase_extensions() {
assert_eq!(to_thumb_if_video("clip.mov").as_deref(), Some("clip.thumb.jpg"));
assert_eq!(to_thumb_if_video("clip.mp4").as_deref(), Some("clip.thumb.jpg"));
assert_eq!(to_thumb_if_video("clip.webm").as_deref(), Some("clip.thumb.jpg"));
}
#[test]
fn test_to_thumb_if_video_recognizes_uppercase_extensions() {
assert_eq!(to_thumb_if_video("clip.MOV").as_deref(), Some("clip.thumb.jpg"));
assert_eq!(to_thumb_if_video("clip.MP4").as_deref(), Some("clip.thumb.jpg"));
assert_eq!(to_thumb_if_video("clip.Mp4").as_deref(), Some("clip.thumb.jpg"));
assert_eq!(to_thumb_if_video("clip.WEBM").as_deref(), Some("clip.thumb.jpg"));
}
#[test]
fn test_to_thumb_if_video_with_directory() {
assert_eq!(to_thumb_if_video("音乐/cover.mp4").as_deref(), Some("音乐/cover.thumb.jpg"));
assert_eq!(to_thumb_if_video("../clip.MOV").as_deref(), Some("../clip.thumb.jpg"));
}
#[test]
fn test_to_thumb_if_video_returns_none_for_images() {
assert_eq!(to_thumb_if_video("photo.jpg"), None);
assert_eq!(to_thumb_if_video("photo.png"), None);
assert_eq!(to_thumb_if_video("photo.webp"), None);
assert_eq!(to_thumb_if_video("photo.JPEG"), None);
}
#[test]
fn test_to_thumb_if_video_returns_none_for_unknown() {
assert_eq!(to_thumb_if_video("file.gif"), None);
assert_eq!(to_thumb_if_video("file"), None);
assert_eq!(to_thumb_if_video(""), None);
}
#[test]
fn test_to_webp_jpg() {
assert_eq!(to_webp("photo.jpg"), "photo.webp");
}
#[test]
fn test_to_webp_jpg_uppercase() {
assert_eq!(to_webp("photo.JPG"), "photo.webp");
}
#[test]
fn test_to_webp_jpeg() {
assert_eq!(to_webp("photo.jpeg"), "photo.webp");
}
#[test]
fn test_to_webp_jpeg_uppercase() {
assert_eq!(to_webp("photo.JPEG"), "photo.webp");
}
#[test]
fn test_to_webp_png() {
assert_eq!(to_webp("photo.png"), "photo.webp");
}
#[test]
fn test_to_webp_png_uppercase() {
assert_eq!(to_webp("photo.PNG"), "photo.webp");
}
#[test]
fn test_to_webp_already_webp() {
assert_eq!(to_webp("photo.webp"), "photo.webp");
}
#[test]
fn test_to_webp_webp_uppercase() {
assert_eq!(to_webp("photo.WEBP"), "photo.webp");
}
#[test]
fn test_to_webp_with_directory() {
assert_eq!(to_webp("images/photo.jpg"), "images/photo.webp");
}
#[test]
fn test_to_webp_with_relative_prefix() {
assert_eq!(to_webp("../photo.png"), "../photo.webp");
}
#[test]
fn test_to_webp_with_dot_prefix() {
assert_eq!(to_webp("./photo.JPG"), "./photo.webp");
}
#[test]
fn test_to_webp_unicode_filename() {
assert_eq!(to_webp("images/冬日之歌.jpg"), "images/冬日之歌.webp");
}
#[test]
fn test_to_webp_space_in_name() {
assert_eq!(to_webp("./Morning Mist.png"), "./Morning Mist.webp");
}
#[test]
fn test_to_webp_unknown_extension_fallback() {
assert_eq!(to_webp("file.gif"), "file.gif.webp");
}
#[test]
fn is_ladder_source_ext_accepts_png_jpg_jpeg_webp_case_insensitively() {
for ext in [
"png", "PNG", "jpg", "JPG", "jpeg", "JPEG", "Jpg", "jPeG", "webp", "WEBP", "WebP",
] {
assert!(is_ladder_source_ext(ext), "{ext} must be a ladder source");
}
}
#[test]
fn is_ladder_source_ext_rejects_non_ladder_formats() {
for ext in ["gif", "svg", "avif", "heic", "bmp", "tiff", ""] {
assert!(!is_ladder_source_ext(ext), "{ext} must NOT be a ladder source");
}
}
#[test]
fn is_webp_source_ext_matches_only_webp() {
for ext in ["webp", "WEBP", "WebP"] {
assert!(is_webp_source_ext(ext), "{ext} must be a webp source");
}
for ext in ["png", "PNG", "jpg", "jpeg", "gif", "svg", ""] {
assert!(!is_webp_source_ext(ext), "{ext} must NOT be a webp source");
}
}
#[test]
fn ladder_is_strictly_ascending() {
assert!(LADDER.windows(2).all(|w| w[0] < w[1]));
}
#[test]
fn ladder_rungs_below_deployed_base_only() {
assert_eq!(ladder_rungs(2000, 1200, false), &[800, 1600][..]);
assert_eq!(ladder_rungs(1601, 900, false), &[800, 1600][..]);
assert_eq!(ladder_rungs(1600, 900, false), &[800][..]);
assert_eq!(ladder_rungs(801, 600, false), &[800][..]);
assert_eq!(ladder_rungs(800, 600, false), &[] as &[u32]);
assert_eq!(ladder_rungs(0, 0, false), &[] as &[u32]);
}
#[test]
fn ladder_rungs_capped_width_never_duplicates_base() {
assert_eq!(ladder_rungs(4000, 3000, false), &[800, 1600][..]);
assert_eq!(ladder_rungs(2400, 1600, false), &[800, 1600][..]);
assert_eq!(ladder_rungs(2400, 2400, false), &[800, 1600][..]);
}
#[test]
fn ladder_rungs_portrait_uses_post_resize_width() {
assert_eq!(ladder_rungs(3024, 4032, false), &[800, 1600][..]);
assert_eq!(ladder_rungs(1179, 8000, false), &[] as &[u32]);
assert_eq!(ladder_rungs(1200, 3600, false), &[] as &[u32]);
}
#[test]
fn ladder_rungs_animated_is_empty() {
assert_eq!(ladder_rungs(2000, 1200, true), &[] as &[u32]);
}
#[test]
fn deployed_width_caps_longest_edge() {
assert_eq!(deployed_width(4000, 3000), 2400);
assert_eq!(deployed_width(2000, 1200), 2000);
assert_eq!(deployed_width(3024, 4032), 1800);
assert_eq!(deployed_width(1179, 8000), 353);
assert_eq!(deployed_width(1200, 3600), 800);
assert_eq!(deployed_width(2400, 2400), 2400);
assert_eq!(deployed_width(1, 100_000), 1);
}
#[test]
fn to_webp_rung_inserts_width_suffix() {
assert_eq!(to_webp_rung("photo.jpg", 800), "photo.w800.webp");
assert_eq!(to_webp_rung("a/b/photo.PNG", 1600), "a/b/photo.w1600.webp");
assert_eq!(to_webp_rung("photo.webp", 800), "photo.w800.webp");
}
#[test]
fn to_webp_rung_unknown_extension_fallback() {
assert_eq!(to_webp_rung("file.gif", 800), "file.gif.w800.webp");
}
}