Add local/external track columns to Jellyfin playlists, remove libraries filter

This commit is contained in:
2026-02-03 16:08:49 -05:00
parent 75c7acb745
commit df7f11e769
2 changed files with 112 additions and 40 deletions

View File

@@ -697,7 +697,7 @@ public class AdminController : ControllerBase
/// Get all playlists from Jellyfin
/// </summary>
[HttpGet("jellyfin/playlists")]
public async Task<IActionResult> GetJellyfinPlaylists([FromQuery] string? userId = null, [FromQuery] string? parentId = null)
public async Task<IActionResult> GetJellyfinPlaylists([FromQuery] string? userId = null)
{
if (string.IsNullOrEmpty(_jellyfinSettings.Url) || string.IsNullOrEmpty(_jellyfinSettings.ApiKey))
{
@@ -706,7 +706,7 @@ public class AdminController : ControllerBase
try
{
// Build URL with optional userId and parentId (library) filters
// Build URL with optional userId filter
var url = $"{_jellyfinSettings.Url}/Items?IncludeItemTypes=Playlist&Recursive=true&Fields=ProviderIds,ChildCount,RecursiveItemCount,SongCount";
if (!string.IsNullOrEmpty(userId))
@@ -714,11 +714,6 @@ public class AdminController : ControllerBase
url += $"&UserId={userId}";
}
if (!string.IsNullOrEmpty(parentId))
{
url += $"&ParentId={parentId}";
}
var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Add("X-Emby-Authorization", GetJellyfinAuthHeader());
@@ -758,13 +753,19 @@ public class AdminController : ControllerBase
var isConfigured = configuredPlaylist != null;
var linkedSpotifyId = configuredPlaylist?.Id;
// Fetch track details to categorize local vs external
var trackStats = await GetPlaylistTrackStats(id!);
playlists.Add(new
{
id,
name,
trackCount = childCount,
linkedSpotifyId,
isConfigured
isConfigured,
localTracks = trackStats.LocalTracks,
externalTracks = trackStats.ExternalTracks,
externalAvailable = trackStats.ExternalAvailable
});
}
}
@@ -778,6 +779,94 @@ public class AdminController : ControllerBase
}
}
/// <summary>
/// Get track statistics for a playlist (local vs external)
/// </summary>
private async Task<(int LocalTracks, int ExternalTracks, int ExternalAvailable)> GetPlaylistTrackStats(string playlistId)
{
try
{
var url = $"{_jellyfinSettings.Url}/Playlists/{playlistId}/Items?Fields=ProviderIds,Path,MediaSources";
var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Add("X-Emby-Authorization", GetJellyfinAuthHeader());
var response = await _jellyfinHttpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
return (0, 0, 0);
}
var json = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(json);
var localTracks = 0;
var externalTracks = 0;
var externalAvailable = 0;
if (doc.RootElement.TryGetProperty("Items", out var items))
{
foreach (var item in items.EnumerateArray())
{
// Check if track has a local path (is in user's library)
var hasPath = item.TryGetProperty("Path", out var path) &&
path.ValueKind == JsonValueKind.String &&
!string.IsNullOrEmpty(path.GetString());
// Check MediaSources to see if it's a local file vs external
var isLocal = false;
if (item.TryGetProperty("MediaSources", out var mediaSources) &&
mediaSources.ValueKind == JsonValueKind.Array)
{
foreach (var source in mediaSources.EnumerateArray())
{
if (source.TryGetProperty("Protocol", out var protocol))
{
var protocolStr = protocol.GetString();
if (protocolStr == "File")
{
isLocal = true;
break;
}
}
// Also check if Path exists in MediaSource
if (source.TryGetProperty("Path", out var sourcePath) &&
sourcePath.ValueKind == JsonValueKind.String &&
!string.IsNullOrEmpty(sourcePath.GetString()))
{
isLocal = true;
break;
}
}
}
// Fallback to checking Path property
if (!isLocal && hasPath)
{
isLocal = true;
}
if (isLocal)
{
localTracks++;
}
else
{
externalTracks++;
// For now, if it's in the playlist but not local, it means a provider found it
externalAvailable++;
}
}
}
return (localTracks, externalTracks, externalAvailable);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to get track stats for playlist {PlaylistId}", playlistId);
return (0, 0, 0);
}
}
/// <summary>
/// Link a Jellyfin playlist to a Spotify playlist
/// </summary>