Skip to main content

#
jellytau_lib/utils/
conversions.rs

    1//! Unit conversion and formatting utilities
    2//!
    3//! This module provides centralized conversion functions for:
    4//! - Jellyfin tick-to-seconds conversions
    5//! - Volume normalization (0-1 vs 0-100 ranges)
    6//! - Time formatting for UI display
    7//!
    8//! These utilities eliminate magic numbers and duplicate conversion logic
    9//! across the codebase.
    10
    11/// Number of Jellyfin ticks per second (10 million)
    12///
    13/// Jellyfin uses "ticks" for time values where 10,000,000 ticks = 1 second.
    14/// This follows the .NET TimeSpan.Ticks convention.
    15pub const TICKS_PER_SECOND: i64 = 10_000_000;
    16
    17/// Convert seconds to Jellyfin ticks
    18///
    19/// # Arguments
    20/// * `seconds` - Time in seconds (e.g., 90.5 for 1 minute 30.5 seconds)
    21///
    22/// # Returns
    23/// Time in Jellyfin ticks (will be rounded down to nearest tick)
    24///
    25/// # Example
    26/// ```
    27/// # use jellytau_lib::utils::conversions::*;
    28/// let ticks = seconds_to_ticks(1.5); // 15,000,000 ticks
    29/// ```
    30#[inline]
    31pub fn seconds_to_ticks(seconds: f64) -> i64 {
    32    (seconds * TICKS_PER_SECOND as f64) as i64
    33}
    34
    35/// Convert Jellyfin ticks to seconds
    36///
    37/// # Arguments
    38/// * `ticks` - Time in Jellyfin ticks
    39///
    40/// # Returns
    41/// Time in seconds as floating point
    42///
    43/// # Example
    44/// ```
    45/// # use jellytau_lib::utils::conversions::*;
    46/// let seconds = ticks_to_seconds(15_000_000); // 1.5 seconds
    47/// ```
    48#[inline]
    49pub fn ticks_to_seconds(ticks: i64) -> f64 {
    50    ticks as f64 / TICKS_PER_SECOND as f64
    51}
    52
    53/// Convert percentage volume (0-100) to normalized (0.0-1.0)
    54///
    55/// Used when receiving volume from Jellyfin remote sessions or UI controls.
    56/// Values outside the 0-100 range are clamped.
    57///
    58/// # Arguments
    59/// * `percent` - Volume as percentage (0 to 100)
    60///
    61/// # Returns
    62/// Normalized volume (0.0 to 1.0)
    63///
    64/// # Example
    65/// ```
    66/// # use jellytau_lib::utils::conversions::*;
    67/// let normalized = percent_to_volume(75.0); // 0.75
    68/// ```
    69#[inline]
    70pub fn percent_to_volume(percent: f64) -> f64 {
    71    percent.clamp(0.0, 100.0) / 100.0
    72}
    73
    74/// Convert normalized volume (0.0-1.0) to percentage (0-100)
    75///
    76/// Used when sending volume to Jellyfin remote sessions, MPV, or ExoPlayer.
    77/// Values outside the 0.0-1.0 range are clamped.
    78///
    79/// # Arguments
    80/// * `volume` - Normalized volume (0.0 to 1.0)
    81///
    82/// # Returns
    83/// Volume as percentage (0 to 100), floored to integer value
    84///
    85/// # Example
    86/// ```
    87/// # use jellytau_lib::utils::conversions::*;
    88/// let percent = volume_to_percent(0.75); // 75.0
    89/// ```
    90#[inline]
    91#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
    92pub fn volume_to_percent(volume: f64) -> f64 {
    93    (volume.clamp(0.0, 1.0) * 100.0).floor()
    94}
    95
    96/// Format time in seconds to MM:SS display string
    97///
    98/// # Arguments
    99/// * `seconds` - Time in seconds
    100///
    101/// # Returns
    102/// Formatted string like "3:45" or "12:09"
    103///
    104/// # Example
    105/// ```
    106/// # use jellytau_lib::utils::conversions::*;
    107/// let formatted = format_time(225.0); // "3:45"
    108/// ```
    109pub fn format_time(seconds: f64) -> String {
    110    let mins = (seconds / 60.0).floor() as i64;
    111    let secs = (seconds % 60.0).floor() as i64;
    112    format!("{}:{:02}", mins, secs)
    113}
    114
    115/// Format time in seconds to HH:MM:SS or MM:SS display string
    116///
    117/// Automatically chooses format based on duration:
    118/// - Less than 1 hour: Returns MM:SS format
    119/// - 1 hour or more: Returns HH:MM:SS format
    120///
    121/// # Arguments
    122/// * `seconds` - Time in seconds
    123///
    124/// # Returns
    125/// Formatted string like "1:23:45" or "3:45"
    126///
    127/// # Example
    128/// ```
    129/// # use jellytau_lib::utils::conversions::*;
    130/// let short = format_time_long(225.0);  // "3:45"
    131/// let long = format_time_long(5025.0);  // "1:23:45"
    132/// ```
    133pub fn format_time_long(seconds: f64) -> String {
    134    let hours = (seconds / 3600.0).floor() as i64;
    135    let mins = ((seconds % 3600.0) / 60.0).floor() as i64;
    136    let secs = (seconds % 60.0).floor() as i64;
    137
    138    if hours > 0 {
    139        format!("{}:{:02}:{:02}", hours, mins, secs)
    140    } else {
    141        format!("{}:{:02}", mins, secs)
    142    }
    143}
    144
    145/// Calculate progress percentage from position and duration
    146///
    147/// # Arguments
    148/// * `position` - Current position in seconds
    149/// * `duration` - Total duration in seconds
    150///
    151/// # Returns
    152/// Progress as percentage (0.0 to 100.0), or 0.0 if duration is invalid
    153///
    154/// # Example
    155/// ```
    156/// # use jellytau_lib::utils::conversions::*;
    157/// let progress = calculate_progress(45.0, 180.0); // 25.0
    158/// ```
    159pub fn calculate_progress(position: f64, duration: f64) -> f64 {
    160    if duration <= 0.0 {
    161        return 0.0;
    162    }
    163    ((position / duration) * 100.0).clamp(0.0, 100.0)
    164}
    165
    166#[cfg(test)]
    167mod tests {
    168    use super::*;
    169
    170    #[test]
    171    fn test_tick_conversion() {
    172        assert_eq!(seconds_to_ticks(1.0), 10_000_000);
    173        assert_eq!(seconds_to_ticks(0.5), 5_000_000);
    174        assert_eq!(ticks_to_seconds(10_000_000), 1.0);
    175        assert_eq!(ticks_to_seconds(5_000_000), 0.5);
    176    }
    177
    178    #[test]
    179    fn test_volume_conversion() {
    180        assert_eq!(percent_to_volume(100.0), 1.0);
    181        assert_eq!(percent_to_volume(50.0), 0.5);
    182        assert_eq!(percent_to_volume(0.0), 0.0);
    183        assert_eq!(volume_to_percent(1.0), 100.0);
    184        assert_eq!(volume_to_percent(0.5), 50.0);
    185        assert_eq!(volume_to_percent(0.0), 0.0);
    186    }
    187
    188    #[test]
    189    fn test_volume_clamping() {
    190        assert_eq!(percent_to_volume(150.0), 1.0);
    191        assert_eq!(percent_to_volume(-10.0), 0.0);
    192        assert_eq!(volume_to_percent(1.5), 100.0);
    193        assert_eq!(volume_to_percent(-0.5), 0.0);
    194    }
    195
    196    #[test]
    197    fn test_time_formatting() {
    198        assert_eq!(format_time(0.0), "0:00");
    199        assert_eq!(format_time(59.0), "0:59");
    200        assert_eq!(format_time(60.0), "1:00");
    201        assert_eq!(format_time(125.0), "2:05");
    202        assert_eq!(format_time(3661.0), "61:01");
    203    }
    204
    205    #[test]
    206    fn test_time_formatting_long() {
    207        assert_eq!(format_time_long(0.0), "0:00");
    208        assert_eq!(format_time_long(59.0), "0:59");
    209        assert_eq!(format_time_long(3599.0), "59:59");
    210        assert_eq!(format_time_long(3600.0), "1:00:00");
    211        assert_eq!(format_time_long(3661.0), "1:01:01");
    212        assert_eq!(format_time_long(7384.0), "2:03:04");
    213    }
    214
    215    #[test]
    216    fn test_progress_calculation() {
    217        assert_eq!(calculate_progress(0.0, 100.0), 0.0);
    218        assert_eq!(calculate_progress(50.0, 100.0), 50.0);
    219        assert_eq!(calculate_progress(100.0, 100.0), 100.0);
    220        assert_eq!(calculate_progress(25.0, 0.0), 0.0); // Invalid duration
    221        assert_eq!(calculate_progress(150.0, 100.0), 100.0); // Clamped
    222    }
    223}