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>
210 lines
8.0 KiB
Swift
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 }
|
|
}
|