You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
250 lines
5.3 KiB
250 lines
5.3 KiB
package main |
|
|
|
import ( |
|
"flag" |
|
"fmt" |
|
"log" |
|
"net/http" |
|
_ "net/http/pprof" |
|
"os" |
|
"os/signal" |
|
"path/filepath" |
|
"runtime/pprof" |
|
"strings" |
|
"syscall" |
|
"time" |
|
|
|
"code.rocketnine.space/tslocum/ez" |
|
) |
|
|
|
const ( |
|
volumeBase = 2 |
|
|
|
versionInfo = `ditty - Audio player - v0.0.0 |
|
https://code.rocketnine.space/tslocum/ditty |
|
The MIT License (MIT) |
|
Copyright (c) 2020 Trevor Slocum <trevor@rocketnine.space> |
|
` |
|
) |
|
|
|
var ( |
|
configPath string |
|
printVersionInfo bool |
|
bufferSize time.Duration |
|
debugAddress string |
|
cpuProfile string |
|
streamFdInt int |
|
streamFd *os.File |
|
restrictLibrary string |
|
showHiddenFolders bool |
|
startingVolumeFlag int |
|
disableAutoplay bool |
|
disableMouse bool |
|
|
|
version = "0.0.0" |
|
done = make(chan bool) |
|
) |
|
|
|
func exit() { |
|
done <- true |
|
} |
|
|
|
func main() { |
|
log.SetFlags(0) |
|
|
|
flag.StringVar(&configPath, "config", "", "path to configuration file") |
|
flag.BoolVar(&printVersionInfo, "version", false, "print version information and exit") |
|
flag.StringVar(&config.Layout, "layout", defaultLayout, "layout of interface elements") |
|
flag.IntVar(&streamFdInt, "fd", -1, "stream audio to file descriptor") |
|
flag.IntVar(&startingVolumeFlag, "volume", -1, "initial volume level 0-100") |
|
flag.StringVar(&restrictLibrary, "restrict-library", "", "restrict library to path") |
|
flag.BoolVar(&disableAutoplay, "disable-autoplay", false, "disable automatically playing the queue") |
|
flag.BoolVar(&disableMouse, "disable-mouse", false, "disable mouse support") |
|
flag.DurationVar(&bufferSize, "buffer-size", defaultBufferSize, "audio buffer size") |
|
flag.StringVar(&debugAddress, "debug-address", "", "address to serve debug info") |
|
flag.BoolVar(&showHiddenFolders, "hidden-folders", false, "show hidden folders") |
|
flag.StringVar(&cpuProfile, "cpu-profile", "", "path to save CPU profiling") |
|
flag.Parse() |
|
|
|
if printVersionInfo { |
|
fmt.Print(strings.Replace(versionInfo, "0.0.0", version, 1)) |
|
return |
|
} |
|
|
|
if restrictLibrary != "" { |
|
var err error |
|
restrictLibrary, err = filepath.Abs(restrictLibrary) |
|
if err != nil { |
|
panic(fmt.Sprintf("failed to restrict library to %s: %s", restrictLibrary, err)) |
|
} |
|
} |
|
|
|
if debugAddress != "" { |
|
go func() { |
|
panic(http.ListenAndServe(debugAddress, nil)) |
|
}() |
|
} |
|
|
|
if cpuProfile != "" { |
|
f, err := os.Create(cpuProfile) |
|
if err != nil { |
|
panic(fmt.Sprintf("could not create CPU profile: %s", err)) |
|
} |
|
defer f.Close() |
|
|
|
if err := pprof.StartCPUProfile(f); err != nil { |
|
panic(fmt.Sprintf("could not start CPU profile: %s", err)) |
|
} |
|
defer pprof.StopCPUProfile() |
|
} |
|
|
|
if configPath == "" { |
|
var err error |
|
configPath, err = ez.DefaultConfigPath("ditty") |
|
if err != nil { |
|
panic(fmt.Sprintf("failed to determine default configuration file path: %s", err)) |
|
} |
|
} |
|
|
|
err := ez.Deserialize(config, configPath) |
|
if err != nil { |
|
panic(fmt.Sprintf("failed to read configuration file: %s", err)) |
|
} |
|
|
|
if startingVolumeFlag >= 0 { |
|
config.Volume = startingVolumeFlag |
|
} |
|
|
|
err = setKeyBinds() |
|
if err != nil { |
|
panic(fmt.Sprintf("failed to set keybinds: %s", err)) |
|
} |
|
|
|
err = initTUI() |
|
if err != nil { |
|
panic(fmt.Sprintf("failed to initialize terminal user interface: %s", err)) |
|
} |
|
defer app.HandlePanic() |
|
|
|
sigc := make(chan os.Signal, 1) |
|
signal.Notify(sigc, |
|
syscall.SIGINT, |
|
syscall.SIGTERM) |
|
go func() { |
|
<-sigc |
|
|
|
done <- true |
|
}() |
|
|
|
go func() { |
|
err := app.Run() |
|
if err != nil { |
|
panic(err) |
|
} |
|
|
|
done <- true |
|
}() |
|
|
|
var vol float64 |
|
if config.Volume == 0 { |
|
vol = -7.5 |
|
} else { |
|
vol = -7.5 + float64(7.5)*(float64(config.Volume)/float64(100)) |
|
if vol < -7.0 { |
|
vol = -7.0 |
|
} |
|
} |
|
setVolume(roundUnit(vol, 0.5)) |
|
|
|
startPath := strings.Join(flag.Args(), " ") |
|
if startPath == "" { |
|
if restrictLibrary != "" { |
|
startPath = restrictLibrary |
|
} else if config.Remember && config.Dir != "" { |
|
startPath = config.Dir |
|
} else { |
|
wd, err := os.Getwd() |
|
if err != nil || wd == "" { |
|
homeDir, err := os.UserHomeDir() |
|
if err == nil && homeDir != "" { |
|
startPath = homeDir |
|
} |
|
} else { |
|
startPath = wd |
|
} |
|
} |
|
} |
|
if startPath == "" { |
|
panic("supply a path to browse") |
|
} |
|
fileInfo, err := os.Stat(startPath) |
|
if err != nil { |
|
panic(err) |
|
} |
|
|
|
var playing bool |
|
if fileInfo.IsDir() { |
|
browseFolder(startPath) |
|
} else { |
|
browseFolder(filepath.Dir(startPath)) |
|
|
|
audioFile, err := openFile(strings.Join(flag.Args(), " "), nil) |
|
if err != nil { |
|
statusText = err.Error() |
|
app.QueueUpdateDraw(updateMain) |
|
} else { |
|
play(audioFile) |
|
playing = true |
|
} |
|
} |
|
|
|
if config.Remember && config.QueueFiles != nil { |
|
queueFiles = config.QueueFiles |
|
app.QueueUpdateDraw(func() { |
|
updateQueue() |
|
queueList.SetCurrentItem(config.QueuePlaying) |
|
if len(queueFiles) > 0 && !playing { |
|
pauseNext = true |
|
seekNext = config.AudioPosition |
|
go queueSelect(config.QueuePlaying) |
|
} |
|
}) |
|
} |
|
|
|
if config.Remember { |
|
go func() { |
|
t := time.NewTicker(5 * time.Minute) |
|
for range t.C { |
|
saveAppState() |
|
ez.Serialize(config, configPath) // Failing isn't an issue, this could be logged though |
|
} |
|
}() |
|
} |
|
|
|
defer func() { |
|
if app != nil { |
|
app.Stop() |
|
} |
|
}() |
|
|
|
t := time.NewTicker(time.Second) |
|
for { |
|
select { |
|
case <-done: |
|
if config.Remember { |
|
saveAppState() |
|
} else { |
|
clearAppState() |
|
} |
|
ez.Serialize(config, configPath) // Failing isn't an issue, this could be logged though |
|
|
|
if streamFd != nil { |
|
streamFd.Close() |
|
} |
|
|
|
return |
|
case <-t.C: |
|
updateStatus() |
|
} |
|
} |
|
}
|
|
|