wahwah 1.0.0 → 1.3.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 +4 -4
- data/lib/wahwah/asf/object.rb +7 -12
- data/lib/wahwah/asf_tag.rb +199 -198
- data/lib/wahwah/errors.rb +2 -1
- data/lib/wahwah/flac/block.rb +8 -14
- data/lib/wahwah/flac/streaminfo_block.rb +5 -5
- data/lib/wahwah/flac_tag.rb +65 -63
- data/lib/wahwah/helper.rb +8 -8
- data/lib/wahwah/id3/comment_frame_body.rb +1 -1
- data/lib/wahwah/id3/frame.rb +57 -61
- data/lib/wahwah/id3/frame_body.rb +6 -7
- data/lib/wahwah/id3/image_frame_body.rb +5 -5
- data/lib/wahwah/id3/text_frame_body.rb +1 -1
- data/lib/wahwah/id3/v1.rb +59 -58
- data/lib/wahwah/id3/v2.rb +42 -35
- data/lib/wahwah/id3/v2_header.rb +4 -4
- data/lib/wahwah/lazy_read.rb +40 -0
- data/lib/wahwah/mp3/mpeg_frame_header.rb +39 -37
- data/lib/wahwah/mp3/vbri_header.rb +2 -2
- data/lib/wahwah/mp3/xing_header.rb +4 -4
- data/lib/wahwah/mp3_tag.rb +52 -51
- data/lib/wahwah/mp4/atom.rb +30 -34
- data/lib/wahwah/mp4_tag.rb +102 -91
- data/lib/wahwah/ogg/flac_tag.rb +3 -3
- data/lib/wahwah/ogg/opus_tag.rb +3 -3
- data/lib/wahwah/ogg/packets.rb +2 -2
- data/lib/wahwah/ogg/page.rb +3 -3
- data/lib/wahwah/ogg/vorbis_comment.rb +15 -15
- data/lib/wahwah/ogg/vorbis_tag.rb +2 -2
- data/lib/wahwah/ogg_tag.rb +34 -34
- data/lib/wahwah/riff/chunk.rb +11 -15
- data/lib/wahwah/riff_tag.rb +81 -79
- data/lib/wahwah/tag.rb +22 -10
- data/lib/wahwah/version.rb +1 -1
- data/lib/wahwah.rb +58 -53
- metadata +28 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ac83f92fa8a66210ab41b57ab7ccac87f7653573860286a5986ffc17132a57eb
|
4
|
+
data.tar.gz: d197f0523c55af55ef826237b227e21d450b13937585eaa456e18360ae1983e6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d9e893bb99650e639f26546705d0d46bdba50da7377bd11f221247adc1069e97738ad2a4ed134fa078682ccf51cc13a37bf1bb2bebee3891e26de662e0f887cc
|
7
|
+
data.tar.gz: 1bf025ffb3ef82c4870477402befafaedb5c1c654726ba77f61c88d7ef8d6516f6d56c79cf54fc45f319a56d97402f0061d8c5c0e20e3e274afc8d40080e4522
|
data/lib/wahwah/asf/object.rb
CHANGED
@@ -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 =
|
17
|
+
HEADER_FORMAT = "a16Q<"
|
16
18
|
|
17
|
-
attr_reader :
|
19
|
+
attr_reader :guid
|
18
20
|
|
19
|
-
def initialize
|
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
|
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
|
data/lib/wahwah/asf_tag.rb
CHANGED
@@ -3,218 +3,219 @@
|
|
3
3
|
module WahWah
|
4
4
|
class AsfTag < Tag
|
5
5
|
HEADER_OBJECT_CONTENT_SIZE = 6
|
6
|
-
HEADER_OBJECT_GUID =
|
7
|
-
FILE_PROPERTIES_OBJECT_GUID =
|
8
|
-
EXTENDED_CONTENT_DESCRIPTION_OBJECT_GUID =
|
9
|
-
STREAM_PROPERTIES_OBJECT_GUID =
|
10
|
-
AUDIO_MEDIA_OBJECT_GUID =
|
11
|
-
CONTENT_DESCRIPTION_OBJECT_GUID =
|
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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
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
|
-
|
34
|
+
total_header_object_size = header_object.size + Asf::Object::HEADER_SIZE
|
36
35
|
|
37
|
-
|
38
|
-
@file_io.seek(HEADER_OBJECT_CONTENT_SIZE, IO::SEEK_CUR)
|
36
|
+
return unless header_object.guid == HEADER_OBJECT_GUID
|
39
37
|
|
40
|
-
|
41
|
-
|
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
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
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
|
-
|
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
data/lib/wahwah/flac/block.rb
CHANGED
@@ -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 =
|
8
|
-
BLOCK_TYPE_INDEX = %w
|
9
|
+
HEADER_FORMAT = "B*"
|
10
|
+
BLOCK_TYPE_INDEX = %w[STREAMINFO PADDING APPLICATION SEEKTABLE VORBIS_COMMENT CUESHEET PICTURE]
|
9
11
|
|
10
|
-
attr_reader :
|
12
|
+
attr_reader :type
|
11
13
|
|
12
|
-
def initialize
|
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).
|
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
|
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.
|
40
|
+
info_bits = block_data.unpack1("x10B64")
|
41
41
|
|
42
42
|
@sample_rate = info_bits[0..19].to_i(2)
|
43
|
-
|
44
|
-
total_samples = info_bits[28
|
43
|
+
@bit_depth = info_bits[23..27].to_i(2) + 1
|
44
|
+
total_samples = info_bits[28..].to_i(2)
|
45
45
|
|
46
|
-
@duration =
|
47
|
-
@bitrate =
|
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
|