ytmusic-cli 0.3.1

A terminal YouTube Music player built with Rust, Ratatui, mpv, yt-dlp, and rs-ytmusic-api
use std::path::PathBuf;

use rs_ytmusic_api::{SongDetailed, UpNextDetails};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Track {
    pub video_id: String,
    pub title: String,
    pub artist: String,
    pub duration: Option<u64>,
    pub thumbnail_url: Option<String>,
    #[serde(skip)]
    pub cached_path: Option<PathBuf>,
}

impl Track {
    pub fn url(&self) -> String {
        format!("https://music.youtube.com/watch?v={}", self.video_id)
    }

    pub fn label(&self) -> String {
        format!("{} - {}", self.title, self.artist)
    }
}

impl From<SongDetailed> for Track {
    fn from(song: SongDetailed) -> Self {
        let thumbnail_url = song.thumbnails.last().map(|t| t.url.clone());
        Self {
            video_id: song.video_id,
            title: song.name,
            artist: song.artist.name,
            duration: song.duration,
            thumbnail_url,
            cached_path: None,
        }
    }
}

impl From<UpNextDetails> for Track {
    fn from(item: UpNextDetails) -> Self {
        let thumbnail_url = item.thumbnails.last().map(|t| t.url.clone());
        Self {
            video_id: item.video_id,
            title: item.title,
            artist: item.artists.name,
            duration: item.duration,
            thumbnail_url,
            cached_path: None,
        }
    }
}

pub fn format_duration(duration: Option<u64>) -> String {
    let Some(total) = duration else {
        return "--:--".to_string();
    };
    let minutes = total / 60;
    let seconds = total % 60;
    format!("{minutes}:{seconds:02}")
}