m3u8 0.6.1 → 0.6.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8bead9657f94696922af4e09d7f19800b80572ff
4
- data.tar.gz: b654a6d47b3cfad36b08f58cb95ffd12e46c3b66
3
+ metadata.gz: 113427414ddf5ec31124dbcc67b905d8f4d29ab9
4
+ data.tar.gz: 0aa8693ced40c395ade39b026ffce25f99fe49d1
5
5
  SHA512:
6
- metadata.gz: fc8d26287b07199eaba044e66330f519a6c96b44b52e607ea48cafb286d4a005e22d1f43cdfb2d353c9ccc79e4ea92fd188ed7e4cdee570580110b7b23ff5f6f
7
- data.tar.gz: 9b82bbb066aa4ccbb6e3f9827ec7ca56fbb845f56ac57826fbe29d37bdd06631bf1800b43cd42e28b582630c5fd50a6c91818af1c87b223e76cac54ed71b0062
6
+ metadata.gz: 12f12fe680621b9bd4bdb8eca4f5042de4f063772fff5ac331db34f12458131e47f1f8420a40af7072f85ed835e623ed4a6324cfb1a912f2e6d251ea11a3a3dc
7
+ data.tar.gz: 038002c0468e4c332ad06d6b38ff748aa6bdead131e0b5eb356da563c85648a5609c87fe233a1343768a532aebe99e12a3b7853b4843d487997fe89193eb9640
@@ -1,5 +1,6 @@
1
1
  AllCops:
2
2
  Exclude:
3
3
  - 'spec/spec_helper.rb'
4
+ - 'm3u8.gemspec'
4
5
  Style/StringLiterals:
5
- EnforcedStyle: single_quotes
6
+ EnforcedStyle: single_quotes
@@ -0,0 +1 @@
1
+ 2.2.3
@@ -1,7 +1,8 @@
1
1
  language: ruby
2
+ sudo: false
2
3
  rvm:
4
+ - 2.2.3
5
+ - 2.1.7
3
6
  - 2.0.0
4
- - 1.9.3
5
- - jruby-19mode
6
7
 
7
- script: rspec spec
8
+ script: rspec spec
@@ -1,3 +1,5 @@
1
+ #### 0.6.2 (2/16/2016) - Added support for EXT-X-SESSION-KEY tags. Merged pulled request #6 from [jviney](https://github.com/jviney), fixing bug with codec string value.
2
+
1
3
  #### 0.6.1 (8/15/2015) - Added support for EXT-X-PROGRAM-DATE-TIME tags.
2
4
 
3
5
  #### 0.6.0 (6/20/2015) - Added support for EXT-X-MAP tags, introduced ByteRange class to handled shared byterange parsing/serialization functionality. Breaking changes: SegmentItem now exposes a single byterange instance accessor rather than byterange_length and byterange_start, made all parse methods static for consistency (ex: MediaItem.parse).
data/Gemfile CHANGED
@@ -4,4 +4,6 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  gem 'coveralls', require: false
7
+ gem 'guard'
8
+ gem 'guard-rspec', require: false
7
9
  gem 'simplecov', require: false
@@ -0,0 +1,5 @@
1
+ guard :rspec, cmd: 'bundle exec rspec' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { 'spec' }
5
+ end
data/README.md CHANGED
@@ -27,17 +27,19 @@ Or install it yourself as:
27
27
  ## Usage (creating playlists)
28
28
 
29
29
  Create a master playlist and add child playlists for adaptive bitrate streaming:
30
+
30
31
  ```ruby
31
32
  require 'm3u8'
32
33
  playlist = M3u8::Playlist.new
33
34
  ```
34
35
 
35
36
  Create a new playlist item with options:
37
+
36
38
  ```ruby
37
39
  options = { width: 1920, height: 1080, profile: 'high', level: 4.1,
38
40
  audio_codec: 'aac-lc', bandwidth: 540, uri: 'test.url' }
39
- item = M3u8::PlaylistItem.new options
40
- playlist.items.push item
41
+ item = M3u8::PlaylistItem.new(options)
42
+ playlist.items << item
41
43
  ```
42
44
 
43
45
  Add alternate audio, camera angles, closed captions and subtitles by creating MediaItem instances and adding them to the Playlist:
@@ -47,42 +49,51 @@ hash = { type: 'AUDIO', group_id: 'audio-lo', language: 'fre',
47
49
  assoc_language: 'spoken', name: 'Francais', autoselect: true,
48
50
  default: false, forced: true, uri: 'frelo/prog_index.m3u8' }
49
51
  item = M3u8::MediaItem.new(hash)
50
- playlist.items.push item
52
+ playlist.items << item
51
53
  ```
52
54
 
53
55
  Create a standard playlist and add MPEG-TS segments via SegmentItem. You can also specify options for this type of playlist, however these options are ignored if playlist becomes a master playlist (anything but segments added):
56
+
54
57
  ```ruby
55
58
  options = { version: 1, cache: false, target: 12, sequence: 1 }
56
- playlist = M3u8::Playlist.new options
59
+ playlist = M3u8::Playlist.new(options)
57
60
 
58
- item = M3u8::SegmentItem.new duration: 11, segment: 'test.ts'
59
- playlist.items.push item
61
+ item = M3u8::SegmentItem.new(duration: 11, segment: 'test.ts')
62
+ playlist.items << item
60
63
  ```
61
64
 
62
65
  You can pass an IO object to the write method:
66
+
63
67
  ```ruby
64
68
  require 'tempfile'
65
- f = Tempfile.new 'test'
66
- playlist.write f
69
+ file = Tempfile.new('test')
70
+ playlist.write(file)
67
71
  ```
72
+
68
73
  You can also access the playlist as a string:
74
+
69
75
  ```ruby
70
76
  playlist.to_s
71
77
  ```
78
+
72
79
  M3u8::Writer is the class that handles generating the playlist output.
73
80
 
74
81
  Alternatively you can set codecs rather than having it generated automatically:
82
+
75
83
  ```ruby
76
84
  options = { width: 1920, height: 1080, codecs: 'avc1.66.30,mp4a.40.2',
77
85
  bandwidth: 540, uri: 'test.url' }
78
- item = M3u8::PlaylistItem.new options
86
+ item = M3u8::PlaylistItem.new(options)
79
87
  ```
88
+
80
89
  Just get the codec string for custom use:
90
+
81
91
  ```ruby
82
92
  options = { profile: 'baseline', level: 3.0, audio_codec: 'aac-lc' }
83
- codecs = M3u8::Playlist.codecs options
93
+ codecs = M3u8::Playlist.codecs(options)
84
94
  # => "avc1.66.30,mp4a.40.2"
85
- ```
95
+ ```
96
+
86
97
  Values for audio_codec (codec name): aac-lc, he-aac, mp3
87
98
 
88
99
  Possible values for profile (H.264 Profile): baseline, main, high.
@@ -95,50 +106,81 @@ Not all Levels and Profiles can be combined, consult H.264 documentation
95
106
 
96
107
  ```ruby
97
108
  file = File.open 'spec/fixtures/master.m3u8'
98
- playlist = M3u8::Playlist.read file
109
+ playlist = M3u8::Playlist.read(file)
99
110
  playlist.master?
100
111
  # => true
101
112
  ```
102
- Acess items in playlist:
113
+
114
+ Access items in playlist:
115
+
103
116
  ```ruby
104
117
  playlist.items.first
105
118
  # => #<M3u8::PlaylistItem:0x007fa569bc7698 @program_id="1", @resolution="1920x1080",
106
119
  # @codecs="avc1.640028,mp4a.40.2", @bandwidth="5042000",
107
120
  # @playlist="hls/1080-7mbps/1080-7mbps.m3u8">
108
121
  ```
122
+
109
123
  Create a new playlist item with options:
124
+
110
125
  ```ruby
111
126
  options = { width: 1920, height: 1080, profile: 'high', level: 4.1,
112
127
  audio_codec: 'aac-lc', bandwidth: 540, uri: 'test.url' }
113
- item = M3u8::PlaylistItem.new options
128
+ item = M3u8::PlaylistItem.new(options)
114
129
  #add it to the top of the playlist
115
- playlist.items.insert 0, item
130
+ playlist.items.unshift(item)
116
131
  ```
132
+
117
133
  M3u8::Reader is the class handles parsing if you want more control over the process.
118
134
 
119
135
  ## Features
120
136
  * Distinction between segment and master playlists is handled automatically (no need to use a different class).
121
- * Automatically generates the audio/video codec string based on names and options you are familar with.
137
+ * Automatically generates the audio/video codec string based on names and options you are familiar with.
122
138
  * Provides validation of input when adding playlists or segments.
123
139
  * Allows all options to be configured on a playlist (caching, version, etc.)
124
- * Supports I-Frames (Intra frames) and Byte Ranges in Segments.
125
- * Supports subtitles, closed captions, alternate audio and video, and comments.
126
- # Supports Session Data in master playlists.
127
- * Supports keys for encrypted media segments (EXT-X-KEY).
128
- * Supports EXT-X-DISCONTINUITY in media segments.
129
140
  * Can write playlist to an IO object (StringIO/File, etc) or access string via to_s.
130
141
  * Can read playlists into a model (Playlist and Items) from an IO object.
131
142
  * Any tag or attribute supported by the object model is supported both parsing and generation of m3u8 playlists.
132
-
133
- ## Missing (but planned) Features
143
+ * Supports I-frames (Intra frames) and byte ranges in Segments.
144
+ * Supports subtitles, closed captions, alternate audio and video, and comments.
145
+ * Supports session data in master playlists.
146
+ * Supports keys for encrypted media segments (EXT-X-KEY, EXT-SESSION-KEY).
147
+
148
+ ## HLS Spec Status (version 17)
149
+ ### Implemented:
150
+ * EXTM3U
151
+ * EXT-X-VERSION
152
+ * EXTINF
153
+ * EXT-X-BYTERANGE
154
+ * EXT-X-DISCONTINUITY
155
+ * EXT-X-KEY
156
+ * EXT-X-MAP
157
+ * EXT-X-PROGRAM-DATE-TIME
158
+ * EXT-X-TARGETDURATION
159
+ * EXT-X-MEDIA-SEQUENCE
160
+ * EXT-X-ENDLIST
161
+ * EXT-X-PLAYLIST-TYPE
162
+ * EXT-X-I-FRAMES-ONLY
163
+ * EXT-X-MEDIA
164
+ * EXT-X-STREAM-INF
165
+ * EXT-X-I-FRAME-STREAM-INF
166
+ * EXT-X-SESSION-DATA
167
+ * EXT-X-SESSION-KEY
168
+
169
+ ### TODO:
170
+ * EXT-X-DISCONTINUITY-SEQUENCE
171
+ * EXT-X-INDEPENDENT-SEGMENTS
172
+ * EXT-X-START
173
+
174
+ ## Roadmap
175
+ * Add the last remaining tags for latest version of the spec.
134
176
  * Validation of all attributes and their values to match the rules defined in the spec.
135
- * Still missing support for a few tags and attributes.
177
+ * Support for different versions of spec, defaulting to latest.
136
178
 
137
179
  ## Contributing
138
180
 
139
181
  1. Fork it ( https://github.com/sethdeckard/m3u8/fork )
140
182
  2. Create your feature branch (`git checkout -b my-new-feature`)
141
- 3. Run the specs, make sure they pass and that new features are covered
183
+ 3. Run the specs, make sure they pass and that new features are covered. Code coverage should be 100%.
142
184
  4. Commit your changes (`git commit -am 'Add some feature'`)
143
185
  5. Push to the branch (`git push origin my-new-feature`)
144
186
  6. Create a new Pull Request
@@ -1,24 +1,11 @@
1
1
  require 'stringio'
2
- require 'm3u8/version'
3
- require 'm3u8/playlist'
4
- require 'm3u8/playlist_item'
5
- require 'm3u8/segment_item'
6
- require 'm3u8/discontinuity_item'
7
- require 'm3u8/key_item'
8
- require 'm3u8/map_item'
9
- require 'm3u8/media_item'
10
- require 'm3u8/time_item'
11
- require 'm3u8/session_data_item'
12
- require 'm3u8/byte_range'
13
- require 'm3u8/reader'
14
- require 'm3u8/writer'
15
- require 'm3u8/error'
2
+ Dir[File.dirname(__FILE__) + '/m3u8/*.rb'].each { |file| require file }
16
3
 
17
4
  # M3u8 provides parsing, generation, and validation of m3u8 playlists
18
5
  module M3u8
19
6
  def parse_attributes(line)
20
- array = line.gsub("\n", '').scan(/([A-z-]+)\s*=\s*("[^"]*"|[^,]*)/)
21
- Hash[array.map { |key, value| [key, value.gsub('"', '')] }]
7
+ array = line.delete("\n").scan(/([A-z-]+)\s*=\s*("[^"]*"|[^,]*)/)
8
+ Hash[array.map { |key, value| [key, value.delete('"')] }]
22
9
  end
23
10
 
24
11
  def parse_yes_no(value)
@@ -0,0 +1,50 @@
1
+ module M3u8
2
+ # Encapsulates logic common to encryption key tags
3
+ module Encryptable
4
+ def self.included(base)
5
+ base.send :attr_accessor, :method
6
+ base.send :attr_accessor, :uri
7
+ base.send :attr_accessor, :iv
8
+ base.send :attr_accessor, :key_format
9
+ base.send :attr_accessor, :key_format_versions
10
+ end
11
+
12
+ def attributes_to_s
13
+ [method_format,
14
+ uri_format,
15
+ iv_format,
16
+ key_format_format,
17
+ key_format_versions_format].compact.join(',')
18
+ end
19
+
20
+ def convert_key_names(attributes)
21
+ { method: attributes['METHOD'], uri: attributes['URI'],
22
+ iv: attributes['IV'], key_format: attributes['KEYFORMAT'],
23
+ key_format_versions: attributes['KEYFORMATVERSIONS'] }
24
+ end
25
+
26
+ private
27
+
28
+ def method_format
29
+ "METHOD=#{method}"
30
+ end
31
+
32
+ def uri_format
33
+ %(URI="#{uri}") unless uri.nil?
34
+ end
35
+
36
+ def iv_format
37
+ "IV=#{iv}" unless iv.nil?
38
+ end
39
+
40
+ def key_format_format
41
+ %(KEYFORMAT="#{key_format}") unless key_format.nil?
42
+ end
43
+
44
+ def key_format_versions_format
45
+ return if key_format_versions.nil?
46
+
47
+ %(KEYFORMATVERSIONS="#{key_format_versions}")
48
+ end
49
+ end
50
+ end
@@ -1,54 +1,23 @@
1
1
  module M3u8
2
2
  # KeyItem represents a set of EXT-X-KEY attributes
3
3
  class KeyItem
4
+ include Encryptable
4
5
  extend M3u8
5
- attr_accessor :method, :uri, :iv, :key_format, :key_format_versions
6
6
 
7
7
  def initialize(params = {})
8
- params.each do |key, value|
8
+ options = convert_key_names(params)
9
+ options.merge(params).each do |key, value|
9
10
  instance_variable_set("@#{key}", value)
10
11
  end
11
12
  end
12
13
 
13
14
  def self.parse(text)
14
- attributes = parse_attributes text
15
- options = { method: attributes['METHOD'], uri: attributes['URI'],
16
- iv: attributes['IV'], key_format: attributes['KEYFORMAT'],
17
- key_format_versions: attributes['KEYFORMATVERSIONS'] }
18
- KeyItem.new options
15
+ attributes = parse_attributes(text)
16
+ KeyItem.new(attributes)
19
17
  end
20
18
 
21
19
  def to_s
22
- attributes = [method_format,
23
- uri_format,
24
- iv_format,
25
- key_format_format,
26
- key_format_versions_format].compact.join(',')
27
- "#EXT-X-KEY:#{attributes}"
28
- end
29
-
30
- private
31
-
32
- def method_format
33
- "METHOD=#{method}"
34
- end
35
-
36
- def uri_format
37
- %(URI="#{uri}") unless uri.nil?
38
- end
39
-
40
- def iv_format
41
- "IV=#{iv}" unless iv.nil?
42
- end
43
-
44
- def key_format_format
45
- %(KEYFORMAT="#{key_format}") unless key_format.nil?
46
- end
47
-
48
- def key_format_versions_format
49
- return if key_format_versions.nil?
50
-
51
- %(KEYFORMATVERSIONS="#{key_format_versions}")
20
+ "#EXT-X-KEY:#{attributes_to_s}"
52
21
  end
53
22
  end
54
23
  end
@@ -148,15 +148,27 @@ module M3u8
148
148
  def video_codec(profile, level)
149
149
  return if profile.nil? || level.nil?
150
150
 
151
- profile = profile.downcase
152
- return 'avc1.66.30' if profile == 'baseline' && level == 3.0
153
- return 'avc1.42001f' if profile == 'baseline' && level == 3.1
154
- return 'avc1.77.30' if profile == 'main' && level == 3.0
155
- return 'avc1.4d001f' if profile == 'main' && level == 3.1
156
- return 'avc1.4d0028' if profile == 'main' && level == 4.0
157
- return 'avc1.64001f' if profile == 'high' && level == 3.1
158
- return 'avc1.640028' if profile == 'high' &&
159
- (level == 4.0 || level == 4.1)
151
+ profile.downcase!
152
+ return baseline_codec_string(level) if profile == 'baseline'
153
+ return main_codec_string(level) if profile == 'main'
154
+ return high_codec_string(level) if profile == 'high'
155
+ end
156
+
157
+ def baseline_codec_string(level)
158
+ return 'avc1.66.30' if level == 3.0
159
+ return 'avc1.42001f' if level == 3.1
160
+ end
161
+
162
+ def main_codec_string(level)
163
+ return 'avc1.77.30' if level == 3.0
164
+ return 'avc1.4d001f' if level == 3.1
165
+ return 'avc1.4d0028' if level == 4.0
166
+ end
167
+
168
+ def high_codec_string(level)
169
+ return 'avc1.64001f' if level == 3.1
170
+ return 'avc1.640028' if level == 4.0
171
+ return 'avc1.640029' if level == 4.1
160
172
  end
161
173
  end
162
174
  end
@@ -22,45 +22,46 @@ module M3u8
22
22
  private
23
23
 
24
24
  def basic_tags
25
- { '#EXT-X-VERSION' => ->(line) { parse_version line } }
25
+ { '#EXT-X-VERSION' => ->(line) { parse_version(line) } }
26
26
  end
27
27
 
28
28
  def media_segment_tags
29
- { '#EXTINF' => ->(line) { parse_segment line },
30
- '#EXT-X-DISCONTINUITY' => ->(line) { parse_discontinuity line },
31
- '#EXT-X-BYTERANGE' => ->(line) { parse_byterange line },
32
- '#EXT-X-KEY' => ->(line) { parse_key line },
33
- '#EXT-X-MAP' => ->(line) { parse_map line },
34
- '#EXT-X-PROGRAM-DATE-TIME' => ->(line) { parse_time line }
29
+ { '#EXTINF' => ->(line) { parse_segment(line) },
30
+ '#EXT-X-DISCONTINUITY' => ->(line) { parse_discontinuity(line) },
31
+ '#EXT-X-BYTERANGE' => ->(line) { parse_byterange(line) },
32
+ '#EXT-X-KEY' => ->(line) { parse_key(line) },
33
+ '#EXT-X-MAP' => ->(line) { parse_map(line) },
34
+ '#EXT-X-PROGRAM-DATE-TIME' => ->(line) { parse_time(line) }
35
35
  }
36
36
  end
37
37
 
38
38
  def media_playlist_tags
39
- { '#EXT-X-MEDIA-SEQUENCE' => ->(line) { parse_sequence line },
40
- '#EXT-X-ALLOW-CACHE' => ->(line) { parse_cache line },
41
- '#EXT-X-TARGETDURATION' => ->(line) { parse_target line },
39
+ { '#EXT-X-MEDIA-SEQUENCE' => ->(line) { parse_sequence(line) },
40
+ '#EXT-X-ALLOW-CACHE' => ->(line) { parse_cache(line) },
41
+ '#EXT-X-TARGETDURATION' => ->(line) { parse_target(line) },
42
42
  '#EXT-X-I-FRAMES-ONLY' => proc { playlist.iframes_only = true },
43
- '#EXT-X-PLAYLIST-TYPE' => ->(line) { parse_playlist_type line }
43
+ '#EXT-X-PLAYLIST-TYPE' => ->(line) { parse_playlist_type(line) }
44
44
  }
45
45
  end
46
46
 
47
47
  def master_playlist_tags
48
- { '#EXT-X-MEDIA' => ->(line) { parse_media line },
49
- '#EXT-X-SESSION-DATA' => ->(line) { parse_session_data line },
50
- '#EXT-X-STREAM-INF' => ->(line) { parse_stream line },
51
- '#EXT-X-I-FRAME-STREAM-INF' => ->(line) { parse_iframe_stream line }
48
+ { '#EXT-X-MEDIA' => ->(line) { parse_media(line) },
49
+ '#EXT-X-SESSION-DATA' => ->(line) { parse_session_data(line) },
50
+ '#EXT-X-SESSION-KEY' => ->(line) { parse_session_key(line) },
51
+ '#EXT-X-STREAM-INF' => ->(line) { parse_stream(line) },
52
+ '#EXT-X-I-FRAME-STREAM-INF' => ->(line) { parse_iframe_stream(line) }
52
53
  }
53
54
  end
54
55
 
55
56
  def parse_line(line)
56
57
  return if match_tag(line)
57
- parse_next_line line if !item.nil? && open
58
+ parse_next_line(line) if !item.nil? && open
58
59
  end
59
60
 
60
61
  def match_tag(line)
61
62
  tag = @tags.select { |key| line.start_with? key }
62
63
  return unless tag.values.first
63
- tag.values.first.call line
64
+ tag.values.first.call(line)
64
65
  true
65
66
  end
66
67
 
@@ -89,16 +90,16 @@ module M3u8
89
90
  self.master = true
90
91
  self.open = true
91
92
 
92
- self.item = M3u8::PlaylistItem.parse line
93
+ self.item = M3u8::PlaylistItem.parse(line)
93
94
  end
94
95
 
95
96
  def parse_iframe_stream(line)
96
97
  self.master = true
97
98
  self.open = false
98
99
 
99
- self.item = M3u8::PlaylistItem.parse line
100
+ self.item = M3u8::PlaylistItem.parse(line)
100
101
  item.iframe = true
101
- playlist.items.push item
102
+ playlist.items << item
102
103
  end
103
104
 
104
105
  def parse_discontinuity(*)
@@ -106,22 +107,22 @@ module M3u8
106
107
  self.open = false
107
108
 
108
109
  self.item = M3u8::DiscontinuityItem.new
109
- playlist.items.push item
110
+ playlist.items << item
110
111
  end
111
112
 
112
113
  def parse_key(line)
113
- item = M3u8::KeyItem.parse line
114
- playlist.items.push item
114
+ item = M3u8::KeyItem.parse(line)
115
+ playlist.items << item
115
116
  end
116
117
 
117
118
  def parse_map(line)
118
- item = M3u8::MapItem.parse line
119
- playlist.items.push item
119
+ item = M3u8::MapItem.parse(line)
120
+ playlist.items << item
120
121
  end
121
122
 
122
123
  def parse_segment(line)
123
124
  self.item = M3u8::SegmentItem.new
124
- values = line.gsub('#EXTINF:', '').gsub("\n", ',').split(',')
125
+ values = line.gsub('#EXTINF:', '').tr("\n", ',').split(',')
125
126
  item.duration = values[0].to_f
126
127
  item.comment = values[1] unless values[1].nil?
127
128
 
@@ -130,34 +131,39 @@ module M3u8
130
131
  end
131
132
 
132
133
  def parse_byterange(line)
133
- values = line.gsub('#EXT-X-BYTERANGE:', '').gsub("\n", ',')
134
+ values = line.gsub('#EXT-X-BYTERANGE:', '').delete("\n")
134
135
  item.byterange = M3u8::ByteRange.parse values
135
136
  end
136
137
 
137
138
  def parse_session_data(line)
138
- item = M3u8::SessionDataItem.parse line
139
- playlist.items.push item
139
+ item = M3u8::SessionDataItem.parse(line)
140
+ playlist.items << item
141
+ end
142
+
143
+ def parse_session_key(line)
144
+ item = M3u8::SessionKeyItem.parse(line)
145
+ playlist.items << item
140
146
  end
141
147
 
142
148
  def parse_media(line)
143
149
  self.open = false
144
- self.item = M3u8::MediaItem.parse line
145
- playlist.items.push item
150
+ self.item = M3u8::MediaItem.parse(line)
151
+ playlist.items << item
146
152
  end
147
153
 
148
154
  def parse_time(line)
149
155
  self.open = false
150
- playlist.items.push M3u8::TimeItem.parse line
156
+ playlist.items << M3u8::TimeItem.parse(line)
151
157
  end
152
158
 
153
159
  def parse_next_line(line)
154
- value = line.gsub "\n", ''
160
+ value = line.delete("\n")
155
161
  if master
156
162
  item.uri = value
157
163
  else
158
164
  item.segment = value
159
165
  end
160
- playlist.items.push item
166
+ playlist.items << item
161
167
  self.open = false
162
168
  end
163
169
  end
@@ -0,0 +1,23 @@
1
+ module M3u8
2
+ # KeyItem represents a set of EXT-X-SESSION-KEY attributes
3
+ class SessionKeyItem
4
+ include Encryptable
5
+ extend M3u8
6
+
7
+ def initialize(params = {})
8
+ options = convert_key_names(params)
9
+ options.merge(params).each do |key, value|
10
+ instance_variable_set("@#{key}", value)
11
+ end
12
+ end
13
+
14
+ def self.parse(text)
15
+ attributes = parse_attributes(text)
16
+ SessionKeyItem.new(attributes)
17
+ end
18
+
19
+ def to_s
20
+ "#EXT-X-SESSION-KEY:#{attributes_to_s}"
21
+ end
22
+ end
23
+ end
@@ -1,4 +1,4 @@
1
1
  # M3u8 provides parsing, generation, and validation of m3u8 playlists
2
2
  module M3u8
3
- VERSION = '0.6.1'
3
+ VERSION = '0.6.2'
4
4
  end
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ['lib']
20
20
 
21
- spec.add_development_dependency 'bundler', '>= 1.6'
22
- spec.add_development_dependency 'rake', '~> 10.0'
23
- spec.add_development_dependency 'rspec', '>= 2.11'
21
+ spec.add_development_dependency 'bundler'
22
+ spec.add_development_dependency 'rake'
23
+ spec.add_development_dependency 'rspec', '~> 3.3'
24
24
  end
@@ -1,4 +1,5 @@
1
1
  #EXTM3U
2
+ #EXT-X-SESSION-KEY:METHOD=AES-128,URI="https://priv.example.com/key.php?r=52"
2
3
  #EXT-X-STREAM-INF:PROGRAM-ID=1,RESOLUTION=1920x1080,CODECS="avc1.640028,mp4a.40.2",BANDWIDTH=5042000
3
4
  hls/1080-7mbps/1080-7mbps.m3u8
4
5
  #EXT-X-STREAM-INF:PROGRAM-ID=1,RESOLUTION=1920x1080,CODECS="avc1.640028,mp4a.40.2",BANDWIDTH=4853000
@@ -162,7 +162,7 @@ describe M3u8::PlaylistItem do
162
162
 
163
163
  options = { profile: 'high', level: 4.1 }
164
164
  item = M3u8::PlaylistItem.new options
165
- expect(item.codecs).to eq 'avc1.640028'
165
+ expect(item.codecs).to eq 'avc1.640029'
166
166
  end
167
167
 
168
168
  it 'should raise error if codecs are missing' do
@@ -39,7 +39,7 @@ describe M3u8::Playlist do
39
39
 
40
40
  output = "#EXTM3U\n" \
41
41
  '#EXT-X-STREAM-INF:PROGRAM-ID=2,RESOLUTION=1920x1080,' +
42
- %(CODECS="avc1.640028,mp4a.40.2",BANDWIDTH=50000\n) +
42
+ %(CODECS="avc1.640029,mp4a.40.2",BANDWIDTH=50000\n) +
43
43
  "playlist_url\n"
44
44
 
45
45
  expect(playlist.to_s).to eq output
@@ -58,7 +58,7 @@ describe M3u8::Playlist do
58
58
  output = "#EXTM3U\n" +
59
59
  %(#EXT-X-STREAM-INF:PROGRAM-ID=1,CODECS="mp4a.40.34") +
60
60
  ",BANDWIDTH=6400\nplaylist_url\n#EXT-X-STREAM-INF:PROGRAM-ID=2," +
61
- %(RESOLUTION=1920x1080,CODECS="avc1.640028,mp4a.40.2") +
61
+ %(RESOLUTION=1920x1080,CODECS="avc1.640029,mp4a.40.2") +
62
62
  ",BANDWIDTH=50000\nplaylist_url\n"
63
63
  expect(playlist.to_s).to eq output
64
64
  end
@@ -208,7 +208,7 @@ describe M3u8::Playlist do
208
208
  file = File.open 'spec/fixtures/master.m3u8'
209
209
  playlist = M3u8::Playlist.read file
210
210
  expect(playlist.master?).to be true
211
- expect(playlist.items.size).to eq 6
211
+ expect(playlist.items.size).to eq(7)
212
212
  end
213
213
 
214
214
  it 'should return the total duration of a playlist' do
@@ -222,6 +222,6 @@ describe M3u8::Playlist do
222
222
  item = M3u8::SegmentItem.new(duration: 8.790, segment: 'test_04.ts')
223
223
  playlist.items.push item
224
224
 
225
- expect(playlist.duration.round(3)).to eq 40.228
225
+ expect(playlist.duration.round(3)).to eq(40.228)
226
226
  end
227
227
  end
@@ -8,6 +8,11 @@ describe M3u8::Reader do
8
8
  expect(playlist.master?).to be true
9
9
 
10
10
  item = playlist.items[0]
11
+ expect(item).to be_a(M3u8::SessionKeyItem)
12
+ expect(item.method).to eq('AES-128')
13
+ expect(item.uri).to eq('https://priv.example.com/key.php?r=52')
14
+
15
+ item = playlist.items[1]
11
16
  expect(item).to be_a(M3u8::PlaylistItem)
12
17
  expect(item.uri).to eq('hls/1080-7mbps/1080-7mbps.m3u8')
13
18
  expect(item.program_id).to eq('1')
@@ -19,7 +24,7 @@ describe M3u8::Reader do
19
24
  expect(item.iframe).to be false
20
25
  expect(item.average_bandwidth).to be_nil
21
26
 
22
- item = playlist.items[5]
27
+ item = playlist.items[6]
23
28
  expect(item).to be_a(M3u8::PlaylistItem)
24
29
  expect(item.uri).to eq('hls/64k/64k.m3u8')
25
30
  expect(item.program_id).to eq('1')
@@ -31,7 +36,7 @@ describe M3u8::Reader do
31
36
  expect(item.iframe).to be false
32
37
  expect(item.average_bandwidth).to be_nil
33
38
 
34
- expect(playlist.items.size).to eq 6
39
+ expect(playlist.items.size).to eq(7)
35
40
 
36
41
  item = playlist.items.last
37
42
  expect(item.resolution).to be_nil
@@ -43,7 +48,7 @@ describe M3u8::Reader do
43
48
  playlist = reader.read file
44
49
  expect(playlist.master?).to be true
45
50
 
46
- expect(playlist.items.size).to eq 7
51
+ expect(playlist.items.size).to eq(7)
47
52
 
48
53
  item = playlist.items[1]
49
54
  expect(item).to be_a(M3u8::PlaylistItem)
@@ -169,12 +174,12 @@ describe M3u8::Reader do
169
174
  reader = M3u8::Reader.new
170
175
  playlist = reader.read file
171
176
 
172
- expect(playlist.items.size).to eq 6
177
+ expect(playlist.items.size).to eq(7)
173
178
 
174
179
  file = File.open 'spec/fixtures/master.m3u8'
175
180
  playlist = reader.read file
176
181
 
177
- expect(playlist.items.size).to eq 6
182
+ expect(playlist.items.size).to eq(7)
178
183
  end
179
184
 
180
185
  it 'should parse playlist with session data' do
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ describe M3u8::SessionKeyItem do
4
+ it 'should initialize with hash' do
5
+ hash = { method: 'AES-128', uri: 'http://test.key',
6
+ iv: 'D512BBF', key_format: 'identity',
7
+ key_format_versions: '1/3' }
8
+ item = M3u8::SessionKeyItem.new(hash)
9
+ expect(item.method).to eq 'AES-128'
10
+ expect(item.uri).to eq 'http://test.key'
11
+ expect(item.iv).to eq 'D512BBF'
12
+ expect(item.key_format).to eq 'identity'
13
+ expect(item.key_format_versions).to eq '1/3'
14
+ end
15
+
16
+ it 'should provide m3u8 format representation' do
17
+ hash = { method: 'AES-128', uri: 'http://test.key',
18
+ iv: 'D512BBF', key_format: 'identity',
19
+ key_format_versions: '1/3' }
20
+ item = M3u8::SessionKeyItem.new(hash)
21
+ expected = %(#EXT-X-SESSION-KEY:METHOD=AES-128,URI="http://test.key",) +
22
+ %(IV=D512BBF,KEYFORMAT="identity",KEYFORMATVERSIONS="1/3")
23
+ expect(item.to_s).to eq expected
24
+
25
+ hash = { method: 'AES-128', uri: 'http://test.key' }
26
+ item = M3u8::SessionKeyItem.new(hash)
27
+ expected = %(#EXT-X-SESSION-KEY:METHOD=AES-128,URI="http://test.key")
28
+ expect(item.to_s).to eq expected
29
+
30
+ hash = { method: 'NONE' }
31
+ item = M3u8::SessionKeyItem.new(hash)
32
+ expected = '#EXT-X-SESSION-KEY:METHOD=NONE'
33
+ expect(item.to_s).to eq expected
34
+ end
35
+
36
+ it 'should parse m3u8 format into instance' do
37
+ format = %(#EXT-X-SESSION-KEY:METHOD=AES-128,URI="http://test.key",) +
38
+ %(IV=D512BBF,KEYFORMAT="identity",KEYFORMATVERSIONS="1/3")
39
+ item = M3u8::KeyItem.parse format
40
+ expect(item.method).to eq 'AES-128'
41
+ expect(item.uri).to eq 'http://test.key'
42
+ expect(item.iv).to eq 'D512BBF'
43
+ expect(item.key_format).to eq 'identity'
44
+ expect(item.key_format_versions).to eq '1/3'
45
+ end
46
+ end
@@ -41,7 +41,7 @@ describe M3u8::Writer do
41
41
 
42
42
  output = "#EXTM3U\n" \
43
43
  '#EXT-X-STREAM-INF:PROGRAM-ID=2,RESOLUTION=1920x1080,' +
44
- %(CODECS="avc1.640028,mp4a.40.2",BANDWIDTH=50000\n) +
44
+ %(CODECS="avc1.640029,mp4a.40.2",BANDWIDTH=50000\n) +
45
45
  "playlist_url\n"
46
46
 
47
47
  io = StringIO.open
@@ -67,7 +67,7 @@ describe M3u8::Writer do
67
67
  output = "#EXTM3U\n" +
68
68
  %(#EXT-X-STREAM-INF:PROGRAM-ID=1,CODECS="mp4a.40.34") +
69
69
  ",BANDWIDTH=6400\nplaylist_url\n#EXT-X-STREAM-INF:PROGRAM-ID=2," +
70
- %(RESOLUTION=1920x1080,CODECS="avc1.640028,mp4a.40.2") +
70
+ %(RESOLUTION=1920x1080,CODECS="avc1.640029,mp4a.40.2") +
71
71
  ",BANDWIDTH=50000\nplaylist_url\n" +
72
72
  %(#EXT-X-SESSION-DATA:DATA-ID="com.test.movie.title",) +
73
73
  %(VALUE="Test",URI="http://test",LANGUAGE="en"\n)
File without changes
@@ -2,10 +2,10 @@ require 'm3u8'
2
2
  require 'simplecov'
3
3
  require 'coveralls'
4
4
 
5
- SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
5
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([
6
6
  SimpleCov::Formatter::HTMLFormatter,
7
7
  Coveralls::SimpleCov::Formatter
8
- ]
8
+ ])
9
9
  SimpleCov.start
10
10
 
11
11
  # This file was generated by the `rspec --init` command. Conventionally, all
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: m3u8
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Seth Deckard
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-08-15 00:00:00.000000000 Z
11
+ date: 2016-02-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -16,42 +16,42 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '1.6'
19
+ version: '0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '1.6'
26
+ version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ">="
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '2.11'
47
+ version: '3.3'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ">="
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '2.11'
54
+ version: '3.3'
55
55
  description: Generate and parse m3u8 playlists for HTTP Live Streaming (HLS).
56
56
  email:
57
57
  - seth@deckard.me
@@ -63,15 +63,18 @@ files:
63
63
  - ".hound.yml"
64
64
  - ".rspec"
65
65
  - ".rubocop.yml"
66
+ - ".ruby-version"
66
67
  - ".travis.yml"
67
68
  - CHANGELOG.md
68
69
  - Gemfile
70
+ - Guardfile
69
71
  - LICENSE.txt
70
72
  - README.md
71
73
  - Rakefile
72
74
  - lib/m3u8.rb
73
75
  - lib/m3u8/byte_range.rb
74
76
  - lib/m3u8/discontinuity_item.rb
77
+ - lib/m3u8/encryptable.rb
75
78
  - lib/m3u8/error.rb
76
79
  - lib/m3u8/key_item.rb
77
80
  - lib/m3u8/map_item.rb
@@ -81,12 +84,11 @@ files:
81
84
  - lib/m3u8/reader.rb
82
85
  - lib/m3u8/segment_item.rb
83
86
  - lib/m3u8/session_data_item.rb
87
+ - lib/m3u8/session_key_item.rb
84
88
  - lib/m3u8/time_item.rb
85
89
  - lib/m3u8/version.rb
86
90
  - lib/m3u8/writer.rb
87
91
  - m3u8.gemspec
88
- - spec/byte_range_spec.rb
89
- - spec/discontinuity_item_spec.rb
90
92
  - spec/fixtures/encrypted.m3u8
91
93
  - spec/fixtures/iframes.m3u8
92
94
  - spec/fixtures/map_playlist.m3u8
@@ -97,18 +99,21 @@ files:
97
99
  - spec/fixtures/session_data.m3u8
98
100
  - spec/fixtures/variant_angles.m3u8
99
101
  - spec/fixtures/variant_audio.m3u8
100
- - spec/key_item_spec.rb
101
- - spec/m3u8_spec.rb
102
- - spec/map_item_spec.rb
103
- - spec/media_item_spec.rb
104
- - spec/playlist_item_spec.rb
105
- - spec/playlist_spec.rb
106
- - spec/reader_spec.rb
107
- - spec/segment_item_spec.rb
108
- - spec/session_data_item_spec.rb
102
+ - spec/lib/m3u8/byte_range_spec.rb
103
+ - spec/lib/m3u8/discontinuity_item_spec.rb
104
+ - spec/lib/m3u8/key_item_spec.rb
105
+ - spec/lib/m3u8/map_item_spec.rb
106
+ - spec/lib/m3u8/media_item_spec.rb
107
+ - spec/lib/m3u8/playlist_item_spec.rb
108
+ - spec/lib/m3u8/playlist_spec.rb
109
+ - spec/lib/m3u8/reader_spec.rb
110
+ - spec/lib/m3u8/segment_item_spec.rb
111
+ - spec/lib/m3u8/session_data_item_spec.rb
112
+ - spec/lib/m3u8/session_key_item_spec.rb
113
+ - spec/lib/m3u8/time_item_spec.rb
114
+ - spec/lib/m3u8/writer_spec.rb
115
+ - spec/lib/m3u8_spec.rb
109
116
  - spec/spec_helper.rb
110
- - spec/time_item_spec.rb
111
- - spec/writer_spec.rb
112
117
  homepage: https://github.com/sethdeckard/m3u8
113
118
  licenses:
114
119
  - MIT
@@ -129,13 +134,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
129
134
  version: '0'
130
135
  requirements: []
131
136
  rubyforge_project:
132
- rubygems_version: 2.4.5
137
+ rubygems_version: 2.4.5.1
133
138
  signing_key:
134
139
  specification_version: 4
135
140
  summary: Generate and parse m3u8 playlists for HTTP Live Streaming (HLS).
136
141
  test_files:
137
- - spec/byte_range_spec.rb
138
- - spec/discontinuity_item_spec.rb
139
142
  - spec/fixtures/encrypted.m3u8
140
143
  - spec/fixtures/iframes.m3u8
141
144
  - spec/fixtures/map_playlist.m3u8
@@ -146,15 +149,18 @@ test_files:
146
149
  - spec/fixtures/session_data.m3u8
147
150
  - spec/fixtures/variant_angles.m3u8
148
151
  - spec/fixtures/variant_audio.m3u8
149
- - spec/key_item_spec.rb
150
- - spec/m3u8_spec.rb
151
- - spec/map_item_spec.rb
152
- - spec/media_item_spec.rb
153
- - spec/playlist_item_spec.rb
154
- - spec/playlist_spec.rb
155
- - spec/reader_spec.rb
156
- - spec/segment_item_spec.rb
157
- - spec/session_data_item_spec.rb
152
+ - spec/lib/m3u8/byte_range_spec.rb
153
+ - spec/lib/m3u8/discontinuity_item_spec.rb
154
+ - spec/lib/m3u8/key_item_spec.rb
155
+ - spec/lib/m3u8/map_item_spec.rb
156
+ - spec/lib/m3u8/media_item_spec.rb
157
+ - spec/lib/m3u8/playlist_item_spec.rb
158
+ - spec/lib/m3u8/playlist_spec.rb
159
+ - spec/lib/m3u8/reader_spec.rb
160
+ - spec/lib/m3u8/segment_item_spec.rb
161
+ - spec/lib/m3u8/session_data_item_spec.rb
162
+ - spec/lib/m3u8/session_key_item_spec.rb
163
+ - spec/lib/m3u8/time_item_spec.rb
164
+ - spec/lib/m3u8/writer_spec.rb
165
+ - spec/lib/m3u8_spec.rb
158
166
  - spec/spec_helper.rb
159
- - spec/time_item_spec.rb
160
- - spec/writer_spec.rb