Files
Flaro/Packages/FlixyKit/Sources/JellyfinKit/JellyfinItem.swift
T
ScarriffleandClaude Opus 5 cb95e3503f Der Folgentitel stand zweimal da
EpisodeTitle statt Name: hier stand der Reihentitel ein zweites Mal, und
im Sendungsblatt hätte derselbe Satz zweimal untereinander gestanden.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 06:27:27 +02:00

210 lines
8.0 KiB
Swift

import Foundation
import MediaKit
/// Ein Eintrag, wie Jellyfin ihn über die Leitung schickt.
///
/// Die Felder heißen groß und die Zeiten sind Ticks zu hundert Nanosekunden —
/// beides bleibt hier unten. Nach oben geht nur `MediaItem`.
public struct JellyfinItem: Decodable, Sendable {
public let id: String
public let name: String?
public let type: String?
public let overview: String?
public let productionYear: Int?
/// Laufzeit in Ticks zu 100 ns.
public let runTimeTicks: Int64?
public let indexNumber: Int?
public let parentIndexNumber: Int?
public let seriesName: String?
public let seriesID: String?
public let seasonID: String?
public let seasonName: String?
public let officialRating: String?
public let communityRating: Double?
public let criticRating: Double?
public let dateCreated: Date?
public let childCount: Int?
public let recursiveItemCount: Int?
public let genres: [String]?
public let people: [Person]?
public let userData: UserData?
public let imageTags: [String: String]?
public let backdropImageTags: [String]?
public let parentBackdropItemID: String?
public let seriesPrimaryImageTag: String?
public let mediaSources: [JellyfinMediaSource]?
public let collectionType: String?
// MARK: - Live-TV
//
// Sender **und** Sendungen kommen bei Jellyfin als derselbe Typ wie
// Filme und Folgen. Deshalb stehen ihre Felder hier und nicht in einem
// eigenen Modell: ein zweites, das zu neunzig Prozent dasselbe trägt,
// wäre eine Kopie mit Pflegeaufwand.
/// Zu welchem Sender eine Sendung gehört.
public let channelID: String?
public let channelName: String?
/// Der Folgentitel einer Sendung — der Reihentitel steht in `name`.
public let episodeTitle: String?
/// Die Nummer, unter der ein Sender im Programm steht — „3" oder „11.1".
public let channelNumber: String?
/// Dasselbe, aber am Sender selbst statt an der Sendung.
public let number: String?
public let startDate: Date?
public let endDate: Date?
/// Was auf diesem Sender gerade läuft.
///
/// Kommt nur, wenn die Senderliste mit `addCurrentProgram` geholt wurde —
/// dann aber gleich mit, und die Liste braucht keinen zweiten Aufruf.
public let currentProgram: JellyfinCurrentProgram?
/// Ob die Sendung zu einem laufenden Serien-Timer gehört.
public let seriesTimerID: String?
public let isMovie: Bool?
public let isSeries: Bool?
public let isNews: Bool?
public let isSports: Bool?
public let isKids: Bool?
public let isLive: Bool?
public struct Person: Decodable, Sendable {
public let id: String?
public let name: String?
public let role: String?
public let type: String?
public let primaryImageTag: String?
private enum CodingKeys: String, CodingKey {
case id = "Id", name = "Name", role = "Role", type = "Type"
case primaryImageTag = "PrimaryImageTag"
}
}
public struct UserData: Decodable, Sendable {
public let playbackPositionTicks: Int64?
public let playCount: Int?
public let played: Bool?
public let isFavorite: Bool?
public let unplayedItemCount: Int?
private enum CodingKeys: String, CodingKey {
case playbackPositionTicks = "PlaybackPositionTicks"
case playCount = "PlayCount"
case played = "Played"
case isFavorite = "IsFavorite"
case unplayedItemCount = "UnplayedItemCount"
}
}
private enum CodingKeys: String, CodingKey {
case id = "Id", name = "Name", type = "Type", overview = "Overview"
case productionYear = "ProductionYear", runTimeTicks = "RunTimeTicks"
case indexNumber = "IndexNumber", parentIndexNumber = "ParentIndexNumber"
case seriesName = "SeriesName", seriesID = "SeriesId"
case seasonID = "SeasonId", seasonName = "SeasonName"
case officialRating = "OfficialRating", communityRating = "CommunityRating"
case criticRating = "CriticRating", dateCreated = "DateCreated"
case childCount = "ChildCount", recursiveItemCount = "RecursiveItemCount"
case genres = "Genres", people = "People", userData = "UserData"
case imageTags = "ImageTags", backdropImageTags = "BackdropImageTags"
case parentBackdropItemID = "ParentBackdropItemId"
case seriesPrimaryImageTag = "SeriesPrimaryImageTag"
case mediaSources = "MediaSources", collectionType = "CollectionType"
case channelID = "ChannelId", channelName = "ChannelName"
case episodeTitle = "EpisodeTitle"
case channelNumber = "ChannelNumber", number = "Number"
case startDate = "StartDate", endDate = "EndDate"
case currentProgram = "CurrentProgram", seriesTimerID = "SeriesTimerId"
case isMovie = "IsMovie", isSeries = "IsSeries", isNews = "IsNews"
case isSports = "IsSports", isKids = "IsKids", isLive = "IsLive"
}
}
/// Was auf einem Sender gerade läuft.
///
/// Ein eigenes, schmales Modell statt eines verschachtelten `JellyfinItem`:
/// ein Struct darf sich nicht selbst enthalten — seine Größe wäre unendlich.
/// Gebraucht werden hier ohnehin nur fünf Felder.
public struct JellyfinCurrentProgram: Decodable, Sendable {
public let id: String
public let name: String?
public let overview: String?
public let startDate: Date?
public let endDate: Date?
private enum CodingKeys: String, CodingKey {
case id = "Id", name = "Name", overview = "Overview"
case startDate = "StartDate", endDate = "EndDate"
}
}
/// Eine Fassung, wie Jellyfin sie meldet.
public struct JellyfinMediaSource: Decodable, Sendable {
public let id: String?
public let container: String?
public let size: Int64?
public let bitrate: Int?
public let runTimeTicks: Int64?
public let mediaStreams: [JellyfinMediaStream]?
public let supportsDirectPlay: Bool?
public let supportsDirectStream: Bool?
public let supportsTranscoding: Bool?
public let transcodingURL: String?
private enum CodingKeys: String, CodingKey {
case id = "Id", container = "Container", size = "Size", bitrate = "Bitrate"
case runTimeTicks = "RunTimeTicks", mediaStreams = "MediaStreams"
case supportsDirectPlay = "SupportsDirectPlay"
case supportsDirectStream = "SupportsDirectStream"
case supportsTranscoding = "SupportsTranscoding"
case transcodingURL = "TranscodingUrl"
}
}
/// Eine Spur, wie Jellyfin sie meldet.
public struct JellyfinMediaStream: Decodable, Sendable {
public let index: Int?
public let type: String?
public let codec: String?
public let language: String?
public let displayTitle: String?
public let channels: Int?
public let isDefault: Bool?
public let isForced: Bool?
public let height: Int?
public let width: Int?
private enum CodingKeys: String, CodingKey {
case index = "Index", type = "Type", codec = "Codec", language = "Language"
case displayTitle = "DisplayTitle", channels = "Channels"
case isDefault = "IsDefault", isForced = "IsForced"
case height = "Height", width = "Width"
}
}
/// Die Hülle, in der Jellyfin Listen schickt.
public struct JellyfinItemPage: Decodable, Sendable {
public let items: [JellyfinItem]
public let totalRecordCount: Int?
public let startIndex: Int?
private enum CodingKeys: String, CodingKey {
case items = "Items"
case totalRecordCount = "TotalRecordCount"
case startIndex = "StartIndex"
}
}
extension Int64 {
/// Ticks zu hundert Nanosekunden in Millisekunden.
///
/// Jellyfins Einheit. Zehntausend Ticks sind eine Millisekunde — eine
/// Zahl, die man beim Lesen zweimal prüft und deshalb genau einmal
/// aufschreibt.
var millisecondsFromTicks: Int { Int(self / 10_000) }
}
extension Int {
var ticksFromMilliseconds: Int64 { Int64(self) * 10_000 }
}