mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-02-09 23:55:10 -05:00
fix: manual mapping UI and [S] tag consistency
- Fix manual mapping track selection visual feedback (use accent color + background) - Clear all playlist caches after manual mapping (matched, ordered, items) - Strip [S] suffix from titles/artists/albums when searching for lyrics - Add [S] suffix to artist and album names when song has [S] for consistency - Ensures external tracks are clearly marked across all metadata fields All 225 tests pass.
This commit is contained in:
@@ -645,9 +645,16 @@ public class AdminController : ControllerBase
|
|||||||
_logger.LogInformation("Manual mapping saved: {Playlist} - Spotify {SpotifyId} → Jellyfin {JellyfinId}",
|
_logger.LogInformation("Manual mapping saved: {Playlist} - Spotify {SpotifyId} → Jellyfin {JellyfinId}",
|
||||||
decodedName, request.SpotifyId, request.JellyfinId);
|
decodedName, request.SpotifyId, request.JellyfinId);
|
||||||
|
|
||||||
// Clear the matched tracks cache to force re-matching
|
// Clear all related caches to force rebuild
|
||||||
var cacheKey = $"spotify:matched:{decodedName}";
|
var matchedCacheKey = $"spotify:matched:{decodedName}";
|
||||||
await _cache.DeleteAsync(cacheKey);
|
var orderedCacheKey = $"spotify:matched:ordered:{decodedName}";
|
||||||
|
var playlistItemsKey = $"spotify:playlist:items:{decodedName}";
|
||||||
|
|
||||||
|
await _cache.DeleteAsync(matchedCacheKey);
|
||||||
|
await _cache.DeleteAsync(orderedCacheKey);
|
||||||
|
await _cache.DeleteAsync(playlistItemsKey);
|
||||||
|
|
||||||
|
_logger.LogInformation("Cleared playlist caches for {Playlist} to force rebuild", decodedName);
|
||||||
|
|
||||||
return Ok(new { message = "Mapping saved successfully" });
|
return Ok(new { message = "Mapping saved successfully" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1183,12 +1183,24 @@ public class JellyfinController : ControllerBase
|
|||||||
return NotFound(new { error = "Song not found" });
|
return NotFound(new { error = "Song not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Strip [S] suffix from title, artist, and album for lyrics search
|
||||||
|
// The [S] tag is added to external tracks but shouldn't be used in lyrics queries
|
||||||
|
var searchTitle = song.Title.Replace(" [S]", "").Trim();
|
||||||
|
var searchArtist = song.Artist?.Replace(" [S]", "").Trim() ?? "";
|
||||||
|
var searchAlbum = song.Album?.Replace(" [S]", "").Trim() ?? "";
|
||||||
|
var searchArtists = song.Artists.Select(a => a.Replace(" [S]", "").Trim()).ToList();
|
||||||
|
|
||||||
|
if (searchArtists.Count == 0 && !string.IsNullOrEmpty(searchArtist))
|
||||||
|
{
|
||||||
|
searchArtists.Add(searchArtist);
|
||||||
|
}
|
||||||
|
|
||||||
LyricsInfo? lyrics = null;
|
LyricsInfo? lyrics = null;
|
||||||
|
|
||||||
// Try Spotify lyrics first (better synced lyrics quality)
|
// Try Spotify lyrics first (better synced lyrics quality)
|
||||||
if (_spotifyLyricsService != null && _spotifyApiSettings.Enabled)
|
if (_spotifyLyricsService != null && _spotifyApiSettings.Enabled)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Trying Spotify lyrics for: {Artist} - {Title}", song.Artist, song.Title);
|
_logger.LogInformation("Trying Spotify lyrics for: {Artist} - {Title}", searchArtist, searchTitle);
|
||||||
|
|
||||||
SpotifyLyricsResult? spotifyLyrics = null;
|
SpotifyLyricsResult? spotifyLyrics = null;
|
||||||
|
|
||||||
@@ -1199,18 +1211,18 @@ public class JellyfinController : ControllerBase
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Search by metadata
|
// Search by metadata (without [S] tags)
|
||||||
spotifyLyrics = await _spotifyLyricsService.SearchAndGetLyricsAsync(
|
spotifyLyrics = await _spotifyLyricsService.SearchAndGetLyricsAsync(
|
||||||
song.Title,
|
searchTitle,
|
||||||
song.Artists.Count > 0 ? song.Artists[0] : song.Artist ?? "",
|
searchArtists.Count > 0 ? searchArtists[0] : searchArtist,
|
||||||
song.Album,
|
searchAlbum,
|
||||||
song.Duration.HasValue ? song.Duration.Value * 1000 : null);
|
song.Duration.HasValue ? song.Duration.Value * 1000 : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spotifyLyrics != null && spotifyLyrics.Lines.Count > 0)
|
if (spotifyLyrics != null && spotifyLyrics.Lines.Count > 0)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Found Spotify lyrics for {Artist} - {Title} ({LineCount} lines, type: {SyncType})",
|
_logger.LogInformation("Found Spotify lyrics for {Artist} - {Title} ({LineCount} lines, type: {SyncType})",
|
||||||
song.Artist, song.Title, spotifyLyrics.Lines.Count, spotifyLyrics.SyncType);
|
searchArtist, searchTitle, spotifyLyrics.Lines.Count, spotifyLyrics.SyncType);
|
||||||
lyrics = _spotifyLyricsService.ToLyricsInfo(spotifyLyrics);
|
lyrics = _spotifyLyricsService.ToLyricsInfo(spotifyLyrics);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1219,15 +1231,15 @@ public class JellyfinController : ControllerBase
|
|||||||
if (lyrics == null)
|
if (lyrics == null)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Searching LRCLIB for lyrics: {Artists} - {Title}",
|
_logger.LogInformation("Searching LRCLIB for lyrics: {Artists} - {Title}",
|
||||||
song.Artists.Count > 0 ? string.Join(", ", song.Artists) : song.Artist,
|
string.Join(", ", searchArtists),
|
||||||
song.Title);
|
searchTitle);
|
||||||
var lrclibService = HttpContext.RequestServices.GetService<LrclibService>();
|
var lrclibService = HttpContext.RequestServices.GetService<LrclibService>();
|
||||||
if (lrclibService != null)
|
if (lrclibService != null)
|
||||||
{
|
{
|
||||||
lyrics = await lrclibService.GetLyricsAsync(
|
lyrics = await lrclibService.GetLyricsAsync(
|
||||||
song.Title,
|
searchTitle,
|
||||||
song.Artists.Count > 0 ? song.Artists.ToArray() : new[] { song.Artist ?? "" },
|
searchArtists.ToArray(),
|
||||||
song.Album ?? "",
|
searchAlbum,
|
||||||
song.Duration ?? 0);
|
song.Duration ?? 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -233,9 +233,29 @@ public class JellyfinResponseBuilder
|
|||||||
{
|
{
|
||||||
// Add " [S]" suffix to external song titles (S = streaming source)
|
// Add " [S]" suffix to external song titles (S = streaming source)
|
||||||
var songTitle = song.Title;
|
var songTitle = song.Title;
|
||||||
|
var artistName = song.Artist;
|
||||||
|
var albumName = song.Album;
|
||||||
|
var artistNames = song.Artists.ToList();
|
||||||
|
|
||||||
if (!song.IsLocal)
|
if (!song.IsLocal)
|
||||||
{
|
{
|
||||||
songTitle = $"{song.Title} [S]";
|
songTitle = $"{song.Title} [S]";
|
||||||
|
|
||||||
|
// Also add [S] to artist and album names for consistency
|
||||||
|
if (!string.IsNullOrEmpty(artistName) && !artistName.EndsWith(" [S]"))
|
||||||
|
{
|
||||||
|
artistName = $"{artistName} [S]";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(albumName) && !albumName.EndsWith(" [S]"))
|
||||||
|
{
|
||||||
|
albumName = $"{albumName} [S]";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add [S] to all artist names in the list
|
||||||
|
artistNames = artistNames.Select(a =>
|
||||||
|
!string.IsNullOrEmpty(a) && !a.EndsWith(" [S]") ? $"{a} [S]" : a
|
||||||
|
).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
var item = new Dictionary<string, object?>
|
var item = new Dictionary<string, object?>
|
||||||
@@ -278,9 +298,9 @@ public class JellyfinResponseBuilder
|
|||||||
["Key"] = $"Audio-{song.Id}",
|
["Key"] = $"Audio-{song.Id}",
|
||||||
["ItemId"] = song.Id
|
["ItemId"] = song.Id
|
||||||
},
|
},
|
||||||
["Artists"] = song.Artists.Count > 0 ? song.Artists.ToArray() : new[] { song.Artist ?? "" },
|
["Artists"] = artistNames.Count > 0 ? artistNames.ToArray() : new[] { artistName ?? "" },
|
||||||
["ArtistItems"] = song.Artists.Count > 0
|
["ArtistItems"] = artistNames.Count > 0
|
||||||
? song.Artists.Select((name, index) => new Dictionary<string, object?>
|
? artistNames.Select((name, index) => new Dictionary<string, object?>
|
||||||
{
|
{
|
||||||
["Name"] = name,
|
["Name"] = name,
|
||||||
["Id"] = index == 0 && song.ArtistId != null
|
["Id"] = index == 0 && song.ArtistId != null
|
||||||
@@ -292,18 +312,18 @@ public class JellyfinResponseBuilder
|
|||||||
new Dictionary<string, object?>
|
new Dictionary<string, object?>
|
||||||
{
|
{
|
||||||
["Id"] = song.ArtistId ?? song.Id,
|
["Id"] = song.ArtistId ?? song.Id,
|
||||||
["Name"] = song.Artist ?? ""
|
["Name"] = artistName ?? ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
["Album"] = song.Album,
|
["Album"] = albumName,
|
||||||
["AlbumId"] = song.AlbumId ?? song.Id,
|
["AlbumId"] = song.AlbumId ?? song.Id,
|
||||||
["AlbumPrimaryImageTag"] = song.AlbumId ?? song.Id,
|
["AlbumPrimaryImageTag"] = song.AlbumId ?? song.Id,
|
||||||
["AlbumArtist"] = song.AlbumArtist ?? song.Artist,
|
["AlbumArtist"] = song.AlbumArtist ?? artistName,
|
||||||
["AlbumArtists"] = new[]
|
["AlbumArtists"] = new[]
|
||||||
{
|
{
|
||||||
new Dictionary<string, object?>
|
new Dictionary<string, object?>
|
||||||
{
|
{
|
||||||
["Name"] = song.AlbumArtist ?? song.Artist ?? "",
|
["Name"] = song.AlbumArtist ?? artistName ?? "",
|
||||||
["Id"] = song.ArtistId ?? song.Id
|
["Id"] = song.ArtistId ?? song.Id
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2005,10 +2005,12 @@
|
|||||||
// Remove selection from all tracks
|
// Remove selection from all tracks
|
||||||
document.querySelectorAll('#map-search-results .track-item').forEach(el => {
|
document.querySelectorAll('#map-search-results .track-item').forEach(el => {
|
||||||
el.style.border = '2px solid transparent';
|
el.style.border = '2px solid transparent';
|
||||||
|
el.style.background = '';
|
||||||
});
|
});
|
||||||
|
|
||||||
// Highlight selected track
|
// Highlight selected track
|
||||||
element.style.border = '2px solid var(--primary)';
|
element.style.border = '2px solid var(--accent)';
|
||||||
|
element.style.background = 'var(--bg-tertiary)';
|
||||||
|
|
||||||
// Store selected ID and enable save button
|
// Store selected ID and enable save button
|
||||||
document.getElementById('map-selected-jellyfin-id').value = jellyfinId;
|
document.getElementById('map-selected-jellyfin-id').value = jellyfinId;
|
||||||
|
|||||||
Reference in New Issue
Block a user