mirror of
https://github.com/SoPat712/allstarr.git
synced 2026-02-09 23:55:10 -05:00
Add favorite-to-keep feature for external tracks
- When favoriting an external track, automatically copy to /kept folder - Organized as Artist/Album/Track structure - Includes cover art if available - Downloads track first if not already cached - Add KEPT_PATH and CACHE_PATH volumes to docker-compose - Update .env.example and README with new feature
This commit is contained in:
@@ -30,6 +30,12 @@ MUSIC_SERVICE=SquidWTF
|
||||
# Path where downloaded songs will be stored on the host (only applies if STORAGE_MODE=Permanent)
|
||||
DOWNLOAD_PATH=./downloads
|
||||
|
||||
# Path where favorited external tracks are permanently kept
|
||||
KEPT_PATH=./kept
|
||||
|
||||
# Path for cache files (Spotify missing tracks, etc.)
|
||||
CACHE_PATH=./cache
|
||||
|
||||
# ===== SQUIDWTF CONFIGURATION =====
|
||||
# Different quality options for SquidWTF. Only FLAC supported right now
|
||||
SQUIDWTF_QUALITY=FLAC
|
||||
|
||||
@@ -83,6 +83,7 @@ This project brings together all the music streaming providers into one unified
|
||||
- **Transparent Proxy**: Sits between your music clients and media server
|
||||
- **Automatic Search**: Searches streaming providers when songs aren't local
|
||||
- **On-the-Fly Downloads**: Songs download and cache for future use
|
||||
- **Favorite to Keep**: When you favorite an external track, it's automatically copied to a permanent `/kept` folder separate from the cache
|
||||
- **External Playlist Support**: Search and download playlists from Deezer, Qobuz, and SquidWTF with M3U generation
|
||||
- **Hi-Res Audio**: SquidWTF supports up to 24-bit/192kHz FLAC
|
||||
- **Full Metadata**: Downloaded files include complete ID3 tags (title, artist, album, track number, year, genre, BPM, ISRC, etc.) and cover art
|
||||
|
||||
@@ -1118,12 +1118,24 @@ public class JellyfinController : ControllerBase
|
||||
}
|
||||
|
||||
// Check if this is an external song/album
|
||||
var (isExternal, _, _) = _localLibraryService.ParseSongId(itemId);
|
||||
var (isExternal, provider, externalId) = _localLibraryService.ParseSongId(itemId);
|
||||
if (isExternal)
|
||||
{
|
||||
// External items don't exist in Jellyfin, so we can't favorite them there
|
||||
// Just return success - the client will show it as favorited
|
||||
_logger.LogDebug("Favoriting external item {ItemId} (not synced to Jellyfin)", itemId);
|
||||
_logger.LogInformation("Favoriting external item {ItemId}, copying to kept folder", itemId);
|
||||
|
||||
// Copy the track to kept folder in background
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await CopyExternalTrackToKeptAsync(itemId, provider!, externalId!);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to copy external track {ItemId} to kept folder", itemId);
|
||||
}
|
||||
});
|
||||
|
||||
return Ok(new { IsFavorite = true });
|
||||
}
|
||||
|
||||
@@ -2083,6 +2095,76 @@ public class JellyfinController : ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies an external track to the kept folder when favorited.
|
||||
/// </summary>
|
||||
private async Task CopyExternalTrackToKeptAsync(string itemId, string provider, string externalId)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get the song metadata
|
||||
var song = await _metadataService.GetSongAsync(provider, externalId);
|
||||
if (song == null)
|
||||
{
|
||||
_logger.LogWarning("Could not find song metadata for {ItemId}", itemId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if file exists in downloads folder
|
||||
var downloadPath = PathHelper.GetSongPath(song.Artist, song.Album, song.Title);
|
||||
if (!System.IO.File.Exists(downloadPath))
|
||||
{
|
||||
_logger.LogInformation("Track not yet downloaded, triggering download for {ItemId}", itemId);
|
||||
|
||||
// Download the track first
|
||||
var downloadResult = await _downloadService.DownloadSongAsync(provider, externalId);
|
||||
if (!downloadResult.IsSuccess)
|
||||
{
|
||||
_logger.LogWarning("Failed to download track {ItemId}: {Error}", itemId, downloadResult.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
downloadPath = downloadResult.Value!.FilePath;
|
||||
}
|
||||
|
||||
// Create kept folder structure: /app/kept/Artist/Album/
|
||||
var keptBasePath = "/app/kept";
|
||||
var keptArtistPath = Path.Combine(keptBasePath, PathHelper.SanitizeFileName(song.Artist));
|
||||
var keptAlbumPath = Path.Combine(keptArtistPath, PathHelper.SanitizeFileName(song.Album));
|
||||
|
||||
Directory.CreateDirectory(keptAlbumPath);
|
||||
|
||||
// Copy file to kept folder
|
||||
var fileName = Path.GetFileName(downloadPath);
|
||||
var keptFilePath = Path.Combine(keptAlbumPath, fileName);
|
||||
|
||||
if (System.IO.File.Exists(keptFilePath))
|
||||
{
|
||||
_logger.LogInformation("Track already exists in kept folder: {Path}", keptFilePath);
|
||||
return;
|
||||
}
|
||||
|
||||
System.IO.File.Copy(downloadPath, keptFilePath, overwrite: false);
|
||||
_logger.LogInformation("✓ Copied favorited track to kept folder: {Path}", keptFilePath);
|
||||
|
||||
// Also copy cover art if it exists
|
||||
var coverPath = Path.Combine(Path.GetDirectoryName(downloadPath)!, "cover.jpg");
|
||||
if (System.IO.File.Exists(coverPath))
|
||||
{
|
||||
var keptCoverPath = Path.Combine(keptAlbumPath, "cover.jpg");
|
||||
if (!System.IO.File.Exists(keptCoverPath))
|
||||
{
|
||||
System.IO.File.Copy(coverPath, keptCoverPath, overwrite: false);
|
||||
_logger.LogDebug("Copied cover art to kept folder");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error copying external track {ItemId} to kept folder", itemId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads missing tracks from file cache as fallback when Redis is empty.
|
||||
/// </summary>
|
||||
|
||||
@@ -93,6 +93,8 @@ services:
|
||||
- Qobuz__Quality=${QOBUZ_QUALITY:-FLAC}
|
||||
volumes:
|
||||
- ${DOWNLOAD_PATH:-./downloads}:/app/downloads
|
||||
- ${KEPT_PATH:-./kept}:/app/kept
|
||||
- ${CACHE_PATH:-./cache}:/app/cache
|
||||
|
||||
networks:
|
||||
allstarr-network:
|
||||
|
||||
Reference in New Issue
Block a user