mirror of
https://github.com/issadarkthing/gomu.git
synced 2025-04-26 13:49:21 +08:00
Merge branch 'master' into youtube-search
This commit is contained in:
commit
f2f225a058
76
README.md
76
README.md
@ -15,6 +15,7 @@ Gomu is a Terminal User Interface **TUI** music player to play mp3 files from yo
|
||||
- [youtube-dl](https://github.com/ytdl-org/youtube-dl) integration
|
||||
- audio file management
|
||||
- customizable
|
||||
- find music from youtube
|
||||
|
||||
## Dependencies
|
||||
If you are using ubuntu, you need to install alsa and required dependencies
|
||||
@ -64,10 +65,19 @@ general:
|
||||
confirm_on_exit: true
|
||||
load_prev_queue: true
|
||||
music_dir: ~/music
|
||||
history_path: ~/.local/share/gomu/urls
|
||||
popup_timeout: 5s
|
||||
volume: 100
|
||||
emoji: true
|
||||
emoji: false
|
||||
fzf: false
|
||||
|
||||
emoji:
|
||||
playlist:
|
||||
file:
|
||||
loop: ﯩ
|
||||
noloop:
|
||||
|
||||
# vi:ft=yaml
|
||||
```
|
||||
|
||||
## Fzf
|
||||
@ -81,28 +91,51 @@ edit this line `fzf: false` to change it into `true` in `~/.config/gomu/config`.
|
||||
## Keybindings
|
||||
Each panel has it's own additional keybinding. To view the available keybinding for the specific panel use `?`
|
||||
|
||||
| Key | Description |
|
||||
|:----------------|-----------------------:|
|
||||
| j | down |
|
||||
| k | up |
|
||||
| tab | change panel |
|
||||
| space | toggle play/pause |
|
||||
| esc | close popup |
|
||||
| n | skip |
|
||||
| q | quit |
|
||||
| l (lowercase L) | add song to queue |
|
||||
| L | add playlist to queue |
|
||||
| h | close node in playlist |
|
||||
| d | remove from queue |
|
||||
| D | delete playlist |
|
||||
| + | volume up |
|
||||
| - | volume down |
|
||||
| y | search youtube |
|
||||
| Y | download audio |
|
||||
| a | create playlist |
|
||||
| ? | toggle help |
|
||||
| Key (General) | Description |
|
||||
|:----------------|--------------------------------:|
|
||||
| tab | change panel |
|
||||
| space | toggle play/pause |
|
||||
| esc | close popup |
|
||||
| n | skip |
|
||||
| q | quit |
|
||||
| + | volume up |
|
||||
| - | volume down |
|
||||
| f | forward 10 seconds |
|
||||
| F | forward 60 seconds |
|
||||
| b | rewind 10 seconds |
|
||||
| B | rewind 60 seconds |
|
||||
| ? | toggle help |
|
||||
|
||||
|
||||
| Key (Playlist) | Description |
|
||||
|:----------------|--------------------------------:|
|
||||
| j | down |
|
||||
| k | up |
|
||||
| h | close node in playlist |
|
||||
| a | create playlist |
|
||||
| l (lowercase L) | add song to queue |
|
||||
| L | add playlist to queue |
|
||||
| d | delete file from filesystemd |
|
||||
| D | delete playlist from filesystem |
|
||||
| Y | download audio |
|
||||
| r | refresh |
|
||||
| R | rename |
|
||||
| y | yank file |
|
||||
| p | paste file |
|
||||
| / | find in playlist |
|
||||
| s | search audio from youtube |
|
||||
|
||||
| Key (Queue) | Description |
|
||||
|:----------------|--------------------------------:|
|
||||
| j | down |
|
||||
| k | up |
|
||||
| l (lowercase L) | play selected song |
|
||||
| d | remove from queue |
|
||||
| D | delete playlist |
|
||||
| z | toggle loop |
|
||||
| s | shuffle |
|
||||
| f | find in queue |
|
||||
|
||||
|
||||
## Project Background
|
||||
I just wanted to implement my own music player with a programming language i'm currently learning ([Go](https://golang.org/)). Gomu might not be stable as it in constant development. For now, it can fulfill basic music player functions such as:
|
||||
@ -111,5 +144,6 @@ I just wanted to implement my own music player with a programming language i'm c
|
||||
- skip
|
||||
- play
|
||||
- pause
|
||||
- forward and rewind
|
||||
|
||||
Seeking and more advanced stuff has not yet been implemented; feel free to contribute :)
|
||||
|
95
command.go
95
command.go
@ -68,7 +68,7 @@ func (c Command) defineCommands() {
|
||||
|
||||
popupId := "youtube-search-input-popup"
|
||||
|
||||
input := newInputPopup(popupId, " Youtube Search ", "search: ")
|
||||
input := newInputPopup(popupId, " Youtube Search ", "search: ", "")
|
||||
|
||||
// quick hack to change the autocomplete text color
|
||||
tview.Styles.PrimitiveBackgroundColor = tcell.ColorBlack
|
||||
@ -163,8 +163,9 @@ func (c Command) defineCommands() {
|
||||
|
||||
audioFile := gomu.playlist.getCurrentFile()
|
||||
currNode := gomu.playlist.GetCurrentNode()
|
||||
if gomu.pages.HasPage("download-popup") {
|
||||
gomu.pages.RemovePage("download-popup")
|
||||
if gomu.pages.HasPage("download-input-popup") {
|
||||
gomu.pages.RemovePage("download-input-popup")
|
||||
gomu.popups.pop()
|
||||
return
|
||||
}
|
||||
// this ensures it downloads to
|
||||
@ -280,27 +281,20 @@ func (c Command) defineCommands() {
|
||||
})
|
||||
|
||||
c.define("play_selected", func() {
|
||||
a, err := gomu.queue.deleteItem(gomu.queue.GetCurrentItem())
|
||||
if err != nil {
|
||||
logError(err)
|
||||
}
|
||||
if gomu.queue.GetItemCount() != 0 && gomu.queue.GetCurrentItem() != -1 {
|
||||
a, err := gomu.queue.deleteItem(gomu.queue.GetCurrentItem())
|
||||
if err != nil {
|
||||
logError(err)
|
||||
}
|
||||
|
||||
gomu.queue.pushFront(a)
|
||||
gomu.player.skip()
|
||||
gomu.queue.pushFront(a)
|
||||
gomu.player.skip()
|
||||
}
|
||||
})
|
||||
|
||||
c.define("toggle_loop", func() {
|
||||
|
||||
isLoop := gomu.player.toggleLoop()
|
||||
var msg string
|
||||
|
||||
if isLoop {
|
||||
msg = "Looping current queue"
|
||||
} else {
|
||||
msg = "Stopped looping current queue"
|
||||
}
|
||||
|
||||
defaultTimedPopup(" Loop ", msg)
|
||||
gomu.queue.isLoop = gomu.player.toggleLoop()
|
||||
gomu.queue.updateTitle()
|
||||
})
|
||||
|
||||
c.define("shuffle_queue", func() {
|
||||
@ -399,4 +393,65 @@ func (c Command) defineCommands() {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
c.define("forward", func() {
|
||||
if gomu.player.isRunning && !gomu.player.ctrl.Paused {
|
||||
position := gomu.playingBar._progress + 10
|
||||
if position < gomu.playingBar.full {
|
||||
gomu.player.seek(position)
|
||||
gomu.playingBar._progress = position - 1
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
c.define("rewind", func() {
|
||||
if gomu.player.isRunning && !gomu.player.ctrl.Paused {
|
||||
position := gomu.playingBar._progress - 10
|
||||
if position-1 > 0 {
|
||||
gomu.player.seek(position)
|
||||
gomu.playingBar._progress = position - 1
|
||||
} else {
|
||||
gomu.player.seek(0)
|
||||
gomu.playingBar._progress = 0
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
c.define("forward_fast", func() {
|
||||
if gomu.player.isRunning && !gomu.player.ctrl.Paused {
|
||||
position := gomu.playingBar._progress + 60
|
||||
if position < gomu.playingBar.full {
|
||||
gomu.player.seek(position)
|
||||
gomu.playingBar._progress = position - 1
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
c.define("rewind_fast", func() {
|
||||
if gomu.player.isRunning && !gomu.player.ctrl.Paused {
|
||||
position := gomu.playingBar._progress - 60
|
||||
if position-1 > 0 {
|
||||
gomu.player.seek(position)
|
||||
gomu.playingBar._progress = position - 1
|
||||
} else {
|
||||
gomu.player.seek(0)
|
||||
gomu.playingBar._progress = 0
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
c.define("yank", func() {
|
||||
err := gomu.playlist.yank()
|
||||
if err != nil {
|
||||
logError(err)
|
||||
}
|
||||
})
|
||||
|
||||
c.define("paste", func() {
|
||||
err := gomu.playlist.paste()
|
||||
if err != nil {
|
||||
logError(err)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
4
go.mod
4
go.mod
@ -5,7 +5,7 @@ go 1.14
|
||||
require (
|
||||
github.com/faiface/beep v1.0.2
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/gdamore/tcell/v2 v2.0.1-0.20201017141208-acf90d56d591
|
||||
github.com/gdamore/tcell/v2 v2.1.0
|
||||
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect
|
||||
github.com/hajimehoshi/go-mp3 v0.3.1 // indirect
|
||||
github.com/hajimehoshi/oto v0.7.1 // indirect
|
||||
@ -15,7 +15,7 @@ require (
|
||||
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
||||
github.com/pelletier/go-toml v1.8.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/rivo/tview v0.0.0-20210117162420-745e4ceeb711
|
||||
github.com/rivo/tview v0.0.0-20210125085121-dbc1f32bb1d0
|
||||
github.com/sahilm/fuzzy v0.1.0
|
||||
github.com/spf13/afero v1.5.1 // indirect
|
||||
github.com/spf13/cast v1.3.1 // indirect
|
||||
|
4
go.sum
4
go.sum
@ -51,6 +51,8 @@ github.com/gdamore/tcell v1.4.0 h1:vUnHwJRvcPQa3tzi+0QI4U9JINXYJlOz9yiaiPQ2wMU=
|
||||
github.com/gdamore/tcell v1.4.0/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0=
|
||||
github.com/gdamore/tcell/v2 v2.0.1-0.20201017141208-acf90d56d591 h1:0WWUDZ1oxq7NxVyGo8M3KI5jbkiwNAdZFFzAdC68up4=
|
||||
github.com/gdamore/tcell/v2 v2.0.1-0.20201017141208-acf90d56d591/go.mod h1:vSVL/GV5mCSlPC6thFP5kfOFdM9MGZcalipmpTxTgQA=
|
||||
github.com/gdamore/tcell/v2 v2.1.0 h1:UnSmozHgBkQi2PGsFr+rpdXuAPRRucMegpQp3Z3kDro=
|
||||
github.com/gdamore/tcell/v2 v2.1.0/go.mod h1:vSVL/GV5mCSlPC6thFP5kfOFdM9MGZcalipmpTxTgQA=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
@ -202,6 +204,8 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rivo/tview v0.0.0-20210117162420-745e4ceeb711 h1:9xC0sXenoeJK2jP8LK24H4FwcCcPwK8ZNCxgURhn52c=
|
||||
github.com/rivo/tview v0.0.0-20210117162420-745e4ceeb711/go.mod h1:1QW7hX7RQzOqyGgx8O64bRPQBrFtPflioPPX5gFPV3A=
|
||||
github.com/rivo/tview v0.0.0-20210125085121-dbc1f32bb1d0 h1:WCfp+Jq9Mx156zIf9X6Frd6F19rf7wIRlm54UPxUfcU=
|
||||
github.com/rivo/tview v0.0.0-20210125085121-dbc1f32bb1d0/go.mod h1:1QW7hX7RQzOqyGgx8O64bRPQBrFtPflioPPX5gFPV3A=
|
||||
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
|
24
gomu.go
24
gomu.go
@ -90,6 +90,24 @@ func (g *Gomu) cyclePanels() Panel {
|
||||
return first
|
||||
}
|
||||
|
||||
func (g *Gomu) cyclePanels2() Panel {
|
||||
first := g.panels[0]
|
||||
second := g.panels[1]
|
||||
if first.HasFocus() {
|
||||
g.setFocusPanel(second)
|
||||
g.prevPanel = second
|
||||
return second
|
||||
} else if second.HasFocus() {
|
||||
g.setFocusPanel(first)
|
||||
g.prevPanel = first
|
||||
return first
|
||||
} else {
|
||||
g.setFocusPanel(first)
|
||||
g.prevPanel = first
|
||||
return first
|
||||
}
|
||||
}
|
||||
|
||||
// Changes title and border color when focusing panel
|
||||
// and changes color of the previous panel as well
|
||||
func (g *Gomu) setFocusPanel(panel Panel) {
|
||||
@ -102,7 +120,9 @@ func (g *Gomu) setFocusPanel(panel Panel) {
|
||||
return
|
||||
}
|
||||
|
||||
g.setUnfocusPanel(g.prevPanel)
|
||||
if g.prevPanel != panel {
|
||||
g.setUnfocusPanel(g.prevPanel)
|
||||
}
|
||||
}
|
||||
|
||||
// Safely write the IsSuspend state, IsSuspend is used to indicate if we
|
||||
@ -141,7 +161,7 @@ func (g *Gomu) setUnfocusPanel(panel Panel) {
|
||||
func (g *Gomu) quit(args Args) error {
|
||||
|
||||
if !*args.empty {
|
||||
err := gomu.queue.saveQueue()
|
||||
err := gomu.queue.saveQueue(true)
|
||||
if err != nil {
|
||||
return tracerr.Wrap(err)
|
||||
}
|
||||
|
1
main.go
1
main.go
@ -30,7 +30,6 @@ func setupLog() {
|
||||
log.Fatalf("Error opening file %s", logFile)
|
||||
}
|
||||
|
||||
|
||||
log.SetOutput(file)
|
||||
log.SetFlags(log.Ldate | log.Ltime | log.Llongfile)
|
||||
}
|
||||
|
69
player.go
69
player.go
@ -24,13 +24,16 @@ type Player struct {
|
||||
done chan bool
|
||||
|
||||
// to control the _volume internally
|
||||
_volume *effects.Volume
|
||||
ctrl *beep.Ctrl
|
||||
volume float64
|
||||
resampler *beep.Resampler
|
||||
position time.Duration
|
||||
length time.Duration
|
||||
currentSong *AudioFile
|
||||
_volume *effects.Volume
|
||||
ctrl *beep.Ctrl
|
||||
volume float64
|
||||
resampler *beep.Resampler
|
||||
position time.Duration
|
||||
length time.Duration
|
||||
currentSong *AudioFile
|
||||
streamSeekCloser beep.StreamSeekCloser
|
||||
// is used to send progress
|
||||
i int
|
||||
}
|
||||
|
||||
func newPlayer() *Player {
|
||||
@ -58,21 +61,24 @@ func (p *Player) run(currSong *AudioFile) error {
|
||||
|
||||
defer f.Close()
|
||||
|
||||
streamer, format, err := mp3.Decode(f)
|
||||
stream, format, err := mp3.Decode(f)
|
||||
|
||||
p.streamSeekCloser = stream
|
||||
p.format = &format
|
||||
|
||||
if err != nil {
|
||||
return tracerr.Wrap(err)
|
||||
}
|
||||
|
||||
defer streamer.Close()
|
||||
defer stream.Close()
|
||||
|
||||
// song duration
|
||||
p.length = format.SampleRate.D(streamer.Len())
|
||||
p.length = p.format.SampleRate.D(p.streamSeekCloser.Len())
|
||||
|
||||
if !p.hasInit {
|
||||
|
||||
err := speaker.
|
||||
Init(format.SampleRate, format.SampleRate.N(time.Second/10))
|
||||
Init(p.format.SampleRate, p.format.SampleRate.N(time.Second/10))
|
||||
|
||||
if err != nil {
|
||||
return tracerr.Wrap(err)
|
||||
@ -81,7 +87,6 @@ func (p *Player) run(currSong *AudioFile) error {
|
||||
p.hasInit = true
|
||||
}
|
||||
|
||||
p.format = &format
|
||||
p.currentSong = currSong
|
||||
|
||||
popupMessage := fmt.Sprintf("%s\n\n[ %s ]",
|
||||
@ -92,7 +97,7 @@ func (p *Player) run(currSong *AudioFile) error {
|
||||
done := make(chan bool, 1)
|
||||
p.done = done
|
||||
|
||||
sstreamer := beep.Seq(streamer, beep.Callback(func() {
|
||||
sstreamer := beep.Seq(p.streamSeekCloser, beep.Callback(func() {
|
||||
done <- true
|
||||
}))
|
||||
|
||||
@ -118,18 +123,9 @@ func (p *Player) run(currSong *AudioFile) error {
|
||||
p._volume = volume
|
||||
speaker.Play(p._volume)
|
||||
|
||||
position := func() time.Duration {
|
||||
return format.SampleRate.D(streamer.Position())
|
||||
}
|
||||
|
||||
p.position = position()
|
||||
p.position = p.getPosition()
|
||||
p.isRunning = true
|
||||
|
||||
if p.isLoop {
|
||||
gomu.queue.enqueue(currSong)
|
||||
gomu.app.Draw()
|
||||
}
|
||||
|
||||
gomu.playingBar.newProgress(currSong.name, int(p.length.Seconds()))
|
||||
|
||||
go func() {
|
||||
@ -139,13 +135,18 @@ func (p *Player) run(currSong *AudioFile) error {
|
||||
}()
|
||||
|
||||
// is used to send progress
|
||||
i := 0
|
||||
p.i = 0
|
||||
|
||||
next:
|
||||
for {
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
if p.isLoop {
|
||||
gomu.queue.enqueue(currSong)
|
||||
gomu.app.Draw()
|
||||
}
|
||||
|
||||
close(done)
|
||||
p.position = 0
|
||||
p.isRunning = false
|
||||
@ -173,14 +174,14 @@ next:
|
||||
continue
|
||||
}
|
||||
|
||||
i++
|
||||
p.i++
|
||||
gomu.playingBar.progress <- 1
|
||||
|
||||
speaker.Lock()
|
||||
p.position = position()
|
||||
p.position = p.getPosition()
|
||||
speaker.Unlock()
|
||||
|
||||
if i > gomu.playingBar.full {
|
||||
if p.i > gomu.playingBar.full {
|
||||
break next
|
||||
}
|
||||
|
||||
@ -248,6 +249,7 @@ func (p *Player) skip() {
|
||||
}
|
||||
|
||||
p.ctrl.Streamer = nil
|
||||
p.streamSeekCloser.Close()
|
||||
p.done <- true
|
||||
}
|
||||
|
||||
@ -290,3 +292,16 @@ func volToHuman(volume float64) int {
|
||||
func absVolume(volume int) float64 {
|
||||
return (float64(volume) - 100) / 10
|
||||
}
|
||||
|
||||
func (p *Player) getPosition() time.Duration {
|
||||
return p.format.SampleRate.D(p.streamSeekCloser.Position())
|
||||
}
|
||||
|
||||
//seek is the function to move forward and rewind
|
||||
func (p *Player) seek(pos int) error {
|
||||
speaker.Lock()
|
||||
defer speaker.Unlock()
|
||||
err := p.streamSeekCloser.Seek(pos * int(p.format.SampleRate))
|
||||
p.i = pos - 1
|
||||
return err
|
||||
}
|
||||
|
165
playlist.go
165
playlist.go
@ -21,7 +21,7 @@ import (
|
||||
"github.com/ztrue/tracerr"
|
||||
)
|
||||
|
||||
// Playlist and mp3 files are represented with this struct
|
||||
// AudioFile is representing directories and mp3 files
|
||||
// if isAudioFile equals to false it is a directory
|
||||
type AudioFile struct {
|
||||
name string
|
||||
@ -43,6 +43,11 @@ type Playlist struct {
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
var (
|
||||
yankFile *AudioFile
|
||||
isYanked bool
|
||||
)
|
||||
|
||||
func (p *Playlist) help() []string {
|
||||
|
||||
return []string{
|
||||
@ -58,6 +63,8 @@ func (p *Playlist) help() []string {
|
||||
"y query audio from youtube and download",
|
||||
"r refresh",
|
||||
"R rename",
|
||||
"y yank file",
|
||||
"p paste file",
|
||||
"/ find in playlist",
|
||||
}
|
||||
|
||||
@ -146,6 +153,8 @@ func newPlaylist(args Args) *Playlist {
|
||||
'h': "close_node",
|
||||
'r': "refresh",
|
||||
'R': "rename",
|
||||
'y': "yank",
|
||||
'p': "paste",
|
||||
'/': "playlist_search",
|
||||
}
|
||||
|
||||
@ -192,6 +201,7 @@ func (p *Playlist) deleteSong(audioFile *AudioFile) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
audioName := getName(audioFile.path)
|
||||
err := os.Remove(audioFile.path)
|
||||
|
||||
if err != nil {
|
||||
@ -204,8 +214,18 @@ func (p *Playlist) deleteSong(audioFile *AudioFile) (err error) {
|
||||
|
||||
defaultTimedPopup(" Success ",
|
||||
audioFile.name+"\nhas been deleted successfully")
|
||||
|
||||
p.refresh()
|
||||
|
||||
//Here we remove the song from queue
|
||||
songPaths := gomu.queue.getItems()
|
||||
if audioName == getName(gomu.player.currentSong.name) {
|
||||
gomu.player.skip()
|
||||
}
|
||||
for i, songPath := range songPaths {
|
||||
if strings.Contains(songPath, audioName) {
|
||||
gomu.queue.deleteItem(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
@ -269,7 +289,11 @@ func (p *Playlist) addAllToQueue(root *tview.TreeNode) {
|
||||
|
||||
for _, v := range childrens {
|
||||
currNode := v.GetReference().(*AudioFile)
|
||||
gomu.queue.enqueue(currNode)
|
||||
if currNode.isAudioFile {
|
||||
if currNode != gomu.player.currentSong {
|
||||
gomu.queue.enqueue(currNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -285,7 +309,7 @@ func (p *Playlist) refresh() {
|
||||
|
||||
populate(root, root.GetReference().(*AudioFile).path)
|
||||
|
||||
root.Walk(func(node, parent *tview.TreeNode) bool {
|
||||
root.Walk(func(node, _ *tview.TreeNode) bool {
|
||||
|
||||
// to preserve previously highlighted node
|
||||
if node.GetText() == prevFileName {
|
||||
@ -504,6 +528,11 @@ func (p *Playlist) rename(newName string) error {
|
||||
return tracerr.Wrap(err)
|
||||
}
|
||||
|
||||
audio.path = newPath
|
||||
gomu.queue.saveQueue(false)
|
||||
gomu.queue.clearQueue()
|
||||
gomu.queue.loadQueue()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -592,18 +621,15 @@ func ytdl(url string, selPlaylist *tview.TreeNode) error {
|
||||
return tracerr.Wrap(err)
|
||||
}
|
||||
|
||||
dir := viper.GetString("general.music_dir")
|
||||
|
||||
selAudioFile := selPlaylist.GetReference().(*AudioFile)
|
||||
selPlaylistName := selAudioFile.name
|
||||
dir := selAudioFile.path
|
||||
|
||||
defaultTimedPopup(" Ytdl ", "Downloading")
|
||||
|
||||
// specify the output path for ytdl
|
||||
outputDir := fmt.Sprintf(
|
||||
"%s/%s/%%(title)s.%%(ext)s",
|
||||
dir,
|
||||
selPlaylistName)
|
||||
"%s/%%(title)s.%%(ext)s",
|
||||
dir)
|
||||
|
||||
args := []string{
|
||||
"--extract-audio",
|
||||
@ -611,6 +637,8 @@ func ytdl(url string, selPlaylist *tview.TreeNode) error {
|
||||
"mp3",
|
||||
"--output",
|
||||
outputDir,
|
||||
// "--cookies",
|
||||
// "~/Downloads/youtube.com_cookies.txt",
|
||||
url,
|
||||
}
|
||||
|
||||
@ -632,7 +660,7 @@ func ytdl(url string, selPlaylist *tview.TreeNode) error {
|
||||
return tracerr.Wrap(err)
|
||||
}
|
||||
|
||||
playlistPath := path.Join(expandTilde(dir), selPlaylistName)
|
||||
playlistPath := dir
|
||||
audioPath := extractFilePath(stdout.Bytes(), playlistPath)
|
||||
|
||||
err = appendFile(expandTilde(viper.GetString("general.history_path")), url+"\n")
|
||||
@ -645,10 +673,9 @@ func ytdl(url string, selPlaylist *tview.TreeNode) error {
|
||||
return tracerr.Wrap(err)
|
||||
}
|
||||
|
||||
downloadFinishedMessage := fmt.Sprintf("Finished downloading\n%s",
|
||||
getName(audioPath))
|
||||
|
||||
downloadFinishedMessage := fmt.Sprintf("Finished downloading\n%s", getName(audioPath))
|
||||
defaultTimedPopup(" Ytdl ", downloadFinishedMessage)
|
||||
gomu.app.Draw()
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -664,19 +691,21 @@ func populate(root *tview.TreeNode, rootPath string) error {
|
||||
|
||||
for _, file := range files {
|
||||
|
||||
path := filepath.Join(rootPath, file.Name())
|
||||
f, err := os.Open(path)
|
||||
|
||||
path, err := filepath.EvalSymlinks(filepath.Join(rootPath, file.Name()))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
defer f.Close()
|
||||
|
||||
songName := getName(file.Name())
|
||||
child := tview.NewTreeNode(songName)
|
||||
|
||||
if !file.IsDir() {
|
||||
if file.Mode().IsRegular() {
|
||||
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
filetype, err := getFileContentType(f)
|
||||
|
||||
@ -689,17 +718,10 @@ func populate(root *tview.TreeNode, rootPath string) error {
|
||||
continue
|
||||
}
|
||||
|
||||
audioLength, err := getLength(path)
|
||||
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
audioFile := &AudioFile{
|
||||
name: songName,
|
||||
path: path,
|
||||
isAudioFile: true,
|
||||
length: audioLength,
|
||||
node: child,
|
||||
parent: root,
|
||||
}
|
||||
@ -716,13 +738,14 @@ func populate(root *tview.TreeNode, rootPath string) error {
|
||||
|
||||
}
|
||||
|
||||
if file.IsDir() {
|
||||
if file.IsDir() || file.Mode()&os.ModeSymlink != 0 {
|
||||
|
||||
audioFile := &AudioFile{
|
||||
name: songName,
|
||||
path: path,
|
||||
node: child,
|
||||
parent: root,
|
||||
name: songName,
|
||||
path: path,
|
||||
isAudioFile: false,
|
||||
node: child,
|
||||
parent: root,
|
||||
}
|
||||
|
||||
displayText := songName
|
||||
@ -743,3 +766,81 @@ func populate(root *tview.TreeNode, rootPath string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Playlist) yank() error {
|
||||
yankFile = p.getCurrentFile()
|
||||
if yankFile == nil {
|
||||
isYanked = false
|
||||
defaultTimedPopup(" Error! ", "No file has been yanked.")
|
||||
return nil
|
||||
}
|
||||
if yankFile.node == p.GetRoot() {
|
||||
isYanked = false
|
||||
defaultTimedPopup(" Error! ", "Please don't yank the root directory.")
|
||||
return nil
|
||||
}
|
||||
isYanked = true
|
||||
defaultTimedPopup(" Success ", yankFile.name+"\n has been yanked successfully.")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Playlist) paste() error {
|
||||
if isYanked {
|
||||
isYanked = false
|
||||
oldPathDir, oldPathFileName := filepath.Split(yankFile.path)
|
||||
pasteFile := p.getCurrentFile()
|
||||
if pasteFile.isAudioFile {
|
||||
newPathDir, _ := filepath.Split(pasteFile.path)
|
||||
if oldPathDir == newPathDir {
|
||||
return nil
|
||||
} else {
|
||||
newPathFull := filepath.Join(newPathDir, oldPathFileName)
|
||||
err := os.Rename(yankFile.path, newPathFull)
|
||||
if err != nil {
|
||||
defaultTimedPopup(" Error ", yankFile.name+"\n has not been pasted.")
|
||||
return tracerr.Wrap(err)
|
||||
}
|
||||
defaultTimedPopup(" Success ", yankFile.name+"\n has been pasted to\n"+pasteFile.name)
|
||||
}
|
||||
} else {
|
||||
newPathDir := pasteFile.path
|
||||
if oldPathDir == newPathDir {
|
||||
return nil
|
||||
} else {
|
||||
newPathFull := filepath.Join(newPathDir, oldPathFileName)
|
||||
err := os.Rename(yankFile.path, newPathFull)
|
||||
if err != nil {
|
||||
defaultTimedPopup(" Error ", yankFile.name+"\n has not been pasted.")
|
||||
return tracerr.Wrap(err)
|
||||
}
|
||||
defaultTimedPopup(" Success ", yankFile.name+"\n has been pasted to\n"+pasteFile.name)
|
||||
}
|
||||
}
|
||||
|
||||
p.refresh()
|
||||
gomu.queue.updateQueueNames()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//populateAudioLength is the most time consuming part of startup,
|
||||
//so here we initialize it separately
|
||||
func populateAudioLength(root *tview.TreeNode) error {
|
||||
root.Walk(func(node *tview.TreeNode, _ *tview.TreeNode) bool {
|
||||
audioFile := node.GetReference().(*AudioFile)
|
||||
if audioFile.isAudioFile {
|
||||
audioLength, err := getLength(audioFile.path)
|
||||
if err != nil {
|
||||
logError(err)
|
||||
return false
|
||||
}
|
||||
audioFile.length = audioLength
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
gomu.queue.updateTitle()
|
||||
return nil
|
||||
}
|
||||
|
89
popup.go
89
popup.go
@ -7,7 +7,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
@ -99,13 +99,13 @@ func confirmationPopup(
|
||||
modal.SetInputCapture(func(e *tcell.EventKey) *tcell.EventKey {
|
||||
switch e.Rune() {
|
||||
case 'h':
|
||||
return tcell.NewEventKey(tcell.KeyLeft, 0, tcell.ModNone)
|
||||
return tcell.NewEventKey(tcell.KeyLeft, 0, tcell.ModNone)
|
||||
case 'j':
|
||||
return tcell.NewEventKey(tcell.KeyLeft, 0, tcell.ModNone)
|
||||
return tcell.NewEventKey(tcell.KeyLeft, 0, tcell.ModNone)
|
||||
case 'k':
|
||||
return tcell.NewEventKey(tcell.KeyRight, 0, tcell.ModNone)
|
||||
return tcell.NewEventKey(tcell.KeyRight, 0, tcell.ModNone)
|
||||
case 'l':
|
||||
return tcell.NewEventKey(tcell.KeyRight, 0, tcell.ModNone)
|
||||
return tcell.NewEventKey(tcell.KeyRight, 0, tcell.ModNone)
|
||||
}
|
||||
return e
|
||||
})
|
||||
@ -163,15 +163,15 @@ func timedPopup(
|
||||
|
||||
box := tview.NewFrame(textView).SetBorders(1, 0, 0, 0, 0, 0)
|
||||
box.SetTitle(title).SetBorder(true).SetBackgroundColor(gomu.colors.popup)
|
||||
popupId := fmt.Sprintf("%s %d", "timeout-popup", popupCounter)
|
||||
popupID := fmt.Sprintf("%s %d", "timeout-popup", popupCounter)
|
||||
|
||||
popupCounter++
|
||||
gomu.pages.AddPage(popupId, topRight(box, width, height), true, true)
|
||||
gomu.pages.AddPage(popupID, topRight(box, width, height), true, true)
|
||||
gomu.app.SetFocus(gomu.prevPanel.(tview.Primitive))
|
||||
|
||||
go func() {
|
||||
time.Sleep(timeout)
|
||||
gomu.pages.RemovePage(popupId)
|
||||
gomu.pages.RemovePage(popupID)
|
||||
gomu.app.Draw()
|
||||
|
||||
// timed popup shouldn't get focused
|
||||
@ -225,6 +225,10 @@ func helpPopup(panel Panel) {
|
||||
"q quit",
|
||||
"+ volume up",
|
||||
"- volume down",
|
||||
"f forward 10 seconds",
|
||||
"F forward 60 seconds",
|
||||
"b rewind 10 seconds",
|
||||
"B rewind 60 seconds",
|
||||
"? toggle help",
|
||||
}
|
||||
|
||||
@ -235,7 +239,7 @@ func helpPopup(panel Panel) {
|
||||
SetSelectedTextColor(gomu.colors.accent)
|
||||
|
||||
for _, v := range append(helpText, genHelp...) {
|
||||
list.AddItem(" " + v, "", 0, nil)
|
||||
list.AddItem(" "+v, "", 0, nil)
|
||||
}
|
||||
|
||||
prev := func() {
|
||||
@ -279,8 +283,8 @@ func downloadMusicPopup(selPlaylist *tview.TreeNode) {
|
||||
|
||||
re := regexp.MustCompile(`^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$`)
|
||||
|
||||
popupId := "download-input-popup"
|
||||
input := newInputPopup(popupId, " Download ", "Url: ")
|
||||
popupID := "download-input-popup"
|
||||
input := newInputPopup(popupID, " Download ", "Url: ", "")
|
||||
|
||||
input.SetDoneFunc(func(key tcell.Key) {
|
||||
|
||||
@ -316,8 +320,8 @@ func downloadMusicPopup(selPlaylist *tview.TreeNode) {
|
||||
// Input popup that takes the name of directory to be created
|
||||
func createPlaylistPopup() {
|
||||
|
||||
popupId := "mkdir-input-popup"
|
||||
input := newInputPopup(popupId, " New Playlist ", "Enter playlist name: ")
|
||||
popupID := "mkdir-input-popup"
|
||||
input := newInputPopup(popupID, " New Playlist ", "Enter playlist name: ", "")
|
||||
|
||||
input.SetDoneFunc(func(key tcell.Key) {
|
||||
|
||||
@ -389,26 +393,25 @@ func searchPopup(stringsToMatch []string, handler func(selected string)) {
|
||||
|
||||
for _, match := range matches {
|
||||
var text strings.Builder
|
||||
matchrune := [] rune(match.Str)
|
||||
matchruneIndexes := match.MatchedIndexes
|
||||
for i:=0; i < len(match.MatchedIndexes); i++{
|
||||
matchruneIndexes[i] = utf8.RuneCountInString(match.Str[0:match.MatchedIndexes[i]])
|
||||
}
|
||||
for i :=0; i < len(matchrune); i++ {
|
||||
if contains(i, matchruneIndexes) {
|
||||
textwithcolor := fmt.Sprintf(highlight, matchrune[i])
|
||||
for _,j := range textwithcolor {
|
||||
text.WriteRune(j)
|
||||
}
|
||||
} else{
|
||||
text.WriteRune(matchrune[i])
|
||||
}
|
||||
}
|
||||
matchrune := []rune(match.Str)
|
||||
matchruneIndexes := match.MatchedIndexes
|
||||
for i := 0; i < len(match.MatchedIndexes); i++ {
|
||||
matchruneIndexes[i] = utf8.RuneCountInString(match.Str[0:match.MatchedIndexes[i]])
|
||||
}
|
||||
for i := 0; i < len(matchrune); i++ {
|
||||
if contains(i, matchruneIndexes) {
|
||||
textwithcolor := fmt.Sprintf(highlight, matchrune[i])
|
||||
for _, j := range textwithcolor {
|
||||
text.WriteRune(j)
|
||||
}
|
||||
} else {
|
||||
text.WriteRune(matchrune[i])
|
||||
}
|
||||
}
|
||||
list.AddItem(text.String(), match.Str, 0, nil)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
input.SetInputCapture(func(e *tcell.EventKey) *tcell.EventKey {
|
||||
|
||||
switch e.Key() {
|
||||
@ -462,7 +465,7 @@ func searchPopup(stringsToMatch []string, handler func(selected string)) {
|
||||
}
|
||||
|
||||
// Creates new popup widget with default settings
|
||||
func newInputPopup(popupId, title, label string) *tview.InputField {
|
||||
func newInputPopup(popupID, title, label string, text string) *tview.InputField {
|
||||
|
||||
inputField := tview.NewInputField().
|
||||
SetLabel(label).
|
||||
@ -476,8 +479,10 @@ func newInputPopup(popupId, title, label string) *tview.InputField {
|
||||
SetBorder(true).
|
||||
SetBorderPadding(1, 0, 2, 2)
|
||||
|
||||
inputField.SetText(text)
|
||||
|
||||
gomu.pages.
|
||||
AddPage(popupId, center(inputField, 60, 5), true, true)
|
||||
AddPage(popupID, center(inputField, 60, 5), true, true)
|
||||
|
||||
gomu.popups.push(inputField)
|
||||
|
||||
@ -486,8 +491,8 @@ func newInputPopup(popupId, title, label string) *tview.InputField {
|
||||
|
||||
func renamePopup(node *AudioFile) {
|
||||
|
||||
popupId := "rename-input-popup"
|
||||
input := newInputPopup(popupId, " Rename ", "New name: ")
|
||||
popupID := "rename-input-popup"
|
||||
input := newInputPopup(popupID, " Rename ", "New name: ", node.name)
|
||||
input.SetInputCapture(func(e *tcell.EventKey) *tcell.EventKey {
|
||||
|
||||
switch e.Key() {
|
||||
@ -501,12 +506,26 @@ func renamePopup(node *AudioFile) {
|
||||
defaultTimedPopup(" Error ", err.Error())
|
||||
logError(err)
|
||||
}
|
||||
gomu.pages.RemovePage(popupId)
|
||||
gomu.pages.RemovePage(popupID)
|
||||
gomu.popups.pop()
|
||||
gomu.playlist.refresh()
|
||||
// gomu.queue.saveQueue()
|
||||
// gomu.queue.clearQueue()
|
||||
// gomu.queue.loadQueue()
|
||||
gomu.queue.updateQueueNames()
|
||||
gomu.setFocusPanel(gomu.playlist)
|
||||
gomu.prevPanel = gomu.playlist
|
||||
// gomu.playlist.setHighlight(node.node)
|
||||
root := gomu.playlist.GetRoot()
|
||||
root.Walk(func(node, _ *tview.TreeNode) bool {
|
||||
if strings.Contains(node.GetText(), newName) {
|
||||
gomu.playlist.setHighlight(node)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
case tcell.KeyEsc:
|
||||
gomu.pages.RemovePage(popupId)
|
||||
gomu.pages.RemovePage(popupID)
|
||||
gomu.popups.pop()
|
||||
}
|
||||
|
||||
|
71
queue.go
71
queue.go
@ -16,6 +16,7 @@ import (
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/ztrue/tracerr"
|
||||
)
|
||||
|
||||
@ -95,8 +96,25 @@ func (q *Queue) updateTitle() string {
|
||||
count = "song"
|
||||
}
|
||||
|
||||
title := fmt.Sprintf("─ Queue ───┤ %d %s | %s ├",
|
||||
len(q.items), count, fmtTime)
|
||||
var loop string
|
||||
isEmoji := viper.GetBool("general.emoji")
|
||||
|
||||
if q.isLoop {
|
||||
if isEmoji {
|
||||
loop = viper.GetString("emoji.loop")
|
||||
} else {
|
||||
loop = "Loop"
|
||||
}
|
||||
} else {
|
||||
if isEmoji {
|
||||
loop = viper.GetString("emoji.noloop")
|
||||
} else {
|
||||
loop = "No loop"
|
||||
}
|
||||
}
|
||||
|
||||
title := fmt.Sprintf("─ Queue ───┤ %d %s | %s | %s ├",
|
||||
len(q.items), count, fmtTime, loop)
|
||||
|
||||
q.SetTitle(title)
|
||||
|
||||
@ -136,20 +154,27 @@ func (q *Queue) dequeue() (*AudioFile, error) {
|
||||
// Add item to the list and returns the length of the queue
|
||||
func (q *Queue) enqueue(audioFile *AudioFile) (int, error) {
|
||||
|
||||
if !gomu.player.isRunning && "false" == os.Getenv("TEST") {
|
||||
//this is to fix the problem bulk_add when paused
|
||||
if !gomu.player.hasInit {
|
||||
if !gomu.player.isRunning && os.Getenv("TEST") == "false" {
|
||||
|
||||
gomu.player.isRunning = true
|
||||
gomu.player.isRunning = true
|
||||
|
||||
go func() {
|
||||
go func() {
|
||||
|
||||
if err := gomu.player.run(audioFile); err != nil {
|
||||
logError(err)
|
||||
}
|
||||
if err := gomu.player.run(audioFile); err != nil {
|
||||
logError(err)
|
||||
}
|
||||
|
||||
}()
|
||||
}()
|
||||
|
||||
return q.GetItemCount(), nil
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if !audioFile.isAudioFile {
|
||||
return q.GetItemCount(), nil
|
||||
|
||||
}
|
||||
|
||||
q.items = append(q.items, audioFile)
|
||||
@ -186,11 +211,25 @@ func (q *Queue) getItems() []string {
|
||||
}
|
||||
|
||||
// Save the current queue
|
||||
func (q *Queue) saveQueue() error {
|
||||
func (q *Queue) saveQueue(isQuit bool) error {
|
||||
|
||||
songPaths := q.getItems()
|
||||
var content strings.Builder
|
||||
|
||||
if gomu.player.hasInit && isQuit {
|
||||
currentSongPath := gomu.player.currentSong.path
|
||||
currentSongInQueue := false
|
||||
for _, songPath := range songPaths {
|
||||
if getName(songPath) == getName(currentSongPath) {
|
||||
currentSongInQueue = true
|
||||
}
|
||||
}
|
||||
if !currentSongInQueue {
|
||||
hashed := sha1Hex(getName(currentSongPath))
|
||||
content.WriteString(hashed + "\n")
|
||||
}
|
||||
}
|
||||
|
||||
for _, songPath := range songPaths {
|
||||
// hashed song name is easier to search through
|
||||
hashed := sha1Hex(getName(songPath))
|
||||
@ -360,7 +399,7 @@ func (q *Queue) shuffle() {
|
||||
q.AddItem(queueText, v.path, 0, nil)
|
||||
}
|
||||
|
||||
q.updateTitle()
|
||||
// q.updateTitle()
|
||||
|
||||
}
|
||||
|
||||
@ -421,3 +460,11 @@ func sha1Hex(input string) string {
|
||||
h.Write([]byte(input))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
//Modify the title of songs in queue
|
||||
func (q *Queue) updateQueueNames() error {
|
||||
q.saveQueue(false)
|
||||
q.clearQueue()
|
||||
q.loadQueue()
|
||||
return nil
|
||||
}
|
||||
|
45
start.go
45
start.go
@ -18,7 +18,7 @@ import (
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Created so we can keep track of childrens in slices
|
||||
// Panel is used to keep track of childrens in slices
|
||||
type Panel interface {
|
||||
HasFocus() bool
|
||||
SetBorderColor(color tcell.Color) *tview.Box
|
||||
@ -29,9 +29,9 @@ type Panel interface {
|
||||
}
|
||||
|
||||
const (
|
||||
CONFIG_PATH = ".config/gomu/config"
|
||||
HISTORY_PATH = "~/.local/share/gomu/urls"
|
||||
MUSIC_PATH = "~/music"
|
||||
configPath = ".config/gomu/config"
|
||||
historyPath = "~/.local/share/gomu/urls"
|
||||
musicPath = "~/music"
|
||||
)
|
||||
|
||||
// Reads config file and sets the options
|
||||
@ -43,6 +43,7 @@ general:
|
||||
confirm_bulk_add: true
|
||||
confirm_on_exit: true
|
||||
load_prev_queue: true
|
||||
queue_loop: true
|
||||
# change this to directory that contains mp3 files
|
||||
music_dir: ~/music
|
||||
# url history of downloaded audio will be saved here
|
||||
@ -60,7 +61,7 @@ general:
|
||||
# if you experiencing error using this invidious instance, you can change it
|
||||
# to another instance from this list:
|
||||
# https://github.com/iv-org/documentation/blob/master/Invidious-Instances.md
|
||||
invidious_instance: "https://invidious.namazso.eu"
|
||||
invidious_instance: "https://vid.puffyan.us"
|
||||
|
||||
# not all colors can be reproducible in terminal
|
||||
# changing hex colors may or may not produce expected result
|
||||
@ -79,7 +80,9 @@ color:
|
||||
emoji:
|
||||
playlist:
|
||||
file:
|
||||
|
||||
loop: ﯩ
|
||||
noloop:
|
||||
|
||||
# vi:ft=yaml
|
||||
`
|
||||
|
||||
@ -91,7 +94,7 @@ emoji:
|
||||
logError(err)
|
||||
}
|
||||
|
||||
defaultPath := path.Join(home, CONFIG_PATH)
|
||||
defaultPath := path.Join(home, configPath)
|
||||
|
||||
if err != nil {
|
||||
logError(err)
|
||||
@ -103,19 +106,18 @@ emoji:
|
||||
viper.AddConfigPath("$HOME/.config/gomu")
|
||||
|
||||
// General config
|
||||
viper.SetDefault("general.music_dir", MUSIC_PATH)
|
||||
viper.SetDefault("general.history_path", HISTORY_PATH)
|
||||
viper.SetDefault("general.music_dir", musicPath)
|
||||
viper.SetDefault("general.history_path", historyPath)
|
||||
viper.SetDefault("general.confirm_on_exit", true)
|
||||
viper.SetDefault("general.confirm_bulk_add", true)
|
||||
viper.SetDefault("general.popup_timeout", "5s")
|
||||
viper.SetDefault("general.volume", 100)
|
||||
viper.SetDefault("general.load_prev_queue", true)
|
||||
viper.SetDefault("general.use_emoji", false)
|
||||
viper.SetDefault("general.invidious_instance", "https://invidious.namazso.eu")
|
||||
viper.SetDefault("general.invidious_instance", "https://vid.puffyan.us")
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
|
||||
|
||||
// creates gomu config dir if does not exist
|
||||
if _, err := os.Stat(defaultPath); err != nil {
|
||||
if err := os.MkdirAll(home+"/.config/gomu", 0755); err != nil {
|
||||
@ -146,9 +148,9 @@ type Args struct {
|
||||
|
||||
func getArgs() Args {
|
||||
ar := Args{
|
||||
config: flag.String("config", CONFIG_PATH, "Specify config file"),
|
||||
config: flag.String("config", configPath, "Specify config file"),
|
||||
empty: flag.Bool("empty", false, "Open gomu with empty queue. Does not override previous queue"),
|
||||
music: flag.String("music", MUSIC_PATH, "Specify music directory"),
|
||||
music: flag.String("music", musicPath, "Specify music directory"),
|
||||
version: flag.Bool("version", false, "Print gomu version"),
|
||||
}
|
||||
flag.Parse()
|
||||
@ -199,6 +201,9 @@ func start(application *tview.Application, args Args) {
|
||||
gomu.setFocusPanel(gomu.playlist)
|
||||
gomu.prevPanel = gomu.playlist
|
||||
|
||||
gomu.player.isLoop = viper.GetBool("general.queue_loop")
|
||||
gomu.queue.isLoop = gomu.player.isLoop
|
||||
|
||||
if !*args.empty && viper.GetBool("general.load_prev_queue") {
|
||||
// load saved queue from previous session
|
||||
if err := gomu.queue.loadQueue(); err != nil {
|
||||
@ -207,14 +212,14 @@ func start(application *tview.Application, args Args) {
|
||||
}
|
||||
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGKILL)
|
||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
|
||||
go func() {
|
||||
sig := <-sigs
|
||||
errMsg := fmt.Sprintf("Received %s. Exiting program", sig.String())
|
||||
logError(errors.New(errMsg))
|
||||
err := gomu.quit(args)
|
||||
if err != nil {
|
||||
logError(errors.New("Unable to quit program"))
|
||||
logError(errors.New("unable to quit program"))
|
||||
}
|
||||
}()
|
||||
|
||||
@ -234,17 +239,23 @@ func start(application *tview.Application, args Args) {
|
||||
if strings.Contains(popupName, "confirmation-") {
|
||||
return e
|
||||
}
|
||||
gomu.cyclePanels()
|
||||
gomu.cyclePanels2()
|
||||
}
|
||||
|
||||
cmds := map[rune]string{
|
||||
'q': "quit",
|
||||
' ': "toggle_pause",
|
||||
'+': "volume_up",
|
||||
'=': "volume_up",
|
||||
'-': "volume_down",
|
||||
'_': "volume_down",
|
||||
'n': "skip",
|
||||
':': "command_search",
|
||||
'?': "toggle_help",
|
||||
'f': "forward",
|
||||
'F': "forward_fast",
|
||||
'b': "rewind",
|
||||
'B': "rewind_fast",
|
||||
}
|
||||
|
||||
for key, cmd := range cmds {
|
||||
@ -268,8 +279,10 @@ func start(application *tview.Application, args Args) {
|
||||
return false
|
||||
})
|
||||
|
||||
go populateAudioLength(gomu.playlist.GetRoot())
|
||||
// main loop
|
||||
if err := application.SetRoot(gomu.pages, true).SetFocus(gomu.playlist).Run(); err != nil {
|
||||
logError(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user