reader.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701
  1. package m3u8
  2. /*
  3. Part of M3U8 parser & generator library.
  4. This file defines functions related to playlist parsing.
  5. Copyright 2013-2017 The Project Developers.
  6. See the AUTHORS and LICENSE files at the top-level directory of this distribution
  7. and at https://github.com/grafov/m3u8/
  8. ॐ तारे तुत्तारे तुरे स्व
  9. */
  10. import (
  11. "bytes"
  12. "errors"
  13. "fmt"
  14. "io"
  15. "regexp"
  16. "strconv"
  17. "strings"
  18. "time"
  19. )
  20. var reKeyValue = regexp.MustCompile(`([a-zA-Z0-9_-]+)=("[^"]+"|[^",]+)`)
  21. // Allow globally apply and/or override Time Parser function.
  22. // Available variants:
  23. // * FullTimeParse - implements full featured ISO/IEC 8601:2004
  24. // * StrictTimeParse - implements only RFC3339 Nanoseconds format
  25. var TimeParse func(value string) (time.Time, error) = FullTimeParse
  26. // Decode parses a master playlist passed from the buffer. If `strict`
  27. // parameter is true then it returns first syntax error.
  28. func (p *MasterPlaylist) Decode(data bytes.Buffer, strict bool) error {
  29. return p.decode(&data, strict)
  30. }
  31. // DecodeFrom parses a master playlist passed from the io.Reader
  32. // stream. If `strict` parameter is true then it returns first syntax
  33. // error.
  34. func (p *MasterPlaylist) DecodeFrom(reader io.Reader, strict bool) error {
  35. buf := new(bytes.Buffer)
  36. _, err := buf.ReadFrom(reader)
  37. if err != nil {
  38. return err
  39. }
  40. return p.decode(buf, strict)
  41. }
  42. // Parse master playlist. Internal function.
  43. func (p *MasterPlaylist) decode(buf *bytes.Buffer, strict bool) error {
  44. var eof bool
  45. state := new(decodingState)
  46. for !eof {
  47. line, err := buf.ReadString('\n')
  48. if err == io.EOF {
  49. eof = true
  50. } else if err != nil {
  51. break
  52. }
  53. err = decodeLineOfMasterPlaylist(p, state, line, strict)
  54. if strict && err != nil {
  55. return err
  56. }
  57. }
  58. if strict && !state.m3u {
  59. return errors.New("#EXTM3U absent")
  60. }
  61. return nil
  62. }
  63. // Decode parses a media playlist passed from the buffer. If `strict`
  64. // parameter is true then return first syntax error.
  65. func (p *MediaPlaylist) Decode(data bytes.Buffer, strict bool) error {
  66. return p.decode(&data, strict)
  67. }
  68. // DecodeFrom parses a media playlist passed from the io.Reader
  69. // stream. If `strict` parameter is true then it returns first syntax
  70. // error.
  71. func (p *MediaPlaylist) DecodeFrom(reader io.Reader, strict bool) error {
  72. buf := new(bytes.Buffer)
  73. _, err := buf.ReadFrom(reader)
  74. if err != nil {
  75. return err
  76. }
  77. return p.decode(buf, strict)
  78. }
  79. func (p *MediaPlaylist) decode(buf *bytes.Buffer, strict bool) error {
  80. var eof bool
  81. var line string
  82. var err error
  83. state := new(decodingState)
  84. wv := new(WV)
  85. for !eof {
  86. if line, err = buf.ReadString('\n'); err == io.EOF {
  87. eof = true
  88. } else if err != nil {
  89. break
  90. }
  91. err = decodeLineOfMediaPlaylist(p, wv, state, line, strict)
  92. if strict && err != nil {
  93. return err
  94. }
  95. }
  96. if state.tagWV {
  97. p.WV = wv
  98. }
  99. if strict && !state.m3u {
  100. return errors.New("#EXTM3U absent")
  101. }
  102. return nil
  103. }
  104. // Decode detects type of playlist and decodes it. It accepts bytes
  105. // buffer as input.
  106. func Decode(data bytes.Buffer, strict bool) (Playlist, ListType, error) {
  107. return decode(&data, strict)
  108. }
  109. // DecodeFrom detects type of playlist and decodes it. It accepts data
  110. // conformed with io.Reader.
  111. func DecodeFrom(reader io.Reader, strict bool) (Playlist, ListType, error) {
  112. buf := new(bytes.Buffer)
  113. _, err := buf.ReadFrom(reader)
  114. if err != nil {
  115. return nil, 0, err
  116. }
  117. return decode(buf, strict)
  118. }
  119. // Detect playlist type and decode it. May be used as decoder for both
  120. // master and media playlists.
  121. func decode(buf *bytes.Buffer, strict bool) (Playlist, ListType, error) {
  122. var eof bool
  123. var line string
  124. var master *MasterPlaylist
  125. var media *MediaPlaylist
  126. var listType ListType
  127. var err error
  128. state := new(decodingState)
  129. wv := new(WV)
  130. master = NewMasterPlaylist()
  131. media, err = NewMediaPlaylist(8, 1024) // Winsize for VoD will become 0, capacity auto extends
  132. if err != nil {
  133. return nil, 0, fmt.Errorf("Create media playlist failed: %s", err)
  134. }
  135. for !eof {
  136. if line, err = buf.ReadString('\n'); err == io.EOF {
  137. eof = true
  138. } else if err != nil {
  139. break
  140. }
  141. // fixes the issues https://github.com/grafov/m3u8/issues/25
  142. // TODO: the same should be done in decode functions of both Master- and MediaPlaylists
  143. // so some DRYing would be needed.
  144. if len(line) < 1 || line == "\r" {
  145. continue
  146. }
  147. err = decodeLineOfMasterPlaylist(master, state, line, strict)
  148. if strict && err != nil {
  149. return master, state.listType, err
  150. }
  151. err = decodeLineOfMediaPlaylist(media, wv, state, line, strict)
  152. if strict && err != nil {
  153. return media, state.listType, err
  154. }
  155. }
  156. if state.listType == MEDIA && state.tagWV {
  157. media.WV = wv
  158. }
  159. if strict && !state.m3u {
  160. return nil, listType, errors.New("#EXTM3U absent")
  161. }
  162. switch state.listType {
  163. case MASTER:
  164. return master, MASTER, nil
  165. case MEDIA:
  166. if media.Closed || media.MediaType == EVENT {
  167. // VoD and Event's should show the entire playlist
  168. media.SetWinSize(0)
  169. }
  170. return media, MEDIA, nil
  171. }
  172. return nil, state.listType, errors.New("Can't detect playlist type")
  173. }
  174. func decodeParamsLine(line string) map[string]string {
  175. out := make(map[string]string)
  176. for _, kv := range reKeyValue.FindAllStringSubmatch(line, -1) {
  177. k, v := kv[1], kv[2]
  178. out[k] = strings.Trim(v, ` "`)
  179. }
  180. return out
  181. }
  182. // Parse one line of master playlist.
  183. func decodeLineOfMasterPlaylist(p *MasterPlaylist, state *decodingState, line string, strict bool) error {
  184. var err error
  185. line = strings.TrimSpace(line)
  186. switch {
  187. case line == "#EXTM3U": // start tag first
  188. state.m3u = true
  189. case strings.HasPrefix(line, "#EXT-X-VERSION:"): // version tag
  190. state.listType = MASTER
  191. _, err = fmt.Sscanf(line, "#EXT-X-VERSION:%d", &p.ver)
  192. if strict && err != nil {
  193. return err
  194. }
  195. case strings.HasPrefix(line, "#EXT-X-MEDIA:"):
  196. var alt Alternative
  197. state.listType = MASTER
  198. for k, v := range decodeParamsLine(line[13:]) {
  199. switch k {
  200. case "TYPE":
  201. alt.Type = v
  202. case "GROUP-ID":
  203. alt.GroupId = v
  204. case "LANGUAGE":
  205. alt.Language = v
  206. case "NAME":
  207. alt.Name = v
  208. case "DEFAULT":
  209. if strings.ToUpper(v) == "YES" {
  210. alt.Default = true
  211. } else if strings.ToUpper(v) == "NO" {
  212. alt.Default = false
  213. } else if strict {
  214. return errors.New("value must be YES or NO")
  215. }
  216. case "AUTOSELECT":
  217. alt.Autoselect = v
  218. case "FORCED":
  219. alt.Forced = v
  220. case "CHARACTERISTICS":
  221. alt.Characteristics = v
  222. case "SUBTITLES":
  223. alt.Subtitles = v
  224. case "URI":
  225. alt.URI = v
  226. }
  227. }
  228. state.alternatives = append(state.alternatives, &alt)
  229. case !state.tagStreamInf && strings.HasPrefix(line, "#EXT-X-STREAM-INF:"):
  230. state.tagStreamInf = true
  231. state.listType = MASTER
  232. state.variant = new(Variant)
  233. if len(state.alternatives) > 0 {
  234. state.variant.Alternatives = state.alternatives
  235. state.alternatives = nil
  236. }
  237. p.Variants = append(p.Variants, state.variant)
  238. for k, v := range decodeParamsLine(line[18:]) {
  239. switch k {
  240. case "PROGRAM-ID":
  241. var val int
  242. val, err = strconv.Atoi(v)
  243. if strict && err != nil {
  244. return err
  245. }
  246. state.variant.ProgramId = uint32(val)
  247. case "BANDWIDTH":
  248. var val int
  249. val, err = strconv.Atoi(v)
  250. if strict && err != nil {
  251. return err
  252. }
  253. state.variant.Bandwidth = uint32(val)
  254. case "CODECS":
  255. state.variant.Codecs = v
  256. case "RESOLUTION":
  257. state.variant.Resolution = v
  258. case "AUDIO":
  259. state.variant.Audio = v
  260. case "VIDEO":
  261. state.variant.Video = v
  262. case "SUBTITLES":
  263. state.variant.Subtitles = v
  264. case "CLOSED-CAPTIONS":
  265. state.variant.Captions = v
  266. case "NAME":
  267. state.variant.Name = v
  268. }
  269. }
  270. case state.tagStreamInf && !strings.HasPrefix(line, "#"):
  271. state.tagStreamInf = false
  272. state.variant.URI = line
  273. case strings.HasPrefix(line, "#EXT-X-I-FRAME-STREAM-INF:"):
  274. state.listType = MASTER
  275. state.variant = new(Variant)
  276. state.variant.Iframe = true
  277. if len(state.alternatives) > 0 {
  278. state.variant.Alternatives = state.alternatives
  279. state.alternatives = nil
  280. }
  281. p.Variants = append(p.Variants, state.variant)
  282. for k, v := range decodeParamsLine(line[26:]) {
  283. switch k {
  284. case "URI":
  285. state.variant.URI = v
  286. case "PROGRAM-ID":
  287. var val int
  288. val, err = strconv.Atoi(v)
  289. if strict && err != nil {
  290. return err
  291. }
  292. state.variant.ProgramId = uint32(val)
  293. case "BANDWIDTH":
  294. var val int
  295. val, err = strconv.Atoi(v)
  296. if strict && err != nil {
  297. return err
  298. }
  299. state.variant.Bandwidth = uint32(val)
  300. case "CODECS":
  301. state.variant.Codecs = v
  302. case "RESOLUTION":
  303. state.variant.Resolution = v
  304. case "AUDIO":
  305. state.variant.Audio = v
  306. case "VIDEO":
  307. state.variant.Video = v
  308. }
  309. }
  310. case strings.HasPrefix(line, "#"): // unknown tags treated as comments
  311. return err
  312. }
  313. return err
  314. }
  315. // Parse one line of media playlist.
  316. func decodeLineOfMediaPlaylist(p *MediaPlaylist, wv *WV, state *decodingState, line string, strict bool) error {
  317. var err error
  318. line = strings.TrimSpace(line)
  319. switch {
  320. case !state.tagInf && strings.HasPrefix(line, "#EXTINF:"):
  321. state.tagInf = true
  322. state.listType = MEDIA
  323. sepIndex := strings.Index(line, ",")
  324. if sepIndex == -1 {
  325. if strict {
  326. return fmt.Errorf("could not parse: %q", line)
  327. }
  328. sepIndex = len(line)
  329. }
  330. duration := line[8:sepIndex]
  331. if len(duration) > 0 {
  332. if state.duration, err = strconv.ParseFloat(duration, 64); strict && err != nil {
  333. return fmt.Errorf("Duration parsing error: %s", err)
  334. }
  335. }
  336. if len(line) > sepIndex {
  337. state.title = line[sepIndex+1:]
  338. }
  339. case !strings.HasPrefix(line, "#"):
  340. if state.tagInf {
  341. err := p.Append(line, state.duration, state.title)
  342. if err == ErrPlaylistFull {
  343. // Extend playlist by doubling size, reset internal state, try again.
  344. // If the second Append fails, the if err block will handle it.
  345. // Retrying instead of being recursive was chosen as the state maybe
  346. // modified non-idempotently.
  347. p.Segments = append(p.Segments, make([]*MediaSegment, p.Count())...)
  348. p.capacity = uint(len(p.Segments))
  349. p.tail = p.count
  350. err = p.Append(line, state.duration, state.title)
  351. }
  352. // Check err for first or subsequent Append()
  353. if err != nil {
  354. return err
  355. }
  356. state.tagInf = false
  357. }
  358. if state.tagRange {
  359. if err = p.SetRange(state.limit, state.offset); strict && err != nil {
  360. return err
  361. }
  362. state.tagRange = false
  363. }
  364. if state.tagSCTE35 {
  365. state.tagSCTE35 = false
  366. if err = p.SetSCTE35(state.scte); strict && err != nil {
  367. return err
  368. }
  369. }
  370. if state.tagDiscontinuity {
  371. state.tagDiscontinuity = false
  372. if err = p.SetDiscontinuity(); strict && err != nil {
  373. return err
  374. }
  375. }
  376. if state.tagProgramDateTime {
  377. state.tagProgramDateTime = false
  378. if err = p.SetProgramDateTime(state.programDateTime); strict && err != nil {
  379. return err
  380. }
  381. }
  382. // If EXT-X-KEY appeared before reference to segment (EXTINF) then it linked to this segment
  383. if state.tagKey {
  384. p.Segments[p.last()].Key = &Key{state.xkey.Method, state.xkey.URI, state.xkey.IV, state.xkey.Keyformat, state.xkey.Keyformatversions}
  385. // First EXT-X-KEY may appeared in the header of the playlist and linked to first segment
  386. // but for convenient playlist generation it also linked as default playlist key
  387. if p.Key == nil {
  388. p.Key = state.xkey
  389. }
  390. state.tagKey = false
  391. }
  392. // If EXT-X-MAP appeared before reference to segment (EXTINF) then it linked to this segment
  393. if state.tagMap {
  394. p.Segments[p.last()].Map = &Map{state.xmap.URI, state.xmap.Limit, state.xmap.Offset}
  395. // First EXT-X-MAP may appeared in the header of the playlist and linked to first segment
  396. // but for convenient playlist generation it also linked as default playlist map
  397. if p.Map == nil {
  398. p.Map = state.xmap
  399. }
  400. state.tagMap = false
  401. }
  402. // start tag first
  403. case line == "#EXTM3U":
  404. state.m3u = true
  405. case line == "#EXT-X-ENDLIST":
  406. state.listType = MEDIA
  407. p.Closed = true
  408. case strings.HasPrefix(line, "#EXT-X-VERSION:"):
  409. state.listType = MEDIA
  410. if _, err = fmt.Sscanf(line, "#EXT-X-VERSION:%d", &p.ver); strict && err != nil {
  411. return err
  412. }
  413. case strings.HasPrefix(line, "#EXT-X-TARGETDURATION:"):
  414. state.listType = MEDIA
  415. if _, err = fmt.Sscanf(line, "#EXT-X-TARGETDURATION:%f", &p.TargetDuration); strict && err != nil {
  416. return err
  417. }
  418. case strings.HasPrefix(line, "#EXT-X-MEDIA-SEQUENCE:"):
  419. state.listType = MEDIA
  420. if _, err = fmt.Sscanf(line, "#EXT-X-MEDIA-SEQUENCE:%d", &p.SeqNo); strict && err != nil {
  421. return err
  422. }
  423. case strings.HasPrefix(line, "#EXT-X-PLAYLIST-TYPE:"):
  424. state.listType = MEDIA
  425. var playlistType string
  426. _, err = fmt.Sscanf(line, "#EXT-X-PLAYLIST-TYPE:%s", &playlistType)
  427. if err != nil {
  428. if strict {
  429. return err
  430. }
  431. } else {
  432. switch playlistType {
  433. case "EVENT":
  434. p.MediaType = EVENT
  435. case "VOD":
  436. p.MediaType = VOD
  437. }
  438. }
  439. case strings.HasPrefix(line, "#EXT-X-KEY:"):
  440. state.listType = MEDIA
  441. state.xkey = new(Key)
  442. for k, v := range decodeParamsLine(line[11:]) {
  443. switch k {
  444. case "METHOD":
  445. state.xkey.Method = v
  446. case "URI":
  447. state.xkey.URI = v
  448. case "IV":
  449. state.xkey.IV = v
  450. case "KEYFORMAT":
  451. state.xkey.Keyformat = v
  452. case "KEYFORMATVERSIONS":
  453. state.xkey.Keyformatversions = v
  454. }
  455. }
  456. state.tagKey = true
  457. case strings.HasPrefix(line, "#EXT-X-MAP:"):
  458. state.listType = MEDIA
  459. state.xmap = new(Map)
  460. for k, v := range decodeParamsLine(line[11:]) {
  461. switch k {
  462. case "URI":
  463. state.xmap.URI = v
  464. case "BYTERANGE":
  465. if _, err = fmt.Sscanf(v, "%d@%d", &state.xmap.Limit, &state.xmap.Offset); strict && err != nil {
  466. return fmt.Errorf("Byterange sub-range length value parsing error: %s", err)
  467. }
  468. }
  469. }
  470. state.tagMap = true
  471. case !state.tagProgramDateTime && strings.HasPrefix(line, "#EXT-X-PROGRAM-DATE-TIME:"):
  472. state.tagProgramDateTime = true
  473. state.listType = MEDIA
  474. if state.programDateTime, err = TimeParse(line[25:]); strict && err != nil {
  475. return err
  476. }
  477. case !state.tagRange && strings.HasPrefix(line, "#EXT-X-BYTERANGE:"):
  478. state.tagRange = true
  479. state.listType = MEDIA
  480. state.offset = 0
  481. params := strings.SplitN(line[17:], "@", 2)
  482. if state.limit, err = strconv.ParseInt(params[0], 10, 64); strict && err != nil {
  483. return fmt.Errorf("Byterange sub-range length value parsing error: %s", err)
  484. }
  485. if len(params) > 1 {
  486. if state.offset, err = strconv.ParseInt(params[1], 10, 64); strict && err != nil {
  487. return fmt.Errorf("Byterange sub-range offset value parsing error: %s", err)
  488. }
  489. }
  490. case !state.tagSCTE35 && strings.HasPrefix(line, "#EXT-SCTE35:"):
  491. state.tagSCTE35 = true
  492. state.listType = MEDIA
  493. state.scte = new(SCTE)
  494. state.scte.Syntax = SCTE35_67_2014
  495. for attribute, value := range decodeParamsLine(line[12:]) {
  496. switch attribute {
  497. case "CUE":
  498. state.scte.Cue = value
  499. case "ID":
  500. state.scte.ID = value
  501. case "TIME":
  502. state.scte.Time, _ = strconv.ParseFloat(value, 64)
  503. }
  504. }
  505. case !state.tagSCTE35 && strings.HasPrefix(line, "#EXT-OATCLS-SCTE35:"):
  506. // EXT-OATCLS-SCTE35 contains the SCTE35 tag, EXT-X-CUE-OUT contains duration
  507. state.tagSCTE35 = true
  508. state.scte = new(SCTE)
  509. state.scte.Syntax = SCTE35_OATCLS
  510. state.scte.Cue = line[19:]
  511. case state.tagSCTE35 && state.scte.Syntax == SCTE35_OATCLS && strings.HasPrefix(line, "#EXT-X-CUE-OUT:"):
  512. // EXT-OATCLS-SCTE35 contains the SCTE35 tag, EXT-X-CUE-OUT contains duration
  513. state.scte.Time, _ = strconv.ParseFloat(line[15:], 64)
  514. state.scte.CueType = SCTE35Cue_Start
  515. case !state.tagSCTE35 && strings.HasPrefix(line, "#EXT-X-CUE-OUT-CONT:"):
  516. state.tagSCTE35 = true
  517. state.scte = new(SCTE)
  518. state.scte.Syntax = SCTE35_OATCLS
  519. state.scte.CueType = SCTE35Cue_Mid
  520. for attribute, value := range decodeParamsLine(line[20:]) {
  521. switch attribute {
  522. case "SCTE35":
  523. state.scte.Cue = value
  524. case "Duration":
  525. state.scte.Time, _ = strconv.ParseFloat(value, 64)
  526. case "ElapsedTime":
  527. state.scte.Elapsed, _ = strconv.ParseFloat(value, 64)
  528. }
  529. }
  530. case !state.tagSCTE35 && line == "#EXT-X-CUE-IN":
  531. state.tagSCTE35 = true
  532. state.scte = new(SCTE)
  533. state.scte.Syntax = SCTE35_OATCLS
  534. state.scte.CueType = SCTE35Cue_End
  535. case !state.tagDiscontinuity && strings.HasPrefix(line, "#EXT-X-DISCONTINUITY"):
  536. state.tagDiscontinuity = true
  537. state.listType = MEDIA
  538. case strings.HasPrefix(line, "#EXT-X-I-FRAMES-ONLY"):
  539. state.listType = MEDIA
  540. p.Iframe = true
  541. case strings.HasPrefix(line, "#WV-AUDIO-CHANNELS"):
  542. state.listType = MEDIA
  543. if _, err = fmt.Sscanf(line, "#WV-AUDIO-CHANNELS %d", &wv.AudioChannels); strict && err != nil {
  544. return err
  545. }
  546. if err == nil {
  547. state.tagWV = true
  548. }
  549. case strings.HasPrefix(line, "#WV-AUDIO-FORMAT"):
  550. state.listType = MEDIA
  551. if _, err = fmt.Sscanf(line, "#WV-AUDIO-FORMAT %d", &wv.AudioFormat); strict && err != nil {
  552. return err
  553. }
  554. if err == nil {
  555. state.tagWV = true
  556. }
  557. case strings.HasPrefix(line, "#WV-AUDIO-PROFILE-IDC"):
  558. state.listType = MEDIA
  559. if _, err = fmt.Sscanf(line, "#WV-AUDIO-PROFILE-IDC %d", &wv.AudioProfileIDC); strict && err != nil {
  560. return err
  561. }
  562. if err == nil {
  563. state.tagWV = true
  564. }
  565. case strings.HasPrefix(line, "#WV-AUDIO-SAMPLE-SIZE"):
  566. state.listType = MEDIA
  567. if _, err = fmt.Sscanf(line, "#WV-AUDIO-SAMPLE-SIZE %d", &wv.AudioSampleSize); strict && err != nil {
  568. return err
  569. }
  570. if err == nil {
  571. state.tagWV = true
  572. }
  573. case strings.HasPrefix(line, "#WV-AUDIO-SAMPLING-FREQUENCY"):
  574. state.listType = MEDIA
  575. if _, err = fmt.Sscanf(line, "#WV-AUDIO-SAMPLING-FREQUENCY %d", &wv.AudioSamplingFrequency); strict && err != nil {
  576. return err
  577. }
  578. if err == nil {
  579. state.tagWV = true
  580. }
  581. case strings.HasPrefix(line, "#WV-CYPHER-VERSION"):
  582. state.listType = MEDIA
  583. wv.CypherVersion = line[19:]
  584. state.tagWV = true
  585. case strings.HasPrefix(line, "#WV-ECM"):
  586. state.listType = MEDIA
  587. if _, err = fmt.Sscanf(line, "#WV-ECM %s", &wv.ECM); strict && err != nil {
  588. return err
  589. }
  590. if err == nil {
  591. state.tagWV = true
  592. }
  593. case strings.HasPrefix(line, "#WV-VIDEO-FORMAT"):
  594. state.listType = MEDIA
  595. if _, err = fmt.Sscanf(line, "#WV-VIDEO-FORMAT %d", &wv.VideoFormat); strict && err != nil {
  596. return err
  597. }
  598. if err == nil {
  599. state.tagWV = true
  600. }
  601. case strings.HasPrefix(line, "#WV-VIDEO-FRAME-RATE"):
  602. state.listType = MEDIA
  603. if _, err = fmt.Sscanf(line, "#WV-VIDEO-FRAME-RATE %d", &wv.VideoFrameRate); strict && err != nil {
  604. return err
  605. }
  606. if err == nil {
  607. state.tagWV = true
  608. }
  609. case strings.HasPrefix(line, "#WV-VIDEO-LEVEL-IDC"):
  610. state.listType = MEDIA
  611. if _, err = fmt.Sscanf(line, "#WV-VIDEO-LEVEL-IDC %d", &wv.VideoLevelIDC); strict && err != nil {
  612. return err
  613. }
  614. if err == nil {
  615. state.tagWV = true
  616. }
  617. case strings.HasPrefix(line, "#WV-VIDEO-PROFILE-IDC"):
  618. state.listType = MEDIA
  619. if _, err = fmt.Sscanf(line, "#WV-VIDEO-PROFILE-IDC %d", &wv.VideoProfileIDC); strict && err != nil {
  620. return err
  621. }
  622. if err == nil {
  623. state.tagWV = true
  624. }
  625. case strings.HasPrefix(line, "#WV-VIDEO-RESOLUTION"):
  626. state.listType = MEDIA
  627. wv.VideoResolution = line[21:]
  628. state.tagWV = true
  629. case strings.HasPrefix(line, "#WV-VIDEO-SAR"):
  630. state.listType = MEDIA
  631. if _, err = fmt.Sscanf(line, "#WV-VIDEO-SAR %s", &wv.VideoSAR); strict && err != nil {
  632. return err
  633. }
  634. if err == nil {
  635. state.tagWV = true
  636. }
  637. case strings.HasPrefix(line, "#"): // unknown tags treated as comments
  638. return err
  639. }
  640. return err
  641. }
  642. // StrictTimeParse implements RFC3339 with Nanoseconds accuracy.
  643. func StrictTimeParse(value string) (time.Time, error) {
  644. return time.Parse(DATETIME, value)
  645. }
  646. // FullTimeParse implements ISO/IEC 8601:2004.
  647. func FullTimeParse(value string) (time.Time, error) {
  648. layouts := []string{
  649. "2006-01-02T15:04:05.999999999Z0700",
  650. "2006-01-02T15:04:05.999999999Z07:00",
  651. "2006-01-02T15:04:05.999999999Z07",
  652. }
  653. var (
  654. err error
  655. t time.Time
  656. )
  657. for _, layout := range layouts {
  658. if t, err = time.Parse(layout, value); err == nil {
  659. return t, nil
  660. }
  661. }
  662. return t, err
  663. }