lib/nu_wav.rb in nu_wav-0.3.0 vs lib/nu_wav.rb in nu_wav-0.3.1
- old
+ new
@@ -1,21 +1,25 @@
# http://www.prss.org/contentdepot/automation_specifications.cfm
# Bill Kelly <billk <at> cts.com> http://article.gmane.org/gmane.comp.lang.ruby.general/43110
-
# BWF intro http://en.wikipedia.org/wiki/Broadcast_Wave_Format
# BWF basics http://tech.ebu.ch/docs/tech/tech3285.pdf
# BWF mpeg http://tech.ebu.ch/docs/tech/tech3285s1.pdf
require 'rubygems'
require 'mp3info'
require 'date'
+require 'tempfile'
module NuWav
DEBUG = false
- PCM_COMPRESSION = 1
+ # 1 is standard integer based, 3 is the floating point PCM
+ PCM_INTEGER_COMPRESSION = 1
+ PCM_FLOATING_COMPRESSION = 3
+ PCM_COMPRESSION = [PCM_INTEGER_COMPRESSION, PCM_FLOATING_COMPRESSION]
+
MPEG_COMPRESSION = 80
ACM_MPEG_LAYER1 = 1
ACM_MPEG_LAYER2 = 2
ACM_MPEG_LAYER3 = 4
@@ -49,53 +53,75 @@
end
def parse(wave_file)
NuWav::WaveFile.log "Processing wave file #{wave_file.inspect}...."
File.open(wave_file, File::RDWR) do |f|
+
#only for windows, make sure we are operating in binary mode
f.binmode
#start at the very beginning, a very good place to start
f.seek(0)
riff, riff_length = read_chunk_header(f)
+ NuWav::WaveFile.log "riff: #{riff}"
NuWav::WaveFile.log "riff_length: #{riff_length}"
raise NotRIFFFormat unless riff == 'RIFF'
riff_end = f.tell + riff_length
riff_type = f.read(4)
raise NotWAVEFormat unless riff_type == 'WAVE'
@header = RiffChunk.new(riff, riff_length, riff_type)
- while f.tell < riff_end
+ while (f.tell + 8) <= riff_end
+ NuWav::WaveFile.log "while #{f.tell} < #{riff_end}"
chunk_name, chunk_length = read_chunk_header(f)
fpos = f.tell
NuWav::WaveFile.log "found chunk: '#{chunk_name}', size #{chunk_length}"
- self.chunks[chunk_name.to_sym] = chunk_class(chunk_name).parse(chunk_name, chunk_length, f)
+
+ if chunk_name && chunk_length
- f.seek(fpos + self.chunks[chunk_name.to_sym].size)
+ self.chunks[chunk_name.to_sym] = chunk_class(chunk_name).parse(chunk_name, chunk_length, f)
+
+ NuWav::WaveFile.log "about to do a seek..."
+ NuWav::WaveFile.log "f.seek #{fpos} + #{self.chunks[chunk_name.to_sym].size}"
+ f.seek(fpos + self.chunks[chunk_name.to_sym].size)
+ NuWav::WaveFile.log "seek done"
+ else
+ NuWav::WaveFile.log "chunk or length was off - remainder of file does not parse properly: #{riff_end} - #{fpos} = #{riff_end - fpos}"
+ f.seek(riff_end)
+ end
end
end
- @chunks.each{|k,v| NuWav::WaveFile.log "#{k}: #{v}\n\n" unless k == :data}
+ @chunks.each{|k,v| NuWav::WaveFile.log "#{k}: #{v}\n\n" unless k.to_s == 'data'}
+ NuWav::WaveFile.log "parse done"
end
def duration
fmt = @chunks[:fmt]
- if (fmt.compression_code.to_i == PCM_COMPRESSION)
+ if (PCM_COMPRESSION.include?(fmt.compression_code.to_i))
data = @chunks[:data]
data.size / (fmt.sample_rate * fmt.number_of_channels * (fmt.sample_bits / 8))
elsif (fmt.compression_code.to_i == MPEG_COMPRESSION)
# <chunk type:fact samples_number:78695424 />
fact = @chunks[:fact]
fact.samples_number / fmt.sample_rate
else
raise "Duration implemented for PCM and MEPG files only."
end
end
+
+ def is_mpeg?
+ (@chunks[:fmt] && (@chunks[:fmt].compression_code.to_i == MPEG_COMPRESSION))
+ end
+ def is_pcm?
+ (@chunks[:fmt] && (PCM_COMPRESSION.include?(@chunks[:fmt].compression_code.to_i)))
+ end
+
def to_s
out = "NuWav:#{@header}\n"
out = [:fmt, :fact, :mext, :bext, :cart, :data ].inject(out) do |s, chunk|
s += "#{self.chunks[chunk]}\n" if self.chunks[chunk]
s
@@ -107,17 +133,21 @@
file_name += ".wav"
end
NuWav::WaveFile.log "NuWav::WaveFile.to_file: file_name = #{file_name}"
#get all the chunks together to get final length
- chunks_out = [:fmt, :fact, :mext, :bext, :cart, :data].inject([]) do |list, chunk|
- out = self.chunks[chunk].to_binary
- NuWav::WaveFile.log out.length
- list << out
+ chunks_out = [:fmt, :fact, :mext, :bext, :cart, :data].inject([]) do |list, chunk|
+ if self.chunks[chunk]
+ out = self.chunks[chunk].to_binary
+ NuWav::WaveFile.log out.length
+ list << out
+ end
list
end
+ # TODO: handle other chunks not in the above list, but that might have been in a parsed wav
+
riff_length = chunks_out.inject(0){|sum, chunk| sum += chunk.size}
NuWav::WaveFile.log "NuWav::WaveFile.to_file: riff_length = #{riff_length}"
#open file for writing
open(file_name, "wb") do |o|
@@ -180,16 +210,12 @@
wave.chunks[:fact] = fact
# NuWav::WaveFile.log "fact: #{fact}"
#mext chunk
mext = MextChunk.new
- # from what I can tell, a 7 indicates a homogenous 44100 or 22050 mpeg audio with no padding
- mext.sound_information = 7
-
- # from what I can tell, a 7 indicates a homogenous 44100 or 22050 mpeg audio with no padding
- # if there is padding, it is more complicated, see sectin 2.2 here: http://tech.ebu.ch/docs/tech/tech3285s1.pdf
- mext.sound_information = 5 if mp3info.header[:padding]
+ mext.sound_information = 5
+ mext.sound_information += 2 if mp3info.header[:padding]
mext.frame_size = calculate_mpeg_frame_size(mp3info)
mext.ancillary_data_length = 0
mext.ancillary_data_def = 0
wave.chunks[:mext] = mext
# NuWav::WaveFile.log "mext: #{mext}"
@@ -249,11 +275,16 @@
hdr = file.read(8)
chunkName, chunkLen = hdr.unpack("A4V")
end
def chunk_class(name)
- constantize("NuWav::#{camelize("#{name}_chunk")}")
+ begin
+ constantize("NuWav::#{camelize("#{name}_chunk")}")
+ rescue NameError
+ NuWav::Chunk
+ end
+
end
# File vendor/rails/activesupport/lib/active_support/inflector.rb, line 147
def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
if first_letter_in_uppercase
@@ -271,11 +302,11 @@
Object.module_eval("::#{$1}", __FILE__, __LINE__)
end
def self.log(m)
if NuWav::DEBUG
- puts "#{Time.now}: NuWav: #{m}"
+ NuWav::WaveFile.log "#{Time.now}: NuWav: #{m}"
end
end
end
@@ -307,14 +338,16 @@
def read_char(start, length=(@raw.length-start))
@raw[start..(start+length-1)]
end
def write_dword(val)
+ val ||= 0
[val].pack('V')
end
def write_word(val)
+ val ||= 0
[val].pack('v')
end
def write_char(val, length=nil)
val ||= ''
@@ -545,19 +578,56 @@
end
end
class DataChunk < Chunk
- alias_method :data, :raw
- alias_method :data=, :raw=
+ attr_accessor :tmp_data_file
+
+ def self.parse(id, size, file)
+ # tmp_data = File.open('./data_chunk.mp2', 'wb')
+ tmp_data = Tempfile.open('data_chunk')
+ tmp_data.binmode
+
+ remaining = size
+ while (remaining > 0 && !file.eof?)
+ read_bytes = [128, remaining].min
+ tmp_data << file.read(read_bytes)
+ remaining -= read_bytes
+ end
+ tmp_data.rewind
+ chunk = self.new(id, size, tmp_data)
+ return chunk
+ end
+
+ def initialize(id=nil, size=nil, tmp_data_file=nil)
+ @id, @size, @tmp_data_file = id, size, tmp_data_file
+ end
+
+ def data
+ f = ''
+ if self.tmp_data_file
+ NuWav::WaveFile.log "we have a tmp_data_file!"
+ self.tmp_data_file.rewind
+ f = self.tmp_data_file.read
+ self.tmp_data_file.rewind
+ else
+ NuWav::WaveFile.log "we have NO tmp_data_file!"
+ end
+ f
+ end
+
def to_s
"<chunk type:data (size:#{data.size})/>"
end
def to_binary
- out = "data" + write_dword(data.size) + data
+ NuWav::WaveFile.log "data chunk to_binary"
+ d = self.data
+ NuWav::WaveFile.log "got data size = #{d.size} #{d[0,10]}"
+ out = "data" + write_dword(d.size) + d
+ out
end
end
end
\ No newline at end of file