ruby-cafinfo 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +3 -0
- data/LICENSE +21 -0
- data/README +76 -0
- data/Rakefile +43 -0
- data/lib/caf/chunk.rb +15 -0
- data/lib/caf/chunk/audio_description.rb +57 -0
- data/lib/caf/chunk/base.rb +43 -0
- data/lib/caf/chunk/data.rb +47 -0
- data/lib/caf/chunk/free.rb +24 -0
- data/lib/caf/chunk/helper.rb +35 -0
- data/lib/caf/chunk/magic_cookie.rb +35 -0
- data/lib/caf/chunk/types.rb +5 -0
- data/lib/caf/errors.rb +4 -0
- data/lib/caf/extension_modules.rb +41 -0
- data/lib/cafinfo.rb +203 -0
- metadata +80 -0
data/CHANGELOG
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2009 Oleguer Huguet Ibars
|
2
|
+
|
3
|
+
The MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
ruby-cafinfo
|
2
|
+
|
3
|
+
by Oleguer Huguet Ibars
|
4
|
+
* http://rubyforge.org/projects/ruby-cafinfo
|
5
|
+
* git repository: git://rubyforge.org/ruby-cafinfo.git
|
6
|
+
|
7
|
+
== DESCRIPTION:
|
8
|
+
|
9
|
+
A ruby library to retrieve information of CAF (Core Audio Format) files, heavily inspired by the excellent ruby-mp3info[http://ruby-mp3info.rubyforge.org/].
|
10
|
+
|
11
|
+
From the CAF format specification:
|
12
|
+
|
13
|
+
"Apple's Core Audio Format (CAF) is a file format for storing and transporting digital
|
14
|
+
audio data. It simplifies the management and manipulation of many types of audio data
|
15
|
+
without the file-size limitations of other audio file formats."
|
16
|
+
|
17
|
+
Specs for CAF format can be found here[http://developer.apple.com/DOCUMENTATION/MusicAudio/Reference/CAFSpec/CAF_intro/CAF_intro.html].
|
18
|
+
|
19
|
+
== FEATURES:
|
20
|
+
|
21
|
+
* written in pure ruby
|
22
|
+
* read low-level informations like bitrate, length, samplerate, etc...
|
23
|
+
* only some chunk types are recognized (Audio Description, Data, Free, Magick Cookie)
|
24
|
+
|
25
|
+
== SYNOPSIS:
|
26
|
+
|
27
|
+
Using as a lib is easy:
|
28
|
+
|
29
|
+
require "cafinfo"
|
30
|
+
# read and display info
|
31
|
+
CafInfo.open("myfile.caf") do |cafinfo|
|
32
|
+
puts cafinfo
|
33
|
+
end
|
34
|
+
|
35
|
+
# read and display some attributes
|
36
|
+
CafInfo.open("myfile.caf") do |caf|
|
37
|
+
puts caf.length
|
38
|
+
puts caf.samplerate
|
39
|
+
puts caf.format
|
40
|
+
end
|
41
|
+
|
42
|
+
# extract payload audio data from a (small) CAF and write to another file
|
43
|
+
audio_pos, audio_length = CafInfo.open("myfile.caf") { |caf| caf.audio_content }
|
44
|
+
File.open("myfile.caf", "rb") do |caf_file|
|
45
|
+
File.open("myaudio.data", "wb") do |f|
|
46
|
+
caf_file.seek(audio_pos)
|
47
|
+
data = caf_file.read(audio_length)
|
48
|
+
f.write(data)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# extract payload audio data from an arbitrarily large CAF and write
|
53
|
+
# to another file, considering that data chunk may not be at the end
|
54
|
+
# of caf file
|
55
|
+
audio_pos, audio_length = CafInfo.open("myfile.caf") { |caf| caf.audio_content }
|
56
|
+
File.open("myfile.caf", "rb") do |caf_file|
|
57
|
+
File.open("myaudio-buffered.data", "wb") do |f|
|
58
|
+
caf_file.seek(audio_pos)
|
59
|
+
while buffer = caf_file.read([4096, audio_length].min) and buffer.size > 0
|
60
|
+
f.write(buffer)
|
61
|
+
audio_length -= buffer.size
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
== INSTALL:
|
67
|
+
|
68
|
+
# gem install ruby-cafinfo
|
69
|
+
|
70
|
+
== LICENSE:
|
71
|
+
|
72
|
+
MIT license
|
73
|
+
|
74
|
+
== TODO:
|
75
|
+
|
76
|
+
* support for other chunk types
|
data/Rakefile
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/clean'
|
4
|
+
require 'rake/gempackagetask'
|
5
|
+
require 'rake/rdoctask'
|
6
|
+
require 'rake/testtask'
|
7
|
+
|
8
|
+
spec = Gem::Specification.new do |s|
|
9
|
+
s.name = 'ruby-cafinfo'
|
10
|
+
s.version = '0.1'
|
11
|
+
s.has_rdoc = true
|
12
|
+
s.extra_rdoc_files = ['README', 'LICENSE', 'CHANGELOG']
|
13
|
+
s.summary = 'ruby-cafinfo is a ruby library to retrieve low level informations on CAF files'
|
14
|
+
s.description = s.summary
|
15
|
+
s.author = 'Oleguer Huguet Ibars'
|
16
|
+
s.email = 'olegueret@rubyforge.org'
|
17
|
+
# s.executables = ['your_executable_here']
|
18
|
+
s.files = %w(LICENSE README Rakefile) + Dir.glob("{bin,lib,spec}/**/*")
|
19
|
+
s.require_path = "lib"
|
20
|
+
#s.bindir = "bin"
|
21
|
+
s.add_dependency 'float-formats', '>=0.1.1'
|
22
|
+
s.rubyforge_project = 'ruby-cafinfo'
|
23
|
+
s.homepage = 'http://ruby-cafinfo.rubyforge.org'
|
24
|
+
end
|
25
|
+
|
26
|
+
Rake::GemPackageTask.new(spec) do |p|
|
27
|
+
p.gem_spec = spec
|
28
|
+
p.need_tar = true
|
29
|
+
p.need_zip = true
|
30
|
+
end
|
31
|
+
|
32
|
+
Rake::RDocTask.new do |rdoc|
|
33
|
+
files =['README', 'LICENSE', 'CHANGELOG', 'lib/**/*.rb']
|
34
|
+
rdoc.rdoc_files.add(files)
|
35
|
+
rdoc.main = "README" # page to start on
|
36
|
+
rdoc.title = "ruby-cafinfo Docs"
|
37
|
+
rdoc.rdoc_dir = 'doc/rdoc' # rdoc output folder
|
38
|
+
rdoc.options << '--line-numbers'
|
39
|
+
end
|
40
|
+
|
41
|
+
Rake::TestTask.new do |t|
|
42
|
+
t.test_files = FileList['test/**/*.rb']
|
43
|
+
end
|
data/lib/caf/chunk.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'caf/chunk/base'
|
2
|
+
|
3
|
+
module Caf
|
4
|
+
module Chunk
|
5
|
+
def self.available_chunk_types
|
6
|
+
@available_chunk_types ||= {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.build(chunk_header)
|
10
|
+
klass = available_chunk_types[(chunk_header[:chunk_type] || "").downcase]
|
11
|
+
klass = Base if klass.nil?
|
12
|
+
klass.new(chunk_header)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'caf/chunk/base'
|
2
|
+
require 'caf/chunk/helper'
|
3
|
+
require 'caf/errors'
|
4
|
+
|
5
|
+
module Caf::Chunk
|
6
|
+
include Helper
|
7
|
+
|
8
|
+
class AudioDescription < Base
|
9
|
+
|
10
|
+
implements 'desc'
|
11
|
+
|
12
|
+
attr_accessor :samplerate, :format_id, :format_flags, :bytes_per_packet,
|
13
|
+
:frames_per_packet, :channels_per_frame, :bits_per_channel
|
14
|
+
|
15
|
+
def initialize(chunk_header)
|
16
|
+
super chunk_header
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
"#{chunk_type} (audio description chunk):\n"+
|
21
|
+
" -samplerate: #{samplerate}\n"+
|
22
|
+
" -format ID: #{format_id}\n"+
|
23
|
+
" -format flags: #{format_flags}\n"+
|
24
|
+
" -bytes per packet: #{bytes_per_packet}\n"+
|
25
|
+
" -frames per packet: #{frames_per_packet}\n"+
|
26
|
+
" -channels per frame: #{channels_per_frame}\n"+
|
27
|
+
" -bits per channel: #{bits_per_channel}\n"
|
28
|
+
end
|
29
|
+
|
30
|
+
def validate
|
31
|
+
raise(Caf::Error, "invalid audio description chunk: sample rate cannot be 0") if samplerate == 0
|
32
|
+
raise(Caf::Error, "invalid audio description chunk: format ID cannot be 0") if format_id == 0
|
33
|
+
raise(Caf::Error, "invalid audio description chunk: channels per frame cannot be 0") if channels_per_frame == 0
|
34
|
+
end
|
35
|
+
|
36
|
+
def fields_size
|
37
|
+
32
|
38
|
+
end
|
39
|
+
|
40
|
+
def read_data(file)
|
41
|
+
data = file.read(self.fields_size)
|
42
|
+
raise(Caf::Error, "chunk data too short: #{self}") if file.eof?
|
43
|
+
|
44
|
+
@samplerate = Helper.read_double(data)
|
45
|
+
@format_id = Helper.read_chars(4, data, 8)
|
46
|
+
@format_flags = Helper.read_int(data, 12)
|
47
|
+
@bytes_per_packet = Helper.read_int(data, 16)
|
48
|
+
@frames_per_packet = Helper.read_int(data, 20)
|
49
|
+
@channels_per_frame = Helper.read_int(data, 24)
|
50
|
+
@bits_per_channel = Helper.read_int(data, 28)
|
51
|
+
|
52
|
+
self.validate
|
53
|
+
|
54
|
+
data.size
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'caf/chunk'
|
2
|
+
require 'caf/chunk/helper'
|
3
|
+
require 'caf/errors'
|
4
|
+
|
5
|
+
module Caf::Chunk
|
6
|
+
include Helper
|
7
|
+
|
8
|
+
class Base
|
9
|
+
|
10
|
+
def self.implements(chunk_type)
|
11
|
+
Caf::Chunk.available_chunk_types[chunk_type] = self
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :chunk_type, :chunk_size
|
15
|
+
|
16
|
+
def initialize(chunk_header)
|
17
|
+
@chunk_type = chunk_header[:chunk_type]
|
18
|
+
@chunk_size = chunk_header[:chunk_size]
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
"#{chunk_type} (unknown chunk)"
|
23
|
+
end
|
24
|
+
|
25
|
+
def validate
|
26
|
+
raise(Caf::Error, "chunk must implement validate method: #{chunk_type}") unless self.instance_of?(Base)
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
def fields_size
|
31
|
+
raise(Caf::Error, "chunk must implement fields_size method: #{chunk_type}") unless self.instance_of?(Base)
|
32
|
+
end
|
33
|
+
|
34
|
+
def read_data(file)
|
35
|
+
file.seek(chunk_size, IO::SEEK_CUR) unless chunk_size == -1
|
36
|
+
end
|
37
|
+
|
38
|
+
def check_type_and_size(expected_klass, expected_chunk_type)
|
39
|
+
raise(Caf::Error, "expected an #{expected_chunk_type} chunk") unless self.instance_of?(expected_klass)
|
40
|
+
raise(Caf::Error, "corrupted chunk: wrong size (expected #{fields_size}, got #{chunk_size}") if fields_size != chunk_size
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'caf/chunk/base'
|
2
|
+
require 'caf/chunk/helper'
|
3
|
+
require 'caf/errors'
|
4
|
+
|
5
|
+
module Caf::Chunk
|
6
|
+
include Helper
|
7
|
+
|
8
|
+
class Data < Base
|
9
|
+
|
10
|
+
implements 'data'
|
11
|
+
|
12
|
+
attr_accessor :edit_count, :size, :position
|
13
|
+
|
14
|
+
def initialize(chunk_header)
|
15
|
+
super chunk_header
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
"#{chunk_type} (data chunk):\n"+
|
20
|
+
" -edit count: #{edit_count}\n" +
|
21
|
+
" -data size: #{size}\n" +
|
22
|
+
" -data position: #{position}\n"
|
23
|
+
end
|
24
|
+
|
25
|
+
def validate
|
26
|
+
end
|
27
|
+
|
28
|
+
def fields_size
|
29
|
+
raise(Caf::Error, "not implemented")
|
30
|
+
end
|
31
|
+
|
32
|
+
def read_data(file)
|
33
|
+
edit = file.read(4)
|
34
|
+
@edit_count = (edit.getbyte(0) << 32) + (edit.getbyte(1) << 16) + (edit.getbyte(2) << 8) + edit.getbyte(3)
|
35
|
+
@position = file.pos
|
36
|
+
unless chunk_size == -1
|
37
|
+
@size = chunk_size - 4 # 4 bytes are from edit_count field
|
38
|
+
file.seek(@size, IO::SEEK_CUR)
|
39
|
+
else
|
40
|
+
file.seek(0, IO::SEEK_END)
|
41
|
+
@size = file.pos - @position
|
42
|
+
# TODO should update chunk_size field with @size + 4 if file is writable
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'caf/chunk/base'
|
2
|
+
require 'caf/errors'
|
3
|
+
|
4
|
+
module Caf::Chunk
|
5
|
+
include Helper
|
6
|
+
|
7
|
+
class Free < Base
|
8
|
+
|
9
|
+
implements 'free'
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
"#{chunk_type} (free chunk)"
|
13
|
+
end
|
14
|
+
|
15
|
+
def validate
|
16
|
+
true
|
17
|
+
end
|
18
|
+
|
19
|
+
def fields_size
|
20
|
+
raise(Caf::Error, "not implemented")
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'float-formats'
|
3
|
+
include FltPnt
|
4
|
+
|
5
|
+
module Caf::Chunk
|
6
|
+
module Helper
|
7
|
+
|
8
|
+
BIT_SHIFTS_8 = [64, 56, 48, 40, 32, 24, 16, 8]
|
9
|
+
|
10
|
+
def self.read_bytes(number, data, offset = 0)
|
11
|
+
buffer = 0
|
12
|
+
number_minus = number - 1
|
13
|
+
number_minus.downto(0) do |i|
|
14
|
+
buffer += (data.getbyte(offset + number_minus - i) << (8 * i))
|
15
|
+
end
|
16
|
+
buffer
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.read_chars(number, data, offset = 0)
|
20
|
+
buffer = ''
|
21
|
+
0.upto(number-1) { |i| buffer += data.getbyte(offset+i).chr }
|
22
|
+
buffer
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.read_double(data, offset = 0)
|
26
|
+
num = read_bytes(8, data, offset)
|
27
|
+
IEEE_binary64.from_bits_integer(num).to_number(Float)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.read_int(data, offset = 0)
|
31
|
+
read_bytes(4, data, offset).to_i
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'caf/chunk/base'
|
2
|
+
require 'caf/errors'
|
3
|
+
|
4
|
+
module Caf::Chunk
|
5
|
+
include Helper
|
6
|
+
|
7
|
+
class MagicCookie < Base
|
8
|
+
|
9
|
+
implements 'kuki'
|
10
|
+
|
11
|
+
attr_reader :size, :position #, :data
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
"#{chunk_type} (magic cookie chunk)\n" +
|
15
|
+
" -cookie size: #{size}\n" +
|
16
|
+
" -cookie position: #{position}\n"
|
17
|
+
end
|
18
|
+
|
19
|
+
def validate
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
def fields_size
|
24
|
+
raise(Caf::Error, "not implemented")
|
25
|
+
end
|
26
|
+
|
27
|
+
def read_data(file)
|
28
|
+
@size = chunk_size
|
29
|
+
@position = file.pos
|
30
|
+
#@data = file.read(chunk_size)
|
31
|
+
super(file)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
data/lib/caf/errors.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
class CafInfo
|
2
|
+
module HashKeys #:nodoc:
|
3
|
+
### lets you specify hash["key"] as hash.key
|
4
|
+
### this came from CodingInRuby on RubyGarden
|
5
|
+
### http://www.rubygarden.org/ruby?CodingInRuby
|
6
|
+
def method_missing(meth,*args)
|
7
|
+
m = meth.id2name
|
8
|
+
if /=$/ =~ m
|
9
|
+
self[m.chop] = (args.length<2 ? args[0] : args)
|
10
|
+
else
|
11
|
+
self[m]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class ::String
|
17
|
+
if RUBY_VERSION < "1.9.0"
|
18
|
+
alias getbyte []
|
19
|
+
else
|
20
|
+
def getbyte(i)
|
21
|
+
self[i].ord
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module CafFileMethods #:nodoc:
|
27
|
+
if RUBY_VERSION < "1.9.0"
|
28
|
+
def getbyte
|
29
|
+
getc
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def get32bits
|
34
|
+
(getbyte << 24) + (getbyte << 16) + (getbyte << 8) + getbyte
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_syncsafe
|
38
|
+
(getbyte << 21) + (getbyte << 14) + (getbyte << 7) + getbyte
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/cafinfo.rb
ADDED
@@ -0,0 +1,203 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# coding:utf-8
|
3
|
+
# License:: Mit
|
4
|
+
# Author:: Oleguer Huguet Ibars (mailto:oleguer_DOT_huguet_AT__gmail_DOT_com)
|
5
|
+
# Website:: http://ruby-cafinfo.rubyforge.org/
|
6
|
+
|
7
|
+
require "fileutils"
|
8
|
+
|
9
|
+
$:.unshift "#{File.dirname(__FILE__)}"
|
10
|
+
require 'caf/extension_modules'
|
11
|
+
require 'caf/errors'
|
12
|
+
require 'caf/chunk/types'
|
13
|
+
|
14
|
+
# ruby -d to display debugging infos
|
15
|
+
|
16
|
+
class CafInfo
|
17
|
+
|
18
|
+
VERSION = "0.1"
|
19
|
+
|
20
|
+
# the original filename
|
21
|
+
attr_reader(:filename)
|
22
|
+
|
23
|
+
# duration in seconds of audio
|
24
|
+
attr_reader(:length)
|
25
|
+
|
26
|
+
# the number of sample frames per second of the data
|
27
|
+
attr_reader(:samplerate)
|
28
|
+
|
29
|
+
# a four-character code indicating the general kind of data in the stream
|
30
|
+
attr_reader(:format)
|
31
|
+
|
32
|
+
# flags specific to each format
|
33
|
+
attr_reader(:format_flags)
|
34
|
+
|
35
|
+
# the number of bytes in a packet of data.
|
36
|
+
attr_reader(:bytes_per_packet)
|
37
|
+
|
38
|
+
# the number of sample frames in each packet of data
|
39
|
+
attr_reader(:frames_per_packet)
|
40
|
+
|
41
|
+
# the number of channels in each frame of data
|
42
|
+
attr_reader(:channels_per_frame)
|
43
|
+
|
44
|
+
# the number of bits of sample data for each channel in a frame of data.
|
45
|
+
# 0 if the data format does not contain separate samples for each channel (for instance any compressed format)
|
46
|
+
attr_reader(:bits_per_channel)
|
47
|
+
|
48
|
+
# variable bitrate => true or false
|
49
|
+
attr_reader(:vbr)
|
50
|
+
|
51
|
+
# variable frame rate => true or false
|
52
|
+
attr_reader(:vfr)
|
53
|
+
|
54
|
+
# Instantiate CafInfo object with name +filename+.
|
55
|
+
def initialize(filename)
|
56
|
+
warn("#{self.class}::new() does not take block; use #{self.class}::open() instead") if block_given?
|
57
|
+
@filename = filename
|
58
|
+
@header = {}
|
59
|
+
reload
|
60
|
+
end
|
61
|
+
|
62
|
+
# reload (or load for the first time) the file from disk
|
63
|
+
def reload
|
64
|
+
raise(Caf::Error, "empty file") unless File.size?(@filename)
|
65
|
+
|
66
|
+
@file = File.new(filename, "rb")
|
67
|
+
@file.extend(CafFileMethods)
|
68
|
+
|
69
|
+
@chunks = {}
|
70
|
+
|
71
|
+
begin
|
72
|
+
@header = read_file_header
|
73
|
+
chunk = read_audio_description_chunk
|
74
|
+
@chunks[chunk.chunk_type.to_sym] = chunk
|
75
|
+
puts "Found chunk: #{@chunks[chunk.chunk_type.to_sym]}" if $DEBUG
|
76
|
+
|
77
|
+
until @file.eof? do
|
78
|
+
chunk = read_chunk_header
|
79
|
+
chunk.read_data(@file)
|
80
|
+
@chunks[chunk.chunk_type.to_sym] = chunk
|
81
|
+
|
82
|
+
puts "Found chunk: #{chunk}" if $DEBUG
|
83
|
+
end
|
84
|
+
ensure
|
85
|
+
@file.close
|
86
|
+
end
|
87
|
+
|
88
|
+
raise(Caf::Error, "data chunk not found") if @chunks[:data].nil?
|
89
|
+
@length = (@frames_per_packet * @chunks[:data].size) / (@samplerate * @bytes_per_packet)
|
90
|
+
end
|
91
|
+
|
92
|
+
# "block version" of CafInfo::new()
|
93
|
+
def self.open(*params)
|
94
|
+
m = self.new(*params)
|
95
|
+
ret = nil
|
96
|
+
if block_given?
|
97
|
+
begin
|
98
|
+
ret = yield(m)
|
99
|
+
ensure
|
100
|
+
m.close
|
101
|
+
end
|
102
|
+
else
|
103
|
+
ret = m
|
104
|
+
end
|
105
|
+
ret
|
106
|
+
end
|
107
|
+
|
108
|
+
# write to another filename at close()
|
109
|
+
def rename(new_filename)
|
110
|
+
@filename = new_filename
|
111
|
+
end
|
112
|
+
|
113
|
+
# this method returns the "audio-only" data boundaries of the file,
|
114
|
+
# i.e. data chunk content. Returned value is an array
|
115
|
+
# [position_in_the_file, length_of_the_data]
|
116
|
+
def audio_content
|
117
|
+
[@chunks[:data].position, @chunks[:data].size]
|
118
|
+
end
|
119
|
+
|
120
|
+
# Flush pending modifications and close the file
|
121
|
+
def close
|
122
|
+
puts "close" if $DEBUG
|
123
|
+
end
|
124
|
+
|
125
|
+
# close and reopen the file, i.e. commit changes to disk and
|
126
|
+
# reload it
|
127
|
+
def flush
|
128
|
+
close
|
129
|
+
reload
|
130
|
+
end
|
131
|
+
|
132
|
+
# inspect inside CafInfo
|
133
|
+
def to_s
|
134
|
+
s = "CAF (version #{@header[:file_version]}) #{@format} #{"VFR " if @vfr}#{@vbr ? "VBR" : "CBR"} #{@samplerate} Hz length #{@length} sec."
|
135
|
+
s << ""
|
136
|
+
s
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
### reads through @file to find its header
|
142
|
+
def read_file_header
|
143
|
+
header = {}
|
144
|
+
head = @file.read(8)
|
145
|
+
raise(Caf::Error, "file header too short") if @file.eof?
|
146
|
+
raise(Caf::Error, "not a CAF file") unless head.match(/^caff/)
|
147
|
+
|
148
|
+
header[:file_version] = (head.getbyte(4) << 8) + head.getbyte(5)
|
149
|
+
raise(Caf::Error, "invalid CAF file version (supported only version 1)") if header[:file_version] != 1
|
150
|
+
|
151
|
+
header[:file_flags] = (head.getbyte(6) << 8) + head.getbyte(7)
|
152
|
+
raise(Caf::Error, "invalid CAF file flags") if header[:file_flags] != 0
|
153
|
+
header
|
154
|
+
end
|
155
|
+
|
156
|
+
def read_chunk_header
|
157
|
+
header = {}
|
158
|
+
head = @file.read(12)
|
159
|
+
|
160
|
+
raise(Caf::Error, "chunk header too short: #{head.size}") if head && head.size < 12
|
161
|
+
|
162
|
+
header[:chunk_type] = head.getbyte(0).chr + head.getbyte(1).chr + head.getbyte(2).chr + head.getbyte(3).chr
|
163
|
+
header[:chunk_size] = (head.getbyte(4) << 512) + (head.getbyte(5) << 256) + (head.getbyte(6) << 128) + (head.getbyte(7) << 64)+
|
164
|
+
(head.getbyte(8) << 32) + (head.getbyte(9) << 16) + (head.getbyte(10) << 8) + head.getbyte(11)
|
165
|
+
Caf::Chunk.build(header)
|
166
|
+
end
|
167
|
+
|
168
|
+
def read_audio_description_chunk
|
169
|
+
chunk = read_chunk_header
|
170
|
+
chunk.check_type_and_size(Caf::Chunk::AudioDescription, 'audio description')
|
171
|
+
|
172
|
+
chunk.read_data(@file)
|
173
|
+
|
174
|
+
@vbr = chunk.bytes_per_packet == 0
|
175
|
+
@vfr = chunk.frames_per_packet == 0
|
176
|
+
@samplerate = chunk.samplerate.to_i
|
177
|
+
@format = chunk.format_id
|
178
|
+
@format_flags = chunk.format_flags
|
179
|
+
@bytes_per_packet = chunk.bytes_per_packet
|
180
|
+
@frames_per_packet = chunk.frames_per_packet
|
181
|
+
@channels_per_frame = chunk.channels_per_frame
|
182
|
+
@bits_per_channel = chunk.bits_per_channel
|
183
|
+
|
184
|
+
chunk
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
|
189
|
+
if $0 == __FILE__
|
190
|
+
error = 0
|
191
|
+
while filename = ARGV.shift
|
192
|
+
begin
|
193
|
+
info = CafInfo.new(filename)
|
194
|
+
puts filename
|
195
|
+
puts info
|
196
|
+
rescue Caf::Error => e
|
197
|
+
puts "#{filename}\nERROR: #{e}"
|
198
|
+
error = 1
|
199
|
+
end
|
200
|
+
puts
|
201
|
+
end
|
202
|
+
exit(error)
|
203
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby-cafinfo
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.1"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Oleguer Huguet Ibars
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-03-19 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: float-formats
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.1.1
|
24
|
+
version:
|
25
|
+
description: ruby-cafinfo is a ruby library to retrieve low level informations on CAF files
|
26
|
+
email: olegueret@rubyforge.org
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README
|
33
|
+
- LICENSE
|
34
|
+
- CHANGELOG
|
35
|
+
files:
|
36
|
+
- LICENSE
|
37
|
+
- README
|
38
|
+
- Rakefile
|
39
|
+
- lib/cafinfo.rb
|
40
|
+
- lib/caf
|
41
|
+
- lib/caf/chunk
|
42
|
+
- lib/caf/chunk/audio_description.rb
|
43
|
+
- lib/caf/chunk/magic_cookie.rb
|
44
|
+
- lib/caf/chunk/base.rb
|
45
|
+
- lib/caf/chunk/free.rb
|
46
|
+
- lib/caf/chunk/types.rb
|
47
|
+
- lib/caf/chunk/data.rb
|
48
|
+
- lib/caf/chunk/helper.rb
|
49
|
+
- lib/caf/extension_modules.rb
|
50
|
+
- lib/caf/errors.rb
|
51
|
+
- lib/caf/chunk.rb
|
52
|
+
- CHANGELOG
|
53
|
+
has_rdoc: true
|
54
|
+
homepage: http://ruby-cafinfo.rubyforge.org
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options: []
|
57
|
+
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: "0"
|
65
|
+
version:
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: "0"
|
71
|
+
version:
|
72
|
+
requirements: []
|
73
|
+
|
74
|
+
rubyforge_project: ruby-cafinfo
|
75
|
+
rubygems_version: 1.3.1
|
76
|
+
signing_key:
|
77
|
+
specification_version: 2
|
78
|
+
summary: ruby-cafinfo is a ruby library to retrieve low level informations on CAF files
|
79
|
+
test_files: []
|
80
|
+
|