feat(jellyfin): show media quality labels
This commit is contained in:
@@ -172,6 +172,46 @@ void main() {
|
||||
expect(extras[1].posterThumb(), extras[1].artPath);
|
||||
});
|
||||
|
||||
test('fetchChildren requests media sources for episode-row quality labels', () async {
|
||||
final requests = <Uri>[];
|
||||
final scoped = JellyfinClient.forTesting(
|
||||
connection: _conn(),
|
||||
httpClient: MockClient((request) async {
|
||||
requests.add(request.url);
|
||||
if (request.url.path == '/Shows/season-1/Seasons') {
|
||||
return http.Response('not found', 404);
|
||||
}
|
||||
if (request.url.path == '/Items') {
|
||||
return http.Response(jsonEncode({'Items': <Object>[], 'TotalRecordCount': 0}), 200);
|
||||
}
|
||||
return http.Response('unexpected ${request.url}', 500);
|
||||
}),
|
||||
);
|
||||
addTearDown(scoped.close);
|
||||
|
||||
await scoped.fetchChildren('season-1');
|
||||
|
||||
final directChildrenRequest = requests.firstWhere((uri) => uri.path == '/Items');
|
||||
expect(directChildrenRequest.queryParameters['Fields']!.split(','), contains('MediaSources'));
|
||||
});
|
||||
|
||||
test('fetchPlayableDescendantsPage requests media sources for episode-row quality labels', () async {
|
||||
Uri? capturedUri;
|
||||
final scoped = JellyfinClient.forTesting(
|
||||
connection: _conn(),
|
||||
httpClient: MockClient((request) async {
|
||||
capturedUri = request.url;
|
||||
return http.Response(jsonEncode({'Items': <Object>[], 'TotalRecordCount': 0}), 200);
|
||||
}),
|
||||
);
|
||||
addTearDown(scoped.close);
|
||||
|
||||
await scoped.fetchPlayableDescendantsPage('show-1');
|
||||
|
||||
expect(capturedUri!.path, '/Items');
|
||||
expect(capturedUri!.queryParameters['Fields']!.split(','), contains('MediaSources'));
|
||||
});
|
||||
|
||||
test('reportPlaybackProgress sends media source and stream indexes', () async {
|
||||
Uri? capturedUri;
|
||||
String? capturedBody;
|
||||
|
||||
@@ -313,6 +313,73 @@ void main() {
|
||||
expect(subtitle.isExternal, isTrue);
|
||||
expect(subtitle.sidecarPath, '/Videos/movie-1/movie-1/Subtitles/2/Stream.srt');
|
||||
});
|
||||
|
||||
test('media streams map Jellyfin Dolby Vision, HDR, and source default audio', () {
|
||||
final json = {
|
||||
'Id': 'movie-1',
|
||||
'Name': 'Movie',
|
||||
'Type': 'Movie',
|
||||
'MediaSources': [
|
||||
{
|
||||
'Id': 'src-1',
|
||||
'DefaultAudioStreamIndex': 2,
|
||||
'MediaStreams': [
|
||||
{
|
||||
'Index': 0,
|
||||
'Type': 'Video',
|
||||
'Codec': 'hevc',
|
||||
'Width': 3840,
|
||||
'Height': 2160,
|
||||
'VideoRangeType': 'DOVI',
|
||||
'VideoRange': 'HDR',
|
||||
'VideoDoViTitle': 'Dolby Vision Profile 8',
|
||||
'DvProfile': 8,
|
||||
'DvLevel': 6,
|
||||
'DvBlSignalCompatibilityId': 1,
|
||||
},
|
||||
{'Index': 1, 'Type': 'Audio', 'Codec': 'eac3', 'Channels': 6, 'IsDefault': true},
|
||||
{'Index': 2, 'Type': 'Audio', 'Codec': 'aac', 'Channels': 2},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
final item = JellyfinMappers.mediaItem(json, serverId: ServerId(_serverId), absolutizer: null)!;
|
||||
final streams = item.mediaVersions!.single.parts.single.streams;
|
||||
final video = streams.firstWhere((stream) => stream.kind == MediaStreamKind.video);
|
||||
final firstAudio = streams.firstWhere((stream) => stream.index == 1);
|
||||
final selectedAudio = streams.firstWhere((stream) => stream.index == 2);
|
||||
|
||||
expect(video.codec, 'hevc');
|
||||
expect(video.hdr, isTrue);
|
||||
expect(video.dolbyVision, isTrue);
|
||||
expect(video.dolbyVisionProfile, 8);
|
||||
expect(firstAudio.selected, isFalse);
|
||||
expect(selectedAudio.selected, isTrue);
|
||||
});
|
||||
|
||||
test('media streams map Jellyfin HDR without Dolby Vision', () {
|
||||
final json = {
|
||||
'Id': 'movie-1',
|
||||
'Name': 'Movie',
|
||||
'Type': 'Movie',
|
||||
'MediaSources': [
|
||||
{
|
||||
'Id': 'src-1',
|
||||
'MediaStreams': [
|
||||
{'Index': 0, 'Type': 'Video', 'Codec': 'hevc', 'VideoRangeType': 'HDR10', 'VideoRange': 'HDR'},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
final item = JellyfinMappers.mediaItem(json, serverId: ServerId(_serverId), absolutizer: null)!;
|
||||
final video = item.mediaVersions!.single.parts.single.streams.single;
|
||||
|
||||
expect(video.hdr, isTrue);
|
||||
expect(video.dolbyVision, isFalse);
|
||||
expect(video.dolbyVisionProfile, isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('JellyfinMappers.library', () {
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:plezy/media/media_kind.dart';
|
||||
import 'package:plezy/media/media_part.dart';
|
||||
import 'package:plezy/media/media_stream.dart';
|
||||
import 'package:plezy/media/media_version.dart';
|
||||
import 'package:plezy/services/jellyfin_mappers.dart';
|
||||
import 'package:plezy/services/plex_mappers.dart';
|
||||
import 'package:plezy/utils/media_quality_labels.dart';
|
||||
|
||||
@@ -151,6 +152,41 @@ void main() {
|
||||
expect(buildMediaQualityLabels(item), ['4K', 'DV P8', 'EAC3 5.1']);
|
||||
});
|
||||
|
||||
test('formats Jellyfin stream metadata from MediaSources', () {
|
||||
final item = JellyfinMappers.mediaItem(
|
||||
{
|
||||
'Id': 'movie-1',
|
||||
'Name': 'Movie',
|
||||
'Type': 'Movie',
|
||||
'MediaSources': [
|
||||
{
|
||||
'Id': 'source-1',
|
||||
'DefaultAudioStreamIndex': 2,
|
||||
'MediaStreams': [
|
||||
{
|
||||
'Index': 0,
|
||||
'Type': 'Video',
|
||||
'Codec': 'hevc',
|
||||
'Width': 3840,
|
||||
'Height': 2160,
|
||||
'VideoRangeType': 'DOVI',
|
||||
'VideoDoViTitle': 'Dolby Vision Profile 8',
|
||||
'DvProfile': 8,
|
||||
'DvBlSignalCompatibilityId': 1,
|
||||
},
|
||||
{'Index': 1, 'Type': 'Audio', 'Codec': 'eac3', 'Channels': 6, 'IsDefault': true},
|
||||
{'Index': 2, 'Type': 'Audio', 'Codec': 'aac', 'Channels': 2},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
serverId: ServerId('jellyfin'),
|
||||
absolutizer: null,
|
||||
)!;
|
||||
|
||||
expect(buildMediaQualityLabels(item), ['4K', 'DV P8', 'AAC Stereo']);
|
||||
});
|
||||
|
||||
test('uses selected audio stream and stereo label', () {
|
||||
final item = _episodeWithVersion(
|
||||
MediaVersion(
|
||||
|
||||
Reference in New Issue
Block a user