ruby-cafinfo 0.1
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/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
|
+
|