id3lib-ruby 0.3.0-mswin32

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES ADDED
@@ -0,0 +1,28 @@
1
+ = id3lib-ruby changes
2
+
3
+ === 0.3.0 (r29)
4
+
5
+ * Added generation of mswin32 binary gem. This means that
6
+ installing on Windows is a piece of cake. No dependencies, no
7
+ compiling, just a gem installation.
8
+ * Changed Info.frame to use hash access instead of find -> faster.
9
+
10
+ === 0.2.1 (r23)
11
+
12
+ * Fixed extconf.rb to print a message and abort if id3lib can't be
13
+ found.
14
+
15
+ === 0.2.0 (r21)
16
+
17
+ * Overhauled direct access methods
18
+ * Remove frames by setting them to nil, e.g. tag.title = nil
19
+ * Removed methods for rarely used frames.
20
+ * All methods use strings now, no special cases like track anymore.
21
+ * Explicit call of to_s in set_frame_text.
22
+ * More unit tests
23
+ * Added method invalid_frames which is useful to detect invalid frame
24
+ data before calling update!.
25
+
26
+ === 0.1.0 (r19)
27
+
28
+ * First release :)
data/README ADDED
@@ -0,0 +1,95 @@
1
+
2
+ = id3lib-ruby
3
+
4
+ id3lib-ruby provides a Ruby interface to the id3lib C++ library for easily
5
+ editing ID3 tags (v1 and v2) like with pyid3lib.
6
+
7
+ The class documentation starts at ID3Lib::Tag.
8
+
9
+
10
+ == Features
11
+
12
+ * Read and write ID3v1 or ID3v2 tags
13
+ * Simple interface for adding, changing and removing frames
14
+ * Quick access to common text frames like title and performer
15
+ * Custom data frames like attached picture (APIC)
16
+ * Pretty complete coverage of id3lib's features
17
+ * UTF-16 support
18
+ * Windows binary gem available
19
+
20
+ See TODO for planned features.
21
+
22
+ The CHANGES file contains a list of changes between versions.
23
+
24
+
25
+ == Online Information
26
+
27
+ The home of id3lib-ruby is http://id3lib-ruby.rubyforge.org
28
+
29
+
30
+ == Installation
31
+
32
+ Note that id3lib has to be installed before running any of the following
33
+ commands, unless you use the Windows binary gem.
34
+
35
+ Installation through RubyGems:
36
+
37
+ gem install id3lib-ruby
38
+
39
+ Manual installation:
40
+
41
+ ruby setup.rb
42
+
43
+ == Usage
44
+
45
+ require 'rubygems'
46
+ require 'id3lib'
47
+
48
+ # Load a tag from a file
49
+ tag = ID3Lib::Tag.new('talk.mp3')
50
+
51
+ # Get and set text frames with convenience methods
52
+ tag.title #=> "Talk"
53
+ tag.album = 'X&Y'
54
+ tag.track = '5/13'
55
+
56
+ # Tag is a subclass of Array and each frame is a Hash
57
+ tag[0]
58
+ #=> { :id => :TPE1, :textenc => 0, :text => "Coldplay" }
59
+
60
+ # Get the number of frames
61
+ tag.length #=> 7
62
+
63
+ # Remove all comment frames
64
+ tag.delete_if{ |frame| frame[:id] == :COMM }
65
+
66
+ # Get info about APIC frame to see which fields are allowed
67
+ ID3Lib::Info.frame(:APIC)
68
+ #=> [ 2, :APIC, "Attached picture",
69
+ #=> [:textenc, :mimetype, :picturetype, :description, :data] ]
70
+
71
+ # Add an attached picture frame
72
+ cover = {
73
+ :id => :APIC,
74
+ :mimetype => 'image/jpeg',
75
+ :picturetype => 3,
76
+ :description => 'A pretty picture',
77
+ :textenc => 0,
78
+ :data => File.read('cover.jpg')
79
+ }
80
+ tag << cover
81
+
82
+ # Last but not least, apply changes
83
+ tag.update!
84
+
85
+
86
+ == Licence
87
+
88
+ This library has Ruby's licence:
89
+
90
+ http://www.ruby-lang.org/en/LICENSE.txt
91
+
92
+
93
+ == Author
94
+
95
+ Robin Stocker <robinstocker at rubyforge.org>
data/Rakefile ADDED
@@ -0,0 +1,119 @@
1
+
2
+ begin
3
+ require 'rubygems'
4
+ require 'rake/gempackagetask'
5
+ rescue Exception
6
+ nil
7
+ end
8
+
9
+ require 'rake/testtask'
10
+ require 'rake/rdoctask'
11
+
12
+
13
+ PKG_VERSION = '0.3.0'
14
+
15
+ PKG_COMMON = FileList[
16
+ 'lib/**/*.rb',
17
+ 'test/test_*.rb',
18
+ 'test/data/*.mp3',
19
+ 'test/data/cover.jpg',
20
+ 'Rakefile',
21
+ 'setup.rb'
22
+ ]
23
+
24
+
25
+ desc "Build extension."
26
+ task :ext do
27
+ sh "cd ext && rake"
28
+ puts "(end)"
29
+ end
30
+
31
+ desc "Build mswin32 extension."
32
+ task :ext_mswin32 do
33
+ sh 'cd ext/mswin32; rake'
34
+ puts "(end)"
35
+ end
36
+
37
+
38
+ Rake::TestTask.new do |t|
39
+ t.libs = ['lib', 'ext']
40
+ t.test_files = FileList['test/test_*.rb']
41
+ t.verbose = true
42
+ end
43
+
44
+
45
+ RDOC_OPTS = ['--line-numbers', '--main', 'README']
46
+
47
+ desc "Generate RDOC documentation."
48
+ Rake::RDocTask.new :rdoc do |rdoc|
49
+ rdoc.rdoc_dir = 'doc'
50
+ rdoc.title = 'id3lib-ruby'
51
+ rdoc.options = RDOC_OPTS
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ rdoc.rdoc_files.include('README', 'TODO', 'CHANGES')
54
+ end
55
+ task :doc => [:rdoc]
56
+
57
+
58
+ if defined? Gem
59
+ spec = Gem::Specification.new do |s|
60
+ s.name = 'id3lib-ruby'
61
+ s.version = PKG_VERSION
62
+ s.summary =
63
+ 'id3lib-ruby provides a Ruby interface to the id3lib C++ library for ' +
64
+ 'easily editing ID3 tags (v1 and v2) like with pyid3lib.'
65
+ s.requirements << 'id3lib C++ library'
66
+ s.files = PKG_COMMON + FileList['ext/extconf.rb', 'ext/*.cxx']
67
+ s.extensions = ['ext/extconf.rb']
68
+ s.test_files = FileList['test/test_*.rb']
69
+ s.has_rdoc = true
70
+ s.extra_rdoc_files = FileList['README', 'CHANGES', 'TODO']
71
+ s.rdoc_options = RDOC_OPTS
72
+ s.author = 'Robin Stocker'
73
+ s.email = 'robinstocker@rubyforge.org'
74
+ s.homepage = 'http://id3lib-ruby.rubyforge.org'
75
+ s.rubyforge_project = "id3lib-ruby"
76
+ end
77
+
78
+ Rake::GemPackageTask.new(spec) do |pkg|
79
+ pkg.need_tar_gz = true
80
+ pkg.need_zip = true
81
+ end
82
+
83
+ spec_mswin32 = spec.clone
84
+ spec_mswin32.files = PKG_COMMON + FileList['ext/mswin32/id3lib_api.so']
85
+ spec_mswin32.extensions = []
86
+ spec_mswin32.require_paths = ['lib', 'ext/mswin32']
87
+ spec_mswin32.platform = Gem::Platform::WIN32
88
+
89
+ desc "Build mswin32 gem."
90
+ task :gem_mswin32 => [:ext_mswin32] do
91
+ Gem::Builder.new(spec_mswin32).build
92
+ mkdir_p "pkg"
93
+ mv "id3lib-ruby-#{PKG_VERSION}-mswin32.gem", "pkg/"
94
+ end
95
+
96
+ end # defined? Gem
97
+
98
+
99
+ task :web => [:web_doc] do
100
+ puts "# Now execute the following:"
101
+ puts "scp web/* robinstocker@rubyforge.org:/var/www/gforge-projects/id3lib-ruby/"
102
+ puts "scp -r web/doc robinstocker@rubyforge.org:/var/www/gforge-projects/id3lib-ruby/doc"
103
+ end
104
+
105
+ desc "Generate RDOC documentation on web."
106
+ Rake::RDocTask.new :web_doc do |rdoc|
107
+ rdoc.rdoc_dir = 'web/doc'
108
+ rdoc.title = 'id3lib-ruby'
109
+ rdoc.options << '--line-numbers' << '--main' << 'ID3Lib::Tag'
110
+ rdoc.rdoc_files.include('README', 'TODO', 'CHANGES')
111
+ rdoc.rdoc_files.include('lib/**/*.rb')
112
+ end
113
+
114
+ task :usage_html do
115
+ require 'syntax/convertors/html'
116
+ convertor = Syntax::Convertors::HTML.for_syntax('ruby')
117
+ html = convertor.convert(File.read('usage.rb'))
118
+ puts html
119
+ end
data/TODO ADDED
@@ -0,0 +1,4 @@
1
+ = id3lib-ruby to-do list
2
+
3
+ * Evaluate a more object-oriented way to handle frames, instead of hashes.
4
+ * Add UTF-8 support if id3lib can handle it.
Binary file
data/lib/id3lib.rb ADDED
@@ -0,0 +1,384 @@
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
+ if text
217
+ self << { :id => id, :text => text.to_s }
218
+ end
219
+ end
220
+
221
+ #
222
+ # Remove all frames with the specified _id_.
223
+ #
224
+ def remove_frame(id)
225
+ delete_if{ |f| f[:id] == id }
226
+ end
227
+
228
+ #
229
+ # Updates the tag. This change can't be undone. _writetype_ specifies
230
+ # which tag type to write and defaults to _readtype_ (see #new).
231
+ #
232
+ # Invalid frames or frame data is ignored. Use #invalid_frames before
233
+ # update! if you want to know if you have invalid data.
234
+ #
235
+ # Returns a number corresponding to the written tag type(s) or nil if
236
+ # the update failed.
237
+ #
238
+ # tag.update!
239
+ # id3v1_tag.update!(ID3Lib::V1)
240
+ #
241
+ def update!(writetype=@readtype)
242
+ @tag.strip(writetype)
243
+ # The following two lines are necessary because of the weird
244
+ # behaviour of id3lib.
245
+ @tag.clear
246
+ @tag.link(@filename, writetype)
247
+
248
+ delete_if do |frame|
249
+ frame_info = Info.frame(frame[:id])
250
+ next true if not frame_info
251
+ libframe = API::Frame.new(frame_info[NUM])
252
+ Frame.write(frame, libframe)
253
+ @tag.add_frame(libframe)
254
+ false
255
+ end
256
+
257
+ @tag.set_padding(@padding)
258
+ tags = @tag.update(writetype)
259
+ return tags == 0 ? nil : tags
260
+ end
261
+
262
+ #
263
+ # Strip tag from file. This is dangerous because you lose all tag
264
+ # information. Specify _striptag_ to only strip a certain tag type.
265
+ # You don't have to call #update! after #strip!.
266
+ #
267
+ # tag.strip!
268
+ # another_tag.strip!(ID3Lib::V1)
269
+ #
270
+ def strip!(striptype=V_ALL)
271
+ clear
272
+ tags = @tag.strip(striptype)
273
+ @tag.clear
274
+ @tag.link(@filename, @readtype)
275
+ tags
276
+ end
277
+
278
+ #
279
+ # Check if there is a tag of type _type_.
280
+ #
281
+ def has_tag?(type=V2)
282
+ @tag.link(@filename, V_ALL)
283
+ @tag.has_tag_type(type)
284
+ end
285
+
286
+ #
287
+ # Returns an Array of invalid frames and fields. If a frame ID is
288
+ # invalid, it alone is in the resulting array. If a frame ID is valid
289
+ # but has invalid fields, the frame ID and the invalid field IDs are
290
+ # included.
291
+ #
292
+ # tag.invalid_frames
293
+ # #=> [ [:TITS], [:TALB, :invalid] ]
294
+ #
295
+ def invalid_frames
296
+ invalid = []
297
+ each do |frame|
298
+ if not info = Info.frame(frame[:id])
299
+ # Frame ID doesn't exist.
300
+ invalid << [frame[:id]]
301
+ next
302
+ end
303
+ # Frame ID is ok, but are all fields ok?
304
+ invalid_fields = frame.keys.reject { |id|
305
+ info[FIELDS].include?(id) or id == :id
306
+ }
307
+ if not invalid_fields.empty?
308
+ invalid << [frame[:id], *invalid_fields]
309
+ end
310
+ end
311
+ invalid.empty? ? nil : invalid
312
+ end
313
+
314
+ private
315
+
316
+ def read_frames
317
+ iterator = @tag.iterator_new
318
+ while libframe = @tag.iterator_next_frame(iterator)
319
+ self << Frame.read(libframe)
320
+ end
321
+ end
322
+
323
+ end
324
+
325
+
326
+ module Frame #:nodoc:
327
+
328
+ def self.read(libframe)
329
+ frame = {}
330
+ info = Info.frame_num(libframe.num)
331
+ frame[:id] = info[ID]
332
+ if info[FIELDS].include?(:textenc)
333
+ textenc = field(libframe, :textenc).integer
334
+ frame[:textenc] = textenc
335
+ end
336
+ info[FIELDS].each do |field_id|
337
+ next if field_id == :textenc
338
+ libfield = field(libframe, field_id)
339
+ frame[field_id] = if textenc and textenc > 0
340
+ libfield.unicode
341
+ else
342
+ case Info::FieldType[libfield.type]
343
+ when :integer : libfield.integer
344
+ when :binary : libfield.binary
345
+ when :text : libfield.ascii
346
+ end
347
+ end
348
+ end
349
+ frame
350
+ end
351
+
352
+ def self.write(frame, libframe)
353
+ if textenc = frame[:textenc]
354
+ field(libframe, :textenc).set_integer(textenc)
355
+ end
356
+ frame.each do |field_id, value|
357
+ next if field_id == :textenc
358
+ unless Info.frame(frame[:id])[FIELDS].include?(field_id)
359
+ # Ignore invalid fields
360
+ next
361
+ end
362
+ libfield = field(libframe, field_id)
363
+ if textenc and textenc > 0
364
+ # Special treatment for Unicode
365
+ libfield.set_encoding(textenc)
366
+ libfield.set_unicode(value)
367
+ else
368
+ case Info::FieldType[libfield.type]
369
+ when :integer : libfield.set_integer(value)
370
+ when :binary : libfield.set_binary(value)
371
+ when :text : libfield.set_ascii(value)
372
+ end
373
+ end
374
+ end
375
+ end
376
+
377
+ def self.field(libframe, id)
378
+ libframe.field(Info.field(id)[NUM])
379
+ end
380
+
381
+ end
382
+
383
+
384
+ end