ruby-mp3info 0.7.2 → 0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +6 -0
- data/Manifest.txt +1 -1
- data/README.md +97 -0
- data/lib/mp3info.rb +3 -3
- data/lib/mp3info/id3v2.rb +138 -0
- data/test/test_ruby-mp3info.rb +33 -0
- metadata +6 -9
- data/README.txt +0 -82
data/History.txt
CHANGED
@@ -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
|
data/Manifest.txt
CHANGED
data/README.md
ADDED
@@ -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...)
|
data/lib/mp3info.rb
CHANGED
@@ -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.
|
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.
|
474
|
+
s << "tag2: "+@tag2.to_inspect_hash.inspect+"\n" if hastag2?
|
475
475
|
s
|
476
476
|
end
|
477
477
|
|
data/lib/mp3info/id3v2.rb
CHANGED
@@ -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
|
|
data/test/test_ruby-mp3info.rb
CHANGED
@@ -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.
|
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:
|
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.
|
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.
|
46
|
-
description:
|
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.
|
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...)
|