commit 585a7c087730c7083c4a5fe80e02575561682dd1
parent b3fe201d0ff0f46894b72a43318de23db09c3acc
Author: Oliver Lowe <o@olowe.co>
Date: Thu, 23 May 2024 20:28:12 +1000
m3u8: Handle writing more attributes of Variants
Involves a correction of how we stored closed caption information.
Reading and writing the Tears of Steel playlist from
test-streams.mux.dev now works ok!
Diffstat:
5 files changed, 32 insertions(+), 33 deletions(-)
diff --git a/m3u8/m3u8.go b/m3u8/m3u8.go
@@ -217,9 +217,16 @@ type Variant struct {
Audio string
Video string
Subtitles string
- ClosedCaptions []string // `NONE` or comma-separated values
+ // May be NoClosedCaptions or the empty string to indicate
+ // absence of closed captions.
+ ClosedCaptions string
}
+// NoClosedCaptions may be the value for Variant.ClosedCaptions to
+// explicitly indicate that no closed captions are available for the
+// Variant.
+const NoClosedCaptions string = "NONE"
+
type HDCPLevel uint8
const (
@@ -228,7 +235,7 @@ const (
HDCPType1
)
-func (l HDCPLevel) String() {
+func (l HDCPLevel) String() string {
switch l {
case HDCPNone:
return "NONE"
diff --git a/m3u8/parse.go b/m3u8/parse.go
@@ -154,11 +154,12 @@ func parseVariant(items chan item) (*Variant, error) {
}
v.HDCP = l
case "AUDIO", "VIDEO", "SUBTITLES":
- name := it.val
+ name := attr.val
it = <-items
if it.typ != itemString {
return nil, fmt.Errorf("parse %s: unexpected %s", name, it)
}
+ it.val = strings.Trim(it.val, `"`)
if name == "AUDIO" {
v.Audio = it.val
} else if name == "VIDEO" {
@@ -171,7 +172,7 @@ func parseVariant(items chan item) (*Variant, error) {
if it.typ != itemString {
return nil, fmt.Errorf("parse closed-captions: unexpcted %s", it)
}
- v.ClosedCaptions = strings.Split(strings.Trim(it.val, `"`), ",")
+ v.ClosedCaptions = strings.Trim(it.val, `"`)
default:
return nil, fmt.Errorf("unknown attribute %s", attr.val)
}
diff --git a/m3u8/parse_test.go b/m3u8/parse_test.go
@@ -4,7 +4,6 @@ import (
"os"
"path"
"path/filepath"
- "reflect"
"testing"
"time"
)
@@ -21,7 +20,8 @@ func TestParse(t *testing.T) {
t.Fatal(err)
}
defer f.Close()
- if _, err := ParsePlaylist(f); err != nil {
+ _, err = ParsePlaylist(f)
+ if err != nil {
t.Fatal(err)
}
})
@@ -66,27 +66,6 @@ func TestParseByteRange(t *testing.T) {
}
}
-func TestParseClosedCaptions(t *testing.T) {
- f, err := os.Open("testdata/closed_captions.m3u8")
- if err != nil {
- t.Fatal(err)
- }
- defer f.Close()
- p, err := ParsePlaylist(f)
- if err != nil {
- t.Fatal(err)
- }
- cc := p.Variants[0].ClosedCaptions[0]
- if cc != "NONE" {
- t.Errorf("want closed captions %q, got %s", "NONE", cc)
- }
- cc2 := p.Variants[1].ClosedCaptions
- want := []string{"something", "another"}
- if !reflect.DeepEqual(want, cc2) {
- t.Errorf("want closed captions %s, got %s", want, cc2)
- }
-}
-
// Tests that we parse floats and integers of different precisions ok.
func TestFrameRate(t *testing.T) {
f, err := os.Open("testdata/frame_rate.m3u8")
diff --git a/m3u8/testdata/closed_captions.m3u8 b/m3u8/testdata/closed_captions.m3u8
@@ -1,6 +0,0 @@
-#EXTM3U
-#EXT-X-STREAM-INF:CLOSED-CAPTIONS=NONE
-tos_1080p/index.m3u8
-## can also have quoted strings, comma-delimeted
-#EXT-X-STREAM-INF:CLOSED-CAPTIONS="something,another"
-playlist.m3u8
diff --git a/m3u8/write.go b/m3u8/write.go
@@ -107,6 +107,24 @@ func Encode(w io.Writer, p *Playlist) error {
if v.Resolution != [2]int{0, 0} {
fmt.Fprintf(w, "RESOLUTION=%dx%d,", v.Resolution[0], v.Resolution[1])
}
+ if v.FrameRate > 0 {
+ fmt.Fprintf(w, "FRAME-RATE=%f,", v.FrameRate)
+ }
+ if v.HDCP != HDCPNone {
+ fmt.Fprintf(w, "HDCP-LEVEL=%s,", v.HDCP)
+ }
+ if v.Audio != "" {
+ fmt.Fprintf(w, "AUDIO=%q,", v.Audio)
+ }
+ if v.Video != "" {
+ fmt.Fprintf(w, "VIDEO=%q,", v.Video)
+ }
+ if v.Subtitles != "" {
+ fmt.Fprintf(w, "SUBTITLES=%q,", v.Subtitles)
+ }
+ if v.ClosedCaptions != "" && v.ClosedCaptions != NoClosedCaptions {
+ fmt.Fprintf(w, "CLOSED-CAPTIONS=%q,", v.ClosedCaptions)
+ }
fmt.Fprintln(w)
fmt.Fprintln(w, v.URI)
}