m3uzi 0.2.1 → 0.4.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.
- data/lib/m3uzi.rb +229 -130
- data/lib/m3uzi/comment.rb +10 -0
- data/lib/m3uzi/file.rb +30 -4
- data/lib/m3uzi/item.rb +9 -0
- data/lib/m3uzi/stream.rb +14 -4
- data/lib/m3uzi/tag.rb +22 -2
- data/lib/m3uzi/version.rb +1 -1
- data/test/m3uzi_test.rb +7 -7
- metadata +5 -3
data/lib/m3uzi.rb
CHANGED
@@ -1,24 +1,20 @@
|
|
1
1
|
$:<< File.dirname(__FILE__)
|
2
|
+
require 'm3uzi/item'
|
2
3
|
require 'm3uzi/tag'
|
3
4
|
require 'm3uzi/file'
|
4
5
|
require 'm3uzi/stream'
|
6
|
+
require 'm3uzi/comment'
|
5
7
|
require 'm3uzi/version'
|
6
8
|
|
7
9
|
class M3Uzi
|
8
10
|
|
9
|
-
|
10
|
-
VALID_TAGS = %w{TARGETDURATION MEDIA-SEQUENCE ALLOW-CACHE ENDLIST KEY}
|
11
|
-
|
12
|
-
attr_accessor :files, :streams
|
13
|
-
attr_accessor :tags, :comments
|
11
|
+
attr_accessor :header_tags, :playlist_items
|
14
12
|
attr_accessor :final_media_file
|
15
13
|
attr_accessor :version
|
16
14
|
|
17
15
|
def initialize
|
18
|
-
@
|
19
|
-
@
|
20
|
-
@tags = []
|
21
|
-
@comments = []
|
16
|
+
@header_tags = {}
|
17
|
+
@playlist_items = []
|
22
18
|
@final_media_file = true
|
23
19
|
@version = 1
|
24
20
|
end
|
@@ -28,78 +24,161 @@ class M3Uzi
|
|
28
24
|
# Read/Write M3U8 Files
|
29
25
|
#-------------------------------------
|
30
26
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
27
|
+
##
|
28
|
+
## For now, reading m3u8 files is not keeping up to date with writing, so we're
|
29
|
+
## disabling it in this version. (Possibly to be re-introduced in the future.)
|
30
|
+
##
|
31
|
+
# def self.read(path)
|
32
|
+
# m3u = self.new
|
33
|
+
# lines = ::File.readlines(path)
|
34
|
+
# lines.each_with_index do |line, i|
|
35
|
+
# case type(line)
|
36
|
+
# when :tag
|
37
|
+
# name, value = parse_general_tag(line)
|
38
|
+
# m3u.add_tag do |tag|
|
39
|
+
# tag.name = name
|
40
|
+
# tag.value = value
|
41
|
+
# end
|
42
|
+
# when :info
|
43
|
+
# duration, description = parse_file_tag(line)
|
44
|
+
# m3u.add_file do |file|
|
45
|
+
# file.path = lines[i+1].strip
|
46
|
+
# file.duration = duration
|
47
|
+
# file.description = description
|
48
|
+
# end
|
49
|
+
# m3u.final_media_file = false
|
50
|
+
# when :stream
|
51
|
+
# attributes = parse_stream_tag(line)
|
52
|
+
# m3u.add_stream do |stream|
|
53
|
+
# stream.path = lines[i+1].strip
|
54
|
+
# attributes.each_pair do |k,v|
|
55
|
+
# k = k.to_s.downcase.sub('-','_')
|
56
|
+
# next unless [:bandwidth, :program_id, :codecs, :resolution].include?(k)
|
57
|
+
# v = $1 if v.to_s =~ /^"(.*)"$/
|
58
|
+
# stream.send("#{k}=", v)
|
59
|
+
# end
|
60
|
+
# end
|
61
|
+
# when :final
|
62
|
+
# m3u.final_media_file = true
|
63
|
+
# else
|
64
|
+
# next
|
65
|
+
# end
|
66
|
+
# end
|
67
|
+
# m3u
|
68
|
+
# end
|
69
69
|
|
70
70
|
def write_to_io(io_stream)
|
71
|
+
reset_encryption_key_history
|
72
|
+
reset_byterange_history
|
73
|
+
|
71
74
|
check_version_restrictions
|
72
75
|
io_stream << "#EXTM3U\n"
|
73
76
|
io_stream << "#EXT-X-VERSION:#{@version.to_i}\n" if @version > 1
|
74
|
-
|
75
|
-
|
77
|
+
|
78
|
+
if items(File).length > 0
|
79
|
+
max_duration = valid_items(File).map { |f| f.duration.to_f }.max || 10.0
|
80
|
+
io_stream << "#EXT-X-TARGETDURATION:#{max_duration.ceil}\n"
|
76
81
|
end
|
77
|
-
|
78
|
-
|
79
|
-
if
|
80
|
-
|
81
|
-
|
82
|
-
|
82
|
+
|
83
|
+
@header_tags.each do |item|
|
84
|
+
io_stream << (item.format + "\n") if item.valid?
|
85
|
+
end
|
86
|
+
|
87
|
+
@playlist_items.each do |item|
|
88
|
+
next unless item.valid?
|
89
|
+
|
90
|
+
if item.kind_of?(File)
|
91
|
+
encryption_key_line = generate_encryption_key_line(item)
|
92
|
+
io_stream << (encryption_key_line + "\n") if encryption_key_line
|
93
|
+
|
94
|
+
byterange_line = generate_byterange_line(item)
|
95
|
+
io_stream << (byterange_line + "\n") if byterange_line
|
83
96
|
end
|
84
|
-
|
85
|
-
io_stream << "\n"
|
97
|
+
|
98
|
+
io_stream << (item.format + "\n")
|
86
99
|
end
|
87
|
-
|
88
|
-
|
89
|
-
|
100
|
+
|
101
|
+
io_stream << "#EXT-X-ENDLIST\n" if items(File).length > 0 && @final_media_file
|
102
|
+
end
|
103
|
+
|
104
|
+
def write(path)
|
105
|
+
::File.open(path, "w") { |f| write_to_io(f) }
|
106
|
+
end
|
107
|
+
|
108
|
+
def items(kind)
|
109
|
+
@playlist_items.select { |item| item.kind_of?(kind) }
|
110
|
+
end
|
111
|
+
|
112
|
+
def valid_items(kind)
|
113
|
+
@playlist_items.select { |item| item.kind_of?(kind) && item.valid? }
|
114
|
+
end
|
115
|
+
|
116
|
+
#-------------------------------------
|
117
|
+
# Playlist generation helpers.
|
118
|
+
#-------------------------------------
|
119
|
+
|
120
|
+
def reset_encryption_key_history
|
121
|
+
@encryption_key_url = nil
|
122
|
+
@encryption_iv = nil
|
123
|
+
@encryption_sequence = 0
|
124
|
+
end
|
125
|
+
|
126
|
+
def generate_encryption_key_line(file)
|
127
|
+
generate_line = false
|
128
|
+
|
129
|
+
default_iv = @encryption_iv || format_iv(@encryption_sequence)
|
130
|
+
|
131
|
+
if (file.encryption_key_url != :unset) && (file.encryption_key_url != @encryption_key_url)
|
132
|
+
@encryption_key_url = file.encryption_key_url
|
133
|
+
generate_line = true
|
90
134
|
end
|
91
|
-
|
92
|
-
|
93
|
-
|
135
|
+
|
136
|
+
if @encryption_key_url && file.encryption_iv != @encryption_iv
|
137
|
+
@encryption_iv = file.encryption_iv
|
138
|
+
generate_line = true
|
139
|
+
end
|
140
|
+
|
141
|
+
@encryption_sequence += 1
|
142
|
+
|
143
|
+
if generate_line
|
144
|
+
if @encryption_key_url.nil?
|
145
|
+
"#EXT-X-KEY:METHOD=NONE"
|
146
|
+
else
|
147
|
+
attrs = ['METHOD=AES-128']
|
148
|
+
attrs << 'URI="' + @encryption_key_url.gsub('"','%22').gsub(/[\r\n]/,'').strip + '"'
|
149
|
+
attrs << "IV=#{@encryption_iv}" if @encryption_iv
|
150
|
+
'#EXT-X-KEY:' + attrs.join(',')
|
151
|
+
end
|
152
|
+
else
|
153
|
+
nil
|
94
154
|
end
|
95
|
-
io_stream << "#EXT-X-ENDLIST\n" if files.length > 0 && final_media_file
|
96
155
|
end
|
97
156
|
|
98
|
-
def
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
157
|
+
def reset_byterange_history
|
158
|
+
@prev_byterange_endpoint = nil
|
159
|
+
end
|
160
|
+
|
161
|
+
def generate_byterange_line(file)
|
162
|
+
line = nil
|
163
|
+
|
164
|
+
if file.byterange
|
165
|
+
if file.byterange_offset && file.byterange_offset != @prev_byterange_endpoint
|
166
|
+
offset = file.byterange_offset
|
167
|
+
elsif @prev_byterange_endpoint.nil?
|
168
|
+
offset = 0
|
169
|
+
else
|
170
|
+
offset = nil
|
171
|
+
end
|
172
|
+
|
173
|
+
line = "#EXT-X-BYTERANGE:#{file.byterange_offset.to_i}"
|
174
|
+
line += "@#{offset}" if offset
|
175
|
+
|
176
|
+
@prev_byterange_endpoint = offset + file.byterange
|
177
|
+
else
|
178
|
+
@prev_byterange_endpoint = nil
|
179
|
+
end
|
180
|
+
|
181
|
+
line
|
103
182
|
end
|
104
183
|
|
105
184
|
|
@@ -107,14 +186,16 @@ class M3Uzi
|
|
107
186
|
# Files
|
108
187
|
#-------------------------------------
|
109
188
|
|
110
|
-
def add_file(
|
189
|
+
def add_file(path = nil, duration = nil)
|
111
190
|
new_file = M3Uzi::File.new
|
112
|
-
|
113
|
-
|
191
|
+
new_file.path = path if path
|
192
|
+
new_file.duration = duration if duration
|
193
|
+
yield(new_file) if block_given?
|
194
|
+
@playlist_items << new_file
|
114
195
|
end
|
115
196
|
|
116
197
|
def filenames
|
117
|
-
|
198
|
+
items(File).map { |file| file.path }
|
118
199
|
end
|
119
200
|
|
120
201
|
|
@@ -122,14 +203,16 @@ class M3Uzi
|
|
122
203
|
# Streams
|
123
204
|
#-------------------------------------
|
124
205
|
|
125
|
-
def add_stream(
|
206
|
+
def add_stream(path = nil, bandwidth = nil)
|
126
207
|
new_stream = M3Uzi::Stream.new
|
127
|
-
|
128
|
-
|
208
|
+
new_stream.path = path
|
209
|
+
new_stream.bandwidth = bandwidth
|
210
|
+
yield(new_stream) if block_given?
|
211
|
+
@playlist_items << new_stream
|
129
212
|
end
|
130
213
|
|
131
214
|
def stream_names
|
132
|
-
|
215
|
+
items(Stream).map { |stream| stream.path }
|
133
216
|
end
|
134
217
|
|
135
218
|
|
@@ -137,56 +220,65 @@ class M3Uzi
|
|
137
220
|
# Tags
|
138
221
|
#-------------------------------------
|
139
222
|
|
140
|
-
def add_tag(
|
223
|
+
def add_tag(name = nil, value = nil)
|
141
224
|
new_tag = M3Uzi::Tag.new
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
def [](key)
|
147
|
-
tag_name = key.to_s.upcase.gsub("_", "-")
|
148
|
-
obj = tags.detect { |tag| tag.name == tag_name }
|
149
|
-
obj && obj.value
|
225
|
+
new_tag.name = name
|
226
|
+
new_tag.value = value
|
227
|
+
yield(new_tag) if block_given?
|
228
|
+
@header_tags[new_tag.name] = new_tag
|
150
229
|
end
|
151
230
|
|
152
|
-
def []
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
231
|
+
# def [](key)
|
232
|
+
# tag_name = key.to_s.upcase.gsub("_", "-")
|
233
|
+
# obj = tags.detect { |tag| tag.name == tag_name }
|
234
|
+
# obj && obj.value
|
235
|
+
# end
|
236
|
+
#
|
237
|
+
# def []=(key, value)
|
238
|
+
# add_tag do |tag|
|
239
|
+
# tag.name = key
|
240
|
+
# tag.value = value
|
241
|
+
# end
|
242
|
+
# end
|
158
243
|
|
159
244
|
|
160
245
|
#-------------------------------------
|
161
246
|
# Comments
|
162
247
|
#-------------------------------------
|
163
248
|
|
164
|
-
def add_comment(comment)
|
165
|
-
|
249
|
+
def add_comment(comment = nil)
|
250
|
+
new_comment = M3Uzi::Comment.new
|
251
|
+
new_comment.text = comment
|
252
|
+
yield(new_comment) if block_given?
|
253
|
+
@playlist_items << new_comment
|
166
254
|
end
|
167
255
|
|
168
|
-
def <<(comment)
|
169
|
-
|
170
|
-
end
|
256
|
+
# def <<(comment)
|
257
|
+
# add_comment(comment)
|
258
|
+
# end
|
171
259
|
|
172
260
|
def check_version_restrictions
|
173
261
|
@version = 1
|
174
262
|
|
263
|
+
#
|
175
264
|
# Version 2 Features
|
176
|
-
|
265
|
+
#
|
266
|
+
|
267
|
+
# Check for custom IV
|
268
|
+
if valid_items(File).detect { |item| item.encryption_key_url && item.encryption_iv }
|
177
269
|
@version = 2 if @version < 2
|
178
270
|
end
|
179
271
|
|
180
272
|
# Version 3 Features
|
181
|
-
if
|
273
|
+
if valid_items(File).detect { |item| item.duration.kind_of?(Float) }
|
182
274
|
@version = 3 if @version < 3
|
183
275
|
end
|
184
276
|
|
185
277
|
# Version 4 Features
|
186
|
-
if
|
278
|
+
if valid_items(File).detect { |item| item.byterange }
|
187
279
|
@version = 4 if @version < 4
|
188
280
|
end
|
189
|
-
if
|
281
|
+
if valid_items(Tag).detect { |item| ['MEDIA','I-FRAMES-ONLY'].include?(item.name) }
|
190
282
|
@version = 4 if @version < 4
|
191
283
|
end
|
192
284
|
|
@@ -199,36 +291,43 @@ class M3Uzi
|
|
199
291
|
|
200
292
|
protected
|
201
293
|
|
202
|
-
def self.type(line)
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
end
|
220
|
-
|
221
|
-
def self.parse_general_tag(line)
|
222
|
-
|
294
|
+
# def self.type(line)
|
295
|
+
# case line
|
296
|
+
# when /^\s*$/
|
297
|
+
# :whitespace
|
298
|
+
# when /^#(?!EXT)/
|
299
|
+
# :comment
|
300
|
+
# when /^#EXTINF/
|
301
|
+
# :info
|
302
|
+
# when /^#EXT(-X)?-STREAM-INF/
|
303
|
+
# :stream
|
304
|
+
# when /^#EXT(-X)?-ENDLIST/
|
305
|
+
# :final
|
306
|
+
# when /^#EXT(?!INF)/
|
307
|
+
# :tag
|
308
|
+
# else
|
309
|
+
# :file
|
310
|
+
# end
|
311
|
+
# end
|
312
|
+
#
|
313
|
+
# def self.parse_general_tag(line)
|
314
|
+
# line.match(/^#EXT(?:-X-)?(?!STREAM-INF)([^:\n]+)(:([^\n]+))?$/).values_at(1, 3)
|
315
|
+
# end
|
316
|
+
#
|
317
|
+
# def self.parse_file_tag(line)
|
318
|
+
# line.match(/^#EXTINF:[ \t]*(\d+),?[ \t]*(.*)$/).values_at(1, 2)
|
319
|
+
# end
|
320
|
+
#
|
321
|
+
# def self.parse_stream_tag(line)
|
322
|
+
# match = line.match(/^#EXT-X-STREAM-INF:(.*)$/)[1]
|
323
|
+
# match.scan(/([A-Z-]+)\s*=\s*("[^"]*"|[^,]*)/) # return attributes as array of arrays
|
324
|
+
# end
|
325
|
+
|
326
|
+
def self.format_iv(num)
|
327
|
+
'0x' + num.to_s(16).rjust(32,'0')
|
223
328
|
end
|
224
329
|
|
225
|
-
def
|
226
|
-
|
330
|
+
def format_iv(num)
|
331
|
+
self.class.format_iv(num)
|
227
332
|
end
|
228
|
-
|
229
|
-
def self.parse_stream_tag(line)
|
230
|
-
match = line.match(/^#EXT-X-STREAM-INF:(.*)$/)[1]
|
231
|
-
match.scan(/([A-Z-]+)\s*=\s*("[^"]*"|[^,]*)/) # return attributes as array of arrays
|
232
|
-
end
|
233
|
-
|
234
333
|
end
|
data/lib/m3uzi/file.rb
CHANGED
@@ -1,15 +1,41 @@
|
|
1
1
|
class M3Uzi
|
2
|
-
class File
|
2
|
+
class File < Item
|
3
3
|
|
4
|
-
attr_accessor :path, :duration, :description, :byterange
|
4
|
+
attr_accessor :path, :duration, :description, :byterange, :byterange_offset, :encryption_key_url, :encryption_iv
|
5
|
+
|
6
|
+
# Unsupported tags: PROGRAM-DATE-TIME, DISCONTINUITY, I-FRAMES-ONLY
|
7
|
+
# Autogenerated tags: EXTINF, BYTERANGE, KEY, VERSION, TARGETDURATION, ENDLIST
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@encryption_key_url = :unset
|
11
|
+
end
|
5
12
|
|
6
13
|
def attribute_string
|
7
14
|
if duration.kind_of?(Float)
|
8
|
-
"#{sprintf('%0.4f',duration)}
|
15
|
+
"#{sprintf('%0.4f',duration)}," + description.to_s.gsub(/[\r\n]/,' ').strip
|
9
16
|
else
|
10
|
-
"#{duration}
|
17
|
+
"#{duration.to_i.round}," + description.to_s.gsub(/[\r\n]/,' ').strip
|
11
18
|
end
|
12
19
|
end
|
13
20
|
|
21
|
+
def format
|
22
|
+
# Need to add key info if appropriate?
|
23
|
+
"#EXTINF:#{attribute_string}\n#{path}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def encryption_iv=(value)
|
27
|
+
if value.to_s =~ /^0x/i
|
28
|
+
value = $'
|
29
|
+
end
|
30
|
+
|
31
|
+
if value.kind_of?(String)
|
32
|
+
raise "Invalid encryption_iv given" unless value.length <= 32 && value =~ /^[0-9a-f]+$/i
|
33
|
+
@encryption_iv = '0x' + value.downcase.rjust(32,'0')
|
34
|
+
elsif value.nil?
|
35
|
+
@encryption_iv = nil
|
36
|
+
else
|
37
|
+
@encryption_iv = M3Uzi.format_iv(value.to_i)
|
38
|
+
end
|
39
|
+
end
|
14
40
|
end
|
15
41
|
end
|
data/lib/m3uzi/item.rb
ADDED
data/lib/m3uzi/stream.rb
CHANGED
@@ -1,17 +1,27 @@
|
|
1
1
|
class M3Uzi
|
2
|
-
|
3
|
-
class Stream
|
2
|
+
class Stream < Item
|
4
3
|
|
5
4
|
attr_accessor :path, :bandwidth, :program_id, :codecs, :resolution
|
6
5
|
|
6
|
+
# Unsupported tags: EXT-X-MEDIA, EXT-X-I-FRAME-STREAM-INF
|
7
|
+
# Unsupported attributes of EXT-X-STREAM-INF: AUDIO, VIDEO
|
8
|
+
|
7
9
|
def attribute_string
|
8
10
|
s = []
|
9
|
-
s << "PROGRAM-ID=#{program_id || 1}"
|
10
|
-
s << "BANDWIDTH=#{bandwidth}"
|
11
|
+
s << "PROGRAM-ID=#{program_id.to_i || 1}"
|
12
|
+
s << "BANDWIDTH=#{bandwidth.to_i}"
|
11
13
|
s << "CODECS=\"#{codecs}\"" if codecs
|
12
14
|
s << "RESOLUTION=#{resolution}" if resolution
|
13
15
|
s.join(',')
|
14
16
|
end
|
17
|
+
|
18
|
+
def format
|
19
|
+
"#EXT-X-STREAM-INF:#{attribute_string}\n#{path}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def valid?
|
23
|
+
!!(path && bandwidth)
|
24
|
+
end
|
15
25
|
end
|
16
26
|
|
17
27
|
end
|
data/lib/m3uzi/tag.rb
CHANGED
@@ -1,14 +1,34 @@
|
|
1
1
|
class M3Uzi
|
2
|
-
|
3
|
-
class Tag
|
2
|
+
class Tag < Item
|
4
3
|
|
5
4
|
attr_reader :name
|
6
5
|
attr_accessor :value
|
7
6
|
|
7
|
+
VALID_TAGS = %w{PLAYLIST-TYPE ALLOW-CACHE}
|
8
|
+
|
9
|
+
# Unsupported tags: MEDIA-SEQUENCE, I-FRAMES-ONLY, PLAYLIST-TYPE
|
10
|
+
# Autogenerated tags: EXTM3U, VERSION, ENDLIST, BYTERANGE, TARGETDURATION
|
11
|
+
|
8
12
|
def name=(n)
|
9
13
|
@name = n.to_s.upcase.gsub("_", "-")
|
10
14
|
end
|
11
15
|
|
16
|
+
def format
|
17
|
+
string << "#EXT-X-#{name}"
|
18
|
+
string << ":#{value}" if value
|
19
|
+
string
|
20
|
+
end
|
21
|
+
|
22
|
+
def valid?
|
23
|
+
case name
|
24
|
+
when 'PLAYLIST-TYPE'
|
25
|
+
['EVENT','VOD'].include?(value)
|
26
|
+
when 'ALLOW-CACHE'
|
27
|
+
['YES','NO'].include?(value)
|
28
|
+
else
|
29
|
+
true
|
30
|
+
end
|
31
|
+
end
|
12
32
|
end
|
13
33
|
|
14
34
|
end
|
data/lib/m3uzi/version.rb
CHANGED
data/test/m3uzi_test.rb
CHANGED
@@ -12,18 +12,18 @@ class M3UziTest < Test::Unit::TestCase
|
|
12
12
|
assert_equal M3Uzi, m3u.class
|
13
13
|
end
|
14
14
|
|
15
|
-
should "read in an index file" do
|
16
|
-
|
17
|
-
|
18
|
-
end
|
15
|
+
# should "read in an index file" do
|
16
|
+
# m3u = M3Uzi.read(File.join(File.dirname(__FILE__), "fixtures/index.m3u8"))
|
17
|
+
# assert_equal M3Uzi, m3u.class
|
18
|
+
# end
|
19
19
|
end
|
20
20
|
|
21
21
|
context "with protocol versions" do
|
22
22
|
should "set version 2 for encryption IV" do
|
23
23
|
m3u = M3Uzi.new
|
24
|
-
m3u
|
24
|
+
m3u.add_file('1.ts',10) { |f| f.encryption_key_url = "key.dat" }
|
25
25
|
assert_equal 1, m3u.check_version_restrictions
|
26
|
-
m3u
|
26
|
+
m3u.add_file('1.ts',10) { |f| f.encryption_key_url = "key.dat"; f.encryption_iv = "0x1234567890abcdef1234567890abcdef" }
|
27
27
|
assert_equal 2, m3u.check_version_restrictions
|
28
28
|
|
29
29
|
output_stream = StringIO.new
|
@@ -53,7 +53,7 @@ class M3UziTest < Test::Unit::TestCase
|
|
53
53
|
|
54
54
|
output_stream = StringIO.new
|
55
55
|
m3u.write_to_io(output_stream)
|
56
|
-
assert output_stream.string =~ /:10\.
|
56
|
+
assert output_stream.string =~ /:10\.0000,/
|
57
57
|
assert output_stream.string !~ /:10,/
|
58
58
|
end
|
59
59
|
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
+
- 4
|
7
8
|
- 2
|
8
|
-
|
9
|
-
version: 0.2.1
|
9
|
+
version: 0.4.2
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Brandon Arbini
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-
|
18
|
+
date: 2011-12-12 00:00:00 -08:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|
@@ -30,7 +30,9 @@ extensions: []
|
|
30
30
|
extra_rdoc_files: []
|
31
31
|
|
32
32
|
files:
|
33
|
+
- lib/m3uzi/comment.rb
|
33
34
|
- lib/m3uzi/file.rb
|
35
|
+
- lib/m3uzi/item.rb
|
34
36
|
- lib/m3uzi/stream.rb
|
35
37
|
- lib/m3uzi/tag.rb
|
36
38
|
- lib/m3uzi/version.rb
|