gomu/playlist.go

759 lines
14 KiB
Go
Raw Normal View History

2020-06-22 00:05:56 +08:00
// Copyright (C) 2020 Raziman
2020-06-19 16:22:20 +08:00
package main
import (
2020-07-03 11:17:45 +08:00
"bytes"
2020-06-23 22:27:02 +08:00
"fmt"
2020-06-19 16:22:20 +08:00
"io/ioutil"
2020-08-11 13:39:02 +08:00
"log"
2020-06-23 18:42:27 +08:00
"os"
2020-07-03 11:17:45 +08:00
"os/exec"
2020-06-25 10:46:45 +08:00
"path"
2020-06-19 16:22:20 +08:00
"path/filepath"
2020-07-22 21:01:13 +08:00
"strings"
2020-06-28 13:41:14 +08:00
"time"
2020-06-19 16:22:20 +08:00
"github.com/gdamore/tcell"
"github.com/rivo/tview"
2020-06-24 20:09:47 +08:00
"github.com/spf13/viper"
2020-07-24 22:27:08 +08:00
spin "github.com/tj/go-spin"
2020-07-21 12:22:00 +08:00
"github.com/ztrue/tracerr"
2020-06-19 16:22:20 +08:00
)
2020-07-23 15:34:56 +08:00
// Playlist and mp3 files are represented with this struct
// if isAudioFile equals to false it is a directory
2020-06-19 16:22:20 +08:00
type AudioFile struct {
2020-07-23 15:15:39 +08:00
name string
path string
isAudioFile bool
length time.Duration
node *tview.TreeNode
parent *tview.TreeNode
2020-06-19 16:22:20 +08:00
}
2020-07-25 22:53:54 +08:00
// Playlist struct represents playlist panel
// that shows the tree of the music directory
2020-06-26 12:54:48 +08:00
type Playlist struct {
*tview.TreeView
2020-07-24 22:27:08 +08:00
prevNode *tview.TreeNode
defaultTitle string
// number of downloads
download int
done chan struct{}
2020-06-26 12:54:48 +08:00
}
2020-06-19 16:22:20 +08:00
2020-07-23 15:15:39 +08:00
func (p *Playlist) help() []string {
2020-07-17 15:34:50 +08:00
return []string{
"j down",
"k up",
"h close node",
"l add song to queue",
"L add playlist to queue",
"d delete file from filesystem",
"D delete playlist from filesystem",
"Y download audio",
"r refresh",
2020-07-24 23:06:34 +08:00
"/ find in playlist",
2020-07-17 15:34:50 +08:00
}
}
2020-07-24 22:27:08 +08:00
// newPlaylist returns new instance of playlist and runs populate function
// on root music directory.
2020-08-11 13:39:02 +08:00
func newPlaylist(args Args) *Playlist {
2020-06-24 20:09:47 +08:00
2020-07-28 11:24:58 +08:00
rootDir, err := filepath.Abs(expandTilde(viper.GetString("general.music_dir")))
2020-06-19 16:22:20 +08:00
2020-08-11 13:39:02 +08:00
// if not default value was given
if *args.music != "~/music" {
rootDir = expandFilePath(*args.music)
}
2020-06-19 16:22:20 +08:00
if err != nil {
2020-08-11 13:39:02 +08:00
log.Fatalf("Unable to find music directory: %e", err)
2020-06-19 16:22:20 +08:00
}
2020-07-01 21:21:09 +08:00
root := tview.NewTreeNode(path.Base(rootDir)).
2020-07-23 15:15:39 +08:00
SetColor(gomu.accentColor)
2020-06-19 16:22:20 +08:00
tree := tview.NewTreeView().SetRoot(root)
2020-06-28 13:41:14 +08:00
2020-07-17 15:34:50 +08:00
playlist := &Playlist{
2020-07-24 22:27:08 +08:00
TreeView: tree,
defaultTitle: "─ Playlist ──┤ 0 downloads ├",
done: make(chan struct{}),
2020-07-17 15:34:50 +08:00
}
2020-06-28 13:41:14 +08:00
rootAudioFile := &AudioFile{
2020-07-23 15:15:39 +08:00
name: root.GetText(),
node: root,
path: rootDir,
2020-06-28 13:41:14 +08:00
}
root.SetReference(rootAudioFile)
2020-06-19 16:22:20 +08:00
2020-07-10 22:05:36 +08:00
playlist.
2020-07-24 22:27:08 +08:00
SetTitle(playlist.defaultTitle).
2020-07-10 22:05:36 +08:00
SetBorder(true).
SetTitleAlign(tview.AlignLeft)
2020-06-19 16:22:20 +08:00
2020-06-25 14:12:19 +08:00
populate(root, rootDir)
2020-06-19 16:22:20 +08:00
2020-06-25 14:12:19 +08:00
var firstChild *tview.TreeNode
2020-06-23 18:42:27 +08:00
2020-06-25 14:12:19 +08:00
if len(root.GetChildren()) == 0 {
firstChild = root
} else {
firstChild = root.GetChildren()[0]
}
2020-06-19 16:22:20 +08:00
2020-07-23 15:15:39 +08:00
playlist.setHighlight(firstChild)
2020-06-19 16:22:20 +08:00
2020-06-26 12:54:48 +08:00
playlist.SetChangedFunc(func(node *tview.TreeNode) {
2020-07-23 15:15:39 +08:00
playlist.setHighlight(node)
2020-06-25 14:12:19 +08:00
})
2020-06-23 18:42:27 +08:00
2020-06-26 12:54:48 +08:00
playlist.SetSelectedFunc(func(node *tview.TreeNode) {
node.SetExpanded(!node.IsExpanded())
})
2020-06-19 16:22:20 +08:00
2020-06-26 12:54:48 +08:00
playlist.SetInputCapture(func(e *tcell.EventKey) *tcell.EventKey {
currNode := playlist.GetCurrentNode()
audioFile := currNode.GetReference().(*AudioFile)
switch e.Rune() {
2020-06-28 13:41:14 +08:00
2020-07-24 14:48:06 +08:00
case ' ':
// Disable default key handler
return nil
2020-07-01 17:47:52 +08:00
case 'a':
2020-07-23 15:15:39 +08:00
name, _ := gomu.pages.GetFrontPage()
2020-07-01 17:47:52 +08:00
if name != "mkdir-popup" {
2020-07-23 15:15:39 +08:00
createPlaylistPopup()
2020-07-01 17:47:52 +08:00
}
2020-06-28 13:41:14 +08:00
case 'D':
2020-07-23 15:15:39 +08:00
err := playlist.deletePlaylist(audioFile)
2020-07-20 21:48:13 +08:00
if err != nil {
2020-07-23 15:15:39 +08:00
logError(err)
2020-06-28 13:41:14 +08:00
}
case 'd':
// prevent from deleting a directory
2020-07-23 15:15:39 +08:00
if !audioFile.isAudioFile {
2020-06-28 13:41:14 +08:00
return e
}
2020-07-23 15:15:39 +08:00
err := playlist.deleteSong(audioFile)
2020-07-20 21:48:13 +08:00
if err != nil {
2020-07-23 15:15:39 +08:00
logError(err)
2020-07-20 21:48:13 +08:00
}
2020-06-28 13:41:14 +08:00
2020-06-27 17:16:49 +08:00
case 'Y':
2020-07-23 15:15:39 +08:00
if gomu.pages.HasPage("download-popup") {
gomu.pages.RemovePage("download-popup")
2020-06-28 13:41:14 +08:00
return e
}
2020-06-27 17:16:49 +08:00
2020-06-28 13:41:14 +08:00
// this ensures it downloads to
// the correct dir
2020-07-23 15:15:39 +08:00
if audioFile.isAudioFile {
downloadMusicPopup(audioFile.parent)
2020-06-28 13:41:14 +08:00
} else {
2020-07-02 16:11:10 +08:00
downloadMusicPopup(currNode)
2020-06-27 17:16:49 +08:00
}
2020-06-26 12:54:48 +08:00
case 'l':
2020-07-23 15:15:39 +08:00
if audioFile.isAudioFile {
gomu.queue.enqueue(audioFile)
2020-07-12 17:05:00 +08:00
} else {
2020-07-10 16:51:20 +08:00
currNode.SetExpanded(true)
}
2020-06-26 12:54:48 +08:00
case 'h':
// if closing node with no children
// close the node's parent
// remove the color of the node
2020-07-23 15:15:39 +08:00
if audioFile.isAudioFile {
parent := audioFile.parent
2020-06-26 12:54:48 +08:00
2020-07-23 15:15:39 +08:00
playlist.setHighlight(parent)
2020-07-01 21:21:09 +08:00
2020-06-26 12:54:48 +08:00
parent.SetExpanded(false)
}
currNode.Collapse()
case 'L':
2020-07-28 11:24:58 +08:00
if !viper.GetBool("general.confirm_bulk_add") {
2020-07-23 15:15:39 +08:00
playlist.addAllToQueue(playlist.GetCurrentNode())
2020-06-26 17:09:15 +08:00
return e
}
2020-06-26 12:54:48 +08:00
confirmationPopup(
"Are you sure to add this whole directory into queue?",
func(_ int, label string) {
if label == "yes" {
2020-07-23 15:15:39 +08:00
playlist.addAllToQueue(playlist.GetCurrentNode())
2020-06-26 12:54:48 +08:00
}
})
2020-07-02 16:11:10 +08:00
case 'r':
2020-07-23 15:15:39 +08:00
playlist.refresh()
2020-07-02 16:11:10 +08:00
2020-07-24 23:06:34 +08:00
case '/':
2020-07-22 21:00:14 +08:00
2020-07-23 15:15:39 +08:00
err := playlist.fuzzyFind()
2020-07-22 21:00:14 +08:00
if err != nil {
2020-07-23 15:15:39 +08:00
logError(err)
2020-07-22 21:00:14 +08:00
}
2020-06-26 12:54:48 +08:00
}
return e
2020-06-19 16:22:20 +08:00
})
2020-06-26 12:54:48 +08:00
return playlist
2020-06-19 16:22:20 +08:00
}
2020-07-23 15:34:56 +08:00
// Deletes song from filesystem
2020-07-23 15:15:39 +08:00
func (p *Playlist) deleteSong(audioFile *AudioFile) (err error) {
2020-07-20 21:48:13 +08:00
confirmationPopup(
"Are you sure to delete this audio file?", func(_ int, buttonName string) {
2020-07-22 21:01:13 +08:00
if buttonName == "no" || buttonName == "" {
2020-07-20 21:48:13 +08:00
return
}
2020-07-23 15:15:39 +08:00
err := os.Remove(audioFile.path)
2020-07-20 21:48:13 +08:00
if err != nil {
2020-07-23 15:15:39 +08:00
timedPopup(" Error ", "Unable to delete "+audioFile.name,
2020-07-20 21:48:13 +08:00
getPopupTimeout(), 0, 0)
2020-07-21 12:22:00 +08:00
err = tracerr.Wrap(err)
2020-07-20 21:48:13 +08:00
} else {
2020-07-23 15:15:39 +08:00
timedPopup(" Success ", audioFile.name+"\nhas been deleted successfully",
2020-07-20 21:48:13 +08:00
getPopupTimeout(), 0, 0)
2020-07-23 15:15:39 +08:00
p.refresh()
2020-07-20 21:48:13 +08:00
}
2020-07-21 01:21:59 +08:00
})
2020-07-20 21:48:13 +08:00
return nil
}
// Deletes playlist/dir from filesystem
2020-07-23 15:15:39 +08:00
func (p *Playlist) deletePlaylist(audioFile *AudioFile) (err error) {
2020-07-20 21:48:13 +08:00
var selectedDir *AudioFile
// gets the parent dir if current focused node is not a dir
2020-07-23 15:15:39 +08:00
if audioFile.isAudioFile {
selectedDir = audioFile.parent.GetReference().(*AudioFile)
2020-07-20 21:48:13 +08:00
} else {
selectedDir = audioFile
}
2020-07-21 01:21:59 +08:00
confirmationPopup("Are you sure to delete this directory?",
2020-07-20 21:48:13 +08:00
func(_ int, buttonName string) {
2020-07-22 21:01:13 +08:00
if buttonName == "no" || buttonName == "" {
2020-07-20 21:48:13 +08:00
return
}
2020-07-23 15:15:39 +08:00
err := os.RemoveAll(selectedDir.path)
2020-07-20 21:48:13 +08:00
if err != nil {
timedPopup(
" Error ",
2020-07-23 15:15:39 +08:00
"Unable to delete dir "+selectedDir.name,
2020-07-20 21:48:13 +08:00
getPopupTimeout(), 0, 0)
2020-07-21 12:22:00 +08:00
err = tracerr.Wrap(err)
2020-07-20 21:48:13 +08:00
} else {
timedPopup(
" Success ",
2020-07-23 15:15:39 +08:00
selectedDir.name+"\nhas been deleted successfully",
2020-07-20 21:48:13 +08:00
getPopupTimeout(), 0, 0)
2020-07-23 15:15:39 +08:00
p.refresh()
2020-07-20 21:48:13 +08:00
}
2020-07-21 01:21:59 +08:00
})
2020-07-20 21:48:13 +08:00
return nil
}
// Bulk add a playlist to queue
2020-07-23 15:15:39 +08:00
func (p *Playlist) addAllToQueue(root *tview.TreeNode) {
2020-06-25 14:12:19 +08:00
var childrens []*tview.TreeNode
childrens = root.GetChildren()
2020-07-01 21:21:09 +08:00
// gets the parent if the highlighted item is a file
2020-07-23 15:15:39 +08:00
if root.GetReference().(*AudioFile).isAudioFile {
childrens = root.GetReference().(*AudioFile).parent.GetChildren()
2020-06-26 12:54:48 +08:00
}
2020-06-25 14:12:19 +08:00
for _, v := range childrens {
currNode := v.GetReference().(*AudioFile)
2020-07-23 15:15:39 +08:00
gomu.queue.enqueue(currNode)
2020-06-25 14:12:19 +08:00
}
2020-06-25 10:46:45 +08:00
}
2020-06-28 13:41:14 +08:00
2020-07-23 15:34:56 +08:00
// Refreshes the playlist and read the whole root music dir
2020-07-23 15:15:39 +08:00
func (p *Playlist) refresh() {
2020-06-28 13:41:14 +08:00
2020-07-23 15:15:39 +08:00
root := gomu.playlist.GetRoot()
2020-06-28 13:41:14 +08:00
2020-07-23 15:15:39 +08:00
prevFileName := gomu.playlist.GetCurrentNode().GetText()
2020-06-28 13:41:14 +08:00
root.ClearChildren()
2020-07-23 15:15:39 +08:00
populate(root, root.GetReference().(*AudioFile).path)
2020-06-28 13:41:14 +08:00
root.Walk(func(node, parent *tview.TreeNode) bool {
// to preserve previously highlighted node
2020-07-13 14:33:29 +08:00
if node.GetText() == prevFileName {
2020-07-23 15:15:39 +08:00
p.setHighlight(node)
2020-06-28 13:41:14 +08:00
return false
}
return true
})
2020-07-01 17:47:52 +08:00
}
// Adds child while setting reference to audio file
2020-07-23 15:15:39 +08:00
func (p *Playlist) addSongToPlaylist(
2020-07-20 21:48:13 +08:00
audioPath string, selPlaylist *tview.TreeNode,
) error {
2020-07-01 17:47:52 +08:00
f, err := os.Open(audioPath)
if err != nil {
2020-07-21 12:22:00 +08:00
return tracerr.Wrap(err)
2020-07-01 17:47:52 +08:00
}
defer f.Close()
2020-07-23 15:15:39 +08:00
node := tview.NewTreeNode(getName(audioPath))
2020-07-01 17:47:52 +08:00
2020-07-23 15:15:39 +08:00
audioLength, err := getLength(audioPath)
2020-07-01 17:47:52 +08:00
if err != nil {
2020-07-21 12:22:00 +08:00
return tracerr.Wrap(err)
2020-07-01 17:47:52 +08:00
}
audioFile := &AudioFile{
2020-07-23 15:15:39 +08:00
name: path.Base(audioPath),
path: audioPath,
isAudioFile: true,
length: audioLength,
parent: selPlaylist,
2020-07-01 17:47:52 +08:00
}
node.SetReference(audioFile)
selPlaylist.AddChild(node)
2020-07-23 15:15:39 +08:00
gomu.app.Draw()
2020-07-01 17:47:52 +08:00
return nil
}
2020-07-22 21:00:14 +08:00
// Gets all audio files walks from music root directory
2020-07-23 15:15:39 +08:00
func (p *Playlist) getAudioFiles() []*AudioFile {
2020-07-10 22:05:36 +08:00
2020-07-10 16:52:37 +08:00
root := p.GetRoot()
audioFiles := []*AudioFile{}
root.Walk(func(node, _ *tview.TreeNode) bool {
audioFile := node.GetReference().(*AudioFile)
audioFiles = append(audioFiles, audioFile)
2020-07-10 22:05:36 +08:00
2020-07-10 16:52:37 +08:00
return true
})
return audioFiles
}
// Creates a directory under selected node, returns error if playlist exists
2020-07-23 15:15:39 +08:00
func (p *Playlist) createPlaylist(name string) error {
2020-07-01 17:47:52 +08:00
selectedNode := p.GetCurrentNode()
2020-07-23 15:15:39 +08:00
parentNode := selectedNode.GetReference().(*AudioFile).parent
2020-07-01 17:47:52 +08:00
// if the current node is the root
// sets the parent to itself
if parentNode == nil {
parentNode = selectedNode
}
audioFile := parentNode.GetReference().(*AudioFile)
2020-07-23 15:15:39 +08:00
err := os.Mkdir(path.Join(audioFile.path, name), 0744)
2020-07-01 17:47:52 +08:00
if err != nil {
2020-07-21 12:22:00 +08:00
return tracerr.Wrap(err)
2020-07-01 17:47:52 +08:00
}
2020-07-23 15:15:39 +08:00
p.refresh()
2020-07-01 17:47:52 +08:00
return nil
2020-06-28 13:41:14 +08:00
}
2020-07-01 21:21:09 +08:00
// This is used to replace default behaviour of SetCurrentNode which
2020-07-01 21:21:09 +08:00
// adds color highlight attributes
2020-07-23 15:15:39 +08:00
func (p *Playlist) setHighlight(currNode *tview.TreeNode) {
2020-07-01 21:21:09 +08:00
if p.prevNode != nil {
2020-07-23 15:15:39 +08:00
p.prevNode.SetColor(gomu.textColor)
2020-07-01 21:21:09 +08:00
}
2020-07-23 15:15:39 +08:00
currNode.SetColor(gomu.accentColor)
2020-07-01 21:21:09 +08:00
p.SetCurrentNode(currNode)
2020-07-23 15:15:39 +08:00
if currNode.GetReference().(*AudioFile).isAudioFile {
2020-07-01 21:21:09 +08:00
p.prevNode = currNode
}
}
2020-07-03 11:17:45 +08:00
2020-07-06 18:58:27 +08:00
// Traverses the playlist and finds the AudioFile struct
2020-07-10 16:52:37 +08:00
// audioName must be hashed with sha1 first
2020-07-23 15:15:39 +08:00
func (p *Playlist) findAudioFile(audioName string) (*AudioFile, error) {
2020-07-06 18:58:27 +08:00
root := p.GetRoot()
var selNode *AudioFile
root.Walk(func(node, _ *tview.TreeNode) bool {
audioFile := node.GetReference().(*AudioFile)
2020-07-23 15:15:39 +08:00
hashed := sha1Hex(getName(audioFile.name))
2020-07-10 16:52:37 +08:00
if hashed == audioName {
2020-07-06 18:58:27 +08:00
selNode = audioFile
return false
}
return true
})
2020-07-21 12:22:00 +08:00
if selNode == nil {
return nil, tracerr.New("no matching audio name")
}
2020-07-06 18:58:27 +08:00
2020-07-21 12:22:00 +08:00
return selNode, nil
2020-07-06 18:58:27 +08:00
}
2020-07-03 11:17:45 +08:00
2020-07-22 21:00:14 +08:00
// Highlight the selected node searched using fzf
2020-07-23 15:15:39 +08:00
func (p *Playlist) fuzzyFind() error {
2020-07-22 21:00:14 +08:00
var result string
var err error
2020-07-23 15:15:39 +08:00
audioFiles := p.getAudioFiles()
2020-07-22 21:00:14 +08:00
paths := make(map[string]*tview.TreeNode, len(audioFiles))
input := make([]string, 0, len(audioFiles))
for _, v := range audioFiles {
2020-07-23 15:15:39 +08:00
rootDir := audioFiles[0].path + "/"
2020-07-22 21:00:14 +08:00
// path relative to music directory
2020-07-23 15:15:39 +08:00
shortPath := strings.TrimPrefix(v.path, rootDir)
paths[shortPath] = v.node
2020-07-22 21:00:14 +08:00
input = append(input, shortPath)
}
2020-07-23 15:15:39 +08:00
gomu.suspend()
ok := gomu.app.Suspend(func() {
res, e := fzfFind(input)
2020-07-22 21:00:14 +08:00
if e != nil {
err = tracerr.Wrap(e)
}
result = res
})
2020-07-23 15:15:39 +08:00
gomu.unsuspend()
2020-07-22 21:00:14 +08:00
if err != nil {
return tracerr.Wrap(err)
}
if !ok {
return tracerr.New("App was not suspended")
}
if result == "" {
return nil
}
if err != nil {
return tracerr.Wrap(err)
}
var selNode *tview.TreeNode
2020-07-25 22:54:53 +08:00
selNode, ok = paths[result]
if ok {
p.setHighlight(selNode)
}
2020-07-22 21:00:14 +08:00
return nil
}
2020-07-25 22:53:54 +08:00
// updateTitle creates a spinning motion on the title
// of the playlist panel when downloading.
2020-07-24 22:27:08 +08:00
func (p *Playlist) updateTitle() {
if p.download == 0 {
p.SetTitle(p.defaultTitle)
return
}
// only one call can be made in one time
if p.download > 1 {
return
}
2020-07-24 22:27:08 +08:00
s := spin.New()
Download:
for {
2020-07-24 23:06:34 +08:00
// prevent from calling tview API when suspending
if gomu.isSuspend {
continue
}
2020-07-24 22:27:08 +08:00
select {
case <-p.done:
p.download -= 1
if p.download == 0 {
p.SetTitle(p.defaultTitle)
break Download
}
case <-time.After(time.Millisecond * 100):
2020-07-25 22:53:25 +08:00
r, g, b := gomu.accentColor.RGB()
hexColor := padHex(r, g, b)
title := fmt.Sprintf("─ Playlist ──┤ %d downloads [green]%s[#%s] ├",
p.download, s.Next(), hexColor)
2020-07-24 22:27:08 +08:00
p.SetTitle(title)
gomu.app.Draw()
}
}
}
2020-07-22 21:00:14 +08:00
// Takes a list of input and suspends tview
// returns empty string if cancelled
2020-07-23 15:15:39 +08:00
func fzfFind(input []string) (string, error) {
2020-07-22 21:00:14 +08:00
var in strings.Builder
var out strings.Builder
for _, v := range input {
in.WriteString(v + "\n")
}
cmd := exec.Command("fzf")
cmd.Stdin = strings.NewReader(in.String())
cmd.Stderr = os.Stderr
cmd.Stdout = &out
if err := cmd.Run(); cmd.ProcessState.ExitCode() == 130 {
// exit code 130 is when we cancel FZF
// not an error
return "", nil
} else if err != nil {
return "", fmt.Errorf("failed to find a file: %s", err)
}
f := strings.TrimSpace(out.String())
return f, nil
}
2020-07-23 15:15:39 +08:00
// Download audio from youtube audio and adds the song to the selected playlist
func ytdl(url string, selPlaylist *tview.TreeNode) error {
2020-07-03 11:17:45 +08:00
// lookup if youtube-dl exists
_, err := exec.LookPath("youtube-dl")
if err != nil {
2020-07-21 01:21:59 +08:00
timedPopup(" Error ", "youtube-dl is not in your $PATH",
2020-07-20 21:48:13 +08:00
getPopupTimeout(), 0, 0)
2020-07-21 12:22:00 +08:00
return tracerr.Wrap(err)
2020-07-03 11:17:45 +08:00
}
2020-07-28 11:24:58 +08:00
dir := viper.GetString("general.music_dir")
2020-07-03 11:17:45 +08:00
selAudioFile := selPlaylist.GetReference().(*AudioFile)
2020-07-23 15:15:39 +08:00
selPlaylistName := selAudioFile.name
2020-07-03 11:17:45 +08:00
2020-07-12 12:21:49 +08:00
timedPopup(" Ytdl ", "Downloading", getPopupTimeout(), 0, 0)
2020-07-03 11:17:45 +08:00
// specify the output path for ytdl
outputDir := fmt.Sprintf(
"%s/%s/%%(title)s.%%(ext)s",
dir,
selPlaylistName)
args := []string{
"--extract-audio",
"--audio-format",
"mp3",
"--output",
outputDir,
url,
}
cmd := exec.Command("youtube-dl", args...)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
2020-07-24 22:27:08 +08:00
gomu.playlist.download++
go gomu.playlist.updateTitle()
2020-07-21 12:22:00 +08:00
err = cmd.Run()
2020-07-12 17:05:00 +08:00
2020-07-24 22:27:08 +08:00
gomu.playlist.done <- struct{}{}
2020-07-21 12:22:00 +08:00
if err != nil {
timedPopup(" Error ", "Error running youtube-dl", getPopupTimeout(), 0, 0)
return tracerr.Wrap(err)
}
2020-07-12 17:05:00 +08:00
2020-07-21 12:22:00 +08:00
playlistPath := path.Join(expandTilde(dir), selPlaylistName)
audioPath := extractFilePath(stdout.Bytes(), playlistPath)
2020-07-03 11:17:45 +08:00
2020-07-23 15:15:39 +08:00
err = gomu.playlist.addSongToPlaylist(audioPath, selPlaylist)
2020-07-03 11:17:45 +08:00
2020-07-21 12:22:00 +08:00
if err != nil {
return tracerr.Wrap(err)
}
2020-07-03 11:17:45 +08:00
2020-07-21 12:22:00 +08:00
downloadFinishedMessage := fmt.Sprintf("Finished downloading\n%s",
path.Base(audioPath))
2020-07-03 11:17:45 +08:00
2020-07-21 12:22:00 +08:00
timedPopup(
" Ytdl ",
downloadFinishedMessage,
getPopupTimeout(), 0, 0)
2020-07-13 14:33:13 +08:00
2020-07-23 15:15:39 +08:00
gomu.app.Draw()
return nil
}
// Add songs and their directories in Playlist panel
func populate(root *tview.TreeNode, rootPath string) error {
files, err := ioutil.ReadDir(rootPath)
if err != nil {
return tracerr.Wrap(err)
}
for _, file := range files {
path := filepath.Join(rootPath, file.Name())
f, err := os.Open(path)
if err != nil {
continue
}
defer f.Close()
songName := getName(file.Name())
child := tview.NewTreeNode(songName)
if !file.IsDir() {
filetype, err := getFileContentType(f)
if err != nil {
continue
}
// skip if not mp3 file
if filetype != "mpeg" {
continue
}
audioLength, err := getLength(path)
if err != nil {
continue
}
audioFile := &AudioFile{
name: songName,
path: path,
isAudioFile: true,
length: audioLength,
node: child,
parent: root,
}
child.SetReference(audioFile)
2020-07-26 00:08:45 +08:00
child.SetText(fmt.Sprintf("🎵 %s", songName))
2020-07-23 15:15:39 +08:00
root.AddChild(child)
}
if file.IsDir() {
audioFile := &AudioFile{
2020-07-24 14:53:17 +08:00
name: songName,
path: path,
node: child,
parent: root,
2020-07-23 15:15:39 +08:00
}
child.SetReference(audioFile)
child.SetColor(gomu.accentColor)
2020-07-26 00:08:45 +08:00
child.SetText(fmt.Sprintf("📁 %s", songName))
2020-07-23 15:15:39 +08:00
root.AddChild(child)
populate(child, path)
}
}
2020-07-03 11:17:45 +08:00
2020-07-21 12:22:00 +08:00
return nil
2020-07-13 14:33:13 +08:00
}