diff --git a/src/Jellyfin.MediaEncoding.Hls/Playlist/DynamicHlsPlaylistGenerator.cs b/src/Jellyfin.MediaEncoding.Hls/Playlist/DynamicHlsPlaylistGenerator.cs index 13204c7578..7abb3c3316 100644 --- a/src/Jellyfin.MediaEncoding.Hls/Playlist/DynamicHlsPlaylistGenerator.cs +++ b/src/Jellyfin.MediaEncoding.Hls/Playlist/DynamicHlsPlaylistGenerator.cs @@ -49,13 +49,12 @@ namespace Jellyfin.MediaEncoding.Hls.Playlist /// public string CreateMainPlaylist(CreateMainPlaylistRequest request) { - IReadOnlyList segments = Array.Empty(); - if (IsExtractionAllowedForFile(request.FilePath)) + IReadOnlyList segments; + if (TryExtractKeyframes(request.FilePath, out var keyframeData)) { - segments = ComputeSegments(request.FilePath, request.DesiredSegmentLengthMs); + segments = ComputeSegments(keyframeData, request.DesiredSegmentLengthMs); } - - if (segments.Count == 0) + else { segments = ComputeEqualLengthSegments(request.DesiredSegmentLengthMs, request.TotalRuntimeTicks); } @@ -115,9 +114,14 @@ namespace Jellyfin.MediaEncoding.Hls.Playlist return builder.ToString(); } - private IReadOnlyList ComputeSegments(string filePath, int desiredSegmentLengthMs) + private bool TryExtractKeyframes(string filePath, [NotNullWhen(true)] out KeyframeData? keyframeData) { - KeyframeData keyframeData; + keyframeData = null; + if (!IsExtractionAllowedForFile(filePath, _serverConfigurationManager.GetEncodingOptions().AllowAutomaticKeyframeExtractionForExtensions)) + { + return false; + } + var cachePath = GetCachePath(filePath); if (TryReadFromCache(cachePath, out var cachedResult)) { @@ -132,31 +136,13 @@ namespace Jellyfin.MediaEncoding.Hls.Playlist catch (Exception ex) { _logger.LogError(ex, "Keyframe extraction failed for path {FilePath}", filePath); - return Array.Empty(); + return false; } CacheResult(cachePath, keyframeData); } - long lastKeyframe = 0; - var result = new List(); - // Scale the segment length to ticks to match the keyframes - var desiredSegmentLengthTicks = TimeSpan.FromMilliseconds(desiredSegmentLengthMs).Ticks; - var desiredCutTime = desiredSegmentLengthTicks; - for (var j = 0; j < keyframeData.KeyframeTicks.Count; j++) - { - var keyframe = keyframeData.KeyframeTicks[j]; - if (keyframe >= desiredCutTime) - { - var currentSegmentLength = keyframe - lastKeyframe; - result.Add(TimeSpan.FromTicks(currentSegmentLength).TotalSeconds); - lastKeyframe = keyframe; - desiredCutTime += desiredSegmentLengthTicks; - } - } - - result.Add(TimeSpan.FromTicks(keyframeData.TotalDuration - lastKeyframe).TotalSeconds); - return result; + return keyframeData.KeyframeTicks.Count > 0; } private void CacheResult(string cachePath, KeyframeData keyframeData) @@ -188,11 +174,10 @@ namespace Jellyfin.MediaEncoding.Hls.Playlist return false; } - private bool IsExtractionAllowedForFile(ReadOnlySpan filePath) + internal static bool IsExtractionAllowedForFile(ReadOnlySpan filePath, string[] allowedExtensions) { // Remove the leading dot var extension = Path.GetExtension(filePath)[1..]; - var allowedExtensions = _serverConfigurationManager.GetEncodingOptions().AllowAutomaticKeyframeExtractionForExtensions; for (var i = 0; i < allowedExtensions.Length; i++) { var allowedExtension = allowedExtensions[i]; @@ -205,7 +190,30 @@ namespace Jellyfin.MediaEncoding.Hls.Playlist return false; } - private static double[] ComputeEqualLengthSegments(long desiredSegmentLengthMs, long totalRuntimeTicks) + internal static IReadOnlyList ComputeSegments(KeyframeData keyframeData, int desiredSegmentLengthMs) + { + long lastKeyframe = 0; + var result = new List(); + // Scale the segment length to ticks to match the keyframes + var desiredSegmentLengthTicks = TimeSpan.FromMilliseconds(desiredSegmentLengthMs).Ticks; + var desiredCutTime = desiredSegmentLengthTicks; + for (var j = 0; j < keyframeData.KeyframeTicks.Count; j++) + { + var keyframe = keyframeData.KeyframeTicks[j]; + if (keyframe >= desiredCutTime) + { + var currentSegmentLength = keyframe - lastKeyframe; + result.Add(TimeSpan.FromTicks(currentSegmentLength).TotalSeconds); + lastKeyframe = keyframe; + desiredCutTime += desiredSegmentLengthTicks; + } + } + + result.Add(TimeSpan.FromTicks(keyframeData.TotalDuration - lastKeyframe).TotalSeconds); + return result; + } + + internal static double[] ComputeEqualLengthSegments(long desiredSegmentLengthMs, long totalRuntimeTicks) { var segmentLengthTicks = TimeSpan.FromMilliseconds(desiredSegmentLengthMs).Ticks; var wholeSegments = totalRuntimeTicks / segmentLengthTicks;