From 6dc61a430ba3a8480399309f277e5debfd6403ba Mon Sep 17 00:00:00 2001 From: Marc Brooks Date: Mon, 15 May 2023 00:38:27 -0500 Subject: [PATCH 1/3] 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 --- MediaBrowser.Controller/Entities/BaseItem.cs | 6 ++-- .../Entities/PeopleHelper.cs | 2 ++ .../Sorting/SortExtensions.cs | 5 +++ .../Parsers/BaseItemXmlParser.cs | 27 ++++++++------ .../Probing/ProbeResultNormalizer.cs | 34 ++++++++++-------- .../MediaInfo/AudioFileProber.cs | 21 ++++++----- .../MediaInfo/FFProbeVideoInfo.cs | 7 ++-- .../Music/AlbumMetadataService.cs | 4 +-- .../Plugins/Omdb/OmdbProvider.cs | 4 +-- .../Plugins/Tmdb/Movies/TmdbMovieProvider.cs | 7 ++-- .../Plugins/Tmdb/TV/TmdbEpisodeProvider.cs | 6 ++-- .../Plugins/Tmdb/TV/TmdbSeasonProvider.cs | 9 ++--- .../Plugins/Tmdb/TV/TmdbSeriesProvider.cs | 4 +-- .../Savers/AlbumNfoSaver.cs | 17 ++++++--- .../Savers/ArtistNfoSaver.cs | 7 +++- .../Savers/BaseNfoSaver.cs | 35 ++++++++++++------- .../Savers/MovieNfoSaver.cs | 3 +- 17 files changed, 123 insertions(+), 75 deletions(-) diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 414488853f..8201ae318b 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/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 names) { - Studios = names.Distinct().ToArray(); + Studios = names.Trimmed().Distinct().ToArray(); } /// diff --git a/MediaBrowser.Controller/Entities/PeopleHelper.cs b/MediaBrowser.Controller/Entities/PeopleHelper.cs index 5292bd7727..d818604365 100644 --- a/MediaBrowser.Controller/Entities/PeopleHelper.cs +++ b/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)) { diff --git a/MediaBrowser.Controller/Sorting/SortExtensions.cs b/MediaBrowser.Controller/Sorting/SortExtensions.cs index f9c0d39ddd..db934e0f47 100644 --- a/MediaBrowser.Controller/Sorting/SortExtensions.cs +++ b/MediaBrowser.Controller/Sorting/SortExtensions.cs @@ -30,5 +30,10 @@ namespace MediaBrowser.Controller.Sorting { return list.ThenByDescending(getName, _comparer); } + + public static IEnumerable Trimmed(this IEnumerable values) + { + return values.Select(i => (i ?? string.Empty).Trim()); + } } } diff --git a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs index e4ac59b676..119effe791 100644 --- a/MediaBrowser.LocalMetadata/Parsers/BaseItemXmlParser.cs +++ b/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); } diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index 334796f585..0dee77db81 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/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 pairs, MediaInfo info) { List peoples = new List(); + 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(info.Genres); foreach (var genre in Split(genreVal, true)) { - if (string.IsNullOrWhiteSpace(genre)) + if (string.IsNullOrEmpty(genre)) { continue; } diff --git a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs index 80bb1a514c..d113cabc7f 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs +++ b/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; diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 246ba2733f..f486f150da 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/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(); - 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() }); } diff --git a/MediaBrowser.Providers/Music/AlbumMetadataService.cs b/MediaBrowser.Providers/Music/AlbumMetadataService.cs index a39bd16cea..daebe85d69 100644 --- a/MediaBrowser.Providers/Music/AlbumMetadataService.cs +++ b/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 }); } diff --git a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs b/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs index de0da7f7bd..ad9edb031c 100644 --- a/MediaBrowser.Providers/Plugins/Omdb/OmdbProvider.cs +++ b/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 }; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs index 8d68e2dcfe..582e05b793 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs +++ b/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 }; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs index e628abde55..4ee1645531 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs +++ b/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 }); } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs index 3f208b5993..b0a1e00df9 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeasonProvider.cs +++ b/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 }); } diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs index e4062740fe..9ace9c6743 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbSeriesProvider.cs +++ b/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 }; } diff --git a/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs index 2385e70485..4cb6f81b73 100644 --- a/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs +++ b/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 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)); } diff --git a/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs index 813d75f6c1..e13ba9385f 100644 --- a/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs +++ b/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 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"); diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs index 2afec3f6cd..7c94b25c4c 100644 --- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs +++ b/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 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)) { diff --git a/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs index bc344d87e0..3ff8749e74 100644 --- a/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs +++ b/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); } From 4e64b261a8a6f3b02a82d295b347755e46ad1cc2 Mon Sep 17 00:00:00 2001 From: Marc Brooks Date: Wed, 5 Feb 2025 18:13:28 -0600 Subject: [PATCH 2/3] Moved Trimmed to Jellyfin.Extensions.StringExtensions --- MediaBrowser.Controller/Sorting/SortExtensions.cs | 5 ----- MediaBrowser.Providers/MediaInfo/AudioFileProber.cs | 1 + MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs | 2 +- MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs | 2 +- MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs | 1 + src/Jellyfin.Extensions/StringExtensions.cs | 12 ++++++++++++ 6 files changed, 16 insertions(+), 7 deletions(-) diff --git a/MediaBrowser.Controller/Sorting/SortExtensions.cs b/MediaBrowser.Controller/Sorting/SortExtensions.cs index db934e0f47..f9c0d39ddd 100644 --- a/MediaBrowser.Controller/Sorting/SortExtensions.cs +++ b/MediaBrowser.Controller/Sorting/SortExtensions.cs @@ -30,10 +30,5 @@ namespace MediaBrowser.Controller.Sorting { return list.ThenByDescending(getName, _comparer); } - - public static IEnumerable Trimmed(this IEnumerable values) - { - return values.Select(i => (i ?? string.Empty).Trim()); - } } } diff --git a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs index 1308e06f9a..0e22dd96ed 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Threading.Tasks; using ATL; using Jellyfin.Data.Enums; +using Jellyfin.Extensions; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index 228c51959d..266e1861f9 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Extensions; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Chapters; using MediaBrowser.Controller.Configuration; @@ -16,7 +17,6 @@ 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; diff --git a/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs index 4cb6f81b73..774539c95e 100644 --- a/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs @@ -4,11 +4,11 @@ using System.Globalization; using System.IO; using System.Linq; using System.Xml; +using Jellyfin.Extensions; 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; diff --git a/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs index 2ff2fc716f..d119751791 100644 --- a/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Xml; +using Jellyfin.Extensions; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; diff --git a/src/Jellyfin.Extensions/StringExtensions.cs b/src/Jellyfin.Extensions/StringExtensions.cs index 4b9677d9f4..715cbf2209 100644 --- a/src/Jellyfin.Extensions/StringExtensions.cs +++ b/src/Jellyfin.Extensions/StringExtensions.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Text.RegularExpressions; using ICU4N.Text; @@ -123,5 +125,15 @@ namespace Jellyfin.Extensions { return (_transliterator.Value is null) ? text : _transliterator.Value.Transliterate(text); } + + /// + /// Ensures all strings are non-null and trimmed of leading an trailing blanks. + /// + /// The enumerable of strings to trim. + /// The enumeration of trimmed strings. + public static IEnumerable Trimmed(this IEnumerable values) + { + return values.Select(i => (i ?? string.Empty).Trim()); + } } } From 114591c1aacbdf4d07e95c536ea2e42af1c5ab0d Mon Sep 17 00:00:00 2001 From: Marc Brooks Date: Tue, 25 Feb 2025 01:51:38 -0600 Subject: [PATCH 3/3] Clean up usings and honor SortName --- MediaBrowser.Controller/Entities/BaseItem.cs | 1 - .../Probing/ProbeResultNormalizer.cs | 1 - .../MediaInfo/AudioFileProber.cs | 1 - .../Plugins/Tmdb/Movies/TmdbMovieProvider.cs | 1 - .../Savers/AlbumNfoSaver.cs | 1 + .../Savers/ArtistNfoSaver.cs | 3 +-- .../Savers/BaseNfoSaver.cs | 20 ++++++++++++++++++- .../Savers/MovieNfoSaver.cs | 1 - 8 files changed, 21 insertions(+), 8 deletions(-) diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 29b0b6861b..95d0f311e4 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -24,7 +24,6 @@ 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; diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs index c6a2ca5a4e..6b0fd9a147 100644 --- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs +++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs @@ -12,7 +12,6 @@ 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; diff --git a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs index 0e22dd96ed..b504da48f7 100644 --- a/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs +++ b/MediaBrowser.Providers/MediaInfo/AudioFileProber.cs @@ -14,7 +14,6 @@ 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; diff --git a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs index ce10d4a8a2..9bb6507fe6 100644 --- a/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs +++ b/MediaBrowser.Providers/Plugins/Tmdb/Movies/TmdbMovieProvider.cs @@ -12,7 +12,6 @@ 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; diff --git a/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs index 774539c95e..440296f095 100644 --- a/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/AlbumNfoSaver.cs @@ -74,6 +74,7 @@ namespace MediaBrowser.XbmcMetadata.Savers foreach (var track in tracks .OrderBy(i => i.ParentIndexNumber ?? 0) .ThenBy(i => i.IndexNumber ?? 0) + .ThenBy(i => SortNameOrName(i)) .ThenBy(i => i.Name?.Trim())) { writer.WriteStartElement("track"); diff --git a/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs index e1d006bfae..b5ba2d24f2 100644 --- a/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/ArtistNfoSaver.cs @@ -7,7 +7,6 @@ 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; @@ -73,7 +72,7 @@ namespace MediaBrowser.XbmcMetadata.Savers { foreach (var album in albums .OrderBy(album => album.ProductionYear ?? 0) - .ThenBy(album => album.SortName?.Trim()) + .ThenBy(album => SortNameOrName(album)) .ThenBy(album => album.Name?.Trim())) { writer.WriteStartElement("album"); diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs index 9c006e2064..f14bd437a8 100644 --- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs @@ -19,7 +19,6 @@ 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; @@ -1038,5 +1037,24 @@ namespace MediaBrowser.XbmcMetadata.Savers private string GetTagForProviderKey(string providerKey) => providerKey.ToLowerInvariant() + "id"; + + protected static string SortNameOrName(BaseItem item) + { + if (item == null) + { + return string.Empty; + } + + if (item.SortName != null) + { + string trimmed = item.SortName.Trim(); + if (trimmed.Length > 0) + { + return trimmed; + } + } + + return (item.Name ?? string.Empty).Trim(); + } } } diff --git a/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs index d119751791..a32491c458 100644 --- a/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs @@ -9,7 +9,6 @@ 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;