id3lib-ruby 0.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.
data/lib/id3lib.rb ADDED
@@ -0,0 +1,349 @@
1
+
2
+ require 'id3lib_api'
3
+ require 'id3lib/info'
4
+ require 'id3lib/accessors'
5
+
6
+
7
+ #
8
+ # This module includes all the classes and constants of id3lib-ruby.
9
+ # Have a look at ID3Lib::Tag for an introduction on how to use this library.
10
+ #
11
+ module ID3Lib
12
+
13
+ # ID3 version 1. All V constants can be used with the methods
14
+ # new, update! or strip! of ID3Lib::Tag.
15
+ V1 = 1
16
+ # ID3 version 2
17
+ V2 = 2
18
+ # No tag type
19
+ V_NONE = 0
20
+ # All tag types
21
+ V_ALL = -1
22
+ # Both ID3 versions
23
+ V_BOTH = V1 | V2
24
+
25
+ NUM = 0
26
+ ID = 1
27
+ DESC = 2
28
+ FIELDS = 3
29
+
30
+ #
31
+ # This class is the main frontend of the library.
32
+ # Use it to read and write ID3 tag data of files.
33
+ #
34
+ # === Example of use
35
+ #
36
+ # tag = ID3Lib::Tag.read('shy_boy.mp3')
37
+ #
38
+ # # Remove comments
39
+ # tag.delete_if{ |frame| frame[:id] == :COMM }
40
+ #
41
+ # # Set year
42
+ # tag.year #=> 2000
43
+ # tag.year = 2005
44
+ #
45
+ # # Apply changes
46
+ # tag.update!
47
+ #
48
+ # === Working with tags
49
+ #
50
+ # You can use a ID3Lib::Tag object like an array. In fact, it is a subclass
51
+ # of Array. An ID3Lib::Tag contains frames which are stored as hashes,
52
+ # with field IDs as keys and field values as values. The frame IDs like TIT2
53
+ # are the ones specified by the ID3 standard. If you don't know these IDs,
54
+ # you probably want to use the accessor methods described afterwards, which
55
+ # have a more natural naming.
56
+ #
57
+ # tag.each do |frame|
58
+ # p frame
59
+ # end
60
+ # #=> {:id => :TIT2, :text => "Shy Boy", :textenc => 0}
61
+ # #=> {:id => :TPE1, :text => "Katie Melua", :textenc => 0}
62
+ # #=> {:id => :TALB, :text => "Piece By Piece", :textenc => 0}
63
+ # #=> {:id => :TRCK, :text => "1/12", :textenc => 0}
64
+ # #=> {:id => :TYER, :text => "2005", :textenc => 0}
65
+ # #=> {:id => :TCON, :text => "Jazz/Blues", :textenc => 0}
66
+ #
67
+ # === Get and set frames
68
+ #
69
+ # There are a number of accessors for text frames like
70
+ # title, performer, album, track, year, comment and genre. Have a look
71
+ # at ID3Lib::Accessors for a complete list.
72
+ #
73
+ # tag.title #=> "Shy Boi"
74
+ #
75
+ # tag.title = 'Shy Boy'
76
+ # tag.title #=> "Shy Boy"
77
+ #
78
+ # tag.track #=> [1,12]
79
+ # tag.year #=> 2005
80
+ #
81
+ # You can always read and write the raw text if you want. You just have
82
+ # to use the "manual access". It is generally encouraged to use the
83
+ # #frame_text method where possible, because the other two result in
84
+ # an exception when the frame isn't found.
85
+ #
86
+ # tag.frame_text(:TRCK) #=> "1/12"
87
+ # tag.frame_text(:TLAN) #=> nil
88
+ #
89
+ # tag.frame(:TRCK)[:text] #=> "1/12"
90
+ # # Raises an exception, because nil[:text] isn't possible:
91
+ # tag.frame(:TLAN)[:text]
92
+ #
93
+ # tag.find{ |f| f[:id] == :TRCK }[:text] #=> "1/12"
94
+ # # Also raises an exception:
95
+ # tag.find{ |f| f[:id] == :TLAN }[:text]
96
+ #
97
+ # Because only text frames can be set with accessors, you have to add
98
+ # special frames by hand.
99
+ #
100
+ # # Add two comments
101
+ # tag << {:id => :COMM, :text => 'chunky bacon'}
102
+ # tag << {:id => :COMM, :text => 'really.'}
103
+ #
104
+ # # Add an attached picture
105
+ # cover = {
106
+ # :id => :APIC,
107
+ # :mimetype => 'image/jpeg',
108
+ # :picturetype => 3,
109
+ # :description => 'A pretty picture',
110
+ # :textenc => 0,
111
+ # :data => File.read('cover.jpg')
112
+ # }
113
+ # tag << cover
114
+ #
115
+ # === Get information about frames
116
+ #
117
+ # In the last example we added an APIC frame. How can we know what data
118
+ # we have to store in the APIC hash?
119
+ #
120
+ # ID3Lib::Info.frame(:APIC)[3]
121
+ # #=> [:textenc, :mimetype, :picturetype, :description, :data]
122
+ #
123
+ # We see, the last element of the info array obtained through
124
+ # ID3Lib::Info.frame is an array of field IDs needed by APIC.
125
+ #
126
+ # Have a look at the ID3Lib::Info module for detailed information.
127
+ #
128
+ # === Write changes to file
129
+ #
130
+ # When you've finished modifying a tag, don't forget to call #update! to
131
+ # write the modifications back to the file. You have to check the return
132
+ # value of update!, it returns nil on failure. This probably means that
133
+ # the file is not writeable or cannot be created.
134
+ #
135
+ # tag.update!
136
+ #
137
+ # === Getting rid of a tag
138
+ #
139
+ # Use the #strip! method to completely remove a tag from a file.
140
+ #
141
+ # tag.strip!
142
+ #
143
+ class Tag < Array
144
+
145
+ include Accessors
146
+
147
+ attr_accessor :padding
148
+
149
+ #
150
+ # Create a new Tag. When a _filename_ is supplied, the tag of the file
151
+ # is read. _tagtype_ specifies the tag type to read and defaults to
152
+ # V_ALL.
153
+ # Use one of ID3Lib::V1, ID3Lib::V2, ID3Lib::V_BOTH or ID3Lib::V_ALL.
154
+ #
155
+ # tag = ID3Lib::Tag.new('shy_boy.mp3')
156
+ #
157
+ # Only read ID3v1 tag:
158
+ #
159
+ # id3v1_tag = ID3Lib::Tag.new('piece_by_piece.mp3', ID3Lib::V1)
160
+ #
161
+ def initialize(filename, readtype=V_ALL)
162
+ @filename = filename
163
+ @readtype = readtype
164
+ @padding = true
165
+
166
+ @tag = API::Tag.new
167
+ @tag.link(@filename, @readtype)
168
+ read_frames
169
+ end
170
+
171
+ #
172
+ # Returns an estimate of the number of bytes required to store the tag
173
+ # data.
174
+ #
175
+ def size
176
+ @tag.size
177
+ end
178
+
179
+ #
180
+ # Simple shortcut for getting a frame by its _id_.
181
+ #
182
+ # tag.frame(:TIT2)
183
+ # #=> {:id => :TIT2, :text => "Shy Boy", :textenc => 0}
184
+ #
185
+ # is the same as:
186
+ #
187
+ # tag.find{ |f| f[:id] == :TIT2 }
188
+ #
189
+ def frame(id)
190
+ find{ |f| f[:id] == id }
191
+ end
192
+
193
+ #
194
+ # Get the text of a frame specified by _id_. Returns nil if the
195
+ # frame can't be found.
196
+ #
197
+ # tag.find{ |f| f[:id] == :TIT2 }[:text] #=> "Shy Boy"
198
+ # tag.frame_text(:TIT2) #=> "Shy Boy"
199
+ #
200
+ # tag.find{ |f| f[:id] == :TLAN } #=> nil
201
+ # tag.frame_text(:TLAN) #=> nil
202
+ #
203
+ def frame_text(id)
204
+ f = frame(id)
205
+ f ? f[:text] : nil
206
+ end
207
+
208
+ #
209
+ # Set the text of a frame. First, all frames with the specified _id_ are
210
+ # deleted and then a new frame with _text_ is appended.
211
+ #
212
+ # tag.set_frame_text(:TLAN, 'eng')
213
+ #
214
+ def set_frame_text(id, text)
215
+ remove_frame(id)
216
+ self << { :id => id, :text => text }
217
+ end
218
+
219
+ #
220
+ # Remove all frames with the specified _id_.
221
+ #
222
+ def remove_frame(id)
223
+ delete_if{ |f| f[:id] == id }
224
+ end
225
+
226
+ #
227
+ # Updates the tag. This change can't be undone. _writetype_ specifies
228
+ # which tag type to write and defaults to _readtype_ (see #new).
229
+ #
230
+ # Returns a number corresponding to the written tag type(s) or nil if
231
+ # the update failed.
232
+ #
233
+ # tag.update!
234
+ # id3v1_tag.update!(ID3Lib::V1)
235
+ #
236
+ def update!(writetype=@readtype)
237
+ @tag.strip(writetype)
238
+ # The following two lines are necessary because of the weird
239
+ # behaviour of id3lib.
240
+ @tag.clear
241
+ @tag.link(@filename, writetype)
242
+
243
+ delete_if do |frame|
244
+ frame_info = Info.frame(frame[:id])
245
+ next true if not frame_info
246
+ libframe = API::Frame.new(frame_info[NUM])
247
+ Frame.write(frame, libframe)
248
+ @tag.add_frame(libframe)
249
+ false
250
+ end
251
+
252
+ @tag.set_padding(@padding)
253
+ tags = @tag.update(writetype)
254
+ return tags == 0 ? nil : tags
255
+ end
256
+
257
+ #
258
+ # Strip tag from file. This is dangerous because you lose all tag
259
+ # information. Specify _striptag_ to only strip a certain tag type.
260
+ # You don't have to call #update! after #strip!.
261
+ #
262
+ # tag.strip!
263
+ # another_tag.strip!(ID3Lib::V1)
264
+ #
265
+ def strip!(striptype=V_ALL)
266
+ clear
267
+ tags = @tag.strip(striptype)
268
+ @tag.clear
269
+ @tag.link(@filename, @readtype)
270
+ tags
271
+ end
272
+
273
+ #
274
+ # Check if there is a tag of type _type_.
275
+ #
276
+ def has_tag?(type=V2)
277
+ @tag.link(@filename, V_ALL)
278
+ @tag.has_tag_type(type)
279
+ end
280
+
281
+ private
282
+
283
+ def read_frames
284
+ iterator = @tag.iterator_new
285
+ while libframe = @tag.iterator_next_frame(iterator)
286
+ self << Frame.read(libframe)
287
+ end
288
+ end
289
+
290
+ end
291
+
292
+
293
+ module Frame #:nodoc:
294
+
295
+ def self.read(libframe)
296
+ frame = {}
297
+ info = Info.frame(libframe.num)
298
+ frame[:id] = info[ID]
299
+ if info[FIELDS].include?(:textenc)
300
+ textenc = field(libframe, :textenc).integer
301
+ frame[:textenc] = textenc
302
+ end
303
+ info[FIELDS].each do |field_id|
304
+ next if field_id == :textenc
305
+ libfield = field(libframe, field_id)
306
+ frame[field_id] = if textenc and textenc > 0
307
+ libfield.unicode
308
+ else
309
+ case Info::FieldType[libfield.type]
310
+ when :integer : libfield.integer
311
+ when :binary : libfield.binary
312
+ when :text : libfield.ascii
313
+ end
314
+ end
315
+ end
316
+ frame
317
+ end
318
+
319
+ def self.write(frame, libframe)
320
+ textenc = frame[:textenc]
321
+ field(libframe, :textenc).set_integer(textenc) if textenc
322
+ frame.each do |field_id, value|
323
+ unless Info.frame(frame[:id])[FIELDS].include?(field_id)
324
+ # TODO: Add method to check if frames are valid.
325
+ next
326
+ end
327
+ next if field_id == :textenc
328
+ libfield = field(libframe, field_id)
329
+ if textenc and textenc > 0
330
+ libfield.set_encoding(textenc)
331
+ libfield.set_unicode(value)
332
+ else
333
+ case Info::FieldType[libfield.type]
334
+ when :integer : libfield.set_integer(value)
335
+ when :binary : libfield.set_binary(value)
336
+ when :text : libfield.set_ascii(value)
337
+ end
338
+ end
339
+ end
340
+ end
341
+
342
+ def self.field(libframe, id)
343
+ libframe.field(Info.field(id)[NUM])
344
+ end
345
+
346
+ end
347
+
348
+
349
+ end
@@ -0,0 +1,260 @@
1
+
2
+ module ID3Lib
3
+
4
+ #
5
+ # The Accessors module defines accessor methods for ID3 text frames.
6
+ #
7
+ # Have a look at the source code to see the ID3 frame ID behind an accessor.
8
+ #
9
+ # === Most used accessors
10
+ #
11
+ # Here's a list of the probably most used accessors:
12
+ #
13
+ # * #title
14
+ # * #performer or #artist
15
+ # * #album
16
+ # * #genre
17
+ # * #year
18
+ # * #track
19
+ # * #part_of_set or #disc
20
+ # * #comment or #comment_frames
21
+ # * #composer
22
+ # * #bpm
23
+ #
24
+ # === Automatic conversion
25
+ #
26
+ # Some accessors automatically convert the raw text to a more
27
+ # suitable type (when reading) and back (when writing). For example,
28
+ # the track information is converted to an array of one or two items.
29
+ #
30
+ # tag.track #=> [1,12]
31
+ #
32
+ # and the year is an integer.
33
+ #
34
+ # tag.year #=> 2005
35
+ #
36
+ # Use the Tag#frame_text method to get the raw text.
37
+ #
38
+ # tag.frame_text(:TRCK) #=> "1/12"
39
+ #
40
+ module Accessors
41
+
42
+ def title() frame_text(:TIT2) end
43
+ def title=(v) set_frame_text(:TIT2, v) end
44
+
45
+ def performer() frame_text(:TPE1) end
46
+ def performer=(v) set_frame_text(:TPE1, v) end
47
+ alias_method :artist, :performer
48
+ alias_method :artist=, :performer=
49
+
50
+ def album() frame_text(:TALB) end
51
+ def album=(v) set_frame_text(:TALB, v) end
52
+
53
+ def comment() frame_text(:COMM) end
54
+ def comment=(v) set_frame_text(:COMM, v) end
55
+
56
+ def genre() frame_text(:TCON) end
57
+ def genre=(v) set_frame_text(:TCON, v) end
58
+ alias_method :content_type, :genre
59
+ alias_method :content_type=, :genre=
60
+
61
+ def bpm() frame_text(:TBPM) end
62
+ def bpm=(v) set_frame_text(:TBPM, v.to_s) end
63
+
64
+ def composer() frame_text(:TCOM) end
65
+ def composer=(v) set_frame_text(:TCOM, v) end
66
+
67
+ def copyright() frame_text(:TCOP) end
68
+ def copyright=(v) set_frame_text(:TCOP, v) end
69
+
70
+ def date() frame_text(:TDAT) end
71
+ def date=(v) set_frame_text(:TDAT, v) end
72
+
73
+ def encoding_time() frame_text(:TDEN) end
74
+ def encoding_time=(v) set_frame_text(:TDEN, v) end
75
+
76
+ def playlist_delay() frame_text(:TDLY) end
77
+ def playlist_delay=(v) set_frame_text(:TDLY, v) end
78
+
79
+ def original_release_time() frame_text(:TDOR) end
80
+ def original_release_time=(v) set_frame_text(:TDOR, v) end
81
+
82
+ def recording_time() frame_text(:TDRC) end
83
+ def recording_time=(v) set_frame_text(:TDRC, v) end
84
+
85
+ def release_time() frame_text(:TDRL) end
86
+ def release_time=(v) set_frame_text(:TDRL, v) end
87
+
88
+ def tagging_time() frame_text(:TDTG) end
89
+ def tagging_time=(v) set_frame_text(:TDTG, v) end
90
+
91
+ def involved_people() frame_text(:TIPL) end
92
+ def involved_people=(v) set_frame_text(:TIPL, v) end
93
+
94
+ def encoded_by() frame_text(:TENC) end
95
+ def encoded_by=(v) set_frame_text(:TENC, v) end
96
+
97
+ def lyricist() frame_text(:TEXT) end
98
+ def lyricist=(v) set_frame_text(:TEXT, v) end
99
+
100
+ def file_type() frame_text(:TFLT) end
101
+ def file_type=(v) set_frame_text(:TFLT, v) end
102
+
103
+ def time() frame_text(:TIME) end
104
+ def time=(v) set_frame_text(:TIME, v) end
105
+
106
+ def content_group() frame_text(:TIT1) end
107
+ def content_group=(v) set_frame_text(:TIT1, v) end
108
+
109
+ def subtitle() frame_text(:TIT3) end
110
+ def subtitle=(v) set_frame_text(:TIT3, v) end
111
+
112
+ def initial_key() frame_text(:TKEY) end
113
+ def initial_key=(v) set_frame_text(:TKEY, v) end
114
+
115
+ def language() frame_text(:TLAN) end
116
+ def language=(v) set_frame_text(:TLAN, v) end
117
+
118
+ def audio_length() frame_text(:TLEN) end
119
+ def audio_length=(v) set_frame_text(:TLEN, v) end
120
+
121
+ def musician_credits() frame_text(:TMCL) end
122
+ def musician_credits=(v) set_frame_text(:TMCL, v) end
123
+
124
+ def media_type() frame_text(:TMED) end
125
+ def media_type=(v) set_frame_text(:TMED, v) end
126
+
127
+ def mood() frame_text(:TMOO) end
128
+ def mood=(v) set_frame_text(:TMOO, v) end
129
+
130
+ def original_title() frame_text(:TOAL) end
131
+ def original_title=(v) set_frame_text(:TOAL, v) end
132
+
133
+ def original_filename() frame_text(:TOFN) end
134
+ def original_filename=(v) set_frame_text(:TOFN, v) end
135
+
136
+ def original_lyricist() frame_text(:TOLY) end
137
+ def original_lyricist=(v) set_frame_text(:TOLY, v) end
138
+
139
+ def original_performer() frame_text(:TOPE) end
140
+ def original_performer=(v) set_frame_text(:TOPE, v) end
141
+
142
+ def file_owner() frame_text(:TOWN) end
143
+ def file_owner=(v) set_frame_text(:TOWN, v) end
144
+
145
+ def band() frame_text(:TPE2) end
146
+ def band=(v) set_frame_text(:TPE2, v) end
147
+
148
+ def conductor() frame_text(:TPE3) end
149
+ def conductor=(v) set_frame_text(:TPE3, v) end
150
+
151
+ def produced_notice() frame_text(:TPRO) end
152
+ def produced_notice=(v) set_frame_text(:TPRO, v) end
153
+
154
+ def publisher() frame_text(:TPUB) end
155
+ def publisher=(v) set_frame_text(:TPUB, v) end
156
+
157
+ def internet_radio_station() frame_text(:TRSN) end
158
+ def internet_radio_station=(v) set_frame_text(:TRSN, v) end
159
+
160
+ def internet_radio_owner() frame_text(:TRSO) end
161
+ def internet_radio_owner=(v) set_frame_text(:TRSO, v) end
162
+
163
+ def album_sort_order() frame_text(:TSOA) end
164
+ def album_sort_order=(v) set_frame_text(:TSOA, v) end
165
+
166
+ def performer_sort_order() frame_text(:TSOP) end
167
+ def performer_sort_order=(v) set_frame_text(:TSOP, v) end
168
+
169
+ def title_sort_order() frame_text(:TSOT) end
170
+ def title_sort_order=(v) set_frame_text(:TSOT, v) end
171
+
172
+ def isrc() frame_text(:TSRC) end
173
+ def isrc=(v) set_frame_text(:TSRC, v) end
174
+
175
+ def encoder_settings() frame_text(:TSSE) end
176
+ def encoder_settings=(v) set_frame_text(:TSSE, v) end
177
+
178
+ def set_subtitle() frame_text(:TSST) end
179
+ def set_subtitle=(v) set_frame_text(:TSST, v) end
180
+
181
+ def terms_of_use() frame_text(:TPE2) end
182
+ def terms_of_use=(v) set_frame_text(:TPE2, v) end
183
+
184
+ def commercial_url() frame_text(:WCOM) end
185
+ def commercial_url=(v) set_frame_text(:WCOM, v) end
186
+
187
+ def copyright_url() frame_text(:WCOP) end
188
+ def copyright_url=(v) set_frame_text(:WCOP, v) end
189
+
190
+ def audio_file_url() frame_text(:WOAF) end
191
+ def audio_file_url=(v) set_frame_text(:WOAF, v) end
192
+
193
+ def performer_url() frame_text(:WOAR) end
194
+ def performer_url=(v) set_frame_text(:WOAR, v) end
195
+
196
+ def audio_source_url() frame_text(:WOAS) end
197
+ def audio_source_url=(v) set_frame_text(:WOAS, v) end
198
+
199
+ def internet_radio_url() frame_text(:WORS) end
200
+ def internet_radio_url=(v) set_frame_text(:WORS, v) end
201
+
202
+ def payment_url() frame_text(:WPAY) end
203
+ def payment_url=(v) set_frame_text(:WPAY, v) end
204
+
205
+ def publisher_url() frame_text(:WPUB) end
206
+ def publisher_url=(v) set_frame_text(:WPUB, v) end
207
+
208
+ # Returns an array of comment frames.
209
+ def comment_frames() select{ |f| f[:id] == :COMM } end
210
+
211
+ #
212
+ # Returns an array of one or two numbers.
213
+ #
214
+ # tag.track #=> [3,11]
215
+ # tag.frame_text(:TRCK) #=> "3/11"
216
+ #
217
+ # tag_2.track #=> [5]
218
+ # tag_2.frame_text(:TRCK) #=> "5"
219
+ #
220
+ def track() frame_text(:TRCK).split('/').collect{ |s| s.to_i } end
221
+
222
+ #
223
+ # Assign either an array or a string.
224
+ #
225
+ # tag.track = [4,11]
226
+ # tag.track = [5]
227
+ # tag.track = "4/11"
228
+ #
229
+ def track=(v) set_frame_text(:TRCK, (v.join('/') rescue v.to_s)) end
230
+
231
+ #
232
+ # Returns an array of one or two numbers like #track.
233
+ #
234
+ def part_of_set() frame_text(:TPOS).split('/').collect{ |s| s.to_i } end
235
+ alias_method :disc, :part_of_set
236
+
237
+ #
238
+ # Assign either an array or a string like #track=.
239
+ #
240
+ def part_of_set=(v) set_frame_text(:TPOS, (v.join('/') rescue v.to_s)) end
241
+ alias_method :disc=, :part_of_set=
242
+
243
+ #
244
+ # Returns a number.
245
+ #
246
+ # tag.year #=> 2004
247
+ #
248
+ def year() frame_text(:TYER).to_i end
249
+
250
+ #
251
+ # Assign a number or a string.
252
+ #
253
+ # tag.year = 2005
254
+ # tag.year = "2005"
255
+ #
256
+ def year=(v) set_frame_text(:TYER, v.to_s) end
257
+
258
+ end
259
+
260
+ end