Compare commits

...

10 commits

Author SHA1 Message Date
Marc Brooks 7aad75d882
Merge d04f255e71 into e2a22cec0e 2024-05-04 13:06:58 -04:00
Nyanmisaka e2a22cec0e Translated using Weblate (Chinese (Simplified))
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/zh_Hans/
2024-05-04 13:06:46 -04:00
queeup 067962ae2a Translated using Weblate (Turkish)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/tr/
2024-05-04 13:06:46 -04:00
Szilágyi Kristóf 8a65d239b7 Translated using Weblate (Hungarian)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/hu/
2024-05-04 13:06:46 -04:00
HiPotionQ8 518404cd1d Translated using Weblate (Arabic)
Translation: Jellyfin/Jellyfin
Translate-URL: https://translate.jellyfin.org/projects/jellyfin/jellyfin-core/ar/
2024-05-04 13:06:46 -04:00
Marc Brooks d04f255e71
Merge branch 'master' into sort-nfo-data 2024-03-18 14:48:56 -05:00
Marc Brooks cb292009e0
Merge branch 'master' into sort-nfo-data 2024-03-05 14:55:32 -06:00
Marc Brooks 3984e18e1a
Resolve CodeQL issue 2023-11-16 12:18:08 -06:00
Marc Brooks 5b98a5cfbe
Merge branch 'master' into sort-nfo-data 2023-11-16 11:19:40 -06:00
Marc Brooks 21d8e5438e
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
2023-08-02 01:06:34 -05:00
21 changed files with 133 additions and 76 deletions

View file

@ -128,5 +128,7 @@
"TaskRefreshTrickplayImages": "توليد صور Trickplay",
"TaskRefreshTrickplayImagesDescription": "يُنشئ معاينات Trickplay لمقاطع الفيديو في المكتبات المُمكّنة.",
"TaskCleanCollectionsAndPlaylists": "حذف المجموعات وقوائم التشغيل",
"TaskCleanCollectionsAndPlaylistsDescription": "حذف عناصر من المجموعات وقوائم التشغيل التي لم تعد موجودة."
"TaskCleanCollectionsAndPlaylistsDescription": "حذف عناصر من المجموعات وقوائم التشغيل التي لم تعد موجودة.",
"TaskAudioNormalization": "تطبيع الصوت",
"TaskAudioNormalizationDescription": "مسح الملفات لتطبيع بيانات الصوت."
}

View file

@ -1,13 +1,13 @@
{
"Albums": "Albumok",
"AppDeviceValues": "Program: {0}, eszköz: {1}",
"AppDeviceValues": "Program: {0}, Eszköz: {1}",
"Application": "Alkalmazás",
"Artists": "Előadók",
"AuthenticationSucceededWithUserName": "{0} sikeresen hitelesítve",
"Books": "Könyvek",
"CameraImageUploadedFrom": "Új kamerakép feltöltve innen: {0}",
"CameraImageUploadedFrom": "Új kamerakép lett feltöltve innen: {0}",
"Channels": "Csatornák",
"ChapterNameValue": "{0}. jelenet",
"ChapterNameValue": "Jelenet {0}",
"Collections": "Gyűjtemények",
"DeviceOfflineWithName": "{0} kijelentkezett",
"DeviceOnlineWithName": "{0} belépett",
@ -15,27 +15,27 @@
"Favorites": "Kedvencek",
"Folders": "Könyvtárak",
"Genres": "Műfajok",
"HeaderAlbumArtists": "Albumelőadók",
"HeaderAlbumArtists": "Album előadók",
"HeaderContinueWatching": "Megtekintés folytatása",
"HeaderFavoriteAlbums": "Kedvenc albumok",
"HeaderFavoriteAlbums": "Kedvenc Albumok",
"HeaderFavoriteArtists": "Kedvenc előadók",
"HeaderFavoriteEpisodes": "Kedvenc epizódok",
"HeaderFavoriteShows": "Kedvenc sorozatok",
"HeaderFavoriteSongs": "Kedvenc számok",
"HeaderFavoriteSongs": "Kedvenc dalok",
"HeaderLiveTV": "Élő TV",
"HeaderNextUp": "Következik",
"HeaderRecordingGroups": "Felvételi csoportok",
"HomeVideos": "Házi videók",
"HomeVideos": "Otthoni videók",
"Inherit": "Örökölt",
"ItemAddedWithName": "{0} hozzáadva a könyvtárhoz",
"ItemRemovedWithName": "{0} eltávolítva a könyvtárból",
"LabelIpAddressValue": "IP-cím: {0}",
"LabelRunningTimeValue": "Lejátszási idő: {0}",
"Latest": "Legújabb",
"MessageApplicationUpdated": "A Jellyfin kiszolgáló frissítve",
"MessageApplicationUpdated": "A Jellyfin kiszolgáló frissítve lett",
"MessageApplicationUpdatedTo": "A Jellyfin kiszolgáló frissítve lett a következőre: {0}",
"MessageNamedServerConfigurationUpdatedWithValue": "A kiszolgálókonfigurációs rész frissítve: {0}",
"MessageServerConfigurationUpdated": "Kiszolgálókonfiguráció frissítve",
"MessageNamedServerConfigurationUpdatedWithValue": "A kiszolgálókonfigurációs rész frissítve lett: {0}",
"MessageServerConfigurationUpdated": "Kiszolgálókonfiguráció frissítve lett",
"MixedContent": "Vegyes tartalom",
"Movies": "Filmek",
"Music": "Zenék",
@ -46,7 +46,7 @@
"NewVersionIsAvailable": "Letölthető a Jellyfin kiszolgáló új verziója.",
"NotificationOptionApplicationUpdateAvailable": "Frissítés érhető el az alkalmazáshoz",
"NotificationOptionApplicationUpdateInstalled": "Alkalmazásfrissítés telepítve",
"NotificationOptionAudioPlayback": "Hanglejátszás elkezdve",
"NotificationOptionAudioPlayback": "Hanglejátszás elkezdődött",
"NotificationOptionAudioPlaybackStopped": "Hanglejátszás leállítva",
"NotificationOptionCameraImageUploaded": "Kamerakép feltöltve",
"NotificationOptionInstallationFailed": "Telepítési hiba",
@ -126,5 +126,9 @@
"External": "Külső",
"HearingImpaired": "Hallássérült",
"TaskRefreshTrickplayImages": "Trickplay képek generálása",
"TaskRefreshTrickplayImagesDescription": "Trickplay előnézetet készít az engedélyezett könyvtárakban lévő videókhoz."
"TaskRefreshTrickplayImagesDescription": "Trickplay előnézetet készít az engedélyezett könyvtárakban lévő videókhoz.",
"TaskAudioNormalization": "Hangerő Normalizáció",
"TaskCleanCollectionsAndPlaylistsDescription": "Nem létező elemek törlése a gyűjteményekből és lejátszási listákról.",
"TaskAudioNormalizationDescription": "Hangerő normalizációs adatok keresése.",
"TaskCleanCollectionsAndPlaylists": "Gyűjtemények és lejátszási listák optimalizálása"
}

View file

@ -128,5 +128,7 @@
"TaskRefreshTrickplayImages": "Trickplay Görselleri Oluştur",
"TaskRefreshTrickplayImagesDescription": "Etkin kütüphanelerdeki videolar için trickplay önizlemeleri oluşturur.",
"TaskCleanCollectionsAndPlaylistsDescription": "Artık var olmayan koleksiyon ve çalma listelerindeki ögeleri kaldırır.",
"TaskCleanCollectionsAndPlaylists": "Koleksiyonları ve çalma listelerini temizleyin"
"TaskCleanCollectionsAndPlaylists": "Koleksiyonları ve çalma listelerini temizleyin",
"TaskAudioNormalizationDescription": "Ses normalleştirme verileri için dosyaları tarar.",
"TaskAudioNormalization": "Ses Normalleştirme"
}

View file

@ -128,5 +128,7 @@
"TaskRefreshTrickplayImages": "生成时间轴缩略图",
"TaskRefreshTrickplayImagesDescription": "为启用的媒体库中的视频生成时间轴缩略图。",
"TaskCleanCollectionsAndPlaylists": "清理合集和播放列表",
"TaskCleanCollectionsAndPlaylistsDescription": "清理合集和播放列表中已不存在的项目。"
"TaskCleanCollectionsAndPlaylistsDescription": "清理合集和播放列表中已不存在的项目。",
"TaskAudioNormalization": "音频标准化",
"TaskAudioNormalizationDescription": "扫描文件以寻找音频标准化数据。"
}

View file

@ -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;
@ -916,7 +917,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));
@ -1771,7 +1772,7 @@ namespace MediaBrowser.Controller.Entities
public void AddStudio(string name)
{
ArgumentException.ThrowIfNullOrEmpty(name);
name = name.Trim();
var current = Studios;
if (!current.Contains(name, StringComparison.OrdinalIgnoreCase))
@ -1790,7 +1791,7 @@ namespace MediaBrowser.Controller.Entities
public void SetStudios(IEnumerable<string> names)
{
Studios = names.Distinct().ToArray();
Studios = names.Trimmed().Distinct().ToArray();
}
/// <summary>
@ -1801,6 +1802,7 @@ namespace MediaBrowser.Controller.Entities
public void AddGenre(string name)
{
ArgumentException.ThrowIfNullOrEmpty(name);
name = name.Trim();
var genres = Genres;
if (!genres.Contains(name, StringComparison.OrdinalIgnoreCase))

View file

@ -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))
{

View file

@ -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());
}
}
}

View file

@ -258,7 +258,12 @@ namespace MediaBrowser.LocalMetadata.Parsers
case "Network":
foreach (var name in reader.GetStringArray())
{
item.AddStudio(name);
if (string.IsNullOrWhiteSpace(name))
{
continue;
}
item.AddStudio(name.Trim());
}
break;
@ -294,7 +299,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
var trailer = reader.ReadNormalizedString();
if (!string.IsNullOrEmpty(trailer))
{
item.AddTrailerUrl(trailer);
item.AddTrailerUrl(trailer.Trim());
}
break;
@ -302,7 +307,10 @@ namespace MediaBrowser.LocalMetadata.Parsers
var displayOrder = reader.ReadNormalizedString();
if (!string.IsNullOrEmpty(displayOrder) && item is IHasDisplayOrder hasDisplayOrder)
{
hasDisplayOrder.DisplayOrder = displayOrder;
if (!string.IsNullOrWhiteSpace(displayOrder))
{
hasDisplayOrder.DisplayOrder = displayOrder.Trim();
}
}
break;
@ -655,7 +663,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
var tag = reader.ReadNormalizedString();
if (!string.IsNullOrEmpty(tag))
{
tags.Add(tag);
tags.Add(tag.Trim());
}
break;
@ -727,7 +735,7 @@ namespace MediaBrowser.LocalMetadata.Parsers
var trailer = reader.ReadNormalizedString();
if (!string.IsNullOrEmpty(trailer))
{
item.AddTrailerUrl(trailer);
item.AddTrailerUrl(trailer.Trim());
}
break;

View file

@ -11,6 +11,7 @@ using System.Xml;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
@ -531,42 +532,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
});
}
@ -616,7 +619,7 @@ namespace MediaBrowser.MediaEncoding.Probing
return new NameValuePair
{
Name = name,
Value = value
Value = value?.Trim()
};
}
@ -1449,7 +1452,7 @@ namespace MediaBrowser.MediaEncoding.Probing
continue;
}
genres.Add(genre);
genres.Add(genre.Trim());
}
info.Genres = genres

View file

@ -11,6 +11,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;
@ -188,7 +189,7 @@ namespace MediaBrowser.Providers.MediaInfo
{
PeopleHelper.AddPerson(people, new PersonInfo
{
Name = albumArtist,
Name = albumArtist.Trim(),
Type = PersonKind.AlbumArtist
});
}
@ -201,7 +202,7 @@ namespace MediaBrowser.Providers.MediaInfo
{
PeopleHelper.AddPerson(people, new PersonInfo
{
Name = performer,
Name = performer.Trim(),
Type = PersonKind.Artist
});
}
@ -213,7 +214,7 @@ namespace MediaBrowser.Providers.MediaInfo
{
PeopleHelper.AddPerson(people, new PersonInfo
{
Name = composer,
Name = composer.Trim(),
Type = PersonKind.Composer
});
}
@ -250,18 +251,18 @@ namespace MediaBrowser.Providers.MediaInfo
if (!audio.LockedFields.Contains(MetadataField.Name) && !string.IsNullOrEmpty(tags.Title))
{
audio.Name = tags.Title;
audio.Name = options.ReplaceAllMetadata || string.IsNullOrEmpty(audio.Name) ? tags.Title.Trim() : audio.Name;
}
if (options.ReplaceAllMetadata)
{
audio.Album = tags.Album;
audio.Album = tags.Album.Trim();
audio.IndexNumber = Convert.ToInt32(tags.Track);
audio.ParentIndexNumber = Convert.ToInt32(tags.Disc);
}
else
{
audio.Album ??= tags.Album;
audio.Album ??= tags.Album.Trim();
audio.IndexNumber ??= Convert.ToInt32(tags.Track);
audio.ParentIndexNumber ??= Convert.ToInt32(tags.Disc);
}
@ -280,7 +281,7 @@ namespace MediaBrowser.Providers.MediaInfo
if (!audio.LockedFields.Contains(MetadataField.Genres))
{
audio.Genres = options.ReplaceAllMetadata || audio.Genres == null || audio.Genres.Length == 0
? tags.Genres.Distinct(StringComparer.OrdinalIgnoreCase).ToArray()
? tags.Genres.Trimmed().Distinct(StringComparer.OrdinalIgnoreCase).ToArray()
: audio.Genres;
}

View file

@ -508,9 +508,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()
});
}

View file

@ -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
});
}

View file

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

View file

@ -258,7 +258,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
};
@ -293,7 +293,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.Movies
var personInfo = new PersonInfo
{
Name = person.Name.Trim(),
Role = person.Job,
Role = person.Job?.Trim(),
Type = type
};

View file

@ -222,7 +222,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
});
@ -236,7 +236,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
});
@ -260,7 +260,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
metadataResult.AddPerson(new PersonInfo
{
Name = person.Name.Trim(),
Role = person.Job,
Role = person.Job?.Trim(),
Type = type
});
}

View file

@ -88,7 +88,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
result.AddPerson(new PersonInfo
{
Name = cast[i].Name.Trim(),
Role = cast[i].Character,
Role = cast[i].Character.Trim(),
Type = PersonKind.Actor,
SortOrder = cast[i].Order
});
@ -111,7 +111,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
result.AddPerson(new PersonInfo
{
Name = person.Name.Trim(),
Role = person.Job,
Role = person.Job?.Trim(),
Type = type
});
}

View file

@ -352,7 +352,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)
@ -390,7 +390,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
yield return new PersonInfo
{
Name = person.Name.Trim(),
Role = person.Job,
Role = person.Job?.Trim(),
Type = type
};
}

View file

@ -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));
}

View file

@ -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");

View file

@ -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))
{

View file

@ -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;
@ -103,7 +104,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);
}