mp3file 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,58 @@
1
+ module Mp3file
2
+ class InvalidID3v1TagError < Mp3fileError; end
3
+
4
+ class ID3v1Tag
5
+ attr_accessor(:title, :artist, :album, :year, :comment, :track, :genre)
6
+
7
+ class ID3v1TagFormat < BinData::Record
8
+ string(:tag_id, :length => 3, :check_value => lambda { value == 'TAG' })
9
+ string(:title, :length => 30)
10
+ string(:artist, :length => 30)
11
+ string(:album, :length => 30)
12
+ string(:year, :length => 4)
13
+ string(:comment, :length => 30)
14
+ uint8(:genre_id)
15
+ end
16
+
17
+ # First group is the original spec, the second are Winamp extensions.
18
+ GENRES =
19
+ %w{ Blues Classic\ Rock Country Dance Disco Funk Grunge Hip-Hop Jazz
20
+ Metal New\ Age Oldies Other Pop R&B Rap Reggae Rock Techno Industrial
21
+ Alternative Ska Death\ Metal Pranks Soundtrack Euro-Techno Ambient Trip-Hop
22
+ Vocal Jazz+Funk Fusion Trance Classical Instrumental Acid House Game
23
+ Sound\ Clip Gospel Noise AlternRock Bass Soul Punk Space Meditative
24
+ Instrumental\ Pop Instrumental\ Rock Ethnic Gothic Darkwave Techno-Industrial
25
+ Electronic Pop-Folk Eurodance Dream Southern\ Rock Comedy Cult Gangsta
26
+ Top\ 40 Christian\ Rap Pop/Funk Jungle Native\ American Cabaret New\ Wave
27
+ Psychadelic Rave Showtunes Trailer Lo-Fi Tribal Acid\ Punk Acid\ Jazz Polka
28
+ Retro Musical Rock\ &\ Roll Hard\ Rock
29
+
30
+ Folk Folk-Rock National\ Folk Swing
31
+ Fast\ Fusion Bebob Latin Revival Celtic Bluegrass Avantgarde Gothic\ Rock
32
+ Progressive\ Rock Psychedelic\ Rock Symphonic\ Rock Slow\ Rock Big\ Band
33
+ Chorus Easy\ Listening Acoustic Humour Speech Chanson Opera Chamber\ Music
34
+ Sonata Symphony Booty\ Bass Primus Porn\ Groove Satire Slow\ Jam Club Tango
35
+ Samba Folklore Ballad Power\ Ballad Rhythmic\ Soul Freestyle Duet Punk\ Rock
36
+ Drum\ Solo A\ capella Euro-House Dance\ Hall }
37
+
38
+ def initialize(io)
39
+ @tag = nil
40
+ begin
41
+ @tag = ID3v1TagFormat.read(io)
42
+ rescue BinData::ValidityError => ve
43
+ raise InvalidID3v1TagError, ve.message
44
+ end
45
+
46
+ @title = @tag.title.split("\x00").first
47
+ @artist = @tag.artist.split("\x00").first
48
+ @album = @tag.album.split("\x00").first
49
+ @year = @tag.year
50
+ split_comment = @tag.comment.split("\x00").reject { |s| s == '' }
51
+ @comment = split_comment.first
52
+ if split_comment.size > 1
53
+ @track = split_comment.last.bytes.first
54
+ end
55
+ @genre = GENRES[@tag.genre_id]
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,24 @@
1
+ module Mp3file::ID3v2
2
+ module BitPaddedInt
3
+ def self.unpad_number(num, bits = 7)
4
+ field = 2**bits - 1
5
+ rv = 0
6
+ 0.upto(3) do |i|
7
+ rv += (num & field) >> (i*(8-bits))
8
+ field = field << 8
9
+ end
10
+ rv
11
+ end
12
+
13
+ def self.pad_number(num, bits = 7)
14
+ field = 2**bits - 1
15
+ num2 = num
16
+ rv = 0
17
+ 0.upto(3) do |i|
18
+ rv += (num2 & field) << (i*8)
19
+ num2 = num2 >> bits
20
+ end
21
+ rv
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,84 @@
1
+ module Mp3file::ID3v2
2
+ class FrameHeader
3
+ class ID3v220FrameHeaderFormat < BinData::Record
4
+ string(:frame_id, :length => 3)
5
+ uint24be(:frame_size)
6
+ end
7
+
8
+ class ID3v230FrameHeaderFormat < BinData::Record
9
+ string(:frame_id, :length => 4)
10
+ uint32be(:frame_size)
11
+ bit1(:tag_alter_preserve)
12
+ bit1(:file_alter_preserve)
13
+ bit1(:read_only)
14
+ bit5(:unused1, :check_value => lambda { value == 0 })
15
+ bit1(:compression)
16
+ bit1(:encryption)
17
+ bit1(:has_group)
18
+ bit5(:unused2, :check_value => lambda { value == 0 })
19
+ uint8(:encryption_type, :onlyif => lambda { encryption == 1 })
20
+ uint8(:group_id, :onlyif => lambda { has_group == 1 })
21
+ end
22
+
23
+ class ID3v240FrameHeaderFormat < BinData::Record
24
+ string(:frame_id, :length => 4)
25
+ uint32be(:frame_size)
26
+ bit1(:unused1, :check_value => lambda { value == 0 })
27
+ bit1(:tag_alter_preserve)
28
+ bit1(:file_alter_preserve)
29
+ bit1(:read_only)
30
+ bit4(:unused2, :check_value => lambda { value == 0 })
31
+ bit1(:unused3, :check_value => lambda { value == 0 })
32
+ bit1(:group)
33
+ bit2(:unused4, :check_value => lambda { value == 0 })
34
+ bit1(:compression)
35
+ bit1(:encryption)
36
+ bit1(:unsynchronized)
37
+ bit1(:data_length_indicator)
38
+ end
39
+
40
+ attr_reader(:frame_id, :size,
41
+ :preserve_on_altered_tag, :preserve_on_altered_file,
42
+ :read_only, :compressed, :encrypted, :encryption_type,
43
+ :group, :unsynchronized, :data_length)
44
+
45
+ def initialize(io, tag)
46
+ @tag = tag
47
+ header = nil
48
+ @preserve_on_altered_tag = false
49
+ @preserve_on_altered_file = false
50
+ @read_only = false
51
+ @compressed = false
52
+ @encrypted = false
53
+ @group = nil
54
+ @unsynchronized = false
55
+ @data_length = 0
56
+
57
+ begin
58
+ if @tag.version >= ID3V2_2_0 && @tag.version < ID3V2_3_0
59
+ header = ID3v220FrameHeaderFormat.read(io)
60
+ elsif @tag.version >= ID3V2_3_0 && @tag.version < ID3V2_4_0
61
+ header = ID3v230FrameHeaderFormat.read(io)
62
+ @preserve_on_altered_tag = header.tag_alter_preserve == 1
63
+ @preserve_on_altered_file = header.file_alter_preserve == 1
64
+ @read_only = header.read_only == 1
65
+ @compressed = header.compression == 1
66
+ if header.encryption == 1
67
+ @encrypted = true
68
+ @encryption_type = header.encryption_type
69
+ end
70
+ if header.has_group == 1
71
+ @group = header.group_id
72
+ end
73
+ elsif @tag.version >= ID3V2_4_0
74
+ header = ID3v240FrameHeaderFormat.read(io)
75
+ end
76
+ rescue BinData::ValidityError => ve
77
+ raise InvalidID3v2TagError, ve.message
78
+ end
79
+
80
+ @frame_id = header.frame_id
81
+ @size = BitPaddedInt.unpad_number(header.frame_size)
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,67 @@
1
+ module Mp3file::ID3v2
2
+ class Header
3
+ attr_reader(
4
+ :version,
5
+ :unsynchronized,
6
+ :extended_header,
7
+ :compression,
8
+ :experimental,
9
+ :footer,
10
+ :tag_size)
11
+
12
+ class ID3v2HeaderFormat < BinData::Record
13
+ string(:tag_id, :length => 3, :check_value => lambda { value == 'ID3' })
14
+ uint8(:vmaj, :check_value => lambda { value >= 2 && value <= 4 })
15
+ uint8(:vmin)
16
+
17
+ bit1(:unsynchronized)
18
+ bit1(:extended_header)
19
+ bit1(:experimental)
20
+ bit1(:footer)
21
+ bit4(:unused, :check_value => lambda { value == 0 })
22
+
23
+ uint32be(:size_padded)
24
+ end
25
+
26
+ def initialize(io)
27
+ header = nil
28
+ begin
29
+ header = ID3v2HeaderFormat.read(io)
30
+ rescue BinData::ValidityError => ve
31
+ raise InvalidID3v2TagError, ve.message
32
+ end
33
+
34
+ @version = Version.new(header.vmaj, header.vmin)
35
+
36
+ @unsynchronized = false
37
+ @extended_header = false
38
+ @compression = false
39
+ @experimental = false
40
+ @footer = false
41
+
42
+ if @version >= ID3V2_2_0 && @version < ID3V2_3_0
43
+ @unsynchronized = header.unsynchronized == 1
44
+ # Bit 6 was redefined in v2.3.0+, and we picked the new name
45
+ # for it above.
46
+ @compression = header.extended_header == 1
47
+ if header.experimental == 1 || header.footer == 1
48
+ raise InvalidID3v2TagError, "Invalid flag set in ID3v2.2 header"
49
+ end
50
+ elsif @version >= ID3V2_3_0 && @version < ID3V2_4_0
51
+ @unsynchronized = header.unsynchronized == 1
52
+ @extended_header = header.extended_header == 1
53
+ @experimental = header.experimental == 1
54
+ if header.footer == 1
55
+ raise InvalidID3v2TagError, "Invalid flag set in ID3v2.3 header"
56
+ end
57
+ elsif @version >= ID3V2_4_0
58
+ @unsynchronized = header.unsynchronized == 1
59
+ @extended_header = header.extended_header == 1
60
+ @experimental = header.experimental == 1
61
+ @footer = header.footer == 1
62
+ end
63
+
64
+ @tag_size = BitPaddedInt.unpad_number(header.size_padded)
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,25 @@
1
+ require 'forwardable'
2
+
3
+ module Mp3file::ID3v2
4
+ class Tag
5
+ extend Forwardable
6
+
7
+ def_delegators(:@header, :version, :unsynchronized, :extended_header,
8
+ :compression, :experimental, :footer)
9
+
10
+ attr_reader(:header, :frames)
11
+
12
+ def initialize(io)
13
+ @header = Header.new(io)
14
+ @frames = []
15
+ end
16
+
17
+ def load_frames
18
+ @frames = []
19
+ end
20
+
21
+ def size
22
+ @header.tag_size + 10
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,4 @@
1
+ module Mp3file::ID3v2
2
+ class TextFrame
3
+ end
4
+ end
@@ -0,0 +1,40 @@
1
+ module Mp3file::ID3v2
2
+ class Version
3
+ include Comparable
4
+
5
+ attr_reader(:vbig, :vmaj, :vmin)
6
+
7
+ def initialize(vmaj, vmin, vbig = 2)
8
+ @vbig = vbig.to_i
9
+ @vmaj = vmaj.to_i
10
+ @vmin = vmin.to_i
11
+ end
12
+
13
+ def <=>(other)
14
+ c = vbig <=> other.vbig
15
+ return c if c != 0
16
+
17
+ c = vmaj <=> other.vmaj
18
+ return c if c != 0
19
+
20
+ vmin <=> other.vmin
21
+ end
22
+
23
+ def to_s
24
+ "ID3v%d.%d.%d" % [ vbig, vmaj, vmin ]
25
+ end
26
+
27
+ def to_byte_string
28
+ [ vmaj, vmin ].pack("cc")
29
+ end
30
+
31
+ def inspect
32
+ "<%p vbig = %p vmaj = %p vmin = %p>" %
33
+ [ self.class, @vbig, @vmaj, @vmin ]
34
+ end
35
+ end
36
+
37
+ ID3V2_4_0 = Version.new(4, 0)
38
+ ID3V2_3_0 = Version.new(3, 0)
39
+ ID3V2_2_0 = Version.new(2, 0)
40
+ end
@@ -0,0 +1,12 @@
1
+ module Mp3file
2
+ module ID3v2
3
+ class InvalidID3v2TagError < Mp3fileError; end
4
+ end
5
+ end
6
+
7
+ require 'mp3file/id3v2/bit_padded_int'
8
+ require 'mp3file/id3v2/version'
9
+ require 'mp3file/id3v2/header'
10
+ require 'mp3file/id3v2/tag'
11
+ require 'mp3file/id3v2/frame_header'
12
+ require 'mp3file/id3v2/text_frame'
@@ -0,0 +1,182 @@
1
+ module Mp3file
2
+ class MP3File
3
+ attr_reader(:file, :file_size, :audio_size)
4
+ attr_reader(:first_header_offset, :first_header)
5
+ attr_reader(:xing_header_offset, :xing_header)
6
+ attr_reader(:vbri_header_offset, :vbri_header)
7
+ attr_reader(:mpeg_version, :layer, :bitrate, :samplerate, :mode)
8
+ attr_reader(:num_frames, :total_samples, :length)
9
+
10
+ attr_accessor(:id3v1_tag)
11
+
12
+ def initialize(file_path)
13
+ file_path = Pathname.new(file_path).expand_path if file_path.is_a?(String)
14
+ load_file(file_path)
15
+ end
16
+
17
+ def vbr?
18
+ @vbr
19
+ end
20
+
21
+ def id3v1tag?
22
+ !@id3v1_tag.nil?
23
+ end
24
+
25
+ def id3v2tag?
26
+ !@id3v2_tag.nil?
27
+ end
28
+
29
+ def title
30
+ value_from_tags(:title)
31
+ end
32
+
33
+ def artist
34
+ value_from_tags(:artist)
35
+ end
36
+
37
+ def album
38
+ value_from_tags(:album)
39
+ end
40
+
41
+ def track
42
+ value_from_tags(:track)
43
+ end
44
+
45
+ def year
46
+ value_from_tags(:year)
47
+ end
48
+
49
+ def comment
50
+ value_from_tags(:comment)
51
+ end
52
+
53
+ def genre
54
+ value_from_tags(:genre)
55
+ end
56
+
57
+ private
58
+
59
+ def value_from_tags(v1_field)
60
+ if @id3v1_tag
61
+ @id3v1_tag.send(v1_field)
62
+ else
63
+ nil
64
+ end
65
+ end
66
+
67
+ def load_file(file_path)
68
+ @file = file_path.open('rb')
69
+ @file.seek(0, IO::SEEK_END)
70
+ @file_size = @file.tell
71
+
72
+ # Try to read an ID3v1 tag.
73
+ @id3v1_tag = nil
74
+ @file.seek(-128, IO::SEEK_END)
75
+ begin
76
+ @id3v1_tag = ID3v1Tag.new(@file)
77
+ rescue InvalidID3v1TagError => e
78
+ @id3v1_tag = nil
79
+ end
80
+ @file.seek(0, IO::SEEK_SET)
81
+
82
+ # Try to detect an ID3v2 header.
83
+ @id3v2_header = nil
84
+ begin
85
+ @id3v2_header = ID3v2::Header.new(@file)
86
+ rescue ID3v2::InvalidID3v2TagError => e
87
+ @id3v2_header = nil
88
+ @file.seek(0, IO::SEEK_SET)
89
+ end
90
+
91
+ # Skip past the ID3v2 header if it's present.
92
+ if @id3v2_header
93
+ @file.seek(@id3v2_header.tag_size + 10, IO::SEEK_SET)
94
+ end
95
+
96
+ # Try to find the first MP3 header.
97
+ @first_header_offset, @first_header = get_next_header(@file)
98
+
99
+ @mpeg_version = @first_header.version
100
+ @layer = @first_header.layer
101
+ @bitrate = @first_header.bitrate / 1000
102
+ @samplerate = @first_header.samplerate
103
+ @mode = @first_header.mode
104
+ @audio_size = @file_size
105
+ if @id3v1_tag
106
+ @audio_size -= 128
107
+ end
108
+ if @id3v2_header
109
+ @audio_size -= (@id3v2_header.tag_size + 10)
110
+ end
111
+
112
+ # If it's VBR, there should be an Xing header after the
113
+ # side_bytes.
114
+ @xing_header = nil
115
+ @file.seek(@first_header.side_bytes, IO::SEEK_CUR)
116
+ begin
117
+ @xing_header = XingHeader.new(@file)
118
+ rescue InvalidXingHeaderError => ve
119
+ @file.seek(@first_header_offset + 4, IO::SEEK_CUR)
120
+ end
121
+
122
+ if @xing_header
123
+ @vbr = true
124
+ # Do the VBR length calculation. What to do if we don't have
125
+ # both of these pieces of information?
126
+ if @xing_header.frames && @xing_header.bytes
127
+ @num_frames = @xing_header.frames
128
+ @total_samples = @xing_header.frames * @first_header.samples
129
+ @length = total_samples / @samplerate
130
+ @bitrate = ((@xing_header.bytes.to_f / @length.to_f) * 8 / 1000).to_i
131
+ end
132
+ else
133
+ # Do the CBR length calculation.
134
+ @vbr = false
135
+ @num_frames = @audio_size / @first_header.frame_size
136
+ @total_samples = @num_frames * @first_header.samples
137
+ @length = @total_samples / @samplerate
138
+ end
139
+
140
+ @file.close
141
+ end
142
+
143
+ def get_next_header(file, offset = nil)
144
+ if offset && offset != file.tell
145
+ file.seek(offset, IO::SEEK_SET)
146
+ end
147
+
148
+ header = nil
149
+ header_offset = file.tell
150
+
151
+ while header.nil?
152
+ begin
153
+ header = MP3Header.new(file)
154
+ header_offset = file.tell - 4
155
+ rescue InvalidMP3HeaderError => e
156
+ header_offset += 1
157
+ file.seek(header_offset, IO::SEEK_SET)
158
+ retry
159
+ end
160
+
161
+ # byte = file.readbyte
162
+ # while byte != 0xFF
163
+ # byte = file.readbyte
164
+ # end
165
+ # header_bytes = [ byte ] + file.read(3).bytes.to_a
166
+ # if header_bytes[1] & 0xE0 != 0xE0
167
+ # file.seek(-3, IO::SEEK_CUR)
168
+ # else
169
+ # header = MP3Header.new(header_bytes)
170
+ # if !header.valid?
171
+ # header = nil
172
+ # file.seek(-3, IO::SEEK_CUR)
173
+ # else
174
+ # header_offset = file.tell - 4
175
+ # end
176
+ # end
177
+ end
178
+
179
+ [ header_offset, header ]
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,109 @@
1
+ module Mp3file
2
+ class InvalidMP3HeaderError < Mp3fileError; end
3
+
4
+ class MP3Header
5
+ attr_reader(:version, :layer, :has_crc, :bitrate,
6
+ :samplerate, :has_padding, :mode, :mode_extension,
7
+ :copyright, :original, :emphasis, :samples, :frame_size,
8
+ :side_bytes)
9
+
10
+ class MP3HeaderFormat < BinData::Record
11
+ uint8(:sync1, :value => 255, :check_value => lambda { value == 255 })
12
+
13
+ bit3(:sync2, :value => 7, :check_value => lambda { value == 7 })
14
+ bit2(:version, :check_value => lambda { value != 1 })
15
+ bit2(:layer, :check_value => lambda { value != 0 })
16
+ bit1(:crc)
17
+
18
+ bit4(:bitrate, :check_value => lambda { value != 15 && value != 0 })
19
+ bit2(:samplerate, :check_value => lambda { value != 3 })
20
+ bit1(:padding)
21
+ bit1(:private)
22
+
23
+ bit2(:mode)
24
+ bit2(:mode_extension)
25
+ bit1(:copyright)
26
+ bit1(:original)
27
+ bit2(:emphasis, :check_value => lambda { value != 2 })
28
+ end
29
+
30
+ MPEG_VERSIONS = [ 'MPEG 2.5', nil, 'MPEG 2', 'MPEG 1' ]
31
+ LAYERS = [ nil, 'Layer III', 'Layer II', 'Layer I' ]
32
+ BITRATES = [
33
+ # MPEG 2.5
34
+ [ nil,
35
+ [ 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 ], # Layer III
36
+ [ 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 ], # Layer II
37
+ [ 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256 ], ], # Layer I
38
+ # reserved
39
+ nil,
40
+ # MPEG 2
41
+ [ nil,
42
+ [ 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 ], # Layer III
43
+ [ 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 ], # Layer II
44
+ [ 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256 ], ], # Layer I
45
+ # MPEG 1
46
+ [ nil,
47
+ [ 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320 ], # Layer III
48
+ [ 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384 ], # Layer II
49
+ [ 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448 ], ] # Layer I
50
+ ]
51
+ SAMPLERATES = [
52
+ [ 11025, 12000, 8000 ], # MPEG 2.5
53
+ nil,
54
+ [ 22050, 24000, 16000 ], # MPEG 2
55
+ [ 44100, 48000, 32000 ], # MPEG 1
56
+ ]
57
+ MODES = [ 'Stereo', 'Joint Stereo', 'Dual Channel', 'Mono' ]
58
+ SAMPLE_COUNTS = [
59
+ [ nil, 576, 1152, 384 ], # MPEG 2.5, III / II / I
60
+ nil,
61
+ [ nil, 576, 1152, 384 ], # MPEG 2, III / II / I
62
+ [ nil, 1152, 1152, 384 ], # MPEG 1, III / II / I
63
+ ]
64
+ SIDE_BYTES = [
65
+ [ 17, 17, 17, 9 ], # MPEG 2.5, Stereo, J-Stereo, Dual Channel, Mono
66
+ nil,
67
+ [ 17, 17, 17, 9 ], # MPEG 2, Stereo, J-Stereo, Dual Channel, Mono
68
+ [ 32, 32, 32, 17 ], # MPEG 1, Stereo, J-Stereo, Dual Channel, Mono
69
+ ]
70
+ MODE_EXTENSIONS_LAYER_I_II = [ 'bands 4 to 31', 'bands 8 to 31', 'bands 12 to 31', 'bands 16 to 31' ]
71
+ MODE_EXTENSIONS_LAYER_III = [ nil, 'Intensity Stereo', 'M/S Stereo', [ 'Intensity Stereo', 'M/S Stereo' ] ]
72
+ EMPHASES = [ 'none', '50/15 ms', nil, 'CCIT J.17' ]
73
+
74
+ def initialize(io)
75
+ begin
76
+ head = MP3HeaderFormat.read(io)
77
+ rescue BinData::ValidityError => ve
78
+ raise InvalidMP3HeaderError, ve.message
79
+ end
80
+
81
+ @version = MPEG_VERSIONS[head.version]
82
+ @layer = LAYERS[head.layer]
83
+ @has_crc = head.crc == 0
84
+ @bitrate = BITRATES[head.version][head.layer][head.bitrate - 1] * 1000
85
+ @samplerate = SAMPLERATES[head.version][head.samplerate]
86
+ @has_padding = head.padding == 1
87
+ @mode = MODES[head.mode]
88
+
89
+ @mode_extension = nil
90
+ if @mode == 'Joint Stereo'
91
+ if [ 'Layer I', 'Layer II' ].include?(@layer)
92
+ @mode_extension = MODE_EXTENSIONS_LAYER_I_II[head.mode_extension]
93
+ elsif @layer == 'Layer III'
94
+ @mode_extension = MODE_EXTENSIONS_LAYER_III[head.mode_extension]
95
+ end
96
+ end
97
+
98
+ @copyright = head.copyright == 1
99
+ @original = head.original == 1
100
+ @emphasis = EMPHASES[head.emphasis]
101
+ @samples = SAMPLE_COUNTS[head.version][head.layer]
102
+
103
+ slot_size = layer == 'Layer I' ? 4 : 1
104
+ pad_slots = has_padding ? 1 : 0
105
+ @frame_size = (((samples.to_f * bitrate.to_f) / (8 * slot_size.to_f * samplerate.to_f)) + pad_slots).to_i * slot_size
106
+ @side_bytes = SIDE_BYTES[head.version][head.mode]
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,3 @@
1
+ module Mp3file
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,39 @@
1
+ module Mp3file
2
+ class InvalidXingHeaderError < Mp3fileError; end
3
+
4
+ class XingHeader
5
+ attr_reader(:frames, :bytes, :toc, :quality)
6
+
7
+ class XingHeaderFormat < BinData::Record
8
+ string(:vbr_id, :length => 4, :check_value => lambda { value == 'Xing' })
9
+
10
+ uint8(:unused1, :check_value => lambda { value == 0 })
11
+ uint8(:unused2, :check_value => lambda { value == 0 })
12
+ uint8(:unused3, :check_value => lambda { value == 0 })
13
+ bit4(:unused4, :check_value => lambda { value == 0 })
14
+ bit1(:quality_present)
15
+ bit1(:toc_present)
16
+ bit1(:bytes_present)
17
+ bit1(:frames_present)
18
+
19
+ uint32be(:frames, :onlyif => lambda { frames_present == 1 })
20
+ uint32be(:bytes, :onlyif => lambda { bytes_present == 1 })
21
+ array(:toc, :type => :uint8, :read_until => lambda { index == 99 }, :onlyif => lambda { toc_present == 1 })
22
+ uint32be(:quality, :onlyif => lambda { quality_present == 1 })
23
+ end
24
+
25
+ def initialize(io)
26
+ head = nil
27
+ begin
28
+ head = XingHeaderFormat.read(io)
29
+ rescue BinData::ValidityError => ve
30
+ raise InvalidXingHeaderError, ve.message
31
+ end
32
+
33
+ @frames = head.frames if head.frames_present == 1
34
+ @bytes = head.bytes if head.bytes_present == 1
35
+ @toc = head.toc.dup if head.toc_present == 1
36
+ @quality = head.quality if head.quality_present == 1
37
+ end
38
+ end
39
+ end
data/lib/mp3file.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'pathname'
2
+
3
+ require 'rubygems'
4
+ require 'bindata'
5
+
6
+ class Mp3fileError < StandardError; end
7
+
8
+ require 'mp3file/mp3_file'
9
+ require 'mp3file/mp3_header'
10
+ require 'mp3file/xing_header'
11
+ require 'mp3file/id3v1_tag'
12
+ require 'mp3file/id3v2'
data/mp3file.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # -*- mode: ruby; encoding: utf-8 -*-
2
+
3
+ $:.push File.expand_path("../lib", __FILE__)
4
+ require "mp3file/version"
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "mp3file"
8
+ s.version = Mp3file::VERSION
9
+ s.platform = Gem::Platform::RUBY
10
+ s.authors = ["Andrew Watts"]
11
+ s.email = ["ahwatts@gmail.com"]
12
+ s.homepage = "http://rubygems.org/gems/mp3file"
13
+ s.summary = %q{Reads MP3 headers and returns their information.}
14
+ s.description = %q{Reads MP3 headers and returns their information.}
15
+
16
+ s.rubyforge_project = "mp3file"
17
+
18
+ s.add_development_dependency('rspec')
19
+ s.add_development_dependency('rake')
20
+ s.add_dependency('bindata')
21
+
22
+ s.files = `git ls-files`.split("\n")
23
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
25
+ s.require_paths = ["lib"]
26
+ end