mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-02-09 23:55:10 -05:00
Add manual track mapping feature
- Add 'Map to Local' button for external tracks in playlist viewer - Search Jellyfin library to find local tracks - Save manual mappings (Spotify ID → Jellyfin ID) in cache - Manual mappings take priority over fuzzy matching - Clear playlist cache when mapping is saved to force refresh - UI shows which tracks are manually mapped in logs
This commit is contained in:
@@ -457,6 +457,105 @@ public class AdminController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Search Jellyfin library for tracks (for manual mapping)
|
||||
/// </summary>
|
||||
[HttpGet("jellyfin/search")]
|
||||
public async Task<IActionResult> SearchJellyfinTracks([FromQuery] string query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
return BadRequest(new { error = "Query is required" });
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var url = $"{_jellyfinSettings.Url}/Items?searchTerm={Uri.EscapeDataString(query)}&includeItemTypes=Audio&recursive=true&limit=20";
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
request.Headers.Add("X-Emby-Authorization", GetJellyfinAuthHeader());
|
||||
|
||||
var response = await _jellyfinHttpClient.SendAsync(request);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return StatusCode((int)response.StatusCode, new { error = "Failed to search Jellyfin" });
|
||||
}
|
||||
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
using var doc = JsonDocument.Parse(json);
|
||||
|
||||
var tracks = new List<object>();
|
||||
if (doc.RootElement.TryGetProperty("Items", out var items))
|
||||
{
|
||||
foreach (var item in items.EnumerateArray())
|
||||
{
|
||||
var id = item.TryGetProperty("Id", out var idEl) ? idEl.GetString() : "";
|
||||
var title = item.TryGetProperty("Name", out var nameEl) ? nameEl.GetString() : "";
|
||||
var album = item.TryGetProperty("Album", out var albumEl) ? albumEl.GetString() : "";
|
||||
var artist = "";
|
||||
|
||||
if (item.TryGetProperty("Artists", out var artistsEl) && artistsEl.GetArrayLength() > 0)
|
||||
{
|
||||
artist = artistsEl[0].GetString() ?? "";
|
||||
}
|
||||
else if (item.TryGetProperty("AlbumArtist", out var albumArtistEl))
|
||||
{
|
||||
artist = albumArtistEl.GetString() ?? "";
|
||||
}
|
||||
|
||||
tracks.Add(new { id, title, artist, album });
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(new { tracks });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to search Jellyfin tracks");
|
||||
return StatusCode(500, new { error = "Search failed" });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save manual track mapping
|
||||
/// </summary>
|
||||
[HttpPost("playlists/{name}/map")]
|
||||
public async Task<IActionResult> SaveManualMapping(string name, [FromBody] ManualMappingRequest request)
|
||||
{
|
||||
var decodedName = Uri.UnescapeDataString(name);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.SpotifyId) || string.IsNullOrWhiteSpace(request.JellyfinId))
|
||||
{
|
||||
return BadRequest(new { error = "SpotifyId and JellyfinId are required" });
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Store mapping in cache (you could also persist to a file)
|
||||
var mappingKey = $"spotify:manual-map:{decodedName}:{request.SpotifyId}";
|
||||
await _cache.SetAsync(mappingKey, request.JellyfinId, TimeSpan.FromDays(365)); // Long TTL
|
||||
|
||||
_logger.LogInformation("Manual mapping saved: {Playlist} - Spotify {SpotifyId} → Jellyfin {JellyfinId}",
|
||||
decodedName, request.SpotifyId, request.JellyfinId);
|
||||
|
||||
// Clear the matched tracks cache to force re-matching
|
||||
var cacheKey = $"spotify:matched:{decodedName}";
|
||||
await _cache.DeleteAsync(cacheKey);
|
||||
|
||||
return Ok(new { message = "Mapping saved successfully" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to save manual mapping");
|
||||
return StatusCode(500, new { error = "Failed to save mapping" });
|
||||
}
|
||||
}
|
||||
|
||||
public class ManualMappingRequest
|
||||
{
|
||||
public string SpotifyId { get; set; } = "";
|
||||
public string JellyfinId { get; set; } = "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Trigger track matching for all playlists
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user