segment.go (7667B)
1 package m3u8 2 3 import ( 4 "bytes" 5 "encoding/hex" 6 "errors" 7 "fmt" 8 "io" 9 "strconv" 10 "strings" 11 "time" 12 ) 13 14 // Media segment tags specified in RFC 8216 section 4.4.4. 15 const ( 16 tagSegmentDuration = "#EXTINF" 17 tagByteRange = "#EXT-X-BYTERANGE" 18 tagDiscontinuity = "#EXT-X-DISCONTINUITY" 19 tagKey = "#EXT-X-KEY" 20 tagMap = "#EXT-X-MAP" 21 tagDateTime = "#EXT-X-PROGRAM-DATE-TIME" 22 tagGap = "#EXT-X-GAP" 23 tagBitrate = "#EXT-X-BITRATE" 24 tagPart = "#EXT-X-PART" 25 tagDateRange = "#EXT-X-DATERANGE" 26 ) 27 28 // parseSegment returns the next segment from items and the leading 29 // item which indicated the start of a segment. 30 func parseSegment(items chan item, leading item) (*Segment, error) { 31 // we've already read one item, so send everything through again 32 // starting with the leading item to maintain the lexer's original order. 33 segItems := make(chan item) 34 go func() { 35 segItems <- leading 36 for it := range items { 37 if it.typ == itemURL { 38 segItems <- it 39 close(segItems) 40 return 41 } 42 segItems <- it 43 } 44 }() 45 46 var seg Segment 47 for it := range segItems { 48 switch it.typ { 49 case itemNewline: 50 continue 51 case itemError: 52 return nil, errors.New(it.val) 53 case itemURL: 54 seg.URI = it.val 55 return &seg, nil 56 } 57 58 switch it.val { 59 case tagSegmentDuration: 60 it = <-segItems 61 if it.typ != itemAttrName && it.typ != itemNumber { 62 return nil, fmt.Errorf("parse segment duration: unexpected %s: want attribute name or number", it) 63 } 64 dur, err := parseSegmentDuration(it.val) 65 if err != nil { 66 return nil, fmt.Errorf("parse segment duration: %w", err) 67 } 68 seg.Duration = dur 69 70 // check for the optional segment title 71 it = <-segItems 72 if it.typ == itemNewline { 73 continue 74 } else if it.typ != itemComma { 75 return nil, fmt.Errorf("expected comma after segment duration, got %s", it) 76 } 77 it = <-segItems 78 seg.Title = it.val 79 80 81 case tagByteRange: 82 it = <-segItems 83 r, err := parseByteRange(it.val) 84 if err != nil { 85 return nil, fmt.Errorf("parse byte range: %w", err) 86 } 87 seg.Range = r 88 case tagDiscontinuity: 89 seg.Discontinuity = true 90 case tagKey: 91 key, err := parseKey(segItems) 92 if err != nil { 93 return nil, fmt.Errorf("parse key: %w", err) 94 } 95 seg.Key = &key 96 case tagMap: 97 m, err := parseMap(segItems) 98 if err != nil { 99 return nil, fmt.Errorf("parse map: %w", err) 100 } 101 seg.Map = &m 102 case tagDateTime: 103 it = <-segItems 104 t, err := time.Parse(rfc3339Milli, it.val) 105 if err != nil { 106 return nil, fmt.Errorf("bad date time tag: %w", err) 107 } 108 seg.DateTime = t 109 default: 110 return nil, fmt.Errorf("parsing %s unsupported", it) 111 } 112 } 113 return nil, fmt.Errorf("no url") 114 } 115 116 func parseSegmentDuration(s string) (time.Duration, error) { 117 // Some numbers can be converted straight to ints, e.g.: 118 // 10 119 // 10.000 120 // Others need to be converted from floating point, e.g: 121 // 9.967 122 // Try the easiest paths first. 123 if !strings.Contains(s, ".") { 124 i, err := strconv.Atoi(s) 125 if err != nil { 126 return 0, err 127 } 128 return time.Duration(i) * time.Second, nil 129 } 130 131 // 10.000 132 before, after, _ := strings.Cut(s, ".") 133 var allZeroes = true 134 for r := range after { 135 if r != '0' { 136 allZeroes = false 137 } 138 } 139 if allZeroes { 140 i, err := strconv.Atoi(before) 141 if err != nil { 142 return 0, err 143 } 144 return time.Duration(i) * time.Second, nil 145 } 146 seconds, err := strconv.ParseFloat(s, 32) 147 if err != nil { 148 return 0, err 149 } 150 // precision based on a 90KHz clock. 151 microseconds := seconds * 1e6 152 return time.Duration(microseconds) * time.Microsecond, nil 153 } 154 155 func parseKey(items chan item) (Key, error) { 156 var key Key 157 for it := range items { 158 switch it.typ { 159 case itemError: 160 return key, errors.New(it.val) 161 case itemNewline: 162 return key, nil 163 case itemComma: 164 continue 165 default: 166 if it.typ != itemAttrName { 167 return Key{}, fmt.Errorf("expected attribute name, got %s", it.val) 168 } 169 } 170 v := <-items 171 if v.typ != itemEquals { 172 return key, fmt.Errorf("expected %q after %s, got %s", "=", it.typ, v) 173 } 174 175 switch it.val { 176 case "METHOD": 177 v = <-items 178 key.Method = parseEncryptMethod(v.val) 179 if key.Method == encryptMethodInvalid { 180 return key, fmt.Errorf("bad encrypt method %q", v.val) 181 } 182 case "URI": 183 v = <-items 184 key.URI = strings.Trim(v.val, `"`) 185 case "IV": 186 v = <-items 187 b, err := hex.DecodeString(strings.TrimPrefix(v.val, "0x")) 188 if err != nil { 189 return key, fmt.Errorf("parse initialisation vector: %w", err) 190 } 191 if len(b) != len(key.IV) { 192 return key, fmt.Errorf("bad initialisation length %d, want %d", len(b), len(key.IV)) 193 } 194 copy(key.IV[:], b) 195 case "KEYFORMAT": 196 v = <-items 197 key.Format = strings.Trim(v.val, `"`) 198 case "KEYFORMATVERSIONS": 199 v = <-items 200 ss := strings.Split(v.val, "/") 201 key.FormatVersions = make([]uint32, len(ss)) 202 for i := range ss { 203 n, err := strconv.Atoi(ss[i]) 204 if err != nil { 205 return key, fmt.Errorf("parse key format version: %w", err) 206 } 207 key.FormatVersions[i] = uint32(n) 208 } 209 default: 210 return key, fmt.Errorf("unexpected attribute %q", it.val) 211 } 212 } 213 return key, fmt.Errorf("unexpected end of tag") 214 } 215 216 func parseMap(items chan item) (Map, error) { 217 var mmap Map 218 for it := range items { 219 switch it.typ { 220 case itemError: 221 return mmap, errors.New(it.val) 222 case itemNewline: 223 return mmap, nil 224 } 225 if it.typ != itemAttrName { 226 return Map{}, fmt.Errorf("unexpected %s %q", it.typ, it.val) 227 } 228 attr := it.val 229 it = <-items 230 if it.typ != itemEquals { 231 return Map{}, fmt.Errorf("expected %q after %s, got %q", "=", attr, it.val) 232 } 233 234 it = <-items 235 switch attr { 236 case "URI": 237 mmap.URI = strings.Trim(it.val, `"`) 238 case "BYTERANGE": 239 r, err := parseByteRange(it.val) 240 if err != nil { 241 return Map{}, fmt.Errorf("parse byte range: %w", err) 242 } 243 mmap.ByteRange = r 244 default: 245 return Map{}, fmt.Errorf("unexpected attribute %q", it.val) 246 } 247 } 248 return Map{}, fmt.Errorf("unexpected end of tag") 249 } 250 251 func writeSegments(w io.Writer, segments []Segment) (n int, err error) { 252 for i, seg := range segments { 253 b, err := seg.MarshalText() 254 if err != nil { 255 return n, fmt.Errorf("segment %d: %w", i, err) 256 } 257 nn, err := fmt.Fprintln(w, string(b)) 258 n += nn 259 if err != nil { 260 return n, err 261 } 262 } 263 return n, nil 264 } 265 266 func (seg *Segment) MarshalText() ([]byte, error) { 267 if seg.URI == "" { 268 return nil, fmt.Errorf("empty URI") 269 } 270 if seg.Duration == 0 { 271 return nil, fmt.Errorf("zero duration") 272 } 273 var tags []string 274 if seg.Discontinuity { 275 tags = append(tags, tagDiscontinuity) 276 } 277 if seg.DateRange != nil { 278 buf := &bytes.Buffer{} 279 if err := writeDateRange(buf, seg.DateRange); err != nil { 280 return nil, fmt.Errorf("write date range: %w", err) 281 } 282 tags = append(tags, buf.String()) 283 } 284 if seg.Range != [2]int{0, 0} { 285 if seg.Range[0] >= seg.Range[1] { 286 return nil, fmt.Errorf("impossible range: offset (%d) must be smaller than next %d", seg.Range[0], seg.Range[1]) 287 } 288 tags = append(tags, fmt.Sprintf("%s:%s", tagByteRange, seg.Range)) 289 } 290 if seg.Key != nil { 291 tags = append(tags, seg.Key.String()) 292 } 293 if seg.Map != nil { 294 tags = append(tags, seg.Map.String()) 295 } 296 if !seg.DateTime.IsZero() { 297 tags = append(tags, fmt.Sprintf("%s:%s", tagDateTime, seg.DateTime.Format(rfc3339Milli))) 298 } 299 us := seg.Duration / time.Microsecond 300 // we do .03f for the same precision as test-streams.mux.dev. 301 durTag := fmt.Sprintf("%s:%.03f", tagSegmentDuration, float32(us)/1e6) 302 if seg.Title != "" { 303 durTag += ","+seg.Title 304 } 305 tags = append(tags, durTag) 306 tags = append(tags, seg.URI) 307 return []byte(strings.Join(tags, "\n")), nil 308 }