package api import ( "io" "net/http" "net/url" "strings" "github.com/ponzischeme89/memby/server/internal/store" ) // allowedImageTypes guards the path segment we forward to Emby. var allowedImageTypes = map[string]string{ "backdrop": "Backdrop", "primary": "Primary", "logo": "Logo", "thumb": "Thumb", } // handleImage proxies artwork. // // Going through the gateway means the TV's image URLs carry a gateway token instead of a // live Emby api_key, and it gives Emby's resized output a cacheable home. Images are // tag-addressed, so a hit can be cached hard by any layer in front of this. func (s *Server) handleImage(w http.ResponseWriter, r *http.Request, sess store.Session) { itemID := r.PathValue("itemId") imageType, ok := allowedImageTypes[strings.ToLower(r.PathValue("imageType"))] if !ok || itemID == "" { writeError(w, http.StatusNotFound, "unknown image") return } params := url.Values{} for _, key := range []string{"tag", "maxWidth", "maxHeight", "quality"} { if v := r.URL.Query().Get(key); v != "" { params.Set(key, v) } } resp, err := s.emby.ImageResponse(r.Context(), credentials(sess), itemID, imageType, params) if err != nil { s.writeUpstreamError(w, err, "could not load the image") return } defer resp.Body.Close() if ct := resp.Header.Get("Content-Type"); ct != "" { w.Header().Set("Content-Type", ct) } if cl := resp.Header.Get("Content-Length"); cl != "" { w.Header().Set("Content-Length", cl) } // A tag identifies exact image bytes, so it can be cached indefinitely. Without one, // stay conservative. if params.Get("tag") != "" { w.Header().Set("Cache-Control", "private, max-age=31536000, immutable") } else { w.Header().Set("Cache-Control", "private, max-age=3600") } w.WriteHeader(http.StatusOK) if _, err := io.Copy(w, resp.Body); err != nil { s.log.Warn("image copy failed", "error", err) } }