commit 12a96d1657062db54156c0647335b9c20b612c16
parent 438034ee863a3422cb08e94105c6f0585674d159
Author: Jack Mordaunt <jackmordaunt@gmail.com>
Date: Wed, 7 Nov 2018 16:37:41 +1300
[+] Select quality.
Diffstat:
4 files changed, 139 insertions(+), 28 deletions(-)
diff --git a/cmd/cli/main.go b/cmd/cli/main.go
@@ -39,7 +39,7 @@ func main() {
dl := giffer.Downloader{
Dir: "./tmp/dl",
}
- downloaded, err := dl.Download(url)
+ downloaded, err := dl.Download(url, giffer.Medium)
if err != nil {
log.Fatalf("downloading: %v", err)
}
diff --git a/cmd/desktop/main.go b/cmd/desktop/main.go
@@ -1,9 +1,7 @@
package main
import (
- "os"
- "net/http/httputil"
- "net/url"
+ "runtime/debug"
"bytes"
"encoding/json"
"flag"
@@ -13,6 +11,9 @@ import (
"io"
"log"
"net/http"
+ "net/http/httputil"
+ "net/url"
+ "os"
"path/filepath"
"strings"
"sync"
@@ -29,9 +30,9 @@ import (
)
var (
- port string
+ port string
devServer string
- static http.Handler // responsible for serving UI files.
+ static http.Handler // responsible for serving UI files.
)
func init() {
@@ -56,7 +57,7 @@ func main() {
Dir: "tmp/download",
},
FFMpeg: &giffer.FFMpeg{
- Dir: "tmp/ffmpeg",
+ Dir: "tmp/ffmpeg",
LeaveMess: true,
},
},
@@ -64,8 +65,8 @@ func main() {
Static: static,
}
svr := &http.Server{
- Addr: fmt.Sprintf(":%s", port),
- Handler: ui,
+ 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,
@@ -104,7 +105,7 @@ func (ui *UI) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (ui *UI) routes() {
log := Log{
- Logger: log.New(os.Stdout, "", 0),
+ Logger: log.New(LogWriteHeaderErrors{Out: os.Stdout}, "", 0),
ShowBody: true,
}
ui.Router.Handle("/gifify", Wrap(ui.gifify(), log))
@@ -117,13 +118,14 @@ 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"`
+ 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 {
@@ -136,7 +138,8 @@ func (ui *UI) gifify() http.HandlerFunc {
req.End,
req.FPS,
req.Width,
- req.Height)
+ req.Height,
+ giffer.Quality(req.Quality))
if err != nil {
ui.error(w, err)
return
@@ -166,8 +169,9 @@ func (g Giffer) GififyURL(
url string,
start, end, fps float64,
width, height int,
+ q giffer.Quality,
) (*RenderedGif, error) {
- videofile, err := g.Download(url)
+ videofile, err := g.Download(url, q)
if err != nil {
return nil, errors.Wrap(err, "downloading")
}
@@ -231,7 +235,7 @@ func (g Giffer) GififyURL(
r := &RenderedGif{
Buffer: buf,
// Keep the title but replace the .mp4 extension with .gif
- FileName: strings.Split(filepath.Base(videofile), ".")[0] + ".gif",
+ FileName: sanitiseFilepath(strings.Split(filepath.Base(videofile), ".")[0] + ".gif"),
}
return r, nil
}
@@ -260,7 +264,7 @@ func (p Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Log creates middleware that logs requests.
// If out is nil it is assumed that no logs are desired.
type Log struct {
- Logger *log.Logger
+ Logger *log.Logger
ShowBody bool
}
@@ -289,10 +293,14 @@ func (l Log) Wrap(next http.Handler) http.Handler {
fmt.Fprintf(msg, "Status Code: %d\n", r.Response.StatusCode)
}
if l.ShowBody {
- by, _ := readUntil(r.Body, 1000*64) // 64Kb
+ 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())
@@ -319,4 +327,45 @@ func Wrap(h http.Handler, wrappers ...Wrapper) http.Handler {
h = w.Wrap(h)
}
return h
-}
-\ No newline at end of file
+}
+
+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/ui/src/App.vue b/cmd/desktop/ui/src/App.vue
@@ -37,10 +37,22 @@
<input name="fps" type="number" placeholder="24" min="0" v-model="form.fps">
</span>
<span class="form-group">
+ <label>quality</label>
+ <select name="quality" v-model="form.quality">
+ <option value="0">Low</option>
+ <option value="1">Medium</option>
+ <option value="2">High</option>
+ <option value="3">Best</option>
+ </select>
+ </span>
+ <span class="form-group">
<button type="submit">Submit</button>
</span>
</form>
</div>
+ <div id="img" class="row">
+
+ </div>
<div class="row">
<pre v-if="errors.length > 0" class="errors">
{{errors}}
@@ -68,6 +80,7 @@ export default {
width: 350,
height: 0,
fps: 24,
+ quality: 0,
},
loading: false,
errors: [],
@@ -77,9 +90,15 @@ export default {
submit() {
console.log("submit")
this.loading = true
+ this.form.quality = Number(this.form.quality)
axios.post("gifify", this.form)
.then(resp => {
- console.log(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)
})
.catch(err => {
console.log(err)
diff --git a/download.go b/download.go
@@ -1,6 +1,7 @@
package giffer
import (
+ "strings"
"os"
"github.com/jackmordaunt/video-downloader/config"
"github.com/pkg/errors"
@@ -35,18 +36,55 @@ type Downloader struct {
// Download the video from URL into Dir and return the full path to the
// downloaded file.
-func (dl Downloader) Download(URL string) (string, error) {
+func (dl Downloader) Download(URL string, q Quality) (string, error) {
if err := os.MkdirAll(dl.Dir, 0755); err != nil && err != os.ErrExist {
return "", errors.Wrap(err, "preparing directories")
}
// Side channel for loading config because of how the package is
// unfortunately structured.
config.OutputPath = dl.Dir
- return Download(URL)
+ return Download(URL, q)
}
+// Quality is an enum representing the various video qualities.
+type Quality int
+
+// Matches returns true if the input represents the quality as a string.
+// This is primarily an adaptor so we don't have to change the video-downloader
+// package.
+func (q Quality) Matches(str string) bool {
+ var patterns []string
+ switch q {
+ case Best:
+ patterns = append(patterns, "1080p")
+ case High:
+ patterns = append(patterns, "720p", "480p")
+ case Medium:
+ patterns = append(patterns, "360p")
+ case Low:
+ patterns = append(patterns, "240p", "144p")
+ }
+ for _, p := range patterns {
+ if strings.Contains(str, p) {
+ return true
+ }
+ }
+ return false
+}
+
+const (
+ // Low 144p
+ Low Quality = iota
+ // Medium 360p
+ Medium
+ // High 720p
+ High
+ // Best 1080p@60 > 720p@60 > 1080p > 720p
+ Best
+)
+
// Download a video from the specified url.
-func Download(videoURL string) (string, error) {
+func Download(videoURL string, quality Quality) (string, error) {
var (
domain string
err error
@@ -113,6 +151,12 @@ func Download(videoURL string) (string, error) {
if item.Err != nil {
return "", errors.Wrap(item.Err, "extracting")
}
+ for k, stream := range item.Streams {
+ if quality.Matches(stream.Quality) {
+ config.Stream = k
+ break
+ }
+ }
path, err = item.Download(videoURL)
if err != nil {
return "", errors.Wrap(err, "downloading")