ruby-ogginfo 0.5 → 0.6.5
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +8 -0
- data/Manifest.txt +9 -1
- data/Rakefile +6 -12
- data/lib/ogg/codecs/comments.rb +50 -0
- data/lib/ogg/codecs/speex.rb +40 -0
- data/lib/ogg/codecs/vorbis.rb +46 -0
- data/lib/ogg/page.rb +78 -0
- data/lib/ogg/reader.rb +37 -0
- data/lib/ogg/writer.rb +48 -0
- data/lib/ogg.rb +139 -0
- data/lib/ogginfo.rb +101 -195
- data/test/test_ruby-ogginfo.rb +80 -19
- data/test/test_ruby-spxinfo.rb +21 -3
- metadata +19 -9
data/History.txt
CHANGED
data/Manifest.txt
CHANGED
@@ -2,6 +2,14 @@ History.txt
|
|
2
2
|
Manifest.txt
|
3
3
|
README.rdoc
|
4
4
|
Rakefile
|
5
|
-
|
5
|
+
lib/ogg/codecs/comments.rb
|
6
|
+
lib/ogg/codecs/speex.rb
|
7
|
+
lib/ogg/codecs/vorbis.rb
|
8
|
+
lib/ogg/page.rb
|
9
|
+
lib/ogg/reader.rb
|
10
|
+
lib/ogg/writer.rb
|
11
|
+
lib/ogg.rb
|
6
12
|
lib/ogginfo.rb
|
13
|
+
setup.rb
|
7
14
|
test/test_ruby-ogginfo.rb
|
15
|
+
test/test_ruby-spxinfo.rb
|
data/Rakefile
CHANGED
@@ -4,19 +4,13 @@ require 'rubygems'
|
|
4
4
|
require 'hoe'
|
5
5
|
|
6
6
|
Hoe.plugin :yard
|
7
|
+
Hoe.plugin :git
|
8
|
+
Hoe.plugin :rcov
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
p.author = 'Guillaume Pierronnet'
|
13
|
-
p.email = 'moumar@rubyforge.org'
|
14
|
-
p.summary = 'ruby-ogginfo is a pure-ruby library that gives low level informations on ogg files'
|
15
|
-
p.description = p.paragraphs_of('README.rdoc', 3).first
|
16
|
-
p.url = p.paragraphs_of('README.rdoc', 1).first
|
17
|
-
p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
|
18
|
-
p.remote_rdoc_dir = ''
|
19
|
-
p.rdoc_locations << "rubyforge.org:/var/www/gforge-projects/ruby-ogginfo/"
|
10
|
+
Hoe.spec('ruby-ogginfo') do
|
11
|
+
developer('Guillaume Pierronnet','moumar@rubyforge.org')
|
12
|
+
developer('Grant Gardner','grant@lastweekend.com.au')
|
13
|
+
summary = 'ruby-ogginfo is a pure-ruby library that gives low level informations on ogg files'
|
20
14
|
end
|
21
15
|
|
22
16
|
# vim: syntax=Ruby
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "stringio"
|
2
|
+
|
3
|
+
module Ogg::Codecs
|
4
|
+
# See http://www.xiph.org/vorbis/doc/v-comment.html
|
5
|
+
# Methods to pack/unpack vorbis comment packets
|
6
|
+
# intended to be included into Codec classes
|
7
|
+
module VorbisComments
|
8
|
+
# unpack a packet, skipping the preamble
|
9
|
+
# returns a 2 element array being a Hash of tag/value pairs and the vendor string
|
10
|
+
def unpack_comments(packet, preamble="")
|
11
|
+
pio = StringIO.new(packet)
|
12
|
+
pio.read(preamble.length)
|
13
|
+
|
14
|
+
vendor_length = pio.read(4).unpack("V").first
|
15
|
+
vendor = pio.read(vendor_length)
|
16
|
+
|
17
|
+
tag = {}
|
18
|
+
tag_size = pio.read(4).unpack("V")[0]
|
19
|
+
|
20
|
+
tag_size.times do |i|
|
21
|
+
size = pio.read(4).unpack("V")[0]
|
22
|
+
comment = pio.read(size)
|
23
|
+
key, val = comment.split(/=/, 2)
|
24
|
+
tag[key.downcase] = val
|
25
|
+
end
|
26
|
+
|
27
|
+
#framing bit = pio.read(1).unpack("C")[0]
|
28
|
+
[ tag, vendor ]
|
29
|
+
end
|
30
|
+
|
31
|
+
# Pack tag Hash and vendor string into an ogg packet.
|
32
|
+
def pack_comments(tag, vendor, preamble="")
|
33
|
+
packet_data = ""
|
34
|
+
packet_data << preamble
|
35
|
+
|
36
|
+
packet_data << [ vendor.length ].pack("V")
|
37
|
+
packet_data << vendor
|
38
|
+
|
39
|
+
packet_data << [tag.size].pack("V")
|
40
|
+
tag.each do |k,v|
|
41
|
+
tag_data = "#{ k }=#{ v }"
|
42
|
+
packet_data << [ tag_data.length ].pack("V")
|
43
|
+
packet_data << tag_data
|
44
|
+
end
|
45
|
+
|
46
|
+
packet_data << "\001"
|
47
|
+
packet_data
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Ogg::Codecs
|
2
|
+
class Speex
|
3
|
+
class << self
|
4
|
+
include VorbisComments
|
5
|
+
|
6
|
+
def match?(packet)
|
7
|
+
/^Speex/ =~ packet
|
8
|
+
end
|
9
|
+
|
10
|
+
def decode_headers(reader)
|
11
|
+
init_packet, tag_packet = reader.read_packets(2)
|
12
|
+
info = extract_info(init_packet)
|
13
|
+
info[:tag], info[:tag_vendor] = unpack_comments(tag_packet)
|
14
|
+
return info
|
15
|
+
end
|
16
|
+
|
17
|
+
def replace_tags(reader, writer, new_tags, vendor)
|
18
|
+
tag_packet = reader.read_packets(1)
|
19
|
+
writer.write_packets(0, pack_comments(new_tags, vendor))
|
20
|
+
end
|
21
|
+
|
22
|
+
def extract_info(info_packet)
|
23
|
+
speex_string,
|
24
|
+
speex_version,
|
25
|
+
speex_version_id,
|
26
|
+
header_size,
|
27
|
+
samplerate,
|
28
|
+
mode,
|
29
|
+
mode_bitstream_version,
|
30
|
+
channels,
|
31
|
+
nominal_bitrate,
|
32
|
+
framesize,
|
33
|
+
vbr = info_packet.unpack("A8A20VVVVVVVVV")
|
34
|
+
#not sure how to make sense of the bitrate info,picard doesn't show it either...
|
35
|
+
|
36
|
+
return { :channels => channels, :samplerate => samplerate, :nominal_bitrate => nominal_bitrate }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Ogg::Codecs
|
2
|
+
class Vorbis
|
3
|
+
class << self
|
4
|
+
include VorbisComments
|
5
|
+
# return true/false based on whether the header packet belongs to us
|
6
|
+
def match?(header_packet)
|
7
|
+
/^\001vorbis.*/ =~ header_packet
|
8
|
+
end
|
9
|
+
|
10
|
+
#consume header and tag pages, return array of two hashes, info and tags
|
11
|
+
def decode_headers(reader)
|
12
|
+
init_pkt, tag_pkt, setup_pkt = reader.read_packets(3)
|
13
|
+
info = extract_info(init_pkt)
|
14
|
+
info[:tag], info[:tag_vendor] = unpack_comments(tag_pkt, "\003vorbis")
|
15
|
+
info
|
16
|
+
end
|
17
|
+
|
18
|
+
# consume pages with old tags/setup packets and rewrite newtags,setup packets
|
19
|
+
# return the number of pages written
|
20
|
+
def replace_tags(reader, writer, new_tags, vendor)
|
21
|
+
tag_pkt, setup_pkt = reader.read_packets(2)
|
22
|
+
writer.write_packets(0, pack_comments(new_tags, vendor, "\003vorbis"), setup_pkt)
|
23
|
+
end
|
24
|
+
|
25
|
+
def extract_info(packet)
|
26
|
+
vorbis_string,
|
27
|
+
vorbis_version,
|
28
|
+
channels,
|
29
|
+
samplerate,
|
30
|
+
upper_bitrate,
|
31
|
+
nominal_bitrate,
|
32
|
+
lower_bitrate = packet.unpack("a7VCV4")
|
33
|
+
|
34
|
+
if nominal_bitrate == 0
|
35
|
+
if (upper_bitrate == 2**32 - 1) || (lower_bitrate == 2**32 - 1)
|
36
|
+
nominal_bitrate = 0
|
37
|
+
else
|
38
|
+
nominal_bitrate = ( upper_bitrate + lower_bitrate) / 2
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
return { :channels => channels, :samplerate => samplerate, :nominal_bitrate => nominal_bitrate }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/ogg/page.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
module Ogg
|
2
|
+
class Page
|
3
|
+
attr_accessor :granule_pos, :bitstream_serial_no, :sequence_no, :segments, :header
|
4
|
+
attr_reader :checksum
|
5
|
+
|
6
|
+
# read an ogg frame from the +file+
|
7
|
+
# file must be positioned at end of frame after this loop
|
8
|
+
# options - :skip_body = seek to end of frame rather than reading in the data
|
9
|
+
def self.read(io, options = {})
|
10
|
+
return nil if io.eof?
|
11
|
+
|
12
|
+
chunk = io.read(27)
|
13
|
+
|
14
|
+
capture_pattern,
|
15
|
+
version,
|
16
|
+
header,
|
17
|
+
granule_pos,
|
18
|
+
bitstream_serial_no,
|
19
|
+
sequence_no,
|
20
|
+
@checksum,
|
21
|
+
segments = chunk.unpack("a4CCQVVVC") #a4CCQNNNC
|
22
|
+
|
23
|
+
if capture_pattern != "OggS"
|
24
|
+
raise(StreamError, "bad magic number '#{ capture_pattern }'")
|
25
|
+
end
|
26
|
+
|
27
|
+
page = Page.new(bitstream_serial_no, granule_pos)
|
28
|
+
page.header = header
|
29
|
+
page.sequence_no = sequence_no
|
30
|
+
raise(StreamError, "got EOF when reading page") if io.eof?
|
31
|
+
|
32
|
+
segment_sizes = io.read(segments).unpack("C*")
|
33
|
+
if options[:skip_body]
|
34
|
+
body_size = segment_sizes.inject(0) { |sum, i| sum + i }
|
35
|
+
io.seek(body_size, IO::SEEK_CUR)
|
36
|
+
else
|
37
|
+
segment_sizes.each do |size|
|
38
|
+
break if io.eof?
|
39
|
+
page.segments << io.read(size)
|
40
|
+
end
|
41
|
+
if options[:checksum]
|
42
|
+
if @checksum != Ogg.compute_checksum(page.pack)
|
43
|
+
raise(StreamError, "bad checksum: expected #{ @checksum }, got #{ page.checksum }")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
page
|
49
|
+
end
|
50
|
+
|
51
|
+
def initialize(bitstream_serial_no = 0, granule_pos = 0)
|
52
|
+
@bitstream_serial_no = bitstream_serial_no
|
53
|
+
@granule_pos = granule_pos
|
54
|
+
@segments = []
|
55
|
+
@header = 0
|
56
|
+
end
|
57
|
+
|
58
|
+
def pack
|
59
|
+
packed = [
|
60
|
+
"OggS",
|
61
|
+
0, #version
|
62
|
+
@header,
|
63
|
+
@granule_pos,
|
64
|
+
@bitstream_serial_no,
|
65
|
+
@sequence_no,
|
66
|
+
0, #checksum
|
67
|
+
@segments.length
|
68
|
+
].pack("a4CCQVVVC")
|
69
|
+
|
70
|
+
packed << @segments.collect { |segment| segment.length }.pack("C*")
|
71
|
+
packed << @segments.join
|
72
|
+
crc = Ogg.compute_checksum(packed)
|
73
|
+
packed[22..25] = [crc].pack("V")
|
74
|
+
packed
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
data/lib/ogg/reader.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module Ogg
|
2
|
+
#Reads pages and packets from an ogg stream
|
3
|
+
class Reader
|
4
|
+
attr_reader :input
|
5
|
+
|
6
|
+
def initialize(input)
|
7
|
+
@input = input
|
8
|
+
end
|
9
|
+
|
10
|
+
def each_pages(options = {})
|
11
|
+
until @input.eof?
|
12
|
+
yield Page.read(@input, options)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def read_packets(max_packets)
|
17
|
+
result = []
|
18
|
+
partial_packet = ""
|
19
|
+
each_pages do |page|
|
20
|
+
partial_packet = page.segments.inject(partial_packet) do |packet,segment|
|
21
|
+
packet << segment
|
22
|
+
if segment.length < 255
|
23
|
+
#end of packet
|
24
|
+
result << packet
|
25
|
+
return result if result.length == max_packets
|
26
|
+
""
|
27
|
+
else
|
28
|
+
packet
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
# We expect packets to reach page boundaries, consider raising exception if partial_packet here.
|
33
|
+
result
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
data/lib/ogg/writer.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
module Ogg
|
4
|
+
# Writes pages or packets to an output io
|
5
|
+
|
6
|
+
class Writer
|
7
|
+
attr_reader :output
|
8
|
+
|
9
|
+
def initialize(bitstream_serial_no, output)
|
10
|
+
@output = output
|
11
|
+
@page_sequence = 0
|
12
|
+
@bitstream_serial_no = bitstream_serial_no
|
13
|
+
end
|
14
|
+
|
15
|
+
# Writes a page to the output, the serial number and page sequence are
|
16
|
+
# are overwritten to be appropriate for this stream.
|
17
|
+
def write_page(page)
|
18
|
+
page.sequence_no = @page_sequence
|
19
|
+
@output << page.pack
|
20
|
+
@page_sequence += 1
|
21
|
+
end
|
22
|
+
|
23
|
+
def write_packets(granule_pos, *packets)
|
24
|
+
written_pages_count = 1
|
25
|
+
page = Page.new(@bitstream_serial_no, granule_pos)
|
26
|
+
packets.each do |packet|
|
27
|
+
io = StringIO.new(packet)
|
28
|
+
|
29
|
+
while !io.eof? do
|
30
|
+
page.segments << io.read(255)
|
31
|
+
if (page.segments.length == 255)
|
32
|
+
page.granule_pos = -1
|
33
|
+
write_page(page)
|
34
|
+
page = Page.new(@bitstream_serial_no, granule_pos)
|
35
|
+
written_pages_count += 1
|
36
|
+
end
|
37
|
+
end
|
38
|
+
#If our packet was an exact multiple of 255 we need to put in an empty closing segment
|
39
|
+
if (page.segments.length == 0 || page.segments.last.length == 255)
|
40
|
+
page.segments << ""
|
41
|
+
end
|
42
|
+
end
|
43
|
+
#we always need to flush the final page.
|
44
|
+
write_page(page)
|
45
|
+
written_pages_count
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/ogg.rb
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
#Ogg framing
|
2
|
+
# see http://www.xiph.org/ogg/vorbis/docs.html for documentation on vorbis format
|
3
|
+
# http://www.xiph.org/vorbis/doc/framing.html
|
4
|
+
|
5
|
+
%w{page reader writer codecs/comments codecs/vorbis codecs/speex}.each do |file|
|
6
|
+
require File.join(File.dirname(__FILE__), "ogg", file)
|
7
|
+
end
|
8
|
+
|
9
|
+
module Ogg
|
10
|
+
# Raised on any kind of Ogg parsing/writing error
|
11
|
+
class StreamError < StandardError; end
|
12
|
+
CHECKSUM_TABLE = [
|
13
|
+
0x00000000,0x04c11db7,0x09823b6e,0x0d4326d9,
|
14
|
+
0x130476dc,0x17c56b6b,0x1a864db2,0x1e475005,
|
15
|
+
0x2608edb8,0x22c9f00f,0x2f8ad6d6,0x2b4bcb61,
|
16
|
+
0x350c9b64,0x31cd86d3,0x3c8ea00a,0x384fbdbd,
|
17
|
+
0x4c11db70,0x48d0c6c7,0x4593e01e,0x4152fda9,
|
18
|
+
0x5f15adac,0x5bd4b01b,0x569796c2,0x52568b75,
|
19
|
+
0x6a1936c8,0x6ed82b7f,0x639b0da6,0x675a1011,
|
20
|
+
0x791d4014,0x7ddc5da3,0x709f7b7a,0x745e66cd,
|
21
|
+
0x9823b6e0,0x9ce2ab57,0x91a18d8e,0x95609039,
|
22
|
+
0x8b27c03c,0x8fe6dd8b,0x82a5fb52,0x8664e6e5,
|
23
|
+
0xbe2b5b58,0xbaea46ef,0xb7a96036,0xb3687d81,
|
24
|
+
0xad2f2d84,0xa9ee3033,0xa4ad16ea,0xa06c0b5d,
|
25
|
+
0xd4326d90,0xd0f37027,0xddb056fe,0xd9714b49,
|
26
|
+
0xc7361b4c,0xc3f706fb,0xceb42022,0xca753d95,
|
27
|
+
0xf23a8028,0xf6fb9d9f,0xfbb8bb46,0xff79a6f1,
|
28
|
+
0xe13ef6f4,0xe5ffeb43,0xe8bccd9a,0xec7dd02d,
|
29
|
+
0x34867077,0x30476dc0,0x3d044b19,0x39c556ae,
|
30
|
+
0x278206ab,0x23431b1c,0x2e003dc5,0x2ac12072,
|
31
|
+
0x128e9dcf,0x164f8078,0x1b0ca6a1,0x1fcdbb16,
|
32
|
+
0x018aeb13,0x054bf6a4,0x0808d07d,0x0cc9cdca,
|
33
|
+
0x7897ab07,0x7c56b6b0,0x71159069,0x75d48dde,
|
34
|
+
0x6b93dddb,0x6f52c06c,0x6211e6b5,0x66d0fb02,
|
35
|
+
0x5e9f46bf,0x5a5e5b08,0x571d7dd1,0x53dc6066,
|
36
|
+
0x4d9b3063,0x495a2dd4,0x44190b0d,0x40d816ba,
|
37
|
+
0xaca5c697,0xa864db20,0xa527fdf9,0xa1e6e04e,
|
38
|
+
0xbfa1b04b,0xbb60adfc,0xb6238b25,0xb2e29692,
|
39
|
+
0x8aad2b2f,0x8e6c3698,0x832f1041,0x87ee0df6,
|
40
|
+
0x99a95df3,0x9d684044,0x902b669d,0x94ea7b2a,
|
41
|
+
0xe0b41de7,0xe4750050,0xe9362689,0xedf73b3e,
|
42
|
+
0xf3b06b3b,0xf771768c,0xfa325055,0xfef34de2,
|
43
|
+
0xc6bcf05f,0xc27dede8,0xcf3ecb31,0xcbffd686,
|
44
|
+
0xd5b88683,0xd1799b34,0xdc3abded,0xd8fba05a,
|
45
|
+
0x690ce0ee,0x6dcdfd59,0x608edb80,0x644fc637,
|
46
|
+
0x7a089632,0x7ec98b85,0x738aad5c,0x774bb0eb,
|
47
|
+
0x4f040d56,0x4bc510e1,0x46863638,0x42472b8f,
|
48
|
+
0x5c007b8a,0x58c1663d,0x558240e4,0x51435d53,
|
49
|
+
0x251d3b9e,0x21dc2629,0x2c9f00f0,0x285e1d47,
|
50
|
+
0x36194d42,0x32d850f5,0x3f9b762c,0x3b5a6b9b,
|
51
|
+
0x0315d626,0x07d4cb91,0x0a97ed48,0x0e56f0ff,
|
52
|
+
0x1011a0fa,0x14d0bd4d,0x19939b94,0x1d528623,
|
53
|
+
0xf12f560e,0xf5ee4bb9,0xf8ad6d60,0xfc6c70d7,
|
54
|
+
0xe22b20d2,0xe6ea3d65,0xeba91bbc,0xef68060b,
|
55
|
+
0xd727bbb6,0xd3e6a601,0xdea580d8,0xda649d6f,
|
56
|
+
0xc423cd6a,0xc0e2d0dd,0xcda1f604,0xc960ebb3,
|
57
|
+
0xbd3e8d7e,0xb9ff90c9,0xb4bcb610,0xb07daba7,
|
58
|
+
0xae3afba2,0xaafbe615,0xa7b8c0cc,0xa379dd7b,
|
59
|
+
0x9b3660c6,0x9ff77d71,0x92b45ba8,0x9675461f,
|
60
|
+
0x8832161a,0x8cf30bad,0x81b02d74,0x857130c3,
|
61
|
+
0x5d8a9099,0x594b8d2e,0x5408abf7,0x50c9b640,
|
62
|
+
0x4e8ee645,0x4a4ffbf2,0x470cdd2b,0x43cdc09c,
|
63
|
+
0x7b827d21,0x7f436096,0x7200464f,0x76c15bf8,
|
64
|
+
0x68860bfd,0x6c47164a,0x61043093,0x65c52d24,
|
65
|
+
0x119b4be9,0x155a565e,0x18197087,0x1cd86d30,
|
66
|
+
0x029f3d35,0x065e2082,0x0b1d065b,0x0fdc1bec,
|
67
|
+
0x3793a651,0x3352bbe6,0x3e119d3f,0x3ad08088,
|
68
|
+
0x2497d08d,0x2056cd3a,0x2d15ebe3,0x29d4f654,
|
69
|
+
0xc5a92679,0xc1683bce,0xcc2b1d17,0xc8ea00a0,
|
70
|
+
0xd6ad50a5,0xd26c4d12,0xdf2f6bcb,0xdbee767c,
|
71
|
+
0xe3a1cbc1,0xe760d676,0xea23f0af,0xeee2ed18,
|
72
|
+
0xf0a5bd1d,0xf464a0aa,0xf9278673,0xfde69bc4,
|
73
|
+
0x89b8fd09,0x8d79e0be,0x803ac667,0x84fbdbd0,
|
74
|
+
0x9abc8bd5,0x9e7d9662,0x933eb0bb,0x97ffad0c,
|
75
|
+
0xafb010b1,0xab710d06,0xa6322bdf,0xa2f33668,
|
76
|
+
0xbcb4666d,0xb8757bda,0xb5365d03,0xb1f740b4
|
77
|
+
]
|
78
|
+
|
79
|
+
|
80
|
+
class << self
|
81
|
+
def detect_codec(input)
|
82
|
+
if input.kind_of?(Page)
|
83
|
+
first_page = input
|
84
|
+
else
|
85
|
+
first_page = Page.read(input)
|
86
|
+
input.rewind
|
87
|
+
end
|
88
|
+
|
89
|
+
codecs = Ogg::Codecs.constants.map { |module_name| Ogg::Codecs.class_eval(module_name) }.select { |c| c.is_a?(Class) }
|
90
|
+
codec = codecs.detect { |c| c.match?(first_page.segments.first) }
|
91
|
+
unless codec
|
92
|
+
raise(StreamError,"unknown codec")
|
93
|
+
end
|
94
|
+
|
95
|
+
return codec
|
96
|
+
end
|
97
|
+
|
98
|
+
# Calculate the checksum from the page (or the pre packed data)
|
99
|
+
# If data it supplied it will be updated to record the checksum value
|
100
|
+
def compute_checksum(data_)
|
101
|
+
data = data_.dup
|
102
|
+
data[22..25] = [0].pack("V")
|
103
|
+
crc = 0
|
104
|
+
|
105
|
+
data.each_byte do |byte|
|
106
|
+
crc = (crc << 8)^CHECKSUM_TABLE[((crc >> 24)&0xff) ^ byte]
|
107
|
+
crc = crc & 0xffffffff
|
108
|
+
end
|
109
|
+
crc
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
if __FILE__ == $0
|
116
|
+
require 'pp'
|
117
|
+
infile = ARGV[0]
|
118
|
+
outfile = ARGV[1]
|
119
|
+
|
120
|
+
vendor = ""
|
121
|
+
tags = { }
|
122
|
+
File.open(infile,"r") do |input|
|
123
|
+
info = Ogg.read_headers(input)
|
124
|
+
pp info
|
125
|
+
vendor = info[:tag_vendor]
|
126
|
+
tag = info[:tag]
|
127
|
+
end
|
128
|
+
|
129
|
+
File.open(infile,"r") do | input |
|
130
|
+
File.open(outfile,"w") do | output |
|
131
|
+
Ogg.replace_tags(input,output,tags,vendor)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
File.open(outfile,"r") do | output |
|
136
|
+
pp Ogg.read_headers(output)
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
data/lib/ogginfo.rb
CHANGED
@@ -5,6 +5,9 @@
|
|
5
5
|
# License: ruby
|
6
6
|
|
7
7
|
require "iconv"
|
8
|
+
require 'forwardable'
|
9
|
+
require "tempfile"
|
10
|
+
require File.join(File.dirname(__FILE__), 'ogg.rb')
|
8
11
|
|
9
12
|
class Hash
|
10
13
|
### lets you specify hash["key"] as hash.key
|
@@ -23,95 +26,59 @@ end
|
|
23
26
|
class OggInfoError < StandardError ; end
|
24
27
|
|
25
28
|
class OggInfo
|
26
|
-
VERSION = "0.5"
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
0x350c9b64,0x31cd86d3,0x3c8ea00a,0x384fbdbd,
|
32
|
-
0x4c11db70,0x48d0c6c7,0x4593e01e,0x4152fda9,
|
33
|
-
0x5f15adac,0x5bd4b01b,0x569796c2,0x52568b75,
|
34
|
-
0x6a1936c8,0x6ed82b7f,0x639b0da6,0x675a1011,
|
35
|
-
0x791d4014,0x7ddc5da3,0x709f7b7a,0x745e66cd,
|
36
|
-
0x9823b6e0,0x9ce2ab57,0x91a18d8e,0x95609039,
|
37
|
-
0x8b27c03c,0x8fe6dd8b,0x82a5fb52,0x8664e6e5,
|
38
|
-
0xbe2b5b58,0xbaea46ef,0xb7a96036,0xb3687d81,
|
39
|
-
0xad2f2d84,0xa9ee3033,0xa4ad16ea,0xa06c0b5d,
|
40
|
-
0xd4326d90,0xd0f37027,0xddb056fe,0xd9714b49,
|
41
|
-
0xc7361b4c,0xc3f706fb,0xceb42022,0xca753d95,
|
42
|
-
0xf23a8028,0xf6fb9d9f,0xfbb8bb46,0xff79a6f1,
|
43
|
-
0xe13ef6f4,0xe5ffeb43,0xe8bccd9a,0xec7dd02d,
|
44
|
-
0x34867077,0x30476dc0,0x3d044b19,0x39c556ae,
|
45
|
-
0x278206ab,0x23431b1c,0x2e003dc5,0x2ac12072,
|
46
|
-
0x128e9dcf,0x164f8078,0x1b0ca6a1,0x1fcdbb16,
|
47
|
-
0x018aeb13,0x054bf6a4,0x0808d07d,0x0cc9cdca,
|
48
|
-
0x7897ab07,0x7c56b6b0,0x71159069,0x75d48dde,
|
49
|
-
0x6b93dddb,0x6f52c06c,0x6211e6b5,0x66d0fb02,
|
50
|
-
0x5e9f46bf,0x5a5e5b08,0x571d7dd1,0x53dc6066,
|
51
|
-
0x4d9b3063,0x495a2dd4,0x44190b0d,0x40d816ba,
|
52
|
-
0xaca5c697,0xa864db20,0xa527fdf9,0xa1e6e04e,
|
53
|
-
0xbfa1b04b,0xbb60adfc,0xb6238b25,0xb2e29692,
|
54
|
-
0x8aad2b2f,0x8e6c3698,0x832f1041,0x87ee0df6,
|
55
|
-
0x99a95df3,0x9d684044,0x902b669d,0x94ea7b2a,
|
56
|
-
0xe0b41de7,0xe4750050,0xe9362689,0xedf73b3e,
|
57
|
-
0xf3b06b3b,0xf771768c,0xfa325055,0xfef34de2,
|
58
|
-
0xc6bcf05f,0xc27dede8,0xcf3ecb31,0xcbffd686,
|
59
|
-
0xd5b88683,0xd1799b34,0xdc3abded,0xd8fba05a,
|
60
|
-
0x690ce0ee,0x6dcdfd59,0x608edb80,0x644fc637,
|
61
|
-
0x7a089632,0x7ec98b85,0x738aad5c,0x774bb0eb,
|
62
|
-
0x4f040d56,0x4bc510e1,0x46863638,0x42472b8f,
|
63
|
-
0x5c007b8a,0x58c1663d,0x558240e4,0x51435d53,
|
64
|
-
0x251d3b9e,0x21dc2629,0x2c9f00f0,0x285e1d47,
|
65
|
-
0x36194d42,0x32d850f5,0x3f9b762c,0x3b5a6b9b,
|
66
|
-
0x0315d626,0x07d4cb91,0x0a97ed48,0x0e56f0ff,
|
67
|
-
0x1011a0fa,0x14d0bd4d,0x19939b94,0x1d528623,
|
68
|
-
0xf12f560e,0xf5ee4bb9,0xf8ad6d60,0xfc6c70d7,
|
69
|
-
0xe22b20d2,0xe6ea3d65,0xeba91bbc,0xef68060b,
|
70
|
-
0xd727bbb6,0xd3e6a601,0xdea580d8,0xda649d6f,
|
71
|
-
0xc423cd6a,0xc0e2d0dd,0xcda1f604,0xc960ebb3,
|
72
|
-
0xbd3e8d7e,0xb9ff90c9,0xb4bcb610,0xb07daba7,
|
73
|
-
0xae3afba2,0xaafbe615,0xa7b8c0cc,0xa379dd7b,
|
74
|
-
0x9b3660c6,0x9ff77d71,0x92b45ba8,0x9675461f,
|
75
|
-
0x8832161a,0x8cf30bad,0x81b02d74,0x857130c3,
|
76
|
-
0x5d8a9099,0x594b8d2e,0x5408abf7,0x50c9b640,
|
77
|
-
0x4e8ee645,0x4a4ffbf2,0x470cdd2b,0x43cdc09c,
|
78
|
-
0x7b827d21,0x7f436096,0x7200464f,0x76c15bf8,
|
79
|
-
0x68860bfd,0x6c47164a,0x61043093,0x65c52d24,
|
80
|
-
0x119b4be9,0x155a565e,0x18197087,0x1cd86d30,
|
81
|
-
0x029f3d35,0x065e2082,0x0b1d065b,0x0fdc1bec,
|
82
|
-
0x3793a651,0x3352bbe6,0x3e119d3f,0x3ad08088,
|
83
|
-
0x2497d08d,0x2056cd3a,0x2d15ebe3,0x29d4f654,
|
84
|
-
0xc5a92679,0xc1683bce,0xcc2b1d17,0xc8ea00a0,
|
85
|
-
0xd6ad50a5,0xd26c4d12,0xdf2f6bcb,0xdbee767c,
|
86
|
-
0xe3a1cbc1,0xe760d676,0xea23f0af,0xeee2ed18,
|
87
|
-
0xf0a5bd1d,0xf464a0aa,0xf9278673,0xfde69bc4,
|
88
|
-
0x89b8fd09,0x8d79e0be,0x803ac667,0x84fbdbd0,
|
89
|
-
0x9abc8bd5,0x9e7d9662,0x933eb0bb,0x97ffad0c,
|
90
|
-
0xafb010b1,0xab710d06,0xa6322bdf,0xa2f33668,
|
91
|
-
0xbcb4666d,0xb8757bda,0xb5365d03,0xb1f740b4
|
92
|
-
]
|
93
|
-
|
94
|
-
attr_reader :channels, :samplerate, :bitrate, :nominal_bitrate, :length
|
29
|
+
VERSION = "0.6.5"
|
30
|
+
extend Forwardable
|
31
|
+
include Ogg
|
32
|
+
|
33
|
+
attr_reader :channels, :samplerate, :nominal_bitrate
|
95
34
|
|
96
35
|
# +tag+ is a hash containing the vorbis tag like "Artist", "Title", and the like
|
97
36
|
attr_reader :tag
|
98
|
-
|
99
|
-
# create new instance of OggInfo, using +charset+ to convert tags
|
37
|
+
|
38
|
+
# create new instance of OggInfo, using +charset+ to convert tags
|
100
39
|
def initialize(filename, charset = "utf-8")
|
101
40
|
@filename = filename
|
102
41
|
@charset = charset
|
103
|
-
@
|
42
|
+
@length = nil
|
43
|
+
@bitrate = nil
|
44
|
+
filesize = File.size(@filename)
|
45
|
+
File.open(@filename) do |file|
|
46
|
+
begin
|
47
|
+
info = read_headers(file)
|
48
|
+
@samplerate = info[:samplerate]
|
49
|
+
@nominal_bitrate = info[:nominal_bitrate]
|
50
|
+
@channels = info[:channels]
|
51
|
+
@tag = info[:tag]
|
52
|
+
# filesize is used to calculate bitrate
|
53
|
+
# but we don't want to include the headers
|
54
|
+
@filesize = file.stat.size - file.pos
|
55
|
+
rescue Ogg::StreamError => se
|
56
|
+
raise(OggInfoError, se.message, se.backtrace)
|
57
|
+
end
|
58
|
+
end
|
104
59
|
|
105
|
-
frames = (1..2).collect { |i| OggInfo.read_frame(@file) }
|
106
|
-
extract_bitstream_infos(frames[0])
|
107
|
-
extract_tag(frames[1])
|
108
60
|
convert_tag_charset("utf-8", @charset)
|
109
61
|
@original_tag = @tag.dup
|
110
|
-
@length = get_length
|
111
|
-
@bitrate = @file.stat.size.to_f*8/@length
|
112
|
-
@file.close
|
113
62
|
end
|
114
63
|
|
64
|
+
# The length in seconds of the track
|
65
|
+
# since this requires reading the whole file we only get it
|
66
|
+
# if called
|
67
|
+
def length
|
68
|
+
unless @length
|
69
|
+
File.open(@filename) do |file|
|
70
|
+
@length = compute_length(file)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
return @length
|
74
|
+
end
|
75
|
+
|
76
|
+
# Calculated bit rate, also lazily loaded
|
77
|
+
# since we depend on the length
|
78
|
+
def bitrate
|
79
|
+
@bitrate ||= (@filesize * 8).to_f / length()
|
80
|
+
end
|
81
|
+
|
115
82
|
# "block version" of ::new()
|
116
83
|
def self.open(*args)
|
117
84
|
m = self.new(*args)
|
@@ -130,148 +97,87 @@ class OggInfo
|
|
130
97
|
|
131
98
|
# commits any tags to file
|
132
99
|
def close
|
133
|
-
if
|
134
|
-
cmd = %w{vorbiscomment -w}
|
100
|
+
if tag != @original_tag
|
135
101
|
convert_tag_charset(@charset, "utf-8")
|
136
|
-
|
137
|
-
|
138
|
-
|
102
|
+
|
103
|
+
tempfile = Tempfile.new("ruby-ogginfo")
|
104
|
+
begin
|
105
|
+
File.open(@filename, "rb") do | input |
|
106
|
+
replace_tags(input, tempfile, tag)
|
107
|
+
end
|
108
|
+
tempfile.close
|
109
|
+
FileUtils.cp(tempfile.path, @filename)
|
110
|
+
ensure
|
111
|
+
tempfile.close!
|
139
112
|
end
|
140
|
-
cmd << @filename
|
141
|
-
system(*cmd)
|
142
113
|
end
|
143
114
|
end
|
144
115
|
|
145
116
|
# check the presence of a tag
|
146
117
|
def hastag?
|
147
|
-
|
118
|
+
!tag.empty?
|
148
119
|
end
|
149
120
|
|
150
121
|
def to_s
|
151
|
-
"channels #{
|
152
|
-
end
|
153
|
-
|
154
|
-
# read an ogg frame from the +file+
|
155
|
-
def self.read_frame(file)
|
156
|
-
frame = {}
|
157
|
-
|
158
|
-
frame[:file_position] = file.pos
|
159
|
-
|
160
|
-
return nil if file.eof?
|
161
|
-
chunk = file.read(27)
|
162
|
-
raise OggInfoError if file.eof?
|
163
|
-
|
164
|
-
capture_pattern,
|
165
|
-
frame[:version],
|
166
|
-
frame[:header_type],
|
167
|
-
frame[:granule_pos],
|
168
|
-
frame[:bitstream_serial_number],
|
169
|
-
frame[:page_sequence_number],
|
170
|
-
frame[:checksum],
|
171
|
-
frame[:page_segments] = chunk.unpack("a4CCQNNNC")
|
172
|
-
|
173
|
-
if capture_pattern != "OggS"
|
174
|
-
raise(OggInfoError, "bad magic number '#{capture_pattern}'")
|
175
|
-
end
|
176
|
-
|
177
|
-
segment_sizes = file.read(frame[:page_segments]).unpack("C*")
|
178
|
-
frame[:body_size] = segment_sizes.inject(0) { |sum, i| sum += i }
|
179
|
-
frame[:header_size] = 27 + frame[:page_segments]
|
180
|
-
frame[:size] = frame[:header_size] + frame[:body_size]
|
181
|
-
file.seek(frame[:body_size], IO::SEEK_CUR)
|
182
|
-
frame
|
183
|
-
end
|
184
|
-
|
185
|
-
# compute the checksum of a given +frame+ from a given +file+, you can compare it with frame[:checksum].
|
186
|
-
def self.checksum(file, frame)
|
187
|
-
original_pos = file.pos
|
188
|
-
file.seek(frame[:file_position])
|
189
|
-
data = file.read(frame[:size])
|
190
|
-
data[22] = data[23] = data[24] = data[25] = 0
|
191
|
-
|
192
|
-
crc = 0
|
193
|
-
data.each_byte do |byte|
|
194
|
-
crc = (crc << 8)^CHECKSUM_TABLE[((crc >> 24)&0xff) ^ byte]
|
195
|
-
crc = crc & 0xffffffff
|
196
|
-
end
|
197
|
-
|
198
|
-
# "reverse" it
|
199
|
-
crc = [crc].pack("V").unpack("N").first
|
200
|
-
file.seek(original_pos)
|
201
|
-
crc
|
122
|
+
"channels #{channels} samplerate #{samplerate} bitrate #{nominal_bitrate} #{tag.inspect}"
|
202
123
|
end
|
203
124
|
|
204
125
|
private
|
205
|
-
def extract_bitstream_infos(frame)
|
206
|
-
@file.seek(frame[:file_position] + frame[:header_size] + 1)
|
207
|
-
@bitstream_format = @file.read(6).rstrip()
|
208
|
-
case @bitstream_format
|
209
|
-
when "vorbis" : extract_vorbis_infos(frame)
|
210
|
-
when "peex" :
|
211
|
-
#Speex does not align itself to a 4 byte boundary.
|
212
|
-
@bitstream_format="Speex"
|
213
|
-
extract_speex_infos(frame)
|
214
|
-
else raise(OggInfoError,"Unknown bitstream format #{@bitstream_format}")
|
215
|
-
end
|
216
|
-
end
|
217
126
|
|
218
|
-
def
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
#not sure how to make sense of the bitrate info,picard doesn't show it either...
|
127
|
+
def read_headers(input)
|
128
|
+
reader = Reader.new(input)
|
129
|
+
codec = Ogg.detect_codec(input)
|
130
|
+
codec.decode_headers(reader)
|
223
131
|
end
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
else
|
233
|
-
@nominal_bitrate = (upper_bitrate + lower_bitrate)/2
|
234
|
-
end
|
235
|
-
end
|
132
|
+
|
133
|
+
# For both Vorbis and Speex, the granule_pos is the number of samples
|
134
|
+
# strictly this should be a codec function.
|
135
|
+
def compute_length(input)
|
136
|
+
reader = Reader.new(input)
|
137
|
+
last_page = nil
|
138
|
+
reader.each_pages({ :skip_body => true, :skip_checksum => true }) { |page| last_page = page }
|
139
|
+
return last_page.granule_pos.to_f / @samplerate
|
236
140
|
end
|
141
|
+
|
237
142
|
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
#
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
@tag[key.downcase] = val
|
143
|
+
# Pipe input to output transforming tags along the way
|
144
|
+
# input/output must be open streams reading for reading/writing
|
145
|
+
def replace_tags(input, output, new_tags, vendor = "ruby-ogginfo")
|
146
|
+
# use the same serial number...
|
147
|
+
first_page = Page.read(input)
|
148
|
+
codec = Ogg.detect_codec(first_page)
|
149
|
+
bitstream_serial_no = first_page.bitstream_serial_no
|
150
|
+
reader = Reader.new(input)
|
151
|
+
writer = Writer.new(bitstream_serial_no, output)
|
152
|
+
|
153
|
+
# Write the first page as is (including presumably the b_o_s header)
|
154
|
+
writer.write_page(first_page)
|
155
|
+
|
156
|
+
upcased_tags = new_tags.inject({}) do |memo, (k, v)|
|
157
|
+
memo[k.upcase] = v
|
158
|
+
memo
|
255
159
|
end
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
160
|
+
# The codecs we know about put comments etc in following pages
|
161
|
+
# as suggested by the spec
|
162
|
+
written_pages_count = codec.replace_tags(reader, writer, upcased_tags, vendor)
|
163
|
+
if written_pages_count > 1
|
164
|
+
# Write the rest of the pages. We have to do page at a time
|
165
|
+
# because our tag replacement may have changed the number of
|
166
|
+
# pages and thus every subsequent page needs to have its
|
167
|
+
# sequence_no updated.
|
168
|
+
reader.each_pages(:skip_checksum => true) do |page|
|
169
|
+
writer.write_page(page)
|
264
170
|
end
|
265
|
-
|
171
|
+
else
|
172
|
+
FileUtils.copy_stream(reader.input, writer.output)
|
266
173
|
end
|
267
|
-
last_frame[:granule_pos].to_f / @samplerate
|
268
174
|
end
|
269
175
|
|
270
176
|
def convert_tag_charset(from_charset, to_charset)
|
271
177
|
return if from_charset == to_charset
|
272
178
|
Iconv.open(to_charset, from_charset) do |ic|
|
273
|
-
|
274
|
-
|
179
|
+
tag.each do |k, v|
|
180
|
+
tag[k] = ic.iconv(v)
|
275
181
|
end
|
276
182
|
end
|
277
183
|
end
|
data/test/test_ruby-ogginfo.rb
CHANGED
@@ -101,7 +101,7 @@ EOF
|
|
101
101
|
|
102
102
|
class OggInfoTest < Test::Unit::TestCase
|
103
103
|
|
104
|
-
TEMP_FILE = File.join(Dir.tmpdir, "
|
104
|
+
TEMP_FILE = File.join(Dir.tmpdir, "test_ogginfo.ogg")
|
105
105
|
|
106
106
|
def setup
|
107
107
|
valid_ogg_file = VALID_OGG.unpack("m*").first
|
@@ -123,40 +123,101 @@ class OggInfoTest < Test::Unit::TestCase
|
|
123
123
|
end
|
124
124
|
|
125
125
|
def test_length
|
126
|
-
generate_ogg
|
127
|
-
OggInfo.open(
|
128
|
-
assert_in_delta(17.0, ogg.length, 1)
|
129
|
-
assert_in_delta(67000.0, ogg.bitrate,
|
126
|
+
tf = generate_ogg
|
127
|
+
OggInfo.open(tf.path) do |ogg|
|
128
|
+
assert_in_delta(17.0, ogg.length, 1, "length has not been correctly guessed")
|
129
|
+
assert_in_delta(67000.0, ogg.bitrate, 2000, "bitrate has not been correctly guessed")
|
130
130
|
end
|
131
131
|
end
|
132
132
|
|
133
133
|
def test_tag_writing
|
134
|
-
|
135
|
-
|
136
|
-
OggInfo.open("test.ogg") do |ogg|
|
137
|
-
tag.each { |k,v| ogg.tag[k] = v }
|
138
|
-
end
|
134
|
+
tag_test("title" => generate_random_string, "artist" => generate_random_string )
|
135
|
+
end
|
139
136
|
|
140
|
-
|
141
|
-
|
142
|
-
end
|
137
|
+
def test_big_tags
|
138
|
+
tag_test("title" => generate_random_string(60000), "artist" => generate_random_string(60000) )
|
143
139
|
end
|
140
|
+
|
144
141
|
|
145
142
|
def test_charset
|
146
|
-
generate_ogg
|
147
|
-
OggInfo.open(
|
143
|
+
tf = generate_ogg
|
144
|
+
OggInfo.open(tf.path, "utf-8") do |ogg|
|
148
145
|
ogg.tag["title"] = "hello\303\251"
|
149
146
|
end
|
150
147
|
|
151
|
-
OggInfo.open(
|
148
|
+
OggInfo.open(tf.path, "iso-8859-1") do |ogg|
|
152
149
|
assert_equal "hello\xe9", ogg.tag["title"]
|
153
150
|
end
|
154
151
|
end
|
155
152
|
|
153
|
+
def test_should_not_fail_when_input_is_truncated
|
154
|
+
valid_ogg = generate_ogg
|
155
|
+
ogg_length = nil
|
156
|
+
OggInfo.open(valid_ogg.path) do |ogg|
|
157
|
+
ogg_length = ogg.length
|
158
|
+
end
|
159
|
+
|
160
|
+
tf = generate_truncated_ogg
|
161
|
+
OggInfo.open(tf.path) do |truncated_ogg|
|
162
|
+
assert ogg_length != truncated_ogg.length
|
163
|
+
end
|
164
|
+
|
165
|
+
reader = Ogg::Reader.new(open(tf.path, "r"))
|
166
|
+
last_page = nil
|
167
|
+
reader.each_pages do |page|
|
168
|
+
last_page = page
|
169
|
+
end
|
170
|
+
assert_not_equal Ogg.compute_checksum(last_page.pack), last_page.checksum
|
171
|
+
end
|
172
|
+
|
173
|
+
def test_checksum
|
174
|
+
tf = generate_truncated_ogg
|
175
|
+
reader = Ogg::Reader.new(open(tf.path))
|
176
|
+
assert_raises(Ogg::StreamError) do
|
177
|
+
reader.each_pages(:checksum => true) do |page|
|
178
|
+
page
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
protected
|
184
|
+
|
185
|
+
|
156
186
|
def generate_ogg
|
157
|
-
|
158
|
-
|
159
|
-
|
187
|
+
generated_ogg_file_path = File.join(File.dirname(__FILE__), "test.ogg")
|
188
|
+
unless test(?f, generated_ogg_file_path)
|
189
|
+
system("dd if=/dev/urandom bs=1024 count=3000 | oggenc -q0 --raw -o #{generated_ogg_file_path} -") or
|
190
|
+
flunk("cannot generate \"#{generated_ogg_file_path}\", tests cannot be fully performed")
|
191
|
+
end
|
192
|
+
tf = Tempfile.new("ruby-ogginfo")
|
193
|
+
tf.close
|
194
|
+
FileUtils.cp(generated_ogg_file_path, tf.path)
|
195
|
+
tf
|
196
|
+
end
|
197
|
+
|
198
|
+
def generate_random_string(size = 256)
|
199
|
+
File.read("/dev/urandom", size)
|
200
|
+
end
|
201
|
+
|
202
|
+
def generate_truncated_ogg
|
203
|
+
valid_ogg = generate_ogg
|
204
|
+
tf = Tempfile.new("ruby-ogginfo")
|
205
|
+
data = File.read(valid_ogg.path, File.size(valid_ogg.path) - 10000)
|
206
|
+
tf.write(data)
|
207
|
+
tf.close
|
208
|
+
tf
|
209
|
+
end
|
210
|
+
|
211
|
+
def tag_test(tag)
|
212
|
+
tf = generate_ogg
|
213
|
+
|
214
|
+
OggInfo.open(tf.path) do |ogg|
|
215
|
+
tag.each { |k,v| ogg.tag[k] = v }
|
216
|
+
end
|
217
|
+
|
218
|
+
OggInfo.open(tf.path) do |ogg|
|
219
|
+
assert_equal tag, ogg.tag
|
160
220
|
end
|
221
|
+
test_length
|
161
222
|
end
|
162
223
|
end
|
data/test/test_ruby-spxinfo.rb
CHANGED
@@ -58,6 +58,7 @@ EOF
|
|
58
58
|
class SpxInfoTest < Test::Unit::TestCase
|
59
59
|
|
60
60
|
TEMP_FILE = File.join(Dir.tmpdir, "test.spxinfo.spx")
|
61
|
+
GEN_FILE = File.join(Dir.tmpdir,"test.spxgen.spx")
|
61
62
|
|
62
63
|
def setup
|
63
64
|
valid_spx_file = VALID_SPX.unpack("m*").first
|
@@ -73,6 +74,7 @@ class SpxInfoTest < Test::Unit::TestCase
|
|
73
74
|
assert_equal 1, spx.channels
|
74
75
|
assert_equal 32000, spx.samplerate
|
75
76
|
assert_in_delta(0.5, spx.length, 1)
|
77
|
+
assert_equal "cityrail",spx.tag["author"]
|
76
78
|
end
|
77
79
|
end
|
78
80
|
|
@@ -81,20 +83,36 @@ class SpxInfoTest < Test::Unit::TestCase
|
|
81
83
|
OggInfo.open("test.spx") do |spx|
|
82
84
|
assert_equal 2, spx.channels
|
83
85
|
assert_equal 44100, spx.samplerate
|
86
|
+
assert_equal "spxinfotest", spx.tag.author
|
84
87
|
end
|
85
88
|
end
|
86
89
|
|
87
90
|
|
88
91
|
def test_charset
|
89
92
|
generate_spx
|
90
|
-
|
91
|
-
|
93
|
+
FileUtils.cp("test.spx",GEN_FILE)
|
94
|
+
OggInfo.open(GEN_FILE, "utf-8") do |spx|
|
95
|
+
assert_equal "hello\303\251",spx.tag["test"]
|
92
96
|
end
|
93
97
|
|
94
|
-
OggInfo.open(
|
98
|
+
OggInfo.open(GEN_FILE, "iso-8859-1") do |spx|
|
95
99
|
assert_equal "hello\xe9", spx.tag["test"]
|
96
100
|
end
|
97
101
|
end
|
102
|
+
|
103
|
+
def test_tag_writing
|
104
|
+
generate_spx
|
105
|
+
FileUtils.cp("test.spx",GEN_FILE)
|
106
|
+
tag = {"title" => "mytitle", "test" => "myartist" }
|
107
|
+
OggInfo.open(GEN_FILE) do |spx|
|
108
|
+
spx.tag.clear
|
109
|
+
tag.each { |k,v| spx.tag[k] = v }
|
110
|
+
end
|
111
|
+
|
112
|
+
OggInfo.open(GEN_FILE) do |spx|
|
113
|
+
assert_equal tag, spx.tag
|
114
|
+
end
|
115
|
+
end
|
98
116
|
|
99
117
|
def generate_spx
|
100
118
|
unless test(?f, "test.spx")
|
metadata
CHANGED
@@ -4,16 +4,18 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
+
- 6
|
7
8
|
- 5
|
8
|
-
version:
|
9
|
+
version: 0.6.5
|
9
10
|
platform: ruby
|
10
11
|
authors:
|
11
12
|
- Guillaume Pierronnet
|
13
|
+
- Grant Gardner
|
12
14
|
autorequire:
|
13
15
|
bindir: bin
|
14
16
|
cert_chain: []
|
15
17
|
|
16
|
-
date: 2011-
|
18
|
+
date: 2011-04-07 00:00:00 +02:00
|
17
19
|
default_executable:
|
18
20
|
dependencies:
|
19
21
|
- !ruby/object:Gem::Dependency
|
@@ -48,7 +50,9 @@ description: |-
|
|
48
50
|
ruby-ogginfo gives you access to low level information on ogg files
|
49
51
|
(bitrate, length, samplerate, encoder, etc... ), as well as tag.
|
50
52
|
It is written in pure ruby.
|
51
|
-
email:
|
53
|
+
email:
|
54
|
+
- moumar@rubyforge.org
|
55
|
+
- grant@lastweekend.com.au
|
52
56
|
executables: []
|
53
57
|
|
54
58
|
extensions: []
|
@@ -61,13 +65,19 @@ files:
|
|
61
65
|
- Manifest.txt
|
62
66
|
- README.rdoc
|
63
67
|
- Rakefile
|
64
|
-
-
|
68
|
+
- lib/ogg/codecs/comments.rb
|
69
|
+
- lib/ogg/codecs/speex.rb
|
70
|
+
- lib/ogg/codecs/vorbis.rb
|
71
|
+
- lib/ogg/page.rb
|
72
|
+
- lib/ogg/reader.rb
|
73
|
+
- lib/ogg/writer.rb
|
74
|
+
- lib/ogg.rb
|
65
75
|
- lib/ogginfo.rb
|
76
|
+
- setup.rb
|
66
77
|
- test/test_ruby-ogginfo.rb
|
78
|
+
- test/test_ruby-spxinfo.rb
|
67
79
|
has_rdoc: yard
|
68
|
-
homepage:
|
69
|
-
http://ruby-ogginfo.rubyforge.org/
|
70
|
-
|
80
|
+
homepage: http://ruby-ogginfo.rubyforge.org/
|
71
81
|
licenses: []
|
72
82
|
|
73
83
|
post_install_message:
|
@@ -97,7 +107,7 @@ rubyforge_project: ruby-ogginfo
|
|
97
107
|
rubygems_version: 1.3.6
|
98
108
|
signing_key:
|
99
109
|
specification_version: 3
|
100
|
-
summary: ruby-ogginfo
|
110
|
+
summary: ruby-ogginfo gives you access to low level information on ogg files (bitrate, length, samplerate, encoder, etc..
|
101
111
|
test_files:
|
102
|
-
- test/test_ruby-ogginfo.rb
|
103
112
|
- test/test_ruby-spxinfo.rb
|
113
|
+
- test/test_ruby-ogginfo.rb
|