ruby-ogginfo 0.5 → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|