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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -1
- data/easytag.gemspec +5 -1
- data/lib/easytag/attributes.rb +0 -0
- data/lib/easytag/attributes/base.rb +103 -0
- data/lib/easytag/attributes/mp3.rb +382 -0
- data/lib/easytag/attributes/mp4.rb +309 -0
- data/lib/easytag/interfaces/mp3.rb +24 -157
- data/lib/easytag/interfaces/mp4.rb +7 -175
- data/lib/easytag/version.rb +1 -1
- data/test/test_consistency.rb +8 -2
- data/test/test_mp3.rb +13 -13
- data/test/test_mp4.rb +1 -1
- data/test/test_util.rb +5 -0
- metadata +10 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1eef251238ecbdd6cc6b83991a3aad154a2a9007
|
4
|
+
data.tar.gz: 844c9b1b74f2a9cd67127e4a8d56a79f0387de48
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0a54438c37cade246206bc4f7bf0d0f0458e7d1fb2d002f09c642aca7a8f818aa7a3e9f54ceba51bbca22cb99fd9c75a330d11fb666fcae830706fe69062f10d
|
7
|
+
data.tar.gz: 5dbbd2002796e72d4c8884950e066be03054e88eee2de85d0f91539b8b84e4bfc347136afce68f9caf288097d0259f44bfb49c282f45d927f4737ec191247d37
|
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,22 @@
|
|
1
|
-
##### v0.
|
1
|
+
##### v0.3.0 (2013-05-29) #####
|
2
|
+
* added:
|
3
|
+
- `#encoded_by`
|
4
|
+
- `#encoder_settings`
|
5
|
+
- `#group`
|
6
|
+
- `#composer`
|
7
|
+
- `#lyrics`
|
8
|
+
- `#compilation?`
|
9
|
+
- `#subtitle`
|
10
|
+
- `#bpm`
|
11
|
+
- `#lyricist`
|
12
|
+
- `#copyright`
|
13
|
+
- `#comment`
|
14
|
+
|
15
|
+
* changed:
|
16
|
+
- `#comments` now returns an array, `#comment` is the
|
17
|
+
equivalent of the old behavior
|
18
|
+
|
19
|
+
##### v0.2.0 (2013-05-25) #####
|
2
20
|
* added:
|
3
21
|
- `#track_num`
|
4
22
|
- `#disc_num`
|
data/easytag.gemspec
CHANGED
@@ -6,7 +6,11 @@ Gem::Specification.new do |s|
|
|
6
6
|
s.name = 'easytag'
|
7
7
|
s.version = EasyTag::VERSION
|
8
8
|
s.summary = 'A simple audio metadata tagging interface'
|
9
|
-
s.description =
|
9
|
+
s.description = <<-EOF
|
10
|
+
EasyTag is an abstraction layer to the TagLib audio tagging library.
|
11
|
+
It is designed to provide a simple and consistent API regardless
|
12
|
+
of file format being read.
|
13
|
+
EOF
|
10
14
|
s.authors = ['Chris Lucas']
|
11
15
|
s.email = ['chris@chrisjlucas.com']
|
12
16
|
s.homepage = 'https://github.com/cjlucas/ruby-easytag'
|
File without changes
|
@@ -0,0 +1,103 @@
|
|
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
|
+
end
|
12
|
+
class BaseAttribute
|
13
|
+
Utilities = EasyTag::Utilities
|
14
|
+
|
15
|
+
def initialize(args)
|
16
|
+
@name = args[:name]
|
17
|
+
@default = args[:default]
|
18
|
+
@type = args[:type] || Type::STRING
|
19
|
+
@options = args[:options] || {}
|
20
|
+
@ivar = BaseAttribute.name_to_ivar(@name)
|
21
|
+
|
22
|
+
if args[:handler].is_a?(Symbol)
|
23
|
+
@handler = method(args[:handler])
|
24
|
+
elsif args[:handler].is_a?(Proc)
|
25
|
+
@handler = args[:handler]
|
26
|
+
end
|
27
|
+
|
28
|
+
# fill default options
|
29
|
+
|
30
|
+
# Remove nil objects from array (post process)
|
31
|
+
@options[:compact] ||= false
|
32
|
+
# normalizes key (if hash) (handler)
|
33
|
+
@options[:normalize] ||= false
|
34
|
+
# cast key (if hash) to symbol (handler)
|
35
|
+
@options[:to_sym] ||= false
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.can_clone?(obj)
|
40
|
+
obj.is_a?(String) || obj.is_a?(Array) || obj.is_a?(Hash)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.deep_copy(obj)
|
44
|
+
Marshal.load(Marshal.dump(obj))
|
45
|
+
end
|
46
|
+
|
47
|
+
def default
|
48
|
+
BaseAttribute.can_clone?(@default) ?
|
49
|
+
BaseAttribute.deep_copy(@default) : @default
|
50
|
+
end
|
51
|
+
|
52
|
+
def call(iface)
|
53
|
+
#puts 'entered call()'
|
54
|
+
data = @handler.call(iface)
|
55
|
+
data = type_cast(data)
|
56
|
+
post_process(data)
|
57
|
+
end
|
58
|
+
|
59
|
+
def type_cast(data)
|
60
|
+
case @type
|
61
|
+
when Type::INT
|
62
|
+
data = data.to_i
|
63
|
+
when Type::DATETIME
|
64
|
+
data = Utilities.get_datetime(data.to_s)
|
65
|
+
end
|
66
|
+
|
67
|
+
data
|
68
|
+
end
|
69
|
+
|
70
|
+
def post_process(data)
|
71
|
+
if @options[:is_flag]
|
72
|
+
data = data.to_i == 1 ? true : false
|
73
|
+
end
|
74
|
+
|
75
|
+
# fall back to default if data is nil
|
76
|
+
data = BaseAttribute.obj_or_nil(data) || default
|
77
|
+
|
78
|
+
# run obj_or_nil on each item in array
|
79
|
+
data.map! { |item| BaseAttribute.obj_or_nil(item) } if data.is_a?(Array)
|
80
|
+
|
81
|
+
if @options[:compact] && data.respond_to?(:compact!)
|
82
|
+
data.compact!
|
83
|
+
end
|
84
|
+
|
85
|
+
data
|
86
|
+
end
|
87
|
+
# avoid returing empty objects
|
88
|
+
def self.obj_or_nil(o)
|
89
|
+
if o.class == String
|
90
|
+
ret = o.empty? ? nil : o
|
91
|
+
else
|
92
|
+
o
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.name_to_ivar(name)
|
97
|
+
name = name.to_s if name.class == Symbol
|
98
|
+
name.gsub!(/\?/, '')
|
99
|
+
name.insert(0, '@')
|
100
|
+
name.to_sym
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,382 @@
|
|
1
|
+
require 'taglib'
|
2
|
+
|
3
|
+
require 'easytag/image'
|
4
|
+
require 'easytag/util'
|
5
|
+
require 'easytag/attributes/base'
|
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
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
def frames_for_id(id, iface)
|
27
|
+
iface.info.id3v2_tag.frame_list(id)
|
28
|
+
end
|
29
|
+
|
30
|
+
def first_frame_for_id(id, iface)
|
31
|
+
frames_for_id(id, iface).first
|
32
|
+
end
|
33
|
+
|
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
|
+
else
|
49
|
+
warn 'no defined frames match the given frame'
|
50
|
+
end
|
51
|
+
|
52
|
+
data
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# read handlers
|
57
|
+
#
|
58
|
+
|
59
|
+
# read_all_id3
|
60
|
+
#
|
61
|
+
# gets data from each frame id given
|
62
|
+
# only falls back to the id3v1 tag if none found
|
63
|
+
def read_all_id3(iface)
|
64
|
+
frames = []
|
65
|
+
@id3v2_frames.each do |f|
|
66
|
+
frames += frames_for_id(f, iface)
|
67
|
+
end
|
68
|
+
|
69
|
+
data = []
|
70
|
+
# only check id3v1 if no id3v2 frames found
|
71
|
+
if frames.empty?
|
72
|
+
data << iface.info.id3v1_tag.send(@id3v1_tag) unless @id3v1_tag.nil?
|
73
|
+
else
|
74
|
+
frames.each { |frame| data << data_from_frame(frame) }
|
75
|
+
end
|
76
|
+
|
77
|
+
data
|
78
|
+
end
|
79
|
+
|
80
|
+
# read_first_id3
|
81
|
+
#
|
82
|
+
# Similar to read_all_id3, but optimized for reading only one frame at max
|
83
|
+
def read_first_id3(iface)
|
84
|
+
frame = nil
|
85
|
+
@id3v2_frames.each do |f|
|
86
|
+
frame = first_frame_for_id(f, iface) if frame.nil?
|
87
|
+
end
|
88
|
+
|
89
|
+
if frame.nil?
|
90
|
+
data = iface.info.id3v1_tag.send(@id3v1_tag) unless @id3v1_tag.nil?
|
91
|
+
else
|
92
|
+
data = data_from_frame(frame)
|
93
|
+
end
|
94
|
+
|
95
|
+
data
|
96
|
+
end
|
97
|
+
|
98
|
+
def read_int_pair(iface)
|
99
|
+
int_pair_str = read_first_id3(iface).to_s
|
100
|
+
EasyTag::Utilities.get_int_pair(int_pair_str)
|
101
|
+
end
|
102
|
+
|
103
|
+
def read_field_list_as_key_value(iface)
|
104
|
+
kv_hash = {}
|
105
|
+
frame_data = read_all_id3(iface)
|
106
|
+
|
107
|
+
frame_data.each do |data|
|
108
|
+
key, value = data
|
109
|
+
key = Utilities.normalize_string(key) if @options[:normalize]
|
110
|
+
key = key.to_sym if @options[:to_sym]
|
111
|
+
kv_hash[key] = value
|
112
|
+
end
|
113
|
+
|
114
|
+
kv_hash
|
115
|
+
end
|
116
|
+
|
117
|
+
def read_date(iface)
|
118
|
+
id3v1 = iface.info.id3v1_tag
|
119
|
+
|
120
|
+
v10_year = id3v1.year.to_s if id3v1.year > 0
|
121
|
+
v23_year = data_from_frame(first_frame_for_id('TYER', iface))
|
122
|
+
v23_date = data_from_frame(first_frame_for_id('TDAT', iface))
|
123
|
+
v24_date = data_from_frame(first_frame_for_id('TDRC', iface))
|
124
|
+
|
125
|
+
# check variables in order of importance
|
126
|
+
date_str = v24_date || v23_year || v10_year
|
127
|
+
# only append v23_date if date_str is currently a year
|
128
|
+
date_str << v23_date unless v23_date.nil? or date_str.length > 4
|
129
|
+
puts "MP3#date: date_str = \"#{date_str}\"" if $DEBUG
|
130
|
+
|
131
|
+
date_str
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
module EasyTag::Attributes
|
137
|
+
MP3_ATTRIB_ARGS = [
|
138
|
+
# title
|
139
|
+
{
|
140
|
+
:name => :title,
|
141
|
+
:id3v2_frames => ['TIT2'],
|
142
|
+
:id3v1_tag => :title,
|
143
|
+
:handler => :read_first_id3,
|
144
|
+
:type => Type::STRING,
|
145
|
+
},
|
146
|
+
|
147
|
+
# title_sort_order
|
148
|
+
# TSOT - (v2.4 only)
|
149
|
+
# XSOT - Musicbrainz Picard custom
|
150
|
+
{
|
151
|
+
:name => :title_sort_order,
|
152
|
+
:id3v2_frames => ['TSOT', 'XSOT'],
|
153
|
+
:handler => :read_first_id3,
|
154
|
+
},
|
155
|
+
|
156
|
+
# subtitle
|
157
|
+
{
|
158
|
+
:name => :subtitle,
|
159
|
+
:id3v2_frames => ['TIT1'],
|
160
|
+
:handler => :read_first_id3,
|
161
|
+
},
|
162
|
+
|
163
|
+
# artist
|
164
|
+
{
|
165
|
+
:name => :artist,
|
166
|
+
:id3v2_frames => ['TPE1'],
|
167
|
+
:id3v1_tag => :artist,
|
168
|
+
:handler => :read_first_id3,
|
169
|
+
},
|
170
|
+
|
171
|
+
# artist_sort_order
|
172
|
+
# TSOP - (v2.4 only)
|
173
|
+
# XSOP - Musicbrainz Picard custom
|
174
|
+
{
|
175
|
+
:name => :artist_sort_order,
|
176
|
+
:id3v2_frames => ['TSOP', 'XSOP'],
|
177
|
+
:handler => :read_first_id3,
|
178
|
+
},
|
179
|
+
|
180
|
+
# album_artist
|
181
|
+
{
|
182
|
+
:name => :album_artist,
|
183
|
+
:id3v2_frames => ['TPE2'],
|
184
|
+
:handler => :read_first_id3,
|
185
|
+
},
|
186
|
+
|
187
|
+
# album_artist_sort_order
|
188
|
+
{
|
189
|
+
:name => :album_artist_sort_order,
|
190
|
+
:handler => lambda { |iface| iface.user_info[:albumartistsort] }
|
191
|
+
},
|
192
|
+
|
193
|
+
# album
|
194
|
+
{
|
195
|
+
:name => :album,
|
196
|
+
:id3v2_frames => ['TALB'],
|
197
|
+
:id3v1_tag => :album,
|
198
|
+
:handler => :read_first_id3,
|
199
|
+
},
|
200
|
+
|
201
|
+
# compilation?
|
202
|
+
{
|
203
|
+
:name => :compilation?,
|
204
|
+
:id3v2_frames => ['TCMP'],
|
205
|
+
:default => false,
|
206
|
+
:handler => :read_first_id3,
|
207
|
+
:options => {:is_flag => true},
|
208
|
+
},
|
209
|
+
|
210
|
+
# album_sort_order
|
211
|
+
# TSOA - (v2.4 only)
|
212
|
+
# XSOA - Musicbrainz Picard custom
|
213
|
+
{
|
214
|
+
:name => :album_sort_order,
|
215
|
+
:id3v2_frames => ['TSOA', 'XSOA'],
|
216
|
+
:handler => :read_first_id3,
|
217
|
+
},
|
218
|
+
|
219
|
+
# genre
|
220
|
+
{
|
221
|
+
:name => :genre,
|
222
|
+
:id3v2_frames => ['TCON'],
|
223
|
+
:id3v1_tag => :genre,
|
224
|
+
:handler => :read_first_id3,
|
225
|
+
},
|
226
|
+
|
227
|
+
# disc_subtitle
|
228
|
+
{
|
229
|
+
:name => :disc_subtitle,
|
230
|
+
:id3v2_frames => ['TSST'],
|
231
|
+
:handler => :read_first_id3,
|
232
|
+
},
|
233
|
+
|
234
|
+
# media
|
235
|
+
{
|
236
|
+
:name => :media,
|
237
|
+
:id3v2_frames => ['TMED'],
|
238
|
+
:handler => :read_first_id3,
|
239
|
+
},
|
240
|
+
|
241
|
+
# label
|
242
|
+
{
|
243
|
+
:name => :label,
|
244
|
+
:id3v2_frames => ['TPUB'],
|
245
|
+
:handler => :read_first_id3,
|
246
|
+
},
|
247
|
+
|
248
|
+
# encoded_by
|
249
|
+
{
|
250
|
+
:name => :encoded_by,
|
251
|
+
:id3v2_frames => ['TENC'],
|
252
|
+
:handler => :read_first_id3,
|
253
|
+
},
|
254
|
+
|
255
|
+
# encoder_settings
|
256
|
+
{
|
257
|
+
:name => :encoder_settings,
|
258
|
+
:id3v2_frames => ['TSSE'],
|
259
|
+
:handler => :read_first_id3,
|
260
|
+
},
|
261
|
+
|
262
|
+
# group
|
263
|
+
{
|
264
|
+
:name => :group,
|
265
|
+
:id3v2_frames => ['TIT1'],
|
266
|
+
:handler => :read_first_id3,
|
267
|
+
},
|
268
|
+
|
269
|
+
# composer
|
270
|
+
{
|
271
|
+
:name => :composer,
|
272
|
+
:id3v2_frames => ['TCOM'],
|
273
|
+
:handler => :read_first_id3,
|
274
|
+
},
|
275
|
+
|
276
|
+
# lyrics
|
277
|
+
{
|
278
|
+
:name => :lyrics,
|
279
|
+
:id3v2_frames => ['USLT'],
|
280
|
+
:handler => :read_first_id3,
|
281
|
+
},
|
282
|
+
|
283
|
+
# lyricist
|
284
|
+
{
|
285
|
+
:name => :lyricist,
|
286
|
+
:id3v2_frames => ['TEXT'],
|
287
|
+
:handler => :read_first_id3,
|
288
|
+
},
|
289
|
+
|
290
|
+
# copyright
|
291
|
+
{
|
292
|
+
:name => :copyright,
|
293
|
+
:id3v2_frames => ['TCOP'],
|
294
|
+
:handler => :read_first_id3,
|
295
|
+
},
|
296
|
+
|
297
|
+
# bpm
|
298
|
+
{
|
299
|
+
:name => :bpm,
|
300
|
+
:id3v2_frames => ['TBPM'],
|
301
|
+
:handler => :read_first_id3,
|
302
|
+
:type => Type::INT,
|
303
|
+
},
|
304
|
+
|
305
|
+
# track_num
|
306
|
+
{
|
307
|
+
:name => :track_num,
|
308
|
+
:id3v2_frames => ['TRCK'],
|
309
|
+
:id3v1_tag => :track,
|
310
|
+
:default => [0, 0],
|
311
|
+
:handler => :read_int_pair,
|
312
|
+
:type => Type::INT_LIST, # don't know if this will ever be useful
|
313
|
+
},
|
314
|
+
|
315
|
+
# disc_num
|
316
|
+
{
|
317
|
+
:name => :disc_num,
|
318
|
+
:id3v2_frames => ['TPOS'],
|
319
|
+
:default => [0, 0],
|
320
|
+
:handler => :read_int_pair,
|
321
|
+
:type => Type::INT_LIST, # don't know if this will ever be useful
|
322
|
+
},
|
323
|
+
|
324
|
+
# original_date
|
325
|
+
# TDOR - orig release date (v2.4 only)
|
326
|
+
# TORY - orig release year (v2.3)
|
327
|
+
{
|
328
|
+
:name => :original_date,
|
329
|
+
:id3v2_frames => ['TDOR', 'TORY'],
|
330
|
+
:handler => :read_first_id3,
|
331
|
+
:type => Type::DATETIME,
|
332
|
+
},
|
333
|
+
|
334
|
+
# comments
|
335
|
+
{
|
336
|
+
:name => :comments,
|
337
|
+
:id3v2_frames => ['COMM'],
|
338
|
+
:id3v1_tag => :comment,
|
339
|
+
:handler => :read_all_id3,
|
340
|
+
:default => [],
|
341
|
+
:options => { :compact => true }
|
342
|
+
},
|
343
|
+
|
344
|
+
# comment
|
345
|
+
{
|
346
|
+
:name => :comment,
|
347
|
+
:handler => lambda { |iface| iface.comments.first }
|
348
|
+
},
|
349
|
+
|
350
|
+
# album_art
|
351
|
+
{
|
352
|
+
:name => :album_art,
|
353
|
+
:id3v2_frames => ['APIC'],
|
354
|
+
:handler => :read_all_id3,
|
355
|
+
:default => [],
|
356
|
+
},
|
357
|
+
|
358
|
+
# date
|
359
|
+
{
|
360
|
+
:name => :date,
|
361
|
+
:handler => :read_date,
|
362
|
+
:type => Type::DATETIME,
|
363
|
+
},
|
364
|
+
|
365
|
+
# year
|
366
|
+
{
|
367
|
+
:name => :year,
|
368
|
+
:handler => lambda { |iface| iface.date.nil? ? 0 : iface.date.year }
|
369
|
+
},
|
370
|
+
|
371
|
+
# user_info
|
372
|
+
{
|
373
|
+
:name => :user_info_new,
|
374
|
+
:id3v2_frames => ['TXXX'],
|
375
|
+
:handler => :read_field_list_as_key_value,
|
376
|
+
:default => {},
|
377
|
+
:options => { :normalize => true,
|
378
|
+
:to_sym => true,
|
379
|
+
:field_list => true },
|
380
|
+
},
|
381
|
+
]
|
382
|
+
end
|