Use music metadata from ffprobe when TagLib fails

TagLib has its own limitations, which cause it to fail on certain audio files or extract incomplete information from the tags. Use the information from ffprobe when TagLib fails to extract data.

Signed-off-by: gnattu <gnattuoc@me.com>
This commit is contained in:
gnattu 2024-05-28 14:24:36 +08:00
parent 730b01fb14
commit e8d1ee0934

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -151,197 +152,213 @@ namespace MediaBrowser.Providers.MediaInfo
/// <param name="tryExtractEmbeddedLyrics">Whether to extract embedded lyrics to lrc file. </param> /// <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) 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; Tag? tags = null;
try
{
using var file = TagLib.File.Create(audio.Path);
var tagTypes = file.TagTypesOnDisk;
if (tagTypes.HasFlag(TagTypes.Id3v2)) 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);
}
if (tags is not null)
{
if (audio.SupportsPeople && !audio.LockedFields.Contains(MetadataField.Cast))
{ {
var people = new List<PersonInfo>(); tags = file.GetTag(TagTypes.Id3v2);
var albumArtists = tags.AlbumArtists; }
foreach (var albumArtist in albumArtists) 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);
}
}
catch (Exception e)
{
_logger.LogWarning("TagLib-Sharp does not support this audio: {Exception}", e);
}
finally
{
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))
{
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; var performers = tags.Performers;
foreach (var performer in 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); _libraryManager.UpdatePeople(audio, people);
if (options.ReplaceAllMetadata && performers.Length != 0) 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 (albumArtists.Length == 0)
{
// Album artists not provided, fall back to performers (artists).
albumArtists = performers;
}
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 (!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)
{
try
{ {
audio.Artists = performers; audio.PremiereDate = new DateTime(year, 01, 01);
} }
else if (!options.ReplaceAllMetadata catch (ArgumentOutOfRangeException ex)
&& (audio.Artists is null || audio.Artists.Count == 0))
{ {
audio.Artists = performers; _logger.LogError(ex, "Error parsing YEAR tag in {File}. '{TagValue}' is an invalid year", audio.Path, tags.Year);
}
if (albumArtists.Length == 0)
{
// Album artists not provided, fall back to performers (artists).
albumArtists = performers;
}
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 (!audio.LockedFields.Contains(MetadataField.Name) && !string.IsNullOrEmpty(tags.Title)) if (!audio.LockedFields.Contains(MetadataField.Genres))
{ {
audio.Name = tags.Title; audio.Genres = options.ReplaceAllMetadata || audio.Genres == null || audio.Genres.Length == 0
} ? tags.Genres.Distinct(StringComparer.OrdinalIgnoreCase).ToArray()
: audio.Genres;
}
if (options.ReplaceAllMetadata) if (!double.IsNaN(tags.ReplayGainTrackGain))
{ {
audio.Album = tags.Album; audio.NormalizationGain = (float)tags.ReplayGainTrackGain;
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) if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzArtist, out _))
{ {
var year = Convert.ToInt32(tags.Year); audio.SetProviderId(MetadataProvider.MusicBrainzArtist, tags.MusicBrainzArtistId);
audio.ProductionYear = year; }
if (!audio.PremiereDate.HasValue) if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzAlbumArtist, out _))
{ {
try audio.SetProviderId(MetadataProvider.MusicBrainzAlbumArtist, tags.MusicBrainzReleaseArtistId);
{ }
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)) if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzAlbum, out _))
{ {
audio.Genres = options.ReplaceAllMetadata || audio.Genres == null || audio.Genres.Length == 0 audio.SetProviderId(MetadataProvider.MusicBrainzAlbum, tags.MusicBrainzReleaseId);
? tags.Genres.Distinct(StringComparer.OrdinalIgnoreCase).ToArray() }
: audio.Genres;
}
if (!double.IsNaN(tags.ReplayGainTrackGain)) if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzReleaseGroup, out _))
{ {
audio.NormalizationGain = (float)tags.ReplayGainTrackGain; audio.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, tags.MusicBrainzReleaseGroupId);
} }
if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzArtist, out _)) 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)
{ {
audio.SetProviderId(MetadataProvider.MusicBrainzArtist, tags.MusicBrainzArtistId); audio.SetProviderId(MetadataProvider.MusicBrainzTrack, trackMbId);
} }
}
if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzAlbumArtist, out _)) // Save extracted lyrics if they exist,
{ // and if the audio doesn't yet have lyrics.
audio.SetProviderId(MetadataProvider.MusicBrainzAlbumArtist, tags.MusicBrainzReleaseArtistId); if (!string.IsNullOrWhiteSpace(tags.Lyrics)
} && tryExtractEmbeddedLyrics)
{
if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzAlbum, out _)) await _lyricManager.SaveLyricAsync(audio, "lrc", tags.Lyrics).ConfigureAwait(false);
{
audio.SetProviderId(MetadataProvider.MusicBrainzAlbum, tags.MusicBrainzReleaseId);
}
if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzReleaseGroup, out _))
{
audio.SetProviderId(MetadataProvider.MusicBrainzReleaseGroup, tags.MusicBrainzReleaseGroupId);
}
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)
{
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);
}
} }
} }