-
Notifications
You must be signed in to change notification settings - Fork 338
fix(sticker): send WebP stickers as-is instead of re-encoding them #166
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
FlavioPulli
wants to merge
2
commits into
evolution-foundation:develop
Choose a base branch
from
FlavioPulli:fix/animated-sticker-passthrough
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| package send_service | ||
|
|
||
| // Sticker payload handling. | ||
| // | ||
| // `SendSticker` used to run every sticker through `convertToWebP`: fetch the URL, decode with | ||
| // `image.Decode` and re-encode with `webp.Encode(Quality: 80)`. That is wrong whenever the source | ||
| // is already a WebP — which is the case for every sticker that came from WhatsApp itself: | ||
| // | ||
| // 1. Animated stickers cannot be sent at all. The registered decoder (chai2010/webp) only reads | ||
| // static WebP, so an animated file fails with `webpDecodeRGBA: failed` and the whole send dies. | ||
| // 2. The ones that do go through lose quality for nothing — a perfectly valid WebP is decoded and | ||
| // re-compressed at 80%. | ||
| // | ||
| // So: if the downloaded bytes are already a valid WebP, they are uploaded untouched. Animation and | ||
| // quality survive, and the conversion path is left to the inputs that actually need it (PNG, JPEG). | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "encoding/binary" | ||
| "fmt" | ||
| "image" | ||
| "io" | ||
| "net/http" | ||
|
|
||
| "github.com/chai2010/webp" | ||
| ) | ||
|
|
||
| // maxStickerBytes caps the sticker download. WhatsApp rejects stickers far smaller than this; the | ||
| // limit exists so a hostile URL cannot exhaust the process memory — relevant now that the payload | ||
| // is uploaded rather than decoded, so nothing downstream constrains its size either. | ||
| const maxStickerBytes = 10 << 20 // 10 MiB | ||
|
|
||
| // stickerWebP fetches the sticker URL and returns WebP bytes ready to upload. | ||
| // | ||
| // A source that is already a valid WebP is returned untouched; anything else is decoded and | ||
| // encoded to WebP as before. | ||
| func stickerWebP(url string) ([]byte, error) { | ||
| resp, err := http.Get(url) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to fetch image from URL: %v", err) | ||
| } | ||
| defer resp.Body.Close() | ||
|
|
||
| raw, err := io.ReadAll(io.LimitReader(resp.Body, maxStickerBytes+1)) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to read image from URL: %v", err) | ||
| } | ||
| if len(raw) > maxStickerBytes { | ||
| return nil, fmt.Errorf("sticker exceeds %d bytes", maxStickerBytes) | ||
| } | ||
|
|
||
| if isWebP(raw) { | ||
| return raw, nil | ||
| } | ||
|
|
||
| img, _, err := image.Decode(bytes.NewReader(raw)) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to decode image: %v", err) | ||
| } | ||
| var buf bytes.Buffer | ||
| if err := webp.Encode(&buf, img, &webp.Options{Lossless: false, Quality: 80}); err != nil { | ||
| return nil, fmt.Errorf("failed to encode image to WebP: %v", err) | ||
| } | ||
| return buf.Bytes(), nil | ||
| } | ||
|
|
||
| // isWebP reports whether b is a well-formed WebP container. | ||
| // | ||
| // It checks the RIFF magic AND that the declared payload size fits in the buffer. That second | ||
| // check matters specifically because of the passthrough above: the old conversion path rejected a | ||
| // truncated download for free (a truncated file fails to decode), whereas a partial body still | ||
| // carries valid RIFF/WEBP magic and would be uploaded as-is, reaching the recipient broken. | ||
| func isWebP(b []byte) bool { | ||
| if len(b) < 12 || string(b[0:4]) != "RIFF" || string(b[8:12]) != "WEBP" { | ||
| return false | ||
| } | ||
| // The RIFF size field counts everything after it, i.e. len(file) - 8. Trailing padding is | ||
| // tolerated (some encoders add it); a payload larger than what we hold means truncation. | ||
| return int(binary.LittleEndian.Uint32(b[4:8]))+8 <= len(b) | ||
| } | ||
|
|
||
| // webpIsAnimated reports whether a WebP container declares animation. | ||
| // | ||
| // Only the extended format (VP8X) can be animated, and bit 0x02 of its flags byte is the | ||
| // declaration — the same ANIMATION_FLAG libwebp uses. Plain VP8/VP8L are static by definition. | ||
| func webpIsAnimated(b []byte) bool { | ||
| if !isWebP(b) || len(b) < 21 || string(b[12:16]) != "VP8X" { | ||
| return false | ||
| } | ||
| return b[20]&0x02 != 0 | ||
| } | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.