From ffefd4d5b76972b92e60c018666d9fe3421ceb87 Mon Sep 17 00:00:00 2001 From: Joey Freeland <30938344+jfreeland@users.noreply.github.com> Date: Fri, 12 Feb 2021 12:00:01 -0800 Subject: [PATCH] feat: watch playlists (#2) --- go.mod | 2 ++ go.sum | 3 +++ hlsq.go | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 84 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 74f2e4b..dc2b328 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/soldiermoth/hlsq go 1.14 + +require golang.org/x/text v0.3.4 diff --git a/go.sum b/go.sum index e69de29..c9fe4fe 100644 --- a/go.sum +++ b/go.sum @@ -0,0 +1,3 @@ +golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/hlsq.go b/hlsq.go index 1b975c8..a5ae000 100644 --- a/hlsq.go +++ b/hlsq.go @@ -5,9 +5,13 @@ import ( "fmt" "io" "log" + "net/http" "os" + "strconv" + "time" "github.com/soldiermoth/hlsq/hlsqlib" + "golang.org/x/text/unicode/norm" ) func main() { @@ -20,6 +24,8 @@ func main() { query = flag.String("query", "", "Query") chomp = flag.Bool("chomp", false, "Removes whitespace") demuxed = flag.Bool("demuxed", false, "Set to demuxed colors") + watch = flag.Bool("watch", false, "Continuously watch the playlist (requires url)") + url = flag.String("url", "", "URL of the manifest to watch (required to watch)") r io.Reader err error ) @@ -52,6 +58,8 @@ func main() { if r, err = os.Open(args[0]); err != nil { log.Fatalf("could not open file %q err=%q", args[0], err) } + } else if *watch && *url != "" { + r = getManifest(*url) } else { log.Fatal("expected 1 argument of the m3u8 file to process or to read from stdin") } @@ -71,9 +79,59 @@ func main() { } opts = append([]hlsqlib.SerializeOption{hlsqlib.AttrMatch(queryFunc)}, opts...) } - for scanner.Scan() { - line := scanner.Tag() - hlsqlib.Serialize(os.Stdout, line, opts...) + if *watch && *url != "" { + opts = append(opts, hlsqlib.Chomp) + var updatePeriod time.Duration + seen := make(map[string]bool) + ignoredTagsOnUpdate := []string{ + "#EXTM3U", + "#EXT-X-VERSION", + "#EXT-X-DISCONTINUITY-SEQUENCE", + "#EXT-X-PUBLISHED-TIME", + "#EXT-X-TARGETDURATION", + "#EXT-X-MEDIA-SEQUENCE", + "#EXT-X-PROGRAM-DATE-TIME", + "#EXT-X-KEY", + } + for scanner.Scan() { + line := scanner.Tag() + if line.Name == "#EXT-X-TARGETDURATION" { + d, _ := strconv.Atoi(line.Attrs[0].Key) + updatePeriod = time.Duration(d) * time.Second + } + if line.Name == "#EXTINF" { + segment := norm.NFC.Bytes([]byte(string(line.Trailing[0]))) + seen[string(segment)] = true + } + hlsqlib.Serialize(os.Stdout, line, opts...) + } + for range time.Tick(updatePeriod) { + r = getManifest(*url) + scanner = hlsqlib.NewScanner(r) + updated := false + for scanner.Scan() { + line := scanner.Tag() + if stringInSlice(line.Name, ignoredTagsOnUpdate) { + continue + } + segment := norm.NFC.Bytes([]byte(string(line.Trailing[0]))) + if line.Name == "#EXTINF" && seen[string(segment)] { + continue + } else if line.Name == "#EXTINF" && !seen[string(segment)] { + seen[string(segment)] = true + } + hlsqlib.Serialize(os.Stdout, line, opts...) + updated = true + } + if !updated { + fmt.Println("manifest did not change this period") + } + } + } else { + for scanner.Scan() { + line := scanner.Tag() + hlsqlib.Serialize(os.Stdout, line, opts...) + } } fmt.Fprintln(os.Stdout) } @@ -86,9 +144,27 @@ func (f flagColor) String() string { } return hlsqlib.NewColorizer(*f.Color).S("Example") } + func (f flagColor) Set(s string) (err error) { if s != "" { *f.Color, err = hlsqlib.ParseColor(s) } return } + +func getManifest(url string) io.ReadCloser { + resp, err := http.Get(url) + if err != nil { + log.Fatalf("could not fetch manifest: %v\n", err) + } + return resp.Body +} + +func stringInSlice(a string, list []string) bool { + for _, b := range list { + if b == a { + return true + } + } + return false +}