2022-03-08 01:17:24 +04:00
|
|
|
package radio
|
|
|
|
|
|
|
|
import (
|
2022-03-31 15:35:04 +04:00
|
|
|
"dwelling-radio/pkg/watcher"
|
2022-03-08 01:17:24 +04:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"os/exec"
|
|
|
|
"strings"
|
|
|
|
"time"
|
2022-03-31 15:35:04 +04:00
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
2022-03-08 01:17:24 +04:00
|
|
|
)
|
|
|
|
|
|
|
|
type IcecastStatusDTO struct {
|
|
|
|
Icestats struct {
|
|
|
|
ServerStartISO8601 string `json:"server_start_iso8601"`
|
|
|
|
ServerStartDate string `json:"server_start"`
|
|
|
|
Source struct {
|
|
|
|
Artist string `json:"artist"`
|
|
|
|
Title string `json:"title"`
|
|
|
|
ListenerPeak int `json:"listener_peak"`
|
|
|
|
Listeners int `json:"listeners"`
|
|
|
|
} `json:"source"`
|
|
|
|
} `json:"icestats"`
|
|
|
|
}
|
|
|
|
|
2022-03-30 18:54:50 +04:00
|
|
|
func (is *IcecastStatusDTO) Song() string {
|
|
|
|
return fmt.Sprintf("%s - %s", is.Icestats.Source.Artist, is.Icestats.Source.Title)
|
|
|
|
}
|
|
|
|
|
2022-03-08 01:17:24 +04:00
|
|
|
type IcecastStatus struct {
|
|
|
|
ServerStartISO8601 string `json:"server_start_iso8601"`
|
2022-03-31 02:16:15 +04:00
|
|
|
ServerStartDate string `json:"server_start_date"`
|
2022-03-08 01:17:24 +04:00
|
|
|
SongName string `json:"song"`
|
|
|
|
ListenerPeak int `json:"listener_peak"`
|
|
|
|
Listeners int `json:"listeners"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type Song struct {
|
|
|
|
Time string `json:"time"`
|
|
|
|
Artist string `json:"artist"`
|
|
|
|
Title string `json:"title"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func IcecastGetStatus(icecastURL string) (*IcecastStatus, error) {
|
|
|
|
resp, err := http.Get(icecastURL)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
iceStatDTO := &IcecastStatusDTO{}
|
|
|
|
|
|
|
|
if err := json.NewDecoder(resp.Body).Decode(iceStatDTO); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
iceStat := &IcecastStatus{
|
|
|
|
ServerStartISO8601: iceStatDTO.Icestats.ServerStartISO8601,
|
|
|
|
ServerStartDate: iceStatDTO.Icestats.ServerStartDate,
|
|
|
|
SongName: iceStatDTO.Song(),
|
|
|
|
ListenerPeak: iceStatDTO.Icestats.Source.ListenerPeak,
|
|
|
|
Listeners: iceStatDTO.Icestats.Source.Listeners,
|
|
|
|
}
|
|
|
|
|
|
|
|
return iceStat, nil
|
|
|
|
}
|
|
|
|
|
2022-03-30 20:21:18 +04:00
|
|
|
func IcecastLastPlayedSongs(lastNSongs int, playlistPath string) ([]Song, error) {
|
2022-03-31 15:35:04 +04:00
|
|
|
if len(lastPlayedCache) > 0 {
|
|
|
|
if lastNSongs > len(lastPlayedCache) {
|
|
|
|
lastNSongs = len(lastPlayedCache)
|
|
|
|
}
|
|
|
|
return lastPlayedCache[len(lastPlayedCache)-lastNSongs:], nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return lastPlayedSongs(lastNSongs, playlistPath)
|
|
|
|
}
|
|
|
|
|
|
|
|
func IcecastLastSong(playlistPath string) (Song, error) {
|
|
|
|
if len(lastPlayedCache) > 0 {
|
|
|
|
return lastPlayedCache[len(lastPlayedCache)-1], nil
|
|
|
|
}
|
|
|
|
|
|
|
|
songs, err := IcecastLastPlayedSongs(1, playlistPath)
|
|
|
|
if len(songs) == 0 {
|
|
|
|
return Song{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return songs[0], err
|
|
|
|
}
|
|
|
|
|
|
|
|
func lastPlayedSongs(lastNSongs int, playlistPath string) ([]Song, error) {
|
2022-03-08 01:17:24 +04:00
|
|
|
songs := make([]Song, 0)
|
|
|
|
|
2022-03-30 20:21:18 +04:00
|
|
|
cmd := fmt.Sprintf("tail -n%d %s | head -n-1 | cut -d'|' -f1,4", lastNSongs+1, playlistPath)
|
|
|
|
out, err := exec.Command("bash", "-c", cmd).CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
return songs, err
|
|
|
|
}
|
2022-03-08 01:17:24 +04:00
|
|
|
|
|
|
|
if len(out) == 0 {
|
2022-03-30 20:21:18 +04:00
|
|
|
return songs, nil
|
2022-03-08 01:17:24 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
songs_ := strings.Split(string(out), "\n")
|
|
|
|
|
|
|
|
for _, song := range songs_ {
|
|
|
|
ts := strings.Split(song, "|")
|
2022-03-10 00:10:00 +04:00
|
|
|
if len(ts) <= 1 {
|
|
|
|
continue
|
|
|
|
}
|
2022-03-31 02:16:34 +04:00
|
|
|
tim, _ := time.Parse("02/Jan/2006:15:04:05 -0700", ts[0])
|
2022-03-10 00:10:00 +04:00
|
|
|
at := strings.Split(ts[1], " - ")
|
2022-03-08 01:17:24 +04:00
|
|
|
songs = append(songs, Song{
|
2022-03-31 02:55:05 +04:00
|
|
|
Time: tim.UTC().Format("15:04-0700"),
|
2022-03-08 01:17:24 +04:00
|
|
|
Artist: at[0],
|
|
|
|
Title: at[1]})
|
|
|
|
}
|
|
|
|
|
2022-03-30 20:21:18 +04:00
|
|
|
return songs, nil
|
2022-03-08 01:17:24 +04:00
|
|
|
}
|
2022-03-31 15:35:04 +04:00
|
|
|
|
|
|
|
var playlistWatcher watcher.InotifyWatcher
|
|
|
|
var playlistFired chan uint32
|
|
|
|
var lastPlayedCache []Song
|
|
|
|
|
|
|
|
func IcecastWatchPlaylist(playlistPath string, lastNSongs int) error {
|
|
|
|
playlistWatcher, err := watcher.NewInotifyWatcher()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "cannot instantiate inotify watcher")
|
|
|
|
}
|
|
|
|
|
|
|
|
err = playlistWatcher.AddWatch(playlistPath, watcher.ModMask)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "cannot set a playlist to watch")
|
|
|
|
}
|
|
|
|
|
|
|
|
playlistFired = make(chan uint32)
|
|
|
|
|
|
|
|
playlistWatcher.WatchForMask(playlistFired, watcher.ModMask)
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-playlistFired:
|
|
|
|
songs, err := lastPlayedSongs(lastNSongs, playlistPath)
|
|
|
|
fmt.Println("do we get here?", err)
|
|
|
|
if err == nil && len(songs) > 0 {
|
|
|
|
lastPlayedCache = songs
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
songs, err := lastPlayedSongs(lastNSongs, playlistPath)
|
|
|
|
if err == nil && len(songs) > 0 {
|
|
|
|
lastPlayedCache = songs
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func IcecastWatchClose() {
|
|
|
|
playlistWatcher.Close()
|
|
|
|
}
|