diff --git a/backend/Musify.Api/AppSettings.json b/backend/Musify.Api/AppSettings.json index 93cef14..623f06f 100644 --- a/backend/Musify.Api/AppSettings.json +++ b/backend/Musify.Api/AppSettings.json @@ -54,7 +54,8 @@ "SearchContinuationCacheSeconds": 600, "SearchResultsCacheSeconds": 900, "SongInfoCacheSeconds": 86400, - "ThumbnailSize": 240 + "ThumbnailSize": 240, + "ArtworkSize": 544 }, "PlayList": { "Routes": { diff --git a/backend/Musify.Application/Contracts/IYouTubeMusicService.cs b/backend/Musify.Application/Contracts/IYouTubeMusicService.cs index 97e3dc8..4149a3a 100644 --- a/backend/Musify.Application/Contracts/IYouTubeMusicService.cs +++ b/backend/Musify.Application/Contracts/IYouTubeMusicService.cs @@ -27,5 +27,7 @@ namespace Musify.Application.Contracts Task> GetAudioStreamAsync(string videoId, CancellationToken cancellationToken); Task> GetSongAsync(string videoId, CancellationToken cancellationToken); + + string ResolveArtworkUrl(string thumbnailUrl); } } diff --git a/backend/Musify.Application/PlayLists/AddYouTubeTrackToPlayList.cs b/backend/Musify.Application/PlayLists/AddYouTubeTrackToPlayList.cs index fe4bb4f..09b049d 100644 --- a/backend/Musify.Application/PlayLists/AddYouTubeTrackToPlayList.cs +++ b/backend/Musify.Application/PlayLists/AddYouTubeTrackToPlayList.cs @@ -111,13 +111,13 @@ namespace Musify.Application.PlayLists private async Task> ResolveThumbnailUrlAsync(string videoId, YouTubeSongResult? song, CancellationToken cancellationToken) { if (!string.IsNullOrEmpty(song?.ThumbnailUrl)) - return song.ThumbnailUrl; + return youTubeMusicService.ResolveArtworkUrl(song.ThumbnailUrl); var songResult = await youTubeMusicService.GetSongAsync(videoId, cancellationToken); if (songResult.IsError) return songResult.Errors; - return songResult.Value.ThumbnailUrl; + return youTubeMusicService.ResolveArtworkUrl(songResult.Value.ThumbnailUrl); } private async Task PublishDownloadEventAsync(Guid trackId, string videoId, string thumbnailUrl, CancellationToken cancellationToken) diff --git a/backend/Musify.Infrastructure/Configuration/YouTubeConfiguration.cs b/backend/Musify.Infrastructure/Configuration/YouTubeConfiguration.cs index f2e81e3..8306484 100644 --- a/backend/Musify.Infrastructure/Configuration/YouTubeConfiguration.cs +++ b/backend/Musify.Infrastructure/Configuration/YouTubeConfiguration.cs @@ -23,5 +23,8 @@ namespace Musify.Infrastructure.Configuration [Range(32, 720)] public int ThumbnailSize { get; set; } = 240; + + [Range(120, 1080)] + public int ArtworkSize { get; set; } = 544; } } diff --git a/backend/Musify.Infrastructure/Services/YouTubeMusicService.cs b/backend/Musify.Infrastructure/Services/YouTubeMusicService.cs index d4b1628..3706be7 100644 --- a/backend/Musify.Infrastructure/Services/YouTubeMusicService.cs +++ b/backend/Musify.Infrastructure/Services/YouTubeMusicService.cs @@ -160,6 +160,9 @@ namespace Musify.Infrastructure.Services } } + public string ResolveArtworkUrl(string thumbnailUrl) => + YouTubeThumbnail.WithSize(thumbnailUrl, configuration.ArtworkSize); + private string PickThumbnail(IEnumerable thumbnails) { var ordered = thumbnails.OrderBy(thumbnail => thumbnail.Width).ToList(); diff --git a/web-player/.env.example b/web-player/.env.example index 7430bd4..c538582 100644 --- a/web-player/.env.example +++ b/web-player/.env.example @@ -25,6 +25,17 @@ API_BASE_URL=http://localhost:5111 # Dev already allows a plain-HTTP issuer. Set this only to allow it in a production build (never with a public issuer). # AUTH_ALLOW_INSECURE_HTTP=true +# Optional tuning for the YouTube artwork proxy (/api/youtube/thumbnail), +# which caches the covers in memory so the browser never hits Google directly. +# THUMBNAIL_CACHE_MAX_AGE_SECONDS=2592000 +# THUMBNAIL_FAILURE_CACHE_SECONDS=300 +# THUMBNAIL_CACHE_BYTES=134217728 +# THUMBNAIL_MAX_CONCURRENT_FETCHES=4 +# THUMBNAIL_FETCH_TIMEOUT_MS=8000 +# THUMBNAIL_COOLDOWN_SECONDS=30 +# THUMBNAIL_MAX_COOLDOWN_SECONDS=300 +# THUMBNAIL_MAX_SIZE=720 + # Where to send presigned S3 PUTs when the public S3 host is not reachable from # the server (in Docker: http://seaweedfs:8333). The signed Host header is kept # either way. Defaults to the host in the presigned URL.