First working POC
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
//! Unit conversion and formatting utilities
|
||||
//!
|
||||
//! This module provides centralized conversion functions for:
|
||||
//! - Jellyfin tick-to-seconds conversions
|
||||
//! - Volume normalization (0-1 vs 0-100 ranges)
|
||||
//! - Time formatting for UI display
|
||||
//!
|
||||
//! These utilities eliminate magic numbers and duplicate conversion logic
|
||||
//! across the codebase.
|
||||
|
||||
/// Number of Jellyfin ticks per second (10 million)
|
||||
///
|
||||
/// Jellyfin uses "ticks" for time values where 10,000,000 ticks = 1 second.
|
||||
/// This follows the .NET TimeSpan.Ticks convention.
|
||||
pub const TICKS_PER_SECOND: i64 = 10_000_000;
|
||||
|
||||
/// Convert seconds to Jellyfin ticks
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `seconds` - Time in seconds (e.g., 90.5 for 1 minute 30.5 seconds)
|
||||
///
|
||||
/// # Returns
|
||||
/// Time in Jellyfin ticks (will be rounded down to nearest tick)
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// let ticks = seconds_to_ticks(1.5); // 15,000,000 ticks
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn seconds_to_ticks(seconds: f64) -> i64 {
|
||||
(seconds * TICKS_PER_SECOND as f64) as i64
|
||||
}
|
||||
|
||||
/// Convert Jellyfin ticks to seconds
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `ticks` - Time in Jellyfin ticks
|
||||
///
|
||||
/// # Returns
|
||||
/// Time in seconds as floating point
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// let seconds = ticks_to_seconds(15_000_000); // 1.5 seconds
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn ticks_to_seconds(ticks: i64) -> f64 {
|
||||
ticks as f64 / TICKS_PER_SECOND as f64
|
||||
}
|
||||
|
||||
/// Convert percentage volume (0-100) to normalized (0.0-1.0)
|
||||
///
|
||||
/// Used when receiving volume from Jellyfin remote sessions or UI controls.
|
||||
/// Values outside the 0-100 range are clamped.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `percent` - Volume as percentage (0 to 100)
|
||||
///
|
||||
/// # Returns
|
||||
/// Normalized volume (0.0 to 1.0)
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// let normalized = percent_to_volume(75.0); // 0.75
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn percent_to_volume(percent: f64) -> f64 {
|
||||
percent.clamp(0.0, 100.0) / 100.0
|
||||
}
|
||||
|
||||
/// Convert normalized volume (0.0-1.0) to percentage (0-100)
|
||||
///
|
||||
/// Used when sending volume to Jellyfin remote sessions, MPV, or ExoPlayer.
|
||||
/// Values outside the 0.0-1.0 range are clamped.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `volume` - Normalized volume (0.0 to 1.0)
|
||||
///
|
||||
/// # Returns
|
||||
/// Volume as percentage (0 to 100), floored to integer value
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// let percent = volume_to_percent(0.75); // 75.0
|
||||
/// ```
|
||||
#[inline]
|
||||
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
|
||||
pub fn volume_to_percent(volume: f64) -> f64 {
|
||||
(volume.clamp(0.0, 1.0) * 100.0).floor()
|
||||
}
|
||||
|
||||
/// Format time in seconds to MM:SS display string
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `seconds` - Time in seconds
|
||||
///
|
||||
/// # Returns
|
||||
/// Formatted string like "3:45" or "12:09"
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// let formatted = format_time(225.0); // "3:45"
|
||||
/// ```
|
||||
pub fn format_time(seconds: f64) -> String {
|
||||
let mins = (seconds / 60.0).floor() as i64;
|
||||
let secs = (seconds % 60.0).floor() as i64;
|
||||
format!("{}:{:02}", mins, secs)
|
||||
}
|
||||
|
||||
/// Format time in seconds to HH:MM:SS or MM:SS display string
|
||||
///
|
||||
/// Automatically chooses format based on duration:
|
||||
/// - Less than 1 hour: Returns MM:SS format
|
||||
/// - 1 hour or more: Returns HH:MM:SS format
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `seconds` - Time in seconds
|
||||
///
|
||||
/// # Returns
|
||||
/// Formatted string like "1:23:45" or "3:45"
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// let short = format_time_long(225.0); // "3:45"
|
||||
/// let long = format_time_long(5025.0); // "1:23:45"
|
||||
/// ```
|
||||
pub fn format_time_long(seconds: f64) -> String {
|
||||
let hours = (seconds / 3600.0).floor() as i64;
|
||||
let mins = ((seconds % 3600.0) / 60.0).floor() as i64;
|
||||
let secs = (seconds % 60.0).floor() as i64;
|
||||
|
||||
if hours > 0 {
|
||||
format!("{}:{:02}:{:02}", hours, mins, secs)
|
||||
} else {
|
||||
format!("{}:{:02}", mins, secs)
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate progress percentage from position and duration
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `position` - Current position in seconds
|
||||
/// * `duration` - Total duration in seconds
|
||||
///
|
||||
/// # Returns
|
||||
/// Progress as percentage (0.0 to 100.0), or 0.0 if duration is invalid
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// let progress = calculate_progress(45.0, 180.0); // 25.0
|
||||
/// ```
|
||||
pub fn calculate_progress(position: f64, duration: f64) -> f64 {
|
||||
if duration <= 0.0 {
|
||||
return 0.0;
|
||||
}
|
||||
((position / duration) * 100.0).clamp(0.0, 100.0)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_tick_conversion() {
|
||||
assert_eq!(seconds_to_ticks(1.0), 10_000_000);
|
||||
assert_eq!(seconds_to_ticks(0.5), 5_000_000);
|
||||
assert_eq!(ticks_to_seconds(10_000_000), 1.0);
|
||||
assert_eq!(ticks_to_seconds(5_000_000), 0.5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_volume_conversion() {
|
||||
assert_eq!(percent_to_volume(100.0), 1.0);
|
||||
assert_eq!(percent_to_volume(50.0), 0.5);
|
||||
assert_eq!(percent_to_volume(0.0), 0.0);
|
||||
assert_eq!(volume_to_percent(1.0), 100.0);
|
||||
assert_eq!(volume_to_percent(0.5), 50.0);
|
||||
assert_eq!(volume_to_percent(0.0), 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_volume_clamping() {
|
||||
assert_eq!(percent_to_volume(150.0), 1.0);
|
||||
assert_eq!(percent_to_volume(-10.0), 0.0);
|
||||
assert_eq!(volume_to_percent(1.5), 100.0);
|
||||
assert_eq!(volume_to_percent(-0.5), 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time_formatting() {
|
||||
assert_eq!(format_time(0.0), "0:00");
|
||||
assert_eq!(format_time(59.0), "0:59");
|
||||
assert_eq!(format_time(60.0), "1:00");
|
||||
assert_eq!(format_time(125.0), "2:05");
|
||||
assert_eq!(format_time(3661.0), "61:01");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time_formatting_long() {
|
||||
assert_eq!(format_time_long(0.0), "0:00");
|
||||
assert_eq!(format_time_long(59.0), "0:59");
|
||||
assert_eq!(format_time_long(3599.0), "59:59");
|
||||
assert_eq!(format_time_long(3600.0), "1:00:00");
|
||||
assert_eq!(format_time_long(3661.0), "1:01:01");
|
||||
assert_eq!(format_time_long(7384.0), "2:03:04");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_progress_calculation() {
|
||||
assert_eq!(calculate_progress(0.0, 100.0), 0.0);
|
||||
assert_eq!(calculate_progress(50.0, 100.0), 50.0);
|
||||
assert_eq!(calculate_progress(100.0, 100.0), 100.0);
|
||||
assert_eq!(calculate_progress(25.0, 0.0), 0.0); // Invalid duration
|
||||
assert_eq!(calculate_progress(150.0, 100.0), 100.0); // Clamped
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod conversions;
|
||||
Reference in New Issue
Block a user