From 8ff6bf652c0f49bea398be1f8e420c978fbc708a Mon Sep 17 00:00:00 2001 From: Jason Dove Date: Sun, 5 Mar 2023 21:40:20 -0600 Subject: [PATCH] fix jellyfin streaming and sar calculation (#1195) --- CHANGELOG.md | 4 ++ ErsatzTV.FFmpeg/MediaStream.cs | 40 ++++++++++++++++--- .../Jellyfin/JellyfinApiClient.cs | 19 +++++++-- .../Models/JellyfinMediaStreamResponse.cs | 1 + 4 files changed, 55 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67bd3d65e..dcd586672 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +### Fixed +- Fix scaling anamorphic content from non-local libraries +- Fix direct streaming content from Jellyfin that has external subtitles + - Note that these subtitles are not currently supported in ETV, but they did cause a playback issue ## [0.7.5-beta] - 2023-03-05 ### Added diff --git a/ErsatzTV.FFmpeg/MediaStream.cs b/ErsatzTV.FFmpeg/MediaStream.cs index 1536e8040..5811ec093 100644 --- a/ErsatzTV.FFmpeg/MediaStream.cs +++ b/ErsatzTV.FFmpeg/MediaStream.cs @@ -15,7 +15,7 @@ public record VideoStream( Option PixelFormat, ColorParams ColorParams, FrameSize FrameSize, - string SampleAspectRatio, + string MaybeSampleAspectRatio, string DisplayAspectRatio, Option FrameRate, bool StillImage, @@ -25,7 +25,37 @@ public record VideoStream( StreamKind.Video) { public int BitDepth => PixelFormat.Map(pf => pf.BitDepth).IfNone(8); - + + public string SampleAspectRatio + { + get + { + // some media servers don't provide sample aspect ratio so we have to calculate it + if (string.IsNullOrWhiteSpace(MaybeSampleAspectRatio) || MaybeSampleAspectRatio == "0:0") + { + // first check for decimal DAR + if (!double.TryParse(DisplayAspectRatio, out double dar)) + { + // if not, assume it's a ratio + string[] split = DisplayAspectRatio.Split(':'); + var num = double.Parse(split[0]); + var den = double.Parse(split[1]); + dar = num / den; + } + + double res = FrameSize.Width / (double)FrameSize.Height; + return $"{dar}:{res}"; + } + else + { + string[] split = MaybeSampleAspectRatio.Split(':'); + var num = double.Parse(split[0]); + var den = double.Parse(split[1]); + return $"{num}:{den}"; + } + } + } + public bool IsAnamorphic { get @@ -89,13 +119,13 @@ public record VideoStream( private double GetSAR() { // some media servers don't provide sample aspect ratio so we have to calculate it - if (string.IsNullOrWhiteSpace(SampleAspectRatio)) + if (string.IsNullOrWhiteSpace(MaybeSampleAspectRatio) || MaybeSampleAspectRatio == "0:0") { // first check for decimal DAR if (!double.TryParse(DisplayAspectRatio, out double dar)) { // if not, assume it's a ratio - string[] split = DisplayAspectRatio.Split(':'); + string[] split = DisplayAspectRatio.Split(':'); var num = double.Parse(split[0]); var den = double.Parse(split[1]); dar = num / den; @@ -106,7 +136,7 @@ public record VideoStream( } else { - string[] split = SampleAspectRatio.Split(':'); + string[] split = MaybeSampleAspectRatio.Split(':'); var num = double.Parse(split[0]); var den = double.Parse(split[1]); return num / den; diff --git a/ErsatzTV.Infrastructure/Jellyfin/JellyfinApiClient.cs b/ErsatzTV.Infrastructure/Jellyfin/JellyfinApiClient.cs index 53e8a1682..5b90f6630 100644 --- a/ErsatzTV.Infrastructure/Jellyfin/JellyfinApiClient.cs +++ b/ErsatzTV.Infrastructure/Jellyfin/JellyfinApiClient.cs @@ -895,7 +895,18 @@ public class JellyfinApiClient : IJellyfinApiClient } JellyfinMediaSourceResponse mediaSource = response.MediaSources.Head(); - IList streams = mediaSource.MediaStreams; + + // jellyfin includes external streams first, obscuring real stream indexes + // from the source file + int streamIndexOffset = mediaSource.MediaStreams + .Filter(s => s.IsExternal) + .Map(s => s.Index + 1) + .OrderByDescending(i => i) + .FirstOrDefault(); + + IList streams = mediaSource.MediaStreams + .Filter(s => s.IsExternal == false) + .ToList(); Option maybeVideoStream = streams.Find(s => s.Type == JellyfinMediaStreamType.Video); return maybeVideoStream.Map( @@ -948,7 +959,7 @@ public class JellyfinApiClient : IJellyfinApiClient { MediaVersionId = version.Id, MediaStreamKind = MediaStreamKind.Video, - Index = videoStream.Index, + Index = videoStream.Index - streamIndexOffset, Codec = videoStream.Codec, Profile = (videoStream.Profile ?? string.Empty).ToLowerInvariant(), Default = videoStream.IsDefault, @@ -968,7 +979,7 @@ public class JellyfinApiClient : IJellyfinApiClient { MediaVersionId = version.Id, MediaStreamKind = MediaStreamKind.Audio, - Index = audioStream.Index, + Index = audioStream.Index - streamIndexOffset, Codec = audioStream.Codec, Profile = (audioStream.Profile ?? string.Empty).ToLowerInvariant(), Channels = audioStream.Channels ?? 2, @@ -988,7 +999,7 @@ public class JellyfinApiClient : IJellyfinApiClient { MediaVersionId = version.Id, MediaStreamKind = MediaStreamKind.Subtitle, - Index = subtitleStream.Index, + Index = subtitleStream.Index - streamIndexOffset, Codec = subtitleStream.Codec, Default = subtitleStream.IsDefault, Forced = subtitleStream.IsForced, diff --git a/ErsatzTV.Infrastructure/Jellyfin/Models/JellyfinMediaStreamResponse.cs b/ErsatzTV.Infrastructure/Jellyfin/Models/JellyfinMediaStreamResponse.cs index 3b08c1d4f..2bb23ddcd 100644 --- a/ErsatzTV.Infrastructure/Jellyfin/Models/JellyfinMediaStreamResponse.cs +++ b/ErsatzTV.Infrastructure/Jellyfin/Models/JellyfinMediaStreamResponse.cs @@ -11,6 +11,7 @@ public class JellyfinMediaStreamResponse public int Index { get; set; } public bool IsDefault { get; set; } public bool IsForced { get; set; } + public bool IsExternal { get; set; } public string Profile { get; set; } public string AspectRatio { get; set; } public int? Channels { get; set; }