From a8f1668540be34bc118c9bf08a234b856081011f Mon Sep 17 00:00:00 2001 From: gnattu Date: Sat, 30 Mar 2024 22:53:46 +0800 Subject: fix: unset qmin and qmax for vt (#11246) Co-authored-by: Nyanmisaka --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 85963e66c..ce5e29814 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1333,7 +1333,7 @@ namespace MediaBrowser.Controller.MediaEncoding return ".ts"; } - public string GetVideoBitrateParam(EncodingJobInfo state, string videoCodec) + private string GetVideoBitrateParam(EncodingJobInfo state, string videoCodec) { if (state.OutputVideoBitrate is null) { @@ -1402,7 +1402,7 @@ namespace MediaBrowser.Controller.MediaEncoding { // The `maxrate` and `bufsize` options can potentially lead to performance regression // and even encoder hangs, especially when the value is very high. - return FormattableString.Invariant($" -b:v {bitrate}"); + return FormattableString.Invariant($" -b:v {bitrate} -qmin -1 -qmax -1"); } return FormattableString.Invariant($" -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}"); -- cgit v1.2.3 From fe88a484d18ddbb2fb6859b93ff2e05a166359f1 Mon Sep 17 00:00:00 2001 From: gnattu Date: Sat, 30 Mar 2024 23:25:22 +0800 Subject: fix: don't do empty hwupload for VT (#11235) --- .../MediaEncoding/EncodingHelper.cs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index ce5e29814..250e0143f 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -5119,11 +5119,6 @@ namespace MediaBrowser.Controller.MediaEncoding /* Make main filters for video stream */ var mainFilters = new List(); - // INPUT videotoolbox/memory surface(vram/uma) - // this will pass-through automatically if in/out format matches. - mainFilters.Add("format=nv12|p010le|videotoolbox_vld"); - mainFilters.Add("hwupload=derive_device=videotoolbox"); - // hw deint if (doDeintH2645) { @@ -5179,6 +5174,21 @@ namespace MediaBrowser.Controller.MediaEncoding overlayFilters.Add("overlay_videotoolbox=eof_action=pass:repeatlast=0"); } + var needFiltering = mainFilters.Any(f => !string.IsNullOrEmpty(f)) || + subFilters.Any(f => !string.IsNullOrEmpty(f)) || + overlayFilters.Any(f => !string.IsNullOrEmpty(f)); + + // This is a workaround for ffmpeg's hwupload implementation + // For VideoToolbox encoders, a hwupload without a valid filter actually consuming its frame + // will cause the encoder to produce incorrect frames. + if (needFiltering) + { + // INPUT videotoolbox/memory surface(vram/uma) + // this will pass-through automatically if in/out format matches. + mainFilters.Insert(0, "format=nv12|p010le|videotoolbox_vld"); + mainFilters.Insert(0, "hwupload=derive_device=videotoolbox"); + } + return (mainFilters, subFilters, overlayFilters); } -- cgit v1.2.3 From 6b6aab04ceadfd43a6bd0abb416b08006ff1ca6c Mon Sep 17 00:00:00 2001 From: gnattu Date: Mon, 8 Apr 2024 21:42:47 +0800 Subject: Fix apple audio codecs (#11315) --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 10 +++++++++- MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 250e0143f..717b53a0b 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -107,7 +107,6 @@ namespace MediaBrowser.Controller.MediaEncoding { "wmav2", 2 }, { "libmp3lame", 2 }, { "libfdk_aac", 6 }, - { "aac_at", 6 }, { "ac3", 6 }, { "eac3", 6 }, { "dca", 6 }, @@ -752,6 +751,15 @@ namespace MediaBrowser.Controller.MediaEncoding return "dca"; } + if (string.Equals(codec, "alac", StringComparison.OrdinalIgnoreCase)) + { + // The ffmpeg upstream breaks the AudioToolbox ALAC encoder in version 6.1 but fixes it in version 7.0. + // Since ALAC is lossless in quality and the AudioToolbox encoder is not faster, + // its only benefit is a smaller file size. + // To prevent problems, use the ffmpeg native encoder instead. + return "alac"; + } + return codec.ToLowerInvariant(); } diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index ae0284e3a..5f0779dc7 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -69,6 +69,7 @@ namespace MediaBrowser.MediaEncoding.Encoder "aac_at", "libfdk_aac", "ac3", + "alac", "dca", "libmp3lame", "libopus", -- cgit v1.2.3 From 7d28d08e08a412ab88ede368220562799f2bd7c0 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Sat, 13 Apr 2024 01:45:01 +0200 Subject: Enable more warnings as errors (#11288) --- Emby.Server.Implementations/ApplicationHost.cs | 14 +++++++------- Emby.Server.Implementations/Data/BaseSqliteRepository.cs | 5 +---- Emby.Server.Implementations/Dto/DtoService.cs | 7 ++++--- Emby.Server.Implementations/Library/LibraryManager.cs | 2 +- Emby.Server.Implementations/Library/ResolverHelper.cs | 2 +- .../Library/Resolvers/Movies/BoxSetResolver.cs | 2 +- Emby.Server.Implementations/Session/SessionManager.cs | 5 +---- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 10 +++++----- jellyfin.ruleset | 6 ++++++ 9 files changed, 27 insertions(+), 26 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index acabbb059..6add7e0b3 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -109,13 +109,13 @@ namespace Emby.Server.Implementations /// /// The disposable parts. /// - private readonly ConcurrentDictionary _disposableParts = new(); + private readonly ConcurrentBag _disposableParts = new(); private readonly DeviceId _deviceId; private readonly IConfiguration _startupConfig; private readonly IXmlSerializer _xmlSerializer; private readonly IStartupOptions _startupOptions; - private readonly IPluginManager _pluginManager; + private readonly PluginManager _pluginManager; private List _creatingInstances; @@ -161,7 +161,7 @@ namespace Emby.Server.Implementations ApplicationPaths.PluginsPath, ApplicationVersion); - _disposableParts.TryAdd((PluginManager)_pluginManager, byte.MinValue); + _disposableParts.Add(_pluginManager); } /// @@ -360,7 +360,7 @@ namespace Emby.Server.Implementations { foreach (var part in parts.OfType()) { - _disposableParts.TryAdd(part, byte.MinValue); + _disposableParts.Add(part); } } @@ -381,7 +381,7 @@ namespace Emby.Server.Implementations { foreach (var part in parts.OfType()) { - _disposableParts.TryAdd(part, byte.MinValue); + _disposableParts.Add(part); } } @@ -457,7 +457,7 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(ConfigurationManager); serviceCollection.AddSingleton(ConfigurationManager); serviceCollection.AddSingleton(this); - serviceCollection.AddSingleton(_pluginManager); + serviceCollection.AddSingleton(_pluginManager); serviceCollection.AddSingleton(ApplicationPaths); serviceCollection.AddSingleton(); @@ -965,7 +965,7 @@ namespace Emby.Server.Implementations Logger.LogInformation("Disposing {Type}", type.Name); - foreach (var (part, _) in _disposableParts) + foreach (var part in _disposableParts.ToArray()) { var partType = part.GetType(); if (partType == type) diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs index bf079d90c..b1c99227c 100644 --- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs +++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs @@ -186,10 +186,7 @@ namespace Emby.Server.Implementations.Data protected void CheckDisposed() { - if (_disposed) - { - throw new ObjectDisposedException(GetType().Name, "Object has been disposed and cannot be accessed."); - } + ObjectDisposedException.ThrowIf(_disposed, this); } /// diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 5da9bea26..98eacb52b 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -668,12 +668,13 @@ namespace Emby.Server.Implementations.Dto { dto.ImageBlurHashes ??= new Dictionary>(); - if (!dto.ImageBlurHashes.ContainsKey(image.Type)) + if (!dto.ImageBlurHashes.TryGetValue(image.Type, out var value)) { - dto.ImageBlurHashes[image.Type] = new Dictionary(); + value = new Dictionary(); + dto.ImageBlurHashes[image.Type] = value; } - dto.ImageBlurHashes[image.Type][tag] = image.BlurHash; + value[tag] = image.BlurHash; } return tag; diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index dced6868d..bb5cc746e 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -461,7 +461,7 @@ namespace Emby.Server.Implementations.Library ReportItemRemoved(item, parent); } - private static IEnumerable GetMetadataPaths(BaseItem item, IEnumerable children) + private static List GetMetadataPaths(BaseItem item, IEnumerable children) { var list = new List { diff --git a/Emby.Server.Implementations/Library/ResolverHelper.cs b/Emby.Server.Implementations/Library/ResolverHelper.cs index 7a61e2607..52be76217 100644 --- a/Emby.Server.Implementations/Library/ResolverHelper.cs +++ b/Emby.Server.Implementations/Library/ResolverHelper.cs @@ -35,7 +35,7 @@ namespace Emby.Server.Implementations.Library item.Id = libraryManager.GetNewItemId(item.Path, item.GetType()); - item.IsLocked = item.Path.IndexOf("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) != -1 || + item.IsLocked = item.Path.Contains("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) || item.GetParents().Any(i => i.IsLocked); // Make sure DateCreated and DateModified have values diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs index 6cc04ea81..955055313 100644 --- a/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs +++ b/Emby.Server.Implementations/Library/Resolvers/Movies/BoxSetResolver.cs @@ -33,7 +33,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies return null; } - if (filename.IndexOf("[boxset]", StringComparison.OrdinalIgnoreCase) != -1 || args.ContainsFileSystemEntryByName("collection.xml")) + if (filename.Contains("[boxset]", StringComparison.OrdinalIgnoreCase) || args.ContainsFileSystemEntryByName("collection.xml")) { return new BoxSet { diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs index 75945b08a..06798628f 100644 --- a/Emby.Server.Implementations/Session/SessionManager.cs +++ b/Emby.Server.Implementations/Session/SessionManager.cs @@ -159,10 +159,7 @@ namespace Emby.Server.Implementations.Session private void CheckDisposed() { - if (_disposed) - { - throw new ObjectDisposedException(GetType().Name); - } + ObjectDisposedException.ThrowIf(_disposed, this); } private void OnSessionStarted(SessionInfo info) diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 717b53a0b..eb375c8a2 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1271,23 +1271,23 @@ namespace MediaBrowser.Controller.MediaEncoding { var codec = stream.Codec ?? string.Empty; - return codec.IndexOf("264", StringComparison.OrdinalIgnoreCase) != -1 - || codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1; + return codec.Contains("264", StringComparison.OrdinalIgnoreCase) + || codec.Contains("avc", StringComparison.OrdinalIgnoreCase); } public static bool IsH265(MediaStream stream) { var codec = stream.Codec ?? string.Empty; - return codec.IndexOf("265", StringComparison.OrdinalIgnoreCase) != -1 - || codec.IndexOf("hevc", StringComparison.OrdinalIgnoreCase) != -1; + return codec.Contains("265", StringComparison.OrdinalIgnoreCase) + || codec.Contains("hevc", StringComparison.OrdinalIgnoreCase); } public static bool IsAAC(MediaStream stream) { var codec = stream.Codec ?? string.Empty; - return codec.IndexOf("aac", StringComparison.OrdinalIgnoreCase) != -1; + return codec.Contains("aac", StringComparison.OrdinalIgnoreCase); } public static string GetBitStreamArgs(MediaStream stream) diff --git a/jellyfin.ruleset b/jellyfin.ruleset index 10225e3af..db116f46c 100644 --- a/jellyfin.ruleset +++ b/jellyfin.ruleset @@ -85,6 +85,8 @@ + + @@ -101,6 +103,8 @@ + + @@ -108,6 +112,8 @@ + + -- cgit v1.2.3 From 374b6ca0e22719df25b8d0aa51c510515b928104 Mon Sep 17 00:00:00 2001 From: gnattu Date: Tue, 23 Apr 2024 00:23:36 +0800 Subject: Only apply custom downmix to 5.1 audios (#11401) --- Jellyfin.Api/Controllers/DynamicHlsController.cs | 7 +++---- Jellyfin.Api/Helpers/StreamingHelpers.cs | 7 +++---- .../MediaEncoding/EncodingHelper.cs | 19 ++++++++++++++++--- 3 files changed, 22 insertions(+), 11 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 49fc2f3d7..24edd7958 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1712,12 +1712,11 @@ public class DynamicHlsController : BaseJellyfinApiController var channels = state.OutputAudioChannels; + var useDownMixAlgorithm = state.AudioStream.Channels is 6 && _encodingOptions.DownMixStereoAlgorithm != DownMixStereoAlgorithms.None; + if (channels.HasValue && (channels.Value != 2 - || (state.AudioStream is not null - && state.AudioStream.Channels.HasValue - && state.AudioStream.Channels.Value > 5 - && _encodingOptions.DownMixStereoAlgorithm == DownMixStereoAlgorithms.None))) + || (state.AudioStream?.Channels != null && !useDownMixAlgorithm))) { args += " -ac " + channels.Value; } diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index d6d7a56eb..6cd466da0 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -166,6 +166,9 @@ public static class StreamingHelpers } var outputAudioCodec = streamingRequest.AudioCodec; + state.OutputAudioCodec = outputAudioCodec; + state.OutputContainer = (containerInternal ?? string.Empty).TrimStart('.'); + state.OutputAudioChannels = encodingHelper.GetNumAudioChannelsParam(state, state.AudioStream, state.OutputAudioCodec); if (EncodingHelper.LosslessAudioCodecs.Contains(outputAudioCodec)) { state.OutputAudioBitrate = state.AudioStream.BitRate ?? 0; @@ -180,10 +183,6 @@ public static class StreamingHelpers containerInternal = ".pcm"; } - state.OutputAudioCodec = outputAudioCodec; - state.OutputContainer = (containerInternal ?? string.Empty).TrimStart('.'); - state.OutputAudioChannels = encodingHelper.GetNumAudioChannelsParam(state, state.AudioStream, state.OutputAudioCodec); - if (state.VideoRequest is not null) { state.OutputVideoCodec = state.Request.VideoCodec; diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index eb375c8a2..9c087f011 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2627,7 +2627,7 @@ namespace MediaBrowser.Controller.MediaEncoding && channels.Value == 2 && state.AudioStream is not null && state.AudioStream.Channels.HasValue - && state.AudioStream.Channels.Value > 5) + && state.AudioStream.Channels.Value == 6) { switch (encodingOptions.DownMixStereoAlgorithm) { @@ -2720,7 +2720,20 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.TranscodingType != TranscodingJobType.Progressive && ((resultChannels > 2 && resultChannels < 6) || resultChannels == 7)) { - resultChannels = 2; + // We can let FFMpeg supply an extra LFE channel for 5ch and 7ch to make them 5.1 and 7.1 + if (resultChannels == 5) + { + resultChannels = 6; + } + else if (resultChannels == 7) + { + resultChannels = 8; + } + else + { + // For other weird layout, just downmix to stereo for compatibility + resultChannels = 2; + } } } @@ -6903,7 +6916,7 @@ namespace MediaBrowser.Controller.MediaEncoding var channels = state.OutputAudioChannels; - if (channels.HasValue && ((channels.Value != 2 && state.AudioStream.Channels <= 5) || encodingOptions.DownMixStereoAlgorithm == DownMixStereoAlgorithms.None)) + if (channels.HasValue && ((channels.Value != 2 && state.AudioStream?.Channels != 6) || encodingOptions.DownMixStereoAlgorithm == DownMixStereoAlgorithms.None)) { args += " -ac " + channels.Value; } -- cgit v1.2.3 From 6f78ac2ff36b067ee3448fc5bf3030814b10e79b Mon Sep 17 00:00:00 2001 From: gnattu Date: Tue, 30 Apr 2024 19:41:46 +0800 Subject: Use more accurate rounding in GetFixedOutputSize (#11435) * Use more accurate rounding in GetFixedOutputSize Signed-off-by: gnattu * Force trickplay thumbnails to have even width Signed-off-by: gnattu * Use Convert.ToInt32 Signed-off-by: gnattu * Force video size as thumbnail size if the trickplay width setting is larger This will fix an issue when the trickplay setting contains a very huge width, but the video has a lower resolution than that setting. Our scaling filter logic will not do any upscale, and we have to force to use the video width Signed-off-by: gnattu --------- Signed-off-by: gnattu --- .../Trickplay/TrickplayManager.cs | 31 +++++++++++++++------- .../MediaEncoding/EncodingHelper.cs | 4 +-- 2 files changed, 23 insertions(+), 12 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs b/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs index fed5dab69..31c0be52f 100644 --- a/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs +++ b/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs @@ -106,18 +106,11 @@ public class TrickplayManager : ITrickplayManager } var imgTempDir = string.Empty; - var outputDir = GetTrickplayDirectory(video, width); using (await _resourcePool.LockAsync(cancellationToken).ConfigureAwait(false)) { try { - if (!replace && Directory.Exists(outputDir) && (await GetTrickplayResolutions(video.Id).ConfigureAwait(false)).ContainsKey(width)) - { - _logger.LogDebug("Found existing trickplay files for {ItemId}. Exiting.", video.Id); - return; - } - // Extract images // Note: Media sources under parent items exist as their own video/item as well. Only use this video stream for trickplay. var mediaSource = video.GetMediaSources(false).Find(source => Guid.Parse(source.Id).Equals(video.Id)); @@ -128,17 +121,35 @@ public class TrickplayManager : ITrickplayManager return; } + // The width has to be even, otherwise a lot of filters will not be able to sample it + var actualWidth = 2 * (width / 2); + + // Force using the video width when the trickplay setting has a too large width + if (mediaSource.VideoStream.Width is not null && mediaSource.VideoStream.Width < width) + { + _logger.LogWarning("Video width {VideoWidth} is smaller than trickplay setting {TrickPlayWidth}, using video width for thumbnails", mediaSource.VideoStream.Width, width); + actualWidth = 2 * ((int)mediaSource.VideoStream.Width / 2); + } + + var outputDir = GetTrickplayDirectory(video, actualWidth); + + if (!replace && Directory.Exists(outputDir) && (await GetTrickplayResolutions(video.Id).ConfigureAwait(false)).ContainsKey(actualWidth)) + { + _logger.LogDebug("Found existing trickplay files for {ItemId}. Exiting", video.Id); + return; + } + var mediaPath = mediaSource.Path; var mediaStream = mediaSource.VideoStream; var container = mediaSource.Container; - _logger.LogInformation("Creating trickplay files at {Width} width, for {Path} [ID: {ItemId}]", width, mediaPath, video.Id); + _logger.LogInformation("Creating trickplay files at {Width} width, for {Path} [ID: {ItemId}]", actualWidth, mediaPath, video.Id); imgTempDir = await _mediaEncoder.ExtractVideoImagesOnIntervalAccelerated( mediaPath, container, mediaSource, mediaStream, - width, + actualWidth, TimeSpan.FromMilliseconds(options.Interval), options.EnableHwAcceleration, options.EnableHwEncoding, @@ -159,7 +170,7 @@ public class TrickplayManager : ITrickplayManager .ToList(); // Create tiles - var trickplayInfo = CreateTiles(images, width, options, outputDir); + var trickplayInfo = CreateTiles(images, actualWidth, options, outputDir); // Save tiles info try diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 9c087f011..d6907fdf9 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2984,8 +2984,8 @@ namespace MediaBrowser.Controller.MediaEncoding var scaleW = (double)maximumWidth / outputWidth; var scaleH = (double)maximumHeight / outputHeight; var scale = Math.Min(scaleW, scaleH); - outputWidth = Math.Min(maximumWidth, (int)(outputWidth * scale)); - outputHeight = Math.Min(maximumHeight, (int)(outputHeight * scale)); + outputWidth = Math.Min(maximumWidth, Convert.ToInt32(outputWidth * scale)); + outputHeight = Math.Min(maximumHeight, Convert.ToInt32(outputHeight * scale)); } outputWidth = 2 * (outputWidth / 2); -- cgit v1.2.3 From 424ca49c266349563786a7f1dcfb5fbf7db4b48d Mon Sep 17 00:00:00 2001 From: gnattu Date: Fri, 17 May 2024 13:51:54 -0400 Subject: Backport pull request #11689 from jellyfin/release-10.9.z Workaround ffmpeg keyframe seeking for external subtitles Original-merge: 02937873b1f4e132a50d21fd2eca4c72160dd286 Merged-by: nielsvanvelzen Backported-by: Joshua M. Boniface --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index d6907fdf9..49c208b77 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2771,7 +2771,13 @@ namespace MediaBrowser.Controller.MediaEncoding if (time > 0) { - seekParam += string.Format(CultureInfo.InvariantCulture, "-ss {0}", _mediaEncoder.GetTimeParameter(time)); + // For direct streaming/remuxing, we seek at the exact position of the keyframe + // However, ffmpeg will seek to previous keyframe when the exact time is the input + // Workaround this by adding 0.5s offset to the seeking time to get the exact keyframe on most videos. + // This will help subtitle syncing. + var isHlsRemuxing = state.IsVideoRequest && state.TranscodingType is TranscodingJobType.Hls && IsCopyCodec(state.OutputVideoCodec); + var seekTick = isHlsRemuxing ? time + 5000000L : time; + seekParam += string.Format(CultureInfo.InvariantCulture, "-ss {0}", _mediaEncoder.GetTimeParameter(seekTick)); if (state.IsVideoRequest) { -- cgit v1.2.3 From 487ebd3ca88f1e0ca09c34838738aa2c555c69ab Mon Sep 17 00:00:00 2001 From: gnattu Date: Sat, 25 May 2024 11:46:08 -0400 Subject: Backport pull request #11713 from jellyfin/release-10.9.z Fix VideoToolbox H264 constrained profile option Original-merge: d608f1e3cc55f59ad5445384805d8cf43a98f8a7 Merged-by: Bond-009 Backported-by: Joshua M. Boniface --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 49c208b77..ed64156d6 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2083,6 +2083,18 @@ namespace MediaBrowser.Controller.MediaEncoding profile = "constrained_high"; } + if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase) + && profile.Contains("constrainedbaseline", StringComparison.OrdinalIgnoreCase)) + { + profile = "constrained_baseline"; + } + + if (string.Equals(videoEncoder, "h264_videotoolbox", StringComparison.OrdinalIgnoreCase) + && profile.Contains("constrainedhigh", StringComparison.OrdinalIgnoreCase)) + { + profile = "constrained_high"; + } + if (!string.IsNullOrEmpty(profile)) { // Currently there's no profile option in av1_nvenc encoder -- cgit v1.2.3 From debd9eb8ce3ee2731ee71f508b4260f654b12d02 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Sat, 25 May 2024 11:46:12 -0400 Subject: Backport pull request #11754 from jellyfin/release-10.9.z Fix BD/DVD folder chapter image extraction Original-merge: 52be8be28fa27c0c7b4f53dc32e00ec0543616a9 Merged-by: Bond-009 Backported-by: Joshua M. Boniface --- .../MediaEncoding/EncodingHelper.cs | 12 +----------- .../MediaEncoding/IMediaEncoder.cs | 15 +++++++++++++++ MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 19 ++++++++++++++++--- 3 files changed, 32 insertions(+), 14 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index ed64156d6..5d5b645f6 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -680,16 +680,6 @@ namespace MediaBrowser.Controller.MediaEncoding return -1; } - public string GetInputPathArgument(EncodingJobInfo state) - { - return state.MediaSource.VideoType switch - { - VideoType.Dvd => _mediaEncoder.GetInputArgument(_mediaEncoder.GetPrimaryPlaylistVobFiles(state.MediaPath, null).ToList(), state.MediaSource), - VideoType.BluRay => _mediaEncoder.GetInputArgument(_mediaEncoder.GetPrimaryPlaylistM2tsFiles(state.MediaPath).ToList(), state.MediaSource), - _ => _mediaEncoder.GetInputArgument(state.MediaPath, state.MediaSource) - }; - } - /// /// Gets the audio encoder. /// @@ -1203,7 +1193,7 @@ namespace MediaBrowser.Controller.MediaEncoding else { arg.Append(" -i ") - .Append(GetInputPathArgument(state)); + .Append(_mediaEncoder.GetInputPathArgument(state)); } // sub2video for external graphical subtitles diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index e696fa52c..26c353a54 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -245,6 +245,21 @@ namespace MediaBrowser.Controller.MediaEncoding /// A playlist. IReadOnlyList GetPrimaryPlaylistM2tsFiles(string path); + /// + /// Gets the input path argument from . + /// + /// The . + /// The input path argument. + string GetInputPathArgument(EncodingJobInfo state); + + /// + /// Gets the input path argument. + /// + /// The item path. + /// The . + /// The input path argument. + string GetInputPathArgument(string path, MediaSourceInfo mediaSource); + /// /// Generates a FFmpeg concat config for the source. /// diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 8ea0f58ea..1197fc84c 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -30,10 +30,8 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; -using Microsoft.AspNetCore.Components.Forms; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; -using static Nikse.SubtitleEdit.Core.Common.IfoParser; namespace MediaBrowser.MediaEncoding.Encoder { @@ -621,7 +619,7 @@ namespace MediaBrowser.MediaEncoding.Encoder ImageFormat? targetFormat, CancellationToken cancellationToken) { - var inputArgument = GetInputArgument(inputFile, mediaSource); + var inputArgument = GetInputPathArgument(inputFile, mediaSource); if (!isAudio) { @@ -1147,6 +1145,21 @@ namespace MediaBrowser.MediaEncoding.Encoder .ToList(); } + /// + public string GetInputPathArgument(EncodingJobInfo state) + => GetInputPathArgument(state.MediaPath, state.MediaSource); + + /// + public string GetInputPathArgument(string path, MediaSourceInfo mediaSource) + { + return mediaSource.VideoType switch + { + VideoType.Dvd => GetInputArgument(GetPrimaryPlaylistVobFiles(path, null).ToList(), mediaSource), + VideoType.BluRay => GetInputArgument(GetPrimaryPlaylistM2tsFiles(path).ToList(), mediaSource), + _ => GetInputArgument(path, mediaSource) + }; + } + /// public void GenerateConcatConfig(MediaSourceInfo source, string concatFilePath) { -- cgit v1.2.3 From c7e0be3c3b5d563e6c2093a6620fefd56f143b4e Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Sat, 25 May 2024 11:46:14 -0400 Subject: Backport pull request #11774 from jellyfin/release-10.9.z Apply audio boost when downmixing regardless of downmixalgo Original-merge: 06a5ddda5e6123dab6608f8907c8976a92e398e2 Merged-by: nielsvanvelzen Backported-by: Joshua M. Boniface --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 5d5b645f6..bef294429 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2631,10 +2631,14 @@ namespace MediaBrowser.Controller.MediaEncoding && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value == 6) { + if (!encodingOptions.DownMixAudioBoost.Equals(1)) + { + filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(CultureInfo.InvariantCulture)); + } + switch (encodingOptions.DownMixStereoAlgorithm) { case DownMixStereoAlgorithms.Dave750: - filters.Add("volume=4.25"); filters.Add("pan=stereo|c0=0.5*c2+0.707*c0+0.707*c4+0.5*c3|c1=0.5*c2+0.707*c1+0.707*c5+0.5*c3"); break; case DownMixStereoAlgorithms.NightmodeDialogue: @@ -2642,11 +2646,6 @@ namespace MediaBrowser.Controller.MediaEncoding break; case DownMixStereoAlgorithms.None: default: - if (!encodingOptions.DownMixAudioBoost.Equals(1)) - { - filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(CultureInfo.InvariantCulture)); - } - break; } } -- cgit v1.2.3 From ac0064110b78b5d2cc7d7bfffb1b18c6193cfaeb Mon Sep 17 00:00:00 2001 From: gnattu Date: Sat, 25 May 2024 11:46:22 -0400 Subject: Backport pull request #11798 from jellyfin/release-10.9.z Recalculate trickplay image height for anamorphic videos Original-merge: d9232e05f1280f8f4315ca5b8fc92ddc4a71a96a Merged-by: Bond-009 Backported-by: Joshua M. Boniface --- .../MediaEncoding/EncodingHelper.cs | 27 ++++++++++++++-------- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 16 +++++++++++++ 2 files changed, 33 insertions(+), 10 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index bef294429..90d448c58 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -3162,7 +3162,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) @@ -3191,10 +3193,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 @@ -3210,8 +3213,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 @@ -3221,9 +3225,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 @@ -3233,9 +3238,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 @@ -3245,9 +3251,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 79abb13ac..80ef6ecf7 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -822,6 +822,22 @@ namespace MediaBrowser.MediaEncoding.Encoder options.EnableTonemapping = false; } + 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(':'); + 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 = 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 * ha / wa); + } + } + var baseRequest = new BaseEncodingJobOptions { MaxWidth = maxWidth, MaxFramerate = (float)(1.0 / interval.TotalSeconds) }; var jobState = new EncodingJobInfo(TranscodingJobType.Progressive) { -- cgit v1.2.3 From 2faa8c141fd9b7d1980b8a468f0a8f01b46cbf2d Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Sat, 25 May 2024 11:46:23 -0400 Subject: Backport pull request #11799 from jellyfin/release-10.9.z Disable VA-VK interop on not supported kernel versions Original-merge: eb437e7163b5175cfe9f1123f7884546ef2e3037 Merged-by: Bond-009 Backported-by: Joshua M. Boniface --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 90d448c58..9d0638249 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -55,6 +55,7 @@ namespace MediaBrowser.Controller.MediaEncoding private readonly Version _minKerneli915Hang = new Version(5, 18); private readonly Version _maxKerneli915Hang = new Version(6, 1, 3); private readonly Version _minFixedKernel60i915Hang = new Version(6, 0, 18); + private readonly Version _minKernelVersionAmdVkFmtModifier = new Version(5, 15); private readonly Version _minFFmpegImplictHwaccel = new Version(6, 0); private readonly Version _minFFmpegHwaUnsafeOutput = new Version(6, 0); @@ -995,7 +996,8 @@ namespace MediaBrowser.Controller.MediaEncoding Environment.SetEnvironmentVariable("AMD_DEBUG", "noefc"); if (IsVulkanFullSupported() - && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop) + && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop + && Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier) { args.Append(GetDrmDeviceArgs(options.VaapiDevice, DrmAlias)); args.Append(GetVaapiDeviceArgs(null, null, null, DrmAlias, VaapiAlias)); @@ -4482,7 +4484,8 @@ namespace MediaBrowser.Controller.MediaEncoding // prefered vaapi + vulkan filters pipeline if (_mediaEncoder.IsVaapiDeviceAmd && isVaapiVkSupported - && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop) + && _mediaEncoder.IsVaapiDeviceSupportVulkanDrmInterop + && Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier) { // AMD radeonsi path(targeting Polaris/gfx8+), with extra vulkan tonemap and overlay support. return GetAmdVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder); -- cgit v1.2.3 From fc14c08bccecdc2b4839f7903ca07b2310b53df2 Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Sat, 25 May 2024 11:46:30 -0400 Subject: Backport pull request #11830 from jellyfin/release-10.9.z Fix the IOSurf error in QSV transcoding Original-merge: 5e7514243c71f68afe5e4403c0ee702f63b94b60 Merged-by: crobibero Backported-by: Joshua M. Boniface --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 2 ++ 1 file changed, 2 insertions(+) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 9d0638249..e4ec7a4e3 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -4301,6 +4301,7 @@ namespace MediaBrowser.Controller.MediaEncoding { // map from qsv to vaapi. mainFilters.Add("hwmap=derive_device=vaapi"); + mainFilters.Add("format=vaapi"); } var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12"); @@ -4310,6 +4311,7 @@ namespace MediaBrowser.Controller.MediaEncoding { // map from vaapi to qsv. mainFilters.Add("hwmap=derive_device=qsv"); + mainFilters.Add("format=qsv"); } } -- cgit v1.2.3 From b123f7ffcd639a5be1c0cb996c9e44829ca6afa9 Mon Sep 17 00:00:00 2001 From: gnattu Date: Sat, 1 Jun 2024 18:41:04 -0400 Subject: Backport pull request #11851 from jellyfin/release-10.9.z Relax remuxing requirement for LiveTV Original-merge: 0392daa10397c40d62e2068cd85e030d9ca2d898 Merged-by: crobibero Backported-by: Joshua M. Boniface --- Jellyfin.Api/Helpers/MediaInfoHelper.cs | 13 ------------- Jellyfin.Api/Helpers/StreamingHelpers.cs | 14 ++++++++++++++ MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 6 +++++- 3 files changed, 19 insertions(+), 14 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/Jellyfin.Api/Helpers/MediaInfoHelper.cs b/Jellyfin.Api/Helpers/MediaInfoHelper.cs index 5faa7bc59..212d678a8 100644 --- a/Jellyfin.Api/Helpers/MediaInfoHelper.cs +++ b/Jellyfin.Api/Helpers/MediaInfoHelper.cs @@ -385,19 +385,6 @@ public class MediaInfoHelper /// A containing the . public async Task OpenMediaSource(HttpContext httpContext, LiveStreamRequest request) { - // Enforce more restrictive transcoding profile for LiveTV due to compatability reasons - // Cap the MaxStreamingBitrate to 20Mbps, because we are unable to reliably probe source bitrate, - // which will cause the client to request extremely high bitrate that may fail the player/encoder - request.MaxStreamingBitrate = request.MaxStreamingBitrate > 20000000 ? 20000000 : request.MaxStreamingBitrate; - - if (request.DeviceProfile is not null) - { - // Remove all fmp4 transcoding profiles, because it causes playback error and/or A/V sync issues - // Notably: Some channels won't play on FireFox and LG webOs - // Some channels from HDHomerun will experience A/V sync issues - request.DeviceProfile.TranscodingProfiles = request.DeviceProfile.TranscodingProfiles.Where(p => !string.Equals(p.Container, "mp4", StringComparison.OrdinalIgnoreCase)).ToArray(); - } - var result = await _mediaSourceManager.OpenLiveStream(request, CancellationToken.None).ConfigureAwait(false); var profile = request.DeviceProfile; diff --git a/Jellyfin.Api/Helpers/StreamingHelpers.cs b/Jellyfin.Api/Helpers/StreamingHelpers.cs index 6cd466da0..af4a9e689 100644 --- a/Jellyfin.Api/Helpers/StreamingHelpers.cs +++ b/Jellyfin.Api/Helpers/StreamingHelpers.cs @@ -142,6 +142,20 @@ public static class StreamingHelpers } else { + // Enforce more restrictive transcoding profile for LiveTV due to compatability reasons + // Cap the MaxStreamingBitrate to 30Mbps, because we are unable to reliably probe source bitrate, + // which will cause the client to request extremely high bitrate that may fail the player/encoder + streamingRequest.VideoBitRate = streamingRequest.VideoBitRate > 30000000 ? 30000000 : streamingRequest.VideoBitRate; + + if (streamingRequest.SegmentContainer is not null) + { + // Remove all fmp4 transcoding profiles, because it causes playback error and/or A/V sync issues + // Notably: Some channels won't play on FireFox and LG webOS + // Some channels from HDHomerun will experience A/V sync issues + streamingRequest.SegmentContainer = "ts"; + streamingRequest.VideoCodec = "h264"; + } + var liveStreamInfo = await mediaSourceManager.GetLiveStreamWithDirectStreamProvider(streamingRequest.LiveStreamId, cancellationToken).ConfigureAwait(false); mediaSource = liveStreamInfo.Item1; state.DirectStreamProvider = liveStreamInfo.Item2; diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index e4ec7a4e3..6c2b5bf71 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2320,7 +2320,11 @@ namespace MediaBrowser.Controller.MediaEncoding if (request.VideoBitRate.HasValue && (!videoStream.BitRate.HasValue || videoStream.BitRate.Value > request.VideoBitRate.Value)) { - return false; + // For LiveTV that has no bitrate, let's try copy if other conditions are met + if (string.IsNullOrWhiteSpace(request.LiveStreamId) || videoStream.BitRate.HasValue) + { + return false; + } } var maxBitDepth = state.GetRequestedVideoBitDepth(videoStream.Codec); -- cgit v1.2.3 From 4fa3c30df2b64a14f8299dbf06043bc54df22a7d Mon Sep 17 00:00:00 2001 From: gnattu Date: Sat, 1 Jun 2024 18:41:14 -0400 Subject: Backport pull request #11894 from jellyfin/release-10.9.z Escape tmpConcatPath for DVD and BD folder Original-merge: 26419c64f52895c61e90f2fe5ab6fbd92a117a76 Merged-by: crobibero Backported-by: Joshua M. Boniface --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 6c2b5bf71..03c7faf47 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1189,8 +1189,9 @@ namespace MediaBrowser.Controller.MediaEncoding { var tmpConcatPath = Path.Join(_configurationManager.GetTranscodePath(), state.MediaSource.Id + ".concat"); _mediaEncoder.GenerateConcatConfig(state.MediaSource, tmpConcatPath); - arg.Append(" -f concat -safe 0 -i ") - .Append(tmpConcatPath); + arg.Append(" -f concat -safe 0 -i \"") + .Append(tmpConcatPath) + .Append("\" "); } else { -- cgit v1.2.3 From 9792fb117c38d6fd42a20a8aea3a4829e55febe9 Mon Sep 17 00:00:00 2001 From: gnattu Date: Tue, 18 Jun 2024 06:26:43 +0800 Subject: Enable H.264 Hi10P hardware decode for Apple Silicon on macOS >=14.6 macOS 14.6 beta and macOS 15 beta enables h264 10bit hardware decode on Apple Silicon. Signed-off-by: gnattu --- .../MediaEncoding/EncodingHelper.cs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index e4ec7a4e3..71670a963 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; using System.Threading; @@ -5704,16 +5705,29 @@ namespace MediaBrowser.Controller.MediaEncoding { var bitDepth = GetVideoColorBitDepth(state); - // Only HEVC, VP9 and AV1 formats have 10-bit hardware decoder support now. + // Only HEVC, VP9 and AV1 formats have 10-bit hardware decoder support for most platforms if (bitDepth == 10 && !(string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase) || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase) || string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase) || string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))) { - // One exception is that RKMPP decoder can handle H.264 High 10. - if (!(string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase) - && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))) + // RKMPP has H.264 Hi10P decoder + bool hasHardwareHi10P = string.Equals(options.HardwareAccelerationType, "rkmpp", StringComparison.OrdinalIgnoreCase); + + // VideoToolbox on Apple Silicon has H.264 Hi10P mode enabled after macOS 14.6 + if (string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase)) + { + var ver = Environment.OSVersion.Version; + var arch = RuntimeInformation.OSArchitecture; + if (arch.Equals(Architecture.Arm64) && ver >= new Version(14, 6)) + { + hasHardwareHi10P = true; + } + } + + if (!hasHardwareHi10P + && string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase)) { return null; } -- cgit v1.2.3 From d37c5d8921488c4d3602f7ed84e2dc0ae694b06c Mon Sep 17 00:00:00 2001 From: nyanmisaka Date: Sun, 23 Jun 2024 11:40:59 -0400 Subject: Backport pull request #12149 from jellyfin/release-10.9.z Fix MicroDVD being recognized as DVDSUB subtitles Original-merge: 6010bc01c321ea85935314191f5f8c19fd4b3bfb Merged-by: crobibero Backported-by: Joshua M. Boniface --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 4 ++-- .../Probing/ProbeResultNormalizer.cs | 14 +++++++++----- MediaBrowser.Model/Entities/MediaStream.cs | 16 ++++++++-------- 3 files changed, 19 insertions(+), 15 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index a845c8124..9d7d2fd12 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1209,8 +1209,8 @@ namespace MediaBrowser.Controller.MediaEncoding var subtitlePath = state.SubtitleStream.Path; var subtitleExtension = Path.GetExtension(subtitlePath.AsSpan()); - if (subtitleExtension.Equals(".sub", StringComparison.OrdinalIgnoreCase) - || subtitleExtension.Equals(".sup", StringComparison.OrdinalIgnoreCase)) + // dvdsub/vobsub graphical subtitles use .sub+.idx pairs + if (subtitleExtension.Equals(".sub", StringComparison.OrdinalIgnoreCase)) { var idxFile = Path.ChangeExtension(subtitlePath, ".idx"); if (File.Exists(idxFile)) diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 7d3f9327c..8b2685fe1 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -624,15 +624,19 @@ namespace MediaBrowser.MediaEncoding.Probing { if (string.Equals(codec, "dvb_subtitle", StringComparison.OrdinalIgnoreCase)) { - codec = "dvbsub"; + codec = "DVBSUB"; } - else if ((codec ?? string.Empty).Contains("PGS", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(codec, "dvb_teletext", StringComparison.OrdinalIgnoreCase)) { - codec = "PGSSUB"; + codec = "DVBTXT"; } - else if ((codec ?? string.Empty).Contains("DVD", StringComparison.OrdinalIgnoreCase)) + else if (string.Equals(codec, "dvd_subtitle", StringComparison.OrdinalIgnoreCase)) { - codec = "DVDSUB"; + codec = "DVDSUB"; // .sub+.idx + } + else if (string.Equals(codec, "hdmv_pgs_subtitle", StringComparison.OrdinalIgnoreCase)) + { + codec = "PGSSUB"; // .sup } return codec; diff --git a/MediaBrowser.Model/Entities/MediaStream.cs b/MediaBrowser.Model/Entities/MediaStream.cs index 3e01c6efc..e1082adea 100644 --- a/MediaBrowser.Model/Entities/MediaStream.cs +++ b/MediaBrowser.Model/Entities/MediaStream.cs @@ -656,14 +656,14 @@ namespace MediaBrowser.Model.Entities { string codec = format ?? string.Empty; - // sub = external .sub file - - return !codec.Contains("pgs", StringComparison.OrdinalIgnoreCase) - && !codec.Contains("dvd", StringComparison.OrdinalIgnoreCase) - && !codec.Contains("dvbsub", StringComparison.OrdinalIgnoreCase) - && !string.Equals(codec, "sub", StringComparison.OrdinalIgnoreCase) - && !string.Equals(codec, "sup", StringComparison.OrdinalIgnoreCase) - && !string.Equals(codec, "dvb_subtitle", StringComparison.OrdinalIgnoreCase); + // microdvd and dvdsub/vobsub share the ".sub" file extension, but it's text-based. + + return codec.Contains("microdvd", StringComparison.OrdinalIgnoreCase) + || (!codec.Contains("pgs", StringComparison.OrdinalIgnoreCase) + && !codec.Contains("dvdsub", StringComparison.OrdinalIgnoreCase) + && !codec.Contains("dvbsub", StringComparison.OrdinalIgnoreCase) + && !string.Equals(codec, "sup", StringComparison.OrdinalIgnoreCase) + && !string.Equals(codec, "sub", StringComparison.OrdinalIgnoreCase)); } public bool SupportsSubtitleConversionTo(string toCodec) -- cgit v1.2.3 From 016dc9d86c80c8b9542aff650302ab35d25c84fd Mon Sep 17 00:00:00 2001 From: gnattu Date: Tue, 2 Jul 2024 11:10:51 +0800 Subject: Add SIMD optimized software tonemap support Signed-off-by: gnattu --- .../MediaEncoding/EncodingHelper.cs | 38 ++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 9d7d2fd12..2dbd06da4 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -276,6 +276,21 @@ namespace MediaBrowser.Controller.MediaEncoding && _mediaEncoder.SupportsFilter("scale_vt"); } + private bool IsSwTonemapAvailable(EncodingJobInfo state, EncodingOptions options) + { + if (state.VideoStream is null + || !options.EnableTonemapping + || GetVideoColorBitDepth(state) != 10 + || !_mediaEncoder.SupportsFilter("tonemapx") + || !(string.Equals(state.VideoStream?.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase) || string.Equals(state.VideoStream?.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))) + { + return false; + } + + return state.VideoStream.VideoRange == VideoRange.HDR + && state.VideoStream.VideoRangeType is VideoRangeType.HDR10 or VideoRangeType.HLG or VideoRangeType.DOVIWithHDR10 or VideoRangeType.DOVIWithHLG; + } + private bool IsHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options) { if (state.VideoStream is null @@ -3517,6 +3532,7 @@ namespace MediaBrowser.Controller.MediaEncoding var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true); var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true); var doDeintH2645 = doDeintH264 || doDeintHevc; + var doToneMap = IsSwTonemapAvailable(state, options); var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode; var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream; @@ -3525,7 +3541,7 @@ namespace MediaBrowser.Controller.MediaEncoding /* Make main filters for video stream */ var mainFilters = new List(); - mainFilters.Add(GetOverwriteColorPropertiesParam(state, false)); + mainFilters.Add(GetOverwriteColorPropertiesParam(state, doToneMap)); // INPUT sw surface(memory/copy-back from vram) // sw deint @@ -3550,7 +3566,25 @@ namespace MediaBrowser.Controller.MediaEncoding mainFilters.Add(swScaleFilter); mainFilters.Add("format=" + outFormat); - // sw tonemap <= TODO: finsh the fast tonemap filter + // sw tonemap <= TODO: finish dovi tone mapping + + if (doToneMap) + { + var tonemapArgs = $"tonemapx=tonemap={options.TonemappingAlgorithm}:desat={options.TonemappingDesat}:peak={options.TonemappingPeak}:t=bt709:m=bt709:p=bt709:format=yuv420p"; + + if (options.TonemappingParam != 0) + { + tonemapArgs += $":param={options.TonemappingParam}"; + } + + if (string.Equals(options.TonemappingRange, "tv", StringComparison.OrdinalIgnoreCase) + || string.Equals(options.TonemappingRange, "pc", StringComparison.OrdinalIgnoreCase)) + { + tonemapArgs += $":range={options.TonemappingRange}"; + } + + mainFilters.Add(tonemapArgs); + } // OUTPUT yuv420p/nv12 surface(memory) -- cgit v1.2.3 From 2dee5b8d0426b51ad0c997e1b8a2d2cd20832245 Mon Sep 17 00:00:00 2001 From: gnattu Date: Tue, 2 Jul 2024 12:35:59 +0800 Subject: Correctly specify format for tonemap Signed-off-by: gnattu --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 2dbd06da4..0bb7100de 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -3564,13 +3564,12 @@ namespace MediaBrowser.Controller.MediaEncoding // sw scale mainFilters.Add(swScaleFilter); - mainFilters.Add("format=" + outFormat); // sw tonemap <= TODO: finish dovi tone mapping if (doToneMap) { - var tonemapArgs = $"tonemapx=tonemap={options.TonemappingAlgorithm}:desat={options.TonemappingDesat}:peak={options.TonemappingPeak}:t=bt709:m=bt709:p=bt709:format=yuv420p"; + var tonemapArgs = $"tonemapx=tonemap={options.TonemappingAlgorithm}:desat={options.TonemappingDesat}:peak={options.TonemappingPeak}:t=bt709:m=bt709:p=bt709:format={outFormat}"; if (options.TonemappingParam != 0) { @@ -3585,8 +3584,11 @@ namespace MediaBrowser.Controller.MediaEncoding mainFilters.Add(tonemapArgs); } - - // OUTPUT yuv420p/nv12 surface(memory) + else + { + // OUTPUT yuv420p/nv12 surface(memory) + mainFilters.Add("format=" + outFormat); + } /* Make sub and overlay filters for subtitle stream */ var subFilters = new List(); -- cgit v1.2.3 From c666f9d0501bbe0235315d2c2e668ec6f86eb345 Mon Sep 17 00:00:00 2001 From: Bond-009 Date: Mon, 15 Jul 2024 14:44:14 +0200 Subject: Use real temp dir instead of cache dir for temp files (#12226) --- Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs | 2 +- Emby.Server.Implementations/IO/ManagedFileSystem.cs | 2 +- .../Images/BaseDynamicImageProvider.cs | 1 + .../ScheduledTasks/Tasks/AudioNormalizationTask.cs | 12 +++++++----- .../Trickplay/TrickplayManager.cs | 2 +- Jellyfin.Server/Helpers/StartupHelpers.cs | 1 + MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 10 +++++++--- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 1 + MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs | 11 +---------- src/Jellyfin.Drawing.Skia/SkiaEncoder.cs | 2 +- 10 files changed, 22 insertions(+), 22 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs index 39524be1d..dc845b2d7 100644 --- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs +++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs @@ -104,6 +104,6 @@ namespace Emby.Server.Implementations.AppBase /// Gets the folder path to the temp directory within the cache folder. /// /// The temp directory. - public string TempDirectory => Path.Combine(CachePath, "temp"); + public string TempDirectory => Path.Join(Path.GetTempPath(), "jellyfin"); } } diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs index 250bec9ea..28bb29df8 100644 --- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs +++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs @@ -466,7 +466,7 @@ namespace Emby.Server.Implementations.IO File.Copy(file1, temp1, true); File.Copy(file2, file1, true); - File.Copy(temp1, file2, true); + File.Move(temp1, file2, true); } /// diff --git a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs index 0a3d740cc..82db7c46b 100644 --- a/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs +++ b/Emby.Server.Implementations/Images/BaseDynamicImageProvider.cs @@ -122,6 +122,7 @@ namespace Emby.Server.Implementations.Images } await ProviderManager.SaveImage(item, outputPath, mimeType, imageType, null, false, cancellationToken).ConfigureAwait(false); + File.Delete(outputPath); return ItemUpdateType.ImageUpdate; } diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs index df0fdcab8..301c04915 100644 --- a/Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs +++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs @@ -29,7 +29,7 @@ public partial class AudioNormalizationTask : IScheduledTask private readonly IItemRepository _itemRepository; private readonly ILibraryManager _libraryManager; private readonly IMediaEncoder _mediaEncoder; - private readonly IConfigurationManager _configurationManager; + private readonly IApplicationPaths _applicationPaths; private readonly ILocalizationManager _localization; private readonly ILogger _logger; @@ -39,21 +39,21 @@ public partial class AudioNormalizationTask : IScheduledTask /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. - /// Instance of the interface. + /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. public AudioNormalizationTask( IItemRepository itemRepository, ILibraryManager libraryManager, IMediaEncoder mediaEncoder, - IConfigurationManager configurationManager, + IApplicationPaths applicationPaths, ILocalizationManager localizationManager, ILogger logger) { _itemRepository = itemRepository; _libraryManager = libraryManager; _mediaEncoder = mediaEncoder; - _configurationManager = configurationManager; + _applicationPaths = applicationPaths; _localization = localizationManager; _logger = logger; } @@ -107,7 +107,9 @@ public partial class AudioNormalizationTask : IScheduledTask } _logger.LogInformation("Calculating LUFS for album: {Album} with id: {Id}", a.Name, a.Id); - var tempFile = Path.Join(_configurationManager.GetTranscodePath(), a.Id + ".concat"); + var tempDir = _applicationPaths.TempDirectory; + Directory.CreateDirectory(tempDir); + var tempFile = Path.Join(tempDir, a.Id + ".concat"); var inputLines = albumTracks.Select(x => string.Format(CultureInfo.InvariantCulture, "file '{0}'", x.Path.Replace("'", @"'\''", StringComparison.Ordinal))); await File.WriteAllLinesAsync(tempFile, inputLines, cancellationToken).ConfigureAwait(false); try diff --git a/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs b/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs index efdfc745f..c14be032e 100644 --- a/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs +++ b/Jellyfin.Server.Implementations/Trickplay/TrickplayManager.cs @@ -230,7 +230,7 @@ public class TrickplayManager : ITrickplayManager throw new ArgumentException("Can't create trickplay from 0 images."); } - var workDir = Path.Combine(_appPaths.TempDirectory, Guid.NewGuid().ToString("N")); + var workDir = Path.Combine(_appPaths.TempDirectory, "trickplay_" + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(workDir); var trickplayInfo = new TrickplayInfo diff --git a/Jellyfin.Server/Helpers/StartupHelpers.cs b/Jellyfin.Server/Helpers/StartupHelpers.cs index 5311a30e4..5518d6ba8 100644 --- a/Jellyfin.Server/Helpers/StartupHelpers.cs +++ b/Jellyfin.Server/Helpers/StartupHelpers.cs @@ -60,6 +60,7 @@ public static class StartupHelpers logger.LogInformation("Log directory path: {LogDirectoryPath}", appPaths.LogDirectoryPath); logger.LogInformation("Config directory path: {ConfigurationDirectoryPath}", appPaths.ConfigurationDirectoryPath); logger.LogInformation("Cache path: {CachePath}", appPaths.CachePath); + logger.LogInformation("Temp directory path: {TempDirPath}", appPaths.TempDirectory); logger.LogInformation("Web resources path: {WebPath}", appPaths.WebPath); logger.LogInformation("Application directory: {ApplicationPath}", appPaths.ProgramSystemPath); } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 0bb7100de..b175dc403 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -1203,10 +1203,14 @@ namespace MediaBrowser.Controller.MediaEncoding if (state.MediaSource.VideoType == VideoType.Dvd || state.MediaSource.VideoType == VideoType.BluRay) { - var tmpConcatPath = Path.Join(_configurationManager.GetTranscodePath(), state.MediaSource.Id + ".concat"); - _mediaEncoder.GenerateConcatConfig(state.MediaSource, tmpConcatPath); + var concatFilePath = Path.Join(_configurationManager.CommonApplicationPaths.CachePath, "concat", state.MediaSource.Id + ".concat"); + if (!File.Exists(concatFilePath)) + { + _mediaEncoder.GenerateConcatConfig(state.MediaSource, concatFilePath); + } + arg.Append(" -f concat -safe 0 -i \"") - .Append(tmpConcatPath) + .Append(concatFilePath) .Append("\" "); } else diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index e02f2116a..a7b881221 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -1203,6 +1203,7 @@ namespace MediaBrowser.MediaEncoding.Encoder } // Generate concat configuration entries for each file and write to file + Directory.CreateDirectory(Path.GetDirectoryName(concatFilePath)); using StreamWriter sw = new StreamWriter(concatFilePath); foreach (var path in files) { diff --git a/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs index ea5dbf7f7..0b09e57b5 100644 --- a/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs +++ b/MediaBrowser.MediaEncoding/Transcoding/TranscodeManager.cs @@ -235,15 +235,6 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable if (delete(job.Path!)) { await DeletePartialStreamFiles(job.Path!, job.Type, 0, 1500).ConfigureAwait(false); - if (job.MediaSource?.VideoType == VideoType.Dvd || job.MediaSource?.VideoType == VideoType.BluRay) - { - var concatFilePath = Path.Join(_serverConfigurationManager.GetTranscodePath(), job.MediaSource.Id + ".concat"); - if (File.Exists(concatFilePath)) - { - _logger.LogInformation("Deleting ffmpeg concat configuration at {Path}", concatFilePath); - File.Delete(concatFilePath); - } - } } if (closeLiveStream && !string.IsNullOrWhiteSpace(job.LiveStreamId)) @@ -419,7 +410,7 @@ public sealed class TranscodeManager : ITranscodeManager, IDisposable var attachmentPath = Path.Combine(_appPaths.CachePath, "attachments", state.MediaSource.Id); if (state.MediaSource.VideoType == VideoType.Dvd || state.MediaSource.VideoType == VideoType.BluRay) { - var concatPath = Path.Join(_serverConfigurationManager.GetTranscodePath(), state.MediaSource.Id + ".concat"); + var concatPath = Path.Join(_appPaths.CachePath, "concat", state.MediaSource.Id + ".concat"); await _attachmentExtractor.ExtractAllAttachments(concatPath, state.MediaSource, attachmentPath, cancellationTokenSource.Token).ConfigureAwait(false); } else diff --git a/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs b/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs index 75963226a..ede93aaa5 100644 --- a/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs +++ b/src/Jellyfin.Drawing.Skia/SkiaEncoder.cs @@ -219,7 +219,7 @@ public class SkiaEncoder : IImageEncoder return path; } - var tempPath = Path.Combine(_appPaths.TempDirectory, string.Concat(Guid.NewGuid().ToString(), Path.GetExtension(path.AsSpan()))); + var tempPath = Path.Join(_appPaths.TempDirectory, string.Concat("skia_", Guid.NewGuid().ToString(), Path.GetExtension(path.AsSpan()))); var directory = Path.GetDirectoryName(tempPath) ?? throw new ResourceNotFoundException($"Provided path ({tempPath}) is not valid."); Directory.CreateDirectory(directory); File.Copy(path, tempPath, true); -- cgit v1.2.3 From 78929418cc3aa783db3abaf99d15ba55e41cc2ac Mon Sep 17 00:00:00 2001 From: gnattu Date: Mon, 22 Apr 2024 21:02:20 +0800 Subject: Use better audio VBR settings LAME's VBR mode only has advantage over a certain bitrate range. For very low and very high bitrate, use the ABR mode instead. aac_at's CVBR mode produces very good quality and is not worse than its TVBR mode in blind testing. Use this mode for convenience. The ffmpeg native aac encoder will have quality regression with its VBR mode. Always use CBR mode for ffmpeg's native aac encoder. Signed-off-by: gnattu --- Jellyfin.Api/Controllers/DynamicHlsController.cs | 4 +-- .../MediaEncoding/EncodingHelper.cs | 33 +++++++++++++++------- 2 files changed, 25 insertions(+), 12 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 68602c80d..1e99a9ee3 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -1671,7 +1671,7 @@ public class DynamicHlsController : BaseJellyfinApiController if (audioBitrate.HasValue && !EncodingHelper.LosslessAudioCodecs.Contains(state.ActualOutputAudioCodec, StringComparison.OrdinalIgnoreCase)) { - var vbrParam = _encodingHelper.GetAudioVbrModeParam(audioCodec, audioBitrate.Value / (audioChannels ?? 2)); + var vbrParam = _encodingHelper.GetAudioVbrModeParam(audioCodec, audioBitrate.Value, audioChannels ?? 2); if (_encodingOptions.EnableAudioVbr && vbrParam is not null) { audioTranscodeParams += vbrParam; @@ -1724,7 +1724,7 @@ public class DynamicHlsController : BaseJellyfinApiController var bitrate = state.OutputAudioBitrate; if (bitrate.HasValue && !EncodingHelper.LosslessAudioCodecs.Contains(actualOutputAudioCodec, StringComparison.OrdinalIgnoreCase)) { - var vbrParam = _encodingHelper.GetAudioVbrModeParam(audioCodec, bitrate.Value / (channels ?? 2)); + var vbrParam = _encodingHelper.GetAudioVbrModeParam(audioCodec, bitrate.Value, channels ?? 2); if (_encodingOptions.EnableAudioVbr && vbrParam is not null) { args += vbrParam; diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 9d7d2fd12..a122f3d19 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2586,8 +2586,9 @@ namespace MediaBrowser.Controller.MediaEncoding return 128000 * (outputAudioChannels ?? audioStream.Channels ?? 2); } - public string GetAudioVbrModeParam(string encoder, int bitratePerChannel) + public string GetAudioVbrModeParam(string encoder, int bitrate, int channels) { + var bitratePerChannel = bitrate / channels; if (string.Equals(encoder, "libfdk_aac", StringComparison.OrdinalIgnoreCase)) { return " -vbr:a " + bitratePerChannel switch @@ -2602,14 +2603,26 @@ namespace MediaBrowser.Controller.MediaEncoding if (string.Equals(encoder, "libmp3lame", StringComparison.OrdinalIgnoreCase)) { - return " -qscale:a " + bitratePerChannel switch + // lame's VBR is only good for a certain bitrate range + // For very low and very high bitrate, use abr mode + if (bitratePerChannel is < 122500 and > 48000) { - < 48000 => "8", - < 64000 => "6", - < 88000 => "4", - < 112000 => "2", - _ => "0" - }; + return " -qscale:a " + bitratePerChannel switch + { + < 64000 => "6", + < 88000 => "4", + < 112000 => "2", + _ => "0" + }; + } + + return " -abr:a 1" + " -b:a " + bitrate; + } + + if (string.Equals(encoder, "aac_at", StringComparison.OrdinalIgnoreCase)) + { + // aac_at's CVBR mode + return " -aac_at_mode:a 2" + " -b:a " + bitrate; } if (string.Equals(encoder, "libvorbis", StringComparison.OrdinalIgnoreCase)) @@ -6962,7 +6975,7 @@ namespace MediaBrowser.Controller.MediaEncoding var bitrate = state.OutputAudioBitrate; if (bitrate.HasValue && !LosslessAudioCodecs.Contains(codec, StringComparison.OrdinalIgnoreCase)) { - var vbrParam = GetAudioVbrModeParam(codec, bitrate.Value / (channels ?? 2)); + var vbrParam = GetAudioVbrModeParam(codec, bitrate.Value, channels ?? 2); if (encodingOptions.EnableAudioVbr && vbrParam is not null) { args += vbrParam; @@ -6993,7 +7006,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (bitrate.HasValue && !LosslessAudioCodecs.Contains(outputCodec, StringComparison.OrdinalIgnoreCase)) { - var vbrParam = GetAudioVbrModeParam(GetAudioEncoder(state), bitrate.Value / (channels ?? 2)); + var vbrParam = GetAudioVbrModeParam(GetAudioEncoder(state), bitrate.Value, channels ?? 2); if (encodingOptions.EnableAudioVbr && vbrParam is not null) { audioTranscodeParams.Add(vbrParam); -- cgit v1.2.3 From 0381c5a288bc56e20aa5def05e3d41bacf3519a7 Mon Sep 17 00:00:00 2001 From: gnattu Date: Mon, 6 May 2024 12:48:50 +0800 Subject: Add EnableAudioVbrEncoding to TranscodingProfile This will allow the client selectively disable VBR audio when it causes problems. Signed-off-by: gnattu --- Jellyfin.Api/Controllers/AudioController.cs | 14 ++++-- Jellyfin.Api/Controllers/DynamicHlsController.cs | 53 +++++++++++++++------- Jellyfin.Api/Controllers/VideosController.cs | 14 ++++-- .../MediaEncoding/BaseEncodingJobOptions.cs | 2 + .../MediaEncoding/EncodingHelper.cs | 4 +- .../MediaEncoding/EncodingJobInfo.cs | 8 ++++ MediaBrowser.Model/Dlna/StreamBuilder.cs | 1 + MediaBrowser.Model/Dlna/StreamInfo.cs | 4 ++ MediaBrowser.Model/Dlna/TranscodingProfile.cs | 4 ++ 9 files changed, 78 insertions(+), 26 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/Jellyfin.Api/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs index 72be55513..06b7e6c9f 100644 --- a/Jellyfin.Api/Controllers/AudioController.cs +++ b/Jellyfin.Api/Controllers/AudioController.cs @@ -83,6 +83,7 @@ public class AudioController : BaseJellyfinApiController /// Optional. The index of the video stream to use. If omitted the first video stream will be used. /// Optional. The . /// Optional. The streaming options. + /// Optional. Whether to enable Audio Encoding. /// Audio stream returned. /// A containing the audio file. [HttpGet("{itemId}/stream", Name = "GetAudioStream")] @@ -138,7 +139,8 @@ public class AudioController : BaseJellyfinApiController [FromQuery] int? audioStreamIndex, [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, - [FromQuery] Dictionary? streamOptions) + [FromQuery] Dictionary? streamOptions, + [FromQuery] bool? enableAudioVbrEncoding) { StreamingRequestDto streamingRequest = new StreamingRequestDto { @@ -189,7 +191,8 @@ public class AudioController : BaseJellyfinApiController AudioStreamIndex = audioStreamIndex, VideoStreamIndex = videoStreamIndex, Context = context ?? EncodingContext.Static, - StreamOptions = streamOptions + StreamOptions = streamOptions, + EnableAudioVbrEncoding = enableAudioVbrEncoding ?? true }; return await _audioHelper.GetAudioStream(_transcodingJobType, streamingRequest).ConfigureAwait(false); @@ -247,6 +250,7 @@ public class AudioController : BaseJellyfinApiController /// Optional. The index of the video stream to use. If omitted the first video stream will be used. /// Optional. The . /// Optional. The streaming options. + /// Optional. Whether to enable Audio Encoding. /// Audio stream returned. /// A containing the audio file. [HttpGet("{itemId}/stream.{container}", Name = "GetAudioStreamByContainer")] @@ -302,7 +306,8 @@ public class AudioController : BaseJellyfinApiController [FromQuery] int? audioStreamIndex, [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, - [FromQuery] Dictionary? streamOptions) + [FromQuery] Dictionary? streamOptions, + [FromQuery] bool? enableAudioVbrEncoding) { StreamingRequestDto streamingRequest = new StreamingRequestDto { @@ -353,7 +358,8 @@ public class AudioController : BaseJellyfinApiController AudioStreamIndex = audioStreamIndex, VideoStreamIndex = videoStreamIndex, Context = context ?? EncodingContext.Static, - StreamOptions = streamOptions + StreamOptions = streamOptions, + EnableAudioVbrEncoding = enableAudioVbrEncoding ?? true }; return await _audioHelper.GetAudioStream(_transcodingJobType, streamingRequest).ConfigureAwait(false); diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 1e99a9ee3..a7fc9928e 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -156,6 +156,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// Optional. The max width. /// Optional. The max height. /// Optional. Whether to enable subtitles in the manifest. + /// Optional. Whether to enable Audio Encoding. /// Hls live stream retrieved. /// A containing the hls file. [HttpGet("Videos/{itemId}/live.m3u8")] @@ -213,7 +214,8 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] Dictionary streamOptions, [FromQuery] int? maxWidth, [FromQuery] int? maxHeight, - [FromQuery] bool? enableSubtitlesInManifest) + [FromQuery] bool? enableSubtitlesInManifest, + [FromQuery] bool? enableAudioVbrEncoding) { VideoRequestDto streamingRequest = new VideoRequestDto { @@ -267,7 +269,8 @@ public class DynamicHlsController : BaseJellyfinApiController StreamOptions = streamOptions, MaxHeight = maxHeight, MaxWidth = maxWidth, - EnableSubtitlesInManifest = enableSubtitlesInManifest ?? true + EnableSubtitlesInManifest = enableSubtitlesInManifest ?? true, + EnableAudioVbrEncoding = enableAudioVbrEncoding ?? true }; // CTS lifecycle is managed internally. @@ -393,6 +396,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// Optional. The streaming options. /// Enable adaptive bitrate streaming. /// Enable trickplay image playlists being added to master playlist. + /// Whether to enable Audio Encoding. /// Video stream returned. /// A containing the playlist file. [HttpGet("Videos/{itemId}/master.m3u8")] @@ -451,7 +455,8 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] EncodingContext? context, [FromQuery] Dictionary streamOptions, [FromQuery] bool enableAdaptiveBitrateStreaming = true, - [FromQuery] bool enableTrickplay = true) + [FromQuery] bool enableTrickplay = true, + [FromQuery] bool enableAudioVbrEncoding = true) { var streamingRequest = new HlsVideoRequestDto { @@ -505,7 +510,8 @@ public class DynamicHlsController : BaseJellyfinApiController Context = context ?? EncodingContext.Streaming, StreamOptions = streamOptions, EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming, - EnableTrickplay = enableTrickplay + EnableTrickplay = enableTrickplay, + EnableAudioVbrEncoding = enableAudioVbrEncoding }; return await _dynamicHlsHelper.GetMasterHlsPlaylist(TranscodingJobType, streamingRequest, enableAdaptiveBitrateStreaming).ConfigureAwait(false); @@ -564,6 +570,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// Optional. The . /// Optional. The streaming options. /// Enable adaptive bitrate streaming. + /// Optional. Whether to enable Audio Encoding. /// Audio stream returned. /// A containing the playlist file. [HttpGet("Audio/{itemId}/master.m3u8")] @@ -620,7 +627,8 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, [FromQuery] Dictionary streamOptions, - [FromQuery] bool enableAdaptiveBitrateStreaming = true) + [FromQuery] bool enableAdaptiveBitrateStreaming = true, + [FromQuery] bool enableAudioVbrEncoding = true) { var streamingRequest = new HlsAudioRequestDto { @@ -671,7 +679,8 @@ public class DynamicHlsController : BaseJellyfinApiController VideoStreamIndex = videoStreamIndex, Context = context ?? EncodingContext.Streaming, StreamOptions = streamOptions, - EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming + EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming, + EnableAudioVbrEncoding = enableAudioVbrEncoding }; return await _dynamicHlsHelper.GetMasterHlsPlaylist(TranscodingJobType, streamingRequest, enableAdaptiveBitrateStreaming).ConfigureAwait(false); @@ -730,6 +739,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// Optional. The index of the video stream to use. If omitted the first video stream will be used. /// Optional. The . /// Optional. The streaming options. + /// Optional. Whether to enable Audio Encoding. /// Video stream returned. /// A containing the audio file. [HttpGet("Videos/{itemId}/main.m3u8")] @@ -785,7 +795,8 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] int? audioStreamIndex, [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, - [FromQuery] Dictionary streamOptions) + [FromQuery] Dictionary streamOptions, + [FromQuery] bool? enableAudioVbrEncoding) { using var cancellationTokenSource = new CancellationTokenSource(); var streamingRequest = new VideoRequestDto @@ -838,7 +849,8 @@ public class DynamicHlsController : BaseJellyfinApiController AudioStreamIndex = audioStreamIndex, VideoStreamIndex = videoStreamIndex, Context = context ?? EncodingContext.Streaming, - StreamOptions = streamOptions + StreamOptions = streamOptions, + EnableAudioVbrEncoding = enableAudioVbrEncoding ?? true }; return await GetVariantPlaylistInternal(streamingRequest, cancellationTokenSource) @@ -897,6 +909,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// Optional. The index of the video stream to use. If omitted the first video stream will be used. /// Optional. The . /// Optional. The streaming options. + /// Optional. Whether to enable Audio Encoding. /// Audio stream returned. /// A containing the audio file. [HttpGet("Audio/{itemId}/main.m3u8")] @@ -951,7 +964,8 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] int? audioStreamIndex, [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, - [FromQuery] Dictionary streamOptions) + [FromQuery] Dictionary streamOptions, + [FromQuery] bool? enableAudioVbrEncoding) { using var cancellationTokenSource = new CancellationTokenSource(); var streamingRequest = new StreamingRequestDto @@ -1002,7 +1016,8 @@ public class DynamicHlsController : BaseJellyfinApiController AudioStreamIndex = audioStreamIndex, VideoStreamIndex = videoStreamIndex, Context = context ?? EncodingContext.Streaming, - StreamOptions = streamOptions + StreamOptions = streamOptions, + EnableAudioVbrEncoding = enableAudioVbrEncoding ?? true }; return await GetVariantPlaylistInternal(streamingRequest, cancellationTokenSource) @@ -1067,6 +1082,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// Optional. The index of the video stream to use. If omitted the first video stream will be used. /// Optional. The . /// Optional. The streaming options. + /// Optional. Whether to enable Audio Encoding. /// Video stream returned. /// A containing the audio file. [HttpGet("Videos/{itemId}/hls1/{playlistId}/{segmentId}.{container}")] @@ -1128,7 +1144,8 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] int? audioStreamIndex, [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, - [FromQuery] Dictionary streamOptions) + [FromQuery] Dictionary streamOptions, + [FromQuery] bool? enableAudioVbrEncoding) { var streamingRequest = new VideoRequestDto { @@ -1183,7 +1200,8 @@ public class DynamicHlsController : BaseJellyfinApiController AudioStreamIndex = audioStreamIndex, VideoStreamIndex = videoStreamIndex, Context = context ?? EncodingContext.Streaming, - StreamOptions = streamOptions + StreamOptions = streamOptions, + EnableAudioVbrEncoding = enableAudioVbrEncoding ?? true }; return await GetDynamicSegment(streamingRequest, segmentId) @@ -1247,6 +1265,7 @@ public class DynamicHlsController : BaseJellyfinApiController /// Optional. The index of the video stream to use. If omitted the first video stream will be used. /// Optional. The . /// Optional. The streaming options. + /// Optional. Whether to enable Audio Encoding. /// Video stream returned. /// A containing the audio file. [HttpGet("Audio/{itemId}/hls1/{playlistId}/{segmentId}.{container}")] @@ -1307,7 +1326,8 @@ public class DynamicHlsController : BaseJellyfinApiController [FromQuery] int? audioStreamIndex, [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, - [FromQuery] Dictionary streamOptions) + [FromQuery] Dictionary streamOptions, + [FromQuery] bool? enableAudioVbrEncoding) { var streamingRequest = new StreamingRequestDto { @@ -1360,7 +1380,8 @@ public class DynamicHlsController : BaseJellyfinApiController AudioStreamIndex = audioStreamIndex, VideoStreamIndex = videoStreamIndex, Context = context ?? EncodingContext.Streaming, - StreamOptions = streamOptions + StreamOptions = streamOptions, + EnableAudioVbrEncoding = enableAudioVbrEncoding ?? true }; return await GetDynamicSegment(streamingRequest, segmentId) @@ -1672,7 +1693,7 @@ public class DynamicHlsController : BaseJellyfinApiController if (audioBitrate.HasValue && !EncodingHelper.LosslessAudioCodecs.Contains(state.ActualOutputAudioCodec, StringComparison.OrdinalIgnoreCase)) { var vbrParam = _encodingHelper.GetAudioVbrModeParam(audioCodec, audioBitrate.Value, audioChannels ?? 2); - if (_encodingOptions.EnableAudioVbr && vbrParam is not null) + if (_encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null) { audioTranscodeParams += vbrParam; } @@ -1725,7 +1746,7 @@ public class DynamicHlsController : BaseJellyfinApiController if (bitrate.HasValue && !EncodingHelper.LosslessAudioCodecs.Contains(actualOutputAudioCodec, StringComparison.OrdinalIgnoreCase)) { var vbrParam = _encodingHelper.GetAudioVbrModeParam(audioCodec, bitrate.Value, channels ?? 2); - if (_encodingOptions.EnableAudioVbr && vbrParam is not null) + if (_encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null) { args += vbrParam; } diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs index a9e1d4484..f6050bdf7 100644 --- a/Jellyfin.Api/Controllers/VideosController.cs +++ b/Jellyfin.Api/Controllers/VideosController.cs @@ -306,6 +306,7 @@ public class VideosController : BaseJellyfinApiController /// Optional. The index of the video stream to use. If omitted the first video stream will be used. /// Optional. The . /// Optional. The streaming options. + /// Optional. Whether to enable Audio Encoding. /// Video stream returned. /// A containing the audio file. [HttpGet("{itemId}/stream")] @@ -363,7 +364,8 @@ public class VideosController : BaseJellyfinApiController [FromQuery] int? audioStreamIndex, [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, - [FromQuery] Dictionary streamOptions) + [FromQuery] Dictionary streamOptions, + [FromQuery] bool? enableAudioVbrEncoding) { var isHeadRequest = Request.Method == System.Net.WebRequestMethods.Http.Head; // CTS lifecycle is managed internally. @@ -419,7 +421,8 @@ public class VideosController : BaseJellyfinApiController AudioStreamIndex = audioStreamIndex, VideoStreamIndex = videoStreamIndex, Context = context ?? EncodingContext.Streaming, - StreamOptions = streamOptions + StreamOptions = streamOptions, + EnableAudioVbrEncoding = enableAudioVbrEncoding ?? true }; var state = await StreamingHelpers.GetStreamingState( @@ -544,6 +547,7 @@ public class VideosController : BaseJellyfinApiController /// Optional. The index of the video stream to use. If omitted the first video stream will be used. /// Optional. The . /// Optional. The streaming options. + /// Optional. Whether to enable Audio Encoding. /// Video stream returned. /// A containing the audio file. [HttpGet("{itemId}/stream.{container}")] @@ -601,7 +605,8 @@ public class VideosController : BaseJellyfinApiController [FromQuery] int? audioStreamIndex, [FromQuery] int? videoStreamIndex, [FromQuery] EncodingContext? context, - [FromQuery] Dictionary streamOptions) + [FromQuery] Dictionary streamOptions, + [FromQuery] bool? enableAudioVbrEncoding) { return GetVideoStream( itemId, @@ -654,6 +659,7 @@ public class VideosController : BaseJellyfinApiController audioStreamIndex, videoStreamIndex, context, - streamOptions); + streamOptions, + enableAudioVbrEncoding); } } diff --git a/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs index 29dd190ab..03ec6c658 100644 --- a/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs +++ b/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs @@ -191,6 +191,8 @@ namespace MediaBrowser.Controller.MediaEncoding public Dictionary StreamOptions { get; set; } + public bool EnableAudioVbrEncoding { get; set; } + public string GetOption(string qualifier, string name) { var value = GetOption(qualifier + "-" + name); diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index a122f3d19..ce2414cd1 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -6976,7 +6976,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (bitrate.HasValue && !LosslessAudioCodecs.Contains(codec, StringComparison.OrdinalIgnoreCase)) { var vbrParam = GetAudioVbrModeParam(codec, bitrate.Value, channels ?? 2); - if (encodingOptions.EnableAudioVbr && vbrParam is not null) + if (encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null) { args += vbrParam; } @@ -7007,7 +7007,7 @@ namespace MediaBrowser.Controller.MediaEncoding if (bitrate.HasValue && !LosslessAudioCodecs.Contains(outputCodec, StringComparison.OrdinalIgnoreCase)) { var vbrParam = GetAudioVbrModeParam(GetAudioEncoder(state), bitrate.Value, channels ?? 2); - if (encodingOptions.EnableAudioVbr && vbrParam is not null) + if (encodingOptions.EnableAudioVbr && state.EnableAudioVbrEncoding && vbrParam is not null) { audioTranscodeParams.Add(vbrParam); } diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index f2a0b906d..526aa8f99 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -508,6 +508,14 @@ namespace MediaBrowser.Controller.MediaEncoding } } + public bool EnableAudioVbrEncoding + { + get + { + return BaseRequest.EnableAudioVbrEncoding; + } + } + public int HlsListSize => 0; public bool EnableBreakOnNonKeyFrames(string videoCodec) diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 55d1c3d51..ba958c030 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -542,6 +542,7 @@ namespace MediaBrowser.Model.Dlna playlistItem.EnableMpegtsM2TsMode = transcodingProfile.EnableMpegtsM2TsMode; playlistItem.BreakOnNonKeyFrames = transcodingProfile.BreakOnNonKeyFrames; + playlistItem.EnableAudioVbrEncoding = transcodingProfile.EnableAudioVbrEncoding; if (transcodingProfile.MinSegments > 0) { diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 75e5b6d18..c8a341d41 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -108,6 +108,8 @@ namespace MediaBrowser.Model.Dlna public string? MediaSourceId => MediaSource?.Id; + public bool EnableAudioVbrEncoding { get; set; } + public bool IsDirectStream => MediaSource?.VideoType is not (VideoType.Dvd or VideoType.BluRay) && PlayMethod is PlayMethod.DirectStream or PlayMethod.DirectPlay; @@ -768,6 +770,8 @@ namespace MediaBrowser.Model.Dlna } list.Add(new NameValuePair("RequireAvc", item.RequireAvc.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); + + list.Add(new NameValuePair("EnableAudioVbrEncoding", item.EnableAudioVbrEncoding.ToString(CultureInfo.InvariantCulture).ToLowerInvariant())); } list.Add(new NameValuePair("Tag", item.MediaSource?.ETag ?? string.Empty)); diff --git a/MediaBrowser.Model/Dlna/TranscodingProfile.cs b/MediaBrowser.Model/Dlna/TranscodingProfile.cs index 891448c66..d535e8c18 100644 --- a/MediaBrowser.Model/Dlna/TranscodingProfile.cs +++ b/MediaBrowser.Model/Dlna/TranscodingProfile.cs @@ -70,6 +70,10 @@ namespace MediaBrowser.Model.Dlna public ProfileCondition[] Conditions { get; set; } + [DefaultValue(true)] + [XmlAttribute("enableAudioVbrEncoding")] + public bool EnableAudioVbrEncoding { get; set; } + public string[] GetAudioCodecs() { return ContainerProfile.SplitValue(AudioCodec); -- cgit v1.2.3 From 24910348a1648f9ec05940e3bec137bffa4814a8 Mon Sep 17 00:00:00 2001 From: gnattu Date: Wed, 17 Jul 2024 21:09:30 +0800 Subject: Force channel to be >= 1 Co-authored-by: Cody Robibero --- MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index ce2414cd1..2c35f0a1e 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -2588,7 +2588,7 @@ namespace MediaBrowser.Controller.MediaEncoding public string GetAudioVbrModeParam(string encoder, int bitrate, int channels) { - var bitratePerChannel = bitrate / channels; + var bitratePerChannel = bitrate / Math.Max(channels, 1); if (string.Equals(encoder, "libfdk_aac", StringComparison.OrdinalIgnoreCase)) { return " -vbr:a " + bitratePerChannel switch -- cgit v1.2.3 From 5262439300884680b1425b94de9c2f9c898d83ae Mon Sep 17 00:00:00 2001 From: gnattu Date: Thu, 18 Jul 2024 01:50:32 +0800 Subject: Enable hardware Trickplay processing pipeline for VideoToolbox (#11510) --- .../MediaEncoding/EncodingHelper.cs | 3 ++- .../Encoder/EncoderValidator.cs | 31 ++++++++++++++++++++++ MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 19 ++++++++++++- 3 files changed, 51 insertions(+), 2 deletions(-) (limited to 'MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index b175dc403..98351377e 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -120,7 +120,8 @@ namespace MediaBrowser.Controller.MediaEncoding private static readonly Dictionary _mjpegCodecMap = new(StringComparer.OrdinalIgnoreCase) { { "vaapi", _defaultMjpegEncoder + "_vaapi" }, - { "qsv", _defaultMjpegEncoder + "_qsv" } + { "qsv", _defaultMjpegEncoder + "_qsv" }, + { "videotoolbox", _defaultMjpegEncoder + "_videotoolbox" } }; public static readonly string[] LosslessAudioCodecs = new string[] diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs index 06e2d3783..a865b0e4c 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs @@ -95,6 +95,7 @@ namespace MediaBrowser.MediaEncoding.Encoder "h264_v4l2m2m", "h264_videotoolbox", "hevc_videotoolbox", + "mjpeg_videotoolbox", "h264_rkmpp", "hevc_rkmpp" }; @@ -500,6 +501,11 @@ namespace MediaBrowser.MediaEncoding.Encoder return output.Contains(keyDesc, StringComparison.Ordinal); } + public bool CheckSupportedHwaccelFlag(string flag) + { + return !string.IsNullOrEmpty(flag) && GetProcessExitCode(_encoderPath, $"-loglevel quiet -hwaccel_flags +{flag} -hide_banner -f lavfi -i nullsrc=s=1x1:d=100 -f null -"); + } + private IEnumerable GetCodecs(Codec codec) { string codecstr = codec == Codec.Encoder ? "encoders" : "decoders"; @@ -605,6 +611,31 @@ namespace MediaBrowser.MediaEncoding.Encoder } } + private bool GetProcessExitCode(string path, string arguments) + { + using var process = new Process(); + process.StartInfo = new ProcessStartInfo(path, arguments) + { + CreateNoWindow = true, + UseShellExecute = false, + WindowStyle = ProcessWindowStyle.Hidden, + ErrorDialog = false + }; + _logger.LogDebug("Running {Path} {Arguments}", path, arguments); + + try + { + process.Start(); + process.WaitForExit(); + return process.ExitCode == 0; + } + catch (Exception ex) + { + _logger.LogError("Running {Path} {Arguments} failed with exception {Exception}", path, arguments, ex.Message); + return false; + } + } + [GeneratedRegex("^\\s\\S{6}\\s(?[\\w|-]+)\\s+.+$", RegexOptions.Multiline)] private static partial Regex CodecRegex(); diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index de1b65482..e7e48d6d8 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -74,6 +74,7 @@ namespace MediaBrowser.MediaEncoding.Encoder private IDictionary _filtersWithOption = new Dictionary(); private bool _isPkeyPauseSupported = false; + private bool _isLowPriorityHwDecodeSupported = false; private bool _isVaapiDeviceAmd = false; private bool _isVaapiDeviceInteliHD = false; @@ -194,6 +195,7 @@ namespace MediaBrowser.MediaEncoding.Encoder _threads = EncodingHelper.GetNumberOfThreads(null, options, null); _isPkeyPauseSupported = validator.CheckSupportedRuntimeKey("p pause transcoding"); + _isLowPriorityHwDecodeSupported = validator.CheckSupportedHwaccelFlag("low_priority"); // Check the Vaapi device vendor if (OperatingSystem.IsLinux() @@ -884,6 +886,12 @@ namespace MediaBrowser.MediaEncoding.Encoder inputArg = "-threads " + threads + " " + inputArg; // HW accel args set a different input thread count, only set if disabled } + if (options.HardwareAccelerationType.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase) && _isLowPriorityHwDecodeSupported) + { + // VideoToolbox supports low priority decoding, which is useful for trickplay + inputArg = "-hwaccel_flags +low_priority " + inputArg; + } + if (enableKeyFrameOnlyExtraction) { inputArg = "-skip_frame nokey " + inputArg; @@ -921,6 +929,14 @@ namespace MediaBrowser.MediaEncoding.Encoder encoderQuality = (100 - ((qualityScale - 1) * (100 / 30))) / 118; } + if (vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase)) + { + // videotoolbox's mjpeg encoder uses jpeg quality scaled to QP2LAMBDA (118) instead of ffmpeg defined qscale + // ffmpeg qscale is a value from 1-31, with 1 being best quality and 31 being worst + // jpeg quality is a value from 0-100, with 0 being worst quality and 100 being best + encoderQuality = 118 - ((qualityScale - 1) * (118 / 30)); + } + // Output arguments var targetDirectory = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid().ToString("N")); Directory.CreateDirectory(targetDirectory); @@ -929,12 +945,13 @@ namespace MediaBrowser.MediaEncoding.Encoder // Final command arguments var args = string.Format( CultureInfo.InvariantCulture, - "-loglevel error {0} -an -sn {1} -threads {2} -c:v {3} {4}-f {5} \"{6}\"", + "-loglevel error {0} -an -sn {1} -threads {2} -c:v {3} {4}{5}-f {6} \"{7}\"", inputArg, filterParam, outputThreads.GetValueOrDefault(_threads), vidEncoder, qualityScale.HasValue ? "-qscale:v " + encoderQuality.Value.ToString(CultureInfo.InvariantCulture) + " " : string.Empty, + vidEncoder.Contains("videotoolbox", StringComparison.InvariantCultureIgnoreCase) ? "-allow_sw 1 " : string.Empty, // allow_sw fallback for some intel macs "image2", outputPath); -- cgit v1.2.3