diff options
Diffstat (limited to 'MediaBrowser.Providers/MediaInfo/AudioFileProber.cs')
| -rw-r--r-- | MediaBrowser.Providers/MediaInfo/AudioFileProber.cs | 418 |
1 files changed, 185 insertions, 233 deletions
diff --git a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs index c9fe4c9b6..0083d4f75 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs @@ -1,12 +1,11 @@ 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; +using Jellyfin.Extensions; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; @@ -26,15 +25,12 @@ namespace MediaBrowser.Providers.MediaInfo /// <summary> /// Probes audio files for metadata. /// </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 IItemRepository _itemRepo; private readonly ILibraryManager _libraryManager; + private readonly ILogger<AudioFileProber> _logger; private readonly IMediaSourceManager _mediaSourceManager; private readonly LyricResolver _lyricResolver; private readonly ILyricManager _lyricManager; @@ -58,21 +54,15 @@ namespace MediaBrowser.Providers.MediaInfo LyricResolver lyricResolver, ILyricManager lyricManager) { - _logger = logger; _mediaEncoder = mediaEncoder; _itemRepo = itemRepo; _libraryManager = libraryManager; + _logger = logger; _mediaSourceManager = mediaSourceManager; _lyricResolver = lyricResolver; _lyricManager = lyricManager; } - [GeneratedRegex(@"I:\s+(.*?)\s+LUFS")] - private static partial Regex LUFSRegex(); - - [GeneratedRegex(@"REPLAYGAIN_TRACK_GAIN:\s+-?([0-9.]+)\s+dB")] - private static partial Regex ReplayGainTagRegex(); - /// <summary> /// Probes the specified item for metadata. /// </summary> @@ -115,97 +105,6 @@ namespace MediaBrowser.Providers.MediaInfo await FetchAsync(item, result, options, cancellationToken).ConfigureAwait(false); } - var libraryOptions = _libraryManager.GetLibraryOptions(item); - bool foundLUFSValue = false; - - if (libraryOptions.UseReplayGainTags) - { - using (var process = new Process() - { - StartInfo = new ProcessStartInfo - { - FileName = _mediaEncoder.ProbePath, - Arguments = $"-hide_banner -i \"{path}\"", - 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(); - Match split = ReplayGainTagRegex().Match(output); - - if (split.Success) - { - item.LUFS = DefaultLUFSValue - float.Parse(split.Groups[1].ValueSpan, CultureInfo.InvariantCulture.NumberFormat); - foundLUFSValue = true; - } - else - { - item.LUFS = DefaultLUFSValue; - } - } - } - - if (libraryOptions.EnableLUFSScan && !foundLUFSValue) - { - 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); - } - else - { - item.LUFS = DefaultLUFSValue; - } - } - } - - if (!libraryOptions.EnableLUFSScan && !libraryOptions.UseReplayGainTags) - { - item.LUFS = DefaultLUFSValue; - } - - _logger.LogDebug("LUFS for {ItemName} is {LUFS}.", item.Name, item.LUFS); - return ItemUpdateType.MetadataImport; } @@ -230,14 +129,20 @@ namespace MediaBrowser.Providers.MediaInfo audio.Size = mediaInfo.Size; audio.PremiereDate = mediaInfo.PremiereDate; + // Add external lyrics first to prevent the lrc file get overwritten on first scan + var mediaStreams = new List<MediaStream>(mediaInfo.MediaStreams); + AddExternalLyrics(audio, mediaStreams, options); + var tryExtractEmbeddedLyrics = mediaStreams.All(s => s.Type != MediaStreamType.Lyric); + if (!audio.IsLocked) { - await FetchDataFromTags(audio, options).ConfigureAwait(false); + await FetchDataFromTags(audio, mediaInfo, options, tryExtractEmbeddedLyrics).ConfigureAwait(false); + if (tryExtractEmbeddedLyrics) + { + AddExternalLyrics(audio, mediaStreams, options); + } } - var mediaStreams = new List<MediaStream>(mediaInfo.MediaStreams); - AddExternalLyrics(audio, mediaStreams, options); - audio.HasLyrics = mediaStreams.Any(s => s.Type == MediaStreamType.Lyric); _itemRepo.SaveMediaStreams(audio.Id, mediaStreams, cancellationToken); @@ -247,177 +152,221 @@ namespace MediaBrowser.Providers.MediaInfo /// Fetches data from the tags. /// </summary> /// <param name="audio">The <see cref="Audio"/>.</param> + /// <param name="mediaInfo">The <see cref="Model.MediaInfo.MediaInfo"/>.</param> /// <param name="options">The <see cref="MetadataRefreshOptions"/>.</param> - private async Task FetchDataFromTags(Audio audio, MetadataRefreshOptions options) + /// <param name="tryExtractEmbeddedLyrics">Whether to extract embedded lyrics to lrc file. </param> + private async Task FetchDataFromTags(Audio audio, Model.MediaInfo.MediaInfo mediaInfo, MetadataRefreshOptions options, bool tryExtractEmbeddedLyrics) { - using var file = TagLib.File.Create(audio.Path); - var tagTypes = file.TagTypesOnDisk; Tag? tags = null; - - if (tagTypes.HasFlag(TagTypes.Id3v2)) - { - tags = file.GetTag(TagTypes.Id3v2); - } - else if (tagTypes.HasFlag(TagTypes.Ape)) - { - tags = file.GetTag(TagTypes.Ape); - } - else if (tagTypes.HasFlag(TagTypes.FlacMetadata)) - { - tags = file.GetTag(TagTypes.FlacMetadata); - } - else if (tagTypes.HasFlag(TagTypes.Apple)) + try { - tags = file.GetTag(TagTypes.Apple); - } - else if (tagTypes.HasFlag(TagTypes.Xiph)) - { - tags = file.GetTag(TagTypes.Xiph); - } - else if (tagTypes.HasFlag(TagTypes.AudibleMetadata)) - { - tags = file.GetTag(TagTypes.AudibleMetadata); + using var file = TagLib.File.Create(audio.Path); + var tagTypes = file.TagTypesOnDisk; + + if (tagTypes.HasFlag(TagTypes.Id3v2)) + { + tags = file.GetTag(TagTypes.Id3v2); + } + else if (tagTypes.HasFlag(TagTypes.Ape)) + { + tags = file.GetTag(TagTypes.Ape); + } + else if (tagTypes.HasFlag(TagTypes.FlacMetadata)) + { + tags = file.GetTag(TagTypes.FlacMetadata); + } + else if (tagTypes.HasFlag(TagTypes.Apple)) + { + tags = file.GetTag(TagTypes.Apple); + } + else if (tagTypes.HasFlag(TagTypes.Xiph)) + { + tags = file.GetTag(TagTypes.Xiph); + } + else if (tagTypes.HasFlag(TagTypes.AudibleMetadata)) + { + tags = file.GetTag(TagTypes.AudibleMetadata); + } + else if (tagTypes.HasFlag(TagTypes.Id3v1)) + { + tags = file.GetTag(TagTypes.Id3v1); + } } - else if (tagTypes.HasFlag(TagTypes.Id3v1)) + catch (Exception e) { - tags = file.GetTag(TagTypes.Id3v1); + _logger.LogWarning(e, "TagLib-Sharp does not support this audio"); } - if (tags is not null) + tags ??= new TagLib.Id3v2.Tag(); + tags.AlbumArtists ??= mediaInfo.AlbumArtists; + tags.Album ??= mediaInfo.Album; + tags.Title ??= mediaInfo.Name; + tags.Year = tags.Year == 0U ? Convert.ToUInt32(mediaInfo.ProductionYear, CultureInfo.InvariantCulture) : tags.Year; + tags.Performers ??= mediaInfo.Artists; + tags.Genres ??= mediaInfo.Genres; + tags.Track = tags.Track == 0U ? Convert.ToUInt32(mediaInfo.IndexNumber, CultureInfo.InvariantCulture) : tags.Track; + tags.Disc = tags.Disc == 0U ? Convert.ToUInt32(mediaInfo.ParentIndexNumber, CultureInfo.InvariantCulture) : tags.Disc; + + if (audio.SupportsPeople && !audio.LockedFields.Contains(MetadataField.Cast)) { - if (audio.SupportsPeople && !audio.LockedFields.Contains(MetadataField.Cast)) + var people = new List<PersonInfo>(); + var albumArtists = tags.AlbumArtists; + foreach (var albumArtist in albumArtists) { - var people = new List<PersonInfo>(); - var albumArtists = tags.AlbumArtists; - foreach (var albumArtist in albumArtists) + if (!string.IsNullOrEmpty(albumArtist)) { - if (!string.IsNullOrEmpty(albumArtist)) + PeopleHelper.AddPerson(people, new PersonInfo { - PeopleHelper.AddPerson(people, new PersonInfo - { - Name = albumArtist, - Type = PersonKind.AlbumArtist - }); - } + Name = albumArtist, + Type = PersonKind.AlbumArtist + }); } + } - var performers = tags.Performers; - foreach (var performer in performers) + var performers = tags.Performers; + foreach (var performer in performers) + { + if (!string.IsNullOrEmpty(performer)) { - if (!string.IsNullOrEmpty(performer)) + PeopleHelper.AddPerson(people, new PersonInfo { - PeopleHelper.AddPerson(people, new PersonInfo - { - Name = performer, - Type = PersonKind.Artist - }); - } + Name = performer, + Type = PersonKind.Artist + }); } + } - foreach (var composer in tags.Composers) + foreach (var composer in tags.Composers) + { + if (!string.IsNullOrEmpty(composer)) { - if (!string.IsNullOrEmpty(composer)) + PeopleHelper.AddPerson(people, new PersonInfo { - PeopleHelper.AddPerson(people, new PersonInfo - { - Name = composer, - Type = PersonKind.Composer - }); - } + Name = composer, + Type = PersonKind.Composer + }); } + } - _libraryManager.UpdatePeople(audio, people); - - if (options.ReplaceAllMetadata && performers.Length != 0) - { - audio.Artists = performers; - } - else if (!options.ReplaceAllMetadata - && (audio.Artists is null || audio.Artists.Count == 0)) - { - audio.Artists = performers; - } + _libraryManager.UpdatePeople(audio, people); - if (options.ReplaceAllMetadata && albumArtists.Length != 0) - { - audio.AlbumArtists = albumArtists; - } - else if (!options.ReplaceAllMetadata - && (audio.AlbumArtists is null || audio.AlbumArtists.Count == 0)) - { - audio.AlbumArtists = albumArtists; - } + if (options.ReplaceAllMetadata && performers.Length != 0) + { + audio.Artists = performers; + } + else if (!options.ReplaceAllMetadata + && (audio.Artists is null || audio.Artists.Count == 0)) + { + audio.Artists = performers; } - if (!audio.LockedFields.Contains(MetadataField.Name) && !string.IsNullOrEmpty(tags.Title)) + if (albumArtists.Length == 0) { - audio.Name = tags.Title; + // Album artists not provided, fall back to performers (artists). + albumArtists = performers; } - if (options.ReplaceAllMetadata) + if (options.ReplaceAllMetadata && albumArtists.Length != 0) { - audio.Album = tags.Album; - audio.IndexNumber = Convert.ToInt32(tags.Track); - audio.ParentIndexNumber = Convert.ToInt32(tags.Disc); + audio.AlbumArtists = albumArtists; } - else + else if (!options.ReplaceAllMetadata + && (audio.AlbumArtists is null || audio.AlbumArtists.Count == 0)) { - audio.Album ??= tags.Album; - audio.IndexNumber ??= Convert.ToInt32(tags.Track); - audio.ParentIndexNumber ??= Convert.ToInt32(tags.Disc); + audio.AlbumArtists = albumArtists; } + } - if (tags.Year != 0) - { - var year = Convert.ToInt32(tags.Year); - audio.ProductionYear = year; + if (!audio.LockedFields.Contains(MetadataField.Name) && !string.IsNullOrEmpty(tags.Title)) + { + audio.Name = tags.Title; + } + + if (options.ReplaceAllMetadata) + { + audio.Album = tags.Album; + audio.IndexNumber = Convert.ToInt32(tags.Track); + audio.ParentIndexNumber = Convert.ToInt32(tags.Disc); + } + else + { + 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); + audio.ProductionYear = year; - if (!audio.PremiereDate.HasValue) + if (!audio.PremiereDate.HasValue) + { + try { audio.PremiereDate = new DateTime(year, 01, 01); } + catch (ArgumentOutOfRangeException ex) + { + _logger.LogError(ex, "Error parsing YEAR tag in {File}. '{TagValue}' is an invalid year", audio.Path, tags.Year); + } } + } - if (!audio.LockedFields.Contains(MetadataField.Genres)) - { - audio.Genres = options.ReplaceAllMetadata || audio.Genres == null || audio.Genres.Length == 0 - ? tags.Genres.Distinct(StringComparer.OrdinalIgnoreCase).ToArray() - : audio.Genres; - } + if (!audio.LockedFields.Contains(MetadataField.Genres)) + { + audio.Genres = options.ReplaceAllMetadata || audio.Genres == null || audio.Genres.Length == 0 + ? tags.Genres.Distinct(StringComparer.OrdinalIgnoreCase).ToArray() + : audio.Genres; + } - if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzArtist, out _)) - { - audio.SetProviderId(MetadataProvider.MusicBrainzArtist, tags.MusicBrainzArtistId); - } + if (!double.IsNaN(tags.ReplayGainTrackGain)) + { + audio.NormalizationGain = (float)tags.ReplayGainTrackGain; + } - if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzAlbumArtist, out _)) - { - audio.SetProviderId(MetadataProvider.MusicBrainzAlbumArtist, tags.MusicBrainzReleaseArtistId); - } + if ((options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzArtist, out _)) + && !string.IsNullOrEmpty(tags.MusicBrainzArtistId)) + { + audio.SetProviderId(MetadataProvider.MusicBrainzArtist, tags.MusicBrainzArtistId); + } - if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzAlbum, out _)) - { - audio.SetProviderId(MetadataProvider.MusicBrainzAlbum, tags.MusicBrainzReleaseId); - } + if ((options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzAlbumArtist, out _)) + && !string.IsNullOrEmpty(tags.MusicBrainzReleaseArtistId)) + { + audio.SetProviderId(MetadataProvider.MusicBrainzAlbumArtist, tags.MusicBrainzReleaseArtistId); + } - if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzReleaseGroup, out _)) - { - audio.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, tags.MusicBrainzReleaseGroupId); - } + if ((options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzAlbum, out _)) + && !string.IsNullOrEmpty(tags.MusicBrainzReleaseId)) + { + audio.SetProviderId(MetadataProvider.MusicBrainzAlbum, tags.MusicBrainzReleaseId); + } - if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzTrack, out _)) - { - audio.SetProviderId(MetadataProvider.MusicBrainzTrack, tags.MusicBrainzTrackId); - } + if ((options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzReleaseGroup, out _)) + && !string.IsNullOrEmpty(tags.MusicBrainzReleaseGroupId)) + { + audio.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, tags.MusicBrainzReleaseGroupId); + } - // Save extracted lyrics if they exist, - // and if we are replacing all metadata or the audio doesn't yet have lyrics. - if (!string.IsNullOrWhiteSpace(tags.Lyrics) - && (options.ReplaceAllMetadata || audio.GetMediaStreams().All(s => s.Type != MediaStreamType.Lyric))) + if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzTrack, out _)) + { + // Fallback to ffprobe as TagLib incorrectly provides recording MBID in `tags.MusicBrainzTrackId`. + // See https://github.com/mono/taglib-sharp/issues/304 + var trackMbId = mediaInfo.GetProviderId(MetadataProvider.MusicBrainzTrack); + if (trackMbId is not null) { - await _lyricManager.SaveLyricAsync(audio, "lrc", tags.Lyrics).ConfigureAwait(false); + audio.SetProviderId(MetadataProvider.MusicBrainzTrack, trackMbId); } } + + // Save extracted lyrics if they exist, + // and if the audio doesn't yet have lyrics. + if (!string.IsNullOrWhiteSpace(tags.Lyrics) + && tryExtractEmbeddedLyrics) + { + await _lyricManager.SaveLyricAsync(audio, "lrc", tags.Lyrics).ConfigureAwait(false); + } } private void AddExternalLyrics( @@ -429,7 +378,10 @@ namespace MediaBrowser.Providers.MediaInfo var externalLyricFiles = _lyricResolver.GetExternalStreams(audio, startIndex, options.DirectoryService, false); audio.LyricFiles = externalLyricFiles.Select(i => i.Path).Distinct().ToArray(); - currentStreams.AddRange(externalLyricFiles); + if (externalLyricFiles.Count > 0) + { + currentStreams.Add(externalLyricFiles[0]); + } } } } |
