diff --git a/ambiance.go b/ambiance.go index fb8be98..26cb1fc 100644 --- a/ambiance.go +++ b/ambiance.go @@ -5,7 +5,6 @@ import ( "fmt" "io" "log" - "net/http" "os" "os/exec" "path/filepath" @@ -15,7 +14,7 @@ import ( "github.com/google/uuid" ) -var httpClient = new(http.Client) +//var httpClient = new(http.Client) type Ambiance struct { Id string @@ -67,6 +66,22 @@ func GetAmbiances() (amb []Ambiance, err error) { func AddAmbiance(uri, title string) (Ambiance, error) { var amb Ambiance + msg := make(map[string]interface{}) + msg["event"] = "ambiance_add_start" + data := make(map[string]string) + data["name"] = title + msg["payload"] = data + ws_msg <- msg + + defer func() { + msg = make(map[string]interface{}) + msg["event"] = "ambiance_add_finish" + data = make(map[string]string) + data["name"] = title + msg["payload"] = data + ws_msg <- msg + }() + tmpfile, err := exec.Command("mktemp", "/tmp/dnd_XXXXXXXXXXXX.opus").Output() if err != nil { return amb, err @@ -74,28 +89,23 @@ func AddAmbiance(uri, title string) (Ambiance, error) { tmpfile = tmpfile[:len(tmpfile)-1] + log.Printf("Parsing vid from %s", uri) vid, err := YTUrl(uri) if err != nil { return amb, err } - log.Printf("Start YTdl for %s", uri) - dluri, err := NewYTdl(vid) + log.Printf("Start download with YTdl for %s", uri) + ytfile, err := NewYTdl(vid) if err != nil { return amb, err } - log.Printf("Start download.. %s", uri) - resp, err := httpClient.Get(string(dluri)) - if err != nil { - return amb, err - } - defer resp.Body.Close() - + log.Printf("Start ffmpeg for %s", string(ytfile)) ff := exec.Command( "ffmpeg", "-y", - "-i", "-", + "-i", string(ytfile), "-vn", "-acodec", "copy", "-movflags", "+faststart", @@ -113,7 +123,6 @@ func AddAmbiance(uri, title string) (Ambiance, error) { } ff.Stderr = os.Stderr - ff.Stdin = resp.Body err = ff.Start() if err != nil { @@ -121,9 +130,10 @@ func AddAmbiance(uri, title string) (Ambiance, error) { } log.Printf("Start ffmpeg to extract audio to %s", string(tmpfile)) - msg := make(map[string]interface{}) + + msg = make(map[string]interface{}) msg["event"] = "ambiance_encode_start" - data := make(map[string]string) + data = make(map[string]string) data["name"] = title msg["payload"] = data ws_msg <- msg @@ -146,6 +156,7 @@ func AddAmbiance(uri, title string) (Ambiance, error) { ws_msg <- msg }) } + if err := scanner.Err(); err != nil { return amb, err } @@ -197,6 +208,11 @@ func AddAmbiance(uri, title string) (Ambiance, error) { return amb, err } + err = os.Remove(string(ytfile)) + if err != nil { + return amb, err + } + err = os.Remove(string(tmpfile)) if err != nil { return amb, err diff --git a/src/script.js b/src/script.js index 91875eb..318c40b 100644 --- a/src/script.js +++ b/src/script.js @@ -147,6 +147,39 @@ window.onload = function () { avol.value = data.payload.ambiance avol.nextElementSibling.value = data.payload.ambiance break + case "ambiance_download_start": + var progress = document.querySelector("#progressambiance progress") + progress.style.display = "initial" + progress.style.width = "100%" + progress.value = 0 + break + case "ambiance_download_progress": + var progress = document.querySelector("#progressambiance progress") + progress.style.display = "initial" + progress.value = data.payload.percent + console.log(data) + break + case "ambiance_download_complete": + var progress = document.querySelector("#progressambiance progress") + progress.style.display = "initial" + progress.value = 100 + break + case "ambiance_add_finish": + var title = document.querySelector("#inputambiance > input[name='title']") + var url = document.querySelector("#inputambiance > input[name='url']") + var submit = document.querySelector("#inputambiance > input[name='submit']") + var progress = document.querySelector("#progressambiance progress") + + title.value = "" + url.value = "" + + title.disabled = false + url.disabled = false + submit.disabled = false + + progress.value = 0 + progress.style.display = "none" + break case "ambiance_add": const container = document.querySelector("#ambiance") var newdiv = document.createElement('div'); @@ -164,6 +197,21 @@ window.onload = function () { }) container.insertBefore(newdiv, document.querySelector("#ambiance div:last-child")) + var title = document.querySelector("#inputambiance > input[name='title']") + var url = document.querySelector("#inputambiance > input[name='url']") + var submit = document.querySelector("#inputambiance > input[name='submit']") + var progress = document.querySelector("#progressambiance progress") + + title.value = "" + url.value = "" + + title.disabled = false + url.disabled = false + submit.disabled = false + + progress.value = 0 + progress.style.display = "none" + break case "ambiance_play": document.querySelectorAll("#ambiance > div").forEach((e) => {e.style.removeProperty("background-color")}) @@ -218,6 +266,7 @@ window.onload = function () { //output.innerText = "" var title = document.querySelector("#inputambiance > input[name='title']") var url = document.querySelector("#inputambiance > input[name='url']") + var submit = document.querySelector("#inputambiance > input[name='submit']") if (title.value == "" || url.value == "") { console.log("Title or Url is empty!") return @@ -231,8 +280,9 @@ window.onload = function () { } })) - title.value = "" - url.value = "" + title.disabled = true + url.disabled = true + submit.disabled = true }) addInteractHandler(submit, (e, isTouch) => { diff --git a/tmpl/index.tmpl b/tmpl/index.tmpl index 8985286..49a7e88 100644 --- a/tmpl/index.tmpl +++ b/tmpl/index.tmpl @@ -59,6 +59,12 @@ +
+
+ 0% +
+
+
diff --git a/ytdl.go b/ytdl.go index ab4d18d..895fde4 100644 --- a/ytdl.go +++ b/ytdl.go @@ -1,10 +1,15 @@ package main import ( + "bufio" + "encoding/json" "fmt" + "log" + "math" "net/url" "os" "os/exec" + "strconv" "time" "golang.org/x/time/rate" @@ -14,28 +19,92 @@ var prate = rate.Sometimes{Interval: 1 * time.Second} var yturl = "https://youtu.be/%s" func NewYTdl(vid string) ([]byte, error) { + tmpfile, err := exec.Command("mktemp", "/tmp/dnd_ytdlp_XXXXXXXXXXXX.m4a").Output() + if err != nil { + return nil, err + } + + tmpfile = tmpfile[:len(tmpfile)-1] + ytdl := config.GetString("youtube.ytdl") yt := exec.Command( ytdl, fmt.Sprintf(yturl, vid), + "-q", "--cookies", "./cookies.txt", "--no-call-home", "--no-cache-dir", "--ignore-errors", "--newline", "--restrict-filenames", + "--force-overwrites", + "--progress", + "--progress-template", "download:{ \"dl_bytes\": \"%(progress.downloaded_bytes)s\", \"total_bytes\": \"%(progress.total_bytes)s\" }", "-f", "251", - "--get-url", + "-o", string(tmpfile), ) yt.Stderr = os.Stderr - - uri, err := yt.Output() + ytprogress, err := yt.StdoutPipe() if err != nil { return nil, err } - return uri[:len(uri)-1], nil + err = yt.Start() + if err != nil { + return nil, err + } + + log.Printf("Start ffmpeg to extract audio to %s", string(tmpfile)) + msg := make(map[string]interface{}) + msg["event"] = "ambiance_download_start" + data := make(map[string]string) + data["name"] = fmt.Sprintf(yturl, vid) + msg["payload"] = data + ws_msg <- msg + + msg = make(map[string]interface{}) + msg["event"] = "ambiance_download_progress" + data = make(map[string]string) + data["name"] = fmt.Sprintf(yturl, vid) + + scanner := bufio.NewScanner(ytprogress) + + for scanner.Scan() { + err := json.Unmarshal(scanner.Bytes(), &data) + if err != nil { + log.Println(err) + continue + } + + dl, _ := strconv.ParseFloat(data["dl_bytes"], 64) + total, _ := strconv.ParseFloat(data["total_bytes"], 64) + percent := math.Floor((dl / total) * 100) + data["percent"] = strconv.FormatInt(int64(percent), 10) + + prate.Do(func() { + msg["payload"] = data + ws_msg <- msg + }) + } + + if err := scanner.Err(); err != nil { + return nil, err + } + + err = yt.Wait() + if err != nil { + return nil, err + } + + msg = make(map[string]interface{}) + msg["event"] = "ambiance_download_complete" + data = make(map[string]string) + data["name"] = fmt.Sprintf(yturl, vid) + msg["payload"] = data + ws_msg <- msg + + return tmpfile, nil } func YTUrl(uri string) (vid string, err error) { @@ -47,10 +116,18 @@ func YTUrl(uri string) (vid string, err error) { switch u.Host { case "youtu.be": vid = u.Path[1:] + case "m.youtube.com": + fallthrough case "youtube.com": + fallthrough + case "www.youtube.com": vid = u.Query().Get("v") } + if vid == "" { + return vid, fmt.Errorf("unable to parse vid") + } + return }