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