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 } }