apetag 1.1.2 → 1.1.3
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/apetag.rb +13 -5
- data/test/test_apetag.rb +47 -40
- metadata +69 -40
data/apetag.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# encoding: ascii
|
2
3
|
# This library implements a APEv2 reader/writer.
|
3
4
|
# If called from the command line, it prints out the contents of the APEv2 tag
|
4
5
|
# for the given filename arguments.
|
@@ -35,9 +36,12 @@
|
|
35
36
|
# If you find any bugs, would like additional documentation, or want to submit a
|
36
37
|
# patch, please use Rubyforge (http://rubyforge.org/projects/apetag/).
|
37
38
|
#
|
38
|
-
# The
|
39
|
-
#
|
40
|
-
#
|
39
|
+
# The RDoc for the project is available at http://apetag.rubyforge.org.
|
40
|
+
#
|
41
|
+
# The most current source code can be accessed via github
|
42
|
+
# (http://github.com/jeremyevans/ape_tag_libs). Note that the
|
43
|
+
# library isn't modified on a regular basis, so it is unlikely to be different
|
44
|
+
# from the latest release.
|
41
45
|
#
|
42
46
|
# (1) http://wiki.hydrogenaudio.org/index.php?title=APEv2_specification
|
43
47
|
#
|
@@ -101,7 +105,11 @@ class ApeItem < Array
|
|
101
105
|
key_end = data.index("\0", offset += 8)
|
102
106
|
raise ApeTagError, "Missing key-value separator at offset #{offset}" unless key_end
|
103
107
|
raise ApeTagError, "Invalid item length at offset #{offset}" if (next_item_start=length + key_end + 1) > data.length
|
104
|
-
|
108
|
+
begin
|
109
|
+
item = ApeItem.new(data[offset...key_end], data[(key_end+1)...next_item_start].split("\0"))
|
110
|
+
rescue ArgumentError =>e
|
111
|
+
raise ApeTagError, "ArgumentError: #{e.message}"
|
112
|
+
end
|
105
113
|
item.read_only = flags & 1 > 0
|
106
114
|
item.ape_type = ITEM_TYPES[flags/2]
|
107
115
|
return [item, next_item_start]
|
@@ -162,7 +170,7 @@ class ApeItem < Array
|
|
162
170
|
|
163
171
|
# Check if the given key is a valid APE key (string, 2 <= length <= 255, not containing invalid characters or keys).
|
164
172
|
def valid_key?(key)
|
165
|
-
key.is_a?(String) && key.length >= 2 && key.length <= 255 && key !~ BAD_KEY_RE
|
173
|
+
key.is_a?(String) && key.length >= 2 && key.length <= 255 && (key !~ BAD_KEY_RE rescue false)
|
166
174
|
end
|
167
175
|
|
168
176
|
# Check if the given read only flag is valid (boolean).
|
data/test/test_apetag.rb
CHANGED
@@ -7,11 +7,24 @@ require 'test/unit'
|
|
7
7
|
EMPTY_APE_TAG = "APETAGEX\320\a\0\0 \0\0\0\0\0\0\0\0\0\0\240\0\0\0\0\0\0\0\0APETAGEX\320\a\0\0 \0\0\0\0\0\0\0\0\0\0\200\0\0\0\0\0\0\0\0TAG\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\377"
|
8
8
|
EXAMPLE_APE_TAG = "APETAGEX\xd0\x07\x00\x00\xb0\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00Track\x001\x04\x00\x00\x00\x00\x00\x00\x00Date\x002007\t\x00\x00\x00\x00\x00\x00\x00Comment\x00XXXX-0000\x0b\x00\x00\x00\x00\x00\x00\x00Title\x00Love Cheese\x0b\x00\x00\x00\x00\x00\x00\x00Artist\x00Test Artist\x16\x00\x00\x00\x00\x00\x00\x00Album\x00Test Album\x00Other AlbumAPETAGEX\xd0\x07\x00\x00\xb0\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00TAGLove Cheese\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Test Artist\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Test Album, Other Album\x00\x00\x00\x00\x00\x00\x002007XXXX-0000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xff"
|
9
9
|
EXAMPLE_APE_TAG2 = "APETAGEX\xd0\x07\x00\x00\x99\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00Blah\x00Blah\x04\x00\x00\x00\x00\x00\x00\x00Date\x002007\t\x00\x00\x00\x00\x00\x00\x00Comment\x00XXXX-0000\x0b\x00\x00\x00\x00\x00\x00\x00Artist\x00Test Artist\x16\x00\x00\x00\x00\x00\x00\x00Album\x00Test Album\x00Other AlbumAPETAGEX\xd0\x07\x00\x00\x99\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00TAG\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Test Artist\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Test Album, Other Album\x00\x00\x00\x00\x00\x00\x002007XXXX-0000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff"
|
10
|
+
[EMPTY_APE_TAG, EXAMPLE_APE_TAG, EXAMPLE_APE_TAG2].each{|x| x.force_encoding('binary')} if RUBY_VERSION >= '1.9.0'
|
10
11
|
EMPTY_APE_ONLY_TAG, EXAMPLE_APE_ONLY_TAG, EXAMPLE_APE_ONLY_TAG2 = [EMPTY_APE_TAG, EXAMPLE_APE_TAG, EXAMPLE_APE_TAG2].collect{|x|x[0...-128]}
|
11
12
|
EXAMPLE_APE_FIELDS = {"Track"=>["1"], "Comment"=>["XXXX-0000"], "Album"=>["Test Album", "Other Album"], "Title"=>["Love Cheese"], "Artist"=>["Test Artist"], "Date"=>["2007"]}
|
12
13
|
EXAMPLE_APE_FIELDS2 = {"Blah"=>["Blah"], "Comment"=>["XXXX-0000"], "Album"=>["Test Album", "Other Album"], "Artist"=>["Test Artist"], "Date"=>["2007"]}
|
13
14
|
EXAMPLE_APE_TAG_PRETTY_PRINT = "Album: Test Album, Other Album\nArtist: Test Artist\nComment: XXXX-0000\nDate: 2007\nTitle: Love Cheese\nTrack: 1"
|
14
15
|
|
16
|
+
class String
|
17
|
+
if RUBY_VERSION > '1.9.0'
|
18
|
+
def binary
|
19
|
+
force_encoding('binary')
|
20
|
+
end
|
21
|
+
else
|
22
|
+
def binary
|
23
|
+
self
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
15
28
|
class ApeTagTest < Test::Unit::TestCase
|
16
29
|
def get_ape_tag(f, check_id3)
|
17
30
|
f.is_a?(ApeTag) ? f : ApeTag.new(f, check_id3)
|
@@ -169,17 +182,11 @@ class ApeTagTest < Test::Unit::TestCase
|
|
169
182
|
assert_equal 1, ac.length
|
170
183
|
assert_equal 'Blah', ac.string_value
|
171
184
|
|
172
|
-
# Test create works with random object (Hash in this case)
|
173
|
-
ac = ApeItem.create('Blah', 'xfe'=>132)
|
174
|
-
assert_equal ApeItem, ac.class
|
175
|
-
assert_equal 1, ac.length
|
176
|
-
assert_equal 'xfe132', ac.string_value
|
177
|
-
|
178
185
|
# Test create works with array of mixed objects
|
179
|
-
ac = ApeItem.create('Blah', ['sadf', 'adsfas', 11
|
186
|
+
ac = ApeItem.create('Blah', ['sadf', 'adsfas', 11])
|
180
187
|
assert_equal ApeItem, ac.class
|
181
|
-
assert_equal
|
182
|
-
assert_equal "sadf\0adsfas\00011
|
188
|
+
assert_equal 3, ac.length
|
189
|
+
assert_equal "sadf\0adsfas\00011", ac.string_value
|
183
190
|
end
|
184
191
|
|
185
192
|
# Test ApeItem.parse
|
@@ -204,25 +211,25 @@ class ApeTagTest < Test::Unit::TestCase
|
|
204
211
|
assert_raises(ApeTagError){ApeItem.parse(data, 1)}
|
205
212
|
|
206
213
|
# Test parsing with bad/good flags
|
207
|
-
data[4] = 8
|
214
|
+
data[4,1] = 8.chr
|
208
215
|
assert_raises(ApeTagError){ApeItem.parse(data, 0)}
|
209
|
-
data[4] = 0
|
216
|
+
data[4,1] = 0.chr
|
210
217
|
assert_nothing_raised{ApeItem.parse(data, 0)}
|
211
218
|
|
212
219
|
# Test parsing with length longer than string
|
213
|
-
data[0] = 9
|
220
|
+
data[0,1] = 9.chr
|
214
221
|
assert_raises(ApeTagError){ApeItem.parse(data, 0)}
|
215
222
|
|
216
223
|
# Test parsing with length shorter than string gives valid ApeItem
|
217
224
|
# Of course, the next item will probably be parsed incorrectly
|
218
|
-
data[0] = 3
|
225
|
+
data[0,1] = 3.chr
|
219
226
|
assert_nothing_raised{ai, offset = ApeItem.parse(data, 0)}
|
220
227
|
assert_equal 16, offset
|
221
228
|
assert_equal "BlaH", ai.key
|
222
229
|
assert_equal "BlA", ai.string_value
|
223
230
|
|
224
231
|
# Test parsing gets correct key end
|
225
|
-
data[12] =
|
232
|
+
data[12,1] = "3"
|
226
233
|
assert_nothing_raised{ai, offset = ApeItem.parse(data, 0)}
|
227
234
|
assert_equal "BlaH3BlAh", ai.key
|
228
235
|
assert_equal "XYZ", ai.string_value
|
@@ -239,41 +246,41 @@ class ApeTagTest < Test::Unit::TestCase
|
|
239
246
|
assert_nothing_raised{ApeTag.new(StringIO.new(data)).raw}
|
240
247
|
|
241
248
|
# Test read only tags work
|
242
|
-
data[20] = 1
|
249
|
+
data[20,1] = 1.chr
|
243
250
|
assert_nothing_raised{ApeTag.new(StringIO.new(data)).raw}
|
244
251
|
|
245
252
|
# Test other flags values don't work
|
246
253
|
2.upto(255) do |i|
|
247
|
-
data[20] = i
|
254
|
+
data[20,1] = i.chr
|
248
255
|
assert_raises(ApeTagError){ApeTag.new(StringIO.new(data)).raw}
|
249
256
|
end
|
250
|
-
data[20] = 1
|
257
|
+
data[20,1] = 1.chr
|
251
258
|
2.upto(255) do |i|
|
252
|
-
data[52] = i
|
259
|
+
data[52,1] = i.chr
|
253
260
|
assert_raises(ApeTagError){ApeTag.new(StringIO.new(data)).raw}
|
254
|
-
data[20] = i
|
261
|
+
data[20,1] = i.chr
|
255
262
|
assert_raises(ApeTagError){ApeTag.new(StringIO.new(data)).raw}
|
256
263
|
end
|
257
264
|
|
258
265
|
# Test footer size less than minimum size (32)
|
259
|
-
data[44] = 31
|
266
|
+
data[44,1] = 31.chr
|
260
267
|
assert_raises(ApeTagError){ApeTag.new(StringIO.new(data)).raw}
|
261
|
-
data[44] = 0
|
268
|
+
data[44,1] = 0.chr
|
262
269
|
assert_raises(ApeTagError){ApeTag.new(StringIO.new(data)).raw}
|
263
270
|
|
264
271
|
# Test tag size > 8192, when both larger than file and smaller than file
|
265
|
-
data[44] = 225
|
266
|
-
data[45] = 31
|
272
|
+
data[44,1] = 225.chr
|
273
|
+
data[45,1] = 31.chr
|
267
274
|
assert_raises(ApeTagError){ApeTag.new(StringIO.new(data)).raw}
|
268
275
|
assert_raises(ApeTagError){ApeTag.new(StringIO.new(' '*8192+data)).raw}
|
269
276
|
|
270
277
|
data = EMPTY_APE_TAG.dup
|
271
278
|
# Test unmatching header and footer tag size, with footer size wrong
|
272
|
-
data[44] = 33
|
279
|
+
data[44,1] = 33.chr
|
273
280
|
assert_raises(ApeTagError){ApeTag.new(StringIO.new(data)).raw}
|
274
281
|
|
275
282
|
# Test matching header and footer but size to large for file
|
276
|
-
data[12] = 33
|
283
|
+
data[12,1] = 33.chr
|
277
284
|
assert_raises(ApeTagError){ApeTag.new(StringIO.new(data)).raw}
|
278
285
|
|
279
286
|
# Test that header and footer size isn't too large for file, but doesn't
|
@@ -282,51 +289,51 @@ class ApeTagTest < Test::Unit::TestCase
|
|
282
289
|
assert_raises(ApeTagError){ApeTag.new(StringIO.new(data)).raw}
|
283
290
|
|
284
291
|
# Test unmatching header and footer tag size, with header size wrong
|
285
|
-
data[45] = 32
|
292
|
+
data[45,1] = 32.chr
|
286
293
|
assert_raises(ApeTagError){ApeTag.new(StringIO.new(data)).raw}
|
287
294
|
|
288
295
|
data = EMPTY_APE_TAG.dup
|
289
296
|
# Test item count greater than maximum (64)
|
290
|
-
data[48] = 65
|
297
|
+
data[48,1] = 65.chr
|
291
298
|
assert_raises(ApeTagError){ApeTag.new(StringIO.new(data)).raw}
|
292
299
|
|
293
300
|
# Test item count greater than possible given tag size
|
294
|
-
data[48] = 1
|
301
|
+
data[48,1] = 1.chr
|
295
302
|
assert_raises(ApeTagError){ApeTag.new(StringIO.new(data)).raw}
|
296
303
|
|
297
304
|
# Test unmatched header and footer item count, header size wrong
|
298
|
-
data[48] = 0
|
299
|
-
data[16] = 1
|
305
|
+
data[48,1] = 0.chr
|
306
|
+
data[16,1] = 1.chr
|
300
307
|
assert_raises(ApeTagError){ApeTag.new(StringIO.new(data)).raw}
|
301
308
|
|
302
309
|
# Test unmatched header and footer item count, footer size wrong
|
303
310
|
data = EXAMPLE_APE_TAG.dup
|
304
|
-
data[208-16]
|
311
|
+
data[208-16] = 5.chr
|
305
312
|
assert_raises(ApeTagError){ApeTag.new(StringIO.new(data)).fields}
|
306
313
|
|
307
314
|
# Test missing/corrupt header
|
308
315
|
data = EMPTY_APE_TAG.dup
|
309
|
-
data[0] = 0
|
316
|
+
data[0,1] = 0.chr
|
310
317
|
assert_raises(ApeTagError){ApeTag.new(StringIO.new(data)).fields}
|
311
318
|
|
312
319
|
# Test parsing bad first item size
|
313
320
|
data = EXAMPLE_APE_TAG.dup
|
314
|
-
data[32]
|
321
|
+
data[32,1] = 2.chr
|
315
322
|
assert_raises(ApeTagError){ApeTag.new(StringIO.new(data)).fields}
|
316
323
|
|
317
324
|
# Test parsing bad first item invalid key
|
318
325
|
data = EXAMPLE_APE_TAG.dup
|
319
|
-
data[40] = 0
|
326
|
+
data[40,1] = 0.chr
|
320
327
|
assert_raises(ApeTagError){ApeTag.new(StringIO.new(data)).fields}
|
321
328
|
|
322
329
|
# Test parsing bad first item key end
|
323
330
|
data = EXAMPLE_APE_TAG.dup
|
324
|
-
data[45] = 1
|
331
|
+
data[45,1] = 1.chr
|
325
332
|
assert_raises(ApeTagError){ApeTag.new(StringIO.new(data)).fields}
|
326
333
|
|
327
334
|
# Test parsing bad second item length too long
|
328
335
|
data = EXAMPLE_APE_TAG.dup
|
329
|
-
data[47] = 255
|
336
|
+
data[47,1] = 255.chr
|
330
337
|
assert_raises(ApeTagError){ApeTag.new(StringIO.new(data)).fields}
|
331
338
|
|
332
339
|
# Test parsing case insensitive duplicate keys
|
@@ -340,11 +347,11 @@ class ApeTagTest < Test::Unit::TestCase
|
|
340
347
|
|
341
348
|
# Test parsing incorrect item counts
|
342
349
|
data = EXAMPLE_APE_TAG.dup
|
343
|
-
data[16]
|
344
|
-
data[192]
|
350
|
+
data[16,1] = 5.chr
|
351
|
+
data[192,1] = 5.chr
|
345
352
|
assert_raises(ApeTagError){ApeTag.new(StringIO.new(data)).fields}
|
346
|
-
data[16]
|
347
|
-
data[192]
|
353
|
+
data[16,1] = 7.chr
|
354
|
+
data[192,1] = 7.chr
|
348
355
|
assert_raises(ApeTagError){ApeTag.new(StringIO.new(data)).fields}
|
349
356
|
|
350
357
|
# Test updating works in a case insensitive manner
|
metadata
CHANGED
@@ -1,54 +1,83 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.9.4
|
3
|
-
specification_version: 1
|
4
2
|
name: apetag
|
5
3
|
version: !ruby/object:Gem::Version
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
-
|
11
|
-
|
12
|
-
|
13
|
-
rubyforge_project: apetag
|
14
|
-
description:
|
15
|
-
autorequire: apetag
|
16
|
-
default_executable:
|
17
|
-
bindir: bin
|
18
|
-
has_rdoc: true
|
19
|
-
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
-
requirements:
|
21
|
-
- - ">"
|
22
|
-
- !ruby/object:Gem::Version
|
23
|
-
version: 0.0.0
|
24
|
-
version:
|
4
|
+
hash: 21
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 1
|
9
|
+
- 3
|
10
|
+
version: 1.1.3
|
25
11
|
platform: ruby
|
26
|
-
signing_key:
|
27
|
-
cert_chain:
|
28
|
-
post_install_message:
|
29
12
|
authors:
|
30
13
|
- Jeremy Evans
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
- test/test_apetag.rb
|
35
|
-
rdoc_options: []
|
36
|
-
|
37
|
-
extra_rdoc_files: []
|
38
|
-
|
39
|
-
executables: []
|
40
|
-
|
41
|
-
extensions: []
|
42
|
-
|
43
|
-
requirements: []
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
44
17
|
|
18
|
+
date: 2011-01-16 00:00:00 -08:00
|
19
|
+
default_executable:
|
45
20
|
dependencies:
|
46
21
|
- !ruby/object:Gem::Dependency
|
47
22
|
name: cicphash
|
48
|
-
|
49
|
-
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
50
26
|
requirements:
|
51
27
|
- - ">="
|
52
28
|
- !ruby/object:Gem::Version
|
29
|
+
hash: 23
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 0
|
33
|
+
- 0
|
53
34
|
version: 1.0.0
|
54
|
-
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
description:
|
38
|
+
email: code@jeremyevans.net
|
39
|
+
executables: []
|
40
|
+
|
41
|
+
extensions: []
|
42
|
+
|
43
|
+
extra_rdoc_files: []
|
44
|
+
|
45
|
+
files:
|
46
|
+
- apetag.rb
|
47
|
+
- test/test_apetag.rb
|
48
|
+
has_rdoc: true
|
49
|
+
homepage: http://apetag.rubyforge.org
|
50
|
+
licenses: []
|
51
|
+
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options: []
|
54
|
+
|
55
|
+
require_paths:
|
56
|
+
- .
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
hash: 3
|
63
|
+
segments:
|
64
|
+
- 0
|
65
|
+
version: "0"
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
hash: 3
|
72
|
+
segments:
|
73
|
+
- 0
|
74
|
+
version: "0"
|
75
|
+
requirements: []
|
76
|
+
|
77
|
+
rubyforge_project: apetag
|
78
|
+
rubygems_version: 1.3.7
|
79
|
+
signing_key:
|
80
|
+
specification_version: 3
|
81
|
+
summary: APEv2 Tag Reader/Writer
|
82
|
+
test_files:
|
83
|
+
- test/test_apetag.rb
|