From c6e29647fcdf59ff6f94fc6313edc2e05c9cc32c Mon Sep 17 00:00:00 2001 From: gnattu Date: Wed, 8 May 2024 08:33:23 +0800 Subject: [PATCH 1/4] Recalculate trickplay image height for anamorphic videos Signed-off-by: gnattu --- .../MediaEncoding/EncodingHelper.cs | 27 ++++++++++++------- .../Encoder/MediaEncoder.cs | 16 +++++++++++ 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 8a8912b2ab..411aafd916 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -3172,7 +3172,9 @@ namespace MediaBrowser.Controller.MediaEncoding int? requestedMaxHeight) { var isV4l2 = string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase); + var isMjpeg = videoEncoder is not null && videoEncoder.Contains("mjpeg", StringComparison.OrdinalIgnoreCase); var scaleVal = isV4l2 ? 64 : 2; + var targetAr = isMjpeg ? "(a*sar)" : "a"; // manually calculate AR when using mjpeg encoder // If fixed dimensions were supplied if (requestedWidth.HasValue && requestedHeight.HasValue) @@ -3201,10 +3203,11 @@ namespace MediaBrowser.Controller.MediaEncoding return string.Format( CultureInfo.InvariantCulture, - @"scale=trunc(min(max(iw\,ih*a)\,min({0}\,{1}*a))/{2})*{2}:trunc(min(max(iw/a\,ih)\,min({0}/a\,{1}))/2)*2", + @"scale=trunc(min(max(iw\,ih*{3})\,min({0}\,{1}*{3}))/{2})*{2}:trunc(min(max(iw/{3}\,ih)\,min({0}/{3}\,{1}))/2)*2", maxWidthParam, maxHeightParam, - scaleVal); + scaleVal, + targetAr); } // If a fixed width was requested @@ -3220,8 +3223,9 @@ namespace MediaBrowser.Controller.MediaEncoding return string.Format( CultureInfo.InvariantCulture, - "scale={0}:trunc(ow/a/2)*2", - widthParam); + "scale={0}:trunc(ow/{1}/2)*2", + widthParam, + targetAr); } // If a fixed height was requested @@ -3231,9 +3235,10 @@ namespace MediaBrowser.Controller.MediaEncoding return string.Format( CultureInfo.InvariantCulture, - "scale=trunc(oh*a/{1})*{1}:{0}", + "scale=trunc(oh*{2}/{1})*{1}:{0}", heightParam, - scaleVal); + scaleVal, + targetAr); } // If a max width was requested @@ -3243,9 +3248,10 @@ namespace MediaBrowser.Controller.MediaEncoding return string.Format( CultureInfo.InvariantCulture, - @"scale=trunc(min(max(iw\,ih*a)\,{0})/{1})*{1}:trunc(ow/a/2)*2", + @"scale=trunc(min(max(iw\,ih*{2})\,{0})/{1})*{1}:trunc(ow/{2}/2)*2", maxWidthParam, - scaleVal); + scaleVal, + targetAr); } // If a max height was requested @@ -3255,9 +3261,10 @@ namespace MediaBrowser.Controller.MediaEncoding return string.Format( CultureInfo.InvariantCulture, - @"scale=trunc(oh*a/{1})*{1}:min(max(iw/a\,ih)\,{0})", + @"scale=trunc(oh*{2}/{1})*{1}:min(max(iw/{2}\,ih)\,{0})", maxHeightParam, - scaleVal); + scaleVal, + targetAr); } return string.Empty; diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 8ea0f58ea3..387571cdb5 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -824,6 +824,22 @@ namespace MediaBrowser.MediaEncoding.Encoder options.EnableTonemapping = false; } + if (imageStream.Width is not null && imageStream.Height is not null) + { + // For hardware trickplay encoders, we need to re-calculate the size because they used fixed scale dimensions + var darParts = imageStream.AspectRatio.Split(":"); + var (wa, ha) = (int.Parse(darParts[0], CultureInfo.InvariantCulture), int.Parse(darParts[1], CultureInfo.InvariantCulture)); + // When dimension / DAR does not equal to 1:1, then the frames are most likely stored stretched. + // Note: this might be incorrect for 3D videos as the SAR stored might be per eye instead of per video, but we really can do little about it. + var shouldResetHeight = imageStream.Width * ha != imageStream.Height * wa; + if (shouldResetHeight) + { + // SAR = DAR * Height / Width + // RealHeight = Height / SAR = Height / (DAR * Height / Width) = Width / DAR + imageStream.Height = Convert.ToInt32(imageStream.Width.Value * (double)ha / wa); + } + } + var baseRequest = new BaseEncodingJobOptions { MaxWidth = maxWidth, MaxFramerate = (float)(1.0 / interval.TotalSeconds) }; var jobState = new EncodingJobInfo(TranscodingJobType.Progressive) { From f8da69f8e5a9e88d1829f174354d4c5841e03da9 Mon Sep 17 00:00:00 2001 From: gnattu Date: Thu, 23 May 2024 23:02:57 +0800 Subject: [PATCH 2/4] Allow decimal aspect ratio Signed-off-by: gnattu --- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 387571cdb5..e478fb97fe 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -828,15 +828,15 @@ namespace MediaBrowser.MediaEncoding.Encoder { // For hardware trickplay encoders, we need to re-calculate the size because they used fixed scale dimensions var darParts = imageStream.AspectRatio.Split(":"); - var (wa, ha) = (int.Parse(darParts[0], CultureInfo.InvariantCulture), int.Parse(darParts[1], CultureInfo.InvariantCulture)); + var (wa, ha) = (double.Parse(darParts[0], CultureInfo.InvariantCulture), double.Parse(darParts[1], CultureInfo.InvariantCulture)); // When dimension / DAR does not equal to 1:1, then the frames are most likely stored stretched. // Note: this might be incorrect for 3D videos as the SAR stored might be per eye instead of per video, but we really can do little about it. - var shouldResetHeight = imageStream.Width * ha != imageStream.Height * wa; + var shouldResetHeight = Math.Abs((imageStream.Width.Value * ha) - (imageStream.Height.Value * wa)) > .05; if (shouldResetHeight) { // SAR = DAR * Height / Width // RealHeight = Height / SAR = Height / (DAR * Height / Width) = Width / DAR - imageStream.Height = Convert.ToInt32(imageStream.Width.Value * (double)ha / wa); + imageStream.Height = Convert.ToInt32(imageStream.Width.Value * ha / wa); } } From 933a285bf59edce8c42a9184c2ca25205a9f865e Mon Sep 17 00:00:00 2001 From: gnattu Date: Thu, 23 May 2024 23:03:35 +0800 Subject: [PATCH 3/4] Use single quote Signed-off-by: gnattu --- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index e478fb97fe..ee95804428 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -827,7 +827,7 @@ namespace MediaBrowser.MediaEncoding.Encoder if (imageStream.Width is not null && imageStream.Height is not null) { // For hardware trickplay encoders, we need to re-calculate the size because they used fixed scale dimensions - var darParts = imageStream.AspectRatio.Split(":"); + var darParts = imageStream.AspectRatio.Split(':'); var (wa, ha) = (double.Parse(darParts[0], CultureInfo.InvariantCulture), double.Parse(darParts[1], CultureInfo.InvariantCulture)); // When dimension / DAR does not equal to 1:1, then the frames are most likely stored stretched. // Note: this might be incorrect for 3D videos as the SAR stored might be per eye instead of per video, but we really can do little about it. From 952995f796a1a8de20fb9c174b2b2782bd3b2d33 Mon Sep 17 00:00:00 2001 From: gnattu Date: Thu, 23 May 2024 23:08:29 +0800 Subject: [PATCH 4/4] Ignore NullOrEmpty imageStream.AspectRatio Signed-off-by: gnattu --- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index ee95804428..e804efbc63 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -824,7 +824,7 @@ namespace MediaBrowser.MediaEncoding.Encoder options.EnableTonemapping = false; } - if (imageStream.Width is not null && imageStream.Height is not null) + if (imageStream.Width is not null && imageStream.Height is not null && !string.IsNullOrEmpty(imageStream.AspectRatio)) { // For hardware trickplay encoders, we need to re-calculate the size because they used fixed scale dimensions var darParts = imageStream.AspectRatio.Split(':');