wahwah 1.1.1 → 1.2.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7e3b8992dca29fbbe7a537a7d2ad23ca0fe33e9596dfbabb5131e615461d22b1
4
- data.tar.gz: 58809228e4e9b7f2e40c48136f68cdd2f29939b8628e6690efd75e95cf7c0e88
3
+ metadata.gz: 9db3f9953fd6a1641859fa4d27912ed6fdd1eec6b5a137498e1d7fa669903ea4
4
+ data.tar.gz: 23f36dddc181188c98d77a546960273ed6dd4c1f2dc548d0214cc3f2feeac060
5
5
  SHA512:
6
- metadata.gz: 501f86ff6b621bfb302b7503e05ef45a472c8faa8c128fc8cb15cf92cb35deca4e828d416bfb0e3bc750f63fb2b299cccabee9d9718729372225edb3e4618f9f
7
- data.tar.gz: f1d7c0a92c982180f7041883137b6909c11a761d2da17564aaa4a758297d56db197ae1a1698d3028b8397864ab0673c1f7300acb4bcd6fcfda3d82a6dce1832e
6
+ metadata.gz: 31171f19be0c4777e0fdcaa03c8d02449856847eb205e5867f0d76f0c8e44b221e98c3808834623e4bd943c4f94b8728fef17c0ebc91f3371a5fb2ce636cb5a4
7
+ data.tar.gz: 810d6c29379773f93e61d905be863fbda4eb1b0fa0127b10e52d9357bf1faa267df71f0bebf09d8652733e73c057eecc5e2fd5aea3da2500ac496aa3cb9a774e
@@ -14,7 +14,7 @@ module WahWah
14
14
  prepend LazyRead
15
15
 
16
16
  HEADER_SIZE = 24
17
- HEADER_FORMAT = 'a16Q<'
17
+ HEADER_FORMAT = "a16Q<"
18
18
 
19
19
  attr_reader :guid
20
20
 
@@ -22,7 +22,7 @@ module WahWah
22
22
  guid_bytes, @size = @file_io.read(HEADER_SIZE)&.unpack(HEADER_FORMAT)
23
23
  return unless valid?
24
24
 
25
- @size = @size - HEADER_SIZE
25
+ @size -= HEADER_SIZE
26
26
  @guid = Helper.byte_string_to_guid(guid_bytes)
27
27
  end
28
28
 
@@ -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
- sub_object.skip
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, @bit_depth = object_data.read(type_specific_data_length).unpack('x4VVx2v')
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
@@ -6,8 +6,8 @@ module WahWah
6
6
  prepend LazyRead
7
7
 
8
8
  HEADER_SIZE = 4
9
- HEADER_FORMAT = 'B*'
10
- 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]
11
11
 
12
12
  attr_reader :type
13
13
 
@@ -32,11 +32,11 @@ module WahWah
32
32
  #
33
33
  # 24 Length (in bytes) of metadata to follow
34
34
  # (does not include the size of the METADATA_BLOCK_HEADER)
35
- header_bits = @file_io.read(HEADER_SIZE).unpack(HEADER_FORMAT).first
35
+ header_bits = @file_io.read(HEADER_SIZE).unpack1(HEADER_FORMAT)
36
36
 
37
37
  @last_flag = header_bits[0]
38
38
  @type = BLOCK_TYPE_INDEX[header_bits[1..7].to_i(2)]
39
- @size = header_bits[8..-1].to_i(2)
39
+ @size = header_bits[8..].to_i(2)
40
40
  end
41
41
 
42
42
  def valid?
@@ -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
43
  @bit_depth = info_bits[23..27].to_i(2) + 1
44
- total_samples = info_bits[28..-1].to_i(2)
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 * @bit_depth / 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
@@ -5,80 +5,82 @@ module WahWah
5
5
  include Ogg::VorbisComment
6
6
  include Flac::StreaminfoBlock
7
7
 
8
- TAG_ID = 'fLaC'
8
+ TAG_ID = "fLaC"
9
9
 
10
10
  private
11
- # FLAC structure:
12
- #
13
- # The four byte string "fLaC"
14
- # The STREAMINFO metadata block
15
- # Zero or more other metadata blocks
16
- # One or more audio frames
17
- def parse
18
- # Flac file maybe contain ID3 header on the start, so skip it if exists
19
- id3_header = ID3::V2Header.new(@file_io)
20
- id3_header.valid? ? @file_io.seek(id3_header.size) : @file_io.rewind
21
11
 
22
- return if @file_io.read(4) != TAG_ID
12
+ # FLAC structure:
13
+ #
14
+ # The four byte string "fLaC"
15
+ # The STREAMINFO metadata block
16
+ # Zero or more other metadata blocks
17
+ # One or more audio frames
18
+ def parse
19
+ # Flac file maybe contain ID3 header on the start, so skip it if exists
20
+ id3_header = ID3::V2Header.new(@file_io)
21
+ id3_header.valid? ? @file_io.seek(id3_header.size) : @file_io.rewind
23
22
 
24
- loop do
25
- block = Flac::Block.new(@file_io)
26
- parse_block(block)
23
+ return if @file_io.read(4) != TAG_ID
27
24
 
28
- break if block.is_last? || @file_io.eof?
29
- end
25
+ loop do
26
+ block = Flac::Block.new(@file_io)
27
+ parse_block(block)
28
+
29
+ break if block.is_last? || @file_io.eof?
30
30
  end
31
+ end
31
32
 
32
- def parse_block(block)
33
- return unless block.valid?
33
+ def parse_block(block)
34
+ return unless block.valid?
34
35
 
35
- case block.type
36
- when 'STREAMINFO'
37
- parse_streaminfo_block(block.data)
38
- when 'VORBIS_COMMENT'
39
- parse_vorbis_comment(block.data)
40
- when 'PICTURE'
41
- @images_data.push(block); block.skip
42
- else
43
- block.skip
44
- end
36
+ case block.type
37
+ when "STREAMINFO"
38
+ parse_streaminfo_block(block.data)
39
+ when "VORBIS_COMMENT"
40
+ parse_vorbis_comment(block.data)
41
+ when "PICTURE"
42
+ @images_data.push(block)
43
+ block.skip
44
+ else
45
+ block.skip
45
46
  end
47
+ end
46
48
 
47
- # PICTURE block data structure:
48
- #
49
- # Length(bit) Meaning
50
- #
51
- # 32 The picture type according to the ID3v2 APIC frame:
52
- #
53
- # 32 The length of the MIME type string in bytes.
54
- #
55
- # n*8 The MIME type string.
56
- #
57
- # 32 The length of the description string in bytes.
58
- #
59
- # n*8 The description of the picture, in UTF-8.
60
- #
61
- # 32 The width of the picture in pixels.
62
- #
63
- # 32 The height of the picture in pixels.
64
- #
65
- # 32 The color depth of the picture in bits-per-pixel.
66
- #
67
- # 32 For indexed-color pictures (e.g. GIF), the number of colors used, or 0 for non-indexed pictures.
68
- #
69
- # 32 The length of the picture data in bytes.
70
- #
71
- # n*8 The binary picture data.
72
- def parse_image_data(picture_block)
73
- block_content = StringIO.new(picture_block.data)
49
+ # PICTURE block data structure:
50
+ #
51
+ # Length(bit) Meaning
52
+ #
53
+ # 32 The picture type according to the ID3v2 APIC frame:
54
+ #
55
+ # 32 The length of the MIME type string in bytes.
56
+ #
57
+ # n*8 The MIME type string.
58
+ #
59
+ # 32 The length of the description string in bytes.
60
+ #
61
+ # n*8 The description of the picture, in UTF-8.
62
+ #
63
+ # 32 The width of the picture in pixels.
64
+ #
65
+ # 32 The height of the picture in pixels.
66
+ #
67
+ # 32 The color depth of the picture in bits-per-pixel.
68
+ #
69
+ # 32 For indexed-color pictures (e.g. GIF), the number of colors used, or 0 for non-indexed pictures.
70
+ #
71
+ # 32 The length of the picture data in bytes.
72
+ #
73
+ # n*8 The binary picture data.
74
+ def parse_image_data(picture_block)
75
+ block_content = StringIO.new(picture_block.data)
74
76
 
75
- type_index, mime_type_length = block_content.read(8).unpack('NN')
76
- mime_type = Helper.encode_to_utf8(block_content.read(mime_type_length))
77
- description_length = block_content.read(4).unpack('N').first
78
- data_length = block_content.read(description_length + 20).unpack("#{'x' * (description_length + 16)}N").first
79
- data = block_content.read(data_length)
77
+ type_index, mime_type_length = block_content.read(8).unpack("NN")
78
+ mime_type = Helper.encode_to_utf8(block_content.read(mime_type_length))
79
+ description_length = block_content.read(4).unpack1("N")
80
+ data_length = block_content.read(description_length + 20).unpack1("#{"x" * (description_length + 16)}N")
81
+ data = block_content.read(data_length)
80
82
 
81
- { data: data, mime_type: mime_type, type: ID3::ImageFrameBody::TYPES[type_index] }
82
- end
83
+ {data: data, mime_type: mime_type, type: ID3::ImageFrameBody::TYPES[type_index]}
84
+ end
83
85
  end
84
86
  end