cvium %!s(int64=3) %!d(string=hai) anos
pai
achega
be233b49b6

+ 4 - 8
Jellyfin.sln

@@ -223,10 +223,6 @@ Global
 		{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.Build.0 = Release|Any CPU
-		{25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{25E40B0B-7C89-4230-8911-CBBBCE83FC5B}.Release|Any CPU.Build.0 = Release|Any CPU
 		{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -249,12 +245,12 @@ Global
 		{332A5C7A-F907-47CA-910E-BE6F7371B9E0}.Release|Any CPU.Build.0 = Release|Any CPU
 		{06535CA1-4097-4360-85EB-5FB875D53239}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{06535CA1-4097-4360-85EB-5FB875D53239}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{06535CA1-4097-4360-85EB-5FB875D53239}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{06535CA1-4097-4360-85EB-5FB875D53239}.Release|Any CPU.Build.0 = Release|Any CPU
+		{06535CA1-4097-4360-85EB-5FB875D53239}.Release|Any CPU.ActiveCfg = Debug|Any CPU
+		{06535CA1-4097-4360-85EB-5FB875D53239}.Release|Any CPU.Build.0 = Debug|Any CPU
 		{DA9FD356-4894-4830-B208-D6BCE3E65B11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{DA9FD356-4894-4830-B208-D6BCE3E65B11}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{DA9FD356-4894-4830-B208-D6BCE3E65B11}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{DA9FD356-4894-4830-B208-D6BCE3E65B11}.Release|Any CPU.Build.0 = Release|Any CPU
+		{DA9FD356-4894-4830-B208-D6BCE3E65B11}.Release|Any CPU.ActiveCfg = Debug|Any CPU
+		{DA9FD356-4894-4830-B208-D6BCE3E65B11}.Release|Any CPU.Build.0 = Debug|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 18 - 6
src/Jellyfin.MediaEncoding.Hls/Playlist/DynamicHlsPlaylistGenerator.cs

@@ -26,6 +26,7 @@ namespace Jellyfin.MediaEncoding.Hls.Playlist
         private readonly IMediaEncoder _mediaEncoder;
         private readonly IApplicationPaths _applicationPaths;
         private readonly KeyframeExtractor _keyframeExtractor;
+        private readonly ILogger<DynamicHlsPlaylistGenerator> _logger;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="DynamicHlsPlaylistGenerator"/> class.
@@ -40,6 +41,7 @@ namespace Jellyfin.MediaEncoding.Hls.Playlist
             _mediaEncoder = mediaEncoder;
             _applicationPaths = applicationPaths;
             _keyframeExtractor = new KeyframeExtractor(loggerFactory.CreateLogger<KeyframeExtractor>());
+            _logger = loggerFactory.CreateLogger<DynamicHlsPlaylistGenerator>();
         }
 
         private string KeyframeCachePath => Path.Combine(_applicationPaths.DataPath, "keyframes");
@@ -47,12 +49,13 @@ namespace Jellyfin.MediaEncoding.Hls.Playlist
         /// <inheritdoc />
         public string CreateMainPlaylist(CreateMainPlaylistRequest request)
         {
-            IReadOnlyList<double> segments;
-            if (IsExtractionAllowed(request.FilePath))
+            IReadOnlyList<double> segments = Array.Empty<double>();
+            if (IsExtractionAllowedForFile(request.FilePath))
             {
                 segments = ComputeSegments(request.FilePath, request.DesiredSegmentLengthMs);
             }
-            else
+
+            if (segments.Count == 0)
             {
                 segments = ComputeEqualLengthSegments(request.DesiredSegmentLengthMs, request.TotalRuntimeTicks);
             }
@@ -92,7 +95,7 @@ namespace Jellyfin.MediaEncoding.Hls.Playlist
             foreach (var length in segments)
             {
                 builder.Append("#EXTINF:")
-                    .Append(length.ToString("0.0000", CultureInfo.InvariantCulture))
+                    .Append(length.ToString("0.000000", CultureInfo.InvariantCulture))
                     .AppendLine(", nodesc")
                     .Append(request.EndpointPrefix)
                     .Append(index++)
@@ -122,7 +125,16 @@ namespace Jellyfin.MediaEncoding.Hls.Playlist
             }
             else
             {
-                keyframeData = _keyframeExtractor.GetKeyframeData(filePath, _mediaEncoder.ProbePath, string.Empty);
+                try
+                {
+                    keyframeData = _keyframeExtractor.GetKeyframeData(filePath, _mediaEncoder.ProbePath, string.Empty);
+                }
+                catch (Exception ex)
+                {
+                    _logger.LogError(ex, "Keyframe extraction failed for path {FilePath}", filePath);
+                    return Array.Empty<double>();
+                }
+
                 CacheResult(cachePath, keyframeData);
             }
 
@@ -176,7 +188,7 @@ namespace Jellyfin.MediaEncoding.Hls.Playlist
             return false;
         }
 
-        private bool IsExtractionAllowed(ReadOnlySpan<char> filePath)
+        private bool IsExtractionAllowedForFile(ReadOnlySpan<char> filePath)
         {
             // Remove the leading dot
             var extension = Path.GetExtension(filePath)[1..];

+ 19 - 6
src/Jellyfin.MediaEncoding.Keyframes/KeyframeExtractor.cs

@@ -32,25 +32,38 @@ namespace Jellyfin.MediaEncoding.Keyframes
         /// <returns>An instance of <see cref="KeyframeData"/>.</returns>
         public KeyframeData GetKeyframeData(string filePath, string ffProbePath, string ffToolPath)
         {
-            var extension = Path.GetExtension(filePath);
-            if (string.Equals(extension, ".mkv", StringComparison.OrdinalIgnoreCase))
+            var extension = Path.GetExtension(filePath.AsSpan());
+            if (extension.Equals(".mkv", StringComparison.OrdinalIgnoreCase))
             {
                 try
                 {
                     return MatroskaKeyframeExtractor.GetKeyframeData(filePath);
                 }
-                catch (InvalidOperationException ex)
+                catch (Exception ex)
                 {
-                    _logger.LogError(ex, "{MatroskaKeyframeExtractor} failed to extract keyframes", nameof(MatroskaKeyframeExtractor));
+                    _logger.LogError(ex, "{ExtractorType} failed to extract keyframes", nameof(MatroskaKeyframeExtractor));
                 }
             }
 
-            if (!string.IsNullOrEmpty(ffToolPath))
+            try
             {
                 return FfToolKeyframeExtractor.GetKeyframeData(ffToolPath, filePath);
             }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "{ExtractorType} failed to extract keyframes", nameof(FfToolKeyframeExtractor));
+            }
+
+            try
+            {
+                return FfProbeKeyframeExtractor.GetKeyframeData(ffProbePath, filePath);
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(ex, "{ExtractorType} failed to extract keyframes", nameof(FfProbeKeyframeExtractor));
+            }
 
-            return FfProbeKeyframeExtractor.GetKeyframeData(ffProbePath, filePath);
+            return new KeyframeData(0, Array.Empty<long>());
         }
     }
 }

+ 4 - 4
src/Jellyfin.MediaEncoding.Keyframes/Matroska/MatroskaKeyframeExtractor.cs

@@ -49,7 +49,7 @@ namespace Jellyfin.MediaEncoding.Keyframes.Matroska
 
                 if (trackNumber == videoTrackNumber)
                 {
-                    keyframes.Add(ScaleToNanoseconds(cueTime, info.TimestampScale));
+                    keyframes.Add(ScaleToTicks(cueTime, info.TimestampScale));
                 }
 
                 reader.LeaveContainer();
@@ -57,17 +57,17 @@ namespace Jellyfin.MediaEncoding.Keyframes.Matroska
 
             reader.LeaveContainer();
 
-            var result = new KeyframeData(ScaleToNanoseconds(info.Duration ?? 0, info.TimestampScale), keyframes);
+            var result = new KeyframeData(ScaleToTicks(info.Duration ?? 0, info.TimestampScale), keyframes);
             return result;
         }
 
-        private static long ScaleToNanoseconds(ulong unscaledValue, long timestampScale)
+        private static long ScaleToTicks(ulong unscaledValue, long timestampScale)
         {
             // TimestampScale is in nanoseconds, scale it to get the value in ticks, 1 tick == 100 ns
             return (long)unscaledValue * timestampScale / 100;
         }
 
-        private static long ScaleToNanoseconds(double unscaledValue, long timestampScale)
+        private static long ScaleToTicks(double unscaledValue, long timestampScale)
         {
             // TimestampScale is in nanoseconds, scale it to get the value in ticks, 1 tick == 100 ns
             return Convert.ToInt64(unscaledValue * timestampScale / 100);