169 lines
3.3 KiB
Go
169 lines
3.3 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"flag"
|
|
"io"
|
|
"log"
|
|
"math"
|
|
"math/rand"
|
|
"os"
|
|
"os/exec"
|
|
"os/signal"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/faiface/beep"
|
|
"github.com/faiface/beep/effects"
|
|
"github.com/faiface/beep/flac"
|
|
"github.com/faiface/beep/mp3"
|
|
"github.com/faiface/beep/speaker"
|
|
)
|
|
|
|
const (
|
|
SampleRate = beep.SampleRate(48000)
|
|
MaxRunTime = 1 * time.Hour
|
|
)
|
|
|
|
func xPrintIdle() (idle uint64) {
|
|
cmd := exec.Command("xprintidle")
|
|
cmd.Env = append(os.Environ(), "DISPLAY=:0")
|
|
var out bytes.Buffer
|
|
cmd.Stdout = &out
|
|
if err := cmd.Run(); err != nil {
|
|
log.Println(err)
|
|
} else {
|
|
s := string(out.Bytes())
|
|
if idle, err = strconv.ParseUint(strings.TrimSpace(s), 10, 64); err != nil {
|
|
log.Println(err)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func loadFile(path string) (s beep.StreamSeekCloser, format beep.Format, err error) {
|
|
for _, decoder := range []func(io.ReadCloser) (beep.StreamSeekCloser, beep.Format, error){flac.Decode, mp3.Decode} {
|
|
var fd *os.File
|
|
|
|
fd, err = os.Open(path)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Try decoding as FLAC
|
|
s, format, err = decoder(fd)
|
|
if err == nil {
|
|
break
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
|
|
if len(flag.Args()) < 1 {
|
|
log.Println("missing required argument: input file name")
|
|
return
|
|
}
|
|
|
|
rand.Seed(time.Now().Unix())
|
|
|
|
playlist := []beep.StreamSeekCloser{}
|
|
formats := []beep.Format{}
|
|
|
|
// Load playlist
|
|
for _, arg := range flag.Args() {
|
|
s, f, err := loadFile(arg)
|
|
if err != nil {
|
|
log.Printf("Unable to load %s: %s", arg, err)
|
|
continue
|
|
}
|
|
playlist = append(playlist, s)
|
|
formats = append(formats, f)
|
|
}
|
|
|
|
// Shuffle the playlist
|
|
rand.Shuffle(len(playlist), func(i, j int) {
|
|
playlist[i], playlist[j] = playlist[j], playlist[i]
|
|
formats[i], formats[j] = formats[j], formats[i]
|
|
})
|
|
|
|
// Create infinite stream
|
|
playedItem := -1
|
|
stream := beep.Iterate(func() beep.Streamer {
|
|
playedItem += 1
|
|
if playedItem >= len(playlist) {
|
|
playedItem = 0
|
|
}
|
|
|
|
// In case of loop, ensure we are at the beginning of the stream
|
|
playlist[playedItem].Seek(0)
|
|
|
|
// Resample if needed
|
|
if formats[playedItem].SampleRate != SampleRate {
|
|
return beep.Resample(3, formats[playedItem].SampleRate, SampleRate, playlist[playedItem])
|
|
} else {
|
|
return playlist[playedItem]
|
|
}
|
|
})
|
|
|
|
// Prepare sound player
|
|
speaker.Init(SampleRate, SampleRate.N(time.Second/10))
|
|
|
|
volume := &effects.Volume{stream, 10, -2, false}
|
|
speaker.Play(volume)
|
|
|
|
ticker := time.NewTicker(time.Second)
|
|
defer ticker.Stop()
|
|
|
|
launched := time.Now()
|
|
idle := xPrintIdle()
|
|
ntick := 0
|
|
|
|
// Prepare graceful shutdown
|
|
maxRun := time.After(MaxRunTime)
|
|
interrupt := make(chan os.Signal, 1)
|
|
signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM)
|
|
|
|
loop:
|
|
for {
|
|
select {
|
|
case <-maxRun:
|
|
break loop
|
|
case <-ticker.C:
|
|
ntick += 1
|
|
volume.Volume = -2 - math.Log(5/float64(ntick))/3
|
|
|
|
if xPrintIdle() < idle {
|
|
break loop
|
|
}
|
|
case <-interrupt:
|
|
break loop
|
|
}
|
|
}
|
|
|
|
// Tell parent process that it can launch the wake up procedure
|
|
if time.Since(launched) < MaxRunTime && time.Since(launched) > 60*time.Second {
|
|
if proc, err := os.FindProcess(os.Getppid()); err != nil {
|
|
log.Println(err)
|
|
} else if err := proc.Signal(syscall.SIGHUP); err != nil {
|
|
log.Println(err)
|
|
}
|
|
}
|
|
|
|
// Calm down music
|
|
for i := 0; i < 2000; i += 1 {
|
|
volume.Volume -= 0.001
|
|
time.Sleep(4 * time.Millisecond)
|
|
}
|
|
}
|