class OggFile
class OggError < IOError; end
class MagicError < OggError; end
class VersionError < OggError; end
class PageTypeError < OggError; end
class PageOrderError < OggError; end
class PacketTypeError < OggError; end
class BlocksizeError < OggError; end
class ValueError < OggError; end
class << self
alias :open :new
end
module HeaderFlags
Continued, First, Last = *(0 .. 2).map { |i| 1 << i }
end
module PacketTypes
Identification, Comment, Setup = *1 .. 2
end
AllowedBlocksizes = [64, 128, 256, 512, 1024, 2048, 4096, 8192]
attr_accessor :vorbis_version, :audio_channels, :audio_sample_rate,
:bitrate_maximum, :bitrate_nominal, :bitrate_minimum, :blocksize_0,
:blocksize_1, :comments
def initialize(filename)
data = File.open(filename, "rb") { |file| file.read }
magic, version, header_type, granule_position, serial_number,
page_number, checksum, segment_size, rest = *data.unpack("a4CCQLLLCa*")
raise(MagicError, "File magic bytes are not 'OggS'") if magic != "OggS"
raise(VersionError, "Unknown structure version") if version != 0
raise(PageOrderError, "Out of order page") if page_number != 0
# Checksum is ignored for now
if (header_type & HeaderFlags::First).zero? then
raise(PageTypeError, "Unexpected non-first page at file beginning") end
# Rest of initial page, identification header
segment, id_packet_type, id_magic, @vorbis_version, @audio_channels,
@audio_sample_rate, @bitrate_maximum, @bitrate_nominal, @bitrate_minimum,
blocksize, framing_bit, rest = *rest.unpack(
"a#{segment_size}Ca6LCLlllCCa*")
@blocksize_0 = 2 ** (blocksize & 0b1111) # first 4 bits are 2 exponent
@blocksize_1 = 2 ** (blocksize >> 4) # last 4 bits are 2 exponent
#if id_packet_type != PacketTypes::Identification then
# raise(PacketTypeError, "Out of order packet. Expected Identification, " +
# "but got #{id_packet_type}")
#end
if id_magic != "vorbis" then
raise(MagicError, "Header magic needs to be 'vorbis'") end
raise(VersionError, "Unknown vorbis version") if @vorbis_version != 0
raise(ValueError, "Audio channels need to be > 0") if @audio_channels == 0
if @audio_sample_rate == 0 then
raise(ValueError, "Audio sample rate needs to be > 0")
end
[@blocksize_0, @blocksize_1].each do |bs|
unless AllowedBlocksizes.include?(bs)
raise(ValueError, "Unallowed blocksize #{bs}")
end end
if @blocksize_0 > @blocksize_1 then
raise(ValueError, "Blocksize 0 has to be > blocksize 1")
end
raise(ValueError, "Framing bit has to be non-zero") if framing_bit.zero?
# Second page
magic, version, header_type, granule_position, serial_number,
page_number, checksum, segment_size, rest = *rest.unpack("a4CCQLLLCa*")
raise(MagicError, "File magic bytes are not 'OggS'") if magic != "OggS"
raise(VersionError, "Unknown structure version") if version != 0
raise(PageOrderError, "Out of order page") if page_number != 1
# Checksum is ignored for now
# Rest of second page, comment header
segment, cmt_packet_type, cmt_magic, vendor_length, rest = *rest.unpack(
"a#{segment_size}Ca6LA*")
#if cmt_packet_type != PacketTypes::Comment then
# raise(PacketTypeError, "Out of order packet. Expected Comment, " +
# "but got #{id_packet_type}")
#end
if cmt_magic != "vorbis" then
raise(MagicError, "Header magic needs to be 'vorbis'")
end
vendor, comment_count, rest = *rest.unpack("a#{vendor_length}LA*")
@comments = Hash.new { |hash, key| hash[key] = Array.new }
comment_count.times do
length, rest = *rest.unpack("La*")
string, rest = *rest.unpack("a#{length}a*")
key, value = string.split("=", 2)
@comments[key.upcase] << value
end
framing_bit, rest = *rest.unpack("Ca*")
raise(ValueError, "Framing bit has to be non-zero") if framing_bit.zero?
end
end