reveil/main.go

314 lines
7.6 KiB
Go

package main
import (
"bytes"
"flag"
"fmt"
"io"
"log"
"math"
"math/rand"
"os"
"os/exec"
"os/signal"
"path"
"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"
"github.com/faiface/beep/wav"
)
var (
MaxRunTime = 1 * time.Hour
ntick int64 = 0
)
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 speakToday() {
cmdSetVolume := exec.Command("amixer", "-D", "pulse", "set", "Master", fmt.Sprintf("%d%%", 50+50/(int64(MaxRunTime.Seconds()/3)/ntick+1)))
if err := cmdSetVolume.Run(); err != nil {
log.Println(err)
}
cmd0 := exec.Command("/home/nemunaire/scripts/wakeup/today.sh")
if err := cmd0.Run(); err != nil {
log.Println(err)
}
cmdSetBackVolume := exec.Command("amixer", "-D", "pulse", "set", "Master", "100%")
if err := cmdSetBackVolume.Run(); err != nil {
log.Println(err)
}
}
func speakWeather() {
var icon = "partly-cloudy-day"
cmd := exec.Command("/home/nemunaire/scripts/wakeup/ind_weather.sh")
var out bytes.Buffer
cmd.Stdout = &out
if err := cmd.Run(); err != nil {
log.Println(err)
} else {
icon = string(out.Bytes())
}
var preset = "pika-summer-forest.xml"
switch icon {
case "clear-day", "clear-night":
preset = "grassland.xml"
case "rain", "hail":
preset = "the-perfect-storm.xml"
case "snow", "sleet":
preset = "a-walk-in-the-cold.xml"
case "wind", "tornado":
preset = "desert-wind.xml"
case "fog":
preset = "silent-hill-fog-world.xml"
case "cloudy":
preset = "autumn-forest.xml"
case "thunderstorm":
preset = "heavy-thunderstorm-for-me.xml"
default:
preset = "autumn-forest.xml"
}
cmdAmbiant := exec.Command("python3", "ambient.py", path.Join("presets", preset))
cmdAmbiant.Dir = "/home/nemunaire/workspace/pyambientmixer"
if err := cmdAmbiant.Start(); err != nil {
log.Println(err)
}
cmdSetVolume := exec.Command("amixer", "-D", "pulse", "set", "Master", fmt.Sprintf("%d%%", 50+50/(int64(MaxRunTime.Seconds()/3)/ntick+1)))
if err := cmdSetVolume.Run(); err != nil {
log.Println(err)
}
cmd0 := exec.Command("/home/nemunaire/scripts/wakeup/today.sh")
if err := cmd0.Run(); err != nil {
log.Println(err)
}
cmd1 := exec.Command("/home/nemunaire/scripts/wakeup/weather.sh")
if err := cmd1.Run(); err != nil {
log.Println(err)
}
cmd2 := exec.Command("/home/nemunaire/scripts/wakeup/airparif.sh")
if err := cmd2.Run(); err != nil {
log.Println(err)
}
cmd3 := exec.Command("/home/nemunaire/scripts/wakeup/ratp-traffic.sh", "rers", "B")
if err := cmd3.Run(); err != nil {
log.Println(err)
}
if cmdAmbiant.Process != nil {
(*cmdAmbiant.Process).Kill()
}
cmdAmbiant.Process.Wait()
cmdSetBackVolume := exec.Command("amixer", "-D", "pulse", "set", "Master", "100%")
if err := cmdSetBackVolume.Run(); err != nil {
log.Println(err)
}
}
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, wav.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() {
var weatherTime = flag.Duration("weather", -1, "Speak weather?")
var noshuffle = flag.Bool("noshuffle", false, "Don't shuffle music order")
var ignoreidle = flag.Bool("ignoreidle", false, "Don't stop the reveil on idle detection change")
var sr = flag.Int("samplerate", 44100, "Samplerate for unifying output stream")
var claironTime = flag.Duration("clairon", -1, "Time before running the wake up clairon song")
flag.DurationVar(&MaxRunTime, "maxruntime", MaxRunTime, "Maximum duration before auto exit")
flag.Parse()
if len(flag.Args()) < 1 {
log.Println("missing required argument: input file name")
return
}
rand.Seed(time.Now().UnixNano())
sampleRate := beep.SampleRate(*sr)
playlist := []beep.Streamer{}
formats := []beep.Format{}
// Load playlist
log.Println("Loading 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)
}
if noshuffle == nil || *noshuffle == false {
log.Println("Shuffling playlist...")
// 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]
})
}
var launched time.Time
var volume *effects.Volume
dontUpdateVolume := false
hasClaironed := claironTime == nil || *claironTime == -1
hasSpokeWeather := weatherTime == nil || *weatherTime == -1
// Create infinite stream
playedItem := -1
stream := beep.Iterate(func() beep.Streamer {
if !hasClaironed && time.Since(launched) >= *claironTime {
log.Println("clairon time!")
hasClaironed = true
sample, format, err := loadFile("/home/nemunaire/www/audio/miracle-morning/clairon-reveil.mp3")
if err == nil {
volume.Volume = 1
dontUpdateVolume = true
if format.SampleRate != sampleRate {
return beep.Resample(3, format.SampleRate, sampleRate, sample)
} else {
return sample
}
}
}
if !hasSpokeWeather && time.Since(launched) >= *weatherTime {
log.Println("weather time!")
hasSpokeWeather = true
return beep.Callback(speakWeather)
}
dontUpdateVolume = false
volume.Volume = -2 - math.Log(5/float64(ntick))/3
playedItem += 1
if playedItem >= len(playlist) {
playedItem = 0
}
if i, ok := playlist[playedItem].(beep.StreamSeekCloser); ok {
// In case of loop, ensure we are at the beginning of the stream
i.Seek(0)
}
// Resample if needed
log.Println("playing list item:", playedItem, "/", len(playlist))
if formats[playedItem].SampleRate != sampleRate {
return beep.Resample(3, formats[playedItem].SampleRate, sampleRate, playlist[playedItem])
} else {
return playlist[playedItem]
}
})
// Prepare sound player
log.Println("Initializing 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()
// 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
if !dontUpdateVolume {
volume.Volume = -2 - math.Log(5/float64(ntick))/3
}
if ignoreidle == nil || !*ignoreidle {
if idle < 60000 {
idle = xPrintIdle()
} else 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)
}
}