aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations/ScheduledTasks/Tasks
diff options
context:
space:
mode:
Diffstat (limited to 'Emby.Server.Implementations/ScheduledTasks/Tasks')
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs206
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs1
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupCollectionAndPlaylistPathsTask.cs11
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs44
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs54
5 files changed, 219 insertions, 97 deletions
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs
new file mode 100644
index 000000000..301c04915
--- /dev/null
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs
@@ -0,0 +1,206 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Model.Globalization;
+using MediaBrowser.Model.Tasks;
+using Microsoft.Extensions.Logging;
+
+namespace Emby.Server.Implementations.ScheduledTasks.Tasks;
+
+/// <summary>
+/// The audio normalization task.
+/// </summary>
+public partial class AudioNormalizationTask : IScheduledTask
+{
+ private readonly IItemRepository _itemRepository;
+ private readonly ILibraryManager _libraryManager;
+ private readonly IMediaEncoder _mediaEncoder;
+ private readonly IApplicationPaths _applicationPaths;
+ private readonly ILocalizationManager _localization;
+ private readonly ILogger<AudioNormalizationTask> _logger;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AudioNormalizationTask"/> class.
+ /// </summary>
+ /// <param name="itemRepository">Instance of the <see cref="IItemRepository"/> interface.</param>
+ /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
+ /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
+ /// <param name="applicationPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param>
+ /// <param name="localizationManager">Instance of the <see cref="ILocalizationManager"/> interface.</param>
+ /// <param name="logger">Instance of the <see cref="ILogger{AudioNormalizationTask}"/> interface.</param>
+ public AudioNormalizationTask(
+ IItemRepository itemRepository,
+ ILibraryManager libraryManager,
+ IMediaEncoder mediaEncoder,
+ IApplicationPaths applicationPaths,
+ ILocalizationManager localizationManager,
+ ILogger<AudioNormalizationTask> logger)
+ {
+ _itemRepository = itemRepository;
+ _libraryManager = libraryManager;
+ _mediaEncoder = mediaEncoder;
+ _applicationPaths = applicationPaths;
+ _localization = localizationManager;
+ _logger = logger;
+ }
+
+ /// <inheritdoc />
+ public string Name => _localization.GetLocalizedString("TaskAudioNormalization");
+
+ /// <inheritdoc />
+ public string Description => _localization.GetLocalizedString("TaskAudioNormalizationDescription");
+
+ /// <inheritdoc />
+ public string Category => _localization.GetLocalizedString("TasksLibraryCategory");
+
+ /// <inheritdoc />
+ public string Key => "AudioNormalization";
+
+ [GeneratedRegex(@"^\s+I:\s+(.*?)\s+LUFS")]
+ private static partial Regex LUFSRegex();
+
+ /// <inheritdoc />
+ public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
+ {
+ foreach (var library in _libraryManager.RootFolder.Children)
+ {
+ var libraryOptions = _libraryManager.GetLibraryOptions(library);
+ if (!libraryOptions.EnableLUFSScan)
+ {
+ continue;
+ }
+
+ // Album gain
+ var albums = _libraryManager.GetItemList(new InternalItemsQuery
+ {
+ IncludeItemTypes = [BaseItemKind.MusicAlbum],
+ Parent = library,
+ Recursive = true
+ });
+
+ foreach (var a in albums)
+ {
+ if (a.NormalizationGain.HasValue || a.LUFS.HasValue)
+ {
+ continue;
+ }
+
+ // Skip albums that don't have multiple tracks, album gain is useless here
+ var albumTracks = ((MusicAlbum)a).Tracks.Where(x => x.IsFileProtocol).ToList();
+ if (albumTracks.Count <= 1)
+ {
+ continue;
+ }
+
+ _logger.LogInformation("Calculating LUFS for album: {Album} with id: {Id}", a.Name, a.Id);
+ 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
+ {
+ a.LUFS = await CalculateLUFSAsync(
+ string.Format(CultureInfo.InvariantCulture, "-f concat -safe 0 -i \"{0}\"", tempFile),
+ cancellationToken).ConfigureAwait(false);
+ }
+ finally
+ {
+ File.Delete(tempFile);
+ }
+ }
+
+ _itemRepository.SaveItems(albums, cancellationToken);
+
+ // Track gain
+ var tracks = _libraryManager.GetItemList(new InternalItemsQuery
+ {
+ MediaTypes = [MediaType.Audio],
+ IncludeItemTypes = [BaseItemKind.Audio],
+ Parent = library,
+ Recursive = true
+ });
+
+ foreach (var t in tracks)
+ {
+ if (t.NormalizationGain.HasValue || t.LUFS.HasValue || !t.IsFileProtocol)
+ {
+ continue;
+ }
+
+ t.LUFS = await CalculateLUFSAsync(string.Format(CultureInfo.InvariantCulture, "-i \"{0}\"", t.Path.Replace("\"", "\\\"", StringComparison.Ordinal)), cancellationToken);
+ }
+
+ _itemRepository.SaveItems(tracks, cancellationToken);
+ }
+ }
+
+ /// <inheritdoc />
+ public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
+ {
+ return
+ [
+ new TaskTriggerInfo
+ {
+ Type = TaskTriggerInfo.TriggerInterval,
+ IntervalTicks = TimeSpan.FromHours(24).Ticks
+ }
+ ];
+ }
+
+ private async Task<float?> CalculateLUFSAsync(string inputArgs, CancellationToken cancellationToken)
+ {
+ var args = $"-hide_banner {inputArgs} -af ebur128=framelog=verbose -f null -";
+
+ using (var process = new Process()
+ {
+ StartInfo = new ProcessStartInfo
+ {
+ FileName = _mediaEncoder.EncoderPath,
+ Arguments = args,
+ RedirectStandardOutput = false,
+ RedirectStandardError = true
+ },
+ })
+ {
+ try
+ {
+ _logger.LogDebug("Starting ffmpeg with arguments: {Arguments}", args);
+ process.Start();
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error starting ffmpeg with arguments: {Arguments}", args);
+ return null;
+ }
+
+ using var reader = process.StandardError;
+ await foreach (var line in reader.ReadAllLinesAsync(cancellationToken))
+ {
+ Match match = LUFSRegex().Match(line);
+
+ if (match.Success)
+ {
+ return float.Parse(match.Groups[1].ValueSpan, CultureInfo.InvariantCulture.NumberFormat);
+ }
+ }
+
+ _logger.LogError("Failed to find LUFS value in output");
+ return null;
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
index d03d40863..36456504b 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
@@ -13,7 +13,6 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Tasks;
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupCollectionAndPlaylistPathsTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupCollectionAndPlaylistPathsTask.cs
index 812df8192..804097219 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupCollectionAndPlaylistPathsTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/CleanupCollectionAndPlaylistPathsTask.cs
@@ -116,7 +116,7 @@ public class CleanupCollectionAndPlaylistPathsTask : IScheduledTask
foreach (var linkedChild in folder.LinkedChildren)
{
var path = linkedChild.Path;
- if (!File.Exists(path))
+ if (!File.Exists(path) && !Directory.Exists(path))
{
_logger.LogInformation("Item in {FolderName} cannot be found at {ItemPath}", folder.Name, path);
(itemsToRemove ??= new List<LinkedChild>()).Add(linkedChild);
@@ -127,15 +127,8 @@ public class CleanupCollectionAndPlaylistPathsTask : IScheduledTask
{
_logger.LogDebug("Updating {FolderName}", folder.Name);
folder.LinkedChildren = folder.LinkedChildren.Except(itemsToRemove).ToArray();
+ _providerManager.SaveMetadataAsync(folder, ItemUpdateType.MetadataEdit);
folder.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, cancellationToken);
-
- _providerManager.QueueRefresh(
- folder.Id,
- new MetadataRefreshOptions(new DirectoryService(_fileSystem))
- {
- ForceSave = true
- },
- RefreshPriority.High);
}
}
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
index 03935b384..fc3ad90f6 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
@@ -5,6 +5,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.IO;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Tasks;
@@ -133,53 +134,14 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
cancellationToken.ThrowIfCancellationRequested();
- DeleteFile(file.FullName);
+ FileSystemHelper.DeleteFile(_fileSystem, file.FullName, _logger);
index++;
}
- DeleteEmptyFolders(directory);
+ FileSystemHelper.DeleteEmptyFolders(_fileSystem, directory, _logger);
progress.Report(100);
}
-
- private void DeleteEmptyFolders(string parent)
- {
- foreach (var directory in _fileSystem.GetDirectoryPaths(parent))
- {
- DeleteEmptyFolders(directory);
- if (!_fileSystem.GetFileSystemEntryPaths(directory).Any())
- {
- try
- {
- Directory.Delete(directory, false);
- }
- catch (UnauthorizedAccessException ex)
- {
- _logger.LogError(ex, "Error deleting directory {Path}", directory);
- }
- catch (IOException ex)
- {
- _logger.LogError(ex, "Error deleting directory {Path}", directory);
- }
- }
- }
- }
-
- private void DeleteFile(string path)
- {
- try
- {
- _fileSystem.DeleteFile(path);
- }
- catch (UnauthorizedAccessException ex)
- {
- _logger.LogError(ex, "Error deleting file {Path}", path);
- }
- catch (IOException ex)
- {
- _logger.LogError(ex, "Error deleting file {Path}", path);
- }
- }
}
}
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs
index e4e565c64..254500ccd 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/DeleteTranscodeFileTask.cs
@@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
-using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.IO;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Tasks;
@@ -62,16 +62,17 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
/// <inheritdoc />
public bool IsLogged => true;
- /// <summary>
- /// Creates the triggers that define when the task will run.
- /// </summary>
- /// <returns>IEnumerable{BaseTaskTrigger}.</returns>
+ /// <inheritdoc />
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
return new[]
{
new TaskTriggerInfo
{
+ Type = TaskTriggerInfo.TriggerStartup
+ },
+ new TaskTriggerInfo
+ {
Type = TaskTriggerInfo.TriggerInterval,
IntervalTicks = TimeSpan.FromHours(24).Ticks
}
@@ -113,53 +114,14 @@ namespace Emby.Server.Implementations.ScheduledTasks.Tasks
cancellationToken.ThrowIfCancellationRequested();
- DeleteFile(file.FullName);
+ FileSystemHelper.DeleteFile(_fileSystem, file.FullName, _logger);
index++;
}
- DeleteEmptyFolders(directory);
+ FileSystemHelper.DeleteEmptyFolders(_fileSystem, directory, _logger);
progress.Report(100);
}
-
- private void DeleteEmptyFolders(string parent)
- {
- foreach (var directory in _fileSystem.GetDirectoryPaths(parent))
- {
- DeleteEmptyFolders(directory);
- if (!_fileSystem.GetFileSystemEntryPaths(directory).Any())
- {
- try
- {
- Directory.Delete(directory, false);
- }
- catch (UnauthorizedAccessException ex)
- {
- _logger.LogError(ex, "Error deleting directory {Path}", directory);
- }
- catch (IOException ex)
- {
- _logger.LogError(ex, "Error deleting directory {Path}", directory);
- }
- }
- }
- }
-
- private void DeleteFile(string path)
- {
- try
- {
- _fileSystem.DeleteFile(path);
- }
- catch (UnauthorizedAccessException ex)
- {
- _logger.LogError(ex, "Error deleting file {Path}", path);
- }
- catch (IOException ex)
- {
- _logger.LogError(ex, "Error deleting file {Path}", path);
- }
- }
}
}