ruby-ogg 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/lib/ogg.rb +157 -0
  2. data/lib/vorbis.rb +143 -0
  3. metadata +65 -0
data/lib/ogg.rb ADDED
@@ -0,0 +1,157 @@
1
+ require 'rubygems'
2
+ require 'bindata'
3
+
4
+ require 'stringio'
5
+
6
+ module Ogg
7
+ # This is a superclass for all custom defined Ogg decoding errors
8
+ class DecodingError < StandardError
9
+ end
10
+
11
+ # This error is raised when the file format is not valid Ogg
12
+ class MalformedFileError < DecodingError
13
+ end
14
+
15
+ # Ogg::Decoder is used to decode Ogg bitstreams. The easiest way of properly
16
+ # parsing an Ogg file is to read consecutive packets with the read_packet
17
+ # method. For example:
18
+ #
19
+ # require 'ogg'
20
+ #
21
+ # open("file.ogg", "rb") do |file|
22
+ # dec = Ogg::Decoder.new(file)
23
+ # packet = dec.read_packet
24
+ # # Do something with the packet...
25
+ # end
26
+ #
27
+ # The terms "page" and "packet" have special meanings when dealing with Ogg. A
28
+ # packet is a section of data which is encoded in the Ogg container. A page
29
+ # is a section of the Ogg container used as a means of storing packets.
30
+ # Since packets are what contain the "juicy bits" of the file, Ogg::Decoder
31
+ # provides sufficient abstraction to make handling of individual pages
32
+ # unnecessary. However, if you do need to read pages, that functionality is
33
+ # available via the read_page method.
34
+ class Decoder
35
+ # Create a new Decoder from an IO which should be open for binary reading.
36
+ def initialize io
37
+ @io = io
38
+ @packets = []
39
+ end
40
+
41
+ # Moves the file cursor forward to the next potential page. You probably
42
+ # wish to use the read_page method, which does some validation and actually
43
+ # returns the parsed page.
44
+ def seek_to_page(capture='OggS')
45
+ buffer = @io.read(capture.size)
46
+ page = nil
47
+ while not @io.eof?
48
+ if buffer == capture
49
+ @io.pos -= capture.size
50
+ return @io.pos
51
+ end
52
+ (buffer = buffer[1..-1] << @io.read(1)) rescue Exception
53
+ end
54
+
55
+ raise EOFError
56
+ end
57
+
58
+ # Seek to and read the next page from the bitstream. Returns a Page or
59
+ # nil if there are no pages left.
60
+ def read_page
61
+ page = nil
62
+ while not @io.eof?
63
+ begin
64
+ seek_to_page
65
+ page = Page.read @io
66
+ page = nil unless page.verify_checksum
67
+ break
68
+ rescue Exception => ex
69
+ # False alarm, keep looking...
70
+ end
71
+ end
72
+ return page
73
+ end
74
+
75
+ # Seek to and read the last page in the bitstream.
76
+ def read_last_page
77
+ raise 'Last page can only be read from a file stream' unless @io.is_a? File
78
+ buffer_size = 1024
79
+ pos = @io.stat.size - buffer_size
80
+ while pos > 0
81
+ @io.seek pos, IO::SEEK_SET
82
+ sio = StringIO.new @io.read(buffer_size)
83
+
84
+ dec = Decoder.new(sio)
85
+ sub_pos = nil
86
+
87
+ # Find last page in buffer
88
+ loop do
89
+ begin
90
+ sub_pos = dec.seek_to_page
91
+ sio.pos += 1
92
+ rescue
93
+ break
94
+ end
95
+ end
96
+
97
+ if sub_pos
98
+ @io.seek(pos + sub_pos, IO::SEEK_SET)
99
+ page = read_page
100
+ return page
101
+ end
102
+
103
+ pos -= buffer_size * 2 - ('OggS'.size - 1)
104
+ end
105
+
106
+ # This means that the Ogg file contains no pages
107
+ raise MalformedFileError
108
+ end
109
+
110
+ # Seek to and read the next packet in the bitstream. Returns a string
111
+ # containing the packet's binary data or nil if there are no packets
112
+ # left.
113
+ def read_packet
114
+ return @packets.pop unless @packets.empty?
115
+
116
+ while @packets.empty?
117
+ page = read_page
118
+ raise EOFError.new("End of file reached") if page.nil?
119
+ input = StringIO.new(page.data)
120
+
121
+ page.segment_table.each do |seg|
122
+ @partial ||= ""
123
+
124
+ @partial << input.read(seg)
125
+ if seg != 255
126
+ @packets.insert(0, @partial)
127
+ @partial = nil
128
+ end
129
+ end
130
+ end
131
+
132
+ return @packets.pop
133
+ end
134
+ end
135
+
136
+ # A BinData::Record which represents an Ogg page.
137
+ class Page < BinData::Record
138
+ endian :little
139
+ string :capture, :value => 'OggS', :read_length => 4
140
+ uint8 :version, :value => 0
141
+ uint8 :page_type
142
+ uint64 :granule_position
143
+ uint32 :bitstream_serial_number
144
+ uint32 :page_sequence_number
145
+ uint32 :checksum
146
+ uint8 :page_segments
147
+ array :segment_table, :type => :uint8, :initial_length => :page_segments
148
+ string :data, :read_length => lambda {segment_table.inject(0){|t,e| t+e}}
149
+
150
+ def verify_checksum
151
+ poly = 0x04c11db7
152
+ # TODO: Implement CRC
153
+ return true
154
+ end
155
+ end
156
+ end
157
+
data/lib/vorbis.rb ADDED
@@ -0,0 +1,143 @@
1
+ require 'rubygems'
2
+ require 'bindata'
3
+
4
+ module Vorbis
5
+ # This class reads metadata (such as comments/tags and bitrate) from Vorbis
6
+ # audio files. Here's an example of usage:
7
+ #
8
+ # require 'vorbis'
9
+ #
10
+ # Vorbis::Info.open('echoplex.ogg') do |info|
11
+ # info.comments[:artist].first #=> "Nine Inch Nails"
12
+ # info.comments[:title].first #=> "Echoplex"
13
+ # info.sample_rate #=> 44100
14
+ # end
15
+ #
16
+ # You may notice that for each comment field an array of values is
17
+ # available. This is because it is perfectly valid for a Vorbis file
18
+ # to have multiple artists, titles, or anything else.
19
+ class Info
20
+ attr_reader :identification_header, :comment_header
21
+ attr_reader :comments
22
+ attr_reader :duration, :sample_rate, :nominal_bitrate, :channels, :bitrate
23
+
24
+ # Create a new Vorbis::Info object for reading metadata.
25
+ def initialize(path, container=:ogg)
26
+ if path.is_a? IO
27
+ @io = path
28
+ else
29
+ @io = open(path, 'rb')
30
+ end
31
+
32
+ case container
33
+ when :ogg
34
+ init_ogg
35
+ else
36
+ raise "#{container.to_s} is not a supported container format"
37
+ end
38
+
39
+ @io.close
40
+
41
+ yield self if block_given?
42
+ end
43
+
44
+ def self.open(*args, &block)
45
+ return self.new(*args, &block)
46
+ end
47
+
48
+ # Read Vorbis metadata from within an Ogg container.
49
+ private
50
+ def init_ogg
51
+ require 'ogg'
52
+
53
+ parser = Ogg::Decoder.new @io
54
+
55
+ @identification_header = IdentificationHeader.read(parser.read_packet)
56
+ @sample_rate = @identification_header.audio_sample_rate
57
+ @nominal_bitrate = @identification_header.bitrate_nominal
58
+ @channels = @identification_header.audio_channels
59
+
60
+ @comment_header = CommentHeader.read(parser.read_packet)
61
+ @comments = @comment_header.comments
62
+
63
+ pos_after_headers = @io.pos
64
+
65
+ begin
66
+ # Duration is last granule position divided by sample rate
67
+ pos = parser.read_last_page.granule_position.to_f
68
+ @duration = pos / @sample_rate
69
+ rescue Exception
70
+ @duration = 0
71
+ end
72
+
73
+ begin
74
+ @bitrate = (file.stat.size - pos_after_headers).to_f * 8 / @duration
75
+ rescue Exception
76
+ @bitrate = 0
77
+ end
78
+ end
79
+ end
80
+
81
+ # A BinData::Record which represents a Vorbis identification header.
82
+ class IdentificationHeader < BinData::Record
83
+ endian :little
84
+ uint8 :packet_type, :value => 1
85
+ string :codec, :value => 'vorbis', :read_length => 6
86
+ uint32 :vorbis_version
87
+ uint8 :audio_channels
88
+ uint32 :audio_sample_rate
89
+ int32 :bitrate_maximum
90
+ int32 :bitrate_nominal
91
+ int32 :bitrate_minimum
92
+ bit4 :blocksize_0
93
+ bit4 :blocksize_1
94
+ end
95
+
96
+ # A simple subclass of Hash which converts keys to uppercase strings.
97
+ class InsensitiveHash < Hash
98
+ def [](key)
99
+ super(key.to_s.upcase)
100
+ end
101
+
102
+ def []=(key, value)
103
+ super(key.to_s.upcase, value)
104
+ end
105
+ end
106
+
107
+ # A BinData::BasePrimitive which represents a list of comments as per
108
+ # the Vorbis I comment header specification.
109
+ class Comments < BinData::BasePrimitive
110
+ register(self.name, self)
111
+
112
+ def read_and_return_value(io)
113
+ n_comments = read_uint32le(io)
114
+ comments = InsensitiveHash.new
115
+ n_comments.times do
116
+ length = read_uint32le(io)
117
+ comment = io.readbytes(length)
118
+ key, value = comment.split('=', 2)
119
+ (comments[key] ||= []) << value
120
+ end
121
+ return comments
122
+ end
123
+
124
+ def sensible_default
125
+ return {}
126
+ end
127
+
128
+ def read_uint32le(io)
129
+ return BinData::Uint32le.read(io)
130
+ end
131
+ end
132
+
133
+ # A BinData::Record which represents a Vorbis comment header.
134
+ class CommentHeader < BinData::Record
135
+ endian :little
136
+ uint8 :packet_type, :value => 3
137
+ string :codec, :value => 'vorbis', :read_length => 6
138
+ uint32 :vendor_length
139
+ string :vendor_string, :read_length => :vendor_length
140
+ comments :comments
141
+ end
142
+ end
143
+
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby-ogg
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Aiden Nibali
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-24 00:00:00 +11:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: bindata
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.0.0
24
+ version:
25
+ description:
26
+ email: dismal.denizen@gmail.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - lib/vorbis.rb
35
+ - lib/ogg.rb
36
+ has_rdoc: true
37
+ homepage: http://rubyforge.org/projects/ruby-ogg/
38
+ licenses: []
39
+
40
+ post_install_message:
41
+ rdoc_options: []
42
+
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ version:
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ version:
57
+ requirements: []
58
+
59
+ rubyforge_project: ruby-ogg
60
+ rubygems_version: 1.3.5
61
+ signing_key:
62
+ specification_version: 3
63
+ summary: A library for reading Ogg bitstreams
64
+ test_files: []
65
+