ruby-mp3info 0.7.2 → 0.8
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/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...)
|