id3lib-ruby 0.3.0-mswin32

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/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