MediaKit: die neutralen Datensätze und die Übersetzung aus Plex
`MediaItem` und die Typen darunter — die Form, in der die Oberfläche Titel sieht, unabhängig davon, welcher Server sie geliefert hat. Die Namen sind dort geblieben, wo sie ohnehin neutral waren: `title`, `summary`, `year`, `index`. Umbenannt wurde, was Plex-Jargon ist. `ratingKey` heißt `id`, `viewOffset` heißt `resumeMilliseconds`, und aus dem „Großelternteil" ist die Serie geworden, die von Anfang an gemeint war — ein Name, den man in jeder Ansicht im Kopf übersetzen musste. Zwei Dinge, die dabei ehrlicher werden: `isFavourite` gibt es, aber Plex meldet immer `false`. Ein Feld, das ein Server nicht füllt, ist besser als eine Fähigkeit, die es nur auf einem gibt und die sich nirgends setzen ließe. Dateigrößen sind jetzt `Int64`. Ein Remux überschreitet vier Gigabyte mühelos. Die Übersetzung steht in **einer** Datei und geht in **eine** Richtung. Die Drahtmodelle bleiben, wo sie sind — sie tragen Eigenheiten, die nur Plex hat, und die gehören nicht nach oben. Geprüft wird gegen echte Server-Antworten, nicht gegen erfundene Werte: genau dort stehen die Eigenheiten, an denen eine Übersetzung scheitert. Ein Test vergleicht außerdem beide Wege zum Wiedergabe-Profil — sagten sie Unterschiedliches, spielte ein Titel nach dem Umbau plötzlich über einen anderen Player. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
5b2b86b66b
commit
ba6be4ac79
@@ -0,0 +1,186 @@
|
||||
import Foundation
|
||||
import MediaKit
|
||||
|
||||
/// Die Übersetzung der Plex-Drahtmodelle in die neutrale Form.
|
||||
///
|
||||
/// Eine Datei, eine Richtung. Danach ist unerheblich, von welchem Server ein
|
||||
/// Titel stammt — und genau das war das Ziel: die Oberfläche soll nicht an
|
||||
/// dreißig Stellen wissen müssen, wie Plex seine Felder nennt.
|
||||
///
|
||||
/// Die Drahtmodelle bleiben, wo sie sind. Sie tragen Eigenheiten, die nur
|
||||
/// Plex hat (dynamische Schlüssel in der Hülle, `grandparent` für die Serie,
|
||||
/// Sekunden seit 1970 als Zahl), und die gehören nicht nach oben.
|
||||
extension PlexItem {
|
||||
public var asMediaItem: MediaItem {
|
||||
MediaItem(
|
||||
id: ratingKey,
|
||||
path: key,
|
||||
kind: kind.map(Self.kind) ?? nil,
|
||||
title: title,
|
||||
sortTitle: titleSort,
|
||||
summary: summary,
|
||||
year: year,
|
||||
duration: duration,
|
||||
contentRating: contentRating,
|
||||
rating: rating,
|
||||
audienceRating: audienceRating,
|
||||
thumb: thumb,
|
||||
art: art,
|
||||
parentThumb: parentThumb,
|
||||
seriesThumb: grandparentThumb,
|
||||
seriesArt: grandparentArt,
|
||||
index: index,
|
||||
parentIndex: parentIndex,
|
||||
parentTitle: parentTitle,
|
||||
parentID: parentRatingKey,
|
||||
seriesTitle: grandparentTitle,
|
||||
seriesID: grandparentRatingKey,
|
||||
playCount: viewCount,
|
||||
resumeMilliseconds: viewOffset,
|
||||
lastPlayedAt: lastViewedAt.map { Date(timeIntervalSince1970: TimeInterval($0)) },
|
||||
addedAt: addedAt.map { Date(timeIntervalSince1970: TimeInterval($0)) },
|
||||
// Plex kennt keine Favoriten. Ehrlicher als eine erfundene
|
||||
// Markierung, die sich nirgends setzen ließe.
|
||||
isFavourite: false,
|
||||
episodeCount: leafCount,
|
||||
playedEpisodeCount: viewedLeafCount,
|
||||
childCount: childCount,
|
||||
versions: media.map(\.asMediaVersion),
|
||||
genres: genres.map(\.tag),
|
||||
directors: directors.map(\.tag),
|
||||
writers: writers.map(\.tag),
|
||||
cast: roles.map(\.asMediaPerson),
|
||||
markers: markers.map(\.asMediaMarker),
|
||||
artwork: images.map(\.asMediaArtwork)
|
||||
)
|
||||
}
|
||||
|
||||
private static func kind(_ plex: PlexItem.Kind) -> MediaItem.Kind {
|
||||
switch plex {
|
||||
case .movie: .movie
|
||||
case .show: .show
|
||||
case .season: .season
|
||||
case .episode: .episode
|
||||
case .clip: .clip
|
||||
case .collection: .collection
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension PlexMedia {
|
||||
public var asMediaVersion: MediaVersion {
|
||||
MediaVersion(
|
||||
id: String(id),
|
||||
container: container,
|
||||
videoCodec: videoCodec,
|
||||
audioCodec: audioCodec,
|
||||
videoResolution: videoResolution,
|
||||
bitrate: bitrate,
|
||||
width: width,
|
||||
height: height,
|
||||
audioChannels: audioChannels,
|
||||
duration: duration,
|
||||
files: parts.map(\.asMediaFile)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension PlexPart {
|
||||
public var asMediaFile: MediaFile {
|
||||
MediaFile(
|
||||
id: String(id),
|
||||
key: key,
|
||||
container: container,
|
||||
size: size,
|
||||
duration: duration,
|
||||
file: file,
|
||||
streams: streams.compactMap(\.asMediaStream)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension PlexStream {
|
||||
/// `nil`, wenn Plex eine Spurart meldet, mit der die App nichts anfängt.
|
||||
public var asMediaStream: MediaStream? {
|
||||
let mapped: MediaStream.Kind
|
||||
switch kind {
|
||||
case .video: mapped = .video
|
||||
case .audio: mapped = .audio
|
||||
case .subtitle: mapped = .subtitle
|
||||
case nil: return nil
|
||||
}
|
||||
|
||||
return MediaStream(
|
||||
id: String(id),
|
||||
kind: mapped,
|
||||
codec: codec,
|
||||
language: language,
|
||||
languageCode: languageCode,
|
||||
displayTitle: displayTitle ?? title,
|
||||
channels: channels,
|
||||
isSelected: isSelected,
|
||||
isForced: isForced
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension PlexTag {
|
||||
public var asMediaPerson: MediaPerson {
|
||||
MediaPerson(id: id, name: tag, role: role, thumb: thumb)
|
||||
}
|
||||
}
|
||||
|
||||
extension PlexMarker {
|
||||
public var asMediaMarker: MediaMarker {
|
||||
MediaMarker(
|
||||
kind: MediaMarker.Kind(rawValue: type) ?? .unknown,
|
||||
startMilliseconds: startTimeOffset,
|
||||
endMilliseconds: endTimeOffset
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension PlexImage {
|
||||
public var asMediaArtwork: MediaArtwork {
|
||||
MediaArtwork(
|
||||
kind: MediaArtwork.Kind(rawValue: type) ?? .unknown,
|
||||
path: url
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension PlexLibrary {
|
||||
public var asMediaLibrary: MediaLibrary {
|
||||
MediaLibrary(
|
||||
id: key,
|
||||
title: title,
|
||||
kind: mediaKind,
|
||||
thumb: thumb,
|
||||
art: art
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension PlexLibrary {
|
||||
private var mediaKind: MediaLibrary.Kind {
|
||||
switch kind {
|
||||
case .movie: .movie
|
||||
case .show: .show
|
||||
case .artist: .music
|
||||
case .photo: .photo
|
||||
case .livetv: .liveTV
|
||||
case nil: .other
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension PlexHub {
|
||||
public var asMediaRowGroup: MediaRowGroup {
|
||||
MediaRowGroup(
|
||||
id: hubIdentifier,
|
||||
title: title,
|
||||
moreKey: more == true ? key : nil,
|
||||
items: items.map(\.asMediaItem)
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user