Quellcode durchsuchen

Terminate at null char for audio tags (#14100)

gnattu vor 2 Wochen
Ursprung
Commit
9d601f8e9b

+ 58 - 24
MediaBrowser.Providers/MediaInfo/AudioFileProber.cs

@@ -181,10 +181,18 @@ namespace MediaBrowser.Providers.MediaInfo
             var trackTrackNumber = track.TrackNumber is null or 0 ? mediaInfo.IndexNumber : track.TrackNumber;
             var trackDiscNumber = track.DiscNumber is null or 0 ? mediaInfo.ParentIndexNumber : track.DiscNumber;
 
+            // Some users may use a misbehaved tag editor that writes a null character in the tag when not allowed by the standard.
+            trackTitle = GetSanitizedStringTag(trackTitle, audio.Path);
+            trackAlbum = GetSanitizedStringTag(trackAlbum, audio.Path);
+            var trackAlbumArtist = GetSanitizedStringTag(track.AlbumArtist, audio.Path);
+            var trackArist = GetSanitizedStringTag(track.Artist, audio.Path);
+            var trackComposer = GetSanitizedStringTag(track.Composer, audio.Path);
+            var trackGenre = GetSanitizedStringTag(track.Genre, audio.Path);
+
             if (audio.SupportsPeople && !audio.LockedFields.Contains(MetadataField.Cast))
             {
                 var people = new List<PersonInfo>();
-                var albumArtists = string.IsNullOrEmpty(track.AlbumArtist) ? [] : track.AlbumArtist.Split(InternalValueSeparator);
+                var albumArtists = string.IsNullOrEmpty(trackAlbumArtist) ? [] : trackAlbumArtist.Split(InternalValueSeparator);
 
                 if (libraryOptions.UseCustomTagDelimiters)
                 {
@@ -206,7 +214,7 @@ namespace MediaBrowser.Providers.MediaInfo
                 string[]? performers = null;
                 if (libraryOptions.PreferNonstandardArtistsTag)
                 {
-                    track.AdditionalFields.TryGetValue("ARTISTS", out var artistsTagString);
+                    TryGetSanitizedAdditionalFields(track, "ARTISTS", out var artistsTagString);
                     if (artistsTagString is not null)
                     {
                         performers = artistsTagString.Split(InternalValueSeparator);
@@ -215,7 +223,7 @@ namespace MediaBrowser.Providers.MediaInfo
 
                 if (performers is null || performers.Length == 0)
                 {
-                    performers = string.IsNullOrEmpty(track.Artist) ? [] : track.Artist.Split(InternalValueSeparator);
+                    performers = string.IsNullOrEmpty(trackArist) ? [] : trackArist.Split(InternalValueSeparator);
                 }
 
                 if (libraryOptions.UseCustomTagDelimiters)
@@ -235,15 +243,18 @@ namespace MediaBrowser.Providers.MediaInfo
                     }
                 }
 
-                foreach (var composer in track.Composer.Split(InternalValueSeparator))
+                if (!string.IsNullOrWhiteSpace(trackComposer))
                 {
-                    if (!string.IsNullOrWhiteSpace(composer))
+                    foreach (var composer in trackComposer.Split(InternalValueSeparator))
                     {
-                        PeopleHelper.AddPerson(people, new PersonInfo
+                        if (!string.IsNullOrWhiteSpace(composer))
                         {
-                            Name = composer.Trim(),
-                            Type = PersonKind.Composer
-                        });
+                            PeopleHelper.AddPerson(people, new PersonInfo
+                            {
+                                Name = composer.Trim(),
+                                Type = PersonKind.Composer
+                            });
+                        }
                     }
                 }
 
@@ -320,7 +331,7 @@ namespace MediaBrowser.Providers.MediaInfo
 
             if (!audio.LockedFields.Contains(MetadataField.Genres))
             {
-                var genres = string.IsNullOrEmpty(track.Genre) ? [] : track.Genre.Split(InternalValueSeparator).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
+                var genres = string.IsNullOrEmpty(trackGenre) ? [] : trackGenre.Split(InternalValueSeparator).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
 
                 if (libraryOptions.UseCustomTagDelimiters)
                 {
@@ -334,7 +345,7 @@ namespace MediaBrowser.Providers.MediaInfo
                     : audio.Genres;
             }
 
-            track.AdditionalFields.TryGetValue("REPLAYGAIN_TRACK_GAIN", out var trackGainTag);
+            TryGetSanitizedAdditionalFields(track, "REPLAYGAIN_TRACK_GAIN", out var trackGainTag);
 
             if (trackGainTag is not null)
             {
@@ -351,8 +362,8 @@ namespace MediaBrowser.Providers.MediaInfo
 
             if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzArtist, out _))
             {
-                if ((track.AdditionalFields.TryGetValue("MUSICBRAINZ_ARTISTID", out var musicBrainzArtistTag)
-                     || track.AdditionalFields.TryGetValue("MusicBrainz Artist Id", out musicBrainzArtistTag))
+                if ((TryGetSanitizedAdditionalFields(track, "MUSICBRAINZ_ARTISTID", out var musicBrainzArtistTag)
+                     || TryGetSanitizedAdditionalFields(track, "MusicBrainz Artist Id", out musicBrainzArtistTag))
                     && !string.IsNullOrEmpty(musicBrainzArtistTag))
                 {
                     var id = GetFirstMusicBrainzId(musicBrainzArtistTag, libraryOptions.UseCustomTagDelimiters, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist);
@@ -362,8 +373,8 @@ namespace MediaBrowser.Providers.MediaInfo
 
             if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzAlbumArtist, out _))
             {
-                if ((track.AdditionalFields.TryGetValue("MUSICBRAINZ_ALBUMARTISTID", out var musicBrainzReleaseArtistIdTag)
-                     || track.AdditionalFields.TryGetValue("MusicBrainz Album Artist Id", out musicBrainzReleaseArtistIdTag))
+                if ((TryGetSanitizedAdditionalFields(track, "MUSICBRAINZ_ALBUMARTISTID", out var musicBrainzReleaseArtistIdTag)
+                     || TryGetSanitizedAdditionalFields(track, "MusicBrainz Album Artist Id", out musicBrainzReleaseArtistIdTag))
                     && !string.IsNullOrEmpty(musicBrainzReleaseArtistIdTag))
                 {
                     var id = GetFirstMusicBrainzId(musicBrainzReleaseArtistIdTag, libraryOptions.UseCustomTagDelimiters, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist);
@@ -373,8 +384,8 @@ namespace MediaBrowser.Providers.MediaInfo
 
             if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzAlbum, out _))
             {
-                if ((track.AdditionalFields.TryGetValue("MUSICBRAINZ_ALBUMID", out var musicBrainzReleaseIdTag)
-                     || track.AdditionalFields.TryGetValue("MusicBrainz Album Id", out musicBrainzReleaseIdTag))
+                if ((TryGetSanitizedAdditionalFields(track, "MUSICBRAINZ_ALBUMID", out var musicBrainzReleaseIdTag)
+                     || TryGetSanitizedAdditionalFields(track, "MusicBrainz Album Id", out musicBrainzReleaseIdTag))
                     && !string.IsNullOrEmpty(musicBrainzReleaseIdTag))
                 {
                     var id = GetFirstMusicBrainzId(musicBrainzReleaseIdTag, libraryOptions.UseCustomTagDelimiters, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist);
@@ -384,8 +395,8 @@ namespace MediaBrowser.Providers.MediaInfo
 
             if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzReleaseGroup, out _))
             {
-                if ((track.AdditionalFields.TryGetValue("MUSICBRAINZ_RELEASEGROUPID", out var musicBrainzReleaseGroupIdTag)
-                     || track.AdditionalFields.TryGetValue("MusicBrainz Release Group Id", out musicBrainzReleaseGroupIdTag))
+                if ((TryGetSanitizedAdditionalFields(track, "MUSICBRAINZ_RELEASEGROUPID", out var musicBrainzReleaseGroupIdTag)
+                     || TryGetSanitizedAdditionalFields(track, "MusicBrainz Release Group Id", out musicBrainzReleaseGroupIdTag))
                     && !string.IsNullOrEmpty(musicBrainzReleaseGroupIdTag))
                 {
                     var id = GetFirstMusicBrainzId(musicBrainzReleaseGroupIdTag, libraryOptions.UseCustomTagDelimiters, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist);
@@ -395,8 +406,8 @@ namespace MediaBrowser.Providers.MediaInfo
 
             if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzTrack, out _))
             {
-                if ((track.AdditionalFields.TryGetValue("MUSICBRAINZ_RELEASETRACKID", out var trackMbId)
-                     || track.AdditionalFields.TryGetValue("MusicBrainz Release Track Id", out trackMbId))
+                if ((TryGetSanitizedAdditionalFields(track, "MUSICBRAINZ_RELEASETRACKID", out var trackMbId)
+                     || TryGetSanitizedAdditionalFields(track, "MusicBrainz Release Track Id", out trackMbId))
                     && !string.IsNullOrEmpty(trackMbId))
                 {
                     var id = GetFirstMusicBrainzId(trackMbId, libraryOptions.UseCustomTagDelimiters, libraryOptions.GetCustomTagDelimiters(), libraryOptions.DelimiterWhitelist);
@@ -406,13 +417,13 @@ namespace MediaBrowser.Providers.MediaInfo
 
             if (options.ReplaceAllMetadata || !audio.TryGetProviderId(MetadataProvider.MusicBrainzRecording, out _))
             {
-                if ((track.AdditionalFields.TryGetValue("MUSICBRAINZ_TRACKID", out var recordingMbId)
-                     || track.AdditionalFields.TryGetValue("MusicBrainz Track Id", out recordingMbId))
+                if ((TryGetSanitizedAdditionalFields(track, "MUSICBRAINZ_TRACKID", out var recordingMbId)
+                     || TryGetSanitizedAdditionalFields(track, "MusicBrainz Track Id", out recordingMbId))
                     && !string.IsNullOrEmpty(recordingMbId))
                 {
                     audio.TrySetProviderId(MetadataProvider.MusicBrainzRecording, recordingMbId);
                 }
-                else if (track.AdditionalFields.TryGetValue("UFID", out var ufIdValue) && !string.IsNullOrEmpty(ufIdValue))
+                else if (TryGetSanitizedAdditionalFields(track, "UFID", out var ufIdValue) && !string.IsNullOrEmpty(ufIdValue))
                 {
                     // If tagged with MB Picard, the format is 'http://musicbrainz.org\0<recording MBID>'
                     if (ufIdValue.Contains("musicbrainz.org", StringComparison.OrdinalIgnoreCase))
@@ -485,5 +496,28 @@ namespace MediaBrowser.Providers.MediaInfo
 
             return val;
         }
+
+        private string? GetSanitizedStringTag(string? tag, string filePath)
+        {
+            if (string.IsNullOrEmpty(tag))
+            {
+                return null;
+            }
+
+            var result = tag.TruncateAtNull();
+            if (result.Length != tag.Length)
+            {
+                _logger.LogWarning("Audio file {File} contains a null character in its tag, but this is not allowed by its tagging standard. All characters after the null char will be discarded. Please fix your file", filePath);
+            }
+
+            return result;
+        }
+
+        private bool TryGetSanitizedAdditionalFields(Track track, string field, out string? value)
+        {
+            var hasField = track.AdditionalFields.TryGetValue(field, out value);
+            value = GetSanitizedStringTag(value, track.Path);
+            return hasField;
+        }
     }
 }

+ 13 - 0
src/Jellyfin.Extensions/StringExtensions.cs

@@ -135,5 +135,18 @@ namespace Jellyfin.Extensions
         {
             return values.Select(i => (i ?? string.Empty).Trim());
         }
+
+        /// <summary>
+        /// Truncates a string at the first null character ('\0').
+        /// </summary>
+        /// <param name="text">The input string.</param>
+        /// <returns>
+        /// The substring up to (but not including) the first null character,
+        /// or the original string if no null character is present.
+        /// </returns>
+        public static string TruncateAtNull(this string text)
+        {
+            return string.IsNullOrEmpty(text) ? text : text.AsSpan().LeftPart('\0').ToString();
+        }
     }
 }