commit 83707e293f4d80ac600bde042c36b8009fa7bf9f
parent 12a96d1657062db54156c0647335b9c20b612c16
Author: Jack Mordaunt <jackmordaunt@gmail.com>
Date: Thu, 8 Nov 2018 14:41:48 +1300
[+] Add Gif handling endpoint.
Diffstat:
7 files changed, 512 insertions(+), 331 deletions(-)
diff --git a/cmd/desktop/giffer.go b/cmd/desktop/giffer.go
@@ -0,0 +1,105 @@
+package main
+
+import (
+ "bytes"
+ "image"
+ "image/gif"
+ "log"
+ "path/filepath"
+ "strings"
+ "sync"
+
+ "github.com/disintegration/imaging"
+ "github.com/jackmordaunt/giffer"
+ "github.com/pkg/errors"
+)
+
+// Giffer wraps the giffer business logic.
+type Giffer struct {
+ *giffer.Downloader
+ *giffer.FFMpeg
+}
+
+// GififyURL downloads the video at url and creates a .gif based on the spcified
+// parameters.
+func (g Giffer) GififyURL(
+ url string,
+ start, end, fps float64,
+ width, height int,
+ q giffer.Quality,
+) (*RenderedGif, error) {
+ videofile, err := g.Download(url, q)
+ if err != nil {
+ return nil, errors.Wrap(err, "downloading")
+ }
+ frames, err := g.Extract(videofile, start, end, fps)
+ if err != nil {
+ return nil, errors.Wrap(err, "extracting frames")
+ }
+ type processed struct {
+ Img *image.Paletted
+ Index int
+ }
+ images := make(chan processed)
+ wg := &sync.WaitGroup{}
+ wg.Add(len(frames))
+ for ii, frame := range frames {
+ ii := ii
+ frame := frame
+ go func() {
+ defer wg.Done()
+ if width != 0 || height != 0 {
+ frame = imaging.Resize(frame, width, height, imaging.Box)
+ }
+ buf := bytes.Buffer{}
+ if err := gif.Encode(&buf, frame, nil); err != nil {
+ log.Printf("encoding gif: %v", err)
+ return
+ }
+ tmpimg, err := gif.Decode(&buf)
+ if err != nil {
+ log.Printf("decoding gif: %v", err)
+ return
+ }
+ images <- processed{
+ Img: tmpimg.(*image.Paletted),
+ Index: ii,
+ }
+ }()
+ }
+ go func() {
+ wg.Wait()
+ close(images)
+ }()
+ paletted := make([]*image.Paletted, len(frames))
+ for frame := range images {
+ paletted[frame.Index] = frame.Img
+ }
+ delays := make([]int, len(frames))
+ delay := int(100 / fps)
+ for ii := range delays {
+ delays[ii] = delay
+ }
+ buf := bytes.NewBuffer(nil)
+ cfg := &gif.GIF{
+ Image: paletted,
+ Delay: delays,
+ LoopCount: 0,
+ }
+ if err := gif.EncodeAll(buf, cfg); err != nil {
+ return nil, errors.Wrap(err, "encoding animated gif")
+ }
+ r := &RenderedGif{
+ Buffer: buf,
+ // Keep the title but replace the .mp4 extension with .gif
+ FileName: sanitiseFilepath(strings.Split(filepath.Base(videofile), ".")[0] + ".gif"),
+ }
+ return r, nil
+}
+
+// RenderedGif wraps the gif data with some metadata.
+type RenderedGif struct {
+ *bytes.Buffer
+ // FileName is <title>.<ext>
+ FileName string
+}
diff --git a/cmd/desktop/http.go b/cmd/desktop/http.go
@@ -0,0 +1,154 @@
+package main
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "log"
+ "net/http"
+ "net/http/httputil"
+ "net/url"
+ "runtime/debug"
+ "strings"
+
+ "github.com/pkg/errors"
+)
+
+// Proxy incoming requests to target.
+type Proxy struct {
+ Target *url.URL
+}
+
+func (p Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ proxy := httputil.NewSingleHostReverseProxy(p.Target)
+ r.URL.Host = p.Target.Host
+ r.URL.Scheme = p.Target.Scheme
+ r.Header.Set("X-Forwarded-Host", r.Header.Get("Host"))
+ r.Host = p.Target.Host
+ proxy.ServeHTTP(w, r)
+}
+
+// Log creates middleware that logs requests.
+// If out is nil it is assumed that no logs are desired.
+type Log struct {
+ Logger *log.Logger
+ ShowBody bool
+}
+
+// Middleware the handler with logging middleware.
+func (l Log) Middleware(next http.Handler) http.Handler {
+ if l.Logger == nil {
+ return next
+ }
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ var msg = &strings.Builder{}
+ fmt.Fprintf(msg, "URL: %s\n", r.URL)
+ fmt.Fprintf(msg, "Method: %s\n", r.Method)
+ fmt.Fprintf(msg, "Headers: http.Header{\n")
+ for h, v := range r.Header {
+ fmt.Fprintf(msg, "\t%q: {", h)
+ for ii := range v {
+ fmt.Fprintf(msg, "%s", v[ii])
+ if ii != len(v)-1 {
+ fmt.Fprint(msg, ",")
+ }
+ }
+ fmt.Fprintf(msg, "},\n")
+ }
+ fmt.Fprintf(msg, "}\n")
+ if r.Response != nil {
+ fmt.Fprintf(msg, "Status Code: %d\n", r.Response.StatusCode)
+ }
+ if l.ShowBody {
+ by, _ := readUntil(r.Body, 1000*32) // 32KB
+ if len(by) > 0 {
+ fmt.Fprintf(msg, " Body: %s\n", string(by))
+ }
+ r.Body = ProxyCloser{
+ Reader: io.MultiReader(bytes.NewBuffer(by), r.Body),
+ Closer: r.Body,
+ }
+ }
+ fmt.Fprintf(msg, "\n")
+ l.Logger.Printf(msg.String())
+ next.ServeHTTP(w, r)
+ })
+}
+
+func readUntil(r io.Reader, until int64) ([]byte, error) {
+ buf := bytes.NewBuffer(nil)
+ if _, err := io.CopyN(buf, r, until); err != nil && err != io.EOF {
+ return buf.Bytes(), err
+ }
+ return buf.Bytes(), nil
+}
+
+func sanitiseFilepath(p string) string {
+ r := strings.NewReplacer(
+ "(", "",
+ ")", "",
+ "!", "",
+ "+", "",
+ "?", "",
+ "*", "",
+ "&", "",
+ "^", "",
+ "=", "",
+ " ", "",
+ )
+ return r.Replace(p)
+}
+
+// ProxyCloser decouples the reader from the closer.
+// This enables the use of io.MultiReader while closing the underlying source.
+type ProxyCloser struct {
+ io.Reader
+ io.Closer
+}
+
+// LogWriteHeaderErrors helps when debugging http "multiple response.WriteHeader"
+// calls.
+type LogWriteHeaderErrors struct {
+ Out io.Writer
+}
+
+func (d LogWriteHeaderErrors) Write(p []byte) (n int, err error) {
+ s := string(p)
+ if strings.Contains(s, "multiple response.WriteHeader") {
+ n, err = d.Out.Write(debug.Stack())
+ if err != nil {
+ return n, err
+ }
+ }
+ return d.Out.Write(p)
+}
+
+// FIXME(jfm): The error should be logged instead of returned to client, such
+// that it makes it's way to the developers.
+// You don't want clients knowing your failure modes, since this can lead
+// to security exploits.
+func httpError(w http.ResponseWriter, err error) {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+}
+
+func writeJSON(w http.ResponseWriter, v interface{}) {
+ type appErr struct {
+ Error error `json:"error,omitempty"`
+ }
+ // Wrap an error to ensure the returned object has an "error" property.
+ if err, ok := v.(error); ok {
+ v = appErr{
+ Error: err,
+ }
+ }
+ by, err := json.Marshal(v)
+ if err != nil {
+ httpError(w, errors.Wrap(err, "creating json response"))
+ return
+ }
+ if _, err := w.Write(by); err != nil {
+ // log? panic?
+ panic(errors.Wrap(err, "atttempting to write to http.ResponseWriter"))
+ }
+}
diff --git a/cmd/desktop/main.go b/cmd/desktop/main.go
@@ -1,28 +1,16 @@
package main
import (
- "runtime/debug"
- "bytes"
- "encoding/json"
"flag"
"fmt"
- "image"
- "image/gif"
- "io"
"log"
"net/http"
- "net/http/httputil"
"net/url"
- "os"
- "path/filepath"
- "strings"
- "sync"
+ "time"
- "github.com/disintegration/imaging"
"github.com/jackmordaunt/giffer"
"github.com/GeertJohan/go.rice"
- "github.com/pkg/errors"
"github.com/gorilla/mux"
@@ -65,12 +53,10 @@ func main() {
Static: static,
}
svr := &http.Server{
- Addr: fmt.Sprintf(":%s", port),
- Handler: ui,
- // Can't use these time-outs yet since the gifify route doesn't
- // return immediately (downloads 400MB, etc...).
- // WriteTimeout: 15 * time.Second,
- // ReadTimeout: 15 * time.Second,
+ Addr: fmt.Sprintf(":%s", port),
+ Handler: ui,
+ WriteTimeout: 15 * time.Second,
+ ReadTimeout: 15 * time.Second,
}
go func() {
if err := svr.ListenAndServe(); err != nil {
@@ -87,285 +73,3 @@ func main() {
})
view.Run()
}
-
-// UI serves the user interface over http.
-type UI struct {
- App *Giffer
- Router *mux.Router
- Static http.Handler
- init sync.Once
-}
-
-func (ui *UI) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- ui.init.Do(func() {
- ui.routes()
- })
- ui.Router.ServeHTTP(w, r)
-}
-
-func (ui *UI) routes() {
- log := Log{
- Logger: log.New(LogWriteHeaderErrors{Out: os.Stdout}, "", 0),
- ShowBody: true,
- }
- ui.Router.Handle("/gifify", Wrap(ui.gifify(), log))
- // The router typically treats "/" as a unique router We have to tell
- // it otherwise in order to handle file paths correctly.
- ui.Router.Handle("/{path:.*}", Wrap(ui.Static, log))
-}
-
-func (ui *UI) gifify() http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- defer r.Body.Close()
- type request struct {
- URL string `json:"url,omitempty"`
- Start float64 `json:"start,omitempty"`
- End float64 `json:"end,omitempty"`
- FPS float64 `json:"fps,omitempty"`
- Width int `json:"width,omitempty"`
- Height int `json:"height,omitempty"`
- Output string `json:"output,omitempty"`
- Quality int `json:"quality,omitempty"`
- }
- var req request
- if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
- ui.error(w, errors.Wrap(err, "decoding json request"))
- return
- }
- img, err := ui.App.GififyURL(
- req.URL,
- req.Start,
- req.End,
- req.FPS,
- req.Width,
- req.Height,
- giffer.Quality(req.Quality))
- if err != nil {
- ui.error(w, err)
- return
- }
- w.Header().Set("Content-Type", http.DetectContentType(img.Bytes()))
- w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, img.FileName))
- if _, err := io.Copy(w, img); err != nil {
- ui.error(w, errors.Wrap(err, "writing response"))
- return
- }
- }
-}
-
-func (ui *UI) error(w http.ResponseWriter, err error) {
- http.Error(w, err.Error(), http.StatusInternalServerError)
-}
-
-// Giffer wraps the giffer business logic.
-type Giffer struct {
- *giffer.Downloader
- *giffer.FFMpeg
-}
-
-// GififyURL downloads the video at url and creates a .gif based on the spcified
-// parameters.
-func (g Giffer) GififyURL(
- url string,
- start, end, fps float64,
- width, height int,
- q giffer.Quality,
-) (*RenderedGif, error) {
- videofile, err := g.Download(url, q)
- if err != nil {
- return nil, errors.Wrap(err, "downloading")
- }
- frames, err := g.Extract(videofile, start, end, fps)
- if err != nil {
- return nil, errors.Wrap(err, "extracting frames")
- }
- type processed struct {
- Img *image.Paletted
- Index int
- }
- images := make(chan processed)
- wg := &sync.WaitGroup{}
- wg.Add(len(frames))
- for ii, frame := range frames {
- ii := ii
- frame := frame
- go func() {
- defer wg.Done()
- if width != 0 || height != 0 {
- frame = imaging.Resize(frame, width, height, imaging.Box)
- }
- buf := bytes.Buffer{}
- if err := gif.Encode(&buf, frame, nil); err != nil {
- // errors.Wrap(err, "encoding gif")
- return
- }
- tmpimg, err := gif.Decode(&buf)
- if err != nil {
- // errors.Wrap(err, "decoding gif")
- return
- }
- images <- processed{
- Img: tmpimg.(*image.Paletted),
- Index: ii,
- }
- }()
- }
- go func() {
- wg.Wait()
- close(images)
- }()
- paletted := make([]*image.Paletted, len(frames))
- for frame := range images {
- paletted[frame.Index] = frame.Img
- }
- delays := make([]int, len(frames))
- delay := int(100 / fps)
- for ii := range delays {
- delays[ii] = delay
- }
- buf := bytes.NewBuffer(nil)
- cfg := &gif.GIF{
- Image: paletted,
- Delay: delays,
- LoopCount: 0,
- }
- if err := gif.EncodeAll(buf, cfg); err != nil {
- return nil, errors.Wrap(err, "encoding animated gif")
- }
- r := &RenderedGif{
- Buffer: buf,
- // Keep the title but replace the .mp4 extension with .gif
- FileName: sanitiseFilepath(strings.Split(filepath.Base(videofile), ".")[0] + ".gif"),
- }
- return r, nil
-}
-
-// RenderedGif wraps the gif data with some metadata.
-type RenderedGif struct {
- *bytes.Buffer
- // FileName is <title>.<ext>
- FileName string
-}
-
-// Proxy incoming requests to target.
-type Proxy struct {
- Target *url.URL
-}
-
-func (p Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- proxy := httputil.NewSingleHostReverseProxy(p.Target)
- r.URL.Host = p.Target.Host
- r.URL.Scheme = p.Target.Scheme
- r.Header.Set("X-Forwarded-Host", r.Header.Get("Host"))
- r.Host = p.Target.Host
- proxy.ServeHTTP(w, r)
-}
-
-// Log creates middleware that logs requests.
-// If out is nil it is assumed that no logs are desired.
-type Log struct {
- Logger *log.Logger
- ShowBody bool
-}
-
-// Wrap the handler with logging middleware.
-func (l Log) Wrap(next http.Handler) http.Handler {
- if l.Logger == nil {
- return next
- }
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- var msg = &strings.Builder{}
- fmt.Fprintf(msg, "URL: %s\n", r.URL)
- fmt.Fprintf(msg, "Method: %s\n", r.Method)
- fmt.Fprintf(msg, "Headers: http.Header{\n")
- for h, v := range r.Header {
- fmt.Fprintf(msg, "\t%q: {", h)
- for ii := range v {
- fmt.Fprintf(msg, "%s", v[ii])
- if ii != len(v)-1 {
- fmt.Fprint(msg, ",")
- }
- }
- fmt.Fprintf(msg, "},\n")
- }
- fmt.Fprintf(msg, "}\n")
- if r.Response != nil {
- fmt.Fprintf(msg, "Status Code: %d\n", r.Response.StatusCode)
- }
- if l.ShowBody {
- by, _ := readUntil(r.Body, 1000*32) // 32KB
- if len(by) > 0 {
- fmt.Fprintf(msg, " Body: %s\n", string(by))
- }
- r.Body = ProxyCloser{
- Reader: io.MultiReader(bytes.NewBuffer(by), r.Body),
- Closer: r.Body,
- }
- }
- fmt.Fprintf(msg, "\n")
- l.Logger.Printf(msg.String())
- next.ServeHTTP(w, r)
- })
-}
-
-func readUntil(r io.Reader, until int64) ([]byte, error) {
- buf := bytes.NewBuffer(nil)
- if _, err := io.CopyN(buf, r, until); err != nil && err != io.EOF {
- return buf.Bytes(), err
- }
- return buf.Bytes(), nil
-}
-
-// Wrapper wraps an http.Handler to provide things like middleware.
-type Wrapper interface {
- Wrap(http.Handler) http.Handler
-}
-
-// Wrap the handler with the provided wrappers.
-func Wrap(h http.Handler, wrappers ...Wrapper) http.Handler {
- for _, w := range wrappers {
- h = w.Wrap(h)
- }
- return h
-}
-
-func sanitiseFilepath(p string) string {
- r := strings.NewReplacer(
- "(", "",
- ")", "",
- "!", "",
- "+", "",
- "?", "",
- "*", "",
- "&", "",
- "^", "",
- "=", "",
- " ", "",
- )
- return r.Replace(p)
-}
-
-// ProxyCloser decouples the reader from the closer.
-// This enables the use of io.MultiReader while closing the underlying source.
-type ProxyCloser struct {
- io.Reader
- io.Closer
-}
-
-
-// LogWriteHeaderErrors helps when debugging http "multiple response.WriteHeader"
-// calls.
-type LogWriteHeaderErrors struct {
- Out io.Writer
-}
-
-func (d LogWriteHeaderErrors) Write(p []byte) (n int, err error) {
- s := string(p)
- if strings.Contains(s, "multiple response.WriteHeader") {
- n, err = d.Out.Write(debug.Stack())
- if err != nil {
- return n, err
- }
- }
- return d.Out.Write(p)
-}
diff --git a/cmd/desktop/server.go b/cmd/desktop/server.go
@@ -0,0 +1,197 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "log"
+ "net/http"
+ "os"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/OneOfOne/xxhash"
+ "github.com/jackmordaunt/giffer"
+
+ "github.com/gorilla/mux"
+ "github.com/gorilla/websocket"
+
+ "github.com/pkg/errors"
+)
+
+// UI serves the user interface over http.
+type UI struct {
+ App *Giffer
+ Router *mux.Router
+ Static http.Handler
+
+ gifmap map[string]http.Handler
+ init sync.Once
+}
+
+func (ui *UI) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ ui.init.Do(func() {
+ if ui.gifmap == nil {
+ ui.gifmap = make(map[string]http.Handler)
+ }
+ ui.routes()
+ })
+ ui.Router.ServeHTTP(w, r)
+}
+
+func (ui *UI) routes() {
+ log := Log{
+ Logger: log.New(LogWriteHeaderErrors{Out: os.Stdout}, "", 0),
+ ShowBody: true,
+ }
+ ui.Router.Use(log.Middleware)
+ ui.Router.Handle("/gifify", ui.gifify())
+ ui.Router.Handle("/gifs/{key}", ui.gifs())
+ ui.Router.Handle("/gifs/{key}/info", ui.gifs())
+ ui.Router.Handle("/{path:.*}", ui.Static)
+}
+
+func (ui *UI) gifify() http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ defer r.Body.Close()
+ type request struct {
+ URL string `json:"url,omitempty"`
+ Start float64 `json:"start,omitempty"`
+ End float64 `json:"end,omitempty"`
+ FPS float64 `json:"fps,omitempty"`
+ Width int `json:"width,omitempty"`
+ Height int `json:"height,omitempty"`
+ Output string `json:"output,omitempty"`
+ Quality int `json:"quality,omitempty"`
+ }
+ var req request
+ if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+ httpError(w, errors.Wrap(err, "decoding json request"))
+ return
+ }
+ by, err := json.Marshal(req)
+ if err != nil {
+ httpError(w, fmt.Errorf("marshalling json for hash"))
+ return
+ }
+ h := xxhash.New64()
+ if _, err := h.Write(by); err != nil {
+ httpError(w, fmt.Errorf("writing to hash object"))
+ return
+ }
+ g := &Gif{
+ Upgrader: &websocket.Upgrader{
+ ReadBufferSize: 1024,
+ WriteBufferSize: 1024,
+ },
+ }
+ go g.Process(func() (*RenderedGif, error) {
+ return ui.App.GififyURL(
+ req.URL,
+ req.Start,
+ req.End,
+ req.FPS,
+ req.Width,
+ req.Height,
+ giffer.Quality(req.Quality))
+ })
+ key := fmt.Sprintf("%d", h.Sum64())
+ ui.gifmap[key] = g
+ type response struct {
+ File string `json:"file"`
+ Info string `json:"info"`
+ }
+ // FIXME(jfm): Should these endpoints be typed, instead of
+ // relying on assumptions about the routing?
+ writeJSON(w, response{
+ File: fmt.Sprintf("/gifs/%s", key),
+ Info: fmt.Sprintf("/gifs/%s/info", key),
+ })
+ }
+}
+
+func (ui *UI) gifs() http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ key := mux.Vars(r)["key"]
+ h, ok := ui.gifmap[key]
+ if !ok {
+ writeJSON(w, fmt.Errorf("no gif exists for key %q", key))
+ return
+ }
+ h.ServeHTTP(w, r)
+ }
+}
+
+// Gif handles the serving of a gif file.
+// There are two enpoints:
+type Gif struct {
+ Upgrader *websocket.Upgrader
+ Tick time.Duration
+
+ file *RenderedGif
+ subs map[*websocket.Conn]struct{}
+ subMutex sync.Mutex
+ err error
+ once sync.Once
+}
+
+// Process runs the specified function and sends a websocket message when it
+// completes.
+func (g *Gif) Process(fn func() (*RenderedGif, error)) {
+ type done struct {
+ Err error `json:"error,omitempty"`
+ }
+ g.file, g.err = fn()
+ g.subMutex.Lock()
+ for s := range g.subs {
+ err := s.WriteJSON(done{
+ Err: g.err,
+ })
+ if err != nil {
+ s.Close()
+ delete(g.subs, s)
+ log.Printf("writing json to websocket: %v", err)
+ }
+ }
+ g.subMutex.Unlock()
+}
+
+func (g *Gif) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ if strings.HasSuffix(r.URL.Path, "info") {
+ g.subscribe(w, r)
+ } else {
+ g.serveFile(w, r)
+ }
+}
+
+func (g *Gif) subscribe(w http.ResponseWriter, r *http.Request) {
+ c, err := g.Upgrader.Upgrade(w, r, nil)
+ if err != nil {
+ log.Printf("upgrading websocket: %v", err)
+ return
+ }
+ g.append(c)
+}
+
+func (g *Gif) serveFile(w http.ResponseWriter, r *http.Request) {
+ if g.file == nil {
+ writeJSON(w, fmt.Errorf("gif not ready"))
+ return
+ }
+ w.Header().Set("Content-Type", "image/gif")
+ w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, g.file.FileName))
+ if _, err := io.Copy(w, g.file); err != nil {
+ httpError(w, errors.Wrap(err, "writing gif to response body"))
+ return
+ }
+}
+
+func (g *Gif) append(c *websocket.Conn) {
+ g.once.Do(func() {
+ g.subs = make(map[*websocket.Conn]struct{})
+ })
+ g.subMutex.Lock()
+ g.subs[c] = struct{}{}
+ g.subMutex.Unlock()
+}
diff --git a/cmd/desktop/ui/src/App.vue b/cmd/desktop/ui/src/App.vue
@@ -50,12 +50,12 @@
</span>
</form>
</div>
- <div id="img" class="row">
-
+ <div class="row">
+ <a v-if="link.length > 0" download="memer.gif" :href="link">Download!</a>
</div>
<div class="row">
- <pre v-if="errors.length > 0" class="errors">
- {{errors}}
+ <pre v-for="(err, ii) in errors" :key="ii" class="errors">
+ {{err}}
</pre>
</div>
<div class="row">
@@ -84,28 +84,48 @@ export default {
},
loading: false,
errors: [],
+ link: "",
}
},
methods: {
submit() {
- console.log("submit")
this.loading = true
- this.form.quality = Number(this.form.quality)
+ try {
+ this.form.start = Number(this.form.start)
+ this.form.end = Number(this.form.end)
+ this.form.width = Number(this.form.width)
+ this.form.height = Number(this.form.height)
+ this.form.fps = Number(this.form.fps)
+ this.form.quality = Number(this.form.quality)
+ } catch(e) {
+ this.errors.push("form values are not valid numbers")
+ return
+ }
axios.post("gifify", this.form)
.then(resp => {
- let mime = resp.headers["content-type"]
- console.log(mime)
- let decoded = btoa(resp.data)
- let img = new ImageData()
- img.src = `data:${mime};base64,${decoded}`
- document.getElementById("#img").appendChild(img)
+ console.log(resp)
+ if (resp.data.error) {
+ this.errors.push(resp.data.error)
+ return
+ }
+ console.log("waiting for download to be ready...")
+ let { file, info } = resp.data
+ let updates = new WebSocket(`ws://localhost:8081${info}`)
+ updates.onmessage = (msg) => {
+ if (msg.error !== undefined) {
+ this.errors.push(msg.error)
+ return
+ }
+ console.log("ready to download!")
+ this.link = `http://localhost:8081${file}`
+ updates.close()
+ this.loading = false
+ }
+
})
.catch(err => {
- console.log(err)
- this.errors.push(err)
- })
- .finally(() => {
- this.loading = false
+ console.log("catching error")
+ this.errors.push(err.response.data)
})
}
},
diff --git a/go.mod b/go.mod
@@ -1,16 +1,16 @@
module github.com/jackmordaunt/giffer
require (
- github.com/GeertJohan/go.incremental v0.0.0-20161212213043-1172aab96510 // indirect
github.com/GeertJohan/go.rice v0.0.0-20170420135705-c02ca9a983da
- github.com/akavel/rsrc v0.0.0-20170831122431-f6a15ece2cfd // indirect
+ github.com/OneOfOne/xxhash v1.2.2
+ github.com/cespare/xxhash v1.1.0 // indirect
github.com/daaku/go.zipexe v0.0.0-20150329023125-a5fe2436ffcb // indirect
github.com/disintegration/imaging v1.5.0
+ github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/mux v1.6.2
+ github.com/gorilla/websocket v1.4.0
github.com/jackmordaunt/video-downloader v0.0.0-20181105210957-4fcc0a0db5fa
- github.com/jessevdk/go-flags v1.4.0 // indirect
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 // indirect
- github.com/kr/pty v1.1.3 // indirect
github.com/pkg/errors v0.8.0
github.com/zserge/webview v0.0.0-20181018084947-f390a2df9ec5
golang.org/x/image v0.0.0-20181102021609-63626fb251ce // indirect
diff --git a/go.sum b/go.sum
@@ -1,35 +1,34 @@
-github.com/GeertJohan/go.incremental v0.0.0-20161212213043-1172aab96510 h1:XKmpFaGpsBo5B7NC6RxawBYk6BFi0a6fw03J5PYW/9g=
-github.com/GeertJohan/go.incremental v0.0.0-20161212213043-1172aab96510/go.mod h1:0K8QLSiwClOppBKLLSRX1sFvYdX5/fWqAZkjboOEzak=
github.com/GeertJohan/go.rice v0.0.0-20170420135705-c02ca9a983da h1:UVU3a9pRUyLdnBtn60WjRl0s4SEyJc2ChCY56OAR6wI=
github.com/GeertJohan/go.rice v0.0.0-20170420135705-c02ca9a983da/go.mod h1:DgrzXonpdQbfN3uYaGz1EG4Sbhyum/MMIn6Cphlh2bw=
github.com/MercuryEngineering/CookieMonster v0.0.0-20180304172713-1584578b3403 h1:EtZwYyLbkEcIt+B//6sujwRCnHuTEK3qiSypAX5aJeM=
github.com/MercuryEngineering/CookieMonster v0.0.0-20180304172713-1584578b3403/go.mod h1:mM6WvakkX2m+NgMiPCfFFjwfH4KzENC07zeGEqq9U7s=
+github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
+github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/goquery v1.4.1 h1:smcIRGdYm/w7JSbcdeLHEMzxmsBQvl8lhf0dSw2nzMI=
github.com/PuerkitoBio/goquery v1.4.1/go.mod h1:T9ezsOHcCrDCgA8aF1Cqr3sSYbO/xgdy8/R/XiIMAhA=
-github.com/akavel/rsrc v0.0.0-20170831122431-f6a15ece2cfd h1:yumR8733CaQ3P76MFbIbBKdrJmy4EqnQ5DIhqq8gq2Q=
-github.com/akavel/rsrc v0.0.0-20170831122431-f6a15ece2cfd/go.mod h1:2+aQMrY0hBFBaIr2xxnZ/ctfwnYmMRMbTczYLAC34v4=
github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o=
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
+github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/daaku/go.zipexe v0.0.0-20150329023125-a5fe2436ffcb h1:tUf55Po0vzOendQ7NWytcdK0VuzQmfAgvGBUOQvN0WA=
github.com/daaku/go.zipexe v0.0.0-20150329023125-a5fe2436ffcb/go.mod h1:U0vRfAucUOohvdCxt5MWLF+TePIL0xbCkbKIiV8TQCE=
github.com/disintegration/imaging v1.5.0 h1:uYqUhwNmLU4K1FN44vhqS4TZJRAA4RhBINgbQlKyGi0=
github.com/disintegration/imaging v1.5.0/go.mod h1:9B/deIUIrliYkyMTuXJd6OUFLcrZ2tf+3Qlwnaf/CjU=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
+github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
-github.com/jackmordaunt/video-downloader v0.0.0-20181105202251-49cb2aa58132 h1:IpS/g+mhZdopN8B9PLHyqjALlOGc73RIPCIshatFSmk=
-github.com/jackmordaunt/video-downloader v0.0.0-20181105202251-49cb2aa58132/go.mod h1:lpJaIhnCxJiQ14jMk0Jt0pprldUnVCliN9dUAL6Mu4M=
+github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
+github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/jackmordaunt/video-downloader v0.0.0-20181105210957-4fcc0a0db5fa h1:+rrTx3ivriLXJO6Wew4qwedhW2ZbZ9YGDTuDq50hPx0=
github.com/jackmordaunt/video-downloader v0.0.0-20181105210957-4fcc0a0db5fa/go.mod h1:lpJaIhnCxJiQ14jMk0Jt0pprldUnVCliN9dUAL6Mu4M=
-github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
-github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 h1:PJPDf8OUfOK1bb/NeTKd4f1QXZItOX389VN3B6qC8ro=
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
@@ -40,6 +39,8 @@ github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8Bz
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/zserge/webview v0.0.0-20181018084947-f390a2df9ec5 h1:1zYVGLwZR4gPRQdEiOBf9s63ZHGfCkQ/p99d1zHuZBQ=
github.com/zserge/webview v0.0.0-20181018084947-f390a2df9ec5/go.mod h1:a1CV8KR4Dd1eP2g+mEijGOp+HKczwdKHWyx0aPHKvo4=
golang.org/x/image v0.0.0-20181102021609-63626fb251ce h1:9baQ83qLsketF/x2bdSUNelOJfCgswFxV8yNnV/+6II=