Skip to content

Commit

Permalink
see #34: keep iterating through artists even after a first match was …
Browse files Browse the repository at this point in the history
…found

this is needed in case multiple instances of matching artist names exist and the targeted album/track is not a child of the first matching artist
  • Loading branch information
Viperinius committed Sep 1, 2024
1 parent 411d5bc commit e8b7e26
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 23 deletions.
57 changes: 56 additions & 1 deletion Viperinius.Plugin.SpotifyImport.Tests/PlaylistSyncTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,19 @@ public PlaylistSyncWrapper(

public class PlaylistSyncTests : IDisposable
{
private void SetUpLibManagerMock(MediaBrowser.Controller.Library.ILibraryManager libManagerMock, List<MediaBrowser.Controller.Entities.BaseItem> items)
{
var list = new List<(MediaBrowser.Controller.Entities.BaseItem, MediaBrowser.Model.Dto.ItemCounts)>();
foreach (var item in items)
{
list.Add((item, new MediaBrowser.Model.Dto.ItemCounts()));
}

libManagerMock
.GetArtists(Arg.Any<MediaBrowser.Controller.Entities.InternalItemsQuery>())
.Returns(_ => new MediaBrowser.Model.Querying.QueryResult<(MediaBrowser.Controller.Entities.BaseItem, MediaBrowser.Model.Dto.ItemCounts)>(list));
}

private void SetUpLibManagerMock(MediaBrowser.Controller.Library.ILibraryManager libManagerMock, MediaBrowser.Controller.Entities.BaseItem? item)
{
var list = new List<(MediaBrowser.Controller.Entities.BaseItem, MediaBrowser.Model.Dto.ItemCounts)>();
Expand Down Expand Up @@ -99,6 +112,7 @@ public void TrackMatching_Respects_Level_Default()
{
TrackHelper.SetValidPluginInstance();

Plugin.Instance!.Configuration.ItemMatchCriteriaRaw = (int)(ItemMatchCriteria.TrackName | ItemMatchCriteria.AlbumName | ItemMatchCriteria.AlbumArtists | ItemMatchCriteria.Artists);
Plugin.Instance!.Configuration.ItemMatchLevel = ItemMatchLevel.Default;
var prov = TrackHelper.CreateProviderItem("Track", "Album", new List<string> { "Artist On Album", "Albtist" }, new List<string> { "Just Artist", "Artist 2" });
var jfItems = new List<(bool IsMatch, (Audio Track, MusicAlbum Album, MusicArtist Artist) Item)>
Expand All @@ -124,6 +138,7 @@ public void TrackMatching_Respects_Level_IgnoreCase()
{
TrackHelper.SetValidPluginInstance();

Plugin.Instance!.Configuration.ItemMatchCriteriaRaw = (int)(ItemMatchCriteria.TrackName | ItemMatchCriteria.AlbumName | ItemMatchCriteria.AlbumArtists | ItemMatchCriteria.Artists);
Plugin.Instance!.Configuration.ItemMatchLevel = ItemMatchLevel.IgnoreCase;
var prov = TrackHelper.CreateProviderItem("Track", "Album", new List<string> { "Artist On Album", "Albtist" }, new List<string> { "Just Artist", "Artist 2" });
var jfItems = new List<(bool IsMatch, (Audio Track, MusicAlbum Album, MusicArtist Artist) Item)>
Expand All @@ -148,6 +163,7 @@ public void TrackMatching_Respects_Level_IgnoreCasePunctuation()
{
TrackHelper.SetValidPluginInstance();

Plugin.Instance!.Configuration.ItemMatchCriteriaRaw = (int)(ItemMatchCriteria.TrackName | ItemMatchCriteria.AlbumName | ItemMatchCriteria.AlbumArtists | ItemMatchCriteria.Artists);
Plugin.Instance!.Configuration.ItemMatchLevel = ItemMatchLevel.IgnorePunctuationAndCase;
var prov = TrackHelper.CreateProviderItem("Track", "Album", new List<string> { "Artist On Album", "Albtist" }, new List<string> { "Just Artist", "Artist 2" });
var jfItems = new List<(bool IsMatch, (Audio Track, MusicAlbum Album, MusicArtist Artist) Item)>
Expand All @@ -172,6 +188,7 @@ public void TrackMatching_Respects_Level_IgnoreCasePunctuationParens()
{
TrackHelper.SetValidPluginInstance();

Plugin.Instance!.Configuration.ItemMatchCriteriaRaw = (int)(ItemMatchCriteria.TrackName | ItemMatchCriteria.AlbumName | ItemMatchCriteria.AlbumArtists | ItemMatchCriteria.Artists);
Plugin.Instance!.Configuration.ItemMatchLevel = ItemMatchLevel.IgnoreParensPunctuationAndCase;
var prov = TrackHelper.CreateProviderItem("Track", "Album", new List<string> { "Artist On Album", "Albtist" }, new List<string> { "Just Artist", "Artist 2" });
var jfItems = new List<(bool IsMatch, (Audio Track, MusicAlbum Album, MusicArtist Artist) Item)>
Expand All @@ -196,6 +213,7 @@ public void TrackMatching_Respects_Level_Fuzzy()
{
TrackHelper.SetValidPluginInstance();

Plugin.Instance!.Configuration.ItemMatchCriteriaRaw = (int)(ItemMatchCriteria.TrackName | ItemMatchCriteria.AlbumName | ItemMatchCriteria.AlbumArtists | ItemMatchCriteria.Artists);
Plugin.Instance!.Configuration.ItemMatchLevel = ItemMatchLevel.Fuzzy;
var prov = TrackHelper.CreateProviderItem("Track", "Album", new List<string> { "Artist On Album", "Albtist" }, new List<string> { "Just Artist", "Artist 2" });
var jfItems = new List<(bool IsMatch, (Audio Track, MusicAlbum Album, MusicArtist Artist) Item)>
Expand Down Expand Up @@ -436,7 +454,7 @@ public IEnumerator<object[]> GetEnumerator()
}

[Fact]
public void FindTrackMatch_FromMultipleCandidates()
public void FindTrackMatch_FromMultipleTrackCandidates()
{
TrackHelper.SetValidPluginInstance();
Plugin.Instance!.Configuration.ItemMatchLevel = ItemMatchLevel.IgnoreParensPunctuationAndCase;
Expand Down Expand Up @@ -486,5 +504,42 @@ public void FindTrackMatch_FromMultipleCandidates()
Assert.Contains("Richards", result!.Name);
Assert.True(failedCrit == ItemMatchCriteria.None);
}

[Fact]
public void FindTrackMatch_FromMultipleArtistCandidates()
{
TrackHelper.SetValidPluginInstance();
Plugin.Instance!.Configuration.ItemMatchLevel = ItemMatchLevel.IgnoreParensPunctuationAndCase;
Plugin.Instance!.Configuration.ItemMatchCriteriaRaw = (int)(ItemMatchCriteria.TrackName | ItemMatchCriteria.AlbumName | ItemMatchCriteria.Artists);

var prov = TrackHelper.CreateProviderItem("You Make My Dreams (Come True)", "Voices", new List<string> { "Daryl Hall & John Oates" }, new List<string> { "Daryl Hall & John Oates" });

var (jfTrackCorrect, jfAlbumCorrect, jfArtistCorrect) = TrackHelper.CreateAllJfItems("You Make My Dreams (Come True)", "Voices", "Daryl Hall & John Oates", "Daryl Hall & John Oates");
jfTrackCorrect.Id = Guid.Parse("99999999-0000-0000-0000-000000000000");
jfArtistCorrect.Id = Guid.Parse("00000000-0000-0000-0000-000000000010");

var jfArtistOther1 = ArtistHelper.CreateJfItem("Daryl Hall & John Oates", new List<MusicAlbum>());
jfArtistOther1.Id = Guid.Parse("00000000-0000-0000-0000-000000000001");
var jfArtistOther2 = ArtistHelper.CreateJfItem("Daryl Hall & John Oates", new List<MusicAlbum>());
jfArtistOther2.Id = Guid.Parse("00000000-0000-0000-0000-000000000002");

var loggerMock = Substitute.For<ILogger<PlaylistSync>>();
var plManagerMock = Substitute.For<MediaBrowser.Controller.Playlists.IPlaylistManager>();
var userManagerMock = Substitute.For<MediaBrowser.Controller.Library.IUserManager>();
var libManagerMock = Substitute.For<MediaBrowser.Controller.Library.ILibraryManager>();
SetUpLibManagerMock(libManagerMock, new List<MediaBrowser.Controller.Entities.BaseItem>
{
jfArtistOther1,
jfArtistCorrect,
jfArtistOther2
});
var wrapper = new PlaylistSyncWrapper(loggerMock, plManagerMock, libManagerMock, userManagerMock, new List<ProviderPlaylistInfo>(), new Dictionary<string, string>());

var result = wrapper.WrapGetMatchingTrack(prov, out var failedCrit);
Assert.NotNull(result);
Assert.Equal(result.Name, jfTrackCorrect.Name);
Assert.Equal(result.Id, jfTrackCorrect.Id);
Assert.True(failedCrit == ItemMatchCriteria.None);
}
}
}
74 changes: 52 additions & 22 deletions Viperinius.Plugin.SpotifyImport/PlaylistSync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,11 @@ private async Task FindTracksAndAddToPlaylist(Playlist playlist, ProviderPlaylis

var matchCandidates = new List<(int, ItemMatchLevel, Audio)>();

var artistNextIndex = 0;
while (artistNextIndex >= 0)
var artistProviderNextIndex = 0;
var artistJfNextIndex = 0;
while (artistProviderNextIndex >= 0)
{
var artist = GetArtist(providerTrackInfo, ref artistNextIndex);
var artist = GetArtist(providerTrackInfo, ref artistProviderNextIndex, ref artistJfNextIndex);
if (artist == null)
{
failedMatchCriterium |= ItemMatchCriteria.Artists;
Expand Down Expand Up @@ -302,18 +303,17 @@ private async Task FindTracksAndAddToPlaylist(Playlist playlist, ProviderPlaylis
return null;
}

private MusicArtist? GetArtist(ProviderTrackInfo providerTrackInfo, ref int nextArtistIndex)
private MusicArtist? GetArtist(ProviderTrackInfo providerTrackInfo, ref int nextProviderArtistIndex, ref int nextJfArtistIndex)
{
var artistName = providerTrackInfo.ArtistNames.ElementAtOrDefault(nextArtistIndex);
nextArtistIndex++;
var artistName = providerTrackInfo.ArtistNames.ElementAtOrDefault(nextProviderArtistIndex);
if (string.IsNullOrEmpty(artistName))
{
if (Plugin.Instance?.Configuration.EnableVerboseLogging ?? false)
{
_logger.LogInformation("> Reached end of artist list");
_logger.LogInformation("> Reached end of provider artist list");
}

nextArtistIndex = -1;
nextProviderArtistIndex = -1;
return null;
}

Expand All @@ -323,31 +323,50 @@ private async Task FindTracksAndAddToPlaylist(Playlist playlist, ProviderPlaylis
SearchTerm = artistName[0..Math.Min(artistName.Length, MaxSearchChars)],
});

foreach (var (item, _) in queryResult.Items)
if (queryResult.Items.Count == nextJfArtistIndex || queryResult.Items.Count == 0)
{
if (item is not MusicArtist artist)
{
continue;
}

if (Plugin.Instance?.Configuration.ItemMatchCriteria.HasFlag(ItemMatchCriteria.Artists) ?? false)
if (Plugin.Instance?.Configuration.EnableVerboseLogging ?? false)
{
var level = Plugin.Instance?.Configuration.ItemMatchLevel ?? ItemMatchLevel.Default;
if (!TrackComparison.ArtistOneContained(artist, providerTrackInfo, level))
_logger.LogInformation("> Reached end of jellyfin artist list");
if (queryResult.Items.Count == 0)
{
continue;
_logger.LogDebug("> Did not find any artists for the name {Name}", artistName);
}
}

return artist;
nextProviderArtistIndex++;
nextJfArtistIndex = 0;
return null;
}

if (queryResult.Items.Count == 0 && (Plugin.Instance?.Configuration.EnableVerboseLogging ?? false))
var (item, _) = queryResult.Items.ElementAt(nextJfArtistIndex);
nextJfArtistIndex++;

if (item is not MusicArtist artist)
{
_logger.LogDebug("> Did not find any artists for the name {Name}", artistName);
return null;
}

return null;
if (Plugin.Instance?.Configuration.ItemMatchCriteria.HasFlag(ItemMatchCriteria.Artists) ?? false)
{
var level = Plugin.Instance?.Configuration.ItemMatchLevel ?? ItemMatchLevel.Default;
if (!TrackComparison.ArtistOneContained(artist, providerTrackInfo, level))
{
if (Plugin.Instance?.Configuration.EnableVerboseLogging ?? false)
{
_logger.LogDebug(
"> Artist did not match: \"{Name}\"/\"{SortName}\" [Jellyfin, {Id}], \"{Name}\" [Provider]",
artist.Name,
artist.SortName,
artist.Id,
string.Join("#", providerTrackInfo.ArtistNames));
}

return null;
}
}

return artist;
}

private bool CheckAlbumArtist(MusicAlbum album, ProviderTrackInfo providerTrackInfo)
Expand All @@ -372,6 +391,7 @@ private bool CheckAlbumArtist(MusicAlbum album, ProviderTrackInfo providerTrackI
AlbumArtistIds = new[] { artist.Id },
IncludeItemTypes = new[] { BaseItemKind.MusicAlbum }
});
albums ??= new List<MediaBrowser.Controller.Entities.BaseItem>();
}

var item = albums.ElementAtOrDefault(nextAlbumIndex);
Expand All @@ -397,6 +417,16 @@ private bool CheckAlbumArtist(MusicAlbum album, ProviderTrackInfo providerTrackI
var level = Plugin.Instance?.Configuration.ItemMatchLevel ?? ItemMatchLevel.Default;
if (!TrackComparison.AlbumNameEqual(album, providerTrackInfo, level).ComparisonResult)
{
if (Plugin.Instance?.Configuration.EnableVerboseLogging ?? false)
{
_logger.LogDebug(
"> Album did not match: \"{Name}\"/\"{SortName}\" [Jellyfin, {Id}], \"{Name}\" [Provider]",
album.Name,
album.SortName,
album.Id,
providerTrackInfo.AlbumName);
}

return null;
}
}
Expand Down

0 comments on commit e8b7e26

Please sign in to comment.