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
data/lib/wahwah/mp4_tag.rb
CHANGED
@@ -10,117 +10,128 @@ module WahWah
|
|
10
10
|
"\xA9day".b => :year,
|
11
11
|
"\xA9gen".b => :genre,
|
12
12
|
"\xA9nam".b => :title,
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
"covr".b => :image,
|
14
|
+
"disk".b => :disc,
|
15
|
+
"trkn".b => :track,
|
16
|
+
"aART".b => :albumartist
|
17
17
|
}
|
18
18
|
|
19
19
|
META_ATOM_DECODE_BY_TYPE = {
|
20
|
-
0 => ->
|
21
|
-
1 => ->
|
22
|
-
2 => ->
|
23
|
-
3 => ->
|
24
|
-
|
25
|
-
13 => ->
|
26
|
-
14 => ->
|
27
|
-
|
28
|
-
21 => ->
|
29
|
-
22 => ->
|
30
|
-
23 => ->
|
31
|
-
24 => ->
|
32
|
-
|
33
|
-
65 => ->
|
34
|
-
66 => ->
|
35
|
-
67 => ->
|
36
|
-
74 => ->
|
37
|
-
|
38
|
-
75 => ->
|
39
|
-
76 => ->
|
40
|
-
77 => ->
|
41
|
-
78 => ->
|
20
|
+
0 => ->(data) { data }, # reserved
|
21
|
+
1 => ->(data) { Helper.encode_to_utf8(data) }, # UTF-8
|
22
|
+
2 => ->(data) { Helper.encode_to_utf8(data, "UTF-16BE") }, # UTF-16BE
|
23
|
+
3 => ->(data) { Helper.encode_to_utf8(data, "SJIS") }, # SJIS
|
24
|
+
|
25
|
+
13 => ->(data) { {data: data, mime_type: "image/jpeg", type: :cover} }, # JPEG
|
26
|
+
14 => ->(data) { {data: data, mime_type: "image/png", type: :cover} }, # PNG
|
27
|
+
|
28
|
+
21 => ->(data) { data.unpack1("i>") }, # Big endian signed integer
|
29
|
+
22 => ->(data) { data.unpack1("I>") }, # Big endian unsigned integer
|
30
|
+
23 => ->(data) { data.unpack1("g") }, # Big endian 32-bit floating point value
|
31
|
+
24 => ->(data) { data.unpack1("G") }, # Big endian 64-bit floating point value
|
32
|
+
|
33
|
+
65 => ->(data) { data.unpack1("c") }, # 8-bit signed integer
|
34
|
+
66 => ->(data) { data.unpack1("s>") }, # Big-endian 16-bit signed integer
|
35
|
+
67 => ->(data) { data.unpack1("l>") }, # Big-endian 32-bit signed integer
|
36
|
+
74 => ->(data) { data.unpack1("q>") }, # Big-endian 64-bit signed integer
|
37
|
+
|
38
|
+
75 => ->(data) { data.unpack1("C") }, # 8-bit unsigned integer
|
39
|
+
76 => ->(data) { data.unpack1("S>") }, # Big-endian 16-bit unsigned integer
|
40
|
+
77 => ->(data) { data.unpack1("L>") }, # Big-endian 32-bit unsigned integer
|
41
|
+
78 => ->(data) { data.unpack1("Q>") } # Big-endian 64-bit unsigned integer
|
42
42
|
}
|
43
43
|
|
44
44
|
private
|
45
|
-
def parse
|
46
|
-
movie_atom = Mp4::Atom.find(@file_io, 'moov')
|
47
|
-
return unless movie_atom.valid?
|
48
45
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
46
|
+
def parse
|
47
|
+
movie_atom = Mp4::Atom.find(@file_io, "moov")
|
48
|
+
return unless movie_atom.valid?
|
49
|
+
|
50
|
+
parse_meta_list_atom movie_atom.find("meta", "ilst")
|
51
|
+
parse_mvhd_atom movie_atom.find("mvhd")
|
52
|
+
parse_stsd_atom movie_atom.find("trak", "mdia", "minf", "stbl", "stsd")
|
53
|
+
end
|
54
|
+
|
55
|
+
def parse_meta_list_atom(atom)
|
56
|
+
return unless atom.valid?
|
57
|
+
|
58
|
+
# The metadata item list atom holds a list of actual metadata values that are present in the metadata atom.
|
59
|
+
# The metadata items are formatted as a list of items.
|
60
|
+
# The metadata item list atom is of type ‘ilst’ and contains a number of metadata items, each of which is an atom.
|
61
|
+
# each metadata item atom contains a Value Atom, to hold the value of the metadata item
|
62
|
+
atom.children.each do |child_atom|
|
63
|
+
attr_name = META_ATOM_MAPPING[child_atom.type]
|
64
|
+
|
65
|
+
# The value of the metadata item is expressed as immediate data in a value atom.
|
66
|
+
# The value atom starts with two fields: a type indicator, and a locale indicator.
|
67
|
+
# Both the type and locale indicators are four bytes long.
|
68
|
+
# There may be multiple ‘value’ entries, using different type
|
69
|
+
data_atom = child_atom.find("data")
|
70
|
+
next unless data_atom.valid?
|
71
|
+
|
72
|
+
if attr_name == :image
|
73
|
+
@images_data.push(data_atom)
|
74
|
+
next
|
75
|
+
end
|
76
|
+
|
77
|
+
encoded_data_value = parse_meta_data_atom(data_atom)
|
78
|
+
next if attr_name.nil? || encoded_data_value.nil?
|
79
|
+
|
80
|
+
case attr_name
|
81
|
+
when :comment
|
82
|
+
@comments.push(encoded_data_value)
|
83
|
+
when :track, :disc
|
84
|
+
count, total_count = encoded_data_value.unpack("x2nn")
|
53
85
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
# The metadata items are formatted as a list of items.
|
59
|
-
# The metadata item list atom is of type ‘ilst’ and contains a number of metadata items, each of which is an atom.
|
60
|
-
# each metadata item atom contains a Value Atom, to hold the value of the metadata item
|
61
|
-
atom.children.each do |child_atom|
|
62
|
-
attribute_name = META_ATOM_MAPPING[child_atom.type]
|
63
|
-
|
64
|
-
# The value of the metadata item is expressed as immediate data in a value atom.
|
65
|
-
# The value atom starts with two fields: a type indicator, and a locale indicator.
|
66
|
-
# Both the type and locale indicators are four bytes long.
|
67
|
-
# There may be multiple ‘value’ entries, using different type
|
68
|
-
data_atom = child_atom.find('data')
|
69
|
-
return unless data_atom.valid?
|
70
|
-
|
71
|
-
data_type, data_value = data_atom.data.unpack('Nx4a*')
|
72
|
-
encoded_data_value = META_ATOM_DECODE_BY_TYPE[data_type]&.call(data_value)
|
73
|
-
|
74
|
-
next if attribute_name.nil? || encoded_data_value.nil?
|
75
|
-
|
76
|
-
case attribute_name
|
77
|
-
when :image
|
78
|
-
@images.push(encoded_data_value)
|
79
|
-
when :comment
|
80
|
-
@comments.push(encoded_data_value)
|
81
|
-
when :track, :disc
|
82
|
-
count, total_count = encoded_data_value.unpack('x2nn')
|
83
|
-
|
84
|
-
instance_variable_set("@#{attribute_name}", count) unless count.zero?
|
85
|
-
instance_variable_set("@#{attribute_name}_total", total_count) unless total_count.zero?
|
86
|
-
else
|
87
|
-
instance_variable_set("@#{attribute_name}", encoded_data_value)
|
88
|
-
end
|
86
|
+
instance_variable_set("@#{attr_name}", count) unless count.zero?
|
87
|
+
instance_variable_set("@#{attr_name}_total", total_count) unless total_count.zero?
|
88
|
+
else
|
89
|
+
instance_variable_set("@#{attr_name}", encoded_data_value)
|
89
90
|
end
|
90
91
|
end
|
92
|
+
end
|
91
93
|
|
92
|
-
|
93
|
-
|
94
|
+
def parse_meta_data_atom(atom)
|
95
|
+
data_type, data_value = atom.data.unpack("Nx4a*")
|
96
|
+
META_ATOM_DECODE_BY_TYPE[data_type]&.call(data_value)
|
97
|
+
end
|
94
98
|
|
95
|
-
|
96
|
-
|
99
|
+
def parse_mvhd_atom(atom)
|
100
|
+
return unless atom.valid?
|
97
101
|
|
98
|
-
|
99
|
-
|
102
|
+
atom_data = StringIO.new(atom.data)
|
103
|
+
version = atom_data.read(1).unpack1("c")
|
100
104
|
|
101
|
-
|
102
|
-
|
103
|
-
atom_data.seek(8, IO::SEEK_CUR)
|
105
|
+
# Skip flags
|
106
|
+
atom_data.seek(3, IO::SEEK_CUR)
|
104
107
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
atom_data.seek(16, IO::SEEK_CUR)
|
108
|
+
if version == 0
|
109
|
+
# Skip creation and modification time
|
110
|
+
atom_data.seek(8, IO::SEEK_CUR)
|
109
111
|
|
110
|
-
|
111
|
-
|
112
|
+
time_scale, duration = atom_data.read(8).unpack("l>l>")
|
113
|
+
elsif version == 1
|
114
|
+
# Skip creation and modification time
|
115
|
+
atom_data.seek(16, IO::SEEK_CUR)
|
112
116
|
|
113
|
-
|
117
|
+
time_scale, duration = atom_data.read(12).unpack("l>q>")
|
114
118
|
end
|
115
119
|
|
116
|
-
|
117
|
-
|
120
|
+
@duration = duration / time_scale.to_f
|
121
|
+
end
|
118
122
|
|
119
|
-
|
120
|
-
|
123
|
+
def parse_stsd_atom(atom)
|
124
|
+
return unless atom.valid?
|
121
125
|
|
122
|
-
|
123
|
-
|
124
|
-
|
126
|
+
mp4a_atom = atom.find("mp4a")
|
127
|
+
esds_atom = atom.find("esds")
|
128
|
+
|
129
|
+
@sample_rate = mp4a_atom.data.unpack1("x22I>") if mp4a_atom.valid?
|
130
|
+
@bitrate = esds_atom.data.unpack1("x26I>") / 1000 if esds_atom.valid?
|
131
|
+
end
|
132
|
+
|
133
|
+
def parse_image_data(image_data_atom)
|
134
|
+
parse_meta_data_atom(image_data_atom)
|
135
|
+
end
|
125
136
|
end
|
126
137
|
end
|
data/lib/wahwah/ogg/flac_tag.rb
CHANGED
@@ -6,7 +6,7 @@ module WahWah
|
|
6
6
|
include VorbisComment
|
7
7
|
include Flac::StreaminfoBlock
|
8
8
|
|
9
|
-
attr_reader :bitrate, :duration, :sample_rate, *COMMET_FIELD_MAPPING.values
|
9
|
+
attr_reader :bitrate, :duration, :sample_rate, :bit_depth, *COMMET_FIELD_MAPPING.values
|
10
10
|
|
11
11
|
def initialize(identification_packet, comment_packet)
|
12
12
|
# Identification packet structure:
|
@@ -23,9 +23,9 @@ module WahWah
|
|
23
23
|
# Each such packet will contain a single native FLAC metadata block.
|
24
24
|
# The first of these must be a VORBIS_COMMENT block.
|
25
25
|
|
26
|
-
id, streaminfo_block_data = identification_packet.unpack(
|
26
|
+
id, streaminfo_block_data = identification_packet.unpack("x9A4A*")
|
27
27
|
|
28
|
-
return unless id ==
|
28
|
+
return unless id == "fLaC"
|
29
29
|
streaminfo_block = Flac::Block.new(StringIO.new(streaminfo_block_data))
|
30
30
|
vorbis_comment_block = Flac::Block.new(StringIO.new(comment_packet))
|
31
31
|
|
data/lib/wahwah/ogg/opus_tag.rb
CHANGED
@@ -19,12 +19,12 @@ module WahWah
|
|
19
19
|
# 7) [channel_mapping_family] = read 8 bit as unsigned integer
|
20
20
|
# 8) [channel_mapping_table]
|
21
21
|
@sample_rate = 48000
|
22
|
-
@pre_skip = identification_packet[10..11].
|
22
|
+
@pre_skip = identification_packet[10..11].unpack1("v")
|
23
23
|
|
24
|
-
comment_packet_id, comment_packet_body = [comment_packet[0..7], comment_packet[8
|
24
|
+
comment_packet_id, comment_packet_body = [comment_packet[0..7], comment_packet[8..]]
|
25
25
|
|
26
26
|
# Opus comment packet start with 'OpusTags'
|
27
|
-
return unless comment_packet_id ==
|
27
|
+
return unless comment_packet_id == "OpusTags"
|
28
28
|
|
29
29
|
parse_vorbis_comment(comment_packet_body)
|
30
30
|
end
|
data/lib/wahwah/ogg/packets.rb
CHANGED
@@ -20,7 +20,7 @@ module WahWah
|
|
20
20
|
def each
|
21
21
|
@file_io.rewind
|
22
22
|
|
23
|
-
packet = +
|
23
|
+
packet = +""
|
24
24
|
pages = Ogg::Pages.new(@file_io)
|
25
25
|
|
26
26
|
pages.each do |page|
|
@@ -31,7 +31,7 @@ module WahWah
|
|
31
31
|
# So when segment length is less than 255 byte, it's the final segment.
|
32
32
|
if segment.length < 255
|
33
33
|
yield packet
|
34
|
-
packet = +
|
34
|
+
packet = +""
|
35
35
|
end
|
36
36
|
end
|
37
37
|
end
|
data/lib/wahwah/ogg/page.rb
CHANGED
@@ -99,7 +99,7 @@ module WahWah
|
|
99
99
|
# lacing value.
|
100
100
|
class Page
|
101
101
|
HEADER_SIZE = 27
|
102
|
-
HEADER_FORMAT =
|
102
|
+
HEADER_FORMAT = "A4CxQx12C"
|
103
103
|
|
104
104
|
attr_reader :segments, :granule_position
|
105
105
|
|
@@ -109,12 +109,12 @@ module WahWah
|
|
109
109
|
|
110
110
|
return unless valid?
|
111
111
|
|
112
|
-
segment_table = file_io.read(page_segments).unpack(
|
112
|
+
segment_table = file_io.read(page_segments).unpack("C" * page_segments)
|
113
113
|
@segments = segment_table.map { |segment_length| file_io.read(segment_length) }
|
114
114
|
end
|
115
115
|
|
116
116
|
def valid?
|
117
|
-
@capture_pattern ==
|
117
|
+
@capture_pattern == "OggS" && @version == 0
|
118
118
|
end
|
119
119
|
end
|
120
120
|
end
|
@@ -16,32 +16,32 @@ module WahWah
|
|
16
16
|
# 9) done.
|
17
17
|
module VorbisComment
|
18
18
|
COMMET_FIELD_MAPPING = {
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
19
|
+
"TITLE" => :title,
|
20
|
+
"ALBUM" => :album,
|
21
|
+
"ALBUMARTIST" => :albumartist,
|
22
|
+
"TRACKNUMBER" => :track,
|
23
|
+
"ARTIST" => :artist,
|
24
|
+
"DATE" => :year,
|
25
|
+
"GENRE" => :genre,
|
26
|
+
"DISCNUMBER" => :disc,
|
27
|
+
"COMPOSER" => :composer
|
28
28
|
}
|
29
29
|
|
30
30
|
def parse_vorbis_comment(comment_content)
|
31
31
|
comment_content = StringIO.new(comment_content)
|
32
32
|
|
33
|
-
vendor_length = comment_content.read(4).
|
33
|
+
vendor_length = comment_content.read(4).unpack1("V")
|
34
34
|
comment_content.seek(vendor_length, IO::SEEK_CUR) # Skip vendor_string
|
35
35
|
|
36
|
-
comment_list_length = comment_content.read(4).
|
36
|
+
comment_list_length = comment_content.read(4).unpack1("V")
|
37
37
|
|
38
38
|
comment_list_length.times do
|
39
|
-
comment_length = comment_content.read(4).
|
39
|
+
comment_length = comment_content.read(4).unpack1("V")
|
40
40
|
comment = Helper.encode_to_utf8(comment_content.read(comment_length))
|
41
|
-
field_name, field_value = comment.split(
|
42
|
-
attr_name = COMMET_FIELD_MAPPING[field_name]
|
41
|
+
field_name, field_value = comment.split("=", 2)
|
42
|
+
attr_name = COMMET_FIELD_MAPPING[field_name&.upcase]
|
43
43
|
|
44
|
-
field_value = field_value.to_i if %i
|
44
|
+
field_value = field_value.to_i if %i[track disc].include? attr_name
|
45
45
|
|
46
46
|
instance_variable_set("@#{attr_name}", field_value) unless attr_name.nil?
|
47
47
|
end
|
@@ -20,10 +20,10 @@ module WahWah
|
|
20
20
|
# 8) [blocksize_0] = 2 exponent (read 4 bits as unsigned integer)
|
21
21
|
# 9) [blocksize_1] = 2 exponent (read 4 bits as unsigned integer)
|
22
22
|
# 10) [framing_flag] = read one bit
|
23
|
-
@sample_rate, bitrate = identification_packet[12, 12].unpack(
|
23
|
+
@sample_rate, bitrate = identification_packet[12, 12].unpack("Vx4V")
|
24
24
|
@bitrate = bitrate / 1000
|
25
25
|
|
26
|
-
comment_packet_id, comment_packet_body = [comment_packet[0..6], comment_packet[7
|
26
|
+
comment_packet_id, comment_packet_body = [comment_packet[0..6], comment_packet[7..]]
|
27
27
|
|
28
28
|
# Vorbis comment packet start with "\x03vorbis"
|
29
29
|
return unless comment_packet_id == "\x03vorbis"
|
data/lib/wahwah/ogg_tag.rb
CHANGED
@@ -16,51 +16,51 @@ module WahWah
|
|
16
16
|
:composer,
|
17
17
|
:sample_rate
|
18
18
|
|
19
|
-
|
20
|
-
|
19
|
+
private
|
20
|
+
|
21
|
+
def packets
|
22
|
+
@packets ||= Ogg::Packets.new(@file_io)
|
21
23
|
end
|
22
24
|
|
23
|
-
def
|
24
|
-
@
|
25
|
+
def pages
|
26
|
+
@pages ||= Ogg::Pages.new(@file_io)
|
25
27
|
end
|
26
28
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
end
|
29
|
+
def parse
|
30
|
+
identification_packet, comment_packet = packets.first(2)
|
31
|
+
return if identification_packet.nil? || comment_packet.nil?
|
31
32
|
|
32
|
-
|
33
|
-
|
33
|
+
@overhead_packets_size = identification_packet.size + comment_packet.size
|
34
|
+
|
35
|
+
@tag = if identification_packet.start_with?("\x01vorbis")
|
36
|
+
Ogg::VorbisTag.new(identification_packet, comment_packet)
|
37
|
+
elsif identification_packet.start_with?("OpusHead")
|
38
|
+
Ogg::OpusTag.new(identification_packet, comment_packet)
|
39
|
+
elsif identification_packet.start_with?("\x7FFLAC")
|
40
|
+
Ogg::FlacTag.new(identification_packet, comment_packet)
|
34
41
|
end
|
35
42
|
|
36
|
-
|
37
|
-
|
38
|
-
|
43
|
+
@duration = parse_duration
|
44
|
+
@bitrate = parse_bitrate
|
45
|
+
@bit_depth = parse_bit_depth
|
46
|
+
end
|
39
47
|
|
40
|
-
|
48
|
+
def parse_duration
|
49
|
+
return @tag.duration if @tag.respond_to? :duration
|
41
50
|
|
42
|
-
|
43
|
-
|
44
|
-
Ogg::VorbisTag.new(identification_packet, comment_packet)
|
45
|
-
when identification_packet.start_with?('OpusHead')
|
46
|
-
Ogg::OpusTag.new(identification_packet, comment_packet)
|
47
|
-
when identification_packet.start_with?("\x7FFLAC")
|
48
|
-
Ogg::FlacTag.new(identification_packet, comment_packet)
|
49
|
-
end
|
50
|
-
end
|
51
|
+
last_page = pages.to_a.last
|
52
|
+
pre_skip = @tag.respond_to?(:pre_skip) ? @tag.pre_skip : 0
|
51
53
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
last_page = pages.to_a.last
|
56
|
-
pre_skip = @tag.respond_to?(:pre_skip) ? @tag.pre_skip : 0
|
54
|
+
(last_page.granule_position - pre_skip) / @tag.sample_rate.to_f
|
55
|
+
end
|
57
56
|
|
58
|
-
|
59
|
-
|
57
|
+
def parse_bitrate
|
58
|
+
return @tag.bitrate if @tag.respond_to? :bitrate
|
59
|
+
((file_size - @overhead_packets_size) * 8.0 / duration / 1000).round
|
60
|
+
end
|
60
61
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
end
|
62
|
+
def parse_bit_depth
|
63
|
+
@tag.bit_depth if @tag.respond_to? :bit_depth
|
64
|
+
end
|
65
65
|
end
|
66
66
|
end
|
data/lib/wahwah/riff/chunk.rb
CHANGED
@@ -16,39 +16,35 @@ module WahWah
|
|
16
16
|
# 4 bytes: an ASCII identifier for this particular RIFF or LIST chunk (for RIFF in the typical case, these 4 bytes describe the content of the entire file, such as "AVI " or "WAVE").
|
17
17
|
# rest of data: subchunks.
|
18
18
|
class Chunk
|
19
|
+
prepend LazyRead
|
20
|
+
|
19
21
|
HEADER_SIZE = 8
|
20
|
-
HEADER_FORMAT =
|
22
|
+
HEADER_FORMAT = "A4V"
|
21
23
|
HEADER_TYPE_SIZE = 4
|
22
24
|
|
23
25
|
attr_reader :id, :type
|
24
26
|
|
25
|
-
def initialize
|
26
|
-
@id, @size = file_io.read(HEADER_SIZE)&.unpack(HEADER_FORMAT)
|
27
|
+
def initialize
|
28
|
+
@id, @size = @file_io.read(HEADER_SIZE)&.unpack(HEADER_FORMAT)
|
27
29
|
return unless valid?
|
28
30
|
|
29
|
-
@type = file_io.read(HEADER_TYPE_SIZE).
|
30
|
-
@file_io = file_io
|
31
|
-
@position = file_io.pos
|
31
|
+
@type = @file_io.read(HEADER_TYPE_SIZE).unpack1("A4") if have_type?
|
32
32
|
end
|
33
33
|
|
34
34
|
def size
|
35
|
-
@size
|
35
|
+
@size += 1 if @size.odd?
|
36
36
|
have_type? ? @size - HEADER_TYPE_SIZE : @size
|
37
37
|
end
|
38
38
|
|
39
|
-
def data
|
40
|
-
@file_io.seek(@position)
|
41
|
-
@file_io.read(size)
|
42
|
-
end
|
43
|
-
|
44
39
|
def valid?
|
45
40
|
!@id.empty? && !@size.nil? && @size > 0
|
46
41
|
end
|
47
42
|
|
48
43
|
private
|
49
|
-
|
50
|
-
|
51
|
-
|
44
|
+
|
45
|
+
def have_type?
|
46
|
+
%w[RIFF LIST].include? @id
|
47
|
+
end
|
52
48
|
end
|
53
49
|
end
|
54
50
|
end
|