/* This file is part of jellything (https://codeberg.org/metamuffin/jellything) which is licensed under the GNU Affero General Public License (version 3); see /COPYING. Copyright (C) 2025 metamuffin */ use crate::locale::{Language, TrString, tr, trs}; use jellycommon::{Chapter, MediaInfo, NodeKind, SourceTrackKind}; use std::fmt::Write; pub fn format_duration(d: f64) -> String { format_duration_mode(d, false, Language::English) } pub fn format_duration_long(d: f64, lang: Language) -> String { format_duration_mode(d, true, lang) } fn format_duration_mode(mut d: f64, long_units: bool, lang: Language) -> String { let mut s = String::new(); let sign = if d > 0. { "" } else { "-" }; d = d.abs(); for (short, long, long_pl, k) in [ ("d", "time.day", "time.days", 60. * 60. * 24.), ("h", "time.hour", "time.hours", 60. * 60.), ("m", "time.minute", "time.minutes", 60.), ("s", "time.second", "time.seconds", 1.), ] { let h = (d / k).floor(); d -= h * k; if h > 0. { if long_units { let long = tr(lang, if h != 1. { long_pl } else { long }); let and = format!(" {} ", tr(lang, "time.and_join")); // TODO breaks if seconds is zero write!( s, "{}{h} {long}{}", if k != 1. { "" } else { &and }, if k > 60. { ", " } else { "" }, ) .unwrap(); } else { write!(s, "{h}{short} ").unwrap(); } } } format!("{sign}{}", s.trim()) } pub fn format_size(size: u64) -> String { humansize::format_size(size, humansize::DECIMAL) } pub fn format_kind(k: NodeKind, lang: Language) -> TrString<'static> { trs( &lang, match k { NodeKind::Unknown => "kind.unknown", NodeKind::Movie => "kind.movie", NodeKind::Video => "kind.video", NodeKind::Music => "kind.music", NodeKind::ShortFormVideo => "kind.short_form_video", NodeKind::Collection => "kind.collection", NodeKind::Channel => "kind.channel", NodeKind::Show => "kind.show", NodeKind::Series => "kind.series", NodeKind::Season => "kind.season", NodeKind::Episode => "kind.episode", }, ) } pub trait MediaInfoExt { fn resolution_name(&self) -> &'static str; } impl MediaInfoExt for &MediaInfo { fn resolution_name(&self) -> &'static str { let mut maxdim = 0; for t in &self.tracks { if let SourceTrackKind::Video { width, height, .. } = &t.kind { maxdim = maxdim.max(*width.max(height)) } } match maxdim { 30720.. => "32K", 15360.. => "16K", 7680.. => "8K UHD", 5120.. => "5K UHD", 3840.. => "4K UHD", 2560.. => "QHD 1440p", 1920.. => "FHD 1080p", 1280.. => "HD 720p", 854.. => "SD 480p", _ => "Unkown", } } } pub fn format_count(n: impl Into) -> String { let n: usize = n.into(); if n >= 1_000_000 { format!("{:.1}M", n as f32 / 1_000_000.) } else if n >= 1_000 { format!("{:.1}k", n as f32 / 1_000.) } else { format!("{n}") } } pub fn format_chapter(c: &Chapter) -> (String, String) { ( format!( "{}-{}", c.time_start.map(format_duration).unwrap_or_default(), c.time_end.map(format_duration).unwrap_or_default(), ), c.labels.first().map(|l| l.1.clone()).unwrap_or_default(), ) }