package main import ( "bufio" _ "embed" "flag" "fmt" "html/template" "io/fs" "log" "net/http" "os" "os/exec" "path" "strings" "time" "gitlab.com/balki/ytui/db" ) //go:embed templates/index.html var page string const port = 8080 var ( ytdlCmd = []string{"youtube-dl"} videosPath = "./vids" cachePath = "./cache" dbPath = "./db.json" approval bool = false ) var d *db.Db func parse() { ytcmd := ytdlCmd[0] flag.StringVar(&ytcmd, "ytdlcmd", ytcmd, "youtube-dl command seperated by spaces") flag.StringVar(&videosPath, "videospath", videosPath, "Path where videos are saved") flag.StringVar(&cachePath, "cachepath", cachePath, "Path where temporary download files are saved") flag.StringVar(&dbPath, "datapath", dbPath, "Path where downloaded info is saved") flag.BoolVar(&approval, "approval", approval, "Is approval required before allowing to watch") flag.Parse() if ytcmd != ytdlCmd[0] { ytdlCmd = strings.Fields(ytcmd) } } func main() { fmt.Println("vim-go") parse() tmpl, err := template.New("page").Parse(page) if err != nil { panic(err) } d, err = db.Load(dbPath) if err != nil { panic(err) } defer d.Save() seen := map[string]struct{}{} d.Run(func(d *db.Jdb) { for _, i := range d.Items { seen[i.URL] = struct{}{} } }) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { if r.Method == http.MethodGet { d.Run(func(d *db.Jdb) { if err := tmpl.Execute(w, d); err != nil { panic(err) } }) return } if err := r.ParseForm(); err != nil { panic(err) } yturl := r.PostFormValue("youtube_url") if yturl == "" { panic("yturl empty") } if _, ok := seen[yturl]; !ok { seen[yturl] = struct{}{} id := d.Add(db.Item{ Date: time.Now().String(), URL: yturl, Title: "Loading", Approved: false, Status: db.NotStarted, }) go getTitle(id, yturl) go download(id, yturl) } http.Redirect(w, r, "/", http.StatusSeeOther) }) log.Fatal(http.ListenAndServe(fmt.Sprintf(":%v", port), nil)) } func getTitle(id int, yturl string) { args := append(ytdlCmd, "--get-title", yturl) cmd := exec.Command(args[0], args[1:]...) op, err := cmd.Output() if err != nil { panic(err) } d.Update(id, true, func(i *db.Item) { i.Title = string(op) }) } func download(id int, yturl string) { d.Update(id, true, func(i *db.Item) { i.Status = db.InProgress }) pathTmpl := fmt.Sprintf("%s/video_%d.%%(ext)s", cachePath, id) args := append(ytdlCmd, "--newline", "--output", pathTmpl, yturl) cmd := exec.Command(args[0], args[1:]...) rc, err := cmd.StdoutPipe() if err != nil { log.Panic(err) } if err := cmd.Start(); err != nil { log.Panic(err) } br := bufio.NewReader(rc) for { line, _, err := br.ReadLine() if err != nil { break } d.Update(id, false, func(i *db.Item) { i.Progress = string(line) }) } rc.Close() err = cmd.Wait() var status db.DownloadStatus var fname string if err != nil { status = db.Error } else { status = db.Done matches, err := fs.Glob(os.DirFS(cachePath), fmt.Sprintf("video_%d.*", id)) if err != nil { panic(err) } if len(matches) != 1 { panic(len(matches)) } fname = matches[0] err = os.Rename(path.Join(cachePath, fname), path.Join(videosPath, fname)) if err != nil { panic(err) } } d.Update(id, true, func(i *db.Item) { i.Status = status i.FileName = fname }) }