aboutsummaryrefslogtreecommitdiff
path: root/Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs')
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs203
1 files changed, 139 insertions, 64 deletions
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs
index 8d1d509ff..36708e258 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs
@@ -33,6 +33,8 @@ public partial class AudioNormalizationTask : IScheduledTask
private readonly ILocalizationManager _localization;
private readonly ILogger<AudioNormalizationTask> _logger;
+ private static readonly TimeSpan _dbSaveInterval = TimeSpan.FromMinutes(5);
+
/// <summary>
/// Initializes a new instance of the <see cref="AudioNormalizationTask"/> class.
/// </summary>
@@ -76,94 +78,150 @@ public partial class AudioNormalizationTask : IScheduledTask
/// <inheritdoc />
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
{
- foreach (var library in _libraryManager.RootFolder.Children)
+ var numComplete = 0;
+ var libraries = _libraryManager.RootFolder.Children.Where(library => _libraryManager.GetLibraryOptions(library).EnableLUFSScan).ToArray();
+ double percent = 0;
+
+ foreach (var library in libraries)
{
- var libraryOptions = _libraryManager.GetLibraryOptions(library);
- if (!libraryOptions.EnableLUFSScan)
- {
- continue;
- }
+ var startDbSaveInterval = Stopwatch.GetTimestamp();
+ var albums = _libraryManager.GetItemList(new InternalItemsQuery { IncludeItemTypes = [BaseItemKind.MusicAlbum], Parent = library, Recursive = true });
+ var toSaveDbItems = new List<BaseItem>();
- // Album gain
- var albums = _libraryManager.GetItemList(new InternalItemsQuery
- {
- IncludeItemTypes = [BaseItemKind.MusicAlbum],
- Parent = library,
- Recursive = true
- });
+ double nextPercent = numComplete + 1;
+ nextPercent /= libraries.Length;
+ nextPercent -= percent;
+ // Split the progress for this single library into two halves: album gain and track gain.
+ // The first half will be for album gain, the second half for track gain.
+ nextPercent /= 2;
+ var albumComplete = 0;
foreach (var a in albums)
{
- if (a.NormalizationGain.HasValue || a.LUFS.HasValue)
+ if (!a.NormalizationGain.HasValue && !a.LUFS.HasValue)
{
- continue;
- }
+ // Album gain
+ var albumTracks = ((MusicAlbum)a).Tracks.Where(x => x.IsFileProtocol).ToList();
- // 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;
+ // Skip albums that don't have multiple tracks, album gain is useless here
+ if (albumTracks.Count > 1)
+ {
+ _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),
+ OperatingSystem.IsWindows(), // Wait for process to exit on Windows before we try deleting the concat file
+ cancellationToken).ConfigureAwait(false);
+ toSaveDbItems.Add(a);
+ }
+ finally
+ {
+ try
+ {
+ File.Delete(tempFile);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Failed to delete concat file: {FileName}.", tempFile);
+ }
+ }
+ }
}
- _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),
- OperatingSystem.IsWindows(), // Wait for process to exit on Windows before we try deleting the concat file
- cancellationToken).ConfigureAwait(false);
- }
- finally
+ if (Stopwatch.GetElapsedTime(startDbSaveInterval) > _dbSaveInterval)
{
- File.Delete(tempFile);
+ if (toSaveDbItems.Count > 1)
+ {
+ _itemRepository.SaveItems(toSaveDbItems, cancellationToken);
+ toSaveDbItems.Clear();
+ }
+
+ startDbSaveInterval = Stopwatch.GetTimestamp();
}
+
+ // Update sub-progress for album gain
+ albumComplete++;
+ double albumPercent = albumComplete;
+ albumPercent /= albums.Count;
+
+ progress.Report(100 * (percent + (albumPercent * nextPercent)));
}
- _itemRepository.SaveItems(albums, cancellationToken);
+ // Update progress to start at the track gain percent calculation
+ percent += nextPercent;
- // Track gain
- var tracks = _libraryManager.GetItemList(new InternalItemsQuery
+ if (toSaveDbItems.Count > 1)
{
- MediaTypes = [MediaType.Audio],
- IncludeItemTypes = [BaseItemKind.Audio],
- Parent = library,
- Recursive = true
- });
+ _itemRepository.SaveItems(toSaveDbItems, cancellationToken);
+ toSaveDbItems.Clear();
+ }
+
+ startDbSaveInterval = Stopwatch.GetTimestamp();
+ // Track gain
+ var tracks = _libraryManager.GetItemList(new InternalItemsQuery { MediaTypes = [MediaType.Audio], IncludeItemTypes = [BaseItemKind.Audio], Parent = library, Recursive = true });
+
+ var tracksComplete = 0;
foreach (var t in tracks)
{
- if (t.NormalizationGain.HasValue || t.LUFS.HasValue || !t.IsFileProtocol)
+ 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)),
+ false,
+ cancellationToken).ConfigureAwait(false);
+ toSaveDbItems.Add(t);
+ }
+
+ if (Stopwatch.GetElapsedTime(startDbSaveInterval) > _dbSaveInterval)
+ {
+ if (toSaveDbItems.Count > 1)
+ {
+ _itemRepository.SaveItems(toSaveDbItems, cancellationToken);
+ toSaveDbItems.Clear();
+ }
+
+ startDbSaveInterval = Stopwatch.GetTimestamp();
}
- t.LUFS = await CalculateLUFSAsync(
- string.Format(CultureInfo.InvariantCulture, "-i \"{0}\"", t.Path.Replace("\"", "\\\"", StringComparison.Ordinal)),
- false,
- cancellationToken).ConfigureAwait(false);
+ // Update sub-progress for track gain
+ tracksComplete++;
+ double trackPercent = tracksComplete;
+ trackPercent /= tracks.Count;
+
+ progress.Report(100 * (percent + (trackPercent * nextPercent)));
+ }
+
+ if (toSaveDbItems.Count > 1)
+ {
+ _itemRepository.SaveItems(toSaveDbItems, cancellationToken);
}
- _itemRepository.SaveItems(tracks, cancellationToken);
+ // Update progress
+ numComplete++;
+ percent = numComplete;
+ percent /= libraries.Length;
+
+ progress.Report(100 * percent);
}
+
+ progress.Report(100.0);
}
/// <inheritdoc />
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
- return
- [
- new TaskTriggerInfo
- {
- Type = TaskTriggerInfoType.IntervalTrigger,
- IntervalTicks = TimeSpan.FromHours(24).Ticks
- }
- ];
+ yield return new TaskTriggerInfo
+ {
+ Type = TaskTriggerInfoType.IntervalTrigger,
+ IntervalTicks = TimeSpan.FromHours(24).Ticks
+ };
}
private async Task<float?> CalculateLUFSAsync(string inputArgs, bool waitForExit, CancellationToken cancellationToken)
@@ -181,9 +239,9 @@ public partial class AudioNormalizationTask : IScheduledTask
},
})
{
+ _logger.LogDebug("Starting ffmpeg with arguments: {Arguments}", args);
try
{
- _logger.LogDebug("Starting ffmpeg with arguments: {Arguments}", args);
process.Start();
}
catch (Exception ex)
@@ -192,16 +250,33 @@ public partial class AudioNormalizationTask : IScheduledTask
return null;
}
+ try
+ {
+ process.PriorityClass = ProcessPriorityClass.BelowNormal;
+ }
+ catch (Exception ex)
+ {
+ _logger.LogWarning(ex, "Error setting ffmpeg process priority");
+ }
+
using var reader = process.StandardError;
float? lufs = null;
- await foreach (var line in reader.ReadAllLinesAsync(cancellationToken))
+ var foundLufs = false;
+ await foreach (var line in reader.ReadAllLinesAsync(cancellationToken).ConfigureAwait(false))
{
+ if (foundLufs)
+ {
+ continue;
+ }
+
Match match = LUFSRegex().Match(line);
- if (match.Success)
+ if (!match.Success)
{
- lufs = float.Parse(match.Groups[1].ValueSpan, CultureInfo.InvariantCulture.NumberFormat);
- break;
+ continue;
}
+
+ lufs = float.Parse(match.Groups[1].ValueSpan, CultureInfo.InvariantCulture.NumberFormat);
+ foundLufs = true;
}
if (lufs is null)