2
0
Эх сурвалжийг харах

Sort embedded collections in Nfo files

Because the Nfo files emit the collections as they are in-memory, the
files are not stable in format, genres, tags, albums, people, etc. are emitted in random orders. Add ordering of the collections when emitting the Nfo files so the file remains stable (unchanged) when underlying media information doesn't change.

In the process of this, it became clear that most of the providers and probes don't trim the strings like people's names, genre names, etc. so did a pass of Trim cleanup too.

Specific ordering: (alphabetical/numeric ascending after trimming blanks and defaulting to zero for missing numbers)

BaseItem: Directors, Writers, Trailers (by Url), Production Locations, Genres, Studios, Tags, Custom Provider Data (by key), Linked Children  (by Path>LibraryItemId), Backdrop Images (by path), Actors (by SortOrder>Name)

AlbumNfo: Artists, Album Artists, Tracks (by ParentIndexNumber>IndexNumber>Name)

ArtistNfo: Albums (by Production Year>SortName>Name)

MovieNfo: Artists

Fix Debug build lint


Fix CI debug build lint issue.


Fix review issues

Fixed debug-build lint issues.
Emits the `disc` number to NFO for tracks with a non-zero ParentIndexNumber and only emit `position` if non-zero.
Removed the exception filtering I put in for testing.

Don't emit actors for MusicAlbums or MusicArtists


Swap from String.Trimmed() to ?.Trim()
Addressing PR feedback

Can't use ReadOnlySpan in an async method

Removed now-unused namespace
Marc Brooks 2 жил өмнө
parent
commit
6dc61a430b

+ 3 - 3
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -22,6 +22,7 @@ using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Controller.Sorting;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Globalization;
@@ -915,7 +916,7 @@ namespace MediaBrowser.Controller.Entities
                 // Remove from middle if surrounded by spaces
                 sortable = sortable.Replace(" " + search + " ", " ", StringComparison.Ordinal);
 
-                // Remove from end if followed by a space
+                // Remove from end if preceeded by a space
                 if (sortable.EndsWith(" " + search, StringComparison.Ordinal))
                 {
                     sortable = sortable.Remove(sortable.Length - (search.Length + 1));
@@ -1769,7 +1770,6 @@ namespace MediaBrowser.Controller.Entities
         public void AddStudio(string name)
         {
             ArgumentException.ThrowIfNullOrEmpty(name);
-
             var current = Studios;
 
             if (!current.Contains(name, StringComparison.OrdinalIgnoreCase))
@@ -1788,7 +1788,7 @@ namespace MediaBrowser.Controller.Entities
 
         public void SetStudios(IEnumerable<string> names)
         {
-            Studios = names.Distinct().ToArray();
+            Studios = names.Trimmed().Distinct().ToArray();
         }
 
         /// <summary>

+ 2 - 0
MediaBrowser.Controller/Entities/PeopleHelper.cs

@@ -15,6 +15,8 @@ namespace MediaBrowser.Controller.Entities
             ArgumentNullException.ThrowIfNull(person);
             ArgumentException.ThrowIfNullOrEmpty(person.Name);
 
+            person.Name = person.Name.Trim();
+
             // Normalize
             if (string.Equals(person.Role, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase))
             {

+ 5 - 0
MediaBrowser.Controller/Sorting/SortExtensions.cs

@@ -30,5 +30,10 @@ namespace MediaBrowser.Controller.Sorting
         {
             return list.ThenByDescending(getName, _comparer);
         }
+
+        public static IEnumerable<string> Trimmed(this IEnumerable<string> values)
+        {
+            return values.Select(i => (i ?? string.Empty).Trim());
+        }
     }
 }

+ 16 - 11
MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs

@@ -234,8 +234,8 @@ namespace MediaBrowser.LocalMetadata.Parsers
                     item.CustomRating = reader.ReadNormalizedString();
                     break;
                 case "RunningTime":
-                    var runtimeText = reader.ReadElementContentAsString();
-                    if (!string.IsNullOrWhiteSpace(runtimeText))
+                    var runtimeText = reader.ReadNormalizedString();
+                    if (!string.IsNullOrEmpty(runtimeText))
                     {
                         if (int.TryParse(runtimeText.AsSpan().LeftPart(' '), NumberStyles.Integer, CultureInfo.InvariantCulture, out var runtime))
                         {
@@ -253,7 +253,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
 
                     break;
                 case "LockData":
-                    item.IsLocked = string.Equals(reader.ReadElementContentAsString(), "true", StringComparison.OrdinalIgnoreCase);
+                    item.IsLocked = string.Equals(reader.ReadNormalizedString(), "true", StringComparison.OrdinalIgnoreCase);
                     break;
                 case "Network":
                     foreach (var name in reader.GetStringArray())
@@ -331,9 +331,9 @@ namespace MediaBrowser.LocalMetadata.Parsers
                 case "Rating":
                 case "IMDBrating":
                 {
-                    var rating = reader.ReadElementContentAsString();
+                    var rating = reader.ReadNormalizedString();
 
-                    if (!string.IsNullOrWhiteSpace(rating))
+                    if (!string.IsNullOrEmpty(rating))
                     {
                         // All external meta is saving this as '.' for decimal I believe...but just to be sure
                         if (float.TryParse(rating.Replace(',', '.'), NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var val))
@@ -449,7 +449,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
 
                 case "OwnerUserId":
                 {
-                    var val = reader.ReadElementContentAsString();
+                    var val = reader.ReadNormalizedString();
 
                     if (Guid.TryParse(val, out var guid) && !guid.Equals(Guid.Empty))
                     {
@@ -464,7 +464,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
 
                 case "Format3D":
                 {
-                    var val = reader.ReadElementContentAsString();
+                    var val = reader.ReadNormalizedString();
 
                     if (item is Video video)
                     {
@@ -498,7 +498,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
                     string readerName = reader.Name;
                     if (_validProviderIds!.TryGetValue(readerName, out string? providerIdValue))
                     {
-                        var id = reader.ReadElementContentAsString();
+                        var id = reader.ReadNormalizedString();
                         item.TrySetProviderId(providerIdValue, id);
                     }
                     else
@@ -580,7 +580,12 @@ namespace MediaBrowser.LocalMetadata.Parsers
                     switch (reader.Name)
                     {
                         case "Tagline":
-                            item.Tagline = reader.ReadNormalizedString();
+                            var val = reader.ReadNormalizedString();
+                            if (!string.IsNullOrEmpty(val))
+                            {
+                                item.Tagline = val;
+                            }
+
                             break;
                         default:
                             reader.Skip();
@@ -842,7 +847,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
                             userId = reader.ReadNormalizedString();
                             break;
                         case "CanEdit":
-                            canEdit = string.Equals(reader.ReadElementContentAsString(), "true", StringComparison.OrdinalIgnoreCase);
+                            canEdit = string.Equals(reader.ReadNormalizedString(), "true", StringComparison.OrdinalIgnoreCase);
                             break;
                         default:
                             reader.Skip();
@@ -856,7 +861,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
             }
 
             // This is valid
-            if (!string.IsNullOrWhiteSpace(userId) && Guid.TryParse(userId, out var guid))
+            if (!string.IsNullOrEmpty(userId) && Guid.TryParse(userId, out var guid))
             {
                 return new PlaylistUserPermissions(guid, canEdit);
             }

+ 19 - 15
MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs

@@ -10,7 +10,9 @@ using System.Text.RegularExpressions;
 using System.Xml;
 using Jellyfin.Data.Enums;
 using Jellyfin.Extensions;
+using MediaBrowser.Controller.Extensions;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Sorting;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Globalization;
@@ -531,42 +533,44 @@ namespace MediaBrowser.MediaEncoding.Probing
         private void ProcessPairs(string key, List<NameValuePair> pairs, MediaInfo info)
         {
             List<BaseItemPerson> peoples = new List<BaseItemPerson>();
+            var distinctPairs = pairs.Select(p => p.Value)
+                    .Where(i => !string.IsNullOrWhiteSpace(i))
+                    .Trimmed()
+                    .Distinct(StringComparer.OrdinalIgnoreCase);
+
             if (string.Equals(key, "studio", StringComparison.OrdinalIgnoreCase))
             {
-                info.Studios = pairs.Select(p => p.Value)
-                    .Where(i => !string.IsNullOrWhiteSpace(i))
-                    .Distinct(StringComparer.OrdinalIgnoreCase)
-                    .ToArray();
+                info.Studios = distinctPairs.ToArray();
             }
             else if (string.Equals(key, "screenwriters", StringComparison.OrdinalIgnoreCase))
             {
-                foreach (var pair in pairs)
+                foreach (var pair in distinctPairs)
                 {
                     peoples.Add(new BaseItemPerson
                     {
-                        Name = pair.Value,
+                        Name = pair,
                         Type = PersonKind.Writer
                     });
                 }
             }
             else if (string.Equals(key, "producers", StringComparison.OrdinalIgnoreCase))
             {
-                foreach (var pair in pairs)
+                foreach (var pair in distinctPairs)
                 {
                     peoples.Add(new BaseItemPerson
                     {
-                        Name = pair.Value,
+                        Name = pair,
                         Type = PersonKind.Producer
                     });
                 }
             }
             else if (string.Equals(key, "directors", StringComparison.OrdinalIgnoreCase))
             {
-                foreach (var pair in pairs)
+                foreach (var pair in distinctPairs)
                 {
                     peoples.Add(new BaseItemPerson
                     {
-                        Name = pair.Value,
+                        Name = pair,
                         Type = PersonKind.Director
                     });
                 }
@@ -591,10 +595,10 @@ namespace MediaBrowser.MediaEncoding.Probing
                     switch (reader.Name)
                     {
                         case "key":
-                            name = reader.ReadElementContentAsString();
+                            name = reader.ReadNormalizedString();
                             break;
                         case "string":
-                            value = reader.ReadElementContentAsString();
+                            value = reader.ReadNormalizedString();
                             break;
                         default:
                             reader.Skip();
@@ -607,8 +611,8 @@ namespace MediaBrowser.MediaEncoding.Probing
                 }
             }
 
-            if (string.IsNullOrWhiteSpace(name)
-                || string.IsNullOrWhiteSpace(value))
+            if (string.IsNullOrEmpty(name)
+                || string.IsNullOrEmpty(value))
             {
                 return null;
             }
@@ -1453,7 +1457,7 @@ namespace MediaBrowser.MediaEncoding.Probing
             var genres = new List<string>(info.Genres);
             foreach (var genre in Split(genreVal, true))
             {
-                if (string.IsNullOrWhiteSpace(genre))
+                if (string.IsNullOrEmpty(genre))
                 {
                     continue;
                 }

+ 12 - 9
MediaBrowser.Providers/MediaInfo/AudioFileProber.cs

@@ -13,6 +13,7 @@ using MediaBrowser.Controller.Lyrics;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Controller.Sorting;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
@@ -183,11 +184,11 @@ namespace MediaBrowser.Providers.MediaInfo
 
                 foreach (var albumArtist in albumArtists)
                 {
-                    if (!string.IsNullOrEmpty(albumArtist))
+                    if (!string.IsNullOrWhiteSpace(albumArtist))
                     {
                         PeopleHelper.AddPerson(people, new PersonInfo
                         {
-                            Name = albumArtist,
+                            Name = albumArtist.Trim(),
                             Type = PersonKind.AlbumArtist
                         });
                     }
@@ -215,11 +216,11 @@ namespace MediaBrowser.Providers.MediaInfo
 
                 foreach (var performer in performers)
                 {
-                    if (!string.IsNullOrEmpty(performer))
+                    if (!string.IsNullOrWhiteSpace(performer))
                     {
                         PeopleHelper.AddPerson(people, new PersonInfo
                         {
-                            Name = performer,
+                            Name = performer.Trim(),
                             Type = PersonKind.Artist
                         });
                     }
@@ -227,11 +228,11 @@ namespace MediaBrowser.Providers.MediaInfo
 
                 foreach (var composer in track.Composer.Split(InternalValueSeparator))
                 {
-                    if (!string.IsNullOrEmpty(composer))
+                    if (!string.IsNullOrWhiteSpace(composer))
                     {
                         PeopleHelper.AddPerson(people, new PersonInfo
                         {
-                            Name = composer,
+                            Name = composer.Trim(),
                             Type = PersonKind.Composer
                         });
                     }
@@ -273,13 +274,13 @@ namespace MediaBrowser.Providers.MediaInfo
 
             if (options.ReplaceAllMetadata)
             {
-                audio.Album = track.Album;
+                audio.Album = track.Album.Trim();
                 audio.IndexNumber = track.TrackNumber;
                 audio.ParentIndexNumber = track.DiscNumber;
             }
             else
             {
-                audio.Album ??= track.Album;
+                audio.Album ??= track.Album.Trim();
                 audio.IndexNumber ??= track.TrackNumber;
                 audio.ParentIndexNumber ??= track.DiscNumber;
             }
@@ -309,13 +310,15 @@ namespace MediaBrowser.Providers.MediaInfo
 
             if (!audio.LockedFields.Contains(MetadataField.Genres))
             {
-                var genres = string.IsNullOrEmpty(track.Genre) ? mediaInfo.Genres : track.Genre.Split(InternalValueSeparator).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
+                var genres = string.IsNullOrEmpty(track.Genre) ? mediaInfo.Genres : track.Genre.Split(InternalValueSeparator);
 
                 if (libraryOptions.UseCustomTagDelimiters)
                 {
                     genres = genres.SelectMany(g => SplitWithCustomDelimiter(g, libraryOptions.CustomTagDelimiters, libraryOptions.DelimiterWhitelist)).ToArray();
                 }
 
+                genres = genres.Trimmed().Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
+
                 audio.Genres = options.ReplaceAllMetadata || audio.Genres is null || audio.Genres.Length == 0
                     ? genres
                     : audio.Genres;

+ 4 - 3
MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs

@@ -16,6 +16,7 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.MediaEncoding;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Controller.Sorting;
 using MediaBrowser.Controller.Subtitles;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Dlna;
@@ -400,7 +401,7 @@ namespace MediaBrowser.Providers.MediaInfo
                 {
                     video.Genres = Array.Empty<string>();
 
-                    foreach (var genre in data.Genres)
+                    foreach (var genre in data.Genres.Trimmed())
                     {
                         video.AddGenre(genre);
                     }
@@ -509,9 +510,9 @@ namespace MediaBrowser.Providers.MediaInfo
                 {
                     PeopleHelper.AddPerson(people, new PersonInfo
                     {
-                        Name = person.Name,
+                        Name = person.Name.Trim(),
                         Type = person.Type,
-                        Role = person.Role
+                        Role = person.Role.Trim()
                     });
                 }
 

+ 2 - 2
MediaBrowser.Providers/Music/AlbumMetadataService.cs

@@ -187,7 +187,7 @@ namespace MediaBrowser.Providers.Music
                 {
                     PeopleHelper.AddPerson(people, new PersonInfo
                     {
-                        Name = albumArtist,
+                        Name = albumArtist.Trim(),
                         Type = PersonKind.AlbumArtist
                     });
                 }
@@ -196,7 +196,7 @@ namespace MediaBrowser.Providers.Music
                 {
                     PeopleHelper.AddPerson(people, new PersonInfo
                     {
-                        Name = artist,
+                        Name = artist.Trim(),
                         Type = PersonKind.Artist
                     });
                 }

+ 2 - 2
MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs

@@ -421,7 +421,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
             {
                 var person = new PersonInfo
                 {
-                    Name = result.Director,
+                    Name = result.Director.Trim(),
                     Type = PersonKind.Director
                 };
 
@@ -432,7 +432,7 @@ namespace MediaBrowser.Providers.Plugins.Omdb
             {
                 var person = new PersonInfo
                 {
-                    Name = result.Writer,
+                    Name = result.Writer.Trim(),
                     Type = PersonKind.Writer
                 };
 

+ 4 - 3
MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs

@@ -12,6 +12,7 @@ using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Controller.Sorting;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Providers;
 using TMDbLib.Objects.Find;
@@ -234,7 +235,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
 
             var genres = movieResult.Genres;
 
-            foreach (var genre in genres.Select(g => g.Name))
+            foreach (var genre in genres.Select(g => g.Name).Trimmed())
             {
                 movie.AddGenre(genre);
             }
@@ -254,7 +255,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
                     var personInfo = new PersonInfo
                     {
                         Name = actor.Name.Trim(),
-                        Role = actor.Character,
+                        Role = actor.Character.Trim(),
                         Type = PersonKind.Actor,
                         SortOrder = actor.Order
                     };
@@ -289,7 +290,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
                     var personInfo = new PersonInfo
                     {
                         Name = person.Name.Trim(),
-                        Role = person.Job,
+                        Role = person.Job?.Trim(),
                         Type = type
                     };
 

+ 3 - 3
MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs

@@ -211,7 +211,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
                     metadataResult.AddPerson(new PersonInfo
                     {
                         Name = actor.Name.Trim(),
-                        Role = actor.Character,
+                        Role = actor.Character.Trim(),
                         Type = PersonKind.Actor,
                         SortOrder = actor.Order
                     });
@@ -225,7 +225,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
                     metadataResult.AddPerson(new PersonInfo
                     {
                         Name = guest.Name.Trim(),
-                        Role = guest.Character,
+                        Role = guest.Character.Trim(),
                         Type = PersonKind.GuestStar,
                         SortOrder = guest.Order
                     });
@@ -249,7 +249,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
                     metadataResult.AddPerson(new PersonInfo
                     {
                         Name = person.Name.Trim(),
-                        Role = person.Job,
+                        Role = person.Job?.Trim(),
                         Type = type
                     });
                 }

+ 5 - 4
MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs

@@ -82,12 +82,13 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
                 var cast = credits.Cast.OrderBy(c => c.Order).Take(Plugin.Instance.Configuration.MaxCastMembers).ToList();
                 for (var i = 0; i < cast.Count; i++)
                 {
+                    var member = cast[i];
                     result.AddPerson(new PersonInfo
                     {
-                        Name = cast[i].Name.Trim(),
-                        Role = cast[i].Character,
+                        Name = member.Name.Trim(),
+                        Role = member.Character.Trim(),
                         Type = PersonKind.Actor,
-                        SortOrder = cast[i].Order
+                        SortOrder = member.Order
                     });
                 }
             }
@@ -108,7 +109,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
                     result.AddPerson(new PersonInfo
                     {
                         Name = person.Name.Trim(),
-                        Role = person.Job,
+                        Role = person.Job?.Trim(),
                         Type = type
                     });
                 }

+ 2 - 2
MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs

@@ -330,7 +330,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
                     var personInfo = new PersonInfo
                     {
                         Name = actor.Name.Trim(),
-                        Role = actor.Character,
+                        Role = actor.Character.Trim(),
                         Type = PersonKind.Actor,
                         SortOrder = actor.Order,
                         ImageUrl = _tmdbClientManager.GetPosterUrl(actor.ProfilePath)
@@ -368,7 +368,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
                     yield return new PersonInfo
                     {
                         Name = person.Name.Trim(),
-                        Role = person.Job,
+                        Role = person.Job?.Trim(),
                         Type = type
                     };
                 }

+ 13 - 4
MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs

@@ -8,6 +8,7 @@ using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Sorting;
 using MediaBrowser.Model.IO;
 using Microsoft.Extensions.Logging;
 
@@ -55,12 +56,12 @@ namespace MediaBrowser.XbmcMetadata.Savers
         {
             var album = (MusicAlbum)item;
 
-            foreach (var artist in album.Artists)
+            foreach (var artist in album.Artists.Trimmed().OrderBy(artist => artist))
             {
                 writer.WriteElementString("artist", artist);
             }
 
-            foreach (var artist in album.AlbumArtists)
+            foreach (var artist in album.AlbumArtists.Trimmed().OrderBy(artist => artist))
             {
                 writer.WriteElementString("albumartist", artist);
             }
@@ -70,11 +71,19 @@ namespace MediaBrowser.XbmcMetadata.Savers
 
         private void AddTracks(IEnumerable<BaseItem> tracks, XmlWriter writer)
         {
-            foreach (var track in tracks.OrderBy(i => i.ParentIndexNumber ?? 0).ThenBy(i => i.IndexNumber ?? 0))
+            foreach (var track in tracks
+                .OrderBy(i => i.ParentIndexNumber ?? 0)
+                .ThenBy(i => i.IndexNumber ?? 0)
+                .ThenBy(i => i.Name?.Trim()))
             {
                 writer.WriteStartElement("track");
 
-                if (track.IndexNumber.HasValue)
+                if (track.ParentIndexNumber.HasValue && track.ParentIndexNumber.Value != 0)
+                {
+                    writer.WriteElementString("disc", track.ParentIndexNumber.Value.ToString(CultureInfo.InvariantCulture));
+                }
+
+                if (track.IndexNumber.HasValue && track.IndexNumber.Value != 0)
                 {
                     writer.WriteElementString("position", track.IndexNumber.Value.ToString(CultureInfo.InvariantCulture));
                 }

+ 6 - 1
MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs

@@ -1,11 +1,13 @@
 using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
+using System.Linq;
 using System.Xml;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Sorting;
 using MediaBrowser.Model.IO;
 using MediaBrowser.XbmcMetadata.Configuration;
 using Microsoft.Extensions.Logging;
@@ -69,7 +71,10 @@ namespace MediaBrowser.XbmcMetadata.Savers
 
         private void AddAlbums(IList<BaseItem> albums, XmlWriter writer)
         {
-            foreach (var album in albums)
+            foreach (var album in albums
+                .OrderBy(album => album.ProductionYear ?? 0)
+                .ThenBy(album => album.SortName?.Trim())
+                .ThenBy(album => album.Name?.Trim()))
             {
                 writer.WriteStartElement("album");
 

+ 23 - 12
MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs

@@ -19,6 +19,7 @@ using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Sorting;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.IO;
@@ -488,7 +489,9 @@ namespace MediaBrowser.XbmcMetadata.Savers
 
             var directors = people
                 .Where(i => i.IsType(PersonKind.Director))
-                .Select(i => i.Name)
+                .Select(i => i.Name?.Trim())
+                .Distinct(StringComparer.OrdinalIgnoreCase)
+                .OrderBy(i => i)
                 .ToList();
 
             foreach (var person in directors)
@@ -498,8 +501,9 @@ namespace MediaBrowser.XbmcMetadata.Savers
 
             var writers = people
                 .Where(i => i.IsType(PersonKind.Writer))
-                .Select(i => i.Name)
+                .Select(i => i.Name?.Trim())
                 .Distinct(StringComparer.OrdinalIgnoreCase)
+                .OrderBy(i => i)
                 .ToList();
 
             foreach (var person in writers)
@@ -512,7 +516,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
                 writer.WriteElementString("credits", person);
             }
 
-            foreach (var trailer in item.RemoteTrailers)
+            foreach (var trailer in item.RemoteTrailers.OrderBy(t => t.Url?.Trim()))
             {
                 writer.WriteElementString("trailer", GetOutputTrailerUrl(trailer.Url));
             }
@@ -660,22 +664,22 @@ namespace MediaBrowser.XbmcMetadata.Savers
                 writer.WriteElementString("tagline", item.Tagline);
             }
 
-            foreach (var country in item.ProductionLocations)
+            foreach (var country in item.ProductionLocations.Trimmed().OrderBy(country => country))
             {
                 writer.WriteElementString("country", country);
             }
 
-            foreach (var genre in item.Genres)
+            foreach (var genre in item.Genres.Trimmed().OrderBy(genre => genre))
             {
                 writer.WriteElementString("genre", genre);
             }
 
-            foreach (var studio in item.Studios)
+            foreach (var studio in item.Studios.Trimmed().OrderBy(studio => studio))
             {
                 writer.WriteElementString("studio", studio);
             }
 
-            foreach (var tag in item.Tags)
+            foreach (var tag in item.Tags.Trimmed().OrderBy(tag => tag))
             {
                 if (item is MusicAlbum || item is MusicArtist)
                 {
@@ -752,7 +756,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
 
             if (item.ProviderIds is not null)
             {
-                foreach (var providerKey in item.ProviderIds.Keys)
+                foreach (var providerKey in item.ProviderIds.Keys.OrderBy(providerKey => providerKey))
                 {
                     var providerId = item.ProviderIds[providerKey];
                     if (!string.IsNullOrEmpty(providerId) && !writtenProviderIds.Contains(providerKey))
@@ -764,7 +768,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
                             XmlConvert.VerifyName(tagName);
                             Logger.LogDebug("Saving custom provider tagname {0}", tagName);
 
-                            writer.WriteElementString(GetTagForProviderKey(providerKey), providerId);
+                            writer.WriteElementString(tagName, providerId);
                         }
                         catch (ArgumentException)
                         {
@@ -785,7 +789,10 @@ namespace MediaBrowser.XbmcMetadata.Savers
 
             AddUserData(item, writer, userManager, userDataRepo, options);
 
-            AddActors(people, writer, libraryManager, options.SaveImagePathsInNfo);
+            if (item is not MusicAlbum && item is not MusicArtist)
+            {
+                AddActors(people, writer, libraryManager, options.SaveImagePathsInNfo);
+            }
 
             if (item is BoxSet folder)
             {
@@ -797,6 +804,8 @@ namespace MediaBrowser.XbmcMetadata.Savers
         {
             var items = item.LinkedChildren
                 .Where(i => i.Type == LinkedChildType.Manual)
+                .OrderBy(i => i.Path?.Trim())
+                .ThenBy(i => i.LibraryItemId?.Trim())
                 .ToList();
 
             foreach (var link in items)
@@ -839,7 +848,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
                 writer.WriteElementString("poster", GetImagePathToSave(image, libraryManager));
             }
 
-            foreach (var backdrop in item.GetImages(ImageType.Backdrop))
+            foreach (var backdrop in item.GetImages(ImageType.Backdrop).OrderBy(b => b.Path?.Trim()))
             {
                 writer.WriteElementString("fanart", GetImagePathToSave(backdrop, libraryManager));
             }
@@ -913,7 +922,9 @@ namespace MediaBrowser.XbmcMetadata.Savers
 
         private void AddActors(List<PersonInfo> people, XmlWriter writer, ILibraryManager libraryManager, bool saveImagePath)
         {
-            foreach (var person in people)
+            foreach (var person in people
+                .OrderBy(person => person.SortOrder ?? 0)
+                .ThenBy(person => person.Name?.Trim()))
             {
                 if (person.IsType(PersonKind.Director) || person.IsType(PersonKind.Writer))
                 {

+ 2 - 1
MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs

@@ -8,6 +8,7 @@ using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
+using MediaBrowser.Controller.Sorting;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.IO;
 using Microsoft.Extensions.Logging;
@@ -100,7 +101,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
 
             if (item is MusicVideo musicVideo)
             {
-                foreach (var artist in musicVideo.Artists)
+                foreach (var artist in musicVideo.Artists.Trimmed().OrderBy(artist => artist))
                 {
                     writer.WriteElementString("artist", artist);
                 }