ruby-mp3info 0.7.2 → 0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,9 @@
1
+ === 0.8 / 2013-01-28
2
+
3
+ * allow higher level of reading and writing to APIC tag:
4
+ * tag2.pictures, tag2.remove_pictures, tag2.add_picture
5
+ * added "<<...snip...>>>" to shorten APIC tag when Hash.inspect called (not a shallow copy hash, deep copied hash so does not effect data) tag2.inspect
6
+
1
7
  === 0.7.2 / 2012-12-24
2
8
 
3
9
  * do not try to commit changes to IO or StringIO
@@ -1,6 +1,6 @@
1
1
  History.txt
2
2
  Manifest.txt
3
- README.txt
3
+ README.md
4
4
  Rakefile
5
5
  lib/mp3info.rb
6
6
  lib/mp3info/extension_modules.rb
@@ -0,0 +1,97 @@
1
+ # ruby-mp3info
2
+
3
+ * http://github.com/moumar/ruby-mp3info
4
+
5
+ ## Description
6
+
7
+ ruby-mp3info read low-level informations and manipulate tags on mp3 files.
8
+
9
+ ## Features/Problems
10
+
11
+ * Written in pure ruby
12
+ * Read low-level informations like bitrate, length, samplerate, etc...
13
+ * Read, write, remove id3v1 and id3v2 tags
14
+ * Correctly read VBR files (with or without Xing header)
15
+ * Only 2.3 version is supported for writings id3v2 tags
16
+ * id3v2 tags are always written in UTF-16 encoding
17
+
18
+ ## Synopsis
19
+
20
+ ```ruby
21
+ require "mp3info"
22
+
23
+ # Read and display infos & tags
24
+
25
+ Mp3Info.open("myfile.mp3") do |mp3info|
26
+ puts mp3info
27
+ end
28
+
29
+ # read/write tag1 and tag2 with Mp3Info#tag attribute
30
+ # when reading tag2 have priority over tag1
31
+ # when writing, each tag is written.
32
+
33
+ Mp3Info.open("myfile.mp3") do |mp3|
34
+ puts mp3.tag.title
35
+ puts mp3.tag.artist
36
+ puts mp3.tag.album
37
+ puts mp3.tag.tracknum
38
+ mp3.tag.title = "track title"
39
+ mp3.tag.artist = "artist name"
40
+ end
41
+
42
+ # tags are written when calling Mp3Info#close or at the end of the #open block
43
+
44
+ ### access id3v2 tags
45
+
46
+ Mp3Info.open("myfile.mp3") do |mp3|
47
+ # you can access four letter v2 tags like this
48
+ puts mp3.tag2.TIT2
49
+ mp3.tag2.TIT2 = "new TIT2"
50
+ # or like that
51
+ mp3.tag2["TIT2"]
52
+ # at this time, only COMM tag is processed after reading and before writing
53
+ # according to ID3v2#options hash
54
+ mp3.tag2.options[:lang] = "FRE"
55
+ mp3.tag2.COMM = "my comment in french, correctly handled when reading and writing"
56
+ end
57
+
58
+ ### image manipulation
59
+
60
+ file = File.new('input_img','rb')
61
+ Mp3Info.open '1.mp3' do |m|
62
+ pictures = m.tag2.get_pictures # array of images :
63
+ pictures.each do |description, data|
64
+ File.open(description, "wb") { |f| f.write data }
65
+ end
66
+ m.tag2.remove_pictures # remove all images
67
+ m.tag2.add_picture(file.read) # optionally, you may include options for the header:
68
+ # options = { mime: 'gif', description: "description",
69
+ # pic_type: 0 }
70
+ end
71
+ ```
72
+
73
+ ## Requirements
74
+
75
+ * iconv when using ruby 1.8
76
+
77
+ ## Install
78
+
79
+ gem install ruby-mp3info
80
+
81
+ ## Developers
82
+
83
+ After checking out the source, run:
84
+
85
+ $ rake newb
86
+
87
+ This task will install any missing dependencies, run the tests/specs, and generate the RDoc.
88
+
89
+ ## License
90
+
91
+ ruby
92
+
93
+ ## TODO:
94
+
95
+ * encoder detection
96
+ * support for more tags in id3v2
97
+ * generalize id3v2 with other audio formats (APE, MPC, OGG, etc...)
@@ -5,8 +5,8 @@
5
5
 
6
6
  require "fileutils"
7
7
  require "stringio"
8
- require "mp3info/extension_modules"
9
8
  require "mp3info/id3v2"
9
+ require "mp3info/extension_modules"
10
10
 
11
11
  # ruby -d to display debugging infos
12
12
 
@@ -18,7 +18,7 @@ end
18
18
 
19
19
  class Mp3Info
20
20
 
21
- VERSION = "0.7.2"
21
+ VERSION = "0.8"
22
22
 
23
23
  LAYER = [ nil, 3, 2, 1]
24
24
  BITRATE = {
@@ -471,7 +471,7 @@ class Mp3Info
471
471
  def to_s
472
472
  s = "MPEG #{@mpeg_version} Layer #{@layer} #{@vbr ? "VBR" : "CBR"} #{@bitrate} Kbps #{@channel_mode} #{@samplerate} Hz length #{@length} sec. header #{@header.inspect} "
473
473
  s << "tag1: "+@tag1.to_hash.inspect+"\n" if hastag1?
474
- s << "tag2: "+@tag2.to_hash.inspect+"\n" if hastag2?
474
+ s << "tag2: "+@tag2.to_inspect_hash.inspect+"\n" if hastag2?
475
475
  s
476
476
  end
477
477
 
@@ -217,6 +217,136 @@ class ID3v2 < DelegateClass(Hash)
217
217
  end
218
218
  end
219
219
 
220
+ ### cuts out long tag values from hash for display on screen
221
+ def to_inspect_hash
222
+ result = Marshal.load(Marshal.dump(self.to_hash))
223
+ result.each do |k,v|
224
+ if v.is_a? Array
225
+ v.each_index do |i, item|
226
+ if (v[i].is_a? String and v[i].length > 128)
227
+ result[k][i] = pretty_header(v[i])
228
+ end
229
+ end
230
+ elsif v.is_a? String and v.length > 128
231
+ result[k] = pretty_header(v) # this method 'snips' long data
232
+ end
233
+ end
234
+ result
235
+ end
236
+
237
+ ### ID3V2::add_picture
238
+ ### Takes an image string as input and writes it with header.
239
+ ### Mime type is automatically guessed by default.
240
+ ### It is possible but not necessary to include:
241
+ ### :pic_type => 0 - 14 (see http://id3.org/id3v2.3.0#Attached_picture)
242
+ ### :mime => 'gif'
243
+ ### :description => "Image description"
244
+ def add_picture(data, opts = {})
245
+ options = {
246
+ :pic_type => 0,
247
+ :mime => nil,
248
+ :description => "image"
249
+ }
250
+ options.update(opts)
251
+ jpg = Regexp.new( "^\xFF".force_encoding("BINARY"),
252
+ Regexp::FIXEDENCODING )
253
+ png = Regexp.new( "^\x89PNG".force_encoding("BINARY"),
254
+ Regexp::FIXEDENCODING )
255
+ gif = Regexp.new( "^\x89GIF".force_encoding("BINARY"),
256
+ Regexp::FIXEDENCODING )
257
+
258
+ mime = options[:mime]
259
+ mime ||= "jpg" if data.match jpg
260
+ mime ||= "png" if data.match png
261
+ mime ||= "gif" if data.match gif
262
+ pic_type = options[:pic_type]
263
+ pic_type = ["%02i" % pic_type].pack('H*')
264
+ desc = "#{options[:description]}"
265
+ header = "\x00image/#{mime}\x00#{pic_type}#{desc}\x00"
266
+ self["APIC"] = header + data.force_encoding('BINARY')
267
+ end
268
+
269
+ ### Returns an array of images:
270
+ ### [ ["01_.jpg", "Image Data in Binary String"],
271
+ ### ["02_.png", "Another Image in a String"] ]
272
+ ###
273
+ ### e.g. to write all images:
274
+ ### mp3.tag2.pictures.each do |image|
275
+ ### File.open(img[0], 'wb'){|f| f.write img[1])
276
+ ### end
277
+ def pictures
278
+ apic_images = [self["APIC"]].flatten.dup
279
+ result = []
280
+ apic_images.each_index do |index|
281
+ pic = apic_images[index]
282
+ next if !pic.is_a?(String) or pic == ""
283
+ pic.force_encoding 'BINARY'
284
+ picture = []
285
+ jpg = Regexp.new("jpg|JPG|jpeg|JPEG".force_encoding("BINARY"),
286
+ Regexp::FIXEDENCODING )
287
+ png = Regexp.new("png|PNG".force_encoding("BINARY"),
288
+ Regexp::FIXEDENCODING )
289
+ header = pic.unpack('a120').first.force_encoding "BINARY"
290
+ mime_pos = 0
291
+
292
+ # safest way to correctly extract jpg and png is finding mime
293
+ if header.match jpg
294
+ mime = "jpg"
295
+ mime_pos = header =~ jpg
296
+ start = Regexp.new("^\377".force_encoding("BINARY"),
297
+ Regexp::FIXEDENCODING )
298
+ elsif header.match png
299
+ mime = "png"
300
+ mime_pos = header =~ png
301
+ start = Regexp.new("^\x89PNG".force_encoding("BINARY"),
302
+ Regexp::FIXEDENCODING )
303
+ else
304
+ mime = "dat"
305
+ end
306
+
307
+ puts "analysing image: #{header.inspect}..." if $DEBUG
308
+ mim, pic_type, desc, data = pic[mime_pos, pic.length].unpack('Z*hZ*a*')
309
+
310
+ if mime != "dat" and (!data.match(start) or data.nil?)
311
+ real_start = pic =~ start
312
+ data = pic[real_start, pic.length]
313
+ end
314
+
315
+ if mime == "dat"
316
+ # if no jpg or png, extract data anyway e.g. gif
317
+ mime, desc, data = pic.unpack('h Z* h Z* a*').values_at(1,3,4)
318
+ end
319
+
320
+ if mime == "jpg"
321
+ # inspect jpg image header (first 10 chars) for "\xFF\x00" (expect "\xFF")
322
+ trailing_null_byte = Regexp.new("(\377)(\000)".force_encoding('BINARY'),
323
+ Regexp::FIXEDENCODING)
324
+ if (data =~ trailing_null_byte) < 10
325
+ data.gsub!(trailing_null_byte, "\xff".force_encoding('BINARY'))
326
+ end
327
+ end
328
+
329
+ desc = "%02i_#{desc[0,25]}" % (index + 1)
330
+
331
+ filename = desc.match("#{mime}$") ? desc : "#{desc}.#{mime}"
332
+ filename.gsub!('/','')
333
+
334
+ picture[0] = filename
335
+ picture[1] = data
336
+ result << picture
337
+ end
338
+ result
339
+ end
340
+
341
+ def inspect
342
+ self.to_inspect_hash
343
+ end
344
+
345
+ def remove_pictures
346
+ self.APIC = ""
347
+ self.PIC = ""
348
+ end
349
+
220
350
  ### gets id3v2 tag information from io object (must support #seek() method)
221
351
  def from_io(io)
222
352
  @io = io
@@ -461,6 +591,14 @@ class ID3v2 < DelegateClass(Hash)
461
591
  def to_syncsafe(num)
462
592
  ( (num<<3) & 0x7f000000 ) + ( (num<<2) & 0x7f0000 ) + ( (num<<1) & 0x7f00 ) + ( num & 0x7f )
463
593
  end
594
+
595
+ ### this is especially useful for printing out APIC data because
596
+ ### only the header of the APIC tag is of interest
597
+ ### The result also shows some bytes escaped for cleaner display
598
+ def pretty_header(str, chars=128)
599
+ "#{str.unpack("a#{chars}").first}<<<...snip...>>>".force_encoding('BINARY').inspect[1..-2]
600
+ end
601
+
464
602
 
465
603
  end
466
604
 
@@ -189,6 +189,39 @@ class Mp3InfoTest < Test::Unit::TestCase
189
189
  assert_equal( "2.3.0", written_tag.version )
190
190
  end
191
191
 
192
+ # test for good output of APIC tag inspect (escaped and snipped)
193
+ def test_id3v2_to_inspect_hash
194
+ Mp3Info.open(TEMP_FILE) do |mp3|
195
+ mp3.tag2.APIC = "\x00testing.jpg\x00" * 20
196
+ escaped_str = "\\x00testing.jpg\\x00" * 20
197
+ snipped_str = escaped_str.unpack('a185').first + "<<<...snip...>>>"
198
+ assert_equal(snipped_str, mp3.tag2.to_inspect_hash["APIC"])
199
+ end
200
+ end
201
+
202
+ def test_id3v2_get_pictures
203
+ img = "\x89PNG".force_encoding('BINARY') +
204
+ random_string(120).force_encoding('BINARY')
205
+ Mp3Info.open(TEMP_FILE) do |mp3|
206
+ mp3.tag2.add_picture(img, :description => 'example image.png')
207
+ end
208
+ Mp3Info.open(TEMP_FILE) do |mp3|
209
+ assert_equal(["01_example image.png", img], mp3.tag2.pictures[0])
210
+ end
211
+ end
212
+
213
+ def test_id3v2_remove_pictures
214
+ jpg_data = "\xFF".force_encoding('BINARY') +
215
+ random_string(123).force_encoding('BINARY')
216
+ Mp3Info.open(TEMP_FILE) do |mp|
217
+ mp.tag2.add_picture(jpg_data)
218
+ end
219
+ Mp3Info.open(TEMP_FILE) do |mp|
220
+ mp.tag2.remove_pictures
221
+ assert_equal([], mp.tag2.pictures)
222
+ end
223
+ end
224
+
192
225
  def test_id3v2_methods
193
226
  tag = { "TIT2" => "tit2", "TPE1" => "tpe1" }
194
227
  Mp3Info.open(TEMP_FILE) do |mp3|
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-mp3info
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.2
4
+ version: '0.8'
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-25 00:00:00.000000000 Z
12
+ date: 2013-01-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: hoe-yard
@@ -34,7 +34,7 @@ dependencies:
34
34
  requirements:
35
35
  - - ~>
36
36
  - !ruby/object:Gem::Version
37
- version: '3.4'
37
+ version: '3.5'
38
38
  type: :development
39
39
  prerelease: false
40
40
  version_requirements: !ruby/object:Gem::Requirement
@@ -42,10 +42,8 @@ dependencies:
42
42
  requirements:
43
43
  - - ~>
44
44
  - !ruby/object:Gem::Version
45
- version: '3.4'
46
- description: ! 'ruby-mp3info read low-level informations and manipulate tags on
47
-
48
- mp3 files.'
45
+ version: '3.5'
46
+ description: ruby-mp3info read low-level informations and manipulate tags on mp3 files.
49
47
  email:
50
48
  - guillaume.pierronnet@gmail.com
51
49
  executables: []
@@ -53,11 +51,10 @@ extensions: []
53
51
  extra_rdoc_files:
54
52
  - History.txt
55
53
  - Manifest.txt
56
- - README.txt
57
54
  files:
58
55
  - History.txt
59
56
  - Manifest.txt
60
- - README.txt
57
+ - README.md
61
58
  - Rakefile
62
59
  - lib/mp3info.rb
63
60
  - lib/mp3info/extension_modules.rb
data/README.txt DELETED
@@ -1,82 +0,0 @@
1
- = ruby-mp3info
2
-
3
- * http://github.com/moumar/ruby-mp3info
4
-
5
- == DESCRIPTION:
6
-
7
- ruby-mp3info read low-level informations and manipulate tags on
8
- mp3 files.
9
-
10
- == FEATURES/PROBLEMS:
11
-
12
- * written in pure ruby
13
- * read low-level informations like bitrate, length, samplerate, etc...
14
- * read, write, remove id3v1 and id3v2 tags
15
- * correctly read VBR files (with or without Xing header)
16
- * only 2.3 version is supported for writings id3v2 tags
17
- * id3v2 tags are always written in UTF-16 encoding
18
-
19
- == SYNOPSIS:
20
-
21
- ### read and display infos & tags
22
-
23
- require "mp3info"
24
- # read and display infos & tags
25
- Mp3Info.open("myfile.mp3") do |mp3info|
26
- puts mp3info
27
- end
28
-
29
- # read/write tag1 and tag2 with Mp3Info#tag attribute
30
- # when reading tag2 have priority over tag1
31
- # when writing, each tag is written.
32
- Mp3Info.open("myfile.mp3") do |mp3|
33
- puts mp3.tag.title
34
- puts mp3.tag.artist
35
- puts mp3.tag.album
36
- puts mp3.tag.tracknum
37
- mp3.tag.title = "track title"
38
- mp3.tag.artist = "artist name"
39
- end
40
-
41
- # tags are written when calling Mp3Info#close or at the end of the #open block
42
-
43
- ### access id3v2 tags
44
-
45
- Mp3Info.open("myfile.mp3") do |mp3|
46
- # you can access four letter v2 tags like this
47
- puts mp3.tag2.TIT2
48
- mp3.tag2.TIT2 = "new TIT2"
49
- # or like that
50
- mp3.tag2["TIT2"]
51
- # at this time, only COMM tag is processed after reading and before writing
52
- # according to ID3v2#options hash
53
- mp3.tag2.options[:lang] = "FRE"
54
- mp3.tag2.COMM = "my comment in french, correctly handled when reading and writing"
55
- end
56
-
57
- == REQUIREMENTS:
58
-
59
- * iconv when using ruby 1.8
60
-
61
- == INSTALL:
62
-
63
- * gem install ruby-mp3info
64
-
65
- == DEVELOPERS:
66
-
67
- After checking out the source, run:
68
-
69
- $ rake newb
70
-
71
- This task will install any missing dependencies, run the tests/specs,
72
- and generate the RDoc.
73
-
74
- == LICENSE:
75
-
76
- ruby
77
-
78
- == TODO:
79
-
80
- * encoder detection
81
- * support for more tags in id3v2
82
- * generalize id3v2 with other audio formats (APE, MPC, OGG, etc...)