always convert original avatar to png to avoid recompression

peertube
Namekuji 2023-01-27 12:45:52 -05:00
rodzic 6adbf5de60
commit e28f2edb2e
2 zmienionych plików z 45 dodań i 34 usunięć

Wyświetl plik

@ -22,42 +22,18 @@ import (
"gopkg.in/gographics/imagick.v2/imagick" "gopkg.in/gographics/imagick.v2/imagick"
) )
func (u *AudonUser) GetIndicator(ctx context.Context, fnew []byte, room *Room) ([]byte, error) { func (u *AudonUser) GetIndicator(ctx context.Context, fnew []byte, room *Room) ([]byte, []byte, error) {
if u == nil { if u == nil {
return nil, errors.New("nil user") return nil, nil, errors.New("nil user")
} }
mtype := mimetype.Detect(fnew) mtype := mimetype.Detect(fnew)
if !mimetype.EqualsAny(mtype.String(), "image/png", "image/jpeg", "image/webp", "image/gif") { if !mimetype.EqualsAny(mtype.String(), "image/png", "image/jpeg", "image/webp", "image/gif") {
return nil, errors.New("file type not supported") return nil, nil, errors.New("file type not supported")
} }
hash := sha256.Sum256(fnew)
var err error var err error
// Check if user's original avatar exists
saved := u.GetOriginalAvatarPath(hash, mtype)
if _, err := os.Stat(saved); err != nil {
if err := os.MkdirAll(filepath.Dir(saved), 0775); err != nil {
return nil, err
}
// Write user's avatar if the original version doesn't exist
if err := os.WriteFile(saved, fnew, 0664); err != nil {
return nil, err
}
}
fname := filepath.Base(saved)
coll := mainDB.Collection(COLLECTION_USER)
if _, err = coll.UpdateOne(ctx,
bson.D{{Key: "audon_id", Value: u.AudonID}},
bson.D{
{Key: "$set", Value: bson.D{{Key: "avatar", Value: fname}}},
}); err != nil {
return nil, err
}
buf := bytes.NewReader(fnew) buf := bytes.NewReader(fnew)
var newImg image.Image var newImg image.Image
@ -71,9 +47,45 @@ func (u *AudonUser) GetIndicator(ctx context.Context, fnew []byte, room *Room) (
newImg, err = gif.Decode(buf) newImg, err = gif.Decode(buf)
} }
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
return u.createGIF(newImg, room.IsHost(u) || room.IsCoHost(u))
// encode to png to avoid recompression
origBuf := new(bytes.Buffer)
if err := png.Encode(origBuf, newImg); err != nil {
return nil, nil, err
}
origPng := origBuf.Bytes()
hash := sha256.Sum256(origPng)
// Check if user's original avatar exists
filename := fmt.Sprintf("%x.png", hash)
saved := u.getAvatarImagePath(filename)
if _, err := os.Stat(saved); err != nil {
if err := os.MkdirAll(filepath.Dir(saved), 0775); err != nil {
return nil, nil, err
}
// Write user's avatar if the original version doesn't exist
if err := os.WriteFile(saved, origPng, 0664); err != nil {
return nil, nil, err
}
}
coll := mainDB.Collection(COLLECTION_USER)
if _, err = coll.UpdateOne(ctx,
bson.D{{Key: "audon_id", Value: u.AudonID}},
bson.D{
{Key: "$set", Value: bson.D{{Key: "avatar", Value: filename}}},
}); err != nil {
return nil, nil, err
}
indicator, err := u.createGIF(newImg, room.IsHost(u) || room.IsCoHost(u))
if err != nil {
return nil, nil, err
}
return indicator, origPng, nil
} }
func (u *AudonUser) createGIF(avatar image.Image, blue bool) ([]byte, error) { func (u *AudonUser) createGIF(avatar image.Image, blue bool) ([]byte, error) {
@ -129,7 +141,7 @@ func (u *AudonUser) createGIF(avatar image.Image, blue bool) ([]byte, error) {
return os.ReadFile(u.GetGIFAvatarPath()) return os.ReadFile(u.GetGIFAvatarPath())
} }
func (u *AudonUser) GetOriginalAvatarPath(hash [sha256.Size]byte, mtype *mimetype.MIME) string { func (u *AudonUser) getOriginalAvatarPath(hash [sha256.Size]byte, mtype *mimetype.MIME) string {
filename := fmt.Sprintf("%x%s", hash, mtype.Extension()) filename := fmt.Sprintf("%x%s", hash, mtype.Extension())
return u.getAvatarImagePath(filename) return u.getAvatarImagePath(filename)
} }

Wyświetl plik

@ -12,7 +12,6 @@ import (
"os" "os"
"time" "time"
"github.com/gabriel-vasile/mimetype"
"github.com/jaevor/go-nanoid" "github.com/jaevor/go-nanoid"
"github.com/jellydator/ttlcache/v3" "github.com/jellydator/ttlcache/v3"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
@ -380,7 +379,7 @@ func joinRoomHandler(c echo.Context) (err error) {
if user.AvatarFile != "" { if user.AvatarFile != "" {
orig, err := os.ReadFile(user.getAvatarImagePath(user.AvatarFile)) orig, err := os.ReadFile(user.getAvatarImagePath(user.AvatarFile))
if err == nil && orig != nil { if err == nil && orig != nil {
resp.Original = fmt.Sprintf("data:%s;base64,%s", mimetype.Detect(orig), base64.StdEncoding.EncodeToString(orig)) resp.Original = fmt.Sprintf("data:image/png;base64,%s", base64.StdEncoding.EncodeToString(orig))
} else if orig == nil { } else if orig == nil {
user.AvatarFile = "" user.AvatarFile = ""
} }
@ -424,12 +423,12 @@ func joinRoomHandler(c echo.Context) (err error) {
} }
// Generate indicator GIF // Generate indicator GIF
indicator, err := user.GetIndicator(c.Request().Context(), fnew, room) indicator, original, err := user.GetIndicator(c.Request().Context(), fnew, room)
if err != nil { if err != nil {
c.Logger().Error(err) c.Logger().Error(err)
return echo.NewHTTPError(http.StatusInternalServerError) return echo.NewHTTPError(http.StatusInternalServerError)
} }
resp.Original = fmt.Sprintf("data:%s;base64,%s", mimetype.Detect(fnew), base64.StdEncoding.EncodeToString(fnew)) resp.Original = fmt.Sprintf("data:image/png;base64,%s", base64.StdEncoding.EncodeToString(original))
resp.Indicator = fmt.Sprintf("data:image/gif;base64,%s", base64.StdEncoding.EncodeToString(indicator)) resp.Indicator = fmt.Sprintf("data:image/gif;base64,%s", base64.StdEncoding.EncodeToString(indicator))
} else if err != nil { } else if err != nil {
c.Logger().Error(err) c.Logger().Error(err)