easytag 0.4.3 → 1.0.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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -0
  3. data/Gemfile +7 -2
  4. data/README.md +33 -27
  5. data/Rakefile +7 -7
  6. data/easytag.gemspec +2 -4
  7. data/lib/easytag.rb +12 -2
  8. data/lib/easytag/attributes/base.rb +32 -128
  9. data/lib/easytag/attributes/flac.rb +25 -0
  10. data/lib/easytag/attributes/mp3.rb +91 -554
  11. data/lib/easytag/attributes/mp4.rb +22 -492
  12. data/lib/easytag/attributes/ogg.rb +27 -0
  13. data/lib/easytag/attributes/vorbis.rb +18 -0
  14. data/lib/easytag/taggers/base.rb +19 -0
  15. data/lib/easytag/taggers/factory.rb +59 -0
  16. data/lib/easytag/taggers/flac.rb +23 -0
  17. data/lib/easytag/taggers/mp3.rb +77 -0
  18. data/lib/easytag/taggers/mp4.rb +73 -0
  19. data/lib/easytag/taggers/ogg.rb +25 -0
  20. data/lib/easytag/taggers/vorbis.rb +66 -0
  21. data/lib/easytag/util.rb +9 -31
  22. data/lib/easytag/version.rb +3 -4
  23. data/scripts/build_attributes_table.rb +34 -0
  24. data/{test → spec/data}/consistency.01.m4a +0 -0
  25. data/{test → spec/data}/consistency.01.mp3 +0 -0
  26. data/spec/data/consistency.flac +0 -0
  27. data/{test/consistency.02.m4a → spec/data/consistency.m4a} +0 -0
  28. data/{test/consistency.02.mp3 → spec/data/consistency.mp3} +0 -0
  29. data/spec/data/consistency.multiple_images.flac +0 -0
  30. data/{test/no_tags.m4a → spec/data/consistency.multiple_images.m4a} +0 -0
  31. data/spec/data/consistency.multiple_images.mp3 +0 -0
  32. data/spec/data/consistency.multiple_images.ogg +0 -0
  33. data/spec/data/consistency.ogg +0 -0
  34. data/{test → spec/data}/musicbrainz.m4a +0 -0
  35. data/spec/data/no_tags.flac +0 -0
  36. data/spec/data/no_tags.m4a +0 -0
  37. data/{test → spec/data}/no_tags.mp3 +0 -0
  38. data/spec/data/no_tags.ogg +0 -0
  39. data/{test → spec/data}/only_id3v1.mp3 +0 -0
  40. data/{test → spec/data}/only_id3v2.mp3 +0 -0
  41. data/spec/flac_tagger_spec.rb +24 -0
  42. data/spec/mp3_tagger_spec.rb +64 -0
  43. data/spec/mp4_tagger_spec.rb +22 -0
  44. data/spec/ogg_tagger_spec.rb +21 -0
  45. data/spec/shared_examples.rb +115 -0
  46. data/spec/spec_helper.rb +34 -0
  47. data/spec/tagger_factory_spec.rb +27 -0
  48. data/spec/util_spec.rb +47 -0
  49. metadata +47 -48
  50. data/lib/easytag/attributes.rb +0 -0
  51. data/lib/easytag/base.rb +0 -28
  52. data/lib/easytag/file.rb +0 -44
  53. data/lib/easytag/interfaces.rb +0 -18
  54. data/lib/easytag/interfaces/base.rb +0 -24
  55. data/lib/easytag/interfaces/mp3.rb +0 -48
  56. data/lib/easytag/interfaces/mp4.rb +0 -14
  57. data/test/test_consistency.rb +0 -233
  58. data/test/test_mp3.rb +0 -98
  59. data/test/test_mp4.rb +0 -49
  60. data/test/test_util.rb +0 -72
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 53a9645ec483ba68cc28dffd34002d149b43d24d
4
- data.tar.gz: 0cf232f7f04a0f1e3bf6606c8abf363cc2d4ef9f
3
+ metadata.gz: 16662d29b534ff4cc4ec6e52e35b1f725387c01d
4
+ data.tar.gz: f7b35f5fa59150c4937a3781fc206420741e6c8f
5
5
  SHA512:
6
- metadata.gz: 026bbbf0959293a24f17103bc0fa4d53e83c3edf80bf2c1e4553ff8cfefff3c930af5dca8ba205853d20a747be0c20c114f076121c9f8145e45c6497afb45e45
7
- data.tar.gz: 78507cf793dc807eeb7ee8d32ebd57cbf0937cf453a7ced4578d4b495a2016a80b4d8165ece596e534dc511bf89a23651e27684cbe8ea62065a32ff7a94053fc
6
+ metadata.gz: 23e58b9d0726dff60df794e0595fc6fbeac6a8b8a07c3bc82c47a13094cbdc44899886393ba14f491f3c732758866a4d163154c7aa9a6ad313beac9342668ddd
7
+ data.tar.gz: b2b80e1092243a8fa6f0ff6969ea29ef38b3dfa744fff6502376cb4dac412c9b886225351e8782861a0b357cddf69db2146ecda7c3825faf3ed29c2c2267c969
@@ -1,3 +1,14 @@
1
+ ##### v1.0.0 (2013-05-26) #####
2
+ * added: detecting file type from file signature
3
+ * first major release
4
+
5
+ ##### v0.6.0 (2013-05-25) #####
6
+ * complete overhaul, this release is not compatible with any previous release
7
+ * FLAC, Ogg support added
8
+
9
+ ##### v0.4.3 (2013-08-20) #####
10
+ * fixed: uninitialized constant error with regard to file unsupported exception
11
+
1
12
  ##### v0.4.2 (2013-06-23) #####
2
13
  * added: interal support for `TagLib::ID3v2::UnknownFrame`
3
14
  * info: because a new version of taglib-ruby hasn't been officially released
data/Gemfile CHANGED
@@ -1,7 +1,12 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- # for TDAT fix, temporary until maintainers push a new version
4
- gem 'taglib-ruby', github: 'robinst/taglib-ruby', ref: 'bb6453e2e3404cbc8d1e3056880922797167a218'
3
+ source 'http://rubygems.org'
4
+
5
+ group :test do
6
+ gem 'rake'
7
+ gem 'rspec'
8
+ gem 'coveralls'
9
+ end
5
10
 
6
11
  # read dependencies from gemspec file
7
12
  gemspec
data/README.md CHANGED
@@ -1,52 +1,58 @@
1
1
  EasyTag is an abstract interface to the [TagLib](http://taglib.github.io/) audio tagging library. It is designed to provide a simple and consistent API regardless of file format being read.
2
2
 
3
+ Currently support file formats are: MP3, MP4, FLAC, and Ogg.
3
4
  A quick reference to which attributes are currently supported can be found [here](https://github.com/cjlucas/ruby-easytag/wiki/Currently-Supported-Attributes).
4
5
 
5
6
  [![Build Status (master)](https://travis-ci.org/cjlucas/ruby-easytag.png?branch=master "Branch: master")](https://travis-ci.org/cjlucas/ruby-easytag)
6
7
  [![Build Status (develop)](https://travis-ci.org/cjlucas/ruby-easytag.png?branch=develop "Branch: develop")](https://travis-ci.org/cjlucas/ruby-easytag)
7
8
 
9
+ NOTE: v0.6.0+ is not compatible with any version prior to v0.6.0.
10
+
8
11
  ---
9
12
  ## Synopsis ##
13
+
10
14
  ```ruby
11
15
  require 'easytag'
12
16
 
13
- mp3 = EasyTag::File.new("01 Shout Out Loud.mp3")
14
-
15
- # easy access to attributes
16
- mp3.title
17
- # => "Shout Out Loud"
18
- mp3.artist
19
- # => "Amos Lee"
20
- mp3.year
21
- # => 2006
22
- mp3.date
23
- # => #<DateTime: 2006-10-03T00:00:00+00:00 ((2454012j,0s,0n),+0s,2299161j)>
24
- mp3.album_art
25
- # => [#<EasyTag::Image:0x007f7fa542f528>]
26
-
27
- # get access to the taglib-powered backend
28
- mp3.info
29
- # => #<TagLib::MPEG::File:0x007f7fa539e050 @__swigtype__="_p_TagLib__MPEG__File">
30
- mp3.close
31
-
32
- # A block interface is also available
33
- EasyTag::File.open('01 Shout Out Loud.m4a') do |m4a|
34
- puts m4a.title
35
- puts m4a.comments
36
- puts m4a.genre
17
+ tagger = EasyTag.open('audio.flac') # calls EasyTag::TaggerFactory::open
18
+ tagger # => <EasyTag::FLACTagger:0x007fc28aba4c90>
19
+ tagger.title # => "She Lives in My Lap"
20
+ tagger.artist # => "OutKast feat. Rosario Dawson"
21
+ tagger.album_artist # => "OutKast"
22
+ tagger.musicbrainz_artist_id # => ["73fdb566-a9b1-494c-9f32-51768ec9fd27", "9facf8dc-df23-4561-85c5-ece75d692f21"]
23
+
24
+ # The backing taglib object is also available
25
+ f.taglib # => <TagLib::FLAC::File:0x007fc28aba4c68 @__swigtype__="_p_TagLib__FLAC__File">
26
+
27
+ f.close
28
+
29
+ # A block can also be specified
30
+ EasyTag.open('audio.mp3') do |tagger|
31
+ tagger.title # => "She Lives in My Lap"
32
+ tagger.artist # => "OutKast feat. Rosario Dawson"
33
+ tagger.album_artist # => "OutKast"
34
+
35
+ # tagger.close will be called after the block is executed
36
+ end
37
+
38
+ # Taggers also return consistent defaults for missing attributes
39
+ EasyTag..open('no_tags.ogg') do |tagger|
40
+ tagger.year # => 0 (for any int attribute)
41
+ tagger.album_art # => [] (for any list attribute)
42
+ tagger.title # => nil (for any non-int, non-list attribute)
37
43
  end
38
44
  ```
45
+
46
+
39
47
  ## Requirements ##
40
48
  - Ruby versions
41
- - 1.9
42
49
  - 2.0
50
+ - 2.1
43
51
  - Dependencies
44
52
  - [TagLib](http://taglib.github.io/) (1.8+)
45
53
  - [taglib-ruby](https://github.com/robinst/taglib-ruby) (0.6.0+)
46
- - [ruby-mp3info](https://github.com/moumar/ruby-mp3info)
47
54
  - [ruby-imagespec](https://github.com/andersonbrandon/ruby-imagespec)
48
55
 
49
56
  ## TODO ##
50
57
  - API Documentation
51
- - FLAC and OGG support
52
58
  - Write support
data/Rakefile CHANGED
@@ -1,16 +1,16 @@
1
1
  $LOAD_PATH.unshift(File.expand_path('../lib', __FILE__))
2
2
 
3
- require 'rake/testtask'
3
+ require 'rspec/core/rake_task'
4
4
 
5
5
  require 'easytag/version'
6
6
 
7
- task :default => [:test]
7
+ task :default => [:spec]
8
+ task :test => :spec
8
9
 
9
- task :test do
10
- Rake::TestTask.new do |t|
11
- t.libs << 'test'
12
- t.test_files = FileList['test/test_*.rb']
13
- t.verbose = false
10
+ task :spec do
11
+ RSpec::Core::RakeTask.new do |task|
12
+ task.verbose = false
13
+ task.rspec_opts = '--color'
14
14
  end
15
15
  end
16
16
 
@@ -19,9 +19,7 @@ Gem::Specification.new do |s|
19
19
  s.test_files = s.files.select { |f| f.match(/^test\/.*\.rb$/) }
20
20
  s.platform = Gem::Platform::RUBY
21
21
 
22
- #s.add_runtime_dependency('taglib-ruby', '>= 0.6.0')
22
+ s.add_runtime_dependency('taglib-ruby', '>= 0.6.0')
23
23
  s.add_runtime_dependency('ruby-imagespec', '>= 0.3.1')
24
- s.add_runtime_dependency('ruby-mp3info', '>= 0.8')
25
-
26
- s.add_development_dependency('rake')
27
24
  end
25
+
@@ -1,5 +1,15 @@
1
1
  require 'bundler/setup'
2
2
 
3
3
  require 'easytag/util'
4
- require 'easytag/file'
5
- require 'easytag/interfaces'
4
+ require 'easytag/taggers/mp3'
5
+ require 'easytag/taggers/mp4'
6
+ require 'easytag/taggers/vorbis'
7
+ require 'easytag/taggers/flac'
8
+ require 'easytag/taggers/ogg'
9
+ require 'easytag/taggers/factory'
10
+
11
+ module EasyTag
12
+ def self.open(file, &block)
13
+ TaggerFactory.open(file, &block)
14
+ end
15
+ end
@@ -1,143 +1,47 @@
1
- module EasyTag::Attributes
2
- # for type casting
3
- module Type
4
- STRING = 0
5
- INT = 1
6
- FLOAT = 2
7
- INT_LIST = 3
8
- STRING_LIST = 4
9
- BOOLEAN = 5
10
- DATETIME = 6
11
- LIST = 7
12
- end
13
- class BaseAttribute
14
- Utilities = EasyTag::Utilities
15
-
16
- attr_reader :name, :aliases, :ivar
17
-
18
- def initialize(args)
19
- @name = args[:name]
20
- @type = args[:type]
21
- @default = args[:default] || self.class.default_for_type(@type)
22
- @options = args[:options] || {}
23
- @handler_opts = args[:handler_opts] || {}
24
- @aliases = args[:aliases] || []
25
- @ivar = BaseAttribute.name_to_ivar(@name)
26
-
27
- if args[:handler].is_a?(Symbol)
28
- @handler = method(args[:handler])
29
- elsif args[:handler].is_a?(Proc)
30
- @handler = args[:handler]
31
- end
32
-
33
- # fill default options
34
-
35
- # Remove nil objects from array (post process)
36
- @options[:compact] ||= false
37
- # Delete empty objects in array (post process)
38
- @options[:delete_empty] ||= false
39
- # normalizes key (if hash) (handler or post process)
40
- @options[:normalize] ||= false
41
- # cast key (if hash) to symbol (handler or post process)
42
- @options[:to_sym] ||= false
43
-
44
- end
45
-
46
- def self.can_clone?(obj)
47
- obj.is_a?(String) || obj.is_a?(Array) || obj.is_a?(Hash)
48
- end
49
-
50
- def self.deep_copy(obj)
51
- Marshal.load(Marshal.dump(obj))
52
- end
53
-
54
- def self.default_for_type(type)
55
- case type
56
- when Type::STRING
57
- ''
58
- when Type::DATETIME
59
- nil
60
- when Type::INT
61
- 0
62
- when Type::INT_LIST
63
- [0, 0] # TODO: don't assume an INT_LIST is always an int pair
64
- when Type::BOOLEAN
65
- false
66
- when Type::LIST
67
- []
1
+ module EasyTag
2
+ module BaseAttributeAccessors
3
+
4
+ def audio_prop_reader(attr_name, prop_name = nil, **opts)
5
+ prop_name = attr_name if prop_name.nil?
6
+ define_method(attr_name) do
7
+ v = self.class.read_audio_property(taglib, prop_name)
8
+ self.class.post_process(v, opts)
68
9
  end
69
10
  end
70
11
 
71
- def default
72
- BaseAttribute.can_clone?(@default) ?
73
- BaseAttribute.deep_copy(@default) : @default
74
- end
75
-
76
- def call(iface)
77
- data = @handler.call(iface)
78
- data = type_cast(data)
79
- post_process(data)
80
- end
81
-
82
- def type_cast(data)
83
- case @type
84
- when Type::INT
85
- data = data.to_i
86
- when Type::DATETIME
87
- data = Utilities.get_datetime(data.to_s)
88
- when Type::LIST
89
- data = Array(data)
12
+ def cast(data, key, **opts)
13
+ case opts[key]
14
+ when :int
15
+ data.to_i
16
+ when :int_pair
17
+ Utilities.get_int_pair(data)
18
+ when :datetime
19
+ Utilities.get_datetime(data.to_s)
20
+ when :list
21
+ Array(data)
22
+ when :bool
23
+ [1, '1', true].include?(data)
24
+ else
25
+ data
90
26
  end
91
-
92
- data
93
27
  end
94
28
 
95
- def post_process(data)
96
- if @options[:is_flag]
97
- data = data.to_i == 1 ? true : false
98
- end
99
-
100
- # fall back to default if data is nil
101
- data ||= default
102
-
103
- # REVIEW: the compact option may not be needed anymore
104
- # since we've done away with casting empty strings to nil
105
- data.compact! if @options[:compact] && data.respond_to?(:compact!)
106
-
107
- if @options[:delete_empty]
108
- data.select! { |item| !item.empty? if item.respond_to?(:empty?) }
109
- end
110
-
111
- data = Utilities.normalize_object(data) if @options[:normalize]
112
-
113
- # TODO: roll this out to a method that supports more than just array
114
- if @options[:to_sym] && data.is_a?(Array)
115
- data.map! { |item| item.to_sym if item.respond_to?(:to_sym) }
29
+ def extract(data, **opts)
30
+ if opts.has_key?(:extract_list_pos)
31
+ data = data[opts[:extract_list_pos]]
116
32
  end
117
-
118
33
  data
119
34
  end
120
-
121
- def self.name_to_ivar(name)
122
- name.to_s.gsub(/\?/, '').insert(0, '@').to_sym
123
- end
124
-
125
- # read handlers
126
35
 
127
- def read_default(iface)
128
- default
36
+ def post_process(data, opts)
37
+ data = cast(data, :cast, **opts)
38
+ data = extract(data, **opts)
39
+ cast(data, :returns, **opts)
129
40
  end
130
41
 
131
- def read_audio_property(iface)
132
- key = @handler_opts[:key]
133
- warn "@handler_opts[:key] doesn't exist" if key.nil?
134
- iface.info.audio_properties.send(key)
135
- end
136
-
137
- def user_info_lookup(iface)
138
- key = @handler_opts[:key]
139
- warn "@handler_opts[:key] doesn't exist" if key.nil?
140
- iface.user_info_normalized[key]
42
+ def read_audio_property(taglib, key)
43
+ taglib.audio_properties.send(key)
141
44
  end
142
45
  end
143
46
  end
47
+
@@ -0,0 +1,25 @@
1
+ require_relative 'vorbis'
2
+
3
+ module EasyTag
4
+ module FLACAttributeAccessors
5
+ def field_reader(attr_name, fields = nil, **opts)
6
+ fields = attr_name.to_s.upcase if fields.nil?
7
+ define_method(attr_name) do
8
+ v = self.class.read_fields(taglib.xiph_comment, fields, **opts)
9
+ self.class.post_process(v, **opts)
10
+ end
11
+ end
12
+
13
+ def album_art_reader(attr_name, **opts)
14
+ define_method(attr_name) do
15
+ taglib.picture_list.collect do |pic|
16
+ EasyTag::Image.new(pic.data).tap do |img|
17
+ img.mime_type = pic.mime_type
18
+ img.type = pic.type
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ end
25
+ end
@@ -4,605 +4,142 @@ require 'easytag/image'
4
4
  require 'easytag/util'
5
5
  require 'easytag/attributes/base'
6
6
 
7
- module EasyTag::Attributes
8
- class MP3Attribute < BaseAttribute
9
- attr_reader :name, :ivar
10
-
11
- def initialize(args)
12
- super(args)
13
- @id3v2_frames = args[:id3v2_frames] || []
14
- @id3v1_tag = args[:id3v1_tag] || nil
15
-
16
- # fill default options
17
-
18
- # ID3 stores boolean values as numeric strings
19
- # set to true to enable type casting (post process)
20
- @options[:is_flag] ||= false
21
- # return entire field list instead of first item in field list
22
- @options[:field_list] ||= false
7
+ module EasyTag
8
+ module MP3AttributeAccessors
9
+ include BaseAttributeAccessors
10
+
11
+ def single_tag_reader(attr_name, id3v2_frames = nil, id3v1_tag = nil, **opts)
12
+ id3v2_frames = Array(id3v2_frames)
13
+ define_method(attr_name) do
14
+ v = self.class.read_first_tag(taglib, id3v2_frames, id3v1_tag, opts)
15
+ self.class.post_process(v, opts)
16
+ end
23
17
  end
24
18
 
25
-
26
- def frames_for_id(id, iface)
27
- iface.info.id3v2_tag.frame_list(id)
19
+ def all_tags_reader(attr_name, id3v2_frames = nil, id3v1_tag = nil, **opts)
20
+ id3v2_frames = Array(id3v2_frames)
21
+ define_method(attr_name) do
22
+ v = self.class.read_all_tags(taglib, id3v2_frames, id3v1_tag, opts)
23
+ self.class.post_process(v, opts)
24
+ end
28
25
  end
29
26
 
30
- def first_frame_for_id(id, iface)
31
- frames_for_id(id, iface).first
27
+ def user_info_reader(attr_name, key = nil, **opts)
28
+ key = attr_name if key.nil?
29
+ define_method(attr_name) do
30
+ @user_info = self.class.read_user_info(taglib, **opts) if @user_info.nil?
31
+ self.class.post_process(@user_info[key], opts)
32
+ end
32
33
  end
33
34
 
34
- def data_from_frame(frame)
35
- data = nil
36
- if frame.is_a?(TagLib::ID3v2::TextIdentificationFrame)
37
- field_list = frame.field_list
38
- data = @options[:field_list] ? field_list : field_list.first
39
- elsif frame.is_a?(TagLib::ID3v2::UnsynchronizedLyricsFrame)
40
- data = frame.text
41
- elsif frame.is_a?(TagLib::ID3v2::CommentsFrame)
42
- data = frame.text
43
- elsif frame.is_a?(TagLib::ID3v2::AttachedPictureFrame)
44
- data = EasyTag::Image.new(frame.picture)
45
- data.desc = frame.description
46
- data.type = frame.type
47
- data.mime_type = frame.mime_type
48
- elsif frame.is_a?(TagLib::ID3v2::UnknownFrame)
49
- nil
50
- else
51
- warn 'no defined frames match the given frame'
35
+ def ufid_reader(attr_name, owner, **opts)
36
+ define_method(attr_name) do
37
+ v = self.class.read_ufid(taglib, owner, opts)
38
+ self.class.post_process(v, opts)
52
39
  end
40
+ end
53
41
 
54
- data
42
+ def date_reader(attr_name, **opts)
43
+ opts[:returns] = :datetime unless opts.has_key?(:returns)
44
+ define_method(attr_name) do
45
+ v = self.class.read_date(taglib, opts)
46
+ self.class.post_process(v, opts)
47
+ end
55
48
  end
56
49
 
57
- #
58
- # read handlers
59
- #
60
-
61
- # read_all_id3
62
- #
63
- # gets data from each frame id given
64
- # only falls back to the id3v1 tag if none found
65
- def read_all_id3(iface)
50
+ # gets data from each frame id given only falls back
51
+ # to the id3v1 tag if no id3v2 frames were found
52
+ def read_all_tags(taglib, id3v2_frames, id3v1_tag = nil, **opts)
66
53
  frames = []
67
- @id3v2_frames.each do |f|
68
- frames += frames_for_id(f, iface)
69
- end
54
+ id3v2_frames.each { |frame_id| frames += id3v2_frames(taglib, frame_id) }
70
55
 
71
56
  data = []
72
57
  # only check id3v1 if no id3v2 frames found
73
58
  if frames.empty?
74
- data << iface.info.id3v1_tag.send(@id3v1_tag) unless @id3v1_tag.nil?
59
+ data << id3v1_tag(taglib, id3v1_tag) unless id3v1_tag.nil?
75
60
  else
76
- frames.each { |frame| data << data_from_frame(frame) }
61
+ frames.each { |frame| data << data_from_frame(frame, **opts) }
77
62
  end
78
63
 
79
- data
64
+ data.compact
80
65
  end
81
66
 
82
- # read_first_id3
83
- #
84
- # Similar to read_all_id3, but optimized for reading only one frame at max
85
- def read_first_id3(iface)
86
- frame = nil
87
- @id3v2_frames.each do |f|
88
- frame = first_frame_for_id(f, iface) if frame.nil?
89
- end
67
+ def read_first_tag(taglib, id3v2_frames, id3v1_tag = nil, **opts)
68
+ read_all_tags(taglib, id3v2_frames, id3v1_tag, **opts).first
69
+ end
90
70
 
91
- if frame.nil?
92
- data = iface.info.id3v1_tag.send(@id3v1_tag) unless @id3v1_tag.nil?
71
+ def id3v1_tag(taglib, tag_name)
72
+ return nil if taglib.id3v1_tag.empty?
73
+ v = taglib.id3v1_tag.send(tag_name)
74
+ # TEMPFIX: nonexistent id3v1 tags return an empty string (taglib-ruby issue #49)
75
+ case
76
+ when v.is_a?(Fixnum) && v.zero?
77
+ nil
78
+ when v.is_a?(String) && v.empty?
79
+ nil
93
80
  else
94
- data = data_from_frame(frame)
81
+ v
95
82
  end
83
+ end
96
84
 
97
- data
85
+ def id3v2_frames(taglib, frame_id)
86
+ taglib.id3v2_tag.frame_list(frame_id)
98
87
  end
99
88
 
100
- def read_int_pair(iface)
101
- int_pair_str = read_first_id3(iface).to_s
102
- EasyTag::Utilities.get_int_pair(int_pair_str)
89
+ def data_from_frame(frame, **opts)
90
+ case
91
+ when frame.is_a?(TagLib::ID3v2::TextIdentificationFrame)
92
+ field_list = frame.field_list
93
+ opts[:field_list] ? field_list : field_list.first
94
+ when frame.is_a?(TagLib::ID3v2::UnsynchronizedLyricsFrame)
95
+ frame.text
96
+ when frame.is_a?(TagLib::ID3v2::CommentsFrame)
97
+ frame.text
98
+ when frame.is_a?(TagLib::ID3v2::AttachedPictureFrame)
99
+ EasyTag::Image.new(frame.picture).tap do |img|
100
+ img.desc = frame.description
101
+ img.type = frame.type
102
+ img.mime_type = frame.mime_type
103
+ end
104
+ else
105
+ nil
106
+ end
103
107
  end
104
108
 
105
- def read_field_list_as_key_value(iface)
106
- kv_hash = {}
107
- frame_data = read_all_id3(iface)
109
+ def read_user_info(taglib, **opts)
110
+ user_info = {}
111
+ frame_data = read_all_tags(taglib, ['TXXX'], nil, {field_list: true})
108
112
 
109
113
  frame_data.each do |data|
110
114
  key = data[0]
111
115
  values = data[1..-1]
112
116
 
113
- key = Utilities.normalize_string(key) if @options[:normalize]
114
- key = key.to_sym if @options[:to_sym]
115
- kv_hash[key] = values.count > 1 ? values : values.first
117
+ user_info[key] = values.count > 1 ? values : values.first
116
118
  end
117
119
 
118
- kv_hash
120
+ user_info
119
121
  end
120
122
 
121
- def read_date(iface)
122
- id3v1 = iface.info.id3v1_tag
123
-
124
- v10_year = id3v1.year.to_s if id3v1.year > 0
125
- v23_year = data_from_frame(first_frame_for_id('TYER', iface))
126
- v23_date = data_from_frame(first_frame_for_id('TDAT', iface))
127
- v24_date = data_from_frame(first_frame_for_id('TDRC', iface))
123
+ # NOTE: id3v2.3 tags (TYER+TDAT) will lose month/day information due to taglib's
124
+ # internal frame conversion. During the conversion, the TDAT frame is
125
+ # dropped and only the TYER frame is used in the conversion to TDRC.
126
+ # (see: https://github.com/taglib/taglib/issues/127)
127
+ def read_date(taglib, **opts)
128
+ v10_year = taglib.id3v1_tag.year.to_s if taglib.id3v1_tag.year > 0
129
+ v24_date = read_first_tag(taglib, ['TDRC'])
128
130
 
129
131
  # check variables in order of importance
130
- date_str = v24_date || v23_year || v10_year
131
- # only append v23_date if date_str is currently a year
132
- date_str << v23_date unless v23_date.nil? or date_str.length > 4
132
+ date_str = v24_date || v10_year
133
133
  puts "MP3#date: date_str = \"#{date_str}\"" if $DEBUG
134
134
 
135
135
  date_str
136
136
  end
137
137
 
138
- def read_ufid(iface)
139
- frames = iface.info.id3v2_tag.frame_list('UFID')
140
- ufid = nil
141
-
142
- frames.each do |frame|
143
- if @handler_opts[:owner].eql?(frame.owner)
144
- ufid = frame.identifier
145
- break
146
- end
147
- end
148
-
149
- ufid
138
+ def read_ufid(taglib, owner, opts)
139
+ frames = taglib.id3v2_tag.frame_list('UFID')
140
+ frames.each { |frame| return frame.identifier if owner.eql?(frame.owner) }
141
+ nil
150
142
  end
151
143
  end
152
144
  end
153
145
 
154
- module EasyTag::Attributes
155
- MP3_ATTRIB_ARGS = [
156
- # title
157
- {
158
- :name => :title,
159
- :id3v2_frames => ['TIT2'],
160
- :id3v1_tag => :title,
161
- :handler => :read_first_id3,
162
- :type => Type::STRING,
163
- },
164
-
165
- # title_sort_order
166
- # TSOT - (v2.4 only)
167
- # XSOT - Musicbrainz Picard custom
168
- {
169
- :name => :title_sort_order,
170
- :id3v2_frames => ['TSOT', 'XSOT'],
171
- :handler => :read_first_id3,
172
- :type => Type::STRING,
173
- },
174
-
175
- # subtitle
176
- {
177
- :name => :subtitle,
178
- :id3v2_frames => ['TIT3'],
179
- :handler => :read_first_id3,
180
- :type => Type::STRING,
181
- },
182
-
183
- # artist
184
- {
185
- :name => :artist,
186
- :id3v2_frames => ['TPE1'],
187
- :id3v1_tag => :artist,
188
- :handler => :read_first_id3,
189
- :type => Type::STRING,
190
- },
191
-
192
- # artist_sort_order
193
- # TSOP - (v2.4 only)
194
- # XSOP - Musicbrainz Picard custom
195
- {
196
- :name => :artist_sort_order,
197
- :id3v2_frames => ['TSOP', 'XSOP'],
198
- :handler => :read_first_id3,
199
- :type => Type::STRING,
200
- },
201
-
202
- # album_artist
203
- {
204
- :name => :album_artist,
205
- :id3v2_frames => ['TPE2'],
206
- :handler => :read_first_id3,
207
- :type => Type::STRING,
208
- },
209
-
210
- # album_artist_sort_order
211
- {
212
- :name => :album_artist_sort_order,
213
- :handler => :user_info_lookup,
214
- :handler_opts => {:key => :albumartistsort},
215
- :type => Type::STRING,
216
- },
217
-
218
- # album
219
- {
220
- :name => :album,
221
- :id3v2_frames => ['TALB'],
222
- :id3v1_tag => :album,
223
- :handler => :read_first_id3,
224
- :type => Type::STRING,
225
- },
226
-
227
- # compilation?
228
- {
229
- :name => :compilation?,
230
- :id3v2_frames => ['TCMP'],
231
- :handler => :read_first_id3,
232
- :type => Type::BOOLEAN,
233
- # TODO: remove is_flag option, determine boolean value implicitly
234
- :options => {:is_flag => true},
235
- },
236
-
237
- # album_sort_order
238
- # TSOA - (v2.4 only)
239
- # XSOA - Musicbrainz Picard custom
240
- {
241
- :name => :album_sort_order,
242
- :id3v2_frames => ['TSOA', 'XSOA'],
243
- :handler => :read_first_id3,
244
- :type => Type::STRING,
245
- },
246
-
247
- # genre
248
- {
249
- :name => :genre,
250
- :id3v2_frames => ['TCON'],
251
- :id3v1_tag => :genre,
252
- :handler => :read_first_id3,
253
- :type => Type::STRING,
254
- },
255
-
256
- # disc_subtitle
257
- {
258
- :name => :disc_subtitle,
259
- :id3v2_frames => ['TSST'],
260
- :handler => :read_first_id3,
261
- :type => Type::STRING,
262
- },
263
-
264
- # media
265
- {
266
- :name => :media,
267
- :id3v2_frames => ['TMED'],
268
- :handler => :read_first_id3,
269
- :type => Type::STRING,
270
- },
271
-
272
- # label
273
- {
274
- :name => :label,
275
- :id3v2_frames => ['TPUB'],
276
- :handler => :read_first_id3,
277
- :type => Type::STRING,
278
- },
279
-
280
- # encoded_by
281
- {
282
- :name => :encoded_by,
283
- :id3v2_frames => ['TENC'],
284
- :handler => :read_first_id3,
285
- :type => Type::STRING,
286
- },
287
-
288
- # encoder_settings
289
- {
290
- :name => :encoder_settings,
291
- :id3v2_frames => ['TSSE'],
292
- :handler => :read_first_id3,
293
- :type => Type::STRING,
294
- },
295
-
296
- # group
297
- {
298
- :name => :group,
299
- :id3v2_frames => ['TIT1'],
300
- :handler => :read_first_id3,
301
- :type => Type::STRING,
302
- },
303
-
304
- # composer
305
- {
306
- :name => :composer,
307
- :id3v2_frames => ['TCOM'],
308
- :handler => :read_first_id3,
309
- :type => Type::STRING,
310
- },
311
-
312
- # conductor
313
- {
314
- :name => :conductor,
315
- :id3v2_frames => ['TPE3'],
316
- :handler => :read_first_id3,
317
- :type => Type::STRING,
318
- },
319
-
320
- # remixer
321
- {
322
- :name => :remixer,
323
- :id3v2_frames => ['TPE4'],
324
- :handler => :read_first_id3,
325
- :type => Type::STRING,
326
- },
327
-
328
- # lyrics
329
- {
330
- :name => :lyrics,
331
- :id3v2_frames => ['USLT'],
332
- :handler => :read_first_id3,
333
- :type => Type::STRING,
334
- },
335
-
336
- # lyricist
337
- {
338
- :name => :lyricist,
339
- :id3v2_frames => ['TEXT'],
340
- :handler => :read_first_id3,
341
- :type => Type::STRING,
342
- },
343
-
344
- # copyright
345
- {
346
- :name => :copyright,
347
- :id3v2_frames => ['TCOP'],
348
- :handler => :read_first_id3,
349
- :type => Type::STRING,
350
- },
351
-
352
- # bpm
353
- {
354
- :name => :bpm,
355
- :id3v2_frames => ['TBPM'],
356
- :handler => :read_first_id3,
357
- :type => Type::INT,
358
- },
359
-
360
- # mood
361
- {
362
- :name => :mood,
363
- :id3v2_frames => ['TMOO'],
364
- :handler => :read_first_id3,
365
- :type => Type::STRING,
366
- },
367
-
368
- # track_num
369
- {
370
- :name => :track_num,
371
- :id3v2_frames => ['TRCK'],
372
- :id3v1_tag => :track,
373
- :default => [0, 0],
374
- :handler => :read_int_pair,
375
- :type => Type::INT_LIST, # don't know if this will ever be useful
376
- },
377
-
378
- # disc_num
379
- {
380
- :name => :disc_num,
381
- :id3v2_frames => ['TPOS'],
382
- :default => [0, 0],
383
- :handler => :read_int_pair,
384
- :type => Type::INT_LIST, # don't know if this will ever be useful
385
- },
386
-
387
- # original_date
388
- # TDOR - orig release date (v2.4 only)
389
- # TORY - orig release year (v2.3)
390
- {
391
- :name => :original_date,
392
- :id3v2_frames => ['TDOR', 'TORY'],
393
- :handler => :read_first_id3,
394
- :type => Type::DATETIME,
395
- },
396
-
397
- # comments
398
- {
399
- :name => :comments,
400
- :id3v2_frames => ['COMM'],
401
- :id3v1_tag => :comment,
402
- :handler => :read_all_id3,
403
- :default => [],
404
- :options => { :compact => true, :delete_empty => true }
405
- },
406
-
407
- # comment
408
- {
409
- :name => :comment,
410
- :handler => lambda { |iface| iface.comments.first },
411
- :type => Type::STRING,
412
- },
413
-
414
- # album_art
415
- {
416
- :name => :album_art,
417
- :id3v2_frames => ['APIC'],
418
- :handler => :read_all_id3,
419
- :default => [],
420
- },
421
-
422
- # date
423
- {
424
- :name => :date,
425
- :handler => :read_date,
426
- :type => Type::DATETIME,
427
- },
428
-
429
- # year
430
- {
431
- :name => :year,
432
- :handler => lambda { |iface| iface.date.nil? ? 0 : iface.date.year }
433
- },
434
-
435
- # apple_id
436
- {
437
- :name => :apple_id,
438
- :handler => :read_default,
439
- :type => Type::STRING,
440
- },
441
-
442
- # user_info
443
- {
444
- :name => :user_info,
445
- :id3v2_frames => ['TXXX'],
446
- :handler => :read_field_list_as_key_value,
447
- :default => {},
448
- :options => {:field_list => true},
449
- },
450
-
451
- # user_info_normalized
452
- {
453
- :name => :user_info_normalized,
454
- :id3v2_frames => ['TXXX'],
455
- :handler => :read_field_list_as_key_value,
456
- :default => {},
457
- :options => {:normalize => true,
458
- :to_sym => true,
459
- :field_list => true },
460
- },
461
-
462
- # asin
463
- {
464
- :name => :asin,
465
- :handler => :user_info_lookup,
466
- :handler_opts => {:key => :asin},
467
- :type => Type::STRING,
468
- },
469
-
470
- #
471
- # MusicBrainz Attributes
472
- #
473
-
474
- # musicbrainz_track_id
475
- {
476
- :name => :musicbrainz_track_id,
477
- :handler => :read_ufid,
478
- :handler_opts => {:owner => 'http://musicbrainz.org'},
479
- :type => Type::STRING,
480
- },
481
-
482
- # musicbrainz_album_artist_id
483
- {
484
- :name => :musicbrainz_album_artist_id,
485
- :handler => :user_info_lookup,
486
- :handler_opts => {:key => :musicbrainz_album_artist_id},
487
- :type => Type::STRING,
488
- },
489
-
490
- # musicbrainz_artist_id
491
- {
492
- :name => :musicbrainz_artist_id,
493
- :handler => :user_info_lookup,
494
- :handler_opts => {:key => :musicbrainz_artist_id},
495
- :type => Type::LIST,
496
- },
497
-
498
- # musicbrainz_album_id
499
- {
500
- :name => :musicbrainz_album_id,
501
- :handler => :user_info_lookup,
502
- :handler_opts => {:key => :musicbrainz_album_id},
503
- :type => Type::STRING,
504
- },
505
-
506
- # musicbrainz_album_status
507
- {
508
- :name => :musicbrainz_album_status,
509
- :handler => :user_info_lookup,
510
- :handler_opts => {:key => :musicbrainz_album_status},
511
- :type => Type::STRING,
512
- },
513
-
514
- # musicbrainz_album_type
515
- {
516
- :name => :musicbrainz_album_type,
517
- :handler => :user_info_lookup,
518
- :handler_opts => {:key => :musicbrainz_album_type},
519
- :type => Type::LIST,
520
- },
521
-
522
-
523
- # musicbrainz_release_group_id
524
- {
525
- :name => :musicbrainz_release_group_id,
526
- :handler => :user_info_lookup,
527
- :handler_opts => {:key => :musicbrainz_release_group_id},
528
- :type => Type::STRING,
529
- },
530
-
531
- # musicbrainz_album_release_country
532
- {
533
- :name => :musicbrainz_album_release_country,
534
- :handler => :user_info_lookup,
535
- :handler_opts => {:key => :musicbrainz_album_release_country},
536
- :type => Type::STRING,
537
- },
538
-
539
- #
540
- # Audio Properties
541
- #
542
-
543
- # length
544
- {
545
- :name => :length,
546
- :aliases => [:duration],
547
- :handler => :read_audio_property,
548
- :handler_opts => {:key => :length},
549
- :type => Type::INT,
550
- },
551
-
552
- # bitrate
553
- {
554
- :name => :bitrate,
555
- :handler => :read_audio_property,
556
- :handler_opts => {:key => :bitrate},
557
- :type => Type::INT,
558
- },
559
-
560
- # sample_rate
561
- {
562
- :name => :sample_rate,
563
- :handler => :read_audio_property,
564
- :handler_opts => {:key => :sample_rate},
565
- :type => Type::INT,
566
- },
567
-
568
- # channels
569
- {
570
- :name => :channels,
571
- :handler => :read_audio_property,
572
- :handler_opts => {:key => :channels},
573
- :type => Type::INT,
574
- },
575
-
576
- # copyrighted?
577
- {
578
- :name => :copyrighted?,
579
- :handler => :read_audio_property,
580
- :handler_opts => {:key => :copyrighted?},
581
- :type => Type::BOOLEAN,
582
- },
583
-
584
- # layer
585
- {
586
- :name => :layer,
587
- :handler => :read_audio_property,
588
- :handler_opts => {:key => :layer},
589
- :type => Type::INT,
590
- },
591
-
592
- # original?
593
- {
594
- :name => :original?,
595
- :handler => :read_audio_property,
596
- :handler_opts => {:key => :original?},
597
- :type => Type::BOOLEAN,
598
- },
599
-
600
- # protection_enabled?
601
- {
602
- :name => :protection_enabled?,
603
- :handler => :read_audio_property,
604
- :handler_opts => {:key => :protection_enabled},
605
- :type => Type::BOOLEAN,
606
- },
607
- ]
608
- end