m3u8 1.7.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +1 -1
- data/.gitignore +1 -1
- data/.rubocop.yml +2 -2
- data/CHANGELOG.md +18 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +1 -1
- data/README.md +1 -1
- data/lib/m3u8/attribute_formatter.rb +30 -0
- data/lib/m3u8/bitrate_item.rb +7 -0
- data/lib/m3u8/builder.rb +1 -0
- data/lib/m3u8/byte_range.rb +8 -0
- data/lib/m3u8/codecs.rb +89 -0
- data/lib/m3u8/content_steering_item.rb +11 -12
- data/lib/m3u8/date_range_item.rb +103 -127
- data/lib/m3u8/define_item.rb +10 -0
- data/lib/m3u8/discontinuity_item.rb +2 -0
- data/lib/m3u8/encryptable.rb +26 -30
- data/lib/m3u8/gap_item.rb +2 -0
- data/lib/m3u8/key_item.rb +6 -0
- data/lib/m3u8/map_item.rb +14 -6
- data/lib/m3u8/media_item.rb +42 -110
- data/lib/m3u8/part_inf_item.rb +7 -0
- data/lib/m3u8/part_item.rb +16 -18
- data/lib/m3u8/playback_start.rb +17 -13
- data/lib/m3u8/playlist.rb +65 -6
- data/lib/m3u8/playlist_item.rb +113 -220
- data/lib/m3u8/preload_hint_item.rb +16 -28
- data/lib/m3u8/reader.rb +19 -24
- data/lib/m3u8/rendition_report_item.rb +13 -23
- data/lib/m3u8/scte35.rb +2 -1
- data/lib/m3u8/scte35_segmentation_descriptor.rb +3 -1
- data/lib/m3u8/segment_item.rb +20 -1
- data/lib/m3u8/server_control_item.rb +15 -21
- data/lib/m3u8/session_data_item.rb +15 -28
- data/lib/m3u8/session_key_item.rb +7 -1
- data/lib/m3u8/skip_item.rb +12 -12
- data/lib/m3u8/time_item.rb +7 -0
- data/lib/m3u8/version.rb +1 -1
- data/lib/m3u8/writer.rb +5 -0
- data/lib/m3u8.rb +24 -1
- data/m3u8.gemspec +10 -13
- data/spec/lib/m3u8/date_range_item_spec.rb +4 -7
- data/spec/lib/m3u8/playback_start_spec.rb +3 -5
- data/spec/lib/m3u8/playlist_item_spec.rb +11 -9
- data/spec/lib/m3u8/segment_item_spec.rb +23 -0
- data/spec/spec_helper.rb +3 -1
- metadata +10 -119
- data/.hound.yml +0 -3
- data/Guardfile +0 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 395231edf55669bc7358629d116d5f7486a74bf1e7c5f6f9287497b758178511
|
|
4
|
+
data.tar.gz: f1592e2cd9cc9d175e3f06c7b330baffea7271ff1c153895865c35ed96e1c17e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7e0c36b5d0afbd29e1d822c020e0302c052c1051c2637e90795772165f9ca5f2a452ad9e4da593d895f88dbf3527d831829dc741dfbb9d2b88b9f8223f02b3c0
|
|
7
|
+
data.tar.gz: d7631c7d2cf13392f4a317c00f0cf7f90097695da58d20cdb5a76d66db01ce2e93643c48cfbf30d40f9b20260c793ffb9e430f0aea0f99c5c25d8c9c253fca93
|
data/.github/workflows/ci.yml
CHANGED
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
AllCops:
|
|
2
|
-
TargetRubyVersion: 3.
|
|
2
|
+
TargetRubyVersion: 3.1
|
|
3
3
|
NewCops: enable
|
|
4
4
|
SuggestExtensions: false
|
|
5
5
|
Exclude:
|
|
@@ -24,7 +24,7 @@ Metrics/CyclomaticComplexity:
|
|
|
24
24
|
Metrics/PerceivedComplexity:
|
|
25
25
|
Enabled: false
|
|
26
26
|
Layout/LineLength:
|
|
27
|
-
Max:
|
|
27
|
+
Max: 80
|
|
28
28
|
Exclude:
|
|
29
29
|
- 'spec/**/*'
|
|
30
30
|
Style/StringLiterals:
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
**1.8.0**
|
|
2
|
+
|
|
3
|
+
* Standardized all item classes to use class-level `self.parse` methods,
|
|
4
|
+
converting `PlaybackStart`, `DateRangeItem`, `PlaylistItem`, and
|
|
5
|
+
`SegmentItem` from instance-level parse.
|
|
6
|
+
* Applied `AttributeFormatter` to `MapItem` and `PartItem`, replacing
|
|
7
|
+
manual format helpers with `quoted_format` and `unquoted_format`.
|
|
8
|
+
* Extracted `tag_value` helper in `Reader`, replacing repeated `gsub`
|
|
9
|
+
tag-prefix patterns.
|
|
10
|
+
* Normalized `Reader` lambda syntax, converting `proc` blocks to
|
|
11
|
+
lambdas.
|
|
12
|
+
* Removed `include M3u8` from `Reader` since all parsing now uses
|
|
13
|
+
class-level methods.
|
|
14
|
+
* Added YARD `@param`/`@return` documentation to all public methods
|
|
15
|
+
and attributes across the entire codebase.
|
|
16
|
+
|
|
17
|
+
***
|
|
18
|
+
|
|
1
19
|
**1.7.0**
|
|
2
20
|
|
|
3
21
|
* Added HLS Interstitials first-class `DateRangeItem` accessors:
|
data/Gemfile
CHANGED
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module M3u8
|
|
4
|
+
# Shared helpers for formatting HLS tag attributes
|
|
5
|
+
module AttributeFormatter
|
|
6
|
+
# Format a quoted attribute (e.g. KEY="value").
|
|
7
|
+
# @param key [String] attribute name
|
|
8
|
+
# @param value [Object, nil] attribute value
|
|
9
|
+
# @return [String, nil] formatted string or nil when value is nil
|
|
10
|
+
def quoted_format(key, value)
|
|
11
|
+
%(#{key}="#{value}") unless value.nil?
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Format an unquoted attribute (e.g. KEY=value).
|
|
15
|
+
# @param key [String] attribute name
|
|
16
|
+
# @param value [Object, nil] attribute value
|
|
17
|
+
# @return [String, nil] formatted string or nil when value is nil
|
|
18
|
+
def unquoted_format(key, value)
|
|
19
|
+
"#{key}=#{value}" unless value.nil?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Format a YES/NO boolean attribute (e.g. KEY=YES).
|
|
23
|
+
# @param key [String] attribute name
|
|
24
|
+
# @param value [Boolean, nil] attribute value
|
|
25
|
+
# @return [String, nil] formatted string or nil when value is nil
|
|
26
|
+
def boolean_format(key, value)
|
|
27
|
+
"#{key}=#{value == true ? 'YES' : 'NO'}" unless value.nil?
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
data/lib/m3u8/bitrate_item.rb
CHANGED
|
@@ -4,19 +4,26 @@ module M3u8
|
|
|
4
4
|
# BitrateItem represents an EXT-X-BITRATE tag that indicates the
|
|
5
5
|
# approximate bitrate of the following media segments in kbps.
|
|
6
6
|
class BitrateItem
|
|
7
|
+
# @return [Integer, nil] approximate bitrate in kbps
|
|
7
8
|
attr_accessor :bitrate
|
|
8
9
|
|
|
10
|
+
# @param params [Hash] attribute key-value pairs
|
|
9
11
|
def initialize(params = {})
|
|
10
12
|
params.each do |key, value|
|
|
11
13
|
instance_variable_set("@#{key}", value)
|
|
12
14
|
end
|
|
13
15
|
end
|
|
14
16
|
|
|
17
|
+
# Parse an EXT-X-BITRATE tag.
|
|
18
|
+
# @param text [String] raw tag line
|
|
19
|
+
# @return [BitrateItem]
|
|
15
20
|
def self.parse(text)
|
|
16
21
|
value = text.gsub('#EXT-X-BITRATE:', '').strip
|
|
17
22
|
BitrateItem.new(bitrate: value.to_i)
|
|
18
23
|
end
|
|
19
24
|
|
|
25
|
+
# Render as an m3u8 EXT-X-BITRATE tag.
|
|
26
|
+
# @return [String]
|
|
20
27
|
def to_s
|
|
21
28
|
"#EXT-X-BITRATE:#{bitrate}"
|
|
22
29
|
end
|
data/lib/m3u8/builder.rb
CHANGED
data/lib/m3u8/byte_range.rb
CHANGED
|
@@ -3,14 +3,20 @@
|
|
|
3
3
|
module M3u8
|
|
4
4
|
# ByteRange represents sub range of a resource
|
|
5
5
|
class ByteRange
|
|
6
|
+
# @return [Integer, nil] number of bytes
|
|
7
|
+
# @return [Integer, nil] start offset in bytes
|
|
6
8
|
attr_accessor :length, :start
|
|
7
9
|
|
|
10
|
+
# @param params [Hash] :length and optional :start
|
|
8
11
|
def initialize(params = {})
|
|
9
12
|
params.each do |key, value|
|
|
10
13
|
instance_variable_set("@#{key}", value)
|
|
11
14
|
end
|
|
12
15
|
end
|
|
13
16
|
|
|
17
|
+
# Parse a byte range string (e.g. "4500@600").
|
|
18
|
+
# @param text [String] byte range string
|
|
19
|
+
# @return [ByteRange]
|
|
14
20
|
def self.parse(text)
|
|
15
21
|
values = text.split('@')
|
|
16
22
|
length_value = values[0].to_i
|
|
@@ -19,6 +25,8 @@ module M3u8
|
|
|
19
25
|
ByteRange.new(options)
|
|
20
26
|
end
|
|
21
27
|
|
|
28
|
+
# Render as a byte range string (e.g. "4500@600").
|
|
29
|
+
# @return [String]
|
|
22
30
|
def to_s
|
|
23
31
|
"#{length}#{start_format}"
|
|
24
32
|
end
|
data/lib/m3u8/codecs.rb
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module M3u8
|
|
4
|
+
# Codec lookup tables for HLS playlist items
|
|
5
|
+
module Codecs
|
|
6
|
+
AUDIO_CODECS = {
|
|
7
|
+
'aac-lc' => 'mp4a.40.2',
|
|
8
|
+
'he-aac' => 'mp4a.40.5',
|
|
9
|
+
'mp3' => 'mp4a.40.34',
|
|
10
|
+
'ac-3' => 'ac-3',
|
|
11
|
+
'ec-3' => 'ec-3',
|
|
12
|
+
'e-ac-3' => 'ec-3',
|
|
13
|
+
'flac' => 'fLaC',
|
|
14
|
+
'opus' => 'Opus'
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
BASELINE_CODECS = {
|
|
18
|
+
3.0 => 'avc1.66.30',
|
|
19
|
+
3.1 => 'avc1.42001f'
|
|
20
|
+
}.freeze
|
|
21
|
+
|
|
22
|
+
MAIN_CODECS = {
|
|
23
|
+
3.0 => 'avc1.77.30',
|
|
24
|
+
3.1 => 'avc1.4d001f',
|
|
25
|
+
4.0 => 'avc1.4d0028',
|
|
26
|
+
4.1 => 'avc1.4d0029'
|
|
27
|
+
}.freeze
|
|
28
|
+
|
|
29
|
+
HIGH_LEVELS = [3.0, 3.1, 3.2, 4.0, 4.1, 4.2,
|
|
30
|
+
5.0, 5.1, 5.2].freeze
|
|
31
|
+
|
|
32
|
+
HEVC_CODECS = {
|
|
33
|
+
['hevc-main', 3.1] => 'hvc1.1.6.L93.B0',
|
|
34
|
+
['hevc-main', 4.0] => 'hvc1.1.6.L120.B0',
|
|
35
|
+
['hevc-main', 5.0] => 'hvc1.1.6.L150.B0',
|
|
36
|
+
['hevc-main', 5.1] => 'hvc1.1.6.L153.B0',
|
|
37
|
+
['hevc-main-10', 3.1] => 'hvc1.2.4.L93.B0',
|
|
38
|
+
['hevc-main-10', 4.0] => 'hvc1.2.4.L120.B0',
|
|
39
|
+
['hevc-main-10', 5.0] => 'hvc1.2.4.L150.B0',
|
|
40
|
+
['hevc-main-10', 5.1] => 'hvc1.2.4.L153.B0'
|
|
41
|
+
}.freeze
|
|
42
|
+
|
|
43
|
+
AV1_CODECS = {
|
|
44
|
+
['av1-main', 3.1] => 'av01.0.04M.08',
|
|
45
|
+
['av1-main', 4.0] => 'av01.0.08M.08',
|
|
46
|
+
['av1-main', 5.0] => 'av01.0.12M.08',
|
|
47
|
+
['av1-main', 5.1] => 'av01.0.13M.08',
|
|
48
|
+
['av1-high', 3.1] => 'av01.1.04H.10',
|
|
49
|
+
['av1-high', 4.0] => 'av01.1.08H.10',
|
|
50
|
+
['av1-high', 5.0] => 'av01.1.12H.10',
|
|
51
|
+
['av1-high', 5.1] => 'av01.1.13H.10'
|
|
52
|
+
}.freeze
|
|
53
|
+
|
|
54
|
+
# Look up the codec string for an audio codec name.
|
|
55
|
+
# @param codec [String, nil] audio codec name
|
|
56
|
+
# @return [String, nil] codec string
|
|
57
|
+
def self.audio_codec(codec)
|
|
58
|
+
return if codec.nil?
|
|
59
|
+
|
|
60
|
+
AUDIO_CODECS[codec.downcase]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Look up the codec string for a video profile and level.
|
|
64
|
+
# @param profile [String, nil] video profile name
|
|
65
|
+
# @param level [Float, Integer, nil] video level
|
|
66
|
+
# @return [String, nil] codec string
|
|
67
|
+
def self.video_codec(profile, level)
|
|
68
|
+
return if profile.nil? || level.nil?
|
|
69
|
+
|
|
70
|
+
level = level.to_f
|
|
71
|
+
name = profile.downcase
|
|
72
|
+
return BASELINE_CODECS[level] if name == 'baseline'
|
|
73
|
+
return MAIN_CODECS[level] if name == 'main'
|
|
74
|
+
return high_codec_string(level) if name == 'high'
|
|
75
|
+
return HEVC_CODECS[[profile, level]] if name.start_with?('hevc-')
|
|
76
|
+
|
|
77
|
+
AV1_CODECS[[profile, level]] if name.start_with?('av1-')
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def self.high_codec_string(level)
|
|
81
|
+
return nil unless HIGH_LEVELS.include?(level)
|
|
82
|
+
|
|
83
|
+
hex = level.to_s.sub('.', '').to_i.to_s(16)
|
|
84
|
+
"avc1.6400#{hex}"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private_class_method :high_codec_string
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -5,15 +5,22 @@ module M3u8
|
|
|
5
5
|
# indicates a Content Steering Manifest for dynamic pathway selection.
|
|
6
6
|
class ContentSteeringItem
|
|
7
7
|
extend M3u8
|
|
8
|
+
include AttributeFormatter
|
|
8
9
|
|
|
10
|
+
# @return [String, nil] steering manifest server URI
|
|
11
|
+
# @return [String, nil] default pathway ID
|
|
9
12
|
attr_accessor :server_uri, :pathway_id
|
|
10
13
|
|
|
14
|
+
# @param params [Hash] attribute key-value pairs
|
|
11
15
|
def initialize(params = {})
|
|
12
16
|
params.each do |key, value|
|
|
13
17
|
instance_variable_set("@#{key}", value)
|
|
14
18
|
end
|
|
15
19
|
end
|
|
16
20
|
|
|
21
|
+
# Parse an EXT-X-CONTENT-STEERING tag.
|
|
22
|
+
# @param text [String] raw tag line
|
|
23
|
+
# @return [ContentSteeringItem]
|
|
17
24
|
def self.parse(text)
|
|
18
25
|
attributes = parse_attributes(text)
|
|
19
26
|
ContentSteeringItem.new(
|
|
@@ -22,6 +29,8 @@ module M3u8
|
|
|
22
29
|
)
|
|
23
30
|
end
|
|
24
31
|
|
|
32
|
+
# Render as an m3u8 EXT-X-CONTENT-STEERING tag.
|
|
33
|
+
# @return [String]
|
|
25
34
|
def to_s
|
|
26
35
|
"#EXT-X-CONTENT-STEERING:#{formatted_attributes}"
|
|
27
36
|
end
|
|
@@ -29,18 +38,8 @@ module M3u8
|
|
|
29
38
|
private
|
|
30
39
|
|
|
31
40
|
def formatted_attributes
|
|
32
|
-
[
|
|
33
|
-
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def server_uri_format
|
|
37
|
-
%(SERVER-URI="#{server_uri}")
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def pathway_id_format
|
|
41
|
-
return if pathway_id.nil?
|
|
42
|
-
|
|
43
|
-
%(PATHWAY-ID="#{pathway_id}")
|
|
41
|
+
[quoted_format('SERVER-URI', server_uri),
|
|
42
|
+
quoted_format('PATHWAY-ID', pathway_id)].compact.join(',')
|
|
44
43
|
end
|
|
45
44
|
end
|
|
46
45
|
end
|
data/lib/m3u8/date_range_item.rb
CHANGED
|
@@ -3,8 +3,30 @@
|
|
|
3
3
|
module M3u8
|
|
4
4
|
# DateRangeItem represents a #EXT-X-DATERANGE tag
|
|
5
5
|
class DateRangeItem
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
extend M3u8
|
|
7
|
+
include AttributeFormatter
|
|
8
|
+
|
|
9
|
+
# @return [String, nil] unique date range identifier
|
|
10
|
+
# @return [String, nil] CLASS attribute
|
|
11
|
+
# @return [String, nil] start date (ISO 8601)
|
|
12
|
+
# @return [String, nil] end date (ISO 8601)
|
|
13
|
+
# @return [Float, nil] duration in seconds
|
|
14
|
+
# @return [Float, nil] planned duration in seconds
|
|
15
|
+
# @return [String, nil] SCTE-35 command hex string
|
|
16
|
+
# @return [String, nil] SCTE-35 out hex string
|
|
17
|
+
# @return [String, nil] SCTE-35 in hex string
|
|
18
|
+
# @return [String, nil] CUE attribute
|
|
19
|
+
# @return [Boolean, nil] END-ON-NEXT flag
|
|
20
|
+
# @return [Hash, nil] client-defined X- attributes
|
|
21
|
+
# @return [String, nil] interstitial asset URI
|
|
22
|
+
# @return [String, nil] interstitial asset list URI
|
|
23
|
+
# @return [Float, nil] interstitial resume offset
|
|
24
|
+
# @return [Float, nil] interstitial playout limit
|
|
25
|
+
# @return [String, nil] interstitial restrict value
|
|
26
|
+
# @return [String, nil] interstitial snap value
|
|
27
|
+
# @return [String, nil] interstitial timeline occupies
|
|
28
|
+
# @return [String, nil] interstitial timeline style
|
|
29
|
+
# @return [String, nil] content may vary flag
|
|
8
30
|
attr_accessor :id, :class_name, :start_date, :end_date, :duration,
|
|
9
31
|
:planned_duration, :scte35_cmd, :scte35_out, :scte35_in,
|
|
10
32
|
:cue, :end_on_next, :client_attributes,
|
|
@@ -19,86 +41,114 @@ module M3u8
|
|
|
19
41
|
X-CONTENT-MAY-VARY
|
|
20
42
|
].freeze
|
|
21
43
|
|
|
44
|
+
# @param options [Hash] attribute key-value pairs
|
|
22
45
|
def initialize(options = {})
|
|
23
46
|
options.each do |key, value|
|
|
24
47
|
instance_variable_set("@#{key}", value)
|
|
25
48
|
end
|
|
26
49
|
end
|
|
27
50
|
|
|
28
|
-
|
|
51
|
+
# Parse an EXT-X-DATERANGE tag.
|
|
52
|
+
# @param text [String] raw tag line
|
|
53
|
+
# @return [DateRangeItem]
|
|
54
|
+
def self.parse(text)
|
|
29
55
|
attributes = parse_attributes(text)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
56
|
+
options = parse_base_attributes(attributes)
|
|
57
|
+
.merge(parse_interstitials(attributes))
|
|
58
|
+
.merge(client_attributes:
|
|
59
|
+
parse_client_attributes(attributes))
|
|
60
|
+
DateRangeItem.new(options)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def self.parse_base_attributes(attributes)
|
|
64
|
+
{ id: attributes['ID'],
|
|
65
|
+
class_name: attributes['CLASS'],
|
|
66
|
+
start_date: attributes['START-DATE'],
|
|
67
|
+
end_date: attributes['END-DATE'],
|
|
68
|
+
duration: parse_float(attributes['DURATION']),
|
|
69
|
+
planned_duration:
|
|
70
|
+
parse_float(attributes['PLANNED-DURATION']),
|
|
71
|
+
scte35_cmd: attributes['SCTE35-CMD'],
|
|
72
|
+
scte35_out: attributes['SCTE35-OUT'],
|
|
73
|
+
scte35_in: attributes['SCTE35-IN'],
|
|
74
|
+
cue: attributes['CUE'],
|
|
75
|
+
end_on_next: attributes.key?('END-ON-NEXT') }
|
|
76
|
+
end
|
|
77
|
+
private_class_method :parse_base_attributes
|
|
78
|
+
|
|
79
|
+
def self.parse_interstitials(attributes)
|
|
80
|
+
{ asset_uri: attributes['X-ASSET-URI'],
|
|
81
|
+
asset_list: attributes['X-ASSET-LIST'],
|
|
82
|
+
resume_offset:
|
|
83
|
+
parse_float(attributes['X-RESUME-OFFSET']),
|
|
84
|
+
playout_limit:
|
|
85
|
+
parse_float(attributes['X-PLAYOUT-LIMIT']),
|
|
86
|
+
restrict: attributes['X-RESTRICT'],
|
|
87
|
+
snap: attributes['X-SNAP'],
|
|
88
|
+
timeline_occupies:
|
|
89
|
+
attributes['X-TIMELINE-OCCUPIES'],
|
|
90
|
+
timeline_style: attributes['X-TIMELINE-STYLE'],
|
|
91
|
+
content_may_vary:
|
|
92
|
+
attributes['X-CONTENT-MAY-VARY'] }
|
|
93
|
+
end
|
|
94
|
+
private_class_method :parse_interstitials
|
|
95
|
+
|
|
96
|
+
# Render as an m3u8 EXT-X-DATERANGE tag.
|
|
97
|
+
# @return [String]
|
|
45
98
|
def to_s
|
|
46
99
|
"#EXT-X-DATERANGE:#{formatted_attributes}"
|
|
47
100
|
end
|
|
48
101
|
|
|
102
|
+
# Parse SCTE-35 command data.
|
|
103
|
+
# @return [Scte35, nil]
|
|
49
104
|
def scte35_cmd_info
|
|
50
105
|
Scte35.parse(scte35_cmd) unless scte35_cmd.nil?
|
|
51
106
|
end
|
|
52
107
|
|
|
108
|
+
# Parse SCTE-35 out data.
|
|
109
|
+
# @return [Scte35, nil]
|
|
53
110
|
def scte35_out_info
|
|
54
111
|
Scte35.parse(scte35_out) unless scte35_out.nil?
|
|
55
112
|
end
|
|
56
113
|
|
|
114
|
+
# Parse SCTE-35 in data.
|
|
115
|
+
# @return [Scte35, nil]
|
|
57
116
|
def scte35_in_info
|
|
58
117
|
Scte35.parse(scte35_in) unless scte35_in.nil?
|
|
59
118
|
end
|
|
60
119
|
|
|
120
|
+
def self.parse_client_attributes(attributes)
|
|
121
|
+
attributes.select do |key|
|
|
122
|
+
key.start_with?('X-') && !INTERSTITIAL_KEYS.include?(key)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
private_class_method :parse_client_attributes
|
|
126
|
+
|
|
61
127
|
private
|
|
62
128
|
|
|
63
129
|
def formatted_attributes
|
|
64
130
|
[%(ID="#{id}"),
|
|
65
|
-
|
|
131
|
+
quoted_format('CLASS', class_name),
|
|
66
132
|
%(START-DATE="#{start_date}"),
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
133
|
+
quoted_format('END-DATE', end_date),
|
|
134
|
+
unquoted_format('DURATION', duration),
|
|
135
|
+
unquoted_format('PLANNED-DURATION', planned_duration),
|
|
70
136
|
client_attributes_format,
|
|
71
137
|
interstitial_formats,
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
138
|
+
unquoted_format('SCTE35-CMD', scte35_cmd),
|
|
139
|
+
unquoted_format('SCTE35-OUT', scte35_out),
|
|
140
|
+
unquoted_format('SCTE35-IN', scte35_in),
|
|
141
|
+
quoted_format('CUE', cue),
|
|
76
142
|
end_on_next_format].flatten.compact.join(',')
|
|
77
143
|
end
|
|
78
144
|
|
|
79
|
-
def class_name_format
|
|
80
|
-
quoted_format('CLASS', class_name)
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def end_date_format
|
|
84
|
-
quoted_format('END-DATE', end_date)
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def duration_format
|
|
88
|
-
unquoted_format('DURATION', duration)
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def planned_duration_format
|
|
92
|
-
unquoted_format('PLANNED-DURATION', planned_duration)
|
|
93
|
-
end
|
|
94
|
-
|
|
95
145
|
def client_attributes_format
|
|
96
146
|
return if client_attributes.nil? || client_attributes.empty?
|
|
97
147
|
|
|
98
148
|
client_attributes.map do |attribute|
|
|
99
149
|
value = attribute.last
|
|
100
|
-
|
|
101
|
-
"#{attribute.first}=#{
|
|
150
|
+
fmt = decimal?(value) ? value : %("#{value}")
|
|
151
|
+
"#{attribute.first}=#{fmt}"
|
|
102
152
|
end
|
|
103
153
|
end
|
|
104
154
|
|
|
@@ -113,76 +163,16 @@ module M3u8
|
|
|
113
163
|
end
|
|
114
164
|
end
|
|
115
165
|
|
|
116
|
-
def parse_interstitials(attributes)
|
|
117
|
-
@asset_uri = attributes['X-ASSET-URI']
|
|
118
|
-
@asset_list = attributes['X-ASSET-LIST']
|
|
119
|
-
@resume_offset = parse_float(attributes['X-RESUME-OFFSET'])
|
|
120
|
-
@playout_limit = parse_float(attributes['X-PLAYOUT-LIMIT'])
|
|
121
|
-
@restrict = attributes['X-RESTRICT']
|
|
122
|
-
@snap = attributes['X-SNAP']
|
|
123
|
-
@timeline_occupies = attributes['X-TIMELINE-OCCUPIES']
|
|
124
|
-
@timeline_style = attributes['X-TIMELINE-STYLE']
|
|
125
|
-
@content_may_vary = attributes['X-CONTENT-MAY-VARY']
|
|
126
|
-
end
|
|
127
|
-
|
|
128
166
|
def interstitial_formats
|
|
129
|
-
[
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
def asset_list_format
|
|
141
|
-
quoted_format('X-ASSET-LIST', asset_list)
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
def resume_offset_format
|
|
145
|
-
unquoted_format('X-RESUME-OFFSET', resume_offset)
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
def playout_limit_format
|
|
149
|
-
unquoted_format('X-PLAYOUT-LIMIT', playout_limit)
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
def restrict_format
|
|
153
|
-
quoted_format('X-RESTRICT', restrict)
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
def snap_format
|
|
157
|
-
quoted_format('X-SNAP', snap)
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
def timeline_occupies_format
|
|
161
|
-
quoted_format('X-TIMELINE-OCCUPIES', timeline_occupies)
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
def timeline_style_format
|
|
165
|
-
quoted_format('X-TIMELINE-STYLE', timeline_style)
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
def content_may_vary_format
|
|
169
|
-
quoted_format('X-CONTENT-MAY-VARY', content_may_vary)
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
def scte35_cmd_format
|
|
173
|
-
unquoted_format('SCTE35-CMD', scte35_cmd)
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
def scte35_out_format
|
|
177
|
-
unquoted_format('SCTE35-OUT', scte35_out)
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
def scte35_in_format
|
|
181
|
-
unquoted_format('SCTE35-IN', scte35_in)
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
def cue_format
|
|
185
|
-
quoted_format('CUE', cue)
|
|
167
|
+
[quoted_format('X-ASSET-URI', asset_uri),
|
|
168
|
+
quoted_format('X-ASSET-LIST', asset_list),
|
|
169
|
+
unquoted_format('X-RESUME-OFFSET', resume_offset),
|
|
170
|
+
unquoted_format('X-PLAYOUT-LIMIT', playout_limit),
|
|
171
|
+
quoted_format('X-RESTRICT', restrict),
|
|
172
|
+
quoted_format('X-SNAP', snap),
|
|
173
|
+
quoted_format('X-TIMELINE-OCCUPIES', timeline_occupies),
|
|
174
|
+
quoted_format('X-TIMELINE-STYLE', timeline_style),
|
|
175
|
+
quoted_format('X-CONTENT-MAY-VARY', content_may_vary)]
|
|
186
176
|
end
|
|
187
177
|
|
|
188
178
|
def end_on_next_format
|
|
@@ -190,19 +180,5 @@ module M3u8
|
|
|
190
180
|
|
|
191
181
|
'END-ON-NEXT=YES'
|
|
192
182
|
end
|
|
193
|
-
|
|
194
|
-
def quoted_format(key, value)
|
|
195
|
-
%(#{key}="#{value}") unless value.nil?
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
def unquoted_format(key, value)
|
|
199
|
-
"#{key}=#{value}" unless value.nil?
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
def parse_client_attributes(attributes)
|
|
203
|
-
attributes.select do |key|
|
|
204
|
-
key.start_with?('X-') && !INTERSTITIAL_KEYS.include?(key)
|
|
205
|
-
end
|
|
206
|
-
end
|
|
207
183
|
end
|
|
208
184
|
end
|
data/lib/m3u8/define_item.rb
CHANGED
|
@@ -7,14 +7,22 @@ module M3u8
|
|
|
7
7
|
class DefineItem
|
|
8
8
|
extend M3u8
|
|
9
9
|
|
|
10
|
+
# @return [String, nil] variable name
|
|
11
|
+
# @return [String, nil] variable value
|
|
12
|
+
# @return [String, nil] imported variable name
|
|
13
|
+
# @return [String, nil] query parameter name
|
|
10
14
|
attr_accessor :name, :value, :import, :queryparam
|
|
11
15
|
|
|
16
|
+
# @param params [Hash] attribute key-value pairs
|
|
12
17
|
def initialize(params = {})
|
|
13
18
|
params.each do |key, val|
|
|
14
19
|
instance_variable_set("@#{key}", val)
|
|
15
20
|
end
|
|
16
21
|
end
|
|
17
22
|
|
|
23
|
+
# Parse an EXT-X-DEFINE tag.
|
|
24
|
+
# @param text [String] raw tag line
|
|
25
|
+
# @return [DefineItem]
|
|
18
26
|
def self.parse(text)
|
|
19
27
|
attributes = parse_attributes(text)
|
|
20
28
|
DefineItem.new(
|
|
@@ -25,6 +33,8 @@ module M3u8
|
|
|
25
33
|
)
|
|
26
34
|
end
|
|
27
35
|
|
|
36
|
+
# Render as an m3u8 EXT-X-DEFINE tag.
|
|
37
|
+
# @return [String]
|
|
28
38
|
def to_s
|
|
29
39
|
"#EXT-X-DEFINE:#{formatted_attributes}"
|
|
30
40
|
end
|
|
@@ -4,6 +4,8 @@ module M3u8
|
|
|
4
4
|
# DiscontinuityItem represents a EXT-X-DISCONTINUITY tag to indicate a
|
|
5
5
|
# discontinuity between the SegmentItems that proceed and follow it.
|
|
6
6
|
class DiscontinuityItem
|
|
7
|
+
# Render as an m3u8 EXT-X-DISCONTINUITY tag.
|
|
8
|
+
# @return [String]
|
|
7
9
|
def to_s
|
|
8
10
|
"#EXT-X-DISCONTINUITY\n"
|
|
9
11
|
end
|