mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-06-26 08:58:07 +02:00
Audio normalization (#9222)
Co-authored-by: Joe Rogers <1337joe@users.noreply.github.com> Co-authored-by: Bond-009 <bond.009@outlook.com>
This commit is contained in:
parent
47290a8c36
commit
603fce59df
|
@ -126,6 +126,7 @@
|
|||
- [SuperSandro2000](https://github.com/SuperSandro2000)
|
||||
- [tbraeutigam](https://github.com/tbraeutigam)
|
||||
- [teacupx](https://github.com/teacupx)
|
||||
- [TelepathicWalrus](https://github.com/TelepathicWalrus)
|
||||
- [Terror-Gene](https://github.com/Terror-Gene)
|
||||
- [ThatNerdyPikachu](https://github.com/ThatNerdyPikachu)
|
||||
- [ThibaultNocchi](https://github.com/ThibaultNocchi)
|
||||
|
|
|
@ -49,8 +49,8 @@ namespace Emby.Server.Implementations.Data
|
|||
|
||||
private const string SaveItemCommandText =
|
||||
@"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,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,@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,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)";
|
||||
|
||||
private readonly IServerConfigurationManager _config;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
|
@ -110,6 +110,7 @@ namespace Emby.Server.Implementations.Data
|
|||
"PrimaryVersionId",
|
||||
"DateLastMediaAdded",
|
||||
"Album",
|
||||
"LUFS",
|
||||
"CriticRating",
|
||||
"IsVirtualItem",
|
||||
"SeriesName",
|
||||
|
@ -489,6 +490,7 @@ namespace Emby.Server.Implementations.Data
|
|||
AddColumn(db, "TypedBaseItems", "PrimaryVersionId", "Text", existingColumnNames);
|
||||
AddColumn(db, "TypedBaseItems", "DateLastMediaAdded", "DATETIME", existingColumnNames);
|
||||
AddColumn(db, "TypedBaseItems", "Album", "Text", existingColumnNames);
|
||||
AddColumn(db, "TypedBaseItems", "LUFS", "Float", existingColumnNames);
|
||||
AddColumn(db, "TypedBaseItems", "IsVirtualItem", "BIT", existingColumnNames);
|
||||
AddColumn(db, "TypedBaseItems", "SeriesName", "Text", existingColumnNames);
|
||||
AddColumn(db, "TypedBaseItems", "UserDataKey", "Text", existingColumnNames);
|
||||
|
@ -906,6 +908,7 @@ namespace Emby.Server.Implementations.Data
|
|||
}
|
||||
|
||||
saveItemStatement.TryBind("@Album", item.Album);
|
||||
saveItemStatement.TryBind("@LUFS", item.LUFS);
|
||||
saveItemStatement.TryBind("@IsVirtualItem", item.IsVirtualItem);
|
||||
|
||||
if (item is IHasSeries hasSeriesName)
|
||||
|
@ -1756,6 +1759,11 @@ namespace Emby.Server.Implementations.Data
|
|||
item.Album = album;
|
||||
}
|
||||
|
||||
if (reader.TryGetSingle(index++, out var lUFS))
|
||||
{
|
||||
item.LUFS = lUFS;
|
||||
}
|
||||
|
||||
if (reader.TryGetSingle(index++, out var criticRating))
|
||||
{
|
||||
item.CriticRating = criticRating;
|
||||
|
|
|
@ -906,6 +906,7 @@ namespace Emby.Server.Implementations.Dto
|
|||
// Add audio info
|
||||
if (item is Audio audio)
|
||||
{
|
||||
dto.LUFS = audio.LUFS;
|
||||
dto.Album = audio.Album;
|
||||
if (audio.ExtraType.HasValue)
|
||||
{
|
||||
|
|
|
@ -128,6 +128,13 @@ namespace MediaBrowser.Controller.Entities
|
|||
[JsonIgnore]
|
||||
public string Album { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LUFS value.
|
||||
/// </summary>
|
||||
/// <value>The LUFS Value.</value>
|
||||
[JsonIgnore]
|
||||
public float LUFS { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the channel identifier.
|
||||
/// </summary>
|
||||
|
|
|
@ -30,6 +30,8 @@ namespace MediaBrowser.Model.Configuration
|
|||
|
||||
public bool EnableRealtimeMonitor { get; set; }
|
||||
|
||||
public bool EnableLUFSScan { get; set; }
|
||||
|
||||
public bool EnableChapterImageExtraction { get; set; }
|
||||
|
||||
public bool ExtractChapterImagesDuringLibraryScan { get; set; }
|
||||
|
|
|
@ -779,6 +779,12 @@ namespace MediaBrowser.Model.Dto
|
|||
/// <value>The timer identifier.</value>
|
||||
public string TimerId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the LUFS value.
|
||||
/// </summary>
|
||||
/// <value>The LUFS Value.</value>
|
||||
public float LUFS { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current program.
|
||||
/// </summary>
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Data.Enums;
|
||||
|
@ -14,6 +17,7 @@ using MediaBrowser.Model.Dlna;
|
|||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TagLib;
|
||||
|
||||
namespace MediaBrowser.Providers.MediaInfo
|
||||
|
@ -23,6 +27,10 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||
/// </summary>
|
||||
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 IItemRepository _itemRepo;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
@ -31,16 +39,19 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AudioFileProber"/> class.
|
||||
/// </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="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
|
||||
/// <param name="itemRepo">Instance of the <see cref="IItemRepository"/> interface.</param>
|
||||
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
|
||||
public AudioFileProber(
|
||||
ILogger<AudioFileProber> logger,
|
||||
IMediaSourceManager mediaSourceManager,
|
||||
IMediaEncoder mediaEncoder,
|
||||
IItemRepository itemRepo,
|
||||
ILibraryManager libraryManager)
|
||||
{
|
||||
_logger = logger;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_itemRepo = itemRepo;
|
||||
_libraryManager = libraryManager;
|
||||
|
@ -89,6 +100,54 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||
Fetch(item, result, cancellationToken);
|
||||
}
|
||||
|
||||
var libraryOptions = _libraryManager.GetLibraryOptions(item);
|
||||
|
||||
if (libraryOptions.EnableLUFSScan)
|
||||
{
|
||||
string output;
|
||||
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;
|
||||
}
|
||||
|
||||
output = await process.StandardError.ReadToEndAsync(cancellationToken).ConfigureAwait(false);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
MatchCollection split = Regex.Matches(output, @"I:\s+(.*?)\s+LUFS");
|
||||
|
||||
if (split.Count != 0)
|
||||
{
|
||||
item.LUFS = float.Parse(split[0].Groups[1].ValueSpan, CultureInfo.InvariantCulture.NumberFormat);
|
||||
}
|
||||
else
|
||||
{
|
||||
item.LUFS = DefaultLUFSValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
item.LUFS = DefaultLUFSValue;
|
||||
}
|
||||
|
||||
_logger.LogDebug("LUFS for {ItemName} is {LUFS}.", item.Name, item.LUFS);
|
||||
|
||||
return ItemUpdateType.MetadataImport;
|
||||
}
|
||||
|
||||
|
@ -196,6 +255,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||
audio.Album = tags.Album;
|
||||
audio.IndexNumber = Convert.ToInt32(tags.Track);
|
||||
audio.ParentIndexNumber = Convert.ToInt32(tags.Disc);
|
||||
|
||||
if (tags.Year != 0)
|
||||
{
|
||||
var year = Convert.ToInt32(tags.Year);
|
||||
|
|
|
@ -79,7 +79,7 @@ namespace MediaBrowser.Providers.MediaInfo
|
|||
NamingOptions namingOptions)
|
||||
{
|
||||
_logger = loggerFactory.CreateLogger<ProbeProvider>();
|
||||
_audioProber = new AudioFileProber(mediaSourceManager, mediaEncoder, itemRepo, libraryManager);
|
||||
_audioProber = new AudioFileProber(loggerFactory.CreateLogger<AudioFileProber>(), mediaSourceManager, mediaEncoder, itemRepo, libraryManager);
|
||||
_audioResolver = new AudioResolver(loggerFactory.CreateLogger<AudioResolver>(), localization, mediaEncoder, fileSystem, namingOptions);
|
||||
_subtitleResolver = new SubtitleResolver(loggerFactory.CreateLogger<SubtitleResolver>(), localization, mediaEncoder, fileSystem, namingOptions);
|
||||
_videoProber = new FFProbeVideoInfo(
|
||||
|
|
Loading…
Reference in a new issue