m3u8 1.1.0 → 1.3.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/CHANGELOG.md +13 -0
- data/README.md +121 -0
- data/bin/m3u8 +6 -0
- data/lib/m3u8/builder.rb +47 -0
- data/lib/m3u8/cli/inspect_command.rb +97 -0
- data/lib/m3u8/cli/validate_command.rb +23 -0
- data/lib/m3u8/cli.rb +116 -0
- data/lib/m3u8/playlist.rb +23 -8
- data/lib/m3u8/version.rb +1 -1
- data/lib/m3u8.rb +0 -2
- data/spec/lib/m3u8/builder_spec.rb +338 -0
- data/spec/lib/m3u8/cli/inspect_command_spec.rb +102 -0
- data/spec/lib/m3u8/cli/validate_command_spec.rb +35 -0
- data/spec/lib/m3u8/cli_spec.rb +104 -0
- data/spec/lib/m3u8/playlist_spec.rb +38 -18
- data/spec/lib/m3u8/reader_spec.rb +66 -46
- data/spec/lib/m3u8/round_trip_spec.rb +3 -9
- metadata +14 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bbf61a694bdc2daf585ad9f405be821f09ba53289ef075e4342f267e674eb21b
|
|
4
|
+
data.tar.gz: d599367f1344f9b1ad91c20590bc0b4c57a6d0369c71e3a34fd69325ae0c7f44
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9574b7ac1a95fe6b2fabe0bd28576b2b21713a1bed68e2a18d4ea9f644920a91a08e4696df69a19d7b8392eda87fc8fa14b090338ffebe31524cd5f3f4235539
|
|
7
|
+
data.tar.gz: 2f34f676d931929f6671b4e3b3ba3efc0e49e73fc2dc54f6c3a64f499e8d2c1a20b974fcad3febcb0f17252d6aef1f9fb612b1c1ec9ad1911e41aa28314f8ea1
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
**1.3.0**
|
|
2
|
+
|
|
3
|
+
* Added CLI tool (`bin/m3u8`) with `inspect` and `validate` subcommands for inspecting playlist metadata and checking validity from the command line. Supports file arguments and stdin piping.
|
|
4
|
+
* Added `session_keys` convenience accessor to `Playlist`.
|
|
5
|
+
|
|
6
|
+
***
|
|
7
|
+
|
|
8
|
+
**1.2.0**
|
|
9
|
+
|
|
10
|
+
* Added `Playlist.build` with block-based Builder DSL for concise playlist construction. Supports both `instance_eval` (clean DSL) and yielded builder (outer scope access) forms. All 19 item types have corresponding DSL methods.
|
|
11
|
+
|
|
12
|
+
***
|
|
13
|
+
|
|
1
14
|
**1.1.0**
|
|
2
15
|
|
|
3
16
|
* Added convenience accessor methods to `Playlist` for filtering items by type: `segments`, `playlists`, `media_items`, `keys`, `maps`, `date_ranges`, `parts`, `session_data`.
|
data/README.md
CHANGED
|
@@ -30,6 +30,127 @@ Or install it yourself as:
|
|
|
30
30
|
|
|
31
31
|
$ gem install m3u8
|
|
32
32
|
|
|
33
|
+
## CLI
|
|
34
|
+
|
|
35
|
+
The gem includes a command-line tool for inspecting and validating playlists.
|
|
36
|
+
|
|
37
|
+
### Inspect
|
|
38
|
+
|
|
39
|
+
Display playlist metadata and item summary:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
$ m3u8 inspect master.m3u8
|
|
43
|
+
Type: Master
|
|
44
|
+
Independent Segments: Yes
|
|
45
|
+
|
|
46
|
+
Variants: 6
|
|
47
|
+
1920x1080 5042000 bps hls/1080/1080.m3u8
|
|
48
|
+
640x360 861000 bps hls/360/360.m3u8
|
|
49
|
+
Media: 2
|
|
50
|
+
Session Keys: 1
|
|
51
|
+
Session Data: 0
|
|
52
|
+
|
|
53
|
+
$ m3u8 inspect media.m3u8
|
|
54
|
+
Type: Media
|
|
55
|
+
Version: 4
|
|
56
|
+
Sequence: 1
|
|
57
|
+
Target: 12
|
|
58
|
+
Duration: 1371.99s
|
|
59
|
+
Playlist: VOD
|
|
60
|
+
Cache: No
|
|
61
|
+
|
|
62
|
+
Segments: 138
|
|
63
|
+
Keys: 0
|
|
64
|
+
Maps: 0
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Reads from stdin when no file is given:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
$ cat playlist.m3u8 | m3u8 inspect
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Validate
|
|
74
|
+
|
|
75
|
+
Check playlist validity (exit 0 for valid, 1 for invalid):
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
$ m3u8 validate playlist.m3u8
|
|
79
|
+
Valid
|
|
80
|
+
|
|
81
|
+
$ m3u8 validate bad.m3u8
|
|
82
|
+
Invalid: mixed playlist and segment items
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Usage (Builder DSL)
|
|
86
|
+
|
|
87
|
+
`Playlist.build` provides a block-based DSL for concise playlist construction. It supports two forms:
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
# instance_eval form (clean DSL)
|
|
91
|
+
playlist = M3u8::Playlist.build(version: 4, target: 12) do
|
|
92
|
+
segment duration: 11.34, segment: '1080-7mbps00000.ts'
|
|
93
|
+
segment duration: 11.26, segment: '1080-7mbps00001.ts'
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# yielded builder form (access outer scope)
|
|
97
|
+
playlist = M3u8::Playlist.build(version: 4) do |b|
|
|
98
|
+
files.each { |f| b.segment duration: 10.0, segment: f }
|
|
99
|
+
end
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Build a master playlist:
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
playlist = M3u8::Playlist.build(independent_segments: true) do
|
|
106
|
+
media type: 'AUDIO', group_id: 'audio', name: 'English',
|
|
107
|
+
default: true, uri: 'eng/index.m3u8'
|
|
108
|
+
playlist bandwidth: 5_042_000, width: 1920, height: 1080,
|
|
109
|
+
profile: 'high', level: 4.1, audio_codec: 'aac-lc',
|
|
110
|
+
uri: 'hls/1080.m3u8'
|
|
111
|
+
playlist bandwidth: 2_387_000, width: 1280, height: 720,
|
|
112
|
+
profile: 'main', level: 3.1, audio_codec: 'aac-lc',
|
|
113
|
+
uri: 'hls/720.m3u8'
|
|
114
|
+
end
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Build a media playlist:
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
playlist = M3u8::Playlist.build(version: 4, target: 12,
|
|
121
|
+
sequence: 1, type: 'VOD') do
|
|
122
|
+
key method: 'AES-128', uri: 'https://example.com/key.bin'
|
|
123
|
+
map uri: 'init.mp4'
|
|
124
|
+
segment duration: 11.34, segment: '00000.ts'
|
|
125
|
+
discontinuity
|
|
126
|
+
segment duration: 11.26, segment: '00001.ts'
|
|
127
|
+
end
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Build an LL-HLS playlist:
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
133
|
+
sc = M3u8::ServerControlItem.new(
|
|
134
|
+
can_skip_until: 24.0, part_hold_back: 1.0,
|
|
135
|
+
can_block_reload: true
|
|
136
|
+
)
|
|
137
|
+
pi = M3u8::PartInfItem.new(part_target: 0.5)
|
|
138
|
+
|
|
139
|
+
playlist = M3u8::Playlist.build(
|
|
140
|
+
version: 9, target: 4, sequence: 100,
|
|
141
|
+
server_control: sc, part_inf: pi, live: true
|
|
142
|
+
) do
|
|
143
|
+
map uri: 'init.mp4'
|
|
144
|
+
segment duration: 4.0, segment: 'seg100.mp4'
|
|
145
|
+
part duration: 0.5, uri: 'seg101.0.mp4', independent: true
|
|
146
|
+
preload_hint type: 'PART', uri: 'seg101.1.mp4'
|
|
147
|
+
rendition_report uri: '../alt/index.m3u8',
|
|
148
|
+
last_msn: 101, last_part: 0
|
|
149
|
+
end
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
All DSL methods correspond to item classes: `segment`, `playlist`, `media`, `session_data`, `session_key`, `content_steering`, `key`, `map`, `date_range`, `discontinuity`, `gap`, `time`, `bitrate`, `part`, `preload_hint`, `rendition_report`, `skip`, `define`, `playback_start`.
|
|
153
|
+
|
|
33
154
|
## Usage (creating playlists)
|
|
34
155
|
|
|
35
156
|
Create a master playlist and add child playlists for adaptive bitrate streaming:
|
data/bin/m3u8
ADDED
data/lib/m3u8/builder.rb
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module M3u8
|
|
4
|
+
# Builder provides a block-based DSL for constructing playlists
|
|
5
|
+
class Builder
|
|
6
|
+
ITEMS = {
|
|
7
|
+
segment: 'SegmentItem',
|
|
8
|
+
playlist: 'PlaylistItem',
|
|
9
|
+
media: 'MediaItem',
|
|
10
|
+
session_data: 'SessionDataItem',
|
|
11
|
+
session_key: 'SessionKeyItem',
|
|
12
|
+
content_steering: 'ContentSteeringItem',
|
|
13
|
+
key: 'KeyItem',
|
|
14
|
+
map: 'MapItem',
|
|
15
|
+
date_range: 'DateRangeItem',
|
|
16
|
+
time: 'TimeItem',
|
|
17
|
+
bitrate: 'BitrateItem',
|
|
18
|
+
part: 'PartItem',
|
|
19
|
+
preload_hint: 'PreloadHintItem',
|
|
20
|
+
rendition_report: 'RenditionReportItem',
|
|
21
|
+
skip: 'SkipItem',
|
|
22
|
+
define: 'DefineItem',
|
|
23
|
+
playback_start: 'PlaybackStart'
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
ZERO_ARG_ITEMS = {
|
|
27
|
+
discontinuity: 'DiscontinuityItem',
|
|
28
|
+
gap: 'GapItem'
|
|
29
|
+
}.freeze
|
|
30
|
+
|
|
31
|
+
def initialize(playlist)
|
|
32
|
+
@playlist = playlist
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
ITEMS.each do |method_name, class_name|
|
|
36
|
+
define_method(method_name) do |params = {}|
|
|
37
|
+
@playlist.items << M3u8.const_get(class_name).new(params)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
ZERO_ARG_ITEMS.each do |method_name, class_name|
|
|
42
|
+
define_method(method_name) do
|
|
43
|
+
@playlist.items << M3u8.const_get(class_name).new
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module M3u8
|
|
4
|
+
class CLI
|
|
5
|
+
# InspectCommand displays metadata about a playlist
|
|
6
|
+
class InspectCommand
|
|
7
|
+
MEDIA_WIDTH = 12
|
|
8
|
+
MASTER_WIDTH = 23
|
|
9
|
+
|
|
10
|
+
def initialize(playlist, stdout)
|
|
11
|
+
@playlist = playlist
|
|
12
|
+
@stdout = stdout
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def run
|
|
16
|
+
if @playlist.master?
|
|
17
|
+
print_master
|
|
18
|
+
else
|
|
19
|
+
print_media
|
|
20
|
+
end
|
|
21
|
+
0
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def print_media
|
|
27
|
+
field 'Type', 'Media', MEDIA_WIDTH
|
|
28
|
+
field 'Version', @playlist.version, MEDIA_WIDTH
|
|
29
|
+
field 'Sequence', @playlist.sequence, MEDIA_WIDTH
|
|
30
|
+
field 'Target', @playlist.target, MEDIA_WIDTH
|
|
31
|
+
field 'Duration', duration_value, MEDIA_WIDTH
|
|
32
|
+
field 'Playlist', @playlist.type, MEDIA_WIDTH
|
|
33
|
+
field 'Cache', cache_value, MEDIA_WIDTH
|
|
34
|
+
@stdout.puts
|
|
35
|
+
field 'Segments', @playlist.segments.size, MEDIA_WIDTH
|
|
36
|
+
field 'Keys', @playlist.keys.size, MEDIA_WIDTH
|
|
37
|
+
field 'Maps', @playlist.maps.size, MEDIA_WIDTH
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def print_master
|
|
41
|
+
field 'Type', 'Master', MASTER_WIDTH
|
|
42
|
+
field 'Independent Segments',
|
|
43
|
+
independent_segments_value, MASTER_WIDTH
|
|
44
|
+
@stdout.puts
|
|
45
|
+
print_variants
|
|
46
|
+
print_media_items
|
|
47
|
+
field 'Session Keys',
|
|
48
|
+
@playlist.session_keys.size, MASTER_WIDTH
|
|
49
|
+
field 'Session Data',
|
|
50
|
+
@playlist.session_data.size, MASTER_WIDTH
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def print_variants
|
|
54
|
+
variants = @playlist.playlists
|
|
55
|
+
field 'Variants', variants.size, MASTER_WIDTH
|
|
56
|
+
variants.each { |v| @stdout.puts variant_line(v) }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def print_media_items
|
|
60
|
+
items = @playlist.media_items
|
|
61
|
+
field 'Media', items.size, MASTER_WIDTH
|
|
62
|
+
items.each do |m|
|
|
63
|
+
@stdout.puts " #{m.type} #{m.group_id} #{m.name}"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def variant_line(variant)
|
|
68
|
+
res = variant.resolution || ''
|
|
69
|
+
format(' %-11<res>s%<bw>s bps %<uri>s',
|
|
70
|
+
res: res, bw: variant.bandwidth, uri: variant.uri)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def independent_segments_value
|
|
74
|
+
return unless @playlist.independent_segments
|
|
75
|
+
|
|
76
|
+
'Yes'
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def duration_value
|
|
80
|
+
format('%<s>gs', s: @playlist.duration)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def cache_value
|
|
84
|
+
return unless @playlist.cache == false
|
|
85
|
+
|
|
86
|
+
'No'
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def field(label, value, width)
|
|
90
|
+
return if value.nil?
|
|
91
|
+
|
|
92
|
+
@stdout.puts format("%-#{width}<label>s%<value>s",
|
|
93
|
+
label: "#{label}:", value: value)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module M3u8
|
|
4
|
+
class CLI
|
|
5
|
+
# ValidateCommand checks playlist validity
|
|
6
|
+
class ValidateCommand
|
|
7
|
+
def initialize(playlist, stdout)
|
|
8
|
+
@playlist = playlist
|
|
9
|
+
@stdout = stdout
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def run
|
|
13
|
+
if @playlist.valid?
|
|
14
|
+
@stdout.puts 'Valid'
|
|
15
|
+
0
|
|
16
|
+
else
|
|
17
|
+
@stdout.puts 'Invalid: mixed playlist and segment items'
|
|
18
|
+
1
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
data/lib/m3u8/cli.rb
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'optparse'
|
|
4
|
+
require_relative 'cli/inspect_command'
|
|
5
|
+
require_relative 'cli/validate_command'
|
|
6
|
+
|
|
7
|
+
module M3u8
|
|
8
|
+
# CLI provides a command-line interface for inspecting and validating
|
|
9
|
+
# m3u8 playlists
|
|
10
|
+
class CLI
|
|
11
|
+
COMMANDS = %w[inspect validate].freeze
|
|
12
|
+
|
|
13
|
+
def self.run(argv, stdin, stdout, stderr)
|
|
14
|
+
new(argv, stdin, stdout, stderr).run
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def initialize(argv, stdin, stdout, stderr)
|
|
18
|
+
@argv = argv.dup
|
|
19
|
+
@stdin = stdin
|
|
20
|
+
@stdout = stdout
|
|
21
|
+
@stderr = stderr
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def run
|
|
25
|
+
parse_global_options
|
|
26
|
+
dispatch
|
|
27
|
+
rescue OptionParser::InvalidOption => e
|
|
28
|
+
@stderr.puts e.message
|
|
29
|
+
2
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def parse_global_options
|
|
35
|
+
@parser = OptionParser.new do |opts|
|
|
36
|
+
opts.banner = 'Usage: m3u8 <command> [options] [file]'
|
|
37
|
+
opts.separator ''
|
|
38
|
+
opts.separator 'Commands:'
|
|
39
|
+
opts.separator ' inspect Show playlist metadata'
|
|
40
|
+
opts.separator ' validate Check playlist validity'
|
|
41
|
+
opts.separator ''
|
|
42
|
+
opts.on('-v', '--version', 'Show version') do
|
|
43
|
+
@stdout.puts M3u8::VERSION
|
|
44
|
+
throw :exit, 0
|
|
45
|
+
end
|
|
46
|
+
opts.on('-h', '--help', 'Show help') do
|
|
47
|
+
@stdout.puts opts
|
|
48
|
+
throw :exit, 0
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
@exit_code = catch(:exit) do
|
|
53
|
+
@parser.order!(@argv)
|
|
54
|
+
nil
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def dispatch
|
|
59
|
+
return @exit_code if @exit_code
|
|
60
|
+
|
|
61
|
+
command = @argv.shift
|
|
62
|
+
return usage_error if command.nil?
|
|
63
|
+
return usage_error("unknown command: #{command}") \
|
|
64
|
+
unless COMMANDS.include?(command)
|
|
65
|
+
|
|
66
|
+
input = resolve_input
|
|
67
|
+
return 2 unless input
|
|
68
|
+
|
|
69
|
+
playlist = parse_playlist(input)
|
|
70
|
+
return 2 unless playlist
|
|
71
|
+
|
|
72
|
+
execute_command(command, playlist)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def execute_command(command, playlist)
|
|
76
|
+
case command
|
|
77
|
+
when 'inspect'
|
|
78
|
+
InspectCommand.new(playlist, @stdout).run
|
|
79
|
+
when 'validate'
|
|
80
|
+
ValidateCommand.new(playlist, @stdout).run
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def resolve_input
|
|
85
|
+
file = @argv.shift
|
|
86
|
+
if file
|
|
87
|
+
read_file(file)
|
|
88
|
+
elsif !@stdin.tty?
|
|
89
|
+
@stdin.read
|
|
90
|
+
else
|
|
91
|
+
usage_error
|
|
92
|
+
nil
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def read_file(path)
|
|
97
|
+
File.read(path)
|
|
98
|
+
rescue Errno::ENOENT
|
|
99
|
+
@stderr.puts "no such file: #{path}"
|
|
100
|
+
nil
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def parse_playlist(input)
|
|
104
|
+
Playlist.read(input)
|
|
105
|
+
rescue StandardError => e
|
|
106
|
+
@stderr.puts "parse error: #{e.message}"
|
|
107
|
+
nil
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def usage_error(message = nil)
|
|
111
|
+
@stderr.puts message if message
|
|
112
|
+
@stderr.puts @parser.to_s
|
|
113
|
+
2
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
data/lib/m3u8/playlist.rb
CHANGED
|
@@ -14,6 +14,17 @@ module M3u8
|
|
|
14
14
|
@items = []
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
+
def self.build(options = {}, &block)
|
|
18
|
+
playlist = new(options)
|
|
19
|
+
builder = Builder.new(playlist)
|
|
20
|
+
if block.arity == 1
|
|
21
|
+
yield builder
|
|
22
|
+
else
|
|
23
|
+
builder.instance_eval(&block)
|
|
24
|
+
end
|
|
25
|
+
playlist
|
|
26
|
+
end
|
|
27
|
+
|
|
17
28
|
def self.codecs(options = {})
|
|
18
29
|
item = PlaylistItem.new(options)
|
|
19
30
|
item.codecs
|
|
@@ -55,35 +66,39 @@ module M3u8
|
|
|
55
66
|
end
|
|
56
67
|
|
|
57
68
|
def segments
|
|
58
|
-
items.
|
|
69
|
+
items.grep(SegmentItem)
|
|
59
70
|
end
|
|
60
71
|
|
|
61
72
|
def playlists
|
|
62
|
-
items.
|
|
73
|
+
items.grep(PlaylistItem)
|
|
63
74
|
end
|
|
64
75
|
|
|
65
76
|
def media_items
|
|
66
|
-
items.
|
|
77
|
+
items.grep(MediaItem)
|
|
67
78
|
end
|
|
68
79
|
|
|
69
80
|
def keys
|
|
70
|
-
items.
|
|
81
|
+
items.grep(KeyItem)
|
|
71
82
|
end
|
|
72
83
|
|
|
73
84
|
def maps
|
|
74
|
-
items.
|
|
85
|
+
items.grep(MapItem)
|
|
75
86
|
end
|
|
76
87
|
|
|
77
88
|
def date_ranges
|
|
78
|
-
items.
|
|
89
|
+
items.grep(DateRangeItem)
|
|
79
90
|
end
|
|
80
91
|
|
|
81
92
|
def parts
|
|
82
|
-
items.
|
|
93
|
+
items.grep(PartItem)
|
|
83
94
|
end
|
|
84
95
|
|
|
85
96
|
def session_data
|
|
86
|
-
items.
|
|
97
|
+
items.grep(SessionDataItem)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def session_keys
|
|
101
|
+
items.grep(SessionKeyItem)
|
|
87
102
|
end
|
|
88
103
|
|
|
89
104
|
def duration
|
data/lib/m3u8/version.rb
CHANGED
data/lib/m3u8.rb
CHANGED
|
@@ -14,10 +14,8 @@ module M3u8
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def parse_attributes(line)
|
|
17
|
-
# rubocop:disable Style/HashTransformValues
|
|
18
17
|
line.delete("\n").scan(/([A-Za-z0-9-]+)\s*=\s*("[^"]*"|[^,]*)/)
|
|
19
18
|
.to_h { |key, value| [key, value.delete('"')] }
|
|
20
|
-
# rubocop:enable Style/HashTransformValues
|
|
21
19
|
end
|
|
22
20
|
|
|
23
21
|
def parse_float(value)
|