diff --git a/cmd/main.go b/cmd/main.go index ca0a6f6..17bd065 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -6,8 +6,8 @@ import ( "os" "strconv" - "github.com/duncanleo/plex-dvr-hls/config" - "github.com/duncanleo/plex-dvr-hls/routes" + "plex-dvr-hls/config" + "plex-dvr-hls/routes" "github.com/gin-gonic/gin" ) diff --git a/config.example.json b/config.example.json index 7a17935..9fb5873 100644 --- a/config.example.json +++ b/config.example.json @@ -1,5 +1,15 @@ { "name": "Amazing Tuner", "encoder_profile": "cpu", - "tuner_count": 3 + "tuner_count": 3, + "plex_servers": [ + { + "endpoint": "http://your-plex-server-1:32400", + "token": "your-plex-token-1" + }, + { + "endpoint": "http://your-plex-server-2:32400", + "token": "your-plex-token-2" + } + ] } diff --git a/config/channels.go b/config/channels.go index 6ad5ce6..8e4a01b 100644 --- a/config/channels.go +++ b/config/channels.go @@ -2,12 +2,12 @@ package config import ( "encoding/json" + "errors" "log" "os" - "errors" "sync" - "github.com/fsnotify/fsnotify" + "github.com/fsnotify/fsnotify" m3uparser "github.com/pawanpaudel93/go-m3u-parser/m3uparser" ) @@ -24,8 +24,8 @@ type Channel struct { DisableTranscode bool `json:"disableTranscode"` // UserAgent is a custom UA string that will be used by FFMPEG to make requests to the stream URL. - UserAgent *string `json:"userAgent,omitempty"` - Icon *string `json:"icon,omitempty"` + UserAgent *string `json:"userAgent,omitempty"` + Icon *string `json:"icon,omitempty"` } var ( @@ -34,18 +34,18 @@ var ( ) func LoadChannelsFromPl(location string) error { - var userAgent = os.Getenv("UA") - if len(userAgent) == 0 { - userAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36" + var userAgent = os.Getenv("UA") + if len(userAgent) == 0 { + userAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36" } parser := m3uparser.M3uParser{UserAgent: userAgent, Timeout: 60} - parser.ParseM3u(location,true,true) + parser.ParseM3u(location, true, true) streams := parser.GetStreamsSlice() if len(streams) > 0 { - for _, st := range streams { + for _, st := range streams { ch := Channel{Name: st["title"].(string), URL: st["url"].(string), UserAgent: &userAgent} - Channels = append(Channels,ch) - } + Channels = append(Channels, ch) + } if len(Channels) > 0 { return nil } @@ -54,76 +54,81 @@ func LoadChannelsFromPl(location string) error { } func WatchChannelsFile() { - watcher, err := fsnotify.NewWatcher() - if err != nil { - log.Fatal(err) - } - defer watcher.Close() - - err = watcher.Add("channels.json") - if err != nil { - log.Fatal(err) - } - - for { - select { - case event, ok := <-watcher.Events: - if !ok { - return - } - if event.Has(fsnotify.Write) { - log.Println("Detected change in channels.json, reloading channels") - err := LoadChannels() - if err != nil { - log.Printf("Error reloading channels: %s\n", err) - } - } - case err, ok := <-watcher.Errors: - if !ok { - return - } - log.Printf("Watcher error: %s\n", err) - } - } + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Fatal(err) + } + defer watcher.Close() + + err = watcher.Add("channels.json") + if err != nil { + log.Fatal(err) + } + + for { + select { + case event, ok := <-watcher.Events: + if !ok { + return + } + if event.Has(fsnotify.Write) { + log.Println("Detected change in channels.json, reloading channels") + err := LoadChannels() + if err != nil { + log.Printf("Error reloading channels: %s\n", err) + } + + err = RefreshPlexLiveTVGuide() + if err != nil { + log.Printf("Error refreshing Plex LiveTV Guide: %s\n", err) + } + } + case err, ok := <-watcher.Errors: + if !ok { + return + } + log.Printf("Watcher error: %s\n", err) + } + } } func LoadChannels() error { - file, err := os.Open("channels.json") - if err != nil { - return err - } - defer file.Close() - - var channels []Channel - decoder := json.NewDecoder(file) - err = decoder.Decode(&channels) - if err != nil { - return err - } - - mu.Lock() - Channels = channels - mu.Unlock() - - log.Println("Channels reloaded successfully") - return nil + file, err := os.Open("channels.json") + if err != nil { + return err + } + defer file.Close() + + var channels []Channel + decoder := json.NewDecoder(file) + err = decoder.Decode(&channels) + if err != nil { + return err + } + + mu.Lock() + Channels = channels + mu.Unlock() + + log.Println("Channels reloaded successfully") + return nil } func init() { - var playlist = os.Getenv("PLAYLIST") + var playlist = os.Getenv("PLAYLIST") if len(playlist) > 0 { - err := LoadChannelsFromPl(playlist) - if err == nil { + err := LoadChannelsFromPl(playlist) + if err == nil { return } else { - log.Printf("Provided m3u playlist error: %s\n",err) + log.Printf("Provided m3u playlist error: %s\n", err) } } err := LoadChannels() - if err != nil { - log.Fatal(err) - } + if err != nil { + log.Fatal(err) + } - go WatchChannelsFile() + go WatchChannelsFile() } diff --git a/config/config.go b/config/config.go index c576e88..30032ec 100644 --- a/config/config.go +++ b/config/config.go @@ -2,7 +2,9 @@ package config import ( "encoding/json" + "fmt" "log" + "net/http" "os" ) @@ -15,10 +17,16 @@ const ( EncoderProfileOMX EncoderProfile = "omx" ) +type PlexServer struct { + Endpoint string `json:"endpoint"` + Token string `json:"token"` +} + type Config struct { Name string `json:"name"` EncoderProfile *EncoderProfile `json:"encoder_profile"` TunerCount *int `json:"tuner_count"` + PlexServers []PlexServer `json:"plex_servers"` } func (c Config) GetEncoderProfile() EncoderProfile { @@ -42,6 +50,31 @@ var ( Cfg Config ) +func RefreshPlexLiveTVGuide() error { + for _, server := range Cfg.PlexServers { + url := server.Endpoint + "/livetv/dvrs/29/reloadGuide?X-Plex-Token=" + server.Token + + req, err := http.NewRequest("POST", url, nil) + if err != nil { + return err + } + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("failed to refresh Plex LiveTV Guide for server %s, status code: %d", server.Endpoint, resp.StatusCode) + } + + log.Printf("Plex LiveTV Guide refreshed successfully for server %s", server.Endpoint) + } + return nil +} + func init() { file, err := os.Open("config.json") if err != nil { diff --git a/go.mod b/go.mod index 7379c1b..3e215a3 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/duncanleo/plex-dvr-hls +module plex-dvr-hls go 1.23.3 diff --git a/routes/discover.go b/routes/discover.go index a45a642..568f55d 100644 --- a/routes/discover.go +++ b/routes/discover.go @@ -6,7 +6,7 @@ import ( "net/http" "time" - "github.com/duncanleo/plex-dvr-hls/config" + "plex-dvr-hls/config" "github.com/gin-gonic/gin" ) diff --git a/routes/lineup.go b/routes/lineup.go index 71ba6f9..49f5d23 100644 --- a/routes/lineup.go +++ b/routes/lineup.go @@ -4,7 +4,7 @@ import ( "fmt" "net/http" - "github.com/duncanleo/plex-dvr-hls/config" + "plex-dvr-hls/config" "github.com/gin-gonic/gin" ) diff --git a/routes/stream.go b/routes/stream.go index e036108..e4a8b77 100644 --- a/routes/stream.go +++ b/routes/stream.go @@ -10,7 +10,7 @@ import ( "strconv" "strings" - "github.com/duncanleo/plex-dvr-hls/config" + "plex-dvr-hls/config" "github.com/gin-gonic/gin" ) diff --git a/routes/xmltv.go b/routes/xmltv.go index 955def1..0607e8d 100644 --- a/routes/xmltv.go +++ b/routes/xmltv.go @@ -7,14 +7,15 @@ import ( "text/template" "time" - "github.com/duncanleo/plex-dvr-hls/config" + "plex-dvr-hls/config" + "github.com/gin-gonic/gin" ) type ChannelSimplified struct { - ID int - Name string - Icon *string + ID int + Name string + Icon *string } type Programme struct {