2020-06-22 00:05:56 +08:00
|
|
|
// Copyright (C) 2020 Raziman
|
|
|
|
|
2020-06-19 16:22:20 +08:00
|
|
|
package main
|
|
|
|
|
2020-06-24 11:34:20 +08:00
|
|
|
import (
|
2020-06-27 17:16:49 +08:00
|
|
|
"bytes"
|
|
|
|
"fmt"
|
2020-06-26 12:54:48 +08:00
|
|
|
"net/http"
|
2020-06-24 11:34:20 +08:00
|
|
|
"os"
|
2020-06-27 17:16:49 +08:00
|
|
|
"os/exec"
|
2020-06-25 14:12:23 +08:00
|
|
|
"path"
|
2020-06-27 17:16:49 +08:00
|
|
|
"regexp"
|
2020-06-24 11:34:20 +08:00
|
|
|
"strings"
|
|
|
|
"time"
|
2020-06-26 12:54:48 +08:00
|
|
|
|
|
|
|
"github.com/faiface/beep/mp3"
|
2020-06-27 17:16:49 +08:00
|
|
|
"github.com/rivo/tview"
|
|
|
|
"github.com/spf13/viper"
|
2020-06-24 11:34:20 +08:00
|
|
|
)
|
2020-06-19 16:22:20 +08:00
|
|
|
|
|
|
|
func log(text string) {
|
|
|
|
|
|
|
|
f, err := os.OpenFile("message.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2020-06-21 23:47:12 +08:00
|
|
|
if _, err := f.Write([]byte(text + "\n")); err != nil {
|
2020-06-19 16:22:20 +08:00
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := f.Close(); err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2020-06-24 11:34:20 +08:00
|
|
|
|
|
|
|
func fmtDuration(input time.Duration) string {
|
|
|
|
|
|
|
|
val := input.Round(time.Second).String()
|
|
|
|
|
|
|
|
if !strings.Contains(val, "m") {
|
|
|
|
val = "0m" + val
|
|
|
|
}
|
|
|
|
val = strings.ReplaceAll(val, "h", ":")
|
|
|
|
val = strings.ReplaceAll(val, "m", ":")
|
|
|
|
val = strings.ReplaceAll(val, "s", "")
|
|
|
|
var result []string
|
|
|
|
|
|
|
|
for _, v := range strings.Split(val, ":") {
|
2020-06-26 12:54:48 +08:00
|
|
|
|
2020-06-24 11:34:20 +08:00
|
|
|
if len(v) < 2 {
|
2020-06-26 12:54:48 +08:00
|
|
|
result = append(result, "0"+v)
|
2020-06-24 11:34:20 +08:00
|
|
|
} else {
|
|
|
|
result = append(result, v)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return strings.Join(result, ":")
|
|
|
|
}
|
2020-06-25 14:12:23 +08:00
|
|
|
|
|
|
|
func expandTilde(_path string) string {
|
|
|
|
|
|
|
|
if !strings.HasPrefix(_path, "~") {
|
|
|
|
return _path
|
|
|
|
}
|
|
|
|
|
2020-06-26 12:54:48 +08:00
|
|
|
home, err := os.UserHomeDir()
|
2020-06-25 14:12:23 +08:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
log(err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
return path.Join(home, strings.TrimPrefix(_path, "~"))
|
|
|
|
|
|
|
|
}
|
2020-06-26 12:54:48 +08:00
|
|
|
|
|
|
|
// gets the length of the song in the queue
|
|
|
|
func GetLength(audioPath string) (time.Duration, error) {
|
|
|
|
|
|
|
|
f, err := os.Open(audioPath)
|
|
|
|
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
streamer, format, err := mp3.Decode(f)
|
|
|
|
|
|
|
|
defer streamer.Close()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return format.SampleRate.D(streamer.Len()), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// detects the filetype of file
|
|
|
|
func GetFileContentType(out *os.File) (string, error) {
|
|
|
|
|
|
|
|
buffer := make([]byte, 512)
|
|
|
|
|
|
|
|
_, err := out.Read(buffer)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
contentType := http.DetectContentType(buffer)
|
|
|
|
|
|
|
|
return strings.SplitAfter(contentType, "/")[1], nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// gets the file name by removing extension and path
|
|
|
|
func GetName(fn string) string {
|
|
|
|
return strings.TrimSuffix(path.Base(fn), path.Ext(fn))
|
|
|
|
}
|
2020-06-27 17:16:49 +08:00
|
|
|
|
|
|
|
// download audio from youtube audio and adds the song to the selected playlist
|
|
|
|
func Ytdl(url string, selPlaylist *tview.TreeNode) {
|
|
|
|
|
2020-06-28 12:52:38 +08:00
|
|
|
// lookup if youtube-dl exists
|
|
|
|
_, err := exec.LookPath("youtube-dl")
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
timeoutPopup(" Error ", "youtube-dl is not in your $PATH", time.Second*5)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-06-27 17:16:49 +08:00
|
|
|
dir := viper.GetString("music_dir")
|
|
|
|
|
|
|
|
selAudioFile := selPlaylist.GetReference().(*AudioFile)
|
|
|
|
selPlaylistName := selAudioFile.Name
|
|
|
|
|
|
|
|
timeoutPopup(" Ytdl ", "Downloading", time.Second*5)
|
|
|
|
|
|
|
|
// specify the output path for ytdl
|
|
|
|
outputDir := fmt.Sprintf(
|
2020-06-27 17:18:10 +08:00
|
|
|
"%s/%s/%%(artist)s - %%(track)s.%%(ext)s",
|
|
|
|
dir,
|
2020-06-27 17:16:49 +08:00
|
|
|
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
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
|
|
|
|
err := cmd.Run()
|
|
|
|
if err != nil {
|
|
|
|
timeoutPopup(" Error ", "An error occured when downloading", time.Second*5)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if cmd.Stderr != nil {
|
|
|
|
timeoutPopup(" Error ", "An error occured when downloading", time.Second*5)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
playlistPath := fmt.Sprintf("%s/%s", expandTilde(dir), selPlaylistName)
|
|
|
|
|
|
|
|
downloadedAudioPath := downloadedFilePath(
|
|
|
|
stdout.Bytes(), playlistPath)
|
|
|
|
|
|
|
|
f, err := os.Open(downloadedAudioPath)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
log(err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
node := tview.NewTreeNode(path.Base(f.Name()))
|
|
|
|
|
|
|
|
audioFile := &AudioFile{
|
|
|
|
Name: f.Name(),
|
|
|
|
Path: downloadedAudioPath,
|
|
|
|
IsAudioFile: true,
|
|
|
|
Parent: selPlaylist,
|
|
|
|
}
|
|
|
|
|
|
|
|
node.SetReference(audioFile)
|
|
|
|
selPlaylist.AddChild(node)
|
|
|
|
app.Draw()
|
|
|
|
|
|
|
|
timeoutPopup(
|
|
|
|
" Ytdl ",
|
2020-06-27 17:18:10 +08:00
|
|
|
fmt.Sprintf("Finished downloading\n%s",
|
|
|
|
path.Base(downloadedAudioPath)), time.Second*5)
|
2020-06-27 17:16:49 +08:00
|
|
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// this just parsing the output from the ytdl to get the audio path
|
|
|
|
// this is used because we need to get the song name
|
|
|
|
// example ~/path/to/song/song.mp3
|
|
|
|
func downloadedFilePath(output []byte, dir string) string {
|
|
|
|
|
|
|
|
regexSearch := fmt.Sprintf(`\[ffmpeg\] Destination: %s\/.*.mp3`,
|
|
|
|
escapeBackSlash(dir))
|
|
|
|
|
|
|
|
parseAudioPathOnly := regexp.MustCompile(`\/.*mp3$`)
|
|
|
|
|
|
|
|
re := regexp.MustCompile(regexSearch)
|
|
|
|
|
|
|
|
return string(parseAudioPathOnly.Find(re.Find(output)))
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func escapeBackSlash(input string) string {
|
|
|
|
return strings.ReplaceAll(input, "/", `\/`)
|
|
|
|
}
|