giffer

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

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 }