From e1442842d5a7c91fd7df9a7ecc2939acb50c8f97 Mon Sep 17 00:00:00 2001 From: aler9 <46489434+aler9@users.noreply.github.com> Date: Tue, 24 Dec 2024 16:08:28 +0100 Subject: [PATCH] normalize playlist primitives --- pkg/playlist/media.go | 17 ++++++------ pkg/playlist/media_key.go | 3 +- pkg/playlist/media_map.go | 15 ++++++---- pkg/playlist/media_part.go | 20 +++++++++----- pkg/playlist/media_part_inf.go | 8 ++++-- pkg/playlist/media_preload_hint.go | 3 +- pkg/playlist/media_segment.go | 5 +++- pkg/playlist/media_server_control.go | 11 ++++++-- pkg/playlist/media_skip.go | 3 +- pkg/playlist/multivariant.go | 2 +- pkg/playlist/multivariant_rendition.go | 3 +- pkg/playlist/multivariant_start.go | 8 ++++-- pkg/playlist/multivariant_variant.go | 3 +- pkg/playlist/primitives/attributes.go | 23 +++++++++------- pkg/playlist/primitives/byterange.go | 38 ++++++++++++++++---------- pkg/playlist/primitives/duration.go | 12 +++++--- pkg/playlist/primitives/header.go | 5 ++-- 17 files changed, 111 insertions(+), 68 deletions(-) diff --git a/pkg/playlist/media.go b/pkg/playlist/media.go index b837acc..c58b908 100644 --- a/pkg/playlist/media.go +++ b/pkg/playlist/media.go @@ -102,7 +102,7 @@ func (m Media) isPlaylist() {} func (m *Media) Unmarshal(buf []byte) error { s := string(buf) - s, err := primitives.HeaderUnmarshal(s) + s, err := primitives.SkipHeader(s) if err != nil { return err } @@ -280,13 +280,13 @@ func (m *Media) Unmarshal(buf []byte) error { return fmt.Errorf("invalid EXTINF: %s", line) } - var du time.Duration - du, err = primitives.DurationUnmarshal(parts[0]) + var d primitives.Duration + err = d.Unmarshal(parts[0]) if err != nil { return err } - curSegment.Duration = du + curSegment.Duration = time.Duration(d) curSegment.Title = strings.TrimSpace(parts[1]) curSegment.Key = curKey @@ -294,15 +294,14 @@ func (m *Media) Unmarshal(buf []byte) error { case strings.HasPrefix(line, "#EXT-X-BYTERANGE:"): line = line[len("#EXT-X-BYTERANGE:"):] - var tmp1 uint64 - var tmp2 *uint64 - tmp1, tmp2, err = primitives.ByteRangeUnmarshal(line) + var br primitives.ByteRange + err = br.Unmarshal(line) if err != nil { return err } - curSegment.ByteRangeLength = &tmp1 - curSegment.ByteRangeStart = tmp2 + curSegment.ByteRangeLength = &br.Length + curSegment.ByteRangeStart = br.Start case strings.HasPrefix(line, "#EXT-X-PART:"): line = line[len("#EXT-X-PART:"):] diff --git a/pkg/playlist/media_key.go b/pkg/playlist/media_key.go index 5186738..e85359c 100644 --- a/pkg/playlist/media_key.go +++ b/pkg/playlist/media_key.go @@ -36,7 +36,8 @@ type MediaKey struct { } func (t *MediaKey) unmarshal(v string) error { - attrs, err := primitives.AttributesUnmarshal(v) + var attrs primitives.Attributes + err := attrs.Unmarshal(v) if err != nil { return err } diff --git a/pkg/playlist/media_map.go b/pkg/playlist/media_map.go index b5a26db..197ba87 100644 --- a/pkg/playlist/media_map.go +++ b/pkg/playlist/media_map.go @@ -18,7 +18,8 @@ type MediaMap struct { } func (t *MediaMap) unmarshal(v string) error { - attrs, err := primitives.AttributesUnmarshal(v) + var attrs primitives.Attributes + err := attrs.Unmarshal(v) if err != nil { return err } @@ -29,13 +30,14 @@ func (t *MediaMap) unmarshal(v string) error { t.URI = val case "BYTERANGE": - length, start, err := primitives.ByteRangeUnmarshal(val) + var br primitives.ByteRange + err := br.Unmarshal(val) if err != nil { return err } - t.ByteRangeLength = &length - t.ByteRangeStart = start + t.ByteRangeLength = &br.Length + t.ByteRangeStart = br.Start } } @@ -50,7 +52,10 @@ func (t MediaMap) marshal() string { ret := "#EXT-X-MAP:URI=\"" + t.URI + "\"" if t.ByteRangeLength != nil { - ret += ",BYTERANGE=" + primitives.ByteRangeMarshal(*t.ByteRangeLength, t.ByteRangeStart) + "" + ret += ",BYTERANGE=" + primitives.ByteRange{ + Length: *t.ByteRangeLength, + Start: t.ByteRangeStart, + }.Marshal() + "" } ret += "\n" diff --git a/pkg/playlist/media_part.go b/pkg/playlist/media_part.go index 059a7b7..87b7447 100644 --- a/pkg/playlist/media_part.go +++ b/pkg/playlist/media_part.go @@ -30,7 +30,8 @@ type MediaPart struct { } func (p *MediaPart) unmarshal(v string) error { - attrs, err := primitives.AttributesUnmarshal(v) + var attrs primitives.Attributes + err := attrs.Unmarshal(v) if err != nil { return err } @@ -38,11 +39,12 @@ func (p *MediaPart) unmarshal(v string) error { for key, val := range attrs { switch key { case "DURATION": - tmp, err := primitives.DurationUnmarshal(val) + var d primitives.Duration + err := d.Unmarshal(val) if err != nil { return err } - p.Duration = tmp + p.Duration = time.Duration(d) case "URI": p.URI = val @@ -51,12 +53,13 @@ func (p *MediaPart) unmarshal(v string) error { p.Independent = (val == "YES") case "BYTERANGE": - length, start, err := primitives.ByteRangeUnmarshal(val) + var br primitives.ByteRange + err := br.Unmarshal(val) if err != nil { return err } - p.ByteRangeLength = &length - p.ByteRangeStart = start + p.ByteRangeLength = &br.Length + p.ByteRangeStart = br.Start case "GAP": p.Gap = true @@ -83,7 +86,10 @@ func (p MediaPart) marshal() string { } if p.ByteRangeLength != nil { - ret += ",BYTERANGE=" + primitives.ByteRangeMarshal(*p.ByteRangeLength, p.ByteRangeStart) + "" + ret += ",BYTERANGE=" + primitives.ByteRange{ + Length: *p.ByteRangeLength, + Start: p.ByteRangeStart, + }.Marshal() + "" } if p.Gap { diff --git a/pkg/playlist/media_part_inf.go b/pkg/playlist/media_part_inf.go index 5fca008..d760b50 100644 --- a/pkg/playlist/media_part_inf.go +++ b/pkg/playlist/media_part_inf.go @@ -16,18 +16,20 @@ type MediaPartInf struct { } func (t *MediaPartInf) unmarshal(v string) error { - attrs, err := primitives.AttributesUnmarshal(v) + var attrs primitives.Attributes + err := attrs.Unmarshal(v) if err != nil { return err } for key, val := range attrs { if key == "PART-TARGET" { - tmp, err := primitives.DurationUnmarshal(val) + var d primitives.Duration + err := d.Unmarshal(val) if err != nil { return err } - t.PartTarget = tmp + t.PartTarget = time.Duration(d) } } diff --git a/pkg/playlist/media_preload_hint.go b/pkg/playlist/media_preload_hint.go index bee4015..814e999 100644 --- a/pkg/playlist/media_preload_hint.go +++ b/pkg/playlist/media_preload_hint.go @@ -21,7 +21,8 @@ type MediaPreloadHint struct { } func (t *MediaPreloadHint) unmarshal(v string) error { - attrs, err := primitives.AttributesUnmarshal(v) + var attrs primitives.Attributes + err := attrs.Unmarshal(v) if err != nil { return err } diff --git a/pkg/playlist/media_segment.go b/pkg/playlist/media_segment.go index 0de36b3..6661d68 100644 --- a/pkg/playlist/media_segment.go +++ b/pkg/playlist/media_segment.go @@ -79,7 +79,10 @@ func (s MediaSegment) marshal() string { ret += "#EXTINF:" + strconv.FormatFloat(s.Duration.Seconds(), 'f', 5, 64) + "," + s.Title + "\n" if s.ByteRangeLength != nil { - ret += "#EXT-X-BYTERANGE:" + primitives.ByteRangeMarshal(*s.ByteRangeLength, s.ByteRangeStart) + "\n" + ret += "#EXT-X-BYTERANGE:" + primitives.ByteRange{ + Length: *s.ByteRangeLength, + Start: s.ByteRangeStart, + }.Marshal() + "\n" } ret += s.URI + "\n" diff --git a/pkg/playlist/media_server_control.go b/pkg/playlist/media_server_control.go index 7233114..2ea78a5 100644 --- a/pkg/playlist/media_server_control.go +++ b/pkg/playlist/media_server_control.go @@ -30,7 +30,8 @@ type MediaServerControl struct { } func (t *MediaServerControl) unmarshal(v string) error { - attrs, err := primitives.AttributesUnmarshal(v) + var attrs primitives.Attributes + err := attrs.Unmarshal(v) if err != nil { return err } @@ -41,17 +42,21 @@ func (t *MediaServerControl) unmarshal(v string) error { t.CanBlockReload = (val == "YES") case "PART-HOLD-BACK": - tmp, err := primitives.DurationUnmarshal(val) + var d primitives.Duration + err := d.Unmarshal(val) if err != nil { return err } + tmp := time.Duration(d) t.PartHoldBack = &tmp case "CAN-SKIP-UNTIL": - tmp, err := primitives.DurationUnmarshal(val) + var d primitives.Duration + err := d.Unmarshal(val) if err != nil { return err } + tmp := time.Duration(d) t.CanSkipUntil = &tmp } } diff --git a/pkg/playlist/media_skip.go b/pkg/playlist/media_skip.go index 9ec4ace..636a969 100644 --- a/pkg/playlist/media_skip.go +++ b/pkg/playlist/media_skip.go @@ -15,7 +15,8 @@ type MediaSkip struct { } func (t *MediaSkip) unmarshal(v string) error { - attrs, err := primitives.AttributesUnmarshal(v) + var attrs primitives.Attributes + err := attrs.Unmarshal(v) if err != nil { return err } diff --git a/pkg/playlist/multivariant.go b/pkg/playlist/multivariant.go index 3aea8e5..4135e78 100644 --- a/pkg/playlist/multivariant.go +++ b/pkg/playlist/multivariant.go @@ -34,7 +34,7 @@ func (m Multivariant) isPlaylist() {} func (m *Multivariant) Unmarshal(buf []byte) error { s := string(buf) - s, err := primitives.HeaderUnmarshal(s) + s, err := primitives.SkipHeader(s) if err != nil { return err } diff --git a/pkg/playlist/multivariant_rendition.go b/pkg/playlist/multivariant_rendition.go index 418b5db..c3f6641 100644 --- a/pkg/playlist/multivariant_rendition.go +++ b/pkg/playlist/multivariant_rendition.go @@ -57,7 +57,8 @@ type MultivariantRendition struct { } func (t *MultivariantRendition) unmarshal(v string) error { - attrs, err := primitives.AttributesUnmarshal(v) + var attrs primitives.Attributes + err := attrs.Unmarshal(v) if err != nil { return err } diff --git a/pkg/playlist/multivariant_start.go b/pkg/playlist/multivariant_start.go index 8f634d9..ce3a640 100644 --- a/pkg/playlist/multivariant_start.go +++ b/pkg/playlist/multivariant_start.go @@ -16,18 +16,20 @@ type MultivariantStart struct { } func (t *MultivariantStart) unmarshal(v string) error { - attrs, err := primitives.AttributesUnmarshal(v) + var attrs primitives.Attributes + err := attrs.Unmarshal(v) if err != nil { return err } for key, val := range attrs { if key == "TIME-OFFSET" { - tmp, err := primitives.DurationUnmarshal(val) + var d primitives.Duration + err := d.Unmarshal(val) if err != nil { return err } - t.TimeOffset = tmp + t.TimeOffset = time.Duration(d) } } diff --git a/pkg/playlist/multivariant_variant.go b/pkg/playlist/multivariant_variant.go index ff4646e..b8aeba4 100644 --- a/pkg/playlist/multivariant_variant.go +++ b/pkg/playlist/multivariant_variant.go @@ -47,7 +47,8 @@ type MultivariantVariant struct { func (v *MultivariantVariant) unmarshal(va string) error { lines := strings.Split(va, "\n") - attrs, err := primitives.AttributesUnmarshal(lines[0]) + var attrs primitives.Attributes + err := attrs.Unmarshal(lines[0]) if err != nil { return err } diff --git a/pkg/playlist/primitives/attributes.go b/pkg/playlist/primitives/attributes.go index 5bfbfe5..c14b882 100644 --- a/pkg/playlist/primitives/attributes.go +++ b/pkg/playlist/primitives/attributes.go @@ -5,9 +5,12 @@ import ( "strings" ) -// AttributesUnmarshal decodes attributes. -func AttributesUnmarshal(v string) (map[string]string, error) { - ret := make(map[string]string) +// Attributes are playlist attributes. +type Attributes map[string]string + +// Unmarshal decodes attributes. +func (a *Attributes) Unmarshal(v string) error { + *a = make(Attributes) for { if len(v) == 0 { @@ -17,7 +20,7 @@ func AttributesUnmarshal(v string) (map[string]string, error) { // read key i := strings.IndexByte(v, '=') if i < 0 { - return nil, fmt.Errorf("key not found") + return fmt.Errorf("key not found") } var key string key, v = v[:i], v[i+1:] @@ -29,14 +32,14 @@ func AttributesUnmarshal(v string) (map[string]string, error) { v = v[1:] i = strings.IndexByte(v, '"') if i < 0 { - return nil, fmt.Errorf("value end delimiter not found") + return fmt.Errorf("value end delimiter not found") } val, v = v[:i], v[i+1:] - ret[key] = val + (*a)[key] = val if len(v) != 0 { if v[0] != ',' { - return nil, fmt.Errorf("delimiter not found") + return fmt.Errorf("delimiter not found") } v = v[1:] } @@ -44,14 +47,14 @@ func AttributesUnmarshal(v string) (map[string]string, error) { i = strings.IndexByte(v, ',') if i >= 0 { val, v = v[:i], v[i+1:] - ret[key] = val + (*a)[key] = val } else { val = v - ret[key] = val + (*a)[key] = val break } } } - return ret, nil + return nil } diff --git a/pkg/playlist/primitives/byterange.go b/pkg/playlist/primitives/byterange.go index e848c08..2dcbe65 100644 --- a/pkg/playlist/primitives/byterange.go +++ b/pkg/playlist/primitives/byterange.go @@ -5,40 +5,50 @@ import ( "strings" ) -// ByteRangeUnmarshal decodes a byte range. -func ByteRangeUnmarshal(v string) (uint64, *uint64, error) { +// ByteRange is a byte range. +type ByteRange struct { + Length uint64 + Start *uint64 +} + +// Unmarshal decodes a byte range. +func (b *ByteRange) Unmarshal(v string) error { i := strings.IndexByte(v, '@') if i >= 0 { str1, str2 := v[:i], v[i+1:] - length, err := strconv.ParseUint(str1, 10, 64) + var err error + b.Length, err = strconv.ParseUint(str1, 10, 64) if err != nil { - return 0, nil, err + return err } start, err := strconv.ParseUint(str2, 10, 64) if err != nil { - return 0, nil, err + return err } - return length, &start, nil + b.Start = &start + + return nil } - length, err := strconv.ParseUint(v, 10, 64) + var err error + b.Length, err = strconv.ParseUint(v, 10, 64) if err != nil { - return 0, nil, err + return err } - return length, nil, nil + return nil } -// ByteRangeMarshal encodes a byte range. -func ByteRangeMarshal(length uint64, start *uint64) string { - ret := strconv.FormatUint(length, 10) +// Marshal encodes a byte range. +func (b ByteRange) Marshal() string { + ret := strconv.FormatUint(b.Length, 10) - if start != nil { - ret += "@" + strconv.FormatUint(*start, 10) + if b.Start != nil { + ret += "@" + strconv.FormatUint(*b.Start, 10) } return ret diff --git a/pkg/playlist/primitives/duration.go b/pkg/playlist/primitives/duration.go index 9fd0a23..62b201e 100644 --- a/pkg/playlist/primitives/duration.go +++ b/pkg/playlist/primitives/duration.go @@ -5,12 +5,16 @@ import ( "time" ) -// DurationUnmarshal decodes a duration. -func DurationUnmarshal(val string) (time.Duration, error) { +// Duration is a playlist duration. +type Duration time.Duration + +// Unmarshal decodes a duration. +func (d *Duration) Unmarshal(val string) error { tmp, err := strconv.ParseFloat(val, 64) if err != nil { - return 0, err + return err } - return time.Duration(tmp * float64(time.Second)), nil + *d = Duration(time.Duration(tmp * float64(time.Second))) + return nil } diff --git a/pkg/playlist/primitives/header.go b/pkg/playlist/primitives/header.go index d1c1b1c..7b03e3e 100644 --- a/pkg/playlist/primitives/header.go +++ b/pkg/playlist/primitives/header.go @@ -4,14 +4,13 @@ import ( "fmt" ) -// HeaderUnmarshal decodes a header. -func HeaderUnmarshal(s string) (string, error) { +// SkipHeader reads and skips a playlist header. +func SkipHeader(s string) (string, error) { var line string line, s = ReadLine(s) if line != "#EXTM3U" { return "", fmt.Errorf("M3U8 header is missing") } - return s, nil }