wahwah 0.1.0.pre.test → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +8 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +22 -0
- data/README.md +35 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/wahwah.rb +3 -74
- data/lib/wahwah/version.rb +2 -4
- data/wahwah.gemspec +27 -0
- metadata +25 -96
- data/LICENSE +0 -21
- data/lib/wahwah/asf/object.rb +0 -39
- data/lib/wahwah/asf_tag.rb +0 -220
- data/lib/wahwah/errors.rb +0 -6
- data/lib/wahwah/flac/block.rb +0 -57
- data/lib/wahwah/flac/streaminfo_block.rb +0 -51
- data/lib/wahwah/flac_tag.rb +0 -84
- data/lib/wahwah/helper.rb +0 -37
- data/lib/wahwah/id3/comment_frame_body.rb +0 -21
- data/lib/wahwah/id3/frame.rb +0 -180
- data/lib/wahwah/id3/frame_body.rb +0 -36
- data/lib/wahwah/id3/genre_frame_body.rb +0 -15
- data/lib/wahwah/id3/image_frame_body.rb +0 -60
- data/lib/wahwah/id3/text_frame_body.rb +0 -16
- data/lib/wahwah/id3/v1.rb +0 -96
- data/lib/wahwah/id3/v2.rb +0 -60
- data/lib/wahwah/id3/v2_header.rb +0 -53
- data/lib/wahwah/mp3/mpeg_frame_header.rb +0 -141
- data/lib/wahwah/mp3/vbri_header.rb +0 -47
- data/lib/wahwah/mp3/xing_header.rb +0 -45
- data/lib/wahwah/mp3_tag.rb +0 -110
- data/lib/wahwah/mp4/atom.rb +0 -105
- data/lib/wahwah/mp4_tag.rb +0 -126
- data/lib/wahwah/ogg/flac_tag.rb +0 -37
- data/lib/wahwah/ogg/opus_tag.rb +0 -33
- data/lib/wahwah/ogg/packets.rb +0 -41
- data/lib/wahwah/ogg/page.rb +0 -121
- data/lib/wahwah/ogg/pages.rb +0 -24
- data/lib/wahwah/ogg/vorbis_comment.rb +0 -51
- data/lib/wahwah/ogg/vorbis_tag.rb +0 -35
- data/lib/wahwah/ogg_tag.rb +0 -66
- data/lib/wahwah/riff/chunk.rb +0 -54
- data/lib/wahwah/riff_tag.rb +0 -140
- data/lib/wahwah/tag.rb +0 -59
- data/lib/wahwah/tag_delegate.rb +0 -16
data/lib/wahwah/mp4/atom.rb
DELETED
@@ -1,105 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module WahWah
|
4
|
-
module Mp4
|
5
|
-
class Atom
|
6
|
-
VERSIONED_ATOMS = %w(meta stsd)
|
7
|
-
FLAGGED_ATOMS = %w(stsd)
|
8
|
-
HEADER_SIZE = 8
|
9
|
-
HEADER_SIZE_FIELD_SIZE = 4
|
10
|
-
EXTENDED_HEADER_SIZE = 8
|
11
|
-
|
12
|
-
attr_reader :size, :type
|
13
|
-
|
14
|
-
def self.find(file_io, *atom_path)
|
15
|
-
file_io.rewind
|
16
|
-
|
17
|
-
atom_type = atom_path.shift
|
18
|
-
|
19
|
-
until file_io.eof?
|
20
|
-
atom = new(file_io)
|
21
|
-
|
22
|
-
next unless atom.valid?
|
23
|
-
file_io.seek(atom.size, IO::SEEK_CUR); next unless atom.type == atom_type
|
24
|
-
|
25
|
-
return atom if atom_path.empty?
|
26
|
-
return atom.find(*atom_path)
|
27
|
-
end
|
28
|
-
|
29
|
-
# Return empty atom if can not found
|
30
|
-
new(StringIO.new(''))
|
31
|
-
end
|
32
|
-
|
33
|
-
# An atom header consists of the following fields:
|
34
|
-
#
|
35
|
-
# Atom size:
|
36
|
-
# A 32-bit integer that indicates the size of the atom, including both the atom header and the atom’s contents,
|
37
|
-
# including any contained atoms. Normally, the size field contains the actual size of the atom.
|
38
|
-
#
|
39
|
-
# Type:
|
40
|
-
# A 32-bit integer that contains the type of the atom.
|
41
|
-
# This can often be usefully treated as a four-character field with a mnemonic value .
|
42
|
-
def initialize(file_io)
|
43
|
-
@size, @type = file_io.read(HEADER_SIZE)&.unpack('Na4')
|
44
|
-
return unless valid?
|
45
|
-
|
46
|
-
# If the size field of an atom is set to 1, the type field is followed by a 64-bit extended size field,
|
47
|
-
# which contains the actual size of the atom as a 64-bit unsigned integer.
|
48
|
-
@size = file_io.read(EXTENDED_HEADER_SIZE).unpack('Q>').first - EXTENDED_HEADER_SIZE if @size == 1
|
49
|
-
|
50
|
-
# If the size field of an atom is set to 0, which is allowed only for a top-level atom,
|
51
|
-
# designates the last atom in the file and indicates that the atom extends to the end of the file.
|
52
|
-
@size = file_io.size if @size == 0
|
53
|
-
@size = @size - HEADER_SIZE
|
54
|
-
@file_io = file_io
|
55
|
-
@position = file_io.pos
|
56
|
-
end
|
57
|
-
|
58
|
-
def data
|
59
|
-
@file_io.seek(@position)
|
60
|
-
@file_io.read(size)
|
61
|
-
end
|
62
|
-
|
63
|
-
def valid?
|
64
|
-
!@size.nil? && @size >= HEADER_SIZE
|
65
|
-
end
|
66
|
-
|
67
|
-
def find(*atom_path)
|
68
|
-
child_atom_index = data.index(atom_path.first)
|
69
|
-
|
70
|
-
# Return empty atom if can not found
|
71
|
-
return self.class.new(StringIO.new('')) if child_atom_index.nil?
|
72
|
-
|
73
|
-
# Because before atom type field there are 4 bytes of size field,
|
74
|
-
# So the child_atom_index should reduce 4.
|
75
|
-
self.class.find(StringIO.new(data[child_atom_index - HEADER_SIZE_FIELD_SIZE..-1]), *atom_path)
|
76
|
-
end
|
77
|
-
|
78
|
-
|
79
|
-
def children
|
80
|
-
@children ||= parse_children_atoms
|
81
|
-
end
|
82
|
-
|
83
|
-
private
|
84
|
-
def parse_children_atoms
|
85
|
-
children_atoms = []
|
86
|
-
atoms_data = data
|
87
|
-
|
88
|
-
# Some atoms data contain extra content before child atom data.
|
89
|
-
# So reduce those extra content to get child atom data.
|
90
|
-
atoms_data = atoms_data[4..-1] if VERSIONED_ATOMS.include? type # Skip 4 bytes for version
|
91
|
-
atoms_data = atoms_data[4..-1] if FLAGGED_ATOMS.include? type # Skip 4 bytes for flag
|
92
|
-
atoms_data_io = StringIO.new(atoms_data)
|
93
|
-
|
94
|
-
until atoms_data_io.eof?
|
95
|
-
atom = self.class.new(atoms_data_io)
|
96
|
-
children_atoms.push(atom)
|
97
|
-
|
98
|
-
atoms_data_io.seek(atom.size, IO::SEEK_CUR)
|
99
|
-
end
|
100
|
-
|
101
|
-
children_atoms
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
data/lib/wahwah/mp4_tag.rb
DELETED
@@ -1,126 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module WahWah
|
4
|
-
class Mp4Tag < Tag
|
5
|
-
META_ATOM_MAPPING = {
|
6
|
-
"\xA9alb".b => :album,
|
7
|
-
"\xA9ART".b => :artist,
|
8
|
-
"\xA9cmt".b => :comment,
|
9
|
-
"\xA9wrt".b => :composer,
|
10
|
-
"\xA9day".b => :year,
|
11
|
-
"\xA9gen".b => :genre,
|
12
|
-
"\xA9nam".b => :title,
|
13
|
-
'covr'.b => :image,
|
14
|
-
'disk'.b => :disc,
|
15
|
-
'trkn'.b => :track,
|
16
|
-
'aART'.b => :albumartist
|
17
|
-
}
|
18
|
-
|
19
|
-
META_ATOM_DECODE_BY_TYPE = {
|
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.unpack('i>').first }, # Big endian signed integer
|
29
|
-
22 => -> (data) { data.unpack('I>').first }, # Big endian unsigned integer
|
30
|
-
23 => -> (data) { data.unpack('g').first }, # Big endian 32-bit floating point value
|
31
|
-
24 => -> (data) { data.unpack('G').first }, # Big endian 64-bit floating point value
|
32
|
-
|
33
|
-
65 => -> (data) { data.unpack('c').first }, # 8-bit signed integer
|
34
|
-
66 => -> (data) { data.unpack('s>').first }, # Big-endian 16-bit signed integer
|
35
|
-
67 => -> (data) { data.unpack('l>').first }, # Big-endian 32-bit signed integer
|
36
|
-
74 => -> (data) { data.unpack('q>').first }, # Big-endian 64-bit signed integer
|
37
|
-
|
38
|
-
75 => -> (data) { data.unpack('C').first }, # 8-bit unsigned integer
|
39
|
-
76 => -> (data) { data.unpack('S>').first }, # Big-endian 16-bit unsigned integer
|
40
|
-
77 => -> (data) { data.unpack('L>').first }, # Big-endian 32-bit unsigned integer
|
41
|
-
78 => -> (data) { data.unpack('Q>').first } # Big-endian 64-bit unsigned integer
|
42
|
-
}
|
43
|
-
|
44
|
-
private
|
45
|
-
def parse
|
46
|
-
movie_atom = Mp4::Atom.find(@file_io, 'moov')
|
47
|
-
return unless movie_atom.valid?
|
48
|
-
|
49
|
-
parse_meta_list_atom movie_atom.find('udta', 'meta', 'ilst')
|
50
|
-
parse_mvhd_atom movie_atom.find('mvhd')
|
51
|
-
parse_stsd_atom movie_atom.find('trak', 'mdia', 'minf', 'stbl', 'stsd')
|
52
|
-
end
|
53
|
-
|
54
|
-
def parse_meta_list_atom(atom)
|
55
|
-
return unless atom.valid?
|
56
|
-
|
57
|
-
# The metadata item list atom holds a list of actual metadata values that are present in the metadata atom.
|
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
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
def parse_mvhd_atom(atom)
|
93
|
-
return unless atom.valid?
|
94
|
-
|
95
|
-
atom_data = StringIO.new(atom.data)
|
96
|
-
version = atom_data.read(1).unpack('c').first
|
97
|
-
|
98
|
-
# Skip flags
|
99
|
-
atom_data.seek(3, IO::SEEK_CUR)
|
100
|
-
|
101
|
-
if version == 0
|
102
|
-
# Skip creation and modification time
|
103
|
-
atom_data.seek(8, IO::SEEK_CUR)
|
104
|
-
|
105
|
-
time_scale, duration = atom_data.read(8).unpack('l>l>')
|
106
|
-
elsif version == 1
|
107
|
-
# Skip creation and modification time
|
108
|
-
atom_data.seek(16, IO::SEEK_CUR)
|
109
|
-
|
110
|
-
time_scale, duration = atom_data.read(12).unpack('l>q>')
|
111
|
-
end
|
112
|
-
|
113
|
-
@duration = (duration / time_scale.to_f).round
|
114
|
-
end
|
115
|
-
|
116
|
-
def parse_stsd_atom(atom)
|
117
|
-
return unless atom.valid?
|
118
|
-
|
119
|
-
mp4a_atom = atom.find('mp4a')
|
120
|
-
esds_atom = atom.find('esds')
|
121
|
-
|
122
|
-
@sample_rate = mp4a_atom.data.unpack('x22I>').first if mp4a_atom.valid?
|
123
|
-
@bitrate = esds_atom.data.unpack('x26I>').first / 1000 if esds_atom.valid?
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
data/lib/wahwah/ogg/flac_tag.rb
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module WahWah
|
4
|
-
module Ogg
|
5
|
-
class FlacTag
|
6
|
-
include VorbisComment
|
7
|
-
include Flac::StreaminfoBlock
|
8
|
-
|
9
|
-
attr_reader :bitrate, :duration, :sample_rate, *COMMET_FIELD_MAPPING.values
|
10
|
-
|
11
|
-
def initialize(identification_packet, comment_packet)
|
12
|
-
# Identification packet structure:
|
13
|
-
#
|
14
|
-
# The one-byte packet type 0x7F
|
15
|
-
# The four-byte ASCII signature "FLAC", i.e. 0x46, 0x4C, 0x41, 0x43
|
16
|
-
# A one-byte binary major version number for the mapping, e.g. 0x01 for mapping version 1.0
|
17
|
-
# A one-byte binary minor version number for the mapping, e.g. 0x00 for mapping version 1.0
|
18
|
-
# A two-byte, big-endian binary number signifying the number of header (non-audio) packets, not including this one.
|
19
|
-
# The four-byte ASCII native FLAC signature "fLaC" according to the FLAC format specification
|
20
|
-
# The STREAMINFO metadata block for the stream.
|
21
|
-
#
|
22
|
-
# The first identification packet is followed by one or more header packets.
|
23
|
-
# Each such packet will contain a single native FLAC metadata block.
|
24
|
-
# The first of these must be a VORBIS_COMMENT block.
|
25
|
-
|
26
|
-
id, streaminfo_block_data = identification_packet.unpack('x9A4A*')
|
27
|
-
|
28
|
-
return unless id == 'fLaC'
|
29
|
-
streaminfo_block = Flac::Block.new(StringIO.new(streaminfo_block_data))
|
30
|
-
vorbis_comment_block = Flac::Block.new(StringIO.new(comment_packet))
|
31
|
-
|
32
|
-
parse_streaminfo_block(streaminfo_block.data)
|
33
|
-
parse_vorbis_comment(vorbis_comment_block.data)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
data/lib/wahwah/ogg/opus_tag.rb
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module WahWah
|
4
|
-
module Ogg
|
5
|
-
class OpusTag
|
6
|
-
include VorbisComment
|
7
|
-
|
8
|
-
attr_reader :sample_rate, :pre_skip, *COMMET_FIELD_MAPPING.values
|
9
|
-
|
10
|
-
def initialize(identification_packet, comment_packet)
|
11
|
-
# Identification packet structure:
|
12
|
-
#
|
13
|
-
# 1) "OpusHead"
|
14
|
-
# 2) [version] = read 8 bits as unsigned integer
|
15
|
-
# 3) [audio_channels] = read 8 bit as unsigned integer
|
16
|
-
# 4) [pre_skip] = read 16 bits as unsigned little endian integer
|
17
|
-
# 5) [input_sample_rate] = read 32 bits as unsigned little endian integer
|
18
|
-
# 6) [output_gain] = read 16 bits as unsigned little endian integer
|
19
|
-
# 7) [channel_mapping_family] = read 8 bit as unsigned integer
|
20
|
-
# 8) [channel_mapping_table]
|
21
|
-
@sample_rate = 48000
|
22
|
-
@pre_skip = identification_packet[10..11].unpack('v').first
|
23
|
-
|
24
|
-
comment_packet_id, comment_packet_body = [comment_packet[0..7], comment_packet[8..-1]]
|
25
|
-
|
26
|
-
# Opus comment packet start with 'OpusTags'
|
27
|
-
return unless comment_packet_id == 'OpusTags'
|
28
|
-
|
29
|
-
parse_vorbis_comment(comment_packet_body)
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
data/lib/wahwah/ogg/packets.rb
DELETED
@@ -1,41 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module WahWah
|
4
|
-
module Ogg
|
5
|
-
# From Ogg's perspective, packets can be of any arbitrary size. A
|
6
|
-
# specific media mapping will define how to group or break up packets
|
7
|
-
# from a specific media encoder. As Ogg pages have a maximum size of
|
8
|
-
# about 64 kBytes, sometimes a packet has to be distributed over
|
9
|
-
# several pages. To simplify that process, Ogg divides each packet
|
10
|
-
# into 255 byte long chunks plus a final shorter chunk. These chunks
|
11
|
-
# are called "Ogg Segments". They are only a logical construct and do
|
12
|
-
# not have a header for themselves.
|
13
|
-
class Packets
|
14
|
-
include Enumerable
|
15
|
-
|
16
|
-
def initialize(file_io)
|
17
|
-
@file_io = file_io
|
18
|
-
end
|
19
|
-
|
20
|
-
def each
|
21
|
-
@file_io.rewind
|
22
|
-
|
23
|
-
packet = +''
|
24
|
-
pages = Ogg::Pages.new(@file_io)
|
25
|
-
|
26
|
-
pages.each do |page|
|
27
|
-
page.segments.each do |segment|
|
28
|
-
packet << segment
|
29
|
-
|
30
|
-
# Ogg divides each packet into 255 byte long segments plus a final shorter segment.
|
31
|
-
# So when segment length is less than 255 byte, it's the final segment.
|
32
|
-
if segment.length < 255
|
33
|
-
yield packet
|
34
|
-
packet = +''
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
data/lib/wahwah/ogg/page.rb
DELETED
@@ -1,121 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module WahWah
|
4
|
-
module Ogg
|
5
|
-
# The Ogg page header has the following format:
|
6
|
-
#
|
7
|
-
# 0 1 2 3
|
8
|
-
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1| Byte
|
9
|
-
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
10
|
-
# | capture_pattern: Magic number for page start "OggS" | 0-3
|
11
|
-
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
12
|
-
# | version | header_type | granule_position | 4-7
|
13
|
-
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
14
|
-
# | | 8-11
|
15
|
-
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
16
|
-
# | | bitstream_serial_number | 12-15
|
17
|
-
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
18
|
-
# | | page_sequence_number | 16-19
|
19
|
-
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
20
|
-
# | | CRC_checksum | 20-23
|
21
|
-
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
22
|
-
# | |page_segments | segment_table | 24-27
|
23
|
-
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
24
|
-
# | ... | 28-
|
25
|
-
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
26
|
-
#
|
27
|
-
#
|
28
|
-
# The fields in the page header have the following meaning:
|
29
|
-
#
|
30
|
-
# 1. capture_pattern: a 4 Byte field that signifies the beginning of a
|
31
|
-
# page. It contains the magic numbers:
|
32
|
-
|
33
|
-
# 0x4f 'O'
|
34
|
-
|
35
|
-
# 0x67 'g'
|
36
|
-
|
37
|
-
# 0x67 'g'
|
38
|
-
|
39
|
-
# 0x53 'S'
|
40
|
-
|
41
|
-
# It helps a decoder to find the page boundaries and regain
|
42
|
-
# synchronisation after parsing a corrupted stream. Once the
|
43
|
-
# capture pattern is found, the decoder verifies page sync and
|
44
|
-
# integrity by computing and comparing the checksum.
|
45
|
-
|
46
|
-
# 2. stream_structure_version: 1 Byte signifying the version number of
|
47
|
-
# the Ogg file format used in this stream (this document specifies
|
48
|
-
# version 0).
|
49
|
-
|
50
|
-
# 3. header_type_flag: the bits in this 1 Byte field identify the
|
51
|
-
# specific type of this page.
|
52
|
-
|
53
|
-
# * bit 0x01
|
54
|
-
|
55
|
-
# set: page contains data of a packet continued from the previous
|
56
|
-
# page
|
57
|
-
|
58
|
-
# unset: page contains a fresh packet
|
59
|
-
|
60
|
-
# * bit 0x02
|
61
|
-
|
62
|
-
# set: this is the first page of a logical bitstream (bos)
|
63
|
-
|
64
|
-
# unset: this page is not a first page
|
65
|
-
|
66
|
-
# * bit 0x04
|
67
|
-
|
68
|
-
# set: this is the last page of a logical bitstream (eos)
|
69
|
-
|
70
|
-
# unset: this page is not a last page
|
71
|
-
|
72
|
-
# 4. granule_position: an 8 Byte field containing position information.
|
73
|
-
# For example, for an audio stream, it MAY contain the total number
|
74
|
-
# of PCM samples encoded after including all frames finished on this
|
75
|
-
# page. For a video stream it MAY contain the total number of video
|
76
|
-
# frames encoded after this page. This is a hint for the decoder
|
77
|
-
# and gives it some timing and position information. Its meaning is
|
78
|
-
# dependent on the codec for that logical bitstream and specified in
|
79
|
-
# a specific media mapping. A special value of -1 (in two's
|
80
|
-
# complement) indicates that no packets finish on this page.
|
81
|
-
|
82
|
-
# 5. bitstream_serial_number: a 4 Byte field containing the unique
|
83
|
-
# serial number by which the logical bitstream is identified.
|
84
|
-
|
85
|
-
# 6. page_sequence_number: a 4 Byte field containing the sequence
|
86
|
-
# number of the page so the decoder can identify page loss. This
|
87
|
-
# sequence number is increasing on each logical bitstream
|
88
|
-
# separately.
|
89
|
-
|
90
|
-
# 7. CRC_checksum: a 4 Byte field containing a 32 bit CRC checksum of
|
91
|
-
# the page (including header with zero CRC field and page content).
|
92
|
-
# The generator polynomial is 0x04c11db7.
|
93
|
-
|
94
|
-
# 8. number_page_segments: 1 Byte giving the number of segment entries
|
95
|
-
# encoded in the segment table.
|
96
|
-
|
97
|
-
# 9. segment_table: number_page_segments Bytes containing the lacing
|
98
|
-
# values of all segments in this page. Each Byte contains one
|
99
|
-
# lacing value.
|
100
|
-
class Page
|
101
|
-
HEADER_SIZE = 27
|
102
|
-
HEADER_FORMAT = 'A4CxQx12C'
|
103
|
-
|
104
|
-
attr_reader :segments, :granule_position
|
105
|
-
|
106
|
-
def initialize(file_io)
|
107
|
-
header_content = file_io.read(HEADER_SIZE)
|
108
|
-
@capture_pattern, @version, @granule_position, page_segments = header_content.unpack(HEADER_FORMAT) if header_content.size >= HEADER_SIZE
|
109
|
-
|
110
|
-
return unless valid?
|
111
|
-
|
112
|
-
segment_table = file_io.read(page_segments).unpack('C' * page_segments)
|
113
|
-
@segments = segment_table.map { |segment_length| file_io.read(segment_length) }
|
114
|
-
end
|
115
|
-
|
116
|
-
def valid?
|
117
|
-
@capture_pattern == 'OggS' && @version == 0
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|