The context menu only offered File Info for movies and episodes, so a track's path, container, and audio stream detail were unreachable even though both backends already answer getFileInfo for them. Gate the entry on the new MediaKind.hasFileInfo instead of a literal kind list: movies, episodes, tracks, and clips are leaf items with real files, while shows, seasons, artists, albums, collections, playlists, and folders carry no Media/MediaSources and would only ever produce the "not available" snackbar. Also fix the Plex stream classifier, which mapped streamType 4 to an embedded image although PlexStreamType.lyrics is 4. Only music tracks carry that type, so a track's lyric stream rendered under "Embedded Images" with the video field block. Type 5 was invented outright and is now unknown. close #1747
85 lines
3.0 KiB
Dart
85 lines
3.0 KiB
Dart
/// Backend-neutral classification of a media item.
|
|
///
|
|
/// Mirrors the categories in [PlexMediaType] but is the canonical type used by
|
|
/// neutral domain models. Each backend's adapter is responsible for mapping
|
|
/// its own type strings (Plex `type` field, Jellyfin `BaseItemKind`) into one
|
|
/// of these values.
|
|
enum MediaKind {
|
|
movie,
|
|
show,
|
|
season,
|
|
episode,
|
|
artist,
|
|
album,
|
|
track,
|
|
collection,
|
|
playlist,
|
|
clip,
|
|
photo,
|
|
|
|
/// Filesystem-style directory row in folder browsing (Plex `/folder`
|
|
/// listings, Jellyfin `Folder`/`CollectionFolder` items). Not playable
|
|
/// media itself — children are fetched via
|
|
/// `MediaServerClient.fetchFolderChildren`.
|
|
folder,
|
|
unknown;
|
|
|
|
bool get isVideo => this == movie || this == episode || this == clip;
|
|
|
|
bool get isMusic => this == artist || this == album || this == track;
|
|
|
|
bool get isPlayable => isVideo || this == track;
|
|
|
|
/// Whether this kind maps to one or more files on disk, i.e. the server can
|
|
/// answer `MediaServerClient.getFileInfo` for it.
|
|
///
|
|
/// Deliberately not folded into [isPlayable] even though the member sets
|
|
/// coincide today: container kinds (show, season, artist, album, collection,
|
|
/// playlist, folder) aggregate leaves and carry no `Media`/`MediaSources` of
|
|
/// their own, while a non-playable kind can still be file-backed.
|
|
bool get hasFileInfo => this == movie || this == episode || this == track || this == clip;
|
|
|
|
/// Whether this container kind derives watched state from aggregate leaves.
|
|
bool get usesLeafWatchCounts => switch (this) {
|
|
show || season || artist || album || collection || playlist || folder => true,
|
|
movie || episode || track || clip || photo || unknown => false,
|
|
};
|
|
|
|
/// Lowercase string id used when persisting or comparing legacy code paths
|
|
/// that still hold raw type strings.
|
|
String get id => switch (this) {
|
|
MediaKind.movie => 'movie',
|
|
MediaKind.show => 'show',
|
|
MediaKind.season => 'season',
|
|
MediaKind.episode => 'episode',
|
|
MediaKind.artist => 'artist',
|
|
MediaKind.album => 'album',
|
|
MediaKind.track => 'track',
|
|
MediaKind.collection => 'collection',
|
|
MediaKind.playlist => 'playlist',
|
|
MediaKind.clip => 'clip',
|
|
MediaKind.photo => 'photo',
|
|
MediaKind.folder => 'folder',
|
|
MediaKind.unknown => 'unknown',
|
|
};
|
|
|
|
static MediaKind fromString(String? raw) {
|
|
if (raw == null) return MediaKind.unknown;
|
|
return switch (raw.toLowerCase()) {
|
|
'movie' => MediaKind.movie,
|
|
'show' || 'series' => MediaKind.show,
|
|
'season' => MediaKind.season,
|
|
'episode' => MediaKind.episode,
|
|
'artist' || 'musicartist' => MediaKind.artist,
|
|
'album' || 'musicalbum' => MediaKind.album,
|
|
'track' || 'audio' => MediaKind.track,
|
|
'collection' || 'boxset' => MediaKind.collection,
|
|
'playlist' => MediaKind.playlist,
|
|
'clip' || 'trailer' || 'video' || 'musicvideo' => MediaKind.clip,
|
|
'photo' => MediaKind.photo,
|
|
'folder' || 'collectionfolder' => MediaKind.folder,
|
|
_ => MediaKind.unknown,
|
|
};
|
|
}
|
|
}
|