corpus

Log | Files | Refs | README | LICENSE

commit 8b8811d7e2c0cf6fe4e57880ef8d0b82470a7563
parent 82bbb061aa0d3d4e001b1727268660f91134d29d
Author: mtmn <miro@haravara.org>
Date:   Wed, 22 Apr 2026 23:56:06 +0200

feat: try various cover art sizes

Diffstat:
Msrc/Cover.purs | 53+++++++++++++++++++++++++++++++----------------------
Msrc/State.elm | 18+++++++++++++++++-
Msrc/Types.elm | 1+
Msrc/View.elm | 19++++++++++++++-----
4 files changed, 63 insertions(+), 28 deletions(-)

diff --git a/src/Cover.purs b/src/Cover.purs @@ -31,11 +31,11 @@ import Image (convertToAvif) import JSURI (encodeURIComponent) import Log as Log import Metrics as Metrics +import Node.Buffer (fromArrayBuffer) import Node.HTTP.OutgoingMessage (setHeader, toWriteable) import Node.HTTP.ServerResponse (setStatusCode, toOutgoingMessage) import Node.HTTP.Types (ServerResponse) import Node.Stream (end) -import Unsafe.Coerce (unsafeCoerce) import Web.URL (URL) import Web.URL as URL import Web.URL.URLSearchParams as URLSearchParams @@ -95,7 +95,7 @@ fetchDiscogsCoverUrl cfg artist release = case cfg.discogsToken of <> (fromMaybe "" $ encodeURIComponent queryStr) <> "&type=release&per_page=1" Log.info $ "Searching Discogs for: " <> queryStr - result <- try $ fetch searchUrl { method: GET, headers: { "User-Agent": "corpus/1.0 +https://github.com/mtmn/corpus", "Authorization": "Discogs token=" <> t } } + result <- try $ fetch searchUrl { method: GET, headers: { "User-Agent": "corpus/1.0 (+https://github.com/mtmn/corpus)", "Authorization": "Discogs token=" <> t } } case result of Right fr | fr.status == 200 -> do json <- fromJson fr.json @@ -107,19 +107,23 @@ fetchDiscogsCoverUrl cfg artist release = case cfg.discogsToken of _ -> pure Nothing -coverSources :: String -> String -> String -> UserConfig -> Array CoverSource -coverSources mbid artist release cfg = +coverSources :: String -> String -> String -> Maybe String -> UserConfig -> Array CoverSource +coverSources mbid artist release mVariant cfg = let safeArtist = sanitizeKey artist safeRelease = sanitizeKey release + variant = fromMaybe "front-500" mVariant + caaSource = + if mbid == "" then [] + else + [ { name: "caa" + , s3Key: "covers/caa/" <> sanitizeKey mbid <> (if variant == "front-500" then "" else "-" <> variant) <> ".avif" + , findUrl: pure $ Just $ "https://coverartarchive.org/release/" <> mbid <> "/" <> variant + } + ] in - [ { name: "caa" - , s3Key: "covers/caa/" <> sanitizeKey mbid <> ".avif" - , findUrl: - if mbid == "" then pure Nothing - else pure $ Just $ "https://coverartarchive.org/release/" <> mbid <> "/front-500" - } - , { name: "lastfm" + caaSource <> + [ { name: "lastfm" , s3Key: "covers/lastfm/" <> safeArtist <> "-" <> safeRelease <> ".avif" , findUrl: if artist == "" || release == "" then pure Nothing @@ -139,9 +143,10 @@ serveCover serveNotFound cfg slug url res = do mbid = fromMaybe "" (getQueryParam "mbid" url) artist = fromMaybe "" (getQueryParam "artist" url) release = fromMaybe "" (getQueryParam "release" url) + variant = getQueryParam "variant" url s3cfg = s3ConfigFromUser cfg - served <- foldM (trySource s3cfg) false (coverSources mbid artist release cfg) + served <- foldM (trySource s3cfg) false (coverSources mbid artist release variant cfg) unless served $ liftEffect $ serveNotFound res where @@ -185,20 +190,24 @@ serveCover serveNotFound cfg slug url res = do setHeader "Cache-Control" "public, max-age=3600" (toOutgoingMessage response) end (toWriteable (toOutgoingMessage response)) when cfg.coverCacheEnabled $ void $ forkAff do - fetchResult <- try $ fetch urlStr { method: GET } + let headers = { "User-Agent": "corpus/1.0 (+https://github.com/mtmn/corpus)" } + fetchResult <- try $ fetch urlStr { method: GET, headers } case fetchResult of Right fr | fr.status == 200 -> do let - isAvif = case Map.lookup (CaseInsensitiveString "content-type") fr.headers of + contentType = Map.lookup (CaseInsensitiveString "content-type") fr.headers + isAvif = case contentType of Just ct | ct == "image/avif" -> true _ -> false - Log.info $ "caching image (" <> (if isAvif then "already avif" else "needs conversion") <> "): " <> urlStr - buf <- fr.arrayBuffer - avifBuf <- if isAvif then pure buf else convertToAvif buf - uploadResult <- try $ uploadToS3 s3cfg s3Key (unsafeCoerce avifBuf) "image/avif" + Log.info $ "Caching image: " <> urlStr + ab <- fr.arrayBuffer + avifAb <- if isAvif then pure ab else convertToAvif ab + avifBuf <- liftEffect $ fromArrayBuffer avifAb + uploadResult <- try $ uploadToS3 s3cfg s3Key avifBuf "image/avif" case uploadResult of Right _ -> Log.info $ "Cached to S3: " <> s3Key - Left err -> Log.error $ "S3 upload failed: " <> Exception.message err - _ -> - Log.warn $ "Background fetch failed for: " <> urlStr - + Left err -> Log.error $ "S3 upload FAILED for " <> s3Key <> ": " <> Exception.message err + Right fr -> + Log.warn $ "Background fetch failed for " <> urlStr <> " with status " <> show fr.status + Left err -> + Log.error $ "Background fetch error for " <> urlStr <> ": " <> Exception.message err diff --git a/src/State.elm b/src/State.elm @@ -29,6 +29,7 @@ init flags url navKey = , loading = True , currentTime = Nothing , failedCovers = Set.empty + , coverFallbacks = Dict.empty , hoveredCover = Nothing , expandedSections = Set.empty , loadedSections = Set.empty @@ -211,7 +212,22 @@ update msg model = ) ImageError url -> - ( { model | failedCovers = Set.insert url model.failedCovers }, Cmd.none ) + let + baseUrl = + if String.contains "&variant=" url then + String.split "&variant=" url |> List.head |> Maybe.withDefault url + + else + url + + currentFallback = + Dict.get baseUrl model.coverFallbacks |> Maybe.withDefault 0 + in + if String.contains "&mbid=" baseUrl && currentFallback < 3 then + ( { model | coverFallbacks = Dict.insert baseUrl (currentFallback + 1) model.coverFallbacks }, Cmd.none ) + + else + ( { model | failedCovers = Set.insert baseUrl model.failedCovers }, Cmd.none ) NextPage -> let diff --git a/src/Types.elm b/src/Types.elm @@ -96,6 +96,7 @@ type alias Model = , loading : Bool , currentTime : Maybe Time.Posix , failedCovers : Set String + , coverFallbacks : Dict String Int , hoveredCover : Maybe Int , expandedSections : Set String , loadedSections : Set String diff --git a/src/View.elm b/src/View.elm @@ -161,7 +161,7 @@ renderContent model = in div [ class "track-with-similar-container" ] [ ul [ class "track-item" ] - [ renderListen model.userSlug model.currentTime model.failedCovers model.hoveredCover model.similarStates idx listen ] + [ renderListen model.userSlug model.currentTime model.failedCovers model.coverFallbacks model.hoveredCover model.similarStates idx listen ] , renderSimilarPanel model.similarStates key ] ) @@ -169,8 +169,8 @@ renderContent model = ) -renderListen : String -> Maybe Time.Posix -> Set String -> Maybe Int -> Dict String SimilarState -> Int -> Listen -> Html Msg -renderListen userSlug currentTime failedCovers hoveredCover similarStates idx listen = +renderListen : String -> Maybe Time.Posix -> Set String -> Dict String Int -> Maybe Int -> Dict String SimilarState -> Int -> Listen -> Html Msg +renderListen userSlug currentTime failedCovers coverFallbacks hoveredCover similarStates idx listen = let artist = Maybe.withDefault "" listen.artistName @@ -192,7 +192,7 @@ renderListen userSlug currentTime failedCovers hoveredCover similarStates idx li Nothing -> listen.releaseMbid - coverUrl = + baseCoverUrl = "/cover?user=" ++ userSlug ++ "&artist=" @@ -207,6 +207,15 @@ renderListen userSlug currentTime failedCovers hoveredCover similarStates idx li "" ) + variant = + case Dict.get baseCoverUrl coverFallbacks |> Maybe.withDefault 0 of + 1 -> "&variant=front-250" + 2 -> "&variant=back-500" + 3 -> "&variant=back-250" + _ -> "" + + coverUrl = baseCoverUrl ++ variant + isZoomed = hoveredCover == Just idx @@ -235,7 +244,7 @@ renderListen userSlug currentTime failedCovers hoveredCover similarStates idx li ] ] , div [ class "cover-wrapper" ] - [ if Set.member coverUrl failedCovers then + [ if Set.member baseCoverUrl failedCovers then text "" else