This commit is contained in:
cvium 2021-09-25 10:41:36 +02:00
parent 35c0801d6c
commit be233b49b6
4 changed files with 45 additions and 24 deletions

View File

@ -223,10 +223,6 @@ Global
{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Debug|Any CPU.Build.0 = Debug|Any CPU {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.ActiveCfg = Release|Any CPU
{42816EA8-4511-4CBF-A9C7-7791D5DDDAE6}.Release|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{3ADBCD8C-C0F2-4956-8FDC-35D686B74CF9}.Debug|Any CPU.Build.0 = 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 {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 {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.ActiveCfg = Debug|Any CPU
{06535CA1-4097-4360-85EB-5FB875D53239}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{06535CA1-4097-4360-85EB-5FB875D53239}.Release|Any CPU.Build.0 = Release|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.ActiveCfg = Debug|Any CPU
{DA9FD356-4894-4830-B208-D6BCE3E65B11}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Debug|Any CPU
{DA9FD356-4894-4830-B208-D6BCE3E65B11}.Release|Any CPU.Build.0 = Release|Any CPU {DA9FD356-4894-4830-B208-D6BCE3E65B11}.Release|Any CPU.Build.0 = Debug|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

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

View File

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

View File

@ -49,7 +49,7 @@ namespace Jellyfin.MediaEncoding.Keyframes.Matroska
if (trackNumber == videoTrackNumber) if (trackNumber == videoTrackNumber)
{ {
keyframes.Add(ScaleToNanoseconds(cueTime, info.TimestampScale)); keyframes.Add(ScaleToTicks(cueTime, info.TimestampScale));
} }
reader.LeaveContainer(); reader.LeaveContainer();
@ -57,17 +57,17 @@ namespace Jellyfin.MediaEncoding.Keyframes.Matroska
reader.LeaveContainer(); 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; 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 // TimestampScale is in nanoseconds, scale it to get the value in ticks, 1 tick == 100 ns
return (long)unscaledValue * timestampScale / 100; 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 // TimestampScale is in nanoseconds, scale it to get the value in ticks, 1 tick == 100 ns
return Convert.ToInt64(unscaledValue * timestampScale / 100); return Convert.ToInt64(unscaledValue * timestampScale / 100);