apetag 1.1.4 → 1.1.5
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 +7 -0
- data/apetag.rb +65 -43
- data/test/test_apetag.rb +161 -12
- metadata +33 -57
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 36144161c33f413d750ae832dbec6ba9612a0001
|
|
4
|
+
data.tar.gz: b9831a6a7e4b30aa0796a2a8fa9f0a229cdf6ce0
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 45ed5df49bcd11636c6bf51d37c6eee643b283536850b2f91b3ef3459ba61cc162cf01f4da32252e36b86082ea654df292b4fc5043e5bf5b65da7d2bf5f04e07
|
|
7
|
+
data.tar.gz: 844c79409f01a86b9cf21becc3c62a5b643bb023314ea0c90ef08b8659f78f23cca193de5c84af87a5a0bc31297971411ed28e85086e15b40848e42460f35930
|
data/apetag.rb
CHANGED
|
@@ -69,7 +69,9 @@ class ApeItem < Array
|
|
|
69
69
|
return value
|
|
70
70
|
end
|
|
71
71
|
value = [value] unless value.is_a?(Array)
|
|
72
|
-
new(key, value)
|
|
72
|
+
item = new(key, value)
|
|
73
|
+
item.valid?
|
|
74
|
+
item
|
|
73
75
|
end
|
|
74
76
|
|
|
75
77
|
# Parse an ApeItem from the given data string starting at the provided offset.
|
|
@@ -84,12 +86,10 @@ class ApeItem < Array
|
|
|
84
86
|
raise ApeTagError, "Missing key-value separator at offset #{offset}" unless key_end
|
|
85
87
|
raise ApeTagError, "Invalid item length at offset #{offset}" if (next_item_start=length + key_end + 1) > data.length
|
|
86
88
|
begin
|
|
87
|
-
item = ApeItem.new_from_parse(data[offset...key_end], data[(key_end+1)...next_item_start].split("\0"))
|
|
89
|
+
item = ApeItem.new_from_parse(data[offset...key_end], data[(key_end+1)...next_item_start].split("\0"), flags)
|
|
88
90
|
rescue ArgumentError =>e
|
|
89
91
|
raise ApeTagError, "ArgumentError: #{e.message}"
|
|
90
92
|
end
|
|
91
|
-
item.read_only = flags & 1 > 0
|
|
92
|
-
item.ape_type = ITEM_TYPES[flags/2]
|
|
93
93
|
return [item, next_item_start]
|
|
94
94
|
end
|
|
95
95
|
|
|
@@ -101,7 +101,6 @@ class ApeItem < Array
|
|
|
101
101
|
self.read_only = false
|
|
102
102
|
self.ape_type = ITEM_TYPES[0]
|
|
103
103
|
super(value)
|
|
104
|
-
raise ApeTagError, "Invalid item value encoding (non UTF-8)" unless valid_value?
|
|
105
104
|
end
|
|
106
105
|
|
|
107
106
|
# Set ape_type if valid, otherwise raise ApeTagError.
|
|
@@ -162,14 +161,16 @@ class ApeItem < Array
|
|
|
162
161
|
# Check if the string value is valid UTF-8.
|
|
163
162
|
def valid_value?
|
|
164
163
|
begin
|
|
165
|
-
if
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
164
|
+
if ape_type == 'utf8' || ape_type == 'external'
|
|
165
|
+
if RUBY_VERSION >= '1.9'
|
|
166
|
+
begin
|
|
167
|
+
map!{|v| v.to_s.encode('UTF-8')}
|
|
168
|
+
rescue EncodingError
|
|
169
|
+
return false
|
|
170
|
+
end
|
|
170
171
|
end
|
|
172
|
+
string_value.unpack('U*')
|
|
171
173
|
end
|
|
172
|
-
string_value.unpack('U*') if ape_type == 'utf8' || ape_type == 'external'
|
|
173
174
|
rescue ArgumentError
|
|
174
175
|
false
|
|
175
176
|
else
|
|
@@ -177,11 +178,26 @@ class ApeItem < Array
|
|
|
177
178
|
end
|
|
178
179
|
end
|
|
179
180
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
181
|
+
def self.new_from_parse(key, value, flags)
|
|
182
|
+
ape_type = flags/2
|
|
183
|
+
if RUBY_VERSION >= '1.9.0'
|
|
184
|
+
key.force_encoding('US-ASCII')
|
|
185
|
+
case ape_type
|
|
186
|
+
when 0, 2
|
|
187
|
+
value = value.map{|v| v.to_s.force_encoding('UTF-8')}
|
|
188
|
+
when 1
|
|
189
|
+
value = value.map{|v| v.to_s.force_encoding('BINARY')}
|
|
190
|
+
end
|
|
183
191
|
end
|
|
192
|
+
value = [''] if value.empty?
|
|
193
|
+
item = new(key, value)
|
|
194
|
+
item.read_only = flags & 1 > 0
|
|
195
|
+
item.ape_type = ITEM_TYPES[ape_type]
|
|
196
|
+
raise ApeTagError, "Invalid item value encoding (non UTF-8)" unless item.valid_value?
|
|
197
|
+
item
|
|
198
|
+
end
|
|
184
199
|
|
|
200
|
+
if RUBY_VERSION >= '1.9.0'
|
|
185
201
|
def encoded_key(key)
|
|
186
202
|
begin
|
|
187
203
|
key.encode('US-ASCII')
|
|
@@ -191,14 +207,12 @@ class ApeItem < Array
|
|
|
191
207
|
end
|
|
192
208
|
|
|
193
209
|
def normalize_encodings
|
|
194
|
-
map!{|v| v.to_s.encode('UTF-8')}
|
|
210
|
+
map!{|v| v.to_s.encode('UTF-8')} if ape_type == 'utf8' || ape_type == 'external'
|
|
195
211
|
self
|
|
212
|
+
rescue Encoding::UndefinedConversionError => e
|
|
213
|
+
raise ApeTagError, "#{e.class}: #{e.message}"
|
|
196
214
|
end
|
|
197
215
|
else
|
|
198
|
-
def self.new_from_parse(key, value)
|
|
199
|
-
new(key, value)
|
|
200
|
-
end
|
|
201
|
-
|
|
202
216
|
def encoded_key(key)
|
|
203
217
|
key
|
|
204
218
|
end
|
|
@@ -242,7 +256,7 @@ class ApeTag
|
|
|
242
256
|
Goa, Drum & Bass, Club-House, Hardcore, Terror, Indie, BritPop, Negerpunk,
|
|
243
257
|
Polsk Punk, Beat, Christian Gangsta Rap, Heavy Metal, Black Metal,
|
|
244
258
|
Crossover, Contemporary Christian, Christian Rock, Merengue, Salsa,
|
|
245
|
-
|
|
259
|
+
Thrash Metal, Anime, Jpop, Synthpop'.split(',').collect{|g| g.strip}
|
|
246
260
|
ID3_GENRES_HASH = CICPHash.new(255.chr)
|
|
247
261
|
ID3_GENRES.each_with_index{|g,i| ID3_GENRES_HASH[g] = i.chr }
|
|
248
262
|
FILE_OBJ_METHODS = %w'close seek read pos write truncate'
|
|
@@ -275,7 +289,7 @@ class ApeTag
|
|
|
275
289
|
@check_id3 = check_id3.nil? ? @@check_id3 : check_id3
|
|
276
290
|
else
|
|
277
291
|
@filename = filename.to_s
|
|
278
|
-
@check_id3 = check_id3.nil? ?
|
|
292
|
+
@check_id3 = check_id3.nil? ? (MP3_RE.match(@filename) ? true : nil) : check_id3
|
|
279
293
|
end
|
|
280
294
|
end
|
|
281
295
|
|
|
@@ -284,10 +298,16 @@ class ApeTag
|
|
|
284
298
|
@has_tag.nil? ? access_file('rb'){has_tag} : @has_tag
|
|
285
299
|
end
|
|
286
300
|
|
|
301
|
+
# Check the file for an ID3 tag. Returns true or false. Raises ApeTagError for corrupt tags.
|
|
302
|
+
def has_id3?
|
|
303
|
+
exists?
|
|
304
|
+
id3 != ''
|
|
305
|
+
end
|
|
306
|
+
|
|
287
307
|
# Remove an APE tag from a file, if one exists.
|
|
288
308
|
# Returns true. Raises ApeTagError for corrupt tags.
|
|
289
309
|
def remove!
|
|
290
|
-
access_file('rb+'){file.truncate(tag_start) if has_tag}
|
|
310
|
+
access_file('rb+'){file.truncate(tag_start) if has_tag || has_id3?}
|
|
291
311
|
@has_tag, @fields, @id3, @tag_size, @tag_start, @tag_data, @tag_header, @tag_footer, @tag_item_count = []
|
|
292
312
|
true
|
|
293
313
|
end
|
|
@@ -302,8 +322,8 @@ class ApeTag
|
|
|
302
322
|
def pretty_print
|
|
303
323
|
begin
|
|
304
324
|
fields.values.sort_by{|value| value.key}.collect{|value| "#{value.key}: #{value.join(', ')}"}.join("\n")
|
|
305
|
-
rescue ApeTagError
|
|
306
|
-
"CORRUPT TAG
|
|
325
|
+
rescue ApeTagError => e
|
|
326
|
+
"CORRUPT TAG!: #{e.message}"
|
|
307
327
|
rescue Errno::ENOENT, Errno::EINVAL
|
|
308
328
|
"FILE NOT FOUND!"
|
|
309
329
|
end
|
|
@@ -464,27 +484,29 @@ class ApeTag
|
|
|
464
484
|
# If the file doesn't have an ID3 and the file already has an APE tag or
|
|
465
485
|
# check_id3 is not set, an ID3 won't be added.
|
|
466
486
|
def update_id3
|
|
467
|
-
return if id3.
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
487
|
+
return if id3.empty? && has_tag
|
|
488
|
+
if !id3.empty? || check_id3
|
|
489
|
+
id3_fields = CICPHash.new('')
|
|
490
|
+
id3_fields['genre'] = 255.chr
|
|
491
|
+
fields.values.each do |value|
|
|
492
|
+
case value.key
|
|
493
|
+
when /\Atrack/i
|
|
494
|
+
id3_fields['track'] = value.string_value.to_i
|
|
495
|
+
id3_fields['track'] = 0 if id3_fields['track'] > 255
|
|
496
|
+
id3_fields['track'] = id3_fields['track'].chr
|
|
497
|
+
when /\Agenre/i
|
|
498
|
+
id3_fields['genre'] = ID3_GENRES_HASH[value.first]
|
|
499
|
+
when /\Adate\z/i
|
|
500
|
+
match = YEAR_RE.match(value.string_value)
|
|
501
|
+
id3_fields['year'] = match[0] if match
|
|
502
|
+
when /\A(title|artist|album|year|comment)\z/i
|
|
503
|
+
id3_fields[value.key] = value.join(', ')
|
|
504
|
+
end
|
|
483
505
|
end
|
|
506
|
+
@id3 = ["TAG", id3_fields['title'], id3_fields['artist'], id3_fields['album'],
|
|
507
|
+
id3_fields['year'], id3_fields['comment'], "\0", id3_fields['track'],
|
|
508
|
+
id3_fields['genre']].pack("a3a30a30a30a4a28a1a1a1")
|
|
484
509
|
end
|
|
485
|
-
@id3 = ["TAG", id3_fields['title'], id3_fields['artist'], id3_fields['album'],
|
|
486
|
-
id3_fields['year'], id3_fields['comment'], "\0", id3_fields['track'],
|
|
487
|
-
id3_fields['genre']].pack("a3a30a30a30a4a28a1a1a1")
|
|
488
510
|
end
|
|
489
511
|
|
|
490
512
|
# Write the APEv2 and ID3v1.1 tags to disk.
|
data/test/test_apetag.rb
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
require 'rubygems'
|
|
3
3
|
require 'apetag'
|
|
4
|
-
require 'stringio'
|
|
5
4
|
require 'test/unit'
|
|
5
|
+
require 'stringio'
|
|
6
|
+
require 'fileutils'
|
|
6
7
|
|
|
7
8
|
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
9
|
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"
|
|
@@ -13,19 +14,169 @@ EXAMPLE_APE_FIELDS = {"Track"=>["1"], "Comment"=>["XXXX-0000"], "Album"=>["Test
|
|
|
13
14
|
EXAMPLE_APE_FIELDS2 = {"Blah"=>["Blah"], "Comment"=>["XXXX-0000"], "Album"=>["Test Album", "Other Album"], "Artist"=>["Test Artist"], "Date"=>["2007"]}
|
|
14
15
|
EXAMPLE_APE_TAG_PRETTY_PRINT = "Album: Test Album, Other Album\nArtist: Test Artist\nComment: XXXX-0000\nDate: 2007\nTitle: Love Cheese\nTrack: 1"
|
|
15
16
|
|
|
16
|
-
class
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
class ApeTagTest < Test::Unit::TestCase
|
|
18
|
+
def binary(str)
|
|
19
|
+
str.force_encoding('BINARY') if str.respond_to?(:force_encoding)
|
|
20
|
+
str
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def utf8(str)
|
|
24
|
+
str.force_encoding('UTF-8') if str.respond_to?(:force_encoding)
|
|
25
|
+
str
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def tagname(name)
|
|
29
|
+
"../test-files/#{name}#{'.tag' unless name =~ /\./}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def tag(name)
|
|
33
|
+
ApeTag.new(tagname(name))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def assert_apetag_raised(name, msg)
|
|
37
|
+
yield tag(name)
|
|
38
|
+
rescue ApeTagError => e
|
|
39
|
+
assert(e.message.include?(msg), "Expected: #{msg.inspect}, received: #{e.message.inspect}")
|
|
21
40
|
else
|
|
22
|
-
|
|
23
|
-
|
|
41
|
+
assert(false, "#{name} did not raise ApeTagError: #{msg}")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def corrupt(name, msg)
|
|
45
|
+
assert_apetag_raised(name, msg){|tag| tag.fields}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def with_test_file(before, opts={})
|
|
49
|
+
temp_name = tagname(opts[:name] || 'test')
|
|
50
|
+
FileUtils.copy(tagname(before), temp_name)
|
|
51
|
+
yield temp_name
|
|
52
|
+
File.delete(temp_name)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def assert_files_equal(before, after, opts={})
|
|
56
|
+
with_test_file(before, opts) do |temp_name|
|
|
57
|
+
yield(opts[:yield] == :name ? temp_name : ApeTag.new(temp_name))
|
|
58
|
+
assert_equal(File.read(tagname(after)), File.read(temp_name))
|
|
24
59
|
end
|
|
25
60
|
end
|
|
26
|
-
end
|
|
27
61
|
|
|
28
|
-
|
|
62
|
+
def test_corrupt
|
|
63
|
+
corrupt("corrupt-count-larger-than-possible", "Item count (1) is larger than possible")
|
|
64
|
+
corrupt("corrupt-count-mismatch", "Header and footer item count does not match")
|
|
65
|
+
corrupt("corrupt-count-over-max-allowed", "Item count (97) is larger than 64")
|
|
66
|
+
corrupt("corrupt-data-remaining", "Data remaining after specified number of items parsed")
|
|
67
|
+
corrupt("corrupt-duplicate-item-key", "Multiple items with same key (\"name\")")
|
|
68
|
+
corrupt("corrupt-finished-without-parsing-all-items", "End of tag reached but more items specified")
|
|
69
|
+
corrupt("corrupt-footer-flags", "Tag has bad footer flags")
|
|
70
|
+
corrupt("corrupt-header", "Missing header")
|
|
71
|
+
corrupt("corrupt-item-flags-invalid", "Invalid item flags at offset 0")
|
|
72
|
+
corrupt("corrupt-item-length-invalid", "Invalid item length at offset 0")
|
|
73
|
+
corrupt("corrupt-key-invalid", "Invalid APE key")
|
|
74
|
+
corrupt("corrupt-key-too-short", "Invalid APE key")
|
|
75
|
+
corrupt("corrupt-key-too-long", "Invalid APE key")
|
|
76
|
+
corrupt("corrupt-min-size", "Tag size (57) smaller than minimum size")
|
|
77
|
+
corrupt("corrupt-missing-key-value-separator", "Missing key-value separator at offset 8")
|
|
78
|
+
corrupt("corrupt-next-start-too-large", "Invalid item length at offset 8")
|
|
79
|
+
corrupt("corrupt-size-larger-than-possible", "Tag size (65) larger than possible")
|
|
80
|
+
corrupt("corrupt-size-mismatch", "Header and footer size does not match")
|
|
81
|
+
corrupt("corrupt-size-over-max-allowed", "Tag size (61504) larger than possible")
|
|
82
|
+
corrupt("corrupt-value-not-utf8", "Invalid item value encoding (non UTF-8)")
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def test_exists?
|
|
86
|
+
assert_equal(false, tag("missing-ok").exists?)
|
|
87
|
+
assert_equal(true, tag("good-empty").exists?)
|
|
88
|
+
assert_equal(false, tag("good-empty-id3-only").exists?)
|
|
89
|
+
assert_equal(true, tag("good-empty-id3").exists?)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def test_has_id3?
|
|
93
|
+
assert_equal(false, tag("missing-ok").has_id3?)
|
|
94
|
+
assert_equal(false, tag("good-empty").has_id3?)
|
|
95
|
+
assert_equal(true, tag("good-empty-id3-only").has_id3?)
|
|
96
|
+
assert_equal(true, tag("good-empty-id3").has_id3?)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def test_parsing
|
|
100
|
+
assert_equal({}, tag("good-empty").fields)
|
|
101
|
+
assert_equal({'name'=>['value']}, tag("good-simple-1").fields)
|
|
102
|
+
assert_equal(['value'], tag("good-simple-1").fields['Name'])
|
|
103
|
+
|
|
104
|
+
assert_equal(63, tag("good-many-items").fields.size)
|
|
105
|
+
assert_equal([''], tag("good-many-items").fields['0n'])
|
|
106
|
+
assert_equal(['a'], tag("good-many-items").fields['1n'])
|
|
107
|
+
assert_equal(['a' * 62], tag("good-many-items").fields['62n'])
|
|
108
|
+
|
|
109
|
+
assert_equal({'name'=>['va', 'ue']}, tag("good-multiple-values").fields)
|
|
110
|
+
|
|
111
|
+
assert_equal('name', tag("good-simple-1").fields['name'].key)
|
|
112
|
+
assert_equal('utf8', tag("good-simple-1").fields['name'].ape_type)
|
|
113
|
+
assert_equal(false, tag("good-simple-1").fields['name'].read_only)
|
|
114
|
+
|
|
115
|
+
assert_equal('name', tag("good-simple-1-ro-external").fields['name'].key)
|
|
116
|
+
assert_equal(['value'], tag("good-simple-1-ro-external").fields['name'])
|
|
117
|
+
assert_equal('external', tag("good-simple-1-ro-external").fields['name'].ape_type)
|
|
118
|
+
assert_equal(true, tag("good-simple-1-ro-external").fields['name'].read_only)
|
|
119
|
+
|
|
120
|
+
assert_equal('name', tag("good-binary-non-utf8-value").fields['name'].key)
|
|
121
|
+
assert_equal(binary("v\x81lue"), tag("good-binary-non-utf8-value").fields['name'][0])
|
|
122
|
+
assert_equal('binary', tag("good-binary-non-utf8-value").fields['name'].ape_type)
|
|
123
|
+
assert_equal(false, tag("good-binary-non-utf8-value").fields['name'].read_only)
|
|
124
|
+
|
|
125
|
+
assert_equal({'name'=>['value']}, ApeTag.new(File.open(tagname("good-simple-1"), 'rb')).fields)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def test_remove!
|
|
129
|
+
assert_files_equal('good-empty', 'missing-ok'){|tag| tag.remove!}
|
|
130
|
+
assert_files_equal('good-empty-id3', 'missing-ok'){|tag| tag.remove!}
|
|
131
|
+
assert_files_equal('good-empty-id3-only', 'missing-ok'){|tag| tag.remove!}
|
|
132
|
+
assert_files_equal('missing-10k', 'missing-10k'){|tag| tag.remove!}
|
|
133
|
+
assert_files_equal('good-empty-id3', 'missing-ok', :yield=>:name){|temp_name| ApeTag.new(File.open(temp_name, 'rb+')).remove!}
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def test_update
|
|
137
|
+
assert_files_equal('good-empty', 'good-empty'){|tag| tag.update{|f|}}
|
|
138
|
+
assert_files_equal('missing-ok', 'good-empty'){|tag| tag.update{|f|}}
|
|
139
|
+
assert_files_equal('good-empty', 'good-simple-1'){|tag| tag.update{|f| f['name'] = 'value'}}
|
|
140
|
+
assert_files_equal('good-simple-1', 'good-empty'){|tag| tag.update{|f| f.delete('name')}}
|
|
141
|
+
assert_files_equal('good-simple-1', 'good-empty'){|tag| tag.update{|f| f.delete('Name')}}
|
|
142
|
+
assert_files_equal('good-empty', 'good-simple-1-ro-external'){|tag| tag.update{|f| ai = ApeItem.new('name', ['value']); ai.read_only = true; ai.ape_type = 'external'; f['name'] = ai}}
|
|
143
|
+
assert_files_equal('good-empty', 'good-binary-non-utf8-value'){|tag| tag.update{|f| ai = ApeItem.new('name', [binary("v\x81lue")]); ai.ape_type = 'binary'; f['name'] = ai}}
|
|
144
|
+
assert_files_equal('good-empty', 'good-many-items'){|tag| tag.update{|f| 63.times{|i| f["#{i}n"] = "a" * i}}}
|
|
145
|
+
assert_files_equal('good-empty', 'good-multiple-values'){|tag| tag.update{|f| f['name'] = ['va', 'ue']}}
|
|
146
|
+
assert_files_equal('good-multiple-values', 'good-simple-1-uc'){|tag| tag.update{|f| f['NAME'] = 'value'}}
|
|
147
|
+
assert_files_equal('good-empty', 'good-simple-1-utf8'){|tag| tag.update{|f| f['name'] = [utf8("v\xc3\x82\xc3\x95")]}}
|
|
148
|
+
|
|
149
|
+
assert_apetag_raised('good-empty', 'Updated tag has too many items (65)'){|tag| tag.update{|f| 65.times{|i| f["#{i}n"] = "a" * i}}}
|
|
150
|
+
assert_apetag_raised('good-empty', 'Updated tag too large (8193)'){|tag| tag.update{|f| f['xn'] = "a" * 8118}}
|
|
151
|
+
assert_apetag_raised('good-empty', 'Invalid APE key'){|tag| tag.update{|f| f['n'] = "a"}}
|
|
152
|
+
assert_apetag_raised('good-empty', 'Invalid APE key'){|tag| tag.update{|f| f['n' * 256] = "a"}}
|
|
153
|
+
assert_apetag_raised('good-empty', 'Invalid APE key'){|tag| tag.update{|f| f["n\0"] = "a"}}
|
|
154
|
+
assert_apetag_raised('good-empty', 'Invalid APE key'){|tag| tag.update{|f| f["n\x1f"] = "a"}}
|
|
155
|
+
assert_apetag_raised('good-empty', 'Invalid APE key'){|tag| tag.update{|f| f[binary("n\x80")] = "a"}}
|
|
156
|
+
assert_apetag_raised('good-empty', 'Invalid APE key'){|tag| tag.update{|f| f[binary("n\xff")] = "a"}}
|
|
157
|
+
assert_apetag_raised('good-empty', 'Invalid APE key'){|tag| tag.update{|f| f["tag"] = "a"}}
|
|
158
|
+
assert_apetag_raised('good-empty', 'Invalid key, value, APE type, or Read-Only Flag'){|tag| tag.update{|f| f["ab"] = utf8("v\xff")}}
|
|
159
|
+
assert_apetag_raised('good-empty', 'Invalid APE type'){|tag| tag.update{|f| ai = ApeItem.new('name', [binary("v\x81lue")]); ai.ape_type = 'foo'; f['name'] = ai}}
|
|
160
|
+
|
|
161
|
+
assert_files_equal('good-empty', 'good-simple-1', :yield=>:name){|temp_name| ApeTag.new(File.open(temp_name, 'rb+'), false).update{|f| f["name"] = 'value'}}
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def test_id3
|
|
165
|
+
assert_files_equal('missing-ok', 'good-empty'){|tag| tag.update{|f|}}
|
|
166
|
+
assert_files_equal('missing-ok', 'good-empty-id3', :yield=>:name){|temp_name| ApeTag.new(temp_name, true).update{|f|}}
|
|
167
|
+
assert_files_equal('missing-ok', 'good-empty-id3', :name=>'test.mp3'){|tag| tag.update{|f|}}
|
|
168
|
+
assert_files_equal('missing-ok', 'good-empty', :name=>'test.mp3', :yield=>:name){|temp_name| ApeTag.new(temp_name, false).update{|f|}}
|
|
169
|
+
|
|
170
|
+
assert_files_equal('good-empty-id3-only', 'good-empty-id3'){|tag| tag.update{|f|}}
|
|
171
|
+
assert_files_equal('good-empty-id3', 'good-simple-4'){|tag| tag.update{|f| f.merge!('track'=>'1', 'genre'=>'Game', 'year'=>'1999', 'title'=>'Test Title', 'artist'=>'Test Artist', 'album'=>'Test Album', 'comment'=>'Test Comment')}}
|
|
172
|
+
assert_files_equal('good-empty-id3', 'good-simple-4-uc'){|tag| tag.update{|f| f.merge!('Track'=>'1', 'Genre'=>'Game', 'Year'=>'1999', 'Title'=>'Test Title', 'Artist'=>'Test Artist', 'Album'=>'Test Album', 'Comment'=>'Test Comment')}}
|
|
173
|
+
assert_files_equal('good-empty-id3', 'good-simple-4-date'){|tag| tag.update{|f| f.merge!('track'=>'1', 'genre'=>'Game', 'date'=>'12/31/1999', 'title'=>'Test Title', 'artist'=>'Test Artist', 'album'=>'Test Album', 'comment'=>'Test Comment')}}
|
|
174
|
+
assert_files_equal('good-empty-id3', 'good-simple-4-long'){|tag| tag.update{|f| f.merge!('track'=>'1', 'genre'=>'Game', 'year'=>'1999'*2, 'title'=>'Test Title'*5, 'artist'=>'Test Artist'*5, 'album'=>'Test Album'*5, 'comment'=>'Test Comment'*5)}}
|
|
175
|
+
|
|
176
|
+
assert_equal(false, ApeTag.new(tagname('good-empty-id3'), false).exists?)
|
|
177
|
+
assert_equal(false, ApeTag.new(tagname('good-empty-id3'), false).has_id3?)
|
|
178
|
+
end
|
|
179
|
+
|
|
29
180
|
def get_ape_tag(f, check_id3)
|
|
30
181
|
f.is_a?(ApeTag) ? f : ApeTag.new(f, check_id3)
|
|
31
182
|
end
|
|
@@ -165,8 +316,6 @@ class ApeTagTest < Test::Unit::TestCase
|
|
|
165
316
|
|
|
166
317
|
# Test create fails with invalid key
|
|
167
318
|
assert_raises(ApeTagError){ApeItem.create('', ai)}
|
|
168
|
-
# Test create fails with invalid UTF-8 value
|
|
169
|
-
assert_raises(ApeTagError){ApeItem.create('xx',["\xfe"])}
|
|
170
319
|
# Test create doesn't fail with valid UTF-8 value
|
|
171
320
|
assert_nothing_raised{ApeItem.create('xx',[[12345, 1345].pack('UU')])}
|
|
172
321
|
|
metadata
CHANGED
|
@@ -1,83 +1,59 @@
|
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: apetag
|
|
3
|
-
version: !ruby/object:Gem::Version
|
|
4
|
-
|
|
5
|
-
prerelease: false
|
|
6
|
-
segments:
|
|
7
|
-
- 1
|
|
8
|
-
- 1
|
|
9
|
-
- 4
|
|
10
|
-
version: 1.1.4
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.1.5
|
|
11
5
|
platform: ruby
|
|
12
|
-
authors:
|
|
6
|
+
authors:
|
|
13
7
|
- Jeremy Evans
|
|
14
8
|
autorequire:
|
|
15
9
|
bindir: bin
|
|
16
10
|
cert_chain: []
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
dependencies:
|
|
21
|
-
- !ruby/object:Gem::Dependency
|
|
11
|
+
date: 2013-02-27 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
22
14
|
name: cicphash
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
- - ">="
|
|
28
|
-
- !ruby/object:Gem::Version
|
|
29
|
-
hash: 23
|
|
30
|
-
segments:
|
|
31
|
-
- 1
|
|
32
|
-
- 0
|
|
33
|
-
- 0
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - '>='
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
34
19
|
version: 1.0.0
|
|
35
20
|
type: :runtime
|
|
36
|
-
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - '>='
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 1.0.0
|
|
37
27
|
description:
|
|
38
28
|
email: code@jeremyevans.net
|
|
39
29
|
executables: []
|
|
40
|
-
|
|
41
30
|
extensions: []
|
|
42
|
-
|
|
43
31
|
extra_rdoc_files: []
|
|
44
|
-
|
|
45
|
-
files:
|
|
32
|
+
files:
|
|
46
33
|
- apetag.rb
|
|
47
34
|
- test/test_apetag.rb
|
|
48
|
-
has_rdoc: true
|
|
49
35
|
homepage: http://apetag.rubyforge.org
|
|
50
36
|
licenses: []
|
|
51
|
-
|
|
37
|
+
metadata: {}
|
|
52
38
|
post_install_message:
|
|
53
39
|
rdoc_options: []
|
|
54
|
-
|
|
55
|
-
require_paths:
|
|
40
|
+
require_paths:
|
|
56
41
|
- .
|
|
57
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
none: false
|
|
68
|
-
requirements:
|
|
69
|
-
- - ">="
|
|
70
|
-
- !ruby/object:Gem::Version
|
|
71
|
-
hash: 3
|
|
72
|
-
segments:
|
|
73
|
-
- 0
|
|
74
|
-
version: "0"
|
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - '>='
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
48
|
+
requirements:
|
|
49
|
+
- - '>='
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '0'
|
|
75
52
|
requirements: []
|
|
76
|
-
|
|
77
53
|
rubyforge_project: apetag
|
|
78
|
-
rubygems_version:
|
|
54
|
+
rubygems_version: 2.0.0
|
|
79
55
|
signing_key:
|
|
80
|
-
specification_version:
|
|
56
|
+
specification_version: 4
|
|
81
57
|
summary: APEv2 Tag Reader/Writer
|
|
82
|
-
test_files:
|
|
58
|
+
test_files:
|
|
83
59
|
- test/test_apetag.rb
|