commit 62d865db8655e6d23b4fccc3280d73c3cdda8ce9 from: Oliver Lowe date: Tue May 20 08:03:56 2025 UTC m3u8: de-indent happy key, playlist parse path Everything else are essentially guard statements commit - 3df4029f13d7c363d034d1c5aed186ba947ab8a6 commit + 62d865db8655e6d23b4fccc3280d73c3cdda8ce9 blob - a71b7f91295d88116f9117e9fd2185a9749e5751 blob + eb9edd1b313d85e154de73ebe1751394c2b0cdc8 --- m3u8/parse.go +++ m3u8/parse.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "io" + "os" "strconv" "strings" "time" @@ -32,70 +33,84 @@ func Decode(rd io.Reader) (*Playlist, error) { if it.typ != itemTag || it.val != tagHead { return nil, fmt.Errorf("expected head tag, got %q", it.val) } + p := &Playlist{} var err error for it := range lex.items { switch it.typ { case itemError: return p, errors.New(it.val) - case itemTag: - switch it.val { - case tagVersion: - it = <-lex.items - if p.Version != 0 { - return p, fmt.Errorf("parse %s: playlist version already specified", it) - } - p.Version, err = strconv.Atoi(it.val) - if err != nil { - return p, fmt.Errorf("parse playlist version: %w", err) - } - case tagIndependentSegments: - p.IndependentSegments = true - case tagVariant: - variant, err := parseVariant(lex.items) - if err != nil { - return p, fmt.Errorf("parse variant: %w", err) - } - p.Variants = append(p.Variants, *variant) - case tagRendition: - rend, err := parseRendition(lex.items) - if err != nil { - return p, fmt.Errorf("parse rendition: %w", err) - } - p.Media = append(p.Media, *rend) - case tagPlaylistType: - it = <-lex.items - typ, err := parsePlaylistType(it) - if err != nil { - return p, fmt.Errorf("parse playlist type: %w", err) - } - p.Type = typ + case itemNewline: + continue + default: + if it.typ != itemTag { + return p, fmt.Errorf("unexpected %s %q, expected tag", it.typ, it.val) + } + } - case tagTargetDuration: - it = <-lex.items - dur, err := parseTargetDuration(it) - if err != nil { - return p, fmt.Errorf("parse target duration: %w", err) - } - p.TargetDuration = dur + switch it.val { + case tagVersion: + it = <-lex.items + if p.Version != 0 { + return p, fmt.Errorf("parse %s: playlist version already specified", it) + } + p.Version, err = strconv.Atoi(it.val) + if err != nil { + return p, fmt.Errorf("parse playlist version: %w", err) + } + case tagIndependentSegments: + p.IndependentSegments = true + case tagVariant: + variant, err := parseVariant(lex.items) + if err != nil { + return p, fmt.Errorf("parse variant: %w", err) + } + p.Variants = append(p.Variants, *variant) + case tagRendition: + rend, err := parseRendition(lex.items) + if err != nil { + return p, fmt.Errorf("parse rendition: %w", err) + } + p.Media = append(p.Media, *rend) + case tagPlaylistType: + it = <-lex.items + typ, err := parsePlaylistType(it) + if err != nil { + return p, fmt.Errorf("parse playlist type: %w", err) + } + p.Type = typ - case tagSegmentDuration, tagByteRange, tagKey: - segment, err := parseSegment(lex.items, it) - if err != nil { - return p, fmt.Errorf("parse segment: %w", err) - } - p.Segments = append(p.Segments, *segment) + case tagTargetDuration: + it = <-lex.items + dur, err := parseTargetDuration(it) + if err != nil { + return p, fmt.Errorf("parse target duration: %w", err) + } + p.TargetDuration = dur - case tagEndList: - p.End = true - case tagMediaSequence: - it = <-lex.items - seq, err := strconv.Atoi(it.val) - if err != nil { - return p, fmt.Errorf("parse media sequence: %w", err) - } - p.Sequence = seq + case tagSegmentDuration, tagByteRange, tagKey: + segment, err := parseSegment(lex.items, it) + if err != nil { + return p, fmt.Errorf("parse segment: %w", err) } + p.Segments = append(p.Segments, *segment) + + case tagEndList: + p.End = true + case tagMediaSequence: + it = <-lex.items + seq, err := strconv.Atoi(it.val) + if err != nil { + return p, fmt.Errorf("parse media sequence: %w", err) + } + p.Sequence = seq + default: + if lex.debug { + fmt.Fprintln(os.Stderr, "unknown tag", it) + } + // throw away whatever is next; we don't support it but also don't want to + // return errors while this package is in development. + <-lex.items } } return p, nil blob - 0b9fdc9478e2542627b7c7f74521a3c2a8189985 blob + 0a40a92ff5f85cbd5b98a03af439c521889bd4ee --- m3u8/segment.go +++ m3u8/segment.go @@ -147,48 +147,54 @@ func parseKey(items chan item) (Key, error) { return key, errors.New(it.val) case itemNewline: return key, nil - case itemAttrName: - v := <-items - if v.typ != itemEquals { - return key, fmt.Errorf("expected %q after %s, got %s", "=", it.typ, v) + case itemComma: + continue + default: + if it.typ != itemAttrName { + return Key{}, fmt.Errorf("expected attribute name, got %s", it.val) } - switch it.val { - case "METHOD": - v = <-items - key.Method = parseEncryptMethod(v.val) - if key.Method == encryptMethodInvalid { - return key, fmt.Errorf("bad encrypt method %q", v.val) - } - case "URI": - v = <-items - key.URI = strings.Trim(v.val, `"`) - case "IV": - v = <-items - b, err := hex.DecodeString(strings.TrimPrefix(v.val, "0x")) + } + v := <-items + if v.typ != itemEquals { + return key, fmt.Errorf("expected %q after %s, got %s", "=", it.typ, v) + } + + switch it.val { + case "METHOD": + v = <-items + key.Method = parseEncryptMethod(v.val) + if key.Method == encryptMethodInvalid { + return key, fmt.Errorf("bad encrypt method %q", v.val) + } + case "URI": + v = <-items + key.URI = strings.Trim(v.val, `"`) + case "IV": + v = <-items + b, err := hex.DecodeString(strings.TrimPrefix(v.val, "0x")) + if err != nil { + return key, fmt.Errorf("parse initialisation vector: %w", err) + } + if len(b) != len(key.IV) { + return key, fmt.Errorf("bad initialisation length %d, want %d", len(b), len(key.IV)) + } + copy(key.IV[:], b) + case "KEYFORMAT": + v = <-items + key.Format = strings.Trim(v.val, `"`) + case "KEYFORMATVERSIONS": + v = <-items + ss := strings.Split(v.val, "/") + key.FormatVersions = make([]uint32, len(ss)) + for i := range ss { + n, err := strconv.Atoi(ss[i]) if err != nil { - return key, fmt.Errorf("parse initialisation vector: %w", err) + return key, fmt.Errorf("parse key format version: %w", err) } - if len(b) != len(key.IV) { - return key, fmt.Errorf("bad initialisation length %d, want %d", len(b), len(key.IV)) - } - copy(key.IV[:], b) - case "KEYFORMAT": - v = <-items - key.Format = strings.Trim(v.val, `"`) - case "KEYFORMATVERSIONS": - v = <-items - ss := strings.Split(v.val, "/") - key.FormatVersions = make([]uint32, len(ss)) - for i := range ss { - n, err := strconv.Atoi(ss[i]) - if err != nil { - return key, fmt.Errorf("parse key format version: %w", err) - } - key.FormatVersions[i] = uint32(n) - } - default: - return key, fmt.Errorf("unexpected attribute %q", it.val) + key.FormatVersions[i] = uint32(n) } + default: + return key, fmt.Errorf("unexpected attribute %q", it.val) } } return key, fmt.Errorf("unexpected end of tag")