mp3file 1.0.3 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/bin/mp3info +25 -13
- data/lib/mp3file/id3v2/frame_header.rb +3 -1
- data/lib/mp3file/id3v2/tag.rb +7 -4
- data/lib/mp3file/mp3_file.rb +45 -16
- data/lib/mp3file/version.rb +1 -1
- data/lib/mp3file/xing_header.rb +2 -1
- data/mp3file.gemspec +2 -0
- metadata +16 -3
- data/Gemfile.lock +0 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0bfcaa237ab4362c6e5ff6267f84c6becd95aab3
|
4
|
+
data.tar.gz: 3d86a90044fbf1ed37cf558a660e464993f71ec8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 13e2cf72a0f6db63c9676d3a78eeed4345bd01e9cfb7487d1b24a864cf9ff35c9a4c945bf3890c97c269efb13ef0125177ee89902d2b5f8eb94f5c798faf36c2
|
7
|
+
data.tar.gz: e483622cdc9d209500c82809fb619f19e8ef0caa454c3ff209923d66438568b173c0d39cbe67c2852d16650f2b690b7b23b0c5b61a7e1d81c95bd4190fe1b16a
|
data/.gitignore
CHANGED
data/bin/mp3info
CHANGED
@@ -3,21 +3,33 @@
|
|
3
3
|
$LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
4
4
|
require 'mp3file'
|
5
5
|
|
6
|
+
def humanize(num, places = 2)
|
7
|
+
if num.is_a?(Float)
|
8
|
+
parts = ("%.#{places}f" % [ num ]).split(".")
|
9
|
+
else
|
10
|
+
parts = num.to_s.to_str.split(".")
|
11
|
+
end
|
12
|
+
|
13
|
+
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1,")
|
14
|
+
parts.join(".")
|
15
|
+
end
|
16
|
+
alias :h :humanize
|
17
|
+
|
6
18
|
ARGV.each do |file|
|
7
19
|
begin
|
8
|
-
mp3 = Mp3file::MP3File.new(file)
|
20
|
+
mp3 = Mp3file::MP3File.new(file, scan_all_headers: true)
|
9
21
|
|
10
22
|
puts("File: #{mp3.file.path}")
|
11
|
-
puts(" Size: #{mp3.file_size} bytes Audio_size: #{mp3.audio_size} bytes")
|
23
|
+
puts(" Size: #{h(mp3.file_size)} bytes Audio_size: #{h(mp3.audio_size)} bytes")
|
12
24
|
puts(" #{mp3.mpeg_version} #{mp3.layer}")
|
13
|
-
puts(" Bitrate: #{mp3.bitrate}
|
14
|
-
puts(" Duration: #{mp3.length} s")
|
15
|
-
puts(" Frames: #{mp3.num_frames} Samples: #{mp3.total_samples}")
|
25
|
+
puts(" Bitrate: #{h(mp3.bitrate)} kbits/sec (vbr? #{mp3.vbr?.to_s}) Samplerate: #{mp3.samplerate} Hz Mode: #{mp3.mode}")
|
26
|
+
puts(" Duration: #{h(mp3.length)} s")
|
27
|
+
puts(" Frames: #{h(mp3.num_frames)} Samples: #{h(mp3.total_samples)}")
|
16
28
|
|
17
29
|
header = mp3.first_header
|
18
|
-
puts(" First MPEG frame at #{mp3.first_header_offset} bytes:")
|
30
|
+
puts(" First MPEG frame at #{h(mp3.first_header_offset)} bytes:")
|
19
31
|
puts(" #{header.version} #{header.layer}")
|
20
|
-
puts(" Bitrate: #{header.bitrate} bps Samplerate: #{header.samplerate} Hz")
|
32
|
+
puts(" Bitrate: #{header.bitrate} bps Samplerate: #{h(header.samplerate)} Hz")
|
21
33
|
puts(" Mode: #{header.mode} Mode Extension: #{header.mode_extension} Emphasis: #{header.emphasis}")
|
22
34
|
puts(" CRC: #{header.has_crc} Padding: #{header.has_padding}")
|
23
35
|
puts(" Copyright: #{header.copyright} Original: #{header.original}")
|
@@ -26,7 +38,7 @@ ARGV.each do |file|
|
|
26
38
|
if mp3.xing_header
|
27
39
|
xing = mp3.xing_header
|
28
40
|
puts(" Xing header:")
|
29
|
-
puts(" Frames: #{xing.frames} Bytes: #{xing.bytes} Quality: #{xing.quality} TOC: #{xing.toc ? 'present' : 'not present'}")
|
41
|
+
puts(" Frames: #{h(xing.frames)} Bytes: #{h(xing.bytes)} Quality: #{xing.quality} TOC: #{xing.toc ? 'present' : 'not present'}")
|
30
42
|
end
|
31
43
|
|
32
44
|
if mp3.vbri_header
|
@@ -47,13 +59,13 @@ ARGV.each do |file|
|
|
47
59
|
if mp3.id3v2tag?
|
48
60
|
tag = mp3.id3v2_tag
|
49
61
|
puts(" ID3v2 tag:")
|
50
|
-
puts(" Size: #{tag.size} bytes")
|
62
|
+
puts(" Size: #{h(tag.size)} bytes")
|
51
63
|
puts(" Version: #{tag.version.inspect}")
|
52
|
-
puts(" Unused byes: #{tag.unused_bytes
|
64
|
+
puts(" Unused byes: #{h(tag.unused_bytes)}")
|
53
65
|
if !tag.frames.empty?
|
54
66
|
puts(" Frames:")
|
55
67
|
tag.frames.each do |frame|
|
56
|
-
puts(" ID: #{frame.frame_id.inspect} size: #{frame.frame_size}")
|
68
|
+
puts(" ID: #{frame.frame_id.inspect} size: #{h(frame.frame_size)}")
|
57
69
|
end
|
58
70
|
end
|
59
71
|
end
|
@@ -63,11 +75,11 @@ ARGV.each do |file|
|
|
63
75
|
puts(" Offset: #{offset} bytes")
|
64
76
|
puts(" Size: #{tag2.size} bytes")
|
65
77
|
puts(" Version: #{tag2.version.inspect}")
|
66
|
-
puts(" Unused byes: #{tag2.unused_bytes
|
78
|
+
puts(" Unused byes: #{h(tag2.unused_bytes)}")
|
67
79
|
if !tag2.frames.empty?
|
68
80
|
puts(" Frames:")
|
69
81
|
tag2.frames.each do |frame|
|
70
|
-
puts(" ID: #{frame.frame_id.inspect} size: #{frame.frame_size}")
|
82
|
+
puts(" ID: #{frame.frame_id.inspect} size: #{h(frame.frame_size)}")
|
71
83
|
end
|
72
84
|
end
|
73
85
|
end
|
@@ -61,6 +61,7 @@ module Mp3file::ID3v2
|
|
61
61
|
if @tag.version >= ID3V2_2_0 && @tag.version < ID3V2_3_0
|
62
62
|
header = ID3v220FrameHeaderFormat.read(io)
|
63
63
|
@header_size = 6
|
64
|
+
@frame_size = header.frame_size
|
64
65
|
elsif @tag.version >= ID3V2_3_0 && @tag.version < ID3V2_4_0
|
65
66
|
header = ID3v230FrameHeaderFormat.read(io)
|
66
67
|
@header_size = 10
|
@@ -75,16 +76,17 @@ module Mp3file::ID3v2
|
|
75
76
|
if header.has_group == 1
|
76
77
|
@group = header.group_id
|
77
78
|
end
|
79
|
+
@frame_size = header.frame_size
|
78
80
|
elsif @tag.version >= ID3V2_4_0
|
79
81
|
header = ID3v240FrameHeaderFormat.read(io)
|
80
82
|
@header_size = 10
|
83
|
+
@frame_size = BitPaddedInt.unpad_number(header.frame_size)
|
81
84
|
end
|
82
85
|
rescue BinData::ValidityError => ve
|
83
86
|
raise InvalidID3v2TagError, ve.message
|
84
87
|
end
|
85
88
|
|
86
89
|
@frame_id = header.frame_id
|
87
|
-
@frame_size = BitPaddedInt.unpad_number(header.frame_size)
|
88
90
|
@size = @header_size + @frame_size
|
89
91
|
end
|
90
92
|
end
|
data/lib/mp3file/id3v2/tag.rb
CHANGED
@@ -25,11 +25,14 @@ module Mp3file::ID3v2
|
|
25
25
|
data.force_encoding("ASCII-8BIT")
|
26
26
|
offset = 0
|
27
27
|
|
28
|
-
|
29
|
-
while frame
|
30
|
-
@frames << frame
|
31
|
-
offset = frame_offset + frame.size
|
28
|
+
loop do
|
32
29
|
frame_offset, frame = get_next_frame_header(data, offset)
|
30
|
+
if frame
|
31
|
+
@frames << frame
|
32
|
+
offset = frame_offset + frame.size
|
33
|
+
else
|
34
|
+
break
|
35
|
+
end
|
33
36
|
end
|
34
37
|
end
|
35
38
|
|
data/lib/mp3file/mp3_file.rb
CHANGED
@@ -11,9 +11,9 @@ module Mp3file
|
|
11
11
|
|
12
12
|
attr_accessor(:id3v1_tag, :id3v2_tag, :extra_id3v2_tags)
|
13
13
|
|
14
|
-
def initialize(file_path)
|
14
|
+
def initialize(file_path, options = {})
|
15
15
|
file_path = Pathname.new(file_path).expand_path if file_path.is_a?(String)
|
16
|
-
load_file(file_path)
|
16
|
+
load_file(file_path, options)
|
17
17
|
end
|
18
18
|
|
19
19
|
def vbr?
|
@@ -82,7 +82,9 @@ module Mp3file
|
|
82
82
|
end
|
83
83
|
end
|
84
84
|
|
85
|
-
def load_file(file_path)
|
85
|
+
def load_file(file_path, options = {})
|
86
|
+
scan_all_headers = %w{ t true 1 }.include?(options[:scan_all_headers].to_s)
|
87
|
+
|
86
88
|
@file = file_path.open('rb')
|
87
89
|
@file.seek(0, IO::SEEK_END)
|
88
90
|
@file_size = @file.tell
|
@@ -150,8 +152,8 @@ module Mp3file
|
|
150
152
|
if @id3v1_tag
|
151
153
|
@audio_size -= 128
|
152
154
|
end
|
153
|
-
if @
|
154
|
-
@audio_size -=
|
155
|
+
if @id3v2_tag
|
156
|
+
@audio_size -= @id3v2_tag.size
|
155
157
|
end
|
156
158
|
|
157
159
|
# If it's VBR, there should be an Xing header after the
|
@@ -164,22 +166,43 @@ module Mp3file
|
|
164
166
|
@file.seek(@first_header_offset + 4, IO::SEEK_CUR)
|
165
167
|
end
|
166
168
|
|
167
|
-
if
|
168
|
-
|
169
|
-
#
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
169
|
+
if scan_all_headers
|
170
|
+
# Loop through all the frame headers, to check for VBR / CBR (as
|
171
|
+
# a Xing header can show up in either case).
|
172
|
+
frame_headers = [ @first_header ]
|
173
|
+
last_header_offset = @first_header_offset
|
174
|
+
loop do
|
175
|
+
file.seek(last_header_offset + frame_headers.last.frame_size)
|
176
|
+
last_header_offset, header = get_next_header(file)
|
177
|
+
if header.nil?
|
178
|
+
break
|
179
|
+
else
|
180
|
+
frame_headers << header
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
uniq_brs = frame_headers.map { |h| h.bitrate }.uniq
|
185
|
+
@vbr = uniq_brs.size > 1
|
186
|
+
if uniq_brs.size == 1
|
187
|
+
@bitrate = uniq_brs.first / 1000
|
176
188
|
end
|
189
|
+
else
|
190
|
+
# Use the Xing header to make the VBR / CBR call. Assume that
|
191
|
+
# Xing headers, when present in a CBR file, are called "Info".
|
192
|
+
@vbr = @xing_header.nil? || @xing_header.name == "Xing"
|
193
|
+
end
|
194
|
+
|
195
|
+
if @xing_header && @xing_header.frames && @xing_header.bytes
|
196
|
+
# Use the Xing header to calculate the duration (and overall bitrate).
|
197
|
+
@num_frames = @xing_header.frames
|
198
|
+
@total_samples = @xing_header.frames * @first_header.samples
|
199
|
+
@length = total_samples.to_f / @samplerate.to_f
|
200
|
+
@bitrate = ((@xing_header.bytes.to_f / @length.to_f) * 8 / 1000)
|
177
201
|
else
|
178
202
|
# Do the CBR length calculation.
|
179
|
-
@vbr = false
|
180
203
|
@num_frames = @audio_size / @first_header.frame_size
|
181
204
|
@total_samples = @num_frames * @first_header.samples
|
182
|
-
@length = @total_samples / @samplerate
|
205
|
+
@length = @total_samples.to_f / @samplerate.to_f
|
183
206
|
end
|
184
207
|
|
185
208
|
@file.close
|
@@ -206,6 +229,8 @@ module Mp3file
|
|
206
229
|
file.seek(header_offset, IO::SEEK_SET)
|
207
230
|
retry
|
208
231
|
end
|
232
|
+
rescue EOFError
|
233
|
+
break
|
209
234
|
end
|
210
235
|
|
211
236
|
# byte = file.readbyte
|
@@ -226,6 +251,10 @@ module Mp3file
|
|
226
251
|
# end
|
227
252
|
end
|
228
253
|
|
254
|
+
# if initial_header_offset != header_offset
|
255
|
+
# puts "Had to skip past #{header_offset - initial_header_offset} to find the next header."
|
256
|
+
# end
|
257
|
+
|
229
258
|
[ header_offset, header ]
|
230
259
|
end
|
231
260
|
end
|
data/lib/mp3file/version.rb
CHANGED
data/lib/mp3file/xing_header.rb
CHANGED
@@ -2,7 +2,7 @@ module Mp3file
|
|
2
2
|
class InvalidXingHeaderError < Mp3fileError; end
|
3
3
|
|
4
4
|
class XingHeader
|
5
|
-
attr_reader(:frames, :bytes, :toc, :quality)
|
5
|
+
attr_reader(:name, :frames, :bytes, :toc, :quality)
|
6
6
|
|
7
7
|
class XingHeaderFormat < BinData::Record
|
8
8
|
string(:vbr_id, :length => 4, :check_value => lambda { value == 'Xing' || value == 'Info' })
|
@@ -30,6 +30,7 @@ module Mp3file
|
|
30
30
|
raise InvalidXingHeaderError, ve.message
|
31
31
|
end
|
32
32
|
|
33
|
+
@name = head.vbr_id
|
33
34
|
@frames = head.frames if head.frames_present == 1
|
34
35
|
@bytes = head.bytes if head.bytes_present == 1
|
35
36
|
@toc = head.toc.dup if head.toc_present == 1
|
data/mp3file.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mp3file
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Watts
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-06-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pry
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: bindata
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -65,7 +79,6 @@ files:
|
|
65
79
|
- ".ruby-gemset"
|
66
80
|
- ".ruby-version"
|
67
81
|
- Gemfile
|
68
|
-
- Gemfile.lock
|
69
82
|
- README.md
|
70
83
|
- Rakefile
|
71
84
|
- bin/mp3info
|
data/Gemfile.lock
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
mp3file (1.0.3)
|
5
|
-
bindata (~> 1.5.0)
|
6
|
-
|
7
|
-
GEM
|
8
|
-
remote: http://rubygems.org/
|
9
|
-
specs:
|
10
|
-
bindata (1.5.1)
|
11
|
-
diff-lcs (1.1.2)
|
12
|
-
rake (0.9.2)
|
13
|
-
rspec (2.6.0)
|
14
|
-
rspec-core (~> 2.6.0)
|
15
|
-
rspec-expectations (~> 2.6.0)
|
16
|
-
rspec-mocks (~> 2.6.0)
|
17
|
-
rspec-core (2.6.4)
|
18
|
-
rspec-expectations (2.6.0)
|
19
|
-
diff-lcs (~> 1.1.2)
|
20
|
-
rspec-mocks (2.6.0)
|
21
|
-
|
22
|
-
PLATFORMS
|
23
|
-
ruby
|
24
|
-
x86-mingw32
|
25
|
-
|
26
|
-
DEPENDENCIES
|
27
|
-
mp3file!
|
28
|
-
rake
|
29
|
-
rspec
|