apetag 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/apetag.rb +32 -38
  2. data/test/test_apetag.rb +6 -8
  3. metadata +13 -5
data/apetag.rb CHANGED
@@ -1,18 +1,18 @@
1
1
  #!/usr/bin/env ruby
2
2
  # This library implements a APEv2 parser/generator.
3
- # If called from the command line, it prints out the contents of the APEv2 tag for the given filename arguments.
3
+ # If called from the command line, it prints out the contents of the APEv2 tag
4
+ # for the given filename arguments.
4
5
  #
5
- # ruby-apetag is a pure Ruby library for manipulating APEv2 (and ID3v1.1) tags.
6
+ # ruby-apetag is a pure Ruby library for manipulating APEv2 tags.
6
7
  # It aims for standards compliance with the APE spec (1). APEv2 is the standard
7
8
  # tagging format for Musepack (.mpc) and Monkey's Audio files (.ape), and it can
8
- # also be used with mp3s as an alternative to the mess that is ID3v2.x
9
- # (technically, it can be used on any file type and is not limited to storing
10
- # just audio file metadata).
9
+ # also be used with mp3s as an alternative to ID3v2.x (technically, it can be
10
+ # used on any file type and is not limited to storing just audio file metadata).
11
11
  #
12
12
  # The module is in written in pure Ruby, so it should be useable on all
13
- # platforms that Ruby supports. It has been tested on OpenBSD.
13
+ # platforms that Ruby supports. It is developed and tested on OpenBSD.
14
14
  # The minimum Ruby version required should be 1.8, but it has only been tested
15
- # on 1.8.4. Modifying the code to work with previous version shouldn't be
15
+ # on 1.8.4+. Modifying the code to work with previous version shouldn't be
16
16
  # difficult, though there aren't any plans to do so.
17
17
  #
18
18
  # General Use:
@@ -20,8 +20,8 @@
20
20
  # require 'apetag'
21
21
  # a = ApeTag.new('file.mp3')
22
22
  # a.exists? # if it already has an APEv2 tag
23
- # a.raw # the raw APEv2+ID3v1.1 tag already on the file
24
- # a.fields # a hash of fields, keys are strings, values are list of strings
23
+ # a.raw # the raw APEv2+ID3v1.1 tag string in the file
24
+ # a.fields # a CICPHash of fields, keys are strings, values are list of strings
25
25
  # a.pretty_print # string suitable for pretty printing
26
26
  # a.update{|fields| fields['Artist']='Test Artist'; fields.delete('Year')}
27
27
  # # Update the tag with the added/changed/deleted fields
@@ -36,8 +36,8 @@
36
36
  # patch, please use Rubyforge (http://rubyforge.org/projects/apetag/).
37
37
  #
38
38
  # The most current source code can be accessed via anonymous SVN at
39
- # svn://suven.no-ip.org/ruby-apetag/. Note that the library isn't modified on a
40
- # regular basis, so it is unlikely to be different from the latest release.
39
+ # svn://code.jeremyevans.net/ruby-apetag/. Note that the library isn't modified
40
+ # on a regular basis, so it is unlikely to be different from the latest release.
41
41
  #
42
42
  # (1) http://wiki.hydrogenaudio.org/index.php?title=APEv2_specification
43
43
  #
@@ -62,6 +62,7 @@
62
62
  # SOFTWARE.
63
63
 
64
64
  require 'set'
65
+ require 'cicphash'
65
66
 
66
67
  # Error raised by the library
67
68
  class ApeTagError < StandardError
@@ -71,11 +72,10 @@ end
71
72
  # Because all items can contain a list of values, this is a subclass of Array.
72
73
  class ApeItem < Array
73
74
  MIN_SIZE = 11 # 4+4+2+1 (length, flags, minimum key length, key-value separator)
74
- BAD_KEYS = Set.new(%w'id3 tag oggs mp+')
75
- BAD_KEY_RE = Regexp.new("[\0-\x1f\x80-\xff]")
75
+ BAD_KEY_RE = /[\0-\x1f\x80-\xff]|\A(?:id3|tag|oggs|mp\+)\z/i
76
76
  ITEM_TYPES = %w'utf8 binary external reserved'
77
77
 
78
- attr_reader :read_only, :ape_type, :key, :key_downcased
78
+ attr_reader :read_only, :ape_type, :key
79
79
 
80
80
  # Creates an APE tag with the appropriate key and value.
81
81
  # If value is a valid ApeItem, just updates the key.
@@ -129,7 +129,6 @@ class ApeItem < Array
129
129
  def key=(key)
130
130
  raise ApeTagError, "Invalid APE key" unless valid_key?(key)
131
131
  @key = key
132
- @key_downcased = key.downcase
133
132
  end
134
133
 
135
134
  # The on disk representation of the entire ApeItem.
@@ -162,9 +161,9 @@ class ApeItem < Array
162
161
  ITEM_TYPES.include?(type)
163
162
  end
164
163
 
165
- # Check if the given key is a valid APE key (string, 2 <= length <= 255, not in ApeItem::BAD_KEYS, not containing invalid characters).
164
+ # Check if the given key is a valid APE key (string, 2 <= length <= 255, not containing invalid characters or keys).
166
165
  def valid_key?(key)
167
- key.is_a?(String) && key.length >= 2 && key.length <= 255 && !BAD_KEYS.include?(key.downcase) && key !~ BAD_KEY_RE
166
+ key.is_a?(String) && key.length >= 2 && key.length <= 255 && key !~ BAD_KEY_RE
168
167
  end
169
168
 
170
169
  # Check if the given read only flag is valid (boolean).
@@ -218,7 +217,7 @@ class ApeTag
218
217
  Polsk Punk, Beat, Christian Gangsta Rap, Heavy Metal, Black Metal,
219
218
  Crossover, Contemporary Christian, Christian Rock, Merengue, Salsa,
220
219
  Trash Meta, Anime, Jpop, Synthpop'.split(',').collect{|g| g.strip}
221
- ID3_GENRES_HASH = Hash.new(255.chr)
220
+ ID3_GENRES_HASH = CICPHash.new(255.chr)
222
221
  ID3_GENRES.each_with_index{|g,i| ID3_GENRES_HASH[g] = i.chr }
223
222
  FILE_OBJ_METHODS = %w'close seek read pos write truncate'
224
223
  YEAR_RE = Regexp.new('\d{4}')
@@ -268,7 +267,7 @@ class ApeTag
268
267
  true
269
268
  end
270
269
 
271
- # A hash of ApeItems found in the file, or an empty hash if the file
270
+ # A CICPHash of ApeItems found in the file, or an empty CICPHash if the file
272
271
  # doesn't have an APE tag. Raises ApeTagError for corrupt tags.
273
272
  def fields
274
273
  @fields || access_file('rb'){get_fields}
@@ -277,7 +276,7 @@ class ApeTag
277
276
  # Pretty print tags, with one line per field, showing key and value.
278
277
  def pretty_print
279
278
  begin
280
- fields.values.sort_by{|value| value.key_downcased}.collect{|value| "#{value.key}: #{value.join(', ')}"}.join("\n")
279
+ fields.values.sort_by{|value| value.key}.collect{|value| "#{value.key}: #{value.join(', ')}"}.join("\n")
281
280
  rescue ApeTagError
282
281
  "CORRUPT TAG!"
283
282
  rescue Errno::ENOENT, Errno::EINVAL
@@ -292,7 +291,7 @@ class ApeTag
292
291
  "#{tag_header}#{tag_data}#{tag_footer}#{id3}"
293
292
  end
294
293
 
295
- # Yields a hash of ApeItems found in the file, or an empty hash if the file
294
+ # Yields a CICPHash of ApeItems found in the file, or an empty CICPHash if the file
296
295
  # doesn't have an APE tag. This hash should be modified (not reassigned) inside
297
296
  # the block. An APEv2+ID3v1.1 tag with the new fields will overwrite the previous
298
297
  # tag. If the file doesn't have an APEv2 tag, one will be created and appended to it.
@@ -346,16 +345,14 @@ class ApeTag
346
345
  # if the file has no APE tag.
347
346
  def get_fields
348
347
  return @fields if @fields
349
- return @fields = Hash.new unless has_tag
350
- ape_items = Hash.new
351
- ape_item_keys = Set.new
348
+ return @fields = CICPHash.new unless has_tag
349
+ ape_items = CICPHash.new
352
350
  offset = 0
353
351
  last_possible_item_start = tag_data.length - ApeItem::MIN_SIZE
354
352
  tag_item_count.times do
355
353
  raise ApeTagError, "End of tag reached but more items specified" if offset > last_possible_item_start
356
354
  item, offset = ApeItem.parse(tag_data, offset)
357
- raise ApeTagError, "Multiple items with same key (#{item.key.inspect})" if ape_item_keys.include?(item.key_downcased)
358
- ape_item_keys.add(item.key_downcased)
355
+ raise ApeTagError, "Multiple items with same key (#{item.key.inspect})" if ape_items.include?(item.key)
359
356
  ape_items[item.key] = item
360
357
  end
361
358
  raise ApeTagError, "Data remaining after specified number of items parsed" if offset != tag_data.length
@@ -415,12 +412,9 @@ class ApeTag
415
412
  # Turn fields hash from a hash of arbitrary objects to a hash of ApeItems
416
413
  # Check that multiple identical keys are not present.
417
414
  def normalize_fields
418
- new_fields = Hash.new
419
- new_fields_keys = Set.new
415
+ new_fields = CICPHash.new
420
416
  fields.each do |key, value|
421
- new_fields[key] = value = ApeItem.create(key, value)
422
- raise ApeTagError, "Multiple items with same key (#{value.key.inspect})" if new_fields_keys.include?(value.key_downcased)
423
- new_fields_keys.add(value.key_downcased)
417
+ new_fields[key] = ApeItem.create(key, value)
424
418
  end
425
419
  @fields = new_fields
426
420
  end
@@ -445,21 +439,21 @@ class ApeTag
445
439
  # check_id3 is not set, an ID3 won't be added.
446
440
  def update_id3
447
441
  return if id3.length == 0 && (has_tag || check_id3 == false)
448
- id3_fields = Hash.new('')
442
+ id3_fields = CICPHash.new('')
449
443
  id3_fields['genre'] = 255.chr
450
444
  fields.values.each do |value|
451
- case value.key_downcased
452
- when /\Atrack/
445
+ case value.key
446
+ when /\Atrack/i
453
447
  id3_fields['track'] = value.string_value.to_i
454
448
  id3_fields['track'] = 0 if id3_fields['track'] > 255
455
449
  id3_fields['track'] = id3_fields['track'].chr
456
- when /\Agenre/
450
+ when /\Agenre/i
457
451
  id3_fields['genre'] = ID3_GENRES_HASH[value.first]
458
- when /\Adate\z/
452
+ when /\Adate\z/i
459
453
  match = YEAR_RE.match(value.string_value)
460
454
  id3_fields['year'] = match[0] if match
461
- when /\A(title|artist|album|year|comment)\z/
462
- id3_fields[value.key_downcased] = value.join(', ')
455
+ when /\A(title|artist|album|year|comment)\z/i
456
+ id3_fields[value.key] = value.join(', ')
463
457
  end
464
458
  end
465
459
  @id3 = ["TAG", id3_fields['title'], id3_fields['artist'], id3_fields['album'],
@@ -102,7 +102,6 @@ class ApeTagTest < Test::Unit::TestCase
102
102
  assert_equal false, ai.read_only
103
103
  assert_equal 'utf8', ai.ape_type
104
104
  assert_equal 'BlaH', ai.key
105
- assert_equal 'blah', ai.key_downcased
106
105
  assert_equal 'BlAh', ai.string_value
107
106
  assert_equal "\04\0\0\0\0\0\0\0BlaH\0BlAh", ai.raw
108
107
  assert_equal true, ai.valid?
@@ -120,8 +119,8 @@ class ApeTagTest < Test::Unit::TestCase
120
119
 
121
120
  # Test valid key settings
122
121
  ((("\0".."\x1f").to_a+("\x80".."\xff").to_a).collect{|x|"#{x} "} +
123
- [nil, 1, '', 'x', 'x'*256]+ApeItem::BAD_KEYS.to_a).each{|x|assert_raises(ApeTagError){ai.key=x}}
124
- ("\x20".."\x7f").to_a.collect{|x|"#{x} "}+ApeItem::BAD_KEYS.to_a.collect{|x|"#{x} "} +
122
+ [nil, 1, '', 'x', 'x'*256, 'id3', 'tag', 'oggs', 'mp+']).each{|x|assert_raises(ApeTagError){ai.key=x}}
123
+ ("\x20".."\x7f").to_a.collect{|x|"#{x} "}+['id3', 'tag', 'oggs', 'mp+'].collect{|x|"#{x} "} +
125
124
  ['xx', 'x'*255].each{|x| assert_nothing_raised{ai.key=x}}
126
125
 
127
126
  # Test valid raw and string value for different settings
@@ -330,11 +329,10 @@ class ApeTagTest < Test::Unit::TestCase
330
329
  data[192] += 2
331
330
  assert_raises(ApeTagError){ApeTag.new(StringIO.new(data)).fields}
332
331
 
333
- # Test updating with duplicate keys added
334
- assert_raises(ApeTagError){ApeTag.new(StringIO.new(EXAMPLE_APE_TAG.dup)).update{|x| x['album']='blah'}}
335
- assert_raises(ApeTagError){ApeTag.new(StringIO.new(EXAMPLE_APE_TAG.dup)).update{|x| x['ALBUM']='blah'}}
336
- # Test updating without duplicate keys added
337
- assert_nothing_raised{ApeTag.new(StringIO.new(EXAMPLE_APE_TAG.dup)).update{|x| x['Album']='blah'}}
332
+ # Test updating works in case insensitive manner
333
+ assert_equal ['blah'], ApeTag.new(StringIO.new(EXAMPLE_APE_TAG.dup)).update{|x| x['album']='blah'}['ALBUM']
334
+ assert_equal ['blah'], ApeTag.new(StringIO.new(EXAMPLE_APE_TAG.dup)).update{|x| x['ALBUM']='blah'}['album']
335
+ assert_equal ['blah'], ApeTag.new(StringIO.new(EXAMPLE_APE_TAG.dup)).update{|x| x['ALbUM']='blah'}['albuM']
338
336
 
339
337
  # Test updating an existing ApeItem via various array methods
340
338
  assert_nothing_raised{ApeTag.new(StringIO.new(EXAMPLE_APE_TAG.dup)).update{|x| x['Album'] += ['blah']}}
metadata CHANGED
@@ -3,12 +3,12 @@ rubygems_version: 0.9.0
3
3
  specification_version: 1
4
4
  name: apetag
5
5
  version: !ruby/object:Gem::Version
6
- version: 1.0.1
7
- date: 2007-05-06 00:00:00 -07:00
6
+ version: 1.1.0
7
+ date: 2007-08-13 00:00:00 -07:00
8
8
  summary: APEv2 Tag Parser/Generator
9
9
  require_paths:
10
10
  - .
11
- email: jeremyevans0@gmail.com
11
+ email: code@jeremyevans.net
12
12
  homepage:
13
13
  rubyforge_project:
14
14
  description:
@@ -42,5 +42,13 @@ extensions: []
42
42
 
43
43
  requirements: []
44
44
 
45
- dependencies: []
46
-
45
+ dependencies:
46
+ - !ruby/object:Gem::Dependency
47
+ name: cicphash
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Version::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 1.0.0
54
+ version: