commit 12b4cf5015a6017ee960c93072ddffdc51bc746c
parent c0e0995563ad5bbb07ebdf0be7dbd67aed858ee0
Author: Oliver Lowe <o@olowe.co>
Date: Sat, 1 Jun 2024 18:44:46 +1000
m3u8: Return valid HLS tags with String()
Without the tag, the string is essentially invalid. Since we're not
exporting the HLS tag names (e.g. "#EXT-STREAM-INF") the String()
method was not useful. Now doing a fmt.Println(variant) returns a
valid HLS tag.
Fixes: https://github.com/untangledco/streaming/issues/14
Diffstat:
5 files changed, 49 insertions(+), 38 deletions(-)
diff --git a/m3u8/m3u8.go b/m3u8/m3u8.go
@@ -72,8 +72,6 @@ type Key struct {
IV [16]byte
}
-const defaultKeyFormat string = "identity"
-
func (k Key) String() string {
var attrs []string
attrs = append(attrs, fmt.Sprintf("METHOD=%s", k.Method))
@@ -89,9 +87,11 @@ func (k Key) String() string {
}
attrs = append(attrs, fmt.Sprintf("KEYFORMATVERSIONS=%q", strings.Join(ss, "/")))
}
- return strings.Join(attrs, ",")
+ return tagKey + ":" + strings.Join(attrs, ",")
}
+const defaultKeyFormat string = "identity"
+
type EncryptMethod uint8
const (
@@ -117,6 +117,13 @@ type Map struct {
ByteRange ByteRange
}
+func (m Map) String() string {
+ if m.ByteRange != [2]int{0, 0} {
+ return fmt.Sprintf("%s:URI=%q,BYTERANGE=%s", tagMap, m.URI, m.ByteRange)
+ }
+ return fmt.Sprintf("%s:URI=%q", tagMap, m.URI)
+}
+
// ByteRange represents...
// The first entry is an offset, the second...?
type ByteRange [2]int
diff --git a/m3u8/master.go b/m3u8/master.go
@@ -81,6 +81,39 @@ type Variant struct {
ClosedCaptions string
}
+func (v Variant) String() string {
+ var attrs []string
+ attrs = append(attrs, fmt.Sprintf("BANDWIDTH=%d", v.Bandwidth))
+ if v.AverageBandwidth > 0 {
+ attrs = append(attrs, fmt.Sprintf("AVERAGE-BANDWIDTH=%d,", v.AverageBandwidth))
+ }
+ if len(v.Codecs) > 0 {
+ attrs = append(attrs, fmt.Sprintf("CODECS=%q", strings.Join(v.Codecs, ",")))
+ }
+ if v.Resolution != [2]int{0, 0} {
+ attrs = append(attrs, fmt.Sprintf("RESOLUTION=%dx%d", v.Resolution[0], v.Resolution[1]))
+ }
+ if v.FrameRate > 0 {
+ attrs = append(attrs, fmt.Sprintf("FRAME-RATE=%.03f", v.FrameRate))
+ }
+ if v.HDCP != HDCPNone {
+ attrs = append(attrs, fmt.Sprintf("HDCP-LEVEL=%s", v.HDCP))
+ }
+ if v.Audio != "" {
+ attrs = append(attrs, fmt.Sprintf("AUDIO=%q", v.Audio))
+ }
+ if v.Video != "" {
+ attrs = append(attrs, fmt.Sprintf("VIDEO=%q", v.Video))
+ }
+ if v.Subtitles != "" {
+ attrs = append(attrs, fmt.Sprintf("SUBTITLES=%q", v.Subtitles))
+ }
+ if v.ClosedCaptions != "" && v.ClosedCaptions != NoClosedCaptions {
+ attrs = append(attrs, fmt.Sprintf("CLOSED-CAPTIONS=%q", v.ClosedCaptions))
+ }
+ return fmt.Sprintf("%s:%s\n%s", tagVariant, strings.Join(attrs, ","), v.URI)
+}
+
// NoClosedCaptions may be the value for Variant.ClosedCaptions to
// explicitly indicate that no closed captions are available for the
// Variant.
@@ -134,5 +167,5 @@ func (sd SessionData) String() string {
if sd.Language != "" {
attrs = append(attrs, fmt.Sprintf("LANGUAGE=%q", sd.Language))
}
- return strings.Join(attrs, ",")
+ return tagSessionData + ":" + strings.Join(attrs, ",")
}
diff --git a/m3u8/segment.go b/m3u8/segment.go
@@ -139,10 +139,10 @@ func writeSegments(w io.Writer, segments []Segment) (n int, err error) {
fmt.Fprintf(w, "%s:%s\n", tagByteRange, seg.Range)
}
if seg.Key != nil {
- fmt.Fprintf(w, "%s:%s\n", tagKey, seg.Key)
+ fmt.Fprintln(w, seg.Key)
}
if seg.Map != nil {
- writeMap(w, *seg.Map)
+ fmt.Fprintln(w, seg.Map)
}
if !seg.DateTime.IsZero() {
fmt.Fprintf(w, "%s:%s\n", tagDateTime, seg.DateTime.Format(RFC3339Milli))
diff --git a/m3u8/segment_test.go b/m3u8/segment_test.go
@@ -86,7 +86,7 @@ func TestWriteKey(t *testing.T) {
Format: defaultKeyFormat,
FormatVersions: []uint32{1, 2, 5},
}
- want := `METHOD=AES-128,URI="magic.key",IV=0x1027000000000000780ae30500000000,KEYFORMAT="identity",KEYFORMATVERSIONS="1/2/5"`
+ want := `#EXT-X-KEY:METHOD=AES-128,URI="magic.key",IV=0x1027000000000000780ae30500000000,KEYFORMAT="identity",KEYFORMATVERSIONS="1/2/5"`
if k.String() != want {
t.Errorf("unexpected segment key text")
t.Log("got:", k.String())
diff --git a/m3u8/write.go b/m3u8/write.go
@@ -103,42 +103,13 @@ func Encode(w io.Writer, p *Playlist) error {
}
func writeVariant(w io.Writer, v *Variant) (n int, err error) {
- var attrs []string
if v.Bandwidth <= 0 {
return 0, fmt.Errorf("invalid bandwidth %d: must be larger than zero", v.Bandwidth)
}
- attrs = append(attrs, fmt.Sprintf("BANDWIDTH=%d", v.Bandwidth))
- if v.AverageBandwidth > 0 {
- attrs = append(attrs, fmt.Sprintf("AVERAGE-BANDWIDTH=%d,", v.AverageBandwidth))
- }
- if len(v.Codecs) > 0 {
- attrs = append(attrs, fmt.Sprintf("CODECS=%q", strings.Join(v.Codecs, ",")))
- }
- if v.Resolution != [2]int{0, 0} {
- attrs = append(attrs, fmt.Sprintf("RESOLUTION=%dx%d", v.Resolution[0], v.Resolution[1]))
- }
- if v.FrameRate > 0 {
- attrs = append(attrs, fmt.Sprintf("FRAME-RATE=%.03f", v.FrameRate))
- }
- if v.HDCP != HDCPNone {
- attrs = append(attrs, fmt.Sprintf("HDCP-LEVEL=%s", v.HDCP))
- }
- if v.Audio != "" {
- attrs = append(attrs, fmt.Sprintf("AUDIO=%q", v.Audio))
- }
- if v.Video != "" {
- attrs = append(attrs, fmt.Sprintf("VIDEO=%q", v.Video))
- }
- if v.Subtitles != "" {
- attrs = append(attrs, fmt.Sprintf("SUBTITLES=%q", v.Subtitles))
- }
- if v.ClosedCaptions != "" && v.ClosedCaptions != NoClosedCaptions {
- attrs = append(attrs, fmt.Sprintf("CLOSED-CAPTIONS=%q", v.ClosedCaptions))
- }
if v.URI == "" {
return 0, fmt.Errorf("empty URI")
}
- return fmt.Fprintf(w, "%s:%s\n%s\n", tagVariant, strings.Join(attrs, ","), v.URI)
+ return fmt.Fprintln(w, v)
}
func writeDateRange(w io.Writer, dr *DateRange) error {
@@ -196,5 +167,5 @@ func writeSessionData(w io.Writer, sd SessionData) (n int, err error) {
if sd.URI != "" && sd.Value != "" {
return 0, fmt.Errorf("only one of Value or URI may be set")
}
- return fmt.Fprintf(w, "%s:%s\n", tagSessionData, sd)
+ return fmt.Fprintln(w, sd)
}