diff --git a/dlserver/go.mod b/dlserver/go.mod new file mode 100644 index 0000000..9b61dce --- /dev/null +++ b/dlserver/go.mod @@ -0,0 +1,5 @@ +module dlserver + +go 1.14 + +require github.com/gorilla/mux v1.8.0 diff --git a/dlserver/go.sum b/dlserver/go.sum new file mode 100644 index 0000000..5350288 --- /dev/null +++ b/dlserver/go.sum @@ -0,0 +1,2 @@ +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= diff --git a/dlserver/main.go b/dlserver/main.go new file mode 100644 index 0000000..bd29ed7 --- /dev/null +++ b/dlserver/main.go @@ -0,0 +1,229 @@ +package main + +import ( + "bufio" + "fmt" + "io" + "log" + "net/http" + "os" + "os/exec" + "regexp" + "runtime" + "strconv" + "time" + + "github.com/gorilla/mux" +) + +func limitLength(s string, length int) string { + if len(s) < length { + return s + } + return s[:length] +} + +// globals, huh, faster than channels and oddly more appropriate, we care less about races than feedback. +var lastPercentage float64 +var lastFilename string + +func convertFile(sourceFileName string, destinationFilename string) error { + launch := "./ffmpeg" + if runtime.GOOS == "windows" { + launch += ".exe" + } + + os.Remove(destinationFilename) + DurationRe := regexp.MustCompile(`DURATION *: ([0-9]+):([0-9]+):([0-9]+)\.([0-9]+)`) + FrameRe := regexp.MustCompile(`frame= ?([0-9]+) `) + FPSValue := 0.0 + finalFramesValue := 0.0 + FPSRe := regexp.MustCompile(`, ([0-9\.]+) fps,`) + fmt.Printf("%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s", launch, "-loglevel", "repeat+level+verbose", "-i", sourceFileName, "-c:v", "libvpx", "-crf", "10", "-b:v", "1M", "-c:a", "libvorbis", "-y", destinationFilename) + cmd := exec.Command(launch, "-progress", "pipe:2", "-i", sourceFileName, "-c:v", "libvpx", "-crf", "10", "-b:v", "1M", "-c:a", "libvorbis", "-y", destinationFilename) + stdout, err := cmd.StderrPipe() + if err != nil { + log.Fatal(err) + } + if err := cmd.Start(); err != nil { + log.Fatal(err) + } + + // Start reading from the file with a reader. + reader := bufio.NewReader(stdout) + var line string + for { + line, err = reader.ReadString('\n') + if err != nil && err != io.EOF { + break + } + fpses := FPSRe.FindAllStringSubmatch(line, 1) + if len(fpses) > 0 { + fmt.Printf("FPS' is %v\n", fpses) + FPSValue, _ = strconv.ParseFloat(fpses[0][1], 64) + } + durations := DurationRe.FindAllStringSubmatch(line, 1) + if len(durations) > 0 { + fmt.Printf("Durations is %v\n", durations) + if FPSValue != 0.0 { + // we have an fps, we can work out max frames! + hrs, _ := strconv.ParseFloat(durations[0][1], 64) + mins, _ := strconv.ParseFloat(durations[0][2], 64) + secs, _ := strconv.ParseFloat(durations[0][3], 64) + aaand, _ := strconv.ParseFloat("0."+durations[0][4], 64) + total := (hrs * 60 * 60) + (mins * 60) + secs + aaand + finalFramesValue = total * FPSValue + fmt.Printf("%0f:%0f:%0f%0.4f * %0.2f = %0.2f seconds = %0.2f frames\n", hrs, mins, secs, aaand, FPSValue, total, finalFramesValue) + } + } + currentFrames := FrameRe.FindAllStringSubmatch(line, 1) + if len(currentFrames) > 0 { + currentFrame, _ := strconv.ParseFloat(currentFrames[0][1], 64) + percentage := currentFrame / finalFramesValue * 100 + lastPercentage = (percentage / 2) + 50 + fmt.Printf("Frame %0f / %0f = Percent : %0.2f\n", currentFrame, finalFramesValue, lastPercentage) + } + + if len(fpses) == 0 && len(durations) == 0 && len(currentFrames) == 0 { + //fmt.Printf("X: %s", line) + } + if err != nil { + break + } + } + if err != io.EOF { + fmt.Printf(" > Failed with error: %v\n", err) + return err + } + lastPercentage = 100 + fmt.Printf("Setting to %0f percent.\n", lastPercentage) + os.Remove(sourceFileName) + go func() { + time.Sleep(10 * time.Second) + lastPercentage = 0 + }() + return nil +} + +func downloadFile(sourceURL string, outputFile string, statusChannel chan float64, nameChannel chan string) error { + progressBackwards := false + lastLastPercentage := 0.0 + launch := "./youtube-dl" + if runtime.GOOS == "windows" { + launch += ".exe" + } + fmt.Printf("%s %s %s %s %s\n", launch, sourceURL, "-o", outputFile, "--newline") + cmd := exec.Command(launch, sourceURL, "-o", outputFile, "--newline") + stdout, err := cmd.StdoutPipe() + if err != nil { + log.Fatal(err) + } + if err := cmd.Start(); err != nil { + log.Fatal(err) + } + re := regexp.MustCompile(`([0-9.]+)%`) + reName := regexp.MustCompile(`Merging formats into "([^"]+)"`) + + // Start reading from the file with a reader. + reader := bufio.NewReader(stdout) + var line string + for { + line, err = reader.ReadString('\n') + if err != nil && err != io.EOF { + break + } + percentages := re.FindAllStringSubmatch(line, 1) + if len(percentages) > 0 { + p, err := strconv.ParseFloat(string(percentages[0][1]), 64) + if err != nil { + fmt.Printf("What kind of number is %v", percentages[0][1]) + } else { + if lastLastPercentage > p { + progressBackwards = true + } + lastLastPercentage = p + if progressBackwards { + // youtube-dl does video, then audio - it's not really half but an estimation + lastPercentage = float64(25) + p/4 + } else { + lastPercentage = p / 4 + } + statusChannel <- lastPercentage + } + } + fmt.Printf("%s\n", line) + if err != nil { + break + } + filenames := reName.FindAllStringSubmatch(line, 1) + if len(filenames) > 0 { + fmt.Printf("Filename: %v", filenames) + lastFilename = filenames[0][1] + nameChannel <- filenames[0][1] + } + } + if err := cmd.Wait(); err != nil { + log.Fatal(err) + } + if err != io.EOF { + fmt.Printf(" > Failed with error: %v\n", err) + return err + } + return convertFile(lastFilename, "converted.webm") +} + +func HomeHandler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, "Hello %s\n", "world") +} + +var c chan float64 +var n chan string + +func FetchHandler(w http.ResponseWriter, r *http.Request) { + //vars := mux.Vars(r) + w.WriteHeader(http.StatusOK) + if lastFilename != "" { + os.Remove(lastFilename) + } + go downloadFile(r.FormValue("url"), "test", c, n) + fmt.Fprint(w, "OK\n") +} + +func StatusHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-type", "application/json") + w.WriteHeader(http.StatusOK) + // Oddly, using channels here is slower and we don't actually care if there's a race condition. + /* if len(c) > 0 { + lastPercentage = <-c + } + if len(n) > 0 { + lastFilename = <-n + } */ + fmt.Printf("Returning %0.2f for %s\n", lastPercentage, lastFilename) + fmt.Fprintf(w, `{"filename": "%s","percentage":%0.2f}`+"\n", lastFilename, lastPercentage) +} + +func main() { + if len(os.Args) > 0 { + os.Chdir(os.Args[1]) + } + r := mux.NewRouter() + r.SkipClean(true) + r.Path("/get").Queries("url", "{.*}").HandlerFunc(FetchHandler) + r.HandleFunc("/status/", StatusHandler) + r.HandleFunc("/", HomeHandler) + http.Handle("/", r) + c = make(chan float64, 999) + n = make(chan string, 999) + srv := &http.Server{ + Handler: r, + Addr: "127.0.0.1:10435", + // Good practice: enforce timeouts for servers you create! + WriteTimeout: 15 * time.Second, + ReadTimeout: 15 * time.Second, + } + + log.Fatal(srv.ListenAndServe()) + +}