mirror of https://github.com/velour/catbase.git
137 lines
3.0 KiB
Go
137 lines
3.0 KiB
Go
|
package gifmeme
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"github.com/andybons/gogif"
|
||
|
"github.com/rs/zerolog/log"
|
||
|
"github.com/velour/catbase/config"
|
||
|
"github.com/velour/catbase/plugins/meme"
|
||
|
"image"
|
||
|
"image/gif"
|
||
|
"net/http"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
const tpc = 5
|
||
|
const nColors = 64
|
||
|
|
||
|
func getGif(c *config.Config, s meme.Specification) ([]byte, int, int, error) {
|
||
|
t0 := time.Now()
|
||
|
resp, err := http.Get(s.ImageURL)
|
||
|
if err != nil {
|
||
|
return nil, 0, 0, err
|
||
|
}
|
||
|
g, err := gif.DecodeAll(resp.Body)
|
||
|
if err != nil {
|
||
|
return nil, 0, 0, err
|
||
|
}
|
||
|
log.Debug().Msgf("Len before adding text: %d", len(g.Image))
|
||
|
err = addText(c, g, s.Configs)
|
||
|
log.Debug().Msgf("Len after adding text: %d", len(g.Image))
|
||
|
log.Debug().Msgf("%v elapsed adding text", time.Now().Sub(t0))
|
||
|
if err != nil {
|
||
|
return nil, 0, 0, err
|
||
|
}
|
||
|
out := bytes.NewBuffer([]byte{})
|
||
|
t1 := time.Now()
|
||
|
err = gif.EncodeAll(out, g)
|
||
|
if err != nil {
|
||
|
return nil, 0, 0, err
|
||
|
}
|
||
|
log.Debug().Msgf("%v elapsed encoding gif", time.Now().Sub(t1))
|
||
|
w, h := g.Image[0].Bounds().Max.X, g.Image[0].Bounds().Max.Y
|
||
|
return out.Bytes(), w, h, nil
|
||
|
}
|
||
|
|
||
|
func addText(c *config.Config, g *gif.GIF, text []meme.Text) error {
|
||
|
totalTime := calcTime(g)
|
||
|
framesNeeded, avgFrames := calcTextTime(text)
|
||
|
loops := 1
|
||
|
|
||
|
if totalTime < framesNeeded {
|
||
|
loops = framesNeeded / totalTime
|
||
|
totalTime = loops * totalTime
|
||
|
} else {
|
||
|
framesNeeded = totalTime
|
||
|
}
|
||
|
|
||
|
log.Debug().Msgf("We need %d loops for %d total time with %d frames",
|
||
|
loops, totalTime, framesNeeded)
|
||
|
|
||
|
frames := []*image.Paletted{}
|
||
|
delays := []int{}
|
||
|
|
||
|
idx := 0
|
||
|
|
||
|
toicfg := meme.MkTextOnImageConfig(c)
|
||
|
|
||
|
bounds := g.Image[0].Bounds()
|
||
|
maxSz := max(bounds.Max.X, bounds.Max.Y)
|
||
|
if toicfg.MaxSz > float64(maxSz) {
|
||
|
toicfg.MaxSz = float64(maxSz)
|
||
|
}
|
||
|
|
||
|
currText := 0
|
||
|
currTextFrames := 0
|
||
|
|
||
|
log.Debug().Msgf("Writing text configs %+v on image", text)
|
||
|
|
||
|
log.Debug().Msgf("Starting text with %+v", text[currText])
|
||
|
|
||
|
for i := 0; i < framesNeeded; i++ {
|
||
|
img := g.Image[idx]
|
||
|
i, err := meme.TextOnImage(toicfg, img, []meme.Text{text[currText]})
|
||
|
if err != nil {
|
||
|
log.Error().Err(err).Msg("error encoding gif")
|
||
|
return err
|
||
|
}
|
||
|
frames = append(frames, imgToPaletted(i))
|
||
|
delays = append(delays, g.Delay[idx])
|
||
|
idx = (idx + 1) % len(g.Image)
|
||
|
currTextFrames++
|
||
|
if currTextFrames > avgFrames {
|
||
|
currText = (currText + 1) % len(text)
|
||
|
currTextFrames = 0
|
||
|
log.Debug().Msgf("Switching text to %+v", text[currText])
|
||
|
}
|
||
|
}
|
||
|
|
||
|
g.Image = frames
|
||
|
g.Delay = delays
|
||
|
g.Disposal = nil
|
||
|
g.Config = image.Config{}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func calcTextTime(text []meme.Text) (int, int) {
|
||
|
total := 1
|
||
|
for _, frame := range text {
|
||
|
total += tpc * len(frame.Text)
|
||
|
}
|
||
|
return total, total / len(text)
|
||
|
}
|
||
|
|
||
|
func calcTime(g *gif.GIF) int {
|
||
|
total := 0
|
||
|
for _, frameDelay := range g.Delay {
|
||
|
total += frameDelay
|
||
|
}
|
||
|
return total
|
||
|
}
|
||
|
|
||
|
func imgToPaletted(i image.Image) *image.Paletted {
|
||
|
bounds := i.Bounds()
|
||
|
p := image.NewPaletted(bounds, nil)
|
||
|
quantizer := gogif.MedianCutQuantizer{NumColor: nColors}
|
||
|
quantizer.Quantize(p, bounds, i, image.Point{})
|
||
|
return p
|
||
|
}
|
||
|
|
||
|
func max(x, y int) int {
|
||
|
if x > y {
|
||
|
return x
|
||
|
}
|
||
|
return y
|
||
|
}
|