easytag 0.2.0 → 0.3.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.
@@ -0,0 +1,309 @@
1
+ # encoding: UTF-8
2
+ require 'easytag/attributes/base'
3
+
4
+ module EasyTag::Attributes
5
+ # type of TagLib::MP4::Item
6
+ module ItemType
7
+ STRING = 0 # not part of TagLib::MP4::Item, just for convenience
8
+ STRING_LIST = 1
9
+ BOOL = 2
10
+ INT = 3
11
+ INT_PAIR = 4
12
+ COVER_ART_LIST = 5
13
+ end
14
+
15
+ class MP4Attribute < BaseAttribute
16
+ attr_reader :name, :ivar
17
+
18
+ def initialize(args)
19
+ super(args)
20
+
21
+ @item_ids = args[:item_ids]
22
+ @item_type = args[:item_type] || ItemType::STRING
23
+ end
24
+
25
+ private
26
+
27
+ def item_for_id(id, iface)
28
+ iface.info.tag.item_list_map.fetch(id)
29
+ end
30
+
31
+ def data_from_item(item)
32
+ case @item_type
33
+ when ItemType::STRING
34
+ item.to_string_list[0]
35
+ when ItemType::STRING_LIST
36
+ item.to_string_list
37
+ when ItemType::BOOL
38
+ item.to_bool
39
+ when ItemType::INT
40
+ item.to_int
41
+ when ItemType::INT_PAIR
42
+ item.to_int_pair
43
+ when ItemType::COVER_ART_LIST
44
+ artwork = []
45
+ item.to_cover_art_list.each do |img|
46
+ artwork << EasyTag::Image.new(img.data)
47
+ end
48
+ artwork
49
+ else
50
+ nil
51
+ end
52
+ end
53
+
54
+ # read handlers
55
+
56
+ def read_first_item(iface)
57
+ item = nil
58
+ @item_ids.each do |id|
59
+ item = item_for_id(id, iface) if item.nil?
60
+ end
61
+
62
+ data_from_item(item) unless item.nil?
63
+ end
64
+
65
+ def read_user_info(iface)
66
+ kv_hash = {}
67
+ iface.info.tag.item_list_map.to_a.each do |key, value|
68
+ match_data = key.match(/\:com.apple.iTunes\:(.*)/)
69
+ if match_data
70
+ key = match_data[1]
71
+ key = Utilities.normalize_string(key) if @options[:normalize]
72
+ key = key.to_sym if @options[:to_sym]
73
+ kv_hash[key] = value.to_string_list[0]
74
+ end
75
+ end
76
+
77
+ kv_hash
78
+ end
79
+
80
+ end
81
+ end
82
+
83
+ module EasyTag::Attributes
84
+ MP4_ATTRIB_ARGS = [
85
+ # title
86
+ {
87
+ :name => :title,
88
+ :item_ids => ['©nam'],
89
+ :handler => :read_first_item,
90
+ },
91
+
92
+ # title_sort_order
93
+ {
94
+ :name => :title_sort_order,
95
+ :item_ids => ['sonm'],
96
+ :handler => :read_first_item,
97
+ },
98
+
99
+ # artist
100
+ {
101
+ :name => :artist,
102
+ :item_ids => ['©ART'],
103
+ :handler => :read_first_item,
104
+ },
105
+
106
+ # artist_sort_order
107
+ {
108
+ :name => :artist_sort_order,
109
+ :item_ids => ['soar'],
110
+ :handler => :read_first_item,
111
+ },
112
+
113
+ # album_artist
114
+ {
115
+ :name => :album_artist,
116
+ :item_ids => ['aART'],
117
+ :handler => :read_first_item,
118
+ },
119
+
120
+ # album_artist_sort_order
121
+ {
122
+ :name => :album_artist_sort_order,
123
+ :item_ids => ['soaa'],
124
+ :handler => :read_first_item,
125
+ },
126
+
127
+ # album
128
+ {
129
+ :name => :album,
130
+ :item_ids => ['©alb'],
131
+ :handler => :read_first_item,
132
+ },
133
+
134
+ # album_sort_order
135
+ {
136
+ :name => :album_sort_order,
137
+ :item_ids => ['soal'],
138
+ :handler => :read_first_item,
139
+ },
140
+
141
+ # genre
142
+ {
143
+ :name => :genre,
144
+ :item_ids => ['©gen'],
145
+ :handler => :read_first_item,
146
+ },
147
+
148
+ # comments
149
+ {
150
+ :name => :comments,
151
+ :item_ids => ['©cmt'],
152
+ :handler => :read_first_item,
153
+ :item_type => ItemType::STRING_LIST,
154
+ :default => [],
155
+ },
156
+
157
+ # comment
158
+ {
159
+ :name => :comment,
160
+ :handler => lambda { |iface| iface.comments.first }
161
+ },
162
+
163
+ # lyrics
164
+ {
165
+ :name => :lyrics,
166
+ :item_ids => ['©lyr'],
167
+ :handler => :read_first_item,
168
+ },
169
+
170
+ # date
171
+ {
172
+ :name => :date,
173
+ :item_ids => ['©day'],
174
+ :handler => :read_first_item,
175
+ :type => Type::DATETIME,
176
+ },
177
+
178
+ # year
179
+ {
180
+ :name => :year,
181
+ :handler => lambda { |iface| iface.date.nil? ? 0 : iface.date.year }
182
+ },
183
+
184
+ # apple_id
185
+ {
186
+ :name => :apple_id,
187
+ :item_ids => ['apid'],
188
+ :handler => :read_first_item,
189
+ },
190
+
191
+ # encoded_by
192
+ {
193
+ :name => :encoded_by,
194
+ :item_ids => ['©enc'],
195
+ :handler => :read_first_item,
196
+ },
197
+
198
+ # encoder_settings
199
+ {
200
+ :name => :encoder_settings,
201
+ :item_ids => ['©too'],
202
+ :handler => :read_first_item,
203
+ },
204
+
205
+ # group
206
+ {
207
+ :name => :group,
208
+ :item_ids => ['©grp'],
209
+ :handler => :read_first_item,
210
+ },
211
+
212
+ # compilation?
213
+ {
214
+ :name => :compilation?,
215
+ :item_ids => ['cpil'],
216
+ :handler => :read_first_item,
217
+ :item_type => ItemType::BOOL,
218
+ :default => false,
219
+ },
220
+
221
+ # bpm
222
+ {
223
+ :name => :bpm,
224
+ :item_ids => ['tmpo'],
225
+ :handler => :read_first_item,
226
+ :item_type => ItemType::INT,
227
+ :default => 0,
228
+ },
229
+
230
+ # copyright
231
+ {
232
+ :name => :copyright,
233
+ :item_ids => ['cprt'],
234
+ :handler => :read_first_item,
235
+ },
236
+
237
+ # track_num
238
+ {
239
+ :name => :track_num,
240
+ :item_ids => ['trkn'],
241
+ :handler => :read_first_item,
242
+ :item_type => ItemType::INT_PAIR,
243
+ :default => [0, 0],
244
+ },
245
+
246
+ # disc_num
247
+ {
248
+ :name => :disc_num,
249
+ :item_ids => ['disk'],
250
+ :handler => :read_first_item,
251
+ :item_type => ItemType::INT_PAIR,
252
+ :default => [0, 0],
253
+ },
254
+
255
+ # album_art
256
+ {
257
+ :name => :album_art,
258
+ :item_ids => ['covr'],
259
+ :handler => :read_first_item,
260
+ :item_type => ItemType::COVER_ART_LIST,
261
+ :default => [],
262
+ },
263
+
264
+ # user_info
265
+ {
266
+ :name => :user_info,
267
+ :handler => :read_user_info,
268
+ :default => {},
269
+ :options => {:normalize => true, :to_sym => true},
270
+ },
271
+
272
+ # subtitle
273
+ {
274
+ :name => :subtitle,
275
+ :handler => lambda { |iface| iface.user_info[:subtitle] }
276
+ },
277
+
278
+ # disc_subtitle
279
+ {
280
+ :name => :disc_subtitle,
281
+ :handler => lambda { |iface| iface.user_info[:discsubtitle] }
282
+ },
283
+
284
+ # media
285
+ {
286
+ :name => :media,
287
+ :handler => lambda { |iface| iface.user_info[:media] }
288
+ },
289
+
290
+ # label
291
+ {
292
+ :name => :label,
293
+ :handler => lambda { |iface| iface.user_info[:label] }
294
+ },
295
+
296
+ # composer
297
+ {
298
+ :name => :composer,
299
+ :handler => lambda { |iface| iface.user_info[:composer] }
300
+ },
301
+
302
+ # lyricist
303
+ {
304
+ :name => :lyricist,
305
+ :handler => lambda { |iface| iface.user_info[:lyricist] }
306
+ },
307
+
308
+ ]
309
+ end
@@ -1,186 +1,53 @@
1
1
  require 'mp3info'
2
+ require 'yaml'
2
3
 
3
- require 'easytag'
4
+ require 'easytag/attributes/mp3'
4
5
 
5
6
  module EasyTag::Interfaces
6
7
 
7
8
  class MP3 < Base
8
9
  def initialize(file)
9
10
  @info = TagLib::MPEG::File.new(file)
10
- @id3v1 = @info.id3v1_tag
11
- @id3v2 = @info.id3v2_tag
12
11
 
13
- # this is required because taglib hash issues with the TDAT/TYER
12
+ add_tdat_to_taglib(file)
13
+ end
14
+
15
+ private
16
+
17
+ def add_tdat_to_taglib(file)
18
+ # this is required because taglib hash issues with the TDAT+TYER
14
19
  # frame (https://github.com/taglib/taglib/issues/127)
15
- @id3v2_hash = ID3v2.new
20
+ id3v2_hash = ID3v2.new
16
21
 
17
22
  File.open(file) do |fp|
18
23
  fp.read(3) # read past ID3 identifier
19
24
  begin
20
- @id3v2_hash.from_io(fp)
25
+ id3v2_hash.from_io(fp)
21
26
  rescue ID3v2Error => e
22
27
  warn 'no id3v2 tags found'
23
28
  end
24
29
  end
25
30
 
26
- end
27
-
28
- def title
29
- obj_for_frame_id('TIT2') or Base.obj_or_nil(@id3v1.title)
30
- end
31
-
32
- def title_sort_order
33
- # TSOT - (v2.4 only)
34
- # XSOT - Musicbrainz Picard custom
35
- obj_for_frame_id('TSOT') || obj_for_frame_id('XSOT')
36
- end
37
-
38
- def artist
39
- obj_for_frame_id('TPE1') or Base.obj_or_nil(@id3v1.artist)
40
- end
41
-
42
- def artist_sort_order
43
- # TSOP - (v2.4 only)
44
- # XSOP - Musicbrainz Picard custom
45
- obj_for_frame_id('TSOP') || obj_for_frame_id('XSOP')
46
- end
47
-
48
- def album_artist
49
- obj_for_frame_id('TPE2')
50
- end
51
-
52
- def album_artist_sort_order
53
- user_info[:albumartistsort]
54
- end
55
-
56
- def album
57
- obj_for_frame_id('TALB') or Base.obj_or_nil(@id3v1.album)
58
- end
59
-
60
- def album_sort_order
61
- # TSOA - (v2.4 only)
62
- # XSOA - Musicbrainz Picard custom
63
- obj_for_frame_id('TSOA') || obj_for_frame_id('XSOA')
64
- end
65
-
66
- # REVIEW: TCON supports genre refining, which we currently don't utilize
67
- def genre
68
- obj_for_frame_id('TCON') or Base.obj_or_nil(@id3v1.genre)
69
- end
70
-
71
- def comments
72
- return @comments unless @comments.nil?
73
-
74
- comm_frame = lookup_frames('COMM').first
75
- comm_str = comm_frame ? comm_frame.text : @id3v1.comment
76
-
77
- @comments = Base.obj_or_nil(comm_str)
78
- end
79
-
80
- def year
81
- date.nil? ? 0 : date.year
82
- end
83
-
84
- def date
85
- return @date unless @date.nil?
86
-
87
- v10_year = @id3v1.year.to_s if @id3v1.year > 0
88
- v23_year = obj_for_frame_id('TYER')
89
- v23_date = Base.obj_or_nil(@id3v2_hash['TDAT'])
90
- v24_date = obj_for_frame_id('TDRC')
31
+ # delete all TDAT frames (taglib-ruby segfaults when trying to read)
32
+ frames = @info.id3v2_tag.frame_list('TDAT')
33
+ frames.each { |frame| @info.id3v2_tag.remove_frame(frame) }
91
34
 
92
- # check variables in order of importance
93
- date_str = v24_date || v23_year || v10_year
94
- # only append v23_date if date_str is currently a year
95
- date_str << v23_date unless v23_date.nil? or date_str.length > 4
96
- puts "MP3#date: date_str = \"#{date_str}\"" if $DEBUG
35
+ if id3v2_hash['TDAT']
36
+ frame = TagLib::ID3v2::TextIdentificationFrame
37
+ .new('TDAT', TagLib::String::UTF8)
97
38
 
98
- @date = EasyTag::Utilities.get_datetime(date_str)
99
- end
100
-
101
- def original_date
102
- return @original_date unless @original_date.nil?
103
-
104
- # TDOR - orig release date (v2.4 only)
105
- # TORY - orig release year (v2.3)
106
- date_str = obj_for_frame_id('TDOR') || obj_for_frame_id('TORY')
107
- @original_date ||= EasyTag::Utilities.get_datetime(date_str)
108
- end
109
-
110
- def album_art
111
- return @album_art unless @album_art.nil?
112
-
113
- @album_art = []
114
- @id3v2.frame_list('APIC').each do |apic|
115
- img = EasyTag::Image.new(apic.picture)
116
- img.desc = apic.description
117
- img.type = apic.type
118
- img.mime_type = apic.mime_type
119
-
120
- @album_art << img
39
+ frame.text = id3v2_hash['TDAT']
40
+ @info.id3v2_tag.add_frame(frame)
121
41
  end
122
-
123
- @album_art
124
42
  end
125
43
 
126
- def track_num
127
- return @track_num unless @track_num.nil?
128
-
129
- pair = int_pair_for_frame_id('TRCK')
130
- @track_num = (pair == [0, 0]) ? [@id3v1.track, 0] : pair
131
- end
132
-
133
- def disc_num
134
- int_pair_for_frame_id('TPOS')
135
- end
136
-
137
- def user_info
138
- return @user_info unless @user_info.nil?
139
-
140
- @user_info = {}
141
- lookup_frames('TXXX').each do |frame|
142
- key, value = frame.field_list
143
- key = EasyTag::Utilities.normalize_string(key)
144
- @user_info[key.to_sym] = value
44
+ EasyTag::Attributes::MP3_ATTRIB_ARGS.each do |attrib_args|
45
+ attrib = EasyTag::Attributes::MP3Attribute.new(attrib_args)
46
+ define_method(attrib.name) do
47
+ instance_variable_get(attrib.ivar) ||
48
+ instance_variable_set(attrib.ivar, attrib.call(self))
145
49
  end
146
-
147
- @user_info
148
50
  end
149
51
 
150
- def disc_subtitle
151
- obj_for_frame_id('TSST')
152
- end
153
-
154
- def media
155
- obj_for_frame_id('TMED')
156
- end
157
-
158
- def label
159
- obj_for_frame_id('TPUB')
160
- end
161
-
162
- private
163
-
164
- # for TPOS and TRCK
165
- def int_pair_for_frame_id(frame_id)
166
- str = obj_for_frame_id(frame_id)
167
- EasyTag::Utilities.get_int_pair(str)
168
- end
169
-
170
- def obj_for_frame_id(frame_id)
171
- Base.obj_or_nil(lookup_first_field(frame_id))
172
- end
173
-
174
- def lookup_frames(frame_id)
175
- frames = @id3v2.frame_list(frame_id)
176
- end
177
-
178
- # get the first field in the first frame
179
- def lookup_first_field(frame_id)
180
- frame = lookup_frames(frame_id).first
181
- warn "frame '#{frame_id}' is not present" if frame.nil?
182
- frame.field_list.first unless frame.nil?
183
- end
184
52
  end
185
53
  end
186
-