a Jellyfin & Subsonic client for the terminal — powered by mpv, Chromecast and UPnP MediaRenderer
mpv chromecast mpris navidrome jellyfin upnp tui
0

Configure Feed

Select the types of activity you want to include in your feed.

fin / crates / fin-jellyfin / src / models.rs
7.4 kB 262 lines
1use serde::{Deserialize, Serialize}; 2 3#[derive(Debug, Clone, Serialize, Deserialize)] 4#[serde(rename_all = "PascalCase")] 5pub struct AuthResult { 6 pub access_token: String, 7 pub server_id: String, 8 pub user: AuthUser, 9} 10 11#[derive(Debug, Clone, Serialize, Deserialize)] 12#[serde(rename_all = "PascalCase")] 13pub struct AuthUser { 14 pub id: String, 15 pub name: String, 16} 17 18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 19pub enum ItemKind { 20 Audio, 21 MusicAlbum, 22 MusicArtist, 23 Playlist, 24 Movie, 25 Series, 26 Season, 27 Episode, 28 Video, 29 Folder, 30 Other, 31} 32 33impl ItemKind { 34 pub fn parse(s: &str) -> Self { 35 match s { 36 "Audio" => Self::Audio, 37 "MusicAlbum" => Self::MusicAlbum, 38 "MusicArtist" => Self::MusicArtist, 39 "Playlist" => Self::Playlist, 40 "Movie" => Self::Movie, 41 "Series" => Self::Series, 42 "Season" => Self::Season, 43 "Episode" => Self::Episode, 44 "Video" => Self::Video, 45 "Folder" | "CollectionFolder" => Self::Folder, 46 _ => Self::Other, 47 } 48 } 49 50 pub fn is_audio(&self) -> bool { 51 matches!(self, Self::Audio | Self::MusicAlbum | Self::MusicArtist) 52 } 53 54 pub fn is_video(&self) -> bool { 55 matches!( 56 self, 57 Self::Movie | Self::Series | Self::Season | Self::Episode | Self::Video 58 ) 59 } 60 61 pub fn is_playable(&self) -> bool { 62 matches!( 63 self, 64 Self::Audio | Self::Movie | Self::Episode | Self::Video 65 ) 66 } 67 68 pub fn icon(&self) -> &'static str { 69 match self { 70 Self::Audio => "", 71 Self::MusicAlbum => "", 72 Self::MusicArtist => "", 73 Self::Playlist => "", 74 Self::Movie => "", 75 Self::Series => "", 76 Self::Season => "", 77 Self::Episode => "", 78 Self::Video => "", 79 Self::Folder => "", 80 Self::Other => "", 81 } 82 } 83} 84 85#[derive(Debug, Clone, Serialize, Deserialize)] 86#[serde(rename_all = "PascalCase")] 87pub struct BaseItem { 88 pub id: String, 89 pub name: String, 90 #[serde(rename = "Type")] 91 pub type_: String, 92 #[serde(default)] 93 pub album: Option<String>, 94 #[serde(default)] 95 pub album_id: Option<String>, 96 #[serde(default)] 97 pub album_artist: Option<String>, 98 #[serde(default)] 99 pub artists: Option<Vec<String>>, 100 #[serde(default)] 101 pub series_name: Option<String>, 102 #[serde(default)] 103 pub production_year: Option<i32>, 104 #[serde(default)] 105 pub run_time_ticks: Option<i64>, 106 #[serde(default)] 107 pub media_type: Option<String>, 108 #[serde(default)] 109 pub index_number: Option<i32>, 110 #[serde(default)] 111 pub parent_index_number: Option<i32>, 112 #[serde(default)] 113 pub image_tags: Option<serde_json::Value>, 114 #[serde(default)] 115 pub is_folder: Option<bool>, 116 #[serde(default)] 117 pub overview: Option<String>, 118} 119 120impl BaseItem { 121 pub fn kind(&self) -> ItemKind { 122 ItemKind::parse(&self.type_) 123 } 124 125 pub fn duration_secs(&self) -> Option<u64> { 126 self.run_time_ticks.map(|t| (t / 10_000_000) as u64) 127 } 128 129 pub fn subtitle(&self) -> String { 130 match self.kind() { 131 ItemKind::Audio => { 132 let artists = self 133 .artists 134 .as_ref() 135 .and_then(|a| { 136 if a.is_empty() { 137 None 138 } else { 139 Some(a.join(", ")) 140 } 141 }) 142 .or_else(|| self.album_artist.clone()) 143 .unwrap_or_default(); 144 let album = self.album.clone().unwrap_or_default(); 145 match (artists.is_empty(), album.is_empty()) { 146 (true, true) => String::new(), 147 (false, true) => artists, 148 (true, false) => album, 149 (false, false) => format!("{}{}", artists, album), 150 } 151 } 152 ItemKind::MusicAlbum => self 153 .album_artist 154 .clone() 155 .or_else(|| self.artists.as_ref().map(|a| a.join(", "))) 156 .unwrap_or_default(), 157 ItemKind::MusicArtist => String::new(), 158 ItemKind::Movie => self 159 .production_year 160 .map(|y| y.to_string()) 161 .unwrap_or_default(), 162 ItemKind::Episode => match (self.parent_index_number, self.index_number) { 163 (Some(s), Some(e)) => format!( 164 "S{:02}E{:02}{}", 165 s, 166 e, 167 self.series_name.clone().unwrap_or_default() 168 ), 169 _ => self.series_name.clone().unwrap_or_default(), 170 }, 171 ItemKind::Series => self 172 .production_year 173 .map(|y| y.to_string()) 174 .unwrap_or_default(), 175 _ => String::new(), 176 } 177 } 178} 179 180#[derive(Debug, Clone, Serialize, Deserialize)] 181#[serde(rename_all = "PascalCase")] 182pub struct SearchResult { 183 #[serde(default)] 184 pub items: Vec<BaseItem>, 185 #[serde(default)] 186 pub total_record_count: i64, 187} 188 189#[derive(Debug, Clone, Serialize, Deserialize)] 190#[serde(rename_all = "PascalCase")] 191pub struct SearchHintResult { 192 #[serde(default)] 193 pub search_hints: Vec<SearchHint>, 194 #[serde(default)] 195 pub total_record_count: i64, 196} 197 198#[derive(Debug, Clone, Serialize, Deserialize)] 199#[serde(rename_all = "PascalCase")] 200pub struct SearchHint { 201 #[serde(default, alias = "ItemId")] 202 pub id: String, 203 pub name: String, 204 #[serde(rename = "Type", default)] 205 pub type_: String, 206 #[serde(default)] 207 pub album: Option<String>, 208 #[serde(default)] 209 pub album_artist: Option<String>, 210 #[serde(default)] 211 pub artists: Option<Vec<String>>, 212 #[serde(default)] 213 pub production_year: Option<i32>, 214 #[serde(default)] 215 pub run_time_ticks: Option<i64>, 216 #[serde(default)] 217 pub media_type: Option<String>, 218 #[serde(default)] 219 pub series_name: Option<String>, 220} 221 222impl SearchHint { 223 pub fn into_base_item(self) -> BaseItem { 224 BaseItem { 225 id: self.id, 226 name: self.name, 227 type_: self.type_, 228 album: self.album, 229 album_id: None, 230 album_artist: self.album_artist, 231 artists: self.artists, 232 series_name: self.series_name, 233 production_year: self.production_year, 234 run_time_ticks: self.run_time_ticks, 235 media_type: self.media_type, 236 index_number: None, 237 parent_index_number: None, 238 image_tags: None, 239 is_folder: None, 240 overview: None, 241 } 242 } 243} 244 245#[derive(Debug, Clone, Serialize, Deserialize)] 246#[serde(rename_all = "PascalCase")] 247pub struct UserView { 248 pub id: String, 249 pub name: String, 250 #[serde(default)] 251 pub collection_type: Option<String>, 252} 253 254#[derive(Debug, Clone, Serialize, Deserialize)] 255#[serde(rename_all = "PascalCase")] 256pub struct UserViewsResult { 257 #[serde(default)] 258 pub items: Vec<UserView>, 259} 260 261pub type Playlist = BaseItem; 262pub type PlaylistItem = BaseItem;