diff options
Diffstat (limited to 'Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs')
| -rw-r--r-- | Emby.Server.Implementations/ScheduledTasks/Tasks/AudioNormalizationTask.cs | 203 |
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) |
