Improve audio normalization

* Move calculation of LUFS to a scheduled task as it's pretty slow
* Correctly calculate album LUFS
* Don't try to convert replaygain tags to LUFS values
pull/11457/head
Bond_009 1 month ago
parent 5612cb8178
commit 88a38a61b5

@ -49,8 +49,8 @@ namespace Emby.Server.Implementations.Data
private const string SaveItemCommandText = private const string SaveItemCommandText =
@"replace into TypedBaseItems @"replace into TypedBaseItems
(guid,type,data,Path,StartDate,EndDate,ChannelId,IsMovie,IsSeries,EpisodeTitle,IsRepeat,CommunityRating,CustomRating,IndexNumber,IsLocked,Name,OfficialRating,MediaType,Overview,ParentIndexNumber,PremiereDate,ProductionYear,ParentId,Genres,InheritedParentalRatingValue,SortName,ForcedSortName,RunTimeTicks,Size,DateCreated,DateModified,PreferredMetadataLanguage,PreferredMetadataCountryCode,Width,Height,DateLastRefreshed,DateLastSaved,IsInMixedFolder,LockedFields,Studios,Audio,ExternalServiceId,Tags,IsFolder,UnratedType,TopParentId,TrailerTypes,CriticRating,CleanName,PresentationUniqueKey,OriginalTitle,PrimaryVersionId,DateLastMediaAdded,Album,LUFS,IsVirtualItem,SeriesName,UserDataKey,SeasonName,SeasonId,SeriesId,ExternalSeriesId,Tagline,ProviderIds,Images,ProductionLocations,ExtraIds,TotalBitrate,ExtraType,Artists,AlbumArtists,ExternalId,SeriesPresentationUniqueKey,ShowId,OwnerId) (guid,type,data,Path,StartDate,EndDate,ChannelId,IsMovie,IsSeries,EpisodeTitle,IsRepeat,CommunityRating,CustomRating,IndexNumber,IsLocked,Name,OfficialRating,MediaType,Overview,ParentIndexNumber,PremiereDate,ProductionYear,ParentId,Genres,InheritedParentalRatingValue,SortName,ForcedSortName,RunTimeTicks,Size,DateCreated,DateModified,PreferredMetadataLanguage,PreferredMetadataCountryCode,Width,Height,DateLastRefreshed,DateLastSaved,IsInMixedFolder,LockedFields,Studios,Audio,ExternalServiceId,Tags,IsFolder,UnratedType,TopParentId,TrailerTypes,CriticRating,CleanName,PresentationUniqueKey,OriginalTitle,PrimaryVersionId,DateLastMediaAdded,Album,LUFS,NormalizationGain,IsVirtualItem,SeriesName,UserDataKey,SeasonName,SeasonId,SeriesId,ExternalSeriesId,Tagline,ProviderIds,Images,ProductionLocations,ExtraIds,TotalBitrate,ExtraType,Artists,AlbumArtists,ExternalId,SeriesPresentationUniqueKey,ShowId,OwnerId)
values (@guid,@type,@data,@Path,@StartDate,@EndDate,@ChannelId,@IsMovie,@IsSeries,@EpisodeTitle,@IsRepeat,@CommunityRating,@CustomRating,@IndexNumber,@IsLocked,@Name,@OfficialRating,@MediaType,@Overview,@ParentIndexNumber,@PremiereDate,@ProductionYear,@ParentId,@Genres,@InheritedParentalRatingValue,@SortName,@ForcedSortName,@RunTimeTicks,@Size,@DateCreated,@DateModified,@PreferredMetadataLanguage,@PreferredMetadataCountryCode,@Width,@Height,@DateLastRefreshed,@DateLastSaved,@IsInMixedFolder,@LockedFields,@Studios,@Audio,@ExternalServiceId,@Tags,@IsFolder,@UnratedType,@TopParentId,@TrailerTypes,@CriticRating,@CleanName,@PresentationUniqueKey,@OriginalTitle,@PrimaryVersionId,@DateLastMediaAdded,@Album,@LUFS,@IsVirtualItem,@SeriesName,@UserDataKey,@SeasonName,@SeasonId,@SeriesId,@ExternalSeriesId,@Tagline,@ProviderIds,@Images,@ProductionLocations,@ExtraIds,@TotalBitrate,@ExtraType,@Artists,@AlbumArtists,@ExternalId,@SeriesPresentationUniqueKey,@ShowId,@OwnerId)"; values (@guid,@type,@data,@Path,@StartDate,@EndDate,@ChannelId,@IsMovie,@IsSeries,@EpisodeTitle,@IsRepeat,@CommunityRating,@CustomRating,@IndexNumber,@IsLocked,@Name,@OfficialRating,@MediaType,@Overview,@ParentIndexNumber,@PremiereDate,@ProductionYear,@ParentId,@Genres,@InheritedParentalRatingValue,@SortName,@ForcedSortName,@RunTimeTicks,@Size,@DateCreated,@DateModified,@PreferredMetadataLanguage,@PreferredMetadataCountryCode,@Width,@Height,@DateLastRefreshed,@DateLastSaved,@IsInMixedFolder,@LockedFields,@Studios,@Audio,@ExternalServiceId,@Tags,@IsFolder,@UnratedType,@TopParentId,@TrailerTypes,@CriticRating,@CleanName,@PresentationUniqueKey,@OriginalTitle,@PrimaryVersionId,@DateLastMediaAdded,@Album,@LUFS,@NormalizationGain,@IsVirtualItem,@SeriesName,@UserDataKey,@SeasonName,@SeasonId,@SeriesId,@ExternalSeriesId,@Tagline,@ProviderIds,@Images,@ProductionLocations,@ExtraIds,@TotalBitrate,@ExtraType,@Artists,@AlbumArtists,@ExternalId,@SeriesPresentationUniqueKey,@ShowId,@OwnerId)";
private readonly IServerConfigurationManager _config; private readonly IServerConfigurationManager _config;
private readonly IServerApplicationHost _appHost; private readonly IServerApplicationHost _appHost;
@ -111,6 +111,7 @@ namespace Emby.Server.Implementations.Data
"DateLastMediaAdded", "DateLastMediaAdded",
"Album", "Album",
"LUFS", "LUFS",
"NormalizationGain",
"CriticRating", "CriticRating",
"IsVirtualItem", "IsVirtualItem",
"SeriesName", "SeriesName",
@ -478,6 +479,7 @@ namespace Emby.Server.Implementations.Data
AddColumn(connection, "TypedBaseItems", "DateLastMediaAdded", "DATETIME", existingColumnNames); AddColumn(connection, "TypedBaseItems", "DateLastMediaAdded", "DATETIME", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "Album", "Text", existingColumnNames); AddColumn(connection, "TypedBaseItems", "Album", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "LUFS", "Float", existingColumnNames); AddColumn(connection, "TypedBaseItems", "LUFS", "Float", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "NormalizationGain", "Float", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "IsVirtualItem", "BIT", existingColumnNames); AddColumn(connection, "TypedBaseItems", "IsVirtualItem", "BIT", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "SeriesName", "Text", existingColumnNames); AddColumn(connection, "TypedBaseItems", "SeriesName", "Text", existingColumnNames);
AddColumn(connection, "TypedBaseItems", "UserDataKey", "Text", existingColumnNames); AddColumn(connection, "TypedBaseItems", "UserDataKey", "Text", existingColumnNames);
@ -886,6 +888,7 @@ namespace Emby.Server.Implementations.Data
saveItemStatement.TryBind("@Album", item.Album); saveItemStatement.TryBind("@Album", item.Album);
saveItemStatement.TryBind("@LUFS", item.LUFS); saveItemStatement.TryBind("@LUFS", item.LUFS);
saveItemStatement.TryBind("@NormalizationGain", item.NormalizationGain);
saveItemStatement.TryBind("@IsVirtualItem", item.IsVirtualItem); saveItemStatement.TryBind("@IsVirtualItem", item.IsVirtualItem);
if (item is IHasSeries hasSeriesName) if (item is IHasSeries hasSeriesName)
@ -1672,6 +1675,11 @@ namespace Emby.Server.Implementations.Data
item.LUFS = lUFS; item.LUFS = lUFS;
} }
if (reader.TryGetSingle(index++, out var normalizationGain))
{
item.NormalizationGain = normalizationGain;
}
if (reader.TryGetSingle(index++, out var criticRating)) if (reader.TryGetSingle(index++, out var criticRating))
{ {
item.CriticRating = criticRating; item.CriticRating = criticRating;

@ -898,7 +898,14 @@ namespace Emby.Server.Implementations.Dto
dto.IsPlaceHolder = supportsPlaceHolders.IsPlaceHolder; dto.IsPlaceHolder = supportsPlaceHolders.IsPlaceHolder;
} }
dto.LUFS = item.LUFS; if (item.LUFS.HasValue)
{
dto.NormalizationGain = -18f - item.LUFS;
}
else if (item.NormalizationGain.HasValue)
{
dto.NormalizationGain = item.NormalizationGain;
}
// Add audio info // Add audio info
if (item is Audio audio) if (item is Audio audio)

@ -13,7 +13,6 @@ using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers; using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;

@ -106,6 +106,8 @@
"TaskCleanCacheDescription": "Deletes cache files no longer needed by the system.", "TaskCleanCacheDescription": "Deletes cache files no longer needed by the system.",
"TaskRefreshChapterImages": "Extract Chapter Images", "TaskRefreshChapterImages": "Extract Chapter Images",
"TaskRefreshChapterImagesDescription": "Creates thumbnails for videos that have chapters.", "TaskRefreshChapterImagesDescription": "Creates thumbnails for videos that have chapters.",
"TaskAudioNormalization": "Audio Normalization",
"TaskAudioNormalizationDescription": "Scans files for audio normalization data.",
"TaskRefreshLibrary": "Scan Media Library", "TaskRefreshLibrary": "Scan Media Library",
"TaskRefreshLibraryDescription": "Scans your media library for new files and refreshes metadata.", "TaskRefreshLibraryDescription": "Scans your media library for new files and refreshes metadata.",
"TaskCleanLogs": "Clean Log Directory", "TaskCleanLogs": "Clean Log Directory",

@ -0,0 +1,196 @@
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 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 splashscreen post scan task.
/// </summary>
public partial class AudioNormalizationTask : IScheduledTask
{
private readonly IItemRepository _itemRepository;
private readonly ILibraryManager _libraryManager;
private readonly IMediaEncoder _mediaEncoder;
private readonly IConfigurationManager _configurationManager;
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="configurationManager">Instance of the <see cref="IConfigurationManager"/> 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,
IConfigurationManager configurationManager,
ILocalizationManager localizationManager,
ILogger<AudioNormalizationTask> logger)
{
_itemRepository = itemRepository;
_libraryManager = libraryManager;
_mediaEncoder = mediaEncoder;
_configurationManager = configurationManager;
_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(@"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;
}
var albumTracks = ((MusicAlbum)a).Tracks.Where(x => x.IsFileProtocol).ToList();
if (albumTracks.Count == 0)
{
continue;
}
var tempFile = Path.Join(_configurationManager.GetTranscodePath(), Guid.NewGuid() + ".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);
a.LUFS = await CalculateLUFSAsync(
string.Format(CultureInfo.InvariantCulture, "-f concat -safe 0 -i \"{0}\"", tempFile),
cancellationToken).ConfigureAwait(false);
File.Delete(tempFile);
}
_itemRepository.SaveItems(albums, cancellationToken);
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 string EscapeFilename(string filename)
=> filename;
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;
var output = await reader.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
MatchCollection split = LUFSRegex().Matches(output);
if (split.Count != 0)
{
return float.Parse(split[0].Groups[1].ValueSpan, CultureInfo.InvariantCulture.NumberFormat);
}
_logger.LogError("Failed to find LUFS value in output:\n{Output}", output);
return null;
}
}
}

@ -13,7 +13,6 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Tasks;

@ -183,14 +183,13 @@ namespace MediaBrowser.Controller.Entities.Audio
progress.Report(percent * 95); progress.Report(percent * 95);
} }
// get album LUFS
LUFS = items.OfType<Audio>().Max(item => item.LUFS);
var parentRefreshOptions = refreshOptions; var parentRefreshOptions = refreshOptions;
if (childUpdateType > ItemUpdateType.None) if (childUpdateType > ItemUpdateType.None)
{ {
parentRefreshOptions = new MetadataRefreshOptions(refreshOptions); parentRefreshOptions = new MetadataRefreshOptions(refreshOptions)
parentRefreshOptions.MetadataRefreshMode = MetadataRefreshMode.FullRefresh; {
MetadataRefreshMode = MetadataRefreshMode.FullRefresh
};
} }
// Refresh current item // Refresh current item

@ -137,6 +137,13 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore] [JsonIgnore]
public float? LUFS { get; set; } public float? LUFS { get; set; }
/// <summary>
/// Gets or sets the gain required for audio normalization.
/// </summary>
/// <value>The gain required for audio normalization..</value>
[JsonIgnore]
public float? NormalizationGain { get; set; }
/// <summary> /// <summary>
/// Gets or sets the channel identifier. /// Gets or sets the channel identifier.
/// </summary> /// </summary>

@ -782,10 +782,10 @@ namespace MediaBrowser.Model.Dto
public string TimerId { get; set; } public string TimerId { get; set; }
/// <summary> /// <summary>
/// Gets or sets the LUFS value. /// Gets or sets the gain required for audio normalization.
/// </summary> /// </summary>
/// <value>The LUFS Value.</value> /// <value>The gain required for audio normalization..</value>
public float? LUFS { get; set; } public float? NormalizationGain { get; set; }
/// <summary> /// <summary>
/// Gets or sets the current program. /// Gets or sets the current program.

@ -1,9 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
@ -18,7 +15,6 @@ using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto; using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using Microsoft.Extensions.Logging;
using TagLib; using TagLib;
namespace MediaBrowser.Providers.MediaInfo namespace MediaBrowser.Providers.MediaInfo
@ -26,12 +22,8 @@ namespace MediaBrowser.Providers.MediaInfo
/// <summary> /// <summary>
/// Probes audio files for metadata. /// Probes audio files for metadata.
/// </summary> /// </summary>
public partial class AudioFileProber public class AudioFileProber
{ {
// Default LUFS value for use with the web interface, at -18db gain will be 1(no db gain).
private const float DefaultLUFSValue = -18;
private readonly ILogger<AudioFileProber> _logger;
private readonly IMediaEncoder _mediaEncoder; private readonly IMediaEncoder _mediaEncoder;
private readonly IItemRepository _itemRepo; private readonly IItemRepository _itemRepo;
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
@ -42,7 +34,6 @@ namespace MediaBrowser.Providers.MediaInfo
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AudioFileProber"/> class. /// Initializes a new instance of the <see cref="AudioFileProber"/> class.
/// </summary> /// </summary>
/// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param> /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param> /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
/// <param name="itemRepo">Instance of the <see cref="IItemRepository"/> interface.</param> /// <param name="itemRepo">Instance of the <see cref="IItemRepository"/> interface.</param>
@ -50,7 +41,6 @@ namespace MediaBrowser.Providers.MediaInfo
/// <param name="lyricResolver">Instance of the <see cref="LyricResolver"/> interface.</param> /// <param name="lyricResolver">Instance of the <see cref="LyricResolver"/> interface.</param>
/// <param name="lyricManager">Instance of the <see cref="ILyricManager"/> interface.</param> /// <param name="lyricManager">Instance of the <see cref="ILyricManager"/> interface.</param>
public AudioFileProber( public AudioFileProber(
ILogger<AudioFileProber> logger,
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,
IMediaEncoder mediaEncoder, IMediaEncoder mediaEncoder,
IItemRepository itemRepo, IItemRepository itemRepo,
@ -58,7 +48,6 @@ namespace MediaBrowser.Providers.MediaInfo
LyricResolver lyricResolver, LyricResolver lyricResolver,
ILyricManager lyricManager) ILyricManager lyricManager)
{ {
_logger = logger;
_mediaEncoder = mediaEncoder; _mediaEncoder = mediaEncoder;
_itemRepo = itemRepo; _itemRepo = itemRepo;
_libraryManager = libraryManager; _libraryManager = libraryManager;
@ -67,9 +56,6 @@ namespace MediaBrowser.Providers.MediaInfo
_lyricManager = lyricManager; _lyricManager = lyricManager;
} }
[GeneratedRegex(@"I:\s+(.*?)\s+LUFS")]
private static partial Regex LUFSRegex();
/// <summary> /// <summary>
/// Probes the specified item for metadata. /// Probes the specified item for metadata.
/// </summary> /// </summary>
@ -112,45 +98,6 @@ namespace MediaBrowser.Providers.MediaInfo
await FetchAsync(item, result, options, cancellationToken).ConfigureAwait(false); await FetchAsync(item, result, options, cancellationToken).ConfigureAwait(false);
} }
var libraryOptions = _libraryManager.GetLibraryOptions(item);
if (libraryOptions.EnableLUFSScan && item.LUFS is null)
{
using (var process = new Process()
{
StartInfo = new ProcessStartInfo
{
FileName = _mediaEncoder.EncoderPath,
Arguments = $"-hide_banner -i \"{path}\" -af ebur128=framelog=verbose -f null -",
RedirectStandardOutput = false,
RedirectStandardError = true
},
})
{
try
{
process.Start();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error starting ffmpeg");
throw;
}
using var reader = process.StandardError;
var output = await reader.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
MatchCollection split = LUFSRegex().Matches(output);
if (split.Count != 0)
{
item.LUFS = float.Parse(split[0].Groups[1].ValueSpan, CultureInfo.InvariantCulture.NumberFormat);
}
}
}
_logger.LogDebug("LUFS for {ItemName} is {LUFS}.", item.Name, item.LUFS);
return ItemUpdateType.MetadataImport; return ItemUpdateType.MetadataImport;
} }
@ -339,7 +286,7 @@ namespace MediaBrowser.Providers.MediaInfo
if (!double.IsNaN(tags.ReplayGainTrackGain)) if (!double.IsNaN(tags.ReplayGainTrackGain))
{ {
audio.LUFS = DefaultLUFSValue - (float)tags.ReplayGainTrackGain; audio.NormalizationGain = (float)tags.ReplayGainTrackGain;
} }
if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzArtist, out _)) if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzArtist, out _))

@ -103,7 +103,6 @@ namespace MediaBrowser.Providers.MediaInfo
_subtitleResolver); _subtitleResolver);
_audioProber = new AudioFileProber( _audioProber = new AudioFileProber(
loggerFactory.CreateLogger<AudioFileProber>(),
mediaSourceManager, mediaSourceManager,
mediaEncoder, mediaEncoder,
itemRepo, itemRepo,

Loading…
Cancel
Save