giffer.go (2599B)
1 package main 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "path/filepath" 7 "strings" 8 9 "github.com/OneOfOne/xxhash" 10 "github.com/jackmordaunt/giffer" 11 "github.com/pkg/errors" 12 ) 13 14 // Giffer wraps the giffer business logic. 15 type Giffer struct { 16 giffer.Downloader 17 giffer.Engine 18 Store GifStore 19 } 20 21 // GifStore contains Gif files. 22 type GifStore interface { 23 Lookup(key string) (*RenderedGif, bool, error) 24 Insert(key string, img *RenderedGif) error 25 // Keys() []string 26 // Last() string 27 } 28 29 // GififyURL downloads the video at url and creates a .gif based on the specified parameters. 30 func (g *Giffer) GififyURL( 31 url string, 32 start, end, fps float64, 33 width, height, fuzz int, 34 ) (*RenderedGif, error) { 35 if g.Store == nil { 36 return g.make(url, start, end, fps, width, height, fuzz) 37 } 38 key, err := hash(fmt.Sprintf("%s_%f_%f_%f_%d_%d", url, start, end, fps, width, height)) 39 if err != nil { 40 return nil, err 41 } 42 img, ok, err := g.Store.Lookup(key) 43 if err != nil { 44 return nil, errors.Wrap(err, "store lookup") 45 } 46 if ok && img != nil { 47 return img, nil 48 } 49 img, err = g.make(url, start, end, fps, width, height, fuzz) 50 if err != nil { 51 return nil, err 52 } 53 if err := g.Store.Insert(key, img); err != nil { 54 return nil, errors.Wrap(err, "inserting gif into store") 55 } 56 return img, nil 57 } 58 59 func (g *Giffer) make( 60 url string, 61 start, end, fps float64, 62 width, height, fuzz int, 63 ) (*RenderedGif, error) { 64 video, err := g.Download(url, start, end) 65 if err != nil { 66 return nil, errors.Wrap(err, "downloading video") 67 } 68 gif, err := g.Transcode(video, start, end, width, height, fps) 69 if err != nil { 70 return nil, errors.Wrap(err, "transcoding video to gif") 71 } 72 if err := g.Crush(gif, fuzz); err != nil { 73 return nil, errors.Wrap(err, "optimising gif image") 74 } 75 defer g.Clean() 76 gifdata, err := ioutil.ReadFile(gif) 77 if err != nil { 78 return nil, errors.Wrap(err, "buffering gif") 79 } 80 img := &RenderedGif{ 81 Data: gifdata, 82 FileName: sanitiseFilepath(strings.Split(filepath.Base(video), ".")[0] + ".gif"), 83 } 84 return img, nil 85 } 86 87 // RenderedGif wraps the gif data with some metadata. 88 type RenderedGif struct { 89 Data []byte 90 // FileName is <title>.<ext> 91 FileName string 92 } 93 94 func hash(input string) (string, error) { 95 hasher := xxhash.New64() 96 _, err := hasher.WriteString(input) 97 if err != nil { 98 return "", errors.Wrap(err, "hashing input") 99 } 100 h := fmt.Sprintf("%d", hasher.Sum64()) 101 return h, nil 102 } 103 104 func sanitiseFilepath(p string) string { 105 r := strings.NewReplacer( 106 "(", "", 107 ")", "", 108 "!", "", 109 "+", "", 110 "?", "", 111 "*", "", 112 "&", "", 113 "^", "", 114 "=", "", 115 " ", "", 116 ) 117 return r.Replace(p) 118 }