bubble up the mime type
This commit is contained in:
@@ -90,7 +90,7 @@ func (a anthropic) requestToAnthropicRequest(req Request) anth.MessagesRequest {
|
|||||||
// Check if image size exceeds 5MiB (5242880 bytes)
|
// Check if image size exceeds 5MiB (5242880 bytes)
|
||||||
if len(raw) >= 5242880 {
|
if len(raw) >= 5242880 {
|
||||||
|
|
||||||
compressed, err := utils.CompressImage(img.Base64, 5*1024*1024)
|
compressed, mime, err := utils.CompressImage(img.Base64, 5*1024*1024)
|
||||||
|
|
||||||
// just replace the image with the compressed one
|
// just replace the image with the compressed one
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -98,6 +98,7 @@ func (a anthropic) requestToAnthropicRequest(req Request) anth.MessagesRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
img.Base64 = compressed
|
img.Base64 = compressed
|
||||||
|
img.ContentType = mime
|
||||||
}
|
}
|
||||||
|
|
||||||
m.Content = append(m.Content, anth.NewImageMessageContent(
|
m.Content = append(m.Content, anth.NewImageMessageContent(
|
||||||
|
@@ -7,7 +7,6 @@ import (
|
|||||||
"image"
|
"image"
|
||||||
"image/gif"
|
"image/gif"
|
||||||
"image/jpeg"
|
"image/jpeg"
|
||||||
"image/png"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"golang.org/x/image/draw"
|
"golang.org/x/image/draw"
|
||||||
@@ -15,18 +14,21 @@ import (
|
|||||||
|
|
||||||
// CompressImage takes a base‑64‑encoded image (JPEG, PNG or GIF) and returns
|
// CompressImage takes a base‑64‑encoded image (JPEG, PNG or GIF) and returns
|
||||||
// a base‑64‑encoded version that is at most maxLength in size, or an error.
|
// a base‑64‑encoded version that is at most maxLength in size, or an error.
|
||||||
func CompressImage(b64 string, maxLength int) (string, error) {
|
func CompressImage(b64 string, maxLength int) (string, string, error) {
|
||||||
raw, err := base64.StdEncoding.DecodeString(b64)
|
raw, err := base64.StdEncoding.DecodeString(b64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("base64 decode: %w", err)
|
return "", "", fmt.Errorf("base64 decode: %w", err)
|
||||||
}
|
|
||||||
if len(raw) <= maxLength {
|
|
||||||
return b64, nil // small enough already
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch mime := http.DetectContentType(raw); mime {
|
mime := http.DetectContentType(raw)
|
||||||
|
if len(raw) <= maxLength {
|
||||||
|
return b64, mime, nil // small enough already
|
||||||
|
}
|
||||||
|
|
||||||
|
switch mime {
|
||||||
case "image/gif":
|
case "image/gif":
|
||||||
return compressGIF(raw, maxLength)
|
return compressGIF(raw, maxLength)
|
||||||
|
|
||||||
default: // jpeg, png, webp, etc. → treat as raster
|
default: // jpeg, png, webp, etc. → treat as raster
|
||||||
return compressRaster(raw, maxLength)
|
return compressRaster(raw, maxLength)
|
||||||
}
|
}
|
||||||
@@ -34,20 +36,20 @@ func CompressImage(b64 string, maxLength int) (string, error) {
|
|||||||
|
|
||||||
// ---------- Raster path (jpeg / png / single‑frame gif) ----------
|
// ---------- Raster path (jpeg / png / single‑frame gif) ----------
|
||||||
|
|
||||||
func compressRaster(src []byte, maxLength int) (string, error) {
|
func compressRaster(src []byte, maxLength int) (string, string, error) {
|
||||||
img, _, err := image.Decode(bytes.NewReader(src))
|
img, _, err := image.Decode(bytes.NewReader(src))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("decode raster: %w", err)
|
return "", "", fmt.Errorf("decode raster: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
quality := 95
|
quality := 95
|
||||||
for {
|
for {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
if err := jpeg.Encode(&buf, img, &jpeg.Options{Quality: quality}); err != nil {
|
if err := jpeg.Encode(&buf, img, &jpeg.Options{Quality: quality}); err != nil {
|
||||||
return "", fmt.Errorf("jpeg encode: %w", err)
|
return "", "", fmt.Errorf("jpeg encode: %w", err)
|
||||||
}
|
}
|
||||||
if buf.Len() <= maxLength {
|
if buf.Len() <= maxLength {
|
||||||
return base64.StdEncoding.EncodeToString(buf.Bytes()), nil
|
return base64.StdEncoding.EncodeToString(buf.Bytes()), "image/jpeg", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if quality > 20 {
|
if quality > 20 {
|
||||||
@@ -58,7 +60,7 @@ func compressRaster(src []byte, maxLength int) (string, error) {
|
|||||||
// down‑scale 80%
|
// down‑scale 80%
|
||||||
b := img.Bounds()
|
b := img.Bounds()
|
||||||
if b.Dx() < 100 || b.Dy() < 100 {
|
if b.Dx() < 100 || b.Dy() < 100 {
|
||||||
return "", fmt.Errorf("cannot compress below %.02fMiB without destroying image", float64(maxLength)/1048576.0)
|
return "", "", fmt.Errorf("cannot compress below %.02fMiB without destroying image", float64(maxLength)/1048576.0)
|
||||||
}
|
}
|
||||||
dst := image.NewRGBA(image.Rect(0, 0, int(float64(b.Dx())*0.8), int(float64(b.Dy())*0.8)))
|
dst := image.NewRGBA(image.Rect(0, 0, int(float64(b.Dx())*0.8), int(float64(b.Dy())*0.8)))
|
||||||
draw.ApproxBiLinear.Scale(dst, dst.Bounds(), img, b, draw.Over, nil)
|
draw.ApproxBiLinear.Scale(dst, dst.Bounds(), img, b, draw.Over, nil)
|
||||||
@@ -69,25 +71,25 @@ func compressRaster(src []byte, maxLength int) (string, error) {
|
|||||||
|
|
||||||
// ---------- Animated GIF path ----------
|
// ---------- Animated GIF path ----------
|
||||||
|
|
||||||
func compressGIF(src []byte, maxLength int) (string, error) {
|
func compressGIF(src []byte, maxLength int) (string, string, error) {
|
||||||
g, err := gif.DecodeAll(bytes.NewReader(src))
|
g, err := gif.DecodeAll(bytes.NewReader(src))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("gif decode: %w", err)
|
return "", "", fmt.Errorf("gif decode: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
if err := gif.EncodeAll(&buf, g); err != nil {
|
if err := gif.EncodeAll(&buf, g); err != nil {
|
||||||
return "", fmt.Errorf("gif encode: %w", err)
|
return "", "", fmt.Errorf("gif encode: %w", err)
|
||||||
}
|
}
|
||||||
if buf.Len() <= maxLength {
|
if buf.Len() <= maxLength {
|
||||||
return base64.StdEncoding.EncodeToString(buf.Bytes()), nil
|
return base64.StdEncoding.EncodeToString(buf.Bytes()), "image/gif", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// down‑scale every frame by 80%
|
// down‑scale every frame by 80%
|
||||||
w, h := g.Config.Width, g.Config.Height
|
w, h := g.Config.Width, g.Config.Height
|
||||||
if w < 100 || h < 100 {
|
if w < 100 || h < 100 {
|
||||||
return "", fmt.Errorf("cannot compress animated GIF below 5 MiB without excessive quality loss")
|
return "", "", fmt.Errorf("cannot compress animated GIF below 5 MiB without excessive quality loss")
|
||||||
}
|
}
|
||||||
|
|
||||||
nw, nh := int(float64(w)*0.8), int(float64(h)*0.8)
|
nw, nh := int(float64(w)*0.8), int(float64(h)*0.8)
|
||||||
@@ -110,20 +112,3 @@ func compressGIF(src []byte, maxLength int) (string, error) {
|
|||||||
// loop back and test size again …
|
// loop back and test size again …
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- Helpers for precise MIME decodes (optional) ----------
|
|
||||||
|
|
||||||
func decode(r *bytes.Reader, mime string) (image.Image, error) {
|
|
||||||
switch mime {
|
|
||||||
case "image/jpeg":
|
|
||||||
return jpeg.Decode(r)
|
|
||||||
case "image/png":
|
|
||||||
return png.Decode(r)
|
|
||||||
case "image/gif":
|
|
||||||
// for single‑frame GIFs only – call DecodeAll in caller if animated
|
|
||||||
return gif.Decode(r)
|
|
||||||
default:
|
|
||||||
i, _, err := image.Decode(r)
|
|
||||||
return i, err // let the stdlib guess the format
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Reference in New Issue
Block a user