wahwah 1.1.1 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/wahwah/asf/object.rb +2 -2
- data/lib/wahwah/asf_tag.rb +199 -198
- data/lib/wahwah/errors.rb +2 -1
- data/lib/wahwah/flac/block.rb +4 -4
- data/lib/wahwah/flac/streaminfo_block.rb +4 -4
- 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 +47 -46
- 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 +41 -35
- data/lib/wahwah/id3/v2_header.rb +4 -4
- data/lib/wahwah/lazy_read.rb +5 -4
- 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 +51 -48
- data/lib/wahwah/mp4/atom.rb +25 -24
- data/lib/wahwah/mp4_tag.rb +101 -97
- data/lib/wahwah/ogg/flac_tag.rb +2 -2
- 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 +14 -14
- data/lib/wahwah/ogg/vorbis_tag.rb +2 -2
- data/lib/wahwah/ogg_tag.rb +34 -34
- data/lib/wahwah/riff/chunk.rb +7 -6
- data/lib/wahwah/riff_tag.rb +81 -79
- data/lib/wahwah/tag.rb +8 -7
- data/lib/wahwah/version.rb +1 -1
- data/lib/wahwah.rb +58 -58
- metadata +13 -14
@@ -46,41 +46,41 @@ module WahWah
|
|
46
46
|
HEADER_SIZE = 4
|
47
47
|
|
48
48
|
FRAME_BITRATE_INDEX = {
|
49
|
-
|
50
|
-
|
51
|
-
|
49
|
+
"MPEG1 layer1" => [0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0],
|
50
|
+
"MPEG1 layer2" => [0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0],
|
51
|
+
"MPEG1 layer3" => [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0],
|
52
52
|
|
53
|
-
|
54
|
-
|
55
|
-
|
53
|
+
"MPEG2 layer1" => [0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0],
|
54
|
+
"MPEG2 layer2" => [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],
|
55
|
+
"MPEG2 layer3" => [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],
|
56
56
|
|
57
|
-
|
58
|
-
|
59
|
-
|
57
|
+
"MPEG2.5 layer1" => [0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0],
|
58
|
+
"MPEG2.5 layer2" => [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],
|
59
|
+
"MPEG2.5 layer3" => [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0]
|
60
60
|
}
|
61
61
|
|
62
|
-
VERSIONS_INDEX = [
|
63
|
-
LAYER_INDEX = [nil,
|
64
|
-
CHANNEL_MODE_INDEX = [
|
62
|
+
VERSIONS_INDEX = ["MPEG2.5", nil, "MPEG2", "MPEG1"]
|
63
|
+
LAYER_INDEX = [nil, "layer3", "layer2", "layer1"]
|
64
|
+
CHANNEL_MODE_INDEX = ["Stereo", "Joint Stereo", "Dual Channel", "Single Channel"]
|
65
65
|
|
66
66
|
SAMPLE_RATE_INDEX = {
|
67
|
-
|
68
|
-
|
69
|
-
|
67
|
+
"MPEG1" => [44100, 48000, 32000],
|
68
|
+
"MPEG2" => [22050, 24000, 16000],
|
69
|
+
"MPEG2.5" => [11025, 12000, 8000]
|
70
70
|
}
|
71
71
|
|
72
72
|
SAMPLES_PER_FRAME_INDEX = {
|
73
|
-
|
74
|
-
|
75
|
-
|
73
|
+
"MPEG1 layer1" => 384,
|
74
|
+
"MPEG1 layer2" => 1152,
|
75
|
+
"MPEG1 layer3" => 1152,
|
76
76
|
|
77
|
-
|
78
|
-
|
79
|
-
|
77
|
+
"MPEG2 layer1" => 384,
|
78
|
+
"MPEG2 layer2" => 1152,
|
79
|
+
"MPEG2 layer3" => 576,
|
80
80
|
|
81
|
-
|
82
|
-
|
83
|
-
|
81
|
+
"MPEG2.5 layer1" => 384,
|
82
|
+
"MPEG2.5 layer2" => 1152,
|
83
|
+
"MPEG2.5 layer3" => 576
|
84
84
|
}
|
85
85
|
|
86
86
|
attr_reader :version, :layer, :frame_bitrate, :channel_mode, :sample_rate
|
@@ -95,13 +95,14 @@ module WahWah
|
|
95
95
|
break if file_io.eof?
|
96
96
|
|
97
97
|
header = file_io.read(HEADER_SIZE)
|
98
|
-
sync_bits = header.
|
98
|
+
sync_bits = header.unpack1("B11")
|
99
99
|
|
100
|
-
if sync_bits == "
|
101
|
-
@header = header.
|
100
|
+
if sync_bits == ("1" * 11).b
|
101
|
+
@header = header.unpack1("B*")
|
102
102
|
@position = offset
|
103
103
|
|
104
|
-
parse
|
104
|
+
parse
|
105
|
+
break
|
105
106
|
end
|
106
107
|
|
107
108
|
offset += 1
|
@@ -127,15 +128,16 @@ module WahWah
|
|
127
128
|
end
|
128
129
|
|
129
130
|
private
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
131
|
+
|
132
|
+
def parse
|
133
|
+
return unless valid?
|
134
|
+
|
135
|
+
@version = VERSIONS_INDEX[@header[11..12].to_i(2)]
|
136
|
+
@layer = LAYER_INDEX[@header[13..14].to_i(2)]
|
137
|
+
@frame_bitrate = FRAME_BITRATE_INDEX[kind]&.fetch(@header[16..19].to_i(2))
|
138
|
+
@channel_mode = CHANNEL_MODE_INDEX[@header[24..25].to_i(2)]
|
139
|
+
@sample_rate = SAMPLE_RATE_INDEX[@version]&.fetch(@header[20..21].to_i(2))
|
140
|
+
end
|
139
141
|
end
|
140
142
|
end
|
141
143
|
end
|
@@ -30,7 +30,7 @@ module WahWah
|
|
30
30
|
# you can calculate the length of this field.
|
31
31
|
class VbriHeader
|
32
32
|
HEADER_SIZE = 32
|
33
|
-
HEADER_FORMAT =
|
33
|
+
HEADER_FORMAT = "A4x6NN"
|
34
34
|
|
35
35
|
attr_reader :frames_count, :bytes_count
|
36
36
|
|
@@ -40,7 +40,7 @@ module WahWah
|
|
40
40
|
end
|
41
41
|
|
42
42
|
def valid?
|
43
|
-
@id ==
|
43
|
+
@id == "VBRI"
|
44
44
|
end
|
45
45
|
end
|
46
46
|
end
|
@@ -30,15 +30,15 @@ module WahWah
|
|
30
30
|
def initialize(file_io, offset = 0)
|
31
31
|
file_io.seek(offset)
|
32
32
|
|
33
|
-
@id, @flags = file_io.read(8)&.unpack(
|
33
|
+
@id, @flags = file_io.read(8)&.unpack("A4N")
|
34
34
|
return unless valid?
|
35
35
|
|
36
|
-
@frames_count = @flags & 1 == 1 ? file_io.read(4).
|
37
|
-
@bytes_count = @flags & 2 == 2 ? file_io.read(4).
|
36
|
+
@frames_count = @flags & 1 == 1 ? file_io.read(4).unpack1("N") : 0
|
37
|
+
@bytes_count = @flags & 2 == 2 ? file_io.read(4).unpack1("N") : 0
|
38
38
|
end
|
39
39
|
|
40
40
|
def valid?
|
41
|
-
%w
|
41
|
+
%w[Xing Info].include? @id
|
42
42
|
end
|
43
43
|
end
|
44
44
|
end
|
data/lib/wahwah/mp3_tag.rb
CHANGED
@@ -42,67 +42,70 @@ module WahWah
|
|
42
42
|
end
|
43
43
|
|
44
44
|
private
|
45
|
-
def parse
|
46
|
-
@id3_tag = parse_id3_tag
|
47
|
-
parse_duration if mpeg_frame_header.valid?
|
48
|
-
end
|
49
45
|
|
50
|
-
|
51
|
-
|
52
|
-
|
46
|
+
def parse
|
47
|
+
@id3_tag = parse_id3_tag
|
48
|
+
parse_duration if mpeg_frame_header.valid?
|
49
|
+
end
|
53
50
|
|
54
|
-
|
55
|
-
|
56
|
-
|
51
|
+
def parse_id3_tag
|
52
|
+
id3_v1_tag = ID3::V1.new(@file_io.dup)
|
53
|
+
id3_v2_tag = ID3::V2.new(@file_io.dup)
|
57
54
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
@bitrate = bytes_count * 8 / @duration / 1000 unless @duration.zero?
|
62
|
-
else
|
63
|
-
@bitrate = mpeg_frame_header.frame_bitrate
|
64
|
-
@duration = (file_size - (@id3_tag&.size || 0)) * 8 / (@bitrate * 1000) unless @bitrate.zero?
|
65
|
-
end
|
66
|
-
end
|
55
|
+
return id3_v2_tag if id3_v2_tag.valid?
|
56
|
+
id3_v1_tag if id3_v1_tag.valid?
|
57
|
+
end
|
67
58
|
|
68
|
-
|
69
|
-
|
70
|
-
@
|
59
|
+
def parse_duration
|
60
|
+
if is_vbr?
|
61
|
+
@duration = frames_count * (mpeg_frame_header.samples_per_frame / sample_rate.to_f)
|
62
|
+
@bitrate = (bytes_count * 8 / @duration / 1000).round unless @duration.zero?
|
63
|
+
else
|
64
|
+
@bitrate = mpeg_frame_header.frame_bitrate
|
65
|
+
@duration = (file_size - (@id3_tag&.size || 0)) * 8 / (@bitrate * 1000).to_f unless @bitrate.zero?
|
71
66
|
end
|
67
|
+
end
|
72
68
|
|
73
|
-
|
74
|
-
|
75
|
-
|
69
|
+
def mpeg_frame_header
|
70
|
+
# Because id3v2 tag on the file header so skip id3v2 tag
|
71
|
+
@mpeg_frame_header ||= Mp3::MpegFrameHeader.new(@file_io, id3v2? ? @id3_tag&.size : 0)
|
72
|
+
end
|
76
73
|
|
77
|
-
|
78
|
-
|
79
|
-
|
74
|
+
def xing_header
|
75
|
+
@xing_header ||= Mp3::XingHeader.new(@file_io, xing_header_offset)
|
76
|
+
end
|
80
77
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
mpeg_frame_side_info_size = mpeg_version == 'MPEG1' ?
|
85
|
-
(channel_mode == 'Single Channel' ? 17 : 32) :
|
86
|
-
(channel_mode == 'Single Channel' ? 9 : 17)
|
78
|
+
def vbri_header
|
79
|
+
@vbri_header ||= Mp3::VbriHeader.new(@file_io, vbri_header_offset)
|
80
|
+
end
|
87
81
|
|
88
|
-
|
82
|
+
def xing_header_offset
|
83
|
+
mpeg_frame_header_position = mpeg_frame_header.position
|
84
|
+
mpeg_frame_header_size = Mp3::MpegFrameHeader::HEADER_SIZE
|
85
|
+
mpeg_frame_side_info_size = if mpeg_version == "MPEG1"
|
86
|
+
channel_mode == "Single Channel" ? 17 : 32
|
87
|
+
else
|
88
|
+
channel_mode == "Single Channel" ? 9 : 17
|
89
89
|
end
|
90
90
|
|
91
|
-
|
92
|
-
|
93
|
-
mpeg_frame_header_size = Mp3::MpegFrameHeader::HEADER_SIZE
|
91
|
+
mpeg_frame_header_position + mpeg_frame_header_size + mpeg_frame_side_info_size
|
92
|
+
end
|
94
93
|
|
95
|
-
|
96
|
-
|
94
|
+
def vbri_header_offset
|
95
|
+
mpeg_frame_header_position = mpeg_frame_header.position
|
96
|
+
mpeg_frame_header_size = Mp3::MpegFrameHeader::HEADER_SIZE
|
97
97
|
|
98
|
-
|
99
|
-
|
100
|
-
vbri_header.frames_count if vbri_header.valid?
|
101
|
-
end
|
98
|
+
mpeg_frame_header_position + mpeg_frame_header_size + 32
|
99
|
+
end
|
102
100
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
101
|
+
def frames_count
|
102
|
+
return xing_header.frames_count if xing_header.valid?
|
103
|
+
vbri_header.frames_count if vbri_header.valid?
|
104
|
+
end
|
105
|
+
|
106
|
+
def bytes_count
|
107
|
+
return xing_header.bytes_count if xing_header.valid?
|
108
|
+
vbri_header.bytes_count if vbri_header.valid?
|
109
|
+
end
|
107
110
|
end
|
108
111
|
end
|
data/lib/wahwah/mp4/atom.rb
CHANGED
@@ -5,8 +5,8 @@ module WahWah
|
|
5
5
|
class Atom
|
6
6
|
prepend LazyRead
|
7
7
|
|
8
|
-
VERSIONED_ATOMS = %w
|
9
|
-
FLAGGED_ATOMS = %w
|
8
|
+
VERSIONED_ATOMS = %w[meta stsd]
|
9
|
+
FLAGGED_ATOMS = %w[stsd]
|
10
10
|
HEADER_SIZE = 8
|
11
11
|
HEADER_SIZE_FIELD_SIZE = 4
|
12
12
|
EXTENDED_HEADER_SIZE = 8
|
@@ -22,14 +22,15 @@ module WahWah
|
|
22
22
|
atom = new(file_io)
|
23
23
|
|
24
24
|
next unless atom.valid?
|
25
|
-
file_io.seek(atom.size, IO::SEEK_CUR)
|
25
|
+
file_io.seek(atom.size, IO::SEEK_CUR)
|
26
|
+
next unless atom.type == atom_type
|
26
27
|
|
27
28
|
return atom if atom_path.empty?
|
28
29
|
return atom.find(*atom_path)
|
29
30
|
end
|
30
31
|
|
31
32
|
# Return empty atom if can not found
|
32
|
-
new(StringIO.new(
|
33
|
+
new(StringIO.new(""))
|
33
34
|
end
|
34
35
|
|
35
36
|
# An atom header consists of the following fields:
|
@@ -42,17 +43,17 @@ module WahWah
|
|
42
43
|
# A 32-bit integer that contains the type of the atom.
|
43
44
|
# This can often be usefully treated as a four-character field with a mnemonic value .
|
44
45
|
def initialize
|
45
|
-
@size, @type = @file_io.read(HEADER_SIZE)&.unpack(
|
46
|
+
@size, @type = @file_io.read(HEADER_SIZE)&.unpack("Na4")
|
46
47
|
return unless valid?
|
47
48
|
|
48
49
|
# If the size field of an atom is set to 1, the type field is followed by a 64-bit extended size field,
|
49
50
|
# which contains the actual size of the atom as a 64-bit unsigned integer.
|
50
|
-
@size = @file_io.read(EXTENDED_HEADER_SIZE).
|
51
|
+
@size = @file_io.read(EXTENDED_HEADER_SIZE).unpack1("Q>") - EXTENDED_HEADER_SIZE if @size == 1
|
51
52
|
|
52
53
|
# If the size field of an atom is set to 0, which is allowed only for a top-level atom,
|
53
54
|
# designates the last atom in the file and indicates that the atom extends to the end of the file.
|
54
55
|
@size = @file_io.size if @size == 0
|
55
|
-
@size
|
56
|
+
@size -= HEADER_SIZE
|
56
57
|
end
|
57
58
|
|
58
59
|
def valid?
|
@@ -63,38 +64,38 @@ module WahWah
|
|
63
64
|
child_atom_index = data.index(atom_path.first)
|
64
65
|
|
65
66
|
# Return empty atom if can not found
|
66
|
-
return self.class.new(StringIO.new(
|
67
|
+
return self.class.new(StringIO.new("")) if child_atom_index.nil?
|
67
68
|
|
68
69
|
# Because before atom type field there are 4 bytes of size field,
|
69
70
|
# So the child_atom_index should reduce 4.
|
70
|
-
self.class.find(StringIO.new(data[child_atom_index - HEADER_SIZE_FIELD_SIZE
|
71
|
+
self.class.find(StringIO.new(data[child_atom_index - HEADER_SIZE_FIELD_SIZE..]), *atom_path)
|
71
72
|
end
|
72
73
|
|
73
|
-
|
74
74
|
def children
|
75
75
|
@children ||= parse_children_atoms
|
76
76
|
end
|
77
77
|
|
78
78
|
private
|
79
|
-
def parse_children_atoms
|
80
|
-
children_atoms = []
|
81
|
-
atoms_data = data
|
82
79
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
atoms_data = atoms_data[4..-1] if FLAGGED_ATOMS.include? type # Skip 4 bytes for flag
|
87
|
-
atoms_data_io = StringIO.new(atoms_data)
|
80
|
+
def parse_children_atoms
|
81
|
+
children_atoms = []
|
82
|
+
atoms_data = data
|
88
83
|
|
89
|
-
|
90
|
-
|
91
|
-
|
84
|
+
# Some atoms data contain extra content before child atom data.
|
85
|
+
# So reduce those extra content to get child atom data.
|
86
|
+
atoms_data = atoms_data[4..] if VERSIONED_ATOMS.include? type # Skip 4 bytes for version
|
87
|
+
atoms_data = atoms_data[4..] if FLAGGED_ATOMS.include? type # Skip 4 bytes for flag
|
88
|
+
atoms_data_io = StringIO.new(atoms_data)
|
92
89
|
|
93
|
-
|
94
|
-
|
90
|
+
until atoms_data_io.eof?
|
91
|
+
atom = self.class.new(atoms_data_io)
|
92
|
+
children_atoms.push(atom)
|
95
93
|
|
96
|
-
|
94
|
+
atom.skip
|
97
95
|
end
|
96
|
+
|
97
|
+
children_atoms
|
98
|
+
end
|
98
99
|
end
|
99
100
|
end
|
100
101
|
end
|
data/lib/wahwah/mp4_tag.rb
CHANGED
@@ -10,124 +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
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
@comments.push(encoded_data_value)
|
79
|
-
when :track, :disc
|
80
|
-
count, total_count = encoded_data_value.unpack('x2nn')
|
81
|
-
|
82
|
-
instance_variable_set("@#{attr_name}", count) unless count.zero?
|
83
|
-
instance_variable_set("@#{attr_name}_total", total_count) unless total_count.zero?
|
84
|
-
else
|
85
|
-
instance_variable_set("@#{attr_name}", encoded_data_value)
|
86
|
-
end
|
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("udta", "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
|
87
75
|
end
|
88
|
-
end
|
89
76
|
|
90
|
-
|
91
|
-
|
92
|
-
|
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")
|
85
|
+
|
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)
|
90
|
+
end
|
93
91
|
end
|
92
|
+
end
|
94
93
|
|
95
|
-
|
96
|
-
|
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
|
97
98
|
|
98
|
-
|
99
|
-
|
99
|
+
def parse_mvhd_atom(atom)
|
100
|
+
return unless atom.valid?
|
100
101
|
|
101
|
-
|
102
|
-
|
102
|
+
atom_data = StringIO.new(atom.data)
|
103
|
+
version = atom_data.read(1).unpack1("c")
|
103
104
|
|
104
|
-
|
105
|
-
|
106
|
-
atom_data.seek(8, IO::SEEK_CUR)
|
105
|
+
# Skip flags
|
106
|
+
atom_data.seek(3, IO::SEEK_CUR)
|
107
107
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
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)
|
112
111
|
|
113
|
-
|
114
|
-
|
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)
|
115
116
|
|
116
|
-
|
117
|
+
time_scale, duration = atom_data.read(12).unpack("l>q>")
|
117
118
|
end
|
118
119
|
|
119
|
-
|
120
|
-
|
120
|
+
@duration = duration / time_scale.to_f
|
121
|
+
end
|
121
122
|
|
122
|
-
|
123
|
-
|
123
|
+
def parse_stsd_atom(atom)
|
124
|
+
return unless atom.valid?
|
124
125
|
|
125
|
-
|
126
|
-
|
127
|
-
end
|
126
|
+
mp4a_atom = atom.find("mp4a")
|
127
|
+
esds_atom = atom.find("esds")
|
128
128
|
|
129
|
-
|
130
|
-
|
131
|
-
|
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
|
132
136
|
end
|
133
137
|
end
|
data/lib/wahwah/ogg/flac_tag.rb
CHANGED
@@ -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
|