wahwah 1.0.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a5677433a8471c7ed8ba480c0c7d42804e0ad3494edc88135728766b2778bc01
4
- data.tar.gz: 3163af32c9dc394f8da2b538a6356c111aeb85981b33ec6c42babbe4634de1c0
3
+ metadata.gz: ac83f92fa8a66210ab41b57ab7ccac87f7653573860286a5986ffc17132a57eb
4
+ data.tar.gz: d197f0523c55af55ef826237b227e21d450b13937585eaa456e18360ae1983e6
5
5
  SHA512:
6
- metadata.gz: dc8742ff453a6f1826b2458b2daf78bdef6825dbb0ce4e208d11b5c1456ff998ab2f0ff33223393a9a2366e42fc39652930866c1316434859444f016529ad9bb
7
- data.tar.gz: 1c0a2099a2df566f755f12074ce5628ebaa831d01c679509965cff9b6b950aaa6af0e0c7bd5e6dd6dd770d3868595225645a6f79a32ba5e377eeb01a2ac4cc0c
6
+ metadata.gz: d9e893bb99650e639f26546705d0d46bdba50da7377bd11f221247adc1069e97738ad2a4ed134fa078682ccf51cc13a37bf1bb2bebee3891e26de662e0f887cc
7
+ data.tar.gz: 1bf025ffb3ef82c4870477402befafaedb5c1c654726ba77f61c88d7ef8d6516f6d56c79cf54fc45f319a56d97402f0061d8c5c0e20e3e274afc8d40080e4522
@@ -11,29 +11,24 @@ module WahWah
11
11
  # 8 bytes: Object size
12
12
  # variable-sized: Object data
13
13
  class Object
14
+ prepend LazyRead
15
+
14
16
  HEADER_SIZE = 24
15
- HEADER_FORMAT = 'a16Q<'
17
+ HEADER_FORMAT = "a16Q<"
16
18
 
17
- attr_reader :size, :guid
19
+ attr_reader :guid
18
20
 
19
- def initialize(file_io)
20
- guid_bytes, @size = file_io.read(HEADER_SIZE)&.unpack(HEADER_FORMAT)
21
+ def initialize
22
+ guid_bytes, @size = @file_io.read(HEADER_SIZE)&.unpack(HEADER_FORMAT)
21
23
  return unless valid?
22
24
 
23
- @size = @size - HEADER_SIZE
25
+ @size -= HEADER_SIZE
24
26
  @guid = Helper.byte_string_to_guid(guid_bytes)
25
- @file_io = file_io
26
- @position = file_io.pos
27
27
  end
28
28
 
29
29
  def valid?
30
30
  !@size.nil? && @size >= HEADER_SIZE
31
31
  end
32
-
33
- def data
34
- @file_io.seek(@position)
35
- @file_io.read(size)
36
- end
37
32
  end
38
33
  end
39
34
  end
@@ -3,218 +3,219 @@
3
3
  module WahWah
4
4
  class AsfTag < Tag
5
5
  HEADER_OBJECT_CONTENT_SIZE = 6
6
- HEADER_OBJECT_GUID = '75B22630-668E-11CF-A6D9-00AA0062CE6C'
7
- FILE_PROPERTIES_OBJECT_GUID = '8CABDCA1-A947-11CF-8EE4-00C00C205365'
8
- EXTENDED_CONTENT_DESCRIPTION_OBJECT_GUID = 'D2D0A440-E307-11D2-97F0-00A0C95EA850'
9
- STREAM_PROPERTIES_OBJECT_GUID = 'B7DC0791-A9B7-11CF-8EE6-00C00C205365'
10
- AUDIO_MEDIA_OBJECT_GUID = 'F8699E40-5B4D-11CF-A8FD-00805F5C442B'
11
- CONTENT_DESCRIPTION_OBJECT_GUID = '75B22633-668E-11CF-A6D9-00AA0062CE6C'
6
+ HEADER_OBJECT_GUID = "75B22630-668E-11CF-A6D9-00AA0062CE6C"
7
+ FILE_PROPERTIES_OBJECT_GUID = "8CABDCA1-A947-11CF-8EE4-00C00C205365"
8
+ EXTENDED_CONTENT_DESCRIPTION_OBJECT_GUID = "D2D0A440-E307-11D2-97F0-00A0C95EA850"
9
+ STREAM_PROPERTIES_OBJECT_GUID = "B7DC0791-A9B7-11CF-8EE6-00C00C205365"
10
+ AUDIO_MEDIA_OBJECT_GUID = "F8699E40-5B4D-11CF-A8FD-00805F5C442B"
11
+ CONTENT_DESCRIPTION_OBJECT_GUID = "75B22633-668E-11CF-A6D9-00AA0062CE6C"
12
12
 
13
13
  EXTENDED_CONTENT_DESCRIPTOR_NAME_MAPPING = {
14
- 'WM/AlbumArtist' => :albumartist,
15
- 'WM/AlbumTitle' => :album,
16
- 'WM/Composer' => :composer,
17
- 'WM/Genre' => :genre,
18
- 'WM/PartOfSet' => :disc,
19
- 'WM/TrackNumber' => :track,
20
- 'WM/Year' => :year
14
+ "WM/AlbumArtist" => :albumartist,
15
+ "WM/AlbumTitle" => :album,
16
+ "WM/Composer" => :composer,
17
+ "WM/Genre" => :genre,
18
+ "WM/PartOfSet" => :disc,
19
+ "WM/TrackNumber" => :track,
20
+ "WM/Year" => :year
21
21
  }
22
22
 
23
23
  private
24
- # ASF files are logically composed of three types of top-level objects:
25
- # the Header Object, the Data Object, and the Index Object(s).
26
- # The Header Object is mandatory and must be placed at the beginning of every ASF file.
27
- # Of the three top-level ASF objects, the Header Object is the only one that contains other ASF objects.
28
- # All Unicode strings in ASF uses UTF-16, little endian, and the Byte-Order Marker (BOM) character is not present.
29
- def parse
30
- header_object = Asf::Object.new(@file_io)
31
- return unless header_object.valid?
32
24
 
33
- total_header_object_size = header_object.size + Asf::Object::HEADER_SIZE
25
+ # ASF files are logically composed of three types of top-level objects:
26
+ # the Header Object, the Data Object, and the Index Object(s).
27
+ # The Header Object is mandatory and must be placed at the beginning of every ASF file.
28
+ # Of the three top-level ASF objects, the Header Object is the only one that contains other ASF objects.
29
+ # All Unicode strings in ASF uses UTF-16, little endian, and the Byte-Order Marker (BOM) character is not present.
30
+ def parse
31
+ header_object = Asf::Object.new(@file_io)
32
+ return unless header_object.valid?
34
33
 
35
- return unless header_object.guid == HEADER_OBJECT_GUID
34
+ total_header_object_size = header_object.size + Asf::Object::HEADER_SIZE
36
35
 
37
- # Header Object contains 6 bytes useless data, so skip it.
38
- @file_io.seek(HEADER_OBJECT_CONTENT_SIZE, IO::SEEK_CUR)
36
+ return unless header_object.guid == HEADER_OBJECT_GUID
39
37
 
40
- until total_header_object_size <= @file_io.pos
41
- sub_object = Asf::Object.new(@file_io)
42
- parse_sub_object(sub_object)
43
- end
44
- end
38
+ # Header Object contains 6 bytes useless data, so skip it.
39
+ @file_io.seek(HEADER_OBJECT_CONTENT_SIZE, IO::SEEK_CUR)
45
40
 
46
- def parse_sub_object(sub_object)
47
- case sub_object.guid
48
- when FILE_PROPERTIES_OBJECT_GUID
49
- parse_file_properties_object(sub_object)
50
- when EXTENDED_CONTENT_DESCRIPTION_OBJECT_GUID
51
- parse_extended_content_description_object(sub_object)
52
- when STREAM_PROPERTIES_OBJECT_GUID
53
- parse_stream_properties_object(sub_object)
54
- when CONTENT_DESCRIPTION_OBJECT_GUID
55
- parse_content_description_object(sub_object)
56
- else
57
- @file_io.seek(sub_object.size, IO::SEEK_CUR)
58
- end
41
+ until total_header_object_size <= @file_io.pos
42
+ sub_object = Asf::Object.new(@file_io)
43
+ parse_sub_object(sub_object)
59
44
  end
60
-
61
- # File Properties Object structure:
62
- #
63
- # Field name Field type Size (bits)
64
- #
65
- # Object ID GUID 128
66
- # Object Size QWORD 64
67
- # File ID GUID 128
68
- # File Size QWORD 64
69
- # Creation Date QWORD 64
70
- # Data Packets Count QWORD 64
71
- # Play Duration QWORD 64
72
- # Send Duration QWORD 64
73
- # Preroll QWORD 64
74
- # Flags DWORD 32
75
- # Broadcast Flag 1 (LSB)
76
- # Seekable Flag 1
77
- # Reserved 30
78
- # Minimum Data Packet Size DWORD 32
79
- # Maximum Data Packet Size DWORD 32
80
- # Maximum Bitrate DWORD 32
81
- #
82
- # Play Duration Specifies the time needed to play the file in 100-nanosecond units.
83
- # The value of this field is invalid if the Broadcast Flag bit in the Flags field is set to 1.
84
- #
85
- # Preroll Specifies the amount of time to buffer data before starting to play the file, in millisecond units.
86
- # If this value is nonzero, the Play Duration field and all of the payload Presentation Time fields have been offset by this amount.
87
- def parse_file_properties_object(object)
88
- play_duration, preroll, flags = object.data.unpack('x40Q<x8Q<b32')
89
- @duration = (play_duration / 10000000.0 - preroll / 1000.0).round if flags[0] == '0'
45
+ end
46
+
47
+ def parse_sub_object(sub_object)
48
+ case sub_object.guid
49
+ when FILE_PROPERTIES_OBJECT_GUID
50
+ parse_file_properties_object(sub_object)
51
+ when EXTENDED_CONTENT_DESCRIPTION_OBJECT_GUID
52
+ parse_extended_content_description_object(sub_object)
53
+ when STREAM_PROPERTIES_OBJECT_GUID
54
+ parse_stream_properties_object(sub_object)
55
+ when CONTENT_DESCRIPTION_OBJECT_GUID
56
+ parse_content_description_object(sub_object)
57
+ else
58
+ sub_object.skip
90
59
  end
91
-
92
- # Extended Content Description Object structure:
93
- #
94
- # Field name Field type Size (bits)
95
- #
96
- # Object ID GUID 128
97
- # Object Size QWORD 64
98
- # Content Descriptors Count WORD 16
99
- # Content Descriptors See text varies
100
- #
101
- #
102
- # The structure of each Content Descriptor:
103
- #
104
- # Field Name Field Type Size (bits)
105
- #
106
- # Descriptor Name Length WORD 16
107
- # Descriptor Name WCHAR varies
108
- # Descriptor Value Data Type WORD 16
109
- # Descriptor Value Length WORD 16
110
- # Descriptor Value See text varies
111
- #
112
- #
113
- # Specifies the type of data stored in the Descriptor Value field.
114
- # The types are defined in the following table.
115
- #
116
- # Value Type Descriptor value length
117
- #
118
- # 0x0000 Unicode string varies
119
- # 0x0001 BYTE array varies
120
- # 0x0002 BOOL 32
121
- # 0x0003 DWORD 32
122
- # 0x0004 QWORD 64
123
- # 0x0005 WORD 16
124
- def parse_extended_content_description_object(object)
125
- object_data = StringIO.new(object.data)
126
- descriptors_count = object_data.read(2).unpack('v').first
127
-
128
- descriptors_count.times do
129
- name_length = object_data.read(2).unpack('v').first
130
- name = Helper.encode_to_utf8(object_data.read(name_length), source_encoding: 'UTF-16LE')
131
- value_type, value_length = object_data.read(4).unpack('vv')
132
- value = object_data.read(value_length)
133
-
134
- attr_value = case value_type
135
- when 0
136
- Helper.encode_to_utf8(value, source_encoding: 'UTF-16LE')
137
- when 1
138
- value
139
- when 2, 3
140
- value.unpack('V').first
141
- when 4
142
- value.unpack('Q<').first
143
- when 5
144
- value.unpack('v').first
145
- end
146
-
147
- attr_name = EXTENDED_CONTENT_DESCRIPTOR_NAME_MAPPING[name]
148
- instance_variable_set("@#{attr_name}", attr_value) unless attr_name.nil?
60
+ end
61
+
62
+ # File Properties Object structure:
63
+ #
64
+ # Field name Field type Size (bits)
65
+ #
66
+ # Object ID GUID 128
67
+ # Object Size QWORD 64
68
+ # File ID GUID 128
69
+ # File Size QWORD 64
70
+ # Creation Date QWORD 64
71
+ # Data Packets Count QWORD 64
72
+ # Play Duration QWORD 64
73
+ # Send Duration QWORD 64
74
+ # Preroll QWORD 64
75
+ # Flags DWORD 32
76
+ # Broadcast Flag 1 (LSB)
77
+ # Seekable Flag 1
78
+ # Reserved 30
79
+ # Minimum Data Packet Size DWORD 32
80
+ # Maximum Data Packet Size DWORD 32
81
+ # Maximum Bitrate DWORD 32
82
+ #
83
+ # Play Duration Specifies the time needed to play the file in 100-nanosecond units.
84
+ # The value of this field is invalid if the Broadcast Flag bit in the Flags field is set to 1.
85
+ #
86
+ # Preroll Specifies the amount of time to buffer data before starting to play the file, in millisecond units.
87
+ # If this value is nonzero, the Play Duration field and all of the payload Presentation Time fields have been offset by this amount.
88
+ def parse_file_properties_object(object)
89
+ play_duration, preroll, flags = object.data.unpack("x40Q<x8Q<b32")
90
+ @duration = play_duration / 10000000.0 - preroll / 1000.0 if flags[0] == "0"
91
+ end
92
+
93
+ # Extended Content Description Object structure:
94
+ #
95
+ # Field name Field type Size (bits)
96
+ #
97
+ # Object ID GUID 128
98
+ # Object Size QWORD 64
99
+ # Content Descriptors Count WORD 16
100
+ # Content Descriptors See text varies
101
+ #
102
+ #
103
+ # The structure of each Content Descriptor:
104
+ #
105
+ # Field Name Field Type Size (bits)
106
+ #
107
+ # Descriptor Name Length WORD 16
108
+ # Descriptor Name WCHAR varies
109
+ # Descriptor Value Data Type WORD 16
110
+ # Descriptor Value Length WORD 16
111
+ # Descriptor Value See text varies
112
+ #
113
+ #
114
+ # Specifies the type of data stored in the Descriptor Value field.
115
+ # The types are defined in the following table.
116
+ #
117
+ # Value Type Descriptor value length
118
+ #
119
+ # 0x0000 Unicode string varies
120
+ # 0x0001 BYTE array varies
121
+ # 0x0002 BOOL 32
122
+ # 0x0003 DWORD 32
123
+ # 0x0004 QWORD 64
124
+ # 0x0005 WORD 16
125
+ def parse_extended_content_description_object(object)
126
+ object_data = StringIO.new(object.data)
127
+ descriptors_count = object_data.read(2).unpack1("v")
128
+
129
+ descriptors_count.times do
130
+ name_length = object_data.read(2).unpack1("v")
131
+ name = Helper.encode_to_utf8(object_data.read(name_length), source_encoding: "UTF-16LE")
132
+ value_type, value_length = object_data.read(4).unpack("vv")
133
+ value = object_data.read(value_length)
134
+
135
+ attr_value = case value_type
136
+ when 0
137
+ Helper.encode_to_utf8(value, source_encoding: "UTF-16LE")
138
+ when 1
139
+ value
140
+ when 2, 3
141
+ value.unpack1("V")
142
+ when 4
143
+ value.unpack1("Q<")
144
+ when 5
145
+ value.unpack1("v")
149
146
  end
150
- end
151
-
152
- # Stream Properties Object structure:
153
- #
154
- # Field Name Field Type Size (bits)
155
- # Object ID GUID 128
156
- # Object Size QWORD 64
157
- # Stream Type GUID 128
158
- # Error Correction Type GUID 128
159
- # Time Offset QWORD 64
160
- # Type-Specific Data Length DWORD 32
161
- # Error Correction Data Length DWORD 32
162
- # Flags WORD 16
163
- # Stream Number 7 (LSB)
164
- # Reserved 8
165
- # Encrypted Content Flag 1
166
- # Reserved DWORD 32
167
- # Type-Specific Data BYTE varies
168
- # Error Correction Data BYTE varies
169
- #
170
- # Stream Type specifies the type of the stream (for example, audio, video, and so on).
171
- # Any streams with unrecognized Stream Type values should be ignored.
172
- #
173
- # Audio media type Object structure:
174
- #
175
- # Field name Field type Size (bits)
176
- #
177
- # Codec ID / Format Tag WORD 16
178
- # Number of Channels WORD 16
179
- # Samples Per Second DWORD 32
180
- # Average Number of Bytes Per Second DWORD 32
181
- # Block Alignment WORD 16
182
- # Bits Per Sample WORD 16
183
- def parse_stream_properties_object(object)
184
- object_data = StringIO.new(object.data)
185
- stream_type, type_specific_data_length = object_data.read(54).unpack('a16x24V')
186
- stream_type_guid = Helper.byte_string_to_guid(stream_type)
187
-
188
- return unless stream_type_guid == AUDIO_MEDIA_OBJECT_GUID
189
-
190
- @sample_rate, bytes_per_second = object_data.read(type_specific_data_length).unpack('x4VV')
191
- @bitrate = (bytes_per_second * 8.0 / 1000).round
192
- end
193
147
 
194
- # Content Description Object structure:
195
- #
196
- # Field name Field type Size (bits)
197
- #
198
- # Object ID GUID 128
199
- # Object Size QWORD 64
200
- # Title Length WORD 16
201
- # Author Length WORD 16
202
- # Copyright Length WORD 16
203
- # Description Length WORD 16
204
- # Rating Length WORD 16
205
- # Title WCHAR Varies
206
- # Author WCHAR Varies
207
- # Copyright WCHAR Varies
208
- # Description WCHAR Varies
209
- # Rating WCHAR Varies
210
- def parse_content_description_object(object)
211
- object_data = StringIO.new(object.data)
212
- title_length, author_length, copyright_length, description_length, _ = object_data.read(10).unpack('v' * 5)
213
-
214
- @title = Helper.encode_to_utf8(object_data.read(title_length), source_encoding: 'UTF-16LE')
215
- @artist = Helper.encode_to_utf8(object_data.read(author_length), source_encoding: 'UTF-16LE')
216
- object_data.seek(copyright_length, IO::SEEK_CUR)
217
- @comments.push(Helper.encode_to_utf8(object_data.read(description_length), source_encoding: 'UTF-16LE'))
148
+ attr_name = EXTENDED_CONTENT_DESCRIPTOR_NAME_MAPPING[name]
149
+ instance_variable_set("@#{attr_name}", attr_value) unless attr_name.nil?
218
150
  end
151
+ end
152
+
153
+ # Stream Properties Object structure:
154
+ #
155
+ # Field Name Field Type Size (bits)
156
+ # Object ID GUID 128
157
+ # Object Size QWORD 64
158
+ # Stream Type GUID 128
159
+ # Error Correction Type GUID 128
160
+ # Time Offset QWORD 64
161
+ # Type-Specific Data Length DWORD 32
162
+ # Error Correction Data Length DWORD 32
163
+ # Flags WORD 16
164
+ # Stream Number 7 (LSB)
165
+ # Reserved 8
166
+ # Encrypted Content Flag 1
167
+ # Reserved DWORD 32
168
+ # Type-Specific Data BYTE varies
169
+ # Error Correction Data BYTE varies
170
+ #
171
+ # Stream Type specifies the type of the stream (for example, audio, video, and so on).
172
+ # Any streams with unrecognized Stream Type values should be ignored.
173
+ #
174
+ # Audio media type Object structure:
175
+ #
176
+ # Field name Field type Size (bits)
177
+ #
178
+ # Codec ID / Format Tag WORD 16
179
+ # Number of Channels WORD 16
180
+ # Samples Per Second DWORD 32
181
+ # Average Number of Bytes Per Second DWORD 32
182
+ # Block Alignment WORD 16
183
+ # Bits Per Sample WORD 16
184
+ def parse_stream_properties_object(object)
185
+ object_data = StringIO.new(object.data)
186
+ stream_type, type_specific_data_length = object_data.read(54).unpack("a16x24V")
187
+ stream_type_guid = Helper.byte_string_to_guid(stream_type)
188
+
189
+ return unless stream_type_guid == AUDIO_MEDIA_OBJECT_GUID
190
+
191
+ @sample_rate, bytes_per_second, @bit_depth = object_data.read(type_specific_data_length).unpack("x4VVx2v")
192
+ @bitrate = (bytes_per_second * 8.0 / 1000).round
193
+ end
194
+
195
+ # Content Description Object structure:
196
+ #
197
+ # Field name Field type Size (bits)
198
+ #
199
+ # Object ID GUID 128
200
+ # Object Size QWORD 64
201
+ # Title Length WORD 16
202
+ # Author Length WORD 16
203
+ # Copyright Length WORD 16
204
+ # Description Length WORD 16
205
+ # Rating Length WORD 16
206
+ # Title WCHAR Varies
207
+ # Author WCHAR Varies
208
+ # Copyright WCHAR Varies
209
+ # Description WCHAR Varies
210
+ # Rating WCHAR Varies
211
+ def parse_content_description_object(object)
212
+ object_data = StringIO.new(object.data)
213
+ title_length, author_length, copyright_length, description_length, _ = object_data.read(10).unpack("v" * 5)
214
+
215
+ @title = Helper.encode_to_utf8(object_data.read(title_length), source_encoding: "UTF-16LE")
216
+ @artist = Helper.encode_to_utf8(object_data.read(author_length), source_encoding: "UTF-16LE")
217
+ object_data.seek(copyright_length, IO::SEEK_CUR)
218
+ @comments.push(Helper.encode_to_utf8(object_data.read(description_length), source_encoding: "UTF-16LE"))
219
+ end
219
220
  end
220
221
  end
data/lib/wahwah/errors.rb CHANGED
@@ -2,5 +2,6 @@
2
2
 
3
3
  module WahWah
4
4
  class WahWahArgumentError < ArgumentError; end
5
- class WahWahNotImplementedError < NotImplementedError; end
5
+
6
+ class WahWahNotImplementedError < RuntimeError; end
6
7
  end
@@ -3,13 +3,15 @@
3
3
  module WahWah
4
4
  module Flac
5
5
  class Block
6
+ prepend LazyRead
7
+
6
8
  HEADER_SIZE = 4
7
- HEADER_FORMAT = 'B*'
8
- BLOCK_TYPE_INDEX = %w(STREAMINFO PADDING APPLICATION SEEKTABLE VORBIS_COMMENT CUESHEET PICTURE)
9
+ HEADER_FORMAT = "B*"
10
+ BLOCK_TYPE_INDEX = %w[STREAMINFO PADDING APPLICATION SEEKTABLE VORBIS_COMMENT CUESHEET PICTURE]
9
11
 
10
- attr_reader :size, :type
12
+ attr_reader :type
11
13
 
12
- def initialize(file_io)
14
+ def initialize
13
15
  # Block header structure:
14
16
  #
15
17
  # Length(bit) Meaning
@@ -30,14 +32,11 @@ module WahWah
30
32
  #
31
33
  # 24 Length (in bytes) of metadata to follow
32
34
  # (does not include the size of the METADATA_BLOCK_HEADER)
33
- header_bits = file_io.read(HEADER_SIZE).unpack(HEADER_FORMAT).first
35
+ header_bits = @file_io.read(HEADER_SIZE).unpack1(HEADER_FORMAT)
34
36
 
35
37
  @last_flag = header_bits[0]
36
38
  @type = BLOCK_TYPE_INDEX[header_bits[1..7].to_i(2)]
37
- @size = header_bits[8..-1].to_i(2)
38
-
39
- @file_io = file_io
40
- @position = file_io.pos
39
+ @size = header_bits[8..].to_i(2)
41
40
  end
42
41
 
43
42
  def valid?
@@ -47,11 +46,6 @@ module WahWah
47
46
  def is_last?
48
47
  @last_flag.to_i == 1
49
48
  end
50
-
51
- def data
52
- @file_io.seek(@position)
53
- @file_io.read(size)
54
- end
55
49
  end
56
50
  end
57
51
  end
@@ -37,14 +37,14 @@ module WahWah
37
37
  def parse_streaminfo_block(block_data)
38
38
  return unless block_data.size == STREAMINFO_BLOCK_SIZE
39
39
 
40
- info_bits = block_data.unpack('x10B64').first
40
+ info_bits = block_data.unpack1("x10B64")
41
41
 
42
42
  @sample_rate = info_bits[0..19].to_i(2)
43
- bits_per_sample = info_bits[23..27].to_i(2) + 1
44
- total_samples = info_bits[28..-1].to_i(2)
43
+ @bit_depth = info_bits[23..27].to_i(2) + 1
44
+ total_samples = info_bits[28..].to_i(2)
45
45
 
46
- @duration = (total_samples.to_f / @sample_rate).round if @sample_rate > 0
47
- @bitrate = @sample_rate * bits_per_sample / 1000
46
+ @duration = total_samples.to_f / @sample_rate if @sample_rate > 0
47
+ @bitrate = @sample_rate * @bit_depth / 1000
48
48
  end
49
49
  end
50
50
  end