aboutsummaryrefslogtreecommitdiff
path: root/src/Jellyfin.MediaEncoding.Hls
diff options
context:
space:
mode:
Diffstat (limited to 'src/Jellyfin.MediaEncoding.Hls')
-rw-r--r--src/Jellyfin.MediaEncoding.Hls/Cache/CacheDecorator.cs79
-rw-r--r--src/Jellyfin.MediaEncoding.Hls/Extractors/FfProbeKeyframeExtractor.cs2
-rw-r--r--src/Jellyfin.MediaEncoding.Hls/Extractors/IKeyframeExtractor.cs4
-rw-r--r--src/Jellyfin.MediaEncoding.Hls/Extractors/MatroskaKeyframeExtractor.cs2
-rw-r--r--src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj1
-rw-r--r--src/Jellyfin.MediaEncoding.Hls/Playlist/CreateMainPlaylistRequest.cs11
-rw-r--r--src/Jellyfin.MediaEncoding.Hls/Playlist/DynamicHlsPlaylistGenerator.cs8
-rw-r--r--src/Jellyfin.MediaEncoding.Hls/ScheduledTasks/KeyframeExtractionScheduledTask.cs24
8 files changed, 53 insertions, 78 deletions
diff --git a/src/Jellyfin.MediaEncoding.Hls/Cache/CacheDecorator.cs b/src/Jellyfin.MediaEncoding.Hls/Cache/CacheDecorator.cs
index 127f4079c..8ca0e869a 100644
--- a/src/Jellyfin.MediaEncoding.Hls/Cache/CacheDecorator.cs
+++ b/src/Jellyfin.MediaEncoding.Hls/Cache/CacheDecorator.cs
@@ -1,13 +1,12 @@
+#pragma warning disable CA1826 // Do not use Enumerable methods on indexable collections
+
using System;
using System.Diagnostics.CodeAnalysis;
-using System.Globalization;
-using System.IO;
-using System.Text.Json;
-using Jellyfin.Extensions.Json;
+using System.Linq;
+using System.Threading;
using Jellyfin.MediaEncoding.Hls.Extractors;
using Jellyfin.MediaEncoding.Keyframes;
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Persistence;
using Microsoft.Extensions.Logging;
namespace Jellyfin.MediaEncoding.Hls.Cache;
@@ -15,82 +14,48 @@ namespace Jellyfin.MediaEncoding.Hls.Cache;
/// <inheritdoc />
public class CacheDecorator : IKeyframeExtractor
{
+ private readonly IKeyframeRepository _keyframeRepository;
private readonly IKeyframeExtractor _keyframeExtractor;
private readonly ILogger<CacheDecorator> _logger;
private readonly string _keyframeExtractorName;
- private static readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
- private readonly string _keyframeCachePath;
/// <summary>
/// Initializes a new instance of the <see cref="CacheDecorator"/> class.
/// </summary>
- /// <param name="applicationPaths">An instance of the <see cref="IApplicationPaths"/> interface.</param>
+ /// <param name="keyframeRepository">An instance of the <see cref="IKeyframeRepository"/> interface.</param>
/// <param name="keyframeExtractor">An instance of the <see cref="IKeyframeExtractor"/> interface.</param>
/// <param name="logger">An instance of the <see cref="ILogger{CacheDecorator}"/> interface.</param>
- public CacheDecorator(IApplicationPaths applicationPaths, IKeyframeExtractor keyframeExtractor, ILogger<CacheDecorator> logger)
+ public CacheDecorator(IKeyframeRepository keyframeRepository, IKeyframeExtractor keyframeExtractor, ILogger<CacheDecorator> logger)
{
- ArgumentNullException.ThrowIfNull(applicationPaths);
+ ArgumentNullException.ThrowIfNull(keyframeRepository);
ArgumentNullException.ThrowIfNull(keyframeExtractor);
+ _keyframeRepository = keyframeRepository;
_keyframeExtractor = keyframeExtractor;
_logger = logger;
_keyframeExtractorName = keyframeExtractor.GetType().Name;
- // TODO make the dir configurable
- _keyframeCachePath = Path.Combine(applicationPaths.DataPath, "keyframes");
}
/// <inheritdoc />
public bool IsMetadataBased => _keyframeExtractor.IsMetadataBased;
/// <inheritdoc />
- public bool TryExtractKeyframes(string filePath, [NotNullWhen(true)] out KeyframeData? keyframeData)
+ public bool TryExtractKeyframes(Guid itemId, string filePath, [NotNullWhen(true)] out KeyframeData? keyframeData)
{
- keyframeData = null;
- var cachePath = GetCachePath(_keyframeCachePath, filePath);
- if (TryReadFromCache(cachePath, out var cachedResult))
+ keyframeData = _keyframeRepository.GetKeyframeData(itemId).FirstOrDefault();
+ if (keyframeData is null)
{
- keyframeData = cachedResult;
- return true;
+ if (!_keyframeExtractor.TryExtractKeyframes(itemId, filePath, out var result))
+ {
+ _logger.LogDebug("Failed to extract keyframes using {ExtractorName}", _keyframeExtractorName);
+ return false;
+ }
+
+ _logger.LogDebug("Successfully extracted keyframes using {ExtractorName}", _keyframeExtractorName);
+ keyframeData = result;
+ _keyframeRepository.SaveKeyframeDataAsync(itemId, keyframeData, CancellationToken.None).GetAwaiter().GetResult();
}
- if (!_keyframeExtractor.TryExtractKeyframes(filePath, out var result))
- {
- _logger.LogDebug("Failed to extract keyframes using {ExtractorName}", _keyframeExtractorName);
- return false;
- }
-
- _logger.LogDebug("Successfully extracted keyframes using {ExtractorName}", _keyframeExtractorName);
- keyframeData = result;
- SaveToCache(cachePath, keyframeData);
return true;
}
-
- private static void SaveToCache(string cachePath, KeyframeData keyframeData)
- {
- var json = JsonSerializer.Serialize(keyframeData, _jsonOptions);
- Directory.CreateDirectory(Path.GetDirectoryName(cachePath) ?? throw new ArgumentException($"Provided path ({cachePath}) is not valid.", nameof(cachePath)));
- File.WriteAllText(cachePath, json);
- }
-
- private static string GetCachePath(string keyframeCachePath, string filePath)
- {
- var lastWriteTimeUtc = File.GetLastWriteTimeUtc(filePath);
- ReadOnlySpan<char> filename = (filePath + "_" + lastWriteTimeUtc.Ticks.ToString(CultureInfo.InvariantCulture)).GetMD5() + ".json";
- var prefix = filename[..1];
-
- return Path.Join(keyframeCachePath, prefix, filename);
- }
-
- private static bool TryReadFromCache(string cachePath, [NotNullWhen(true)] out KeyframeData? cachedResult)
- {
- if (File.Exists(cachePath))
- {
- var bytes = File.ReadAllBytes(cachePath);
- cachedResult = JsonSerializer.Deserialize<KeyframeData>(bytes, _jsonOptions);
- return cachedResult is not null;
- }
-
- cachedResult = null;
- return false;
- }
}
diff --git a/src/Jellyfin.MediaEncoding.Hls/Extractors/FfProbeKeyframeExtractor.cs b/src/Jellyfin.MediaEncoding.Hls/Extractors/FfProbeKeyframeExtractor.cs
index a8daeeb78..a69746fe0 100644
--- a/src/Jellyfin.MediaEncoding.Hls/Extractors/FfProbeKeyframeExtractor.cs
+++ b/src/Jellyfin.MediaEncoding.Hls/Extractors/FfProbeKeyframeExtractor.cs
@@ -34,7 +34,7 @@ public class FfProbeKeyframeExtractor : IKeyframeExtractor
public bool IsMetadataBased => false;
/// <inheritdoc />
- public bool TryExtractKeyframes(string filePath, [NotNullWhen(true)] out KeyframeData? keyframeData)
+ public bool TryExtractKeyframes(Guid itemId, string filePath, [NotNullWhen(true)] out KeyframeData? keyframeData)
{
if (!_namingOptions.VideoFileExtensions.Contains(Path.GetExtension(filePath.AsSpan()), StringComparison.OrdinalIgnoreCase))
{
diff --git a/src/Jellyfin.MediaEncoding.Hls/Extractors/IKeyframeExtractor.cs b/src/Jellyfin.MediaEncoding.Hls/Extractors/IKeyframeExtractor.cs
index 083e93de1..84bccbc72 100644
--- a/src/Jellyfin.MediaEncoding.Hls/Extractors/IKeyframeExtractor.cs
+++ b/src/Jellyfin.MediaEncoding.Hls/Extractors/IKeyframeExtractor.cs
@@ -1,3 +1,4 @@
+using System;
using System.Diagnostics.CodeAnalysis;
using Jellyfin.MediaEncoding.Keyframes;
@@ -16,8 +17,9 @@ public interface IKeyframeExtractor
/// <summary>
/// Attempt to extract keyframes.
/// </summary>
+ /// <param name="itemId">The item id.</param>
/// <param name="filePath">The path to the file.</param>
/// <param name="keyframeData">The keyframes.</param>
/// <returns>A value indicating whether the keyframe extraction was successful.</returns>
- bool TryExtractKeyframes(string filePath, [NotNullWhen(true)] out KeyframeData? keyframeData);
+ bool TryExtractKeyframes(Guid itemId, string filePath, [NotNullWhen(true)] out KeyframeData? keyframeData);
}
diff --git a/src/Jellyfin.MediaEncoding.Hls/Extractors/MatroskaKeyframeExtractor.cs b/src/Jellyfin.MediaEncoding.Hls/Extractors/MatroskaKeyframeExtractor.cs
index 1100f8cd5..c7758e919 100644
--- a/src/Jellyfin.MediaEncoding.Hls/Extractors/MatroskaKeyframeExtractor.cs
+++ b/src/Jellyfin.MediaEncoding.Hls/Extractors/MatroskaKeyframeExtractor.cs
@@ -24,7 +24,7 @@ public class MatroskaKeyframeExtractor : IKeyframeExtractor
public bool IsMetadataBased => true;
/// <inheritdoc />
- public bool TryExtractKeyframes(string filePath, [NotNullWhen(true)] out KeyframeData? keyframeData)
+ public bool TryExtractKeyframes(Guid itemId, string filePath, [NotNullWhen(true)] out KeyframeData? keyframeData)
{
if (!filePath.AsSpan().EndsWith(".mkv", StringComparison.OrdinalIgnoreCase))
{
diff --git a/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj b/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj
index dc581724a..80b5aa84e 100644
--- a/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj
+++ b/src/Jellyfin.MediaEncoding.Hls/Jellyfin.MediaEncoding.Hls.csproj
@@ -7,6 +7,7 @@
<ItemGroup>
<ProjectReference Include="../../MediaBrowser.Common/MediaBrowser.Common.csproj" />
+ <ProjectReference Include="../../MediaBrowser.Model/MediaBrowser.Model.csproj" />
<ProjectReference Include="../../MediaBrowser.Controller/MediaBrowser.Controller.csproj" />
<ProjectReference Include="../Jellyfin.MediaEncoding.Keyframes/Jellyfin.MediaEncoding.Keyframes.csproj" />
</ItemGroup>
diff --git a/src/Jellyfin.MediaEncoding.Hls/Playlist/CreateMainPlaylistRequest.cs b/src/Jellyfin.MediaEncoding.Hls/Playlist/CreateMainPlaylistRequest.cs
index 21d9bb658..f5af50062 100644
--- a/src/Jellyfin.MediaEncoding.Hls/Playlist/CreateMainPlaylistRequest.cs
+++ b/src/Jellyfin.MediaEncoding.Hls/Playlist/CreateMainPlaylistRequest.cs
@@ -1,3 +1,5 @@
+using System;
+
namespace Jellyfin.MediaEncoding.Hls.Playlist;
/// <summary>
@@ -8,6 +10,7 @@ public class CreateMainPlaylistRequest
/// <summary>
/// Initializes a new instance of the <see cref="CreateMainPlaylistRequest"/> class.
/// </summary>
+ /// <param name="mediaSourceId">The media source id.</param>
/// <param name="filePath">The absolute file path to the file.</param>
/// <param name="desiredSegmentLengthMs">The desired segment length in milliseconds.</param>
/// <param name="totalRuntimeTicks">The total duration of the file in ticks.</param>
@@ -15,8 +18,9 @@ public class CreateMainPlaylistRequest
/// <param name="endpointPrefix">The URI prefix for the relative URL in the playlist.</param>
/// <param name="queryString">The desired query string to append (must start with ?).</param>
/// <param name="isRemuxingVideo">Whether the video is being remuxed.</param>
- public CreateMainPlaylistRequest(string filePath, int desiredSegmentLengthMs, long totalRuntimeTicks, string segmentContainer, string endpointPrefix, string queryString, bool isRemuxingVideo)
+ public CreateMainPlaylistRequest(Guid? mediaSourceId, string filePath, int desiredSegmentLengthMs, long totalRuntimeTicks, string segmentContainer, string endpointPrefix, string queryString, bool isRemuxingVideo)
{
+ MediaSourceId = mediaSourceId;
FilePath = filePath;
DesiredSegmentLengthMs = desiredSegmentLengthMs;
TotalRuntimeTicks = totalRuntimeTicks;
@@ -27,6 +31,11 @@ public class CreateMainPlaylistRequest
}
/// <summary>
+ /// Gets the media source id.
+ /// </summary>
+ public Guid? MediaSourceId { get; }
+
+ /// <summary>
/// Gets the file path.
/// </summary>
public string FilePath { get; }
diff --git a/src/Jellyfin.MediaEncoding.Hls/Playlist/DynamicHlsPlaylistGenerator.cs b/src/Jellyfin.MediaEncoding.Hls/Playlist/DynamicHlsPlaylistGenerator.cs
index 1846ba26b..fb5027e5b 100644
--- a/src/Jellyfin.MediaEncoding.Hls/Playlist/DynamicHlsPlaylistGenerator.cs
+++ b/src/Jellyfin.MediaEncoding.Hls/Playlist/DynamicHlsPlaylistGenerator.cs
@@ -35,7 +35,9 @@ public class DynamicHlsPlaylistGenerator : IDynamicHlsPlaylistGenerator
{
IReadOnlyList<double> segments;
// For video transcodes it is sufficient with equal length segments as ffmpeg will create new keyframes
- if (request.IsRemuxingVideo && TryExtractKeyframes(request.FilePath, out var keyframeData))
+ if (request.IsRemuxingVideo
+ && request.MediaSourceId is not null
+ && TryExtractKeyframes(request.MediaSourceId.Value, request.FilePath, out var keyframeData))
{
segments = ComputeSegments(keyframeData, request.DesiredSegmentLengthMs);
}
@@ -104,7 +106,7 @@ public class DynamicHlsPlaylistGenerator : IDynamicHlsPlaylistGenerator
return builder.ToString();
}
- private bool TryExtractKeyframes(string filePath, [NotNullWhen(true)] out KeyframeData? keyframeData)
+ private bool TryExtractKeyframes(Guid itemId, string filePath, [NotNullWhen(true)] out KeyframeData? keyframeData)
{
keyframeData = null;
if (!IsExtractionAllowedForFile(filePath, _serverConfigurationManager.GetEncodingOptions().AllowOnDemandMetadataBasedKeyframeExtractionForExtensions))
@@ -116,7 +118,7 @@ public class DynamicHlsPlaylistGenerator : IDynamicHlsPlaylistGenerator
for (var i = 0; i < len; i++)
{
var extractor = _extractors[i];
- if (!extractor.TryExtractKeyframes(filePath, out var result))
+ if (!extractor.TryExtractKeyframes(itemId, filePath, out var result))
{
continue;
}
diff --git a/src/Jellyfin.MediaEncoding.Hls/ScheduledTasks/KeyframeExtractionScheduledTask.cs b/src/Jellyfin.MediaEncoding.Hls/ScheduledTasks/KeyframeExtractionScheduledTask.cs
index caf6a2aae..d63ee6777 100644
--- a/src/Jellyfin.MediaEncoding.Hls/ScheduledTasks/KeyframeExtractionScheduledTask.cs
+++ b/src/Jellyfin.MediaEncoding.Hls/ScheduledTasks/KeyframeExtractionScheduledTask.cs
@@ -9,7 +9,6 @@ using Jellyfin.MediaEncoding.Hls.Extractors;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Tasks;
@@ -23,7 +22,7 @@ public class KeyframeExtractionScheduledTask : IScheduledTask
private readonly ILocalizationManager _localizationManager;
private readonly ILibraryManager _libraryManager;
private readonly IKeyframeExtractor[] _keyframeExtractors;
- private static readonly BaseItemKind[] _itemTypes = { BaseItemKind.Episode, BaseItemKind.Movie };
+ private static readonly BaseItemKind[] _itemTypes = [BaseItemKind.Episode, BaseItemKind.Movie];
/// <summary>
/// Initializes a new instance of the <see cref="KeyframeExtractionScheduledTask"/> class.
@@ -55,11 +54,11 @@ public class KeyframeExtractionScheduledTask : IScheduledTask
{
var query = new InternalItemsQuery
{
- MediaTypes = new[] { MediaType.Video },
+ MediaTypes = [MediaType.Video],
IsVirtualItem = false,
IncludeItemTypes = _itemTypes,
DtoOptions = new DtoOptions(true),
- SourceTypes = new[] { SourceType.Library },
+ SourceTypes = [SourceType.Library],
Recursive = true,
Limit = Pagesize
};
@@ -74,19 +73,16 @@ public class KeyframeExtractionScheduledTask : IScheduledTask
query.StartIndex = startIndex;
var videos = _libraryManager.GetItemList(query);
- var currentPageCount = videos.Count;
- // TODO parallelize with Parallel.ForEach?
- for (var i = 0; i < currentPageCount; i++)
+ foreach (var video in videos)
{
- var video = videos[i];
// Only local files supported
- if (video.IsFileProtocol && File.Exists(video.Path))
+ var path = video.Path;
+ if (File.Exists(path))
{
- for (var j = 0; j < _keyframeExtractors.Length; j++)
+ foreach (var extractor in _keyframeExtractors)
{
- var extractor = _keyframeExtractors[j];
- // The cache decorator will make sure to save them in the data dir
- if (extractor.TryExtractKeyframes(video.Path, out _))
+ // The cache decorator will make sure to save the keyframes
+ if (extractor.TryExtractKeyframes(video.Id, path, out _))
{
break;
}
@@ -107,5 +103,5 @@ public class KeyframeExtractionScheduledTask : IScheduledTask
}
/// <inheritdoc />
- public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() => Enumerable.Empty<TaskTriggerInfo>();
+ public IEnumerable<TaskTriggerInfo> GetDefaultTriggers() => [];
}