giffer

Create .gif images from sites like youtube.com
Log | Files | Refs | LICENSE

commit b8ea9f21ea593f4db1ed67a645443e5a00841e16
parent 0b643149224c757626a22de2d4972749e1f6b1ca
Author: Jack Mordaunt <jackmordaunt@gmail.com>
Date:   Sun,  2 May 2021 13:40:23 +0800

fix: playback at the correct FPS

Diffstat:
Mcmd/gio/main.go | 56+++++++++++++++++++++++++++++++++++---------------------
1 file changed, 35 insertions(+), 21 deletions(-)

diff --git a/cmd/gio/main.go b/cmd/gio/main.go @@ -60,8 +60,16 @@ type UI struct { Form Form GifPlayer GifPlayer Processing bool - cache *gif.GIF - done chan *gif.GIF + cache *PreparedGif + done chan *PreparedGif +} + +// PreparedGif helper wraps a Gif with FPS metadata. +// This is needed because Gif itself cannot be relied upon to contain proper FPS data in it's delay +// slice. +type PreparedGif struct { + *gif.GIF + FPS float64 } // Loop runs the event loop until terminated. @@ -91,7 +99,7 @@ func (ui *UI) Init() { ui.Form.Height.SetText("350") ui.Form.Start.SetText("0") ui.Form.End.SetText("3") - ui.done = make(chan *gif.GIF) + ui.done = make(chan *PreparedGif) } func (ui *UI) Update(gtx C) { @@ -105,12 +113,13 @@ func (ui *UI) Update(gtx C) { log.Printf("error: opening save file: %v", err) } defer func() { go savef.Close() }() - if err := gif.EncodeAll(savef, ui.cache); err != nil { + if err := gif.EncodeAll(savef, ui.cache.GIF); err != nil { log.Printf("error: saving gif to %q: %v", path, err) } ui.cache = nil } if ui.Form.SubmitBtn.Clicked() { + ui.GifPlayer.Clear() ui.Processing = true var ( url = ui.Form.URL.Text() @@ -154,14 +163,18 @@ func (ui *UI) Update(gtx C) { if err != nil { log.Printf("error: decoding gif: %v", err) } - ui.done <- img + ui.GifPlayer.FPS = fps + ui.done <- &PreparedGif{ + GIF: img, + FPS: fps, + } }() } select { - case g := <-ui.done: - ui.cache = g + case img := <-ui.done: + ui.cache = img ui.Processing = false - ui.GifPlayer.Load(g) + ui.GifPlayer.Load(img) default: } } @@ -313,18 +326,19 @@ func (f *Form) LayoutActions(gtx C, th *m.Theme) D { // GifPlayer animates through a series of frames. // -// TODO(jfm): fix! -// - playing way too fast +// TODO(jfm): Fix artifacting from the ImageStack implementation. +// TODO(jfm): Properly crop gif. type GifPlayer struct { Frames []paint.ImageOp - Delays []time.Duration Cursor int + FPS float64 since time.Time img widget.Image } // Load a Gif image to render. -func (g *GifPlayer) Load(src *gif.GIF) { +func (g *GifPlayer) Load(src *PreparedGif) { + g.FPS = float64(src.FPS) g.Frames = make([]paint.ImageOp, len(src.Image)) for ii := range src.Image { s := ImageStack{Config: src.Config} @@ -333,22 +347,22 @@ func (g *GifPlayer) Load(src *gif.GIF) { } g.Frames[ii] = paint.NewImageOp(s) } - g.Delays = make([]time.Duration, len(src.Delay)) - for ii := range g.Delays { - // Gif delays are in 100th of a second. - g.Delays[ii] = time.Duration(src.Delay[ii]) / 10 * time.Millisecond - } +} + +// Clear the player state. +// Stops playing any current gif. +func (g *GifPlayer) Clear() { + g.Frames = g.Frames[:] + g.Cursor = 0 + g.FPS = 0 } // Ready if the next frame is ready to be displayed. func (g *GifPlayer) Ready(gtx C) bool { - if len(g.Delays) == 0 { - return true - } var ( now = gtx.Now since = g.since - latency = g.Delays[g.Cursor] + latency = time.Second / time.Duration(g.FPS) ) return now.Sub(since).Milliseconds() >= latency.Milliseconds() }