apetag 1.0.1 → 1.1.0

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.
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: