nu_wav 0.3.0 → 0.3.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/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.0
1
+ 0.3.1
data/lib/nu_wav.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  # http://www.prss.org/contentdepot/automation_specifications.cfm
2
2
  # Bill Kelly <billk <at> cts.com> http://article.gmane.org/gmane.comp.lang.ruby.general/43110
3
-
4
3
  # BWF intro http://en.wikipedia.org/wiki/Broadcast_Wave_Format
5
4
  # BWF basics http://tech.ebu.ch/docs/tech/tech3285.pdf
6
5
  # BWF mpeg http://tech.ebu.ch/docs/tech/tech3285s1.pdf
@@ -8,12 +7,17 @@
8
7
  require 'rubygems'
9
8
  require 'mp3info'
10
9
  require 'date'
10
+ require 'tempfile'
11
11
 
12
12
  module NuWav
13
13
 
14
14
  DEBUG = false
15
15
 
16
- PCM_COMPRESSION = 1
16
+ # 1 is standard integer based, 3 is the floating point PCM
17
+ PCM_INTEGER_COMPRESSION = 1
18
+ PCM_FLOATING_COMPRESSION = 3
19
+ PCM_COMPRESSION = [PCM_INTEGER_COMPRESSION, PCM_FLOATING_COMPRESSION]
20
+
17
21
  MPEG_COMPRESSION = 80
18
22
 
19
23
  ACM_MPEG_LAYER1 = 1
@@ -51,12 +55,14 @@ module NuWav
51
55
  def parse(wave_file)
52
56
  NuWav::WaveFile.log "Processing wave file #{wave_file.inspect}...."
53
57
  File.open(wave_file, File::RDWR) do |f|
58
+
54
59
  #only for windows, make sure we are operating in binary mode
55
60
  f.binmode
56
61
  #start at the very beginning, a very good place to start
57
62
  f.seek(0)
58
63
 
59
64
  riff, riff_length = read_chunk_header(f)
65
+ NuWav::WaveFile.log "riff: #{riff}"
60
66
  NuWav::WaveFile.log "riff_length: #{riff_length}"
61
67
  raise NotRIFFFormat unless riff == 'RIFF'
62
68
  riff_end = f.tell + riff_length
@@ -66,23 +72,35 @@ module NuWav
66
72
 
67
73
  @header = RiffChunk.new(riff, riff_length, riff_type)
68
74
 
69
- while f.tell < riff_end
75
+ while (f.tell + 8) <= riff_end
76
+ NuWav::WaveFile.log "while #{f.tell} < #{riff_end}"
70
77
  chunk_name, chunk_length = read_chunk_header(f)
71
78
  fpos = f.tell
72
79
 
73
80
  NuWav::WaveFile.log "found chunk: '#{chunk_name}', size #{chunk_length}"
74
- self.chunks[chunk_name.to_sym] = chunk_class(chunk_name).parse(chunk_name, chunk_length, f)
75
-
76
- f.seek(fpos + self.chunks[chunk_name.to_sym].size)
81
+
82
+ if chunk_name && chunk_length
83
+
84
+ self.chunks[chunk_name.to_sym] = chunk_class(chunk_name).parse(chunk_name, chunk_length, f)
85
+
86
+ NuWav::WaveFile.log "about to do a seek..."
87
+ NuWav::WaveFile.log "f.seek #{fpos} + #{self.chunks[chunk_name.to_sym].size}"
88
+ f.seek(fpos + self.chunks[chunk_name.to_sym].size)
89
+ NuWav::WaveFile.log "seek done"
90
+ else
91
+ NuWav::WaveFile.log "chunk or length was off - remainder of file does not parse properly: #{riff_end} - #{fpos} = #{riff_end - fpos}"
92
+ f.seek(riff_end)
93
+ end
77
94
  end
78
95
  end
79
- @chunks.each{|k,v| NuWav::WaveFile.log "#{k}: #{v}\n\n" unless k == :data}
96
+ @chunks.each{|k,v| NuWav::WaveFile.log "#{k}: #{v}\n\n" unless k.to_s == 'data'}
97
+ NuWav::WaveFile.log "parse done"
80
98
  end
81
99
 
82
100
  def duration
83
101
  fmt = @chunks[:fmt]
84
102
 
85
- if (fmt.compression_code.to_i == PCM_COMPRESSION)
103
+ if (PCM_COMPRESSION.include?(fmt.compression_code.to_i))
86
104
  data = @chunks[:data]
87
105
  data.size / (fmt.sample_rate * fmt.number_of_channels * (fmt.sample_bits / 8))
88
106
  elsif (fmt.compression_code.to_i == MPEG_COMPRESSION)
@@ -93,6 +111,14 @@ module NuWav
93
111
  raise "Duration implemented for PCM and MEPG files only."
94
112
  end
95
113
  end
114
+
115
+ def is_mpeg?
116
+ (@chunks[:fmt] && (@chunks[:fmt].compression_code.to_i == MPEG_COMPRESSION))
117
+ end
118
+
119
+ def is_pcm?
120
+ (@chunks[:fmt] && (PCM_COMPRESSION.include?(@chunks[:fmt].compression_code.to_i)))
121
+ end
96
122
 
97
123
  def to_s
98
124
  out = "NuWav:#{@header}\n"
@@ -109,13 +135,17 @@ module NuWav
109
135
  NuWav::WaveFile.log "NuWav::WaveFile.to_file: file_name = #{file_name}"
110
136
 
111
137
  #get all the chunks together to get final length
112
- chunks_out = [:fmt, :fact, :mext, :bext, :cart, :data].inject([]) do |list, chunk|
113
- out = self.chunks[chunk].to_binary
114
- NuWav::WaveFile.log out.length
115
- list << out
138
+ chunks_out = [:fmt, :fact, :mext, :bext, :cart, :data].inject([]) do |list, chunk|
139
+ if self.chunks[chunk]
140
+ out = self.chunks[chunk].to_binary
141
+ NuWav::WaveFile.log out.length
142
+ list << out
143
+ end
116
144
  list
117
145
  end
118
146
 
147
+ # TODO: handle other chunks not in the above list, but that might have been in a parsed wav
148
+
119
149
  riff_length = chunks_out.inject(0){|sum, chunk| sum += chunk.size}
120
150
  NuWav::WaveFile.log "NuWav::WaveFile.to_file: riff_length = #{riff_length}"
121
151
 
@@ -182,12 +212,8 @@ module NuWav
182
212
 
183
213
  #mext chunk
184
214
  mext = MextChunk.new
185
- # from what I can tell, a 7 indicates a homogenous 44100 or 22050 mpeg audio with no padding
186
- mext.sound_information = 7
187
-
188
- # from what I can tell, a 7 indicates a homogenous 44100 or 22050 mpeg audio with no padding
189
- # if there is padding, it is more complicated, see sectin 2.2 here: http://tech.ebu.ch/docs/tech/tech3285s1.pdf
190
- mext.sound_information = 5 if mp3info.header[:padding]
215
+ mext.sound_information = 5
216
+ mext.sound_information += 2 if mp3info.header[:padding]
191
217
  mext.frame_size = calculate_mpeg_frame_size(mp3info)
192
218
  mext.ancillary_data_length = 0
193
219
  mext.ancillary_data_def = 0
@@ -251,7 +277,12 @@ module NuWav
251
277
  end
252
278
 
253
279
  def chunk_class(name)
254
- constantize("NuWav::#{camelize("#{name}_chunk")}")
280
+ begin
281
+ constantize("NuWav::#{camelize("#{name}_chunk")}")
282
+ rescue NameError
283
+ NuWav::Chunk
284
+ end
285
+
255
286
  end
256
287
 
257
288
  # File vendor/rails/activesupport/lib/active_support/inflector.rb, line 147
@@ -273,7 +304,7 @@ module NuWav
273
304
 
274
305
  def self.log(m)
275
306
  if NuWav::DEBUG
276
- puts "#{Time.now}: NuWav: #{m}"
307
+ NuWav::WaveFile.log "#{Time.now}: NuWav: #{m}"
277
308
  end
278
309
  end
279
310
 
@@ -309,10 +340,12 @@ module NuWav
309
340
  end
310
341
 
311
342
  def write_dword(val)
343
+ val ||= 0
312
344
  [val].pack('V')
313
345
  end
314
346
 
315
347
  def write_word(val)
348
+ val ||= 0
316
349
  [val].pack('v')
317
350
  end
318
351
 
@@ -547,16 +580,53 @@ module NuWav
547
580
  end
548
581
 
549
582
  class DataChunk < Chunk
550
- alias_method :data, :raw
551
- alias_method :data=, :raw=
583
+ attr_accessor :tmp_data_file
584
+
585
+ def self.parse(id, size, file)
552
586
 
587
+ # tmp_data = File.open('./data_chunk.mp2', 'wb')
588
+ tmp_data = Tempfile.open('data_chunk')
589
+ tmp_data.binmode
590
+
591
+ remaining = size
592
+ while (remaining > 0 && !file.eof?)
593
+ read_bytes = [128, remaining].min
594
+ tmp_data << file.read(read_bytes)
595
+ remaining -= read_bytes
596
+ end
597
+ tmp_data.rewind
598
+ chunk = self.new(id, size, tmp_data)
599
+
600
+ return chunk
601
+ end
602
+
603
+ def initialize(id=nil, size=nil, tmp_data_file=nil)
604
+ @id, @size, @tmp_data_file = id, size, tmp_data_file
605
+ end
606
+
607
+ def data
608
+ f = ''
609
+ if self.tmp_data_file
610
+ NuWav::WaveFile.log "we have a tmp_data_file!"
611
+ self.tmp_data_file.rewind
612
+ f = self.tmp_data_file.read
613
+ self.tmp_data_file.rewind
614
+ else
615
+ NuWav::WaveFile.log "we have NO tmp_data_file!"
616
+ end
617
+ f
618
+ end
553
619
 
554
620
  def to_s
555
621
  "<chunk type:data (size:#{data.size})/>"
556
622
  end
557
623
 
558
624
  def to_binary
559
- out = "data" + write_dword(data.size) + data
625
+ NuWav::WaveFile.log "data chunk to_binary"
626
+ d = self.data
627
+ NuWav::WaveFile.log "got data size = #{d.size} #{d[0,10]}"
628
+ out = "data" + write_dword(d.size) + d
629
+ out
560
630
  end
561
631
 
562
632
  end
data/nu_wav.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{nu_wav}
8
- s.version = "0.3.0"
8
+ s.version = "0.3.1"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["kookster"]
12
- s.date = %q{2011-03-02}
12
+ s.date = %q{2011-05-28}
13
13
  s.description = %q{NuWav is a pure ruby audio WAV file parser and writer. It supports Broadcast Wave Format (BWF), inclluding MPEG audio data, and the public radio standard cart chunk.}
14
14
  s.email = %q{andrew@beginsinwonder.com}
15
15
  s.extra_rdoc_files = [
data/test/test_nu_wav.rb CHANGED
@@ -1,11 +1,15 @@
1
1
  require 'helper'
2
2
  require 'nu_wav'
3
+ require 'tempfile'
3
4
 
4
5
  class TestNuWav < Test::Unit::TestCase
5
6
  include NuWav
6
7
 
7
8
  def test_parse_wav
9
+ memory_usage = `ps -o rss= -p #{Process.pid}`.to_i # in kilobytes
10
+ puts "begin test: #{memory_usage/1024} mb"
8
11
  w = WaveFile.parse(File.expand_path(File.dirname(__FILE__) + '/files/test_basic.wav'))
12
+ puts w
9
13
  assert_equal 4260240, w.header.size
10
14
 
11
15
  assert_equal 2, w.chunks.size
@@ -20,10 +24,20 @@ class TestNuWav < Test::Unit::TestCase
20
24
 
21
25
  data = w.chunks[:data]
22
26
  assert_equal 4260204, data.size
27
+
28
+ w.to_file('test_out.wav')
29
+ memory_usage = `ps -o rss= -p #{Process.pid}`.to_i # in kilobytes
30
+ puts "end of test: #{memory_usage/1024} mb"
23
31
  end
24
32
 
25
33
  def test_parse_wav_with_bwf_and_cart_chunk
34
+ memory_usage = `ps -o rss= -p #{Process.pid}`.to_i # in kilobytes
35
+ puts "begin test: #{memory_usage/1024} mb"
36
+
26
37
  w = WaveFile.parse(File.expand_path(File.dirname(__FILE__) + '/files/AfropopW_040_SGMT01.wav'))
38
+ memory_usage = `ps -o rss= -p #{Process.pid}`.to_i # in kilobytes
39
+ puts "after parse: #{memory_usage/1024} mb"
40
+
27
41
  assert_equal 6, w.chunks.size
28
42
 
29
43
  # duration is calculated differently for mpeg and pcm
@@ -67,6 +81,8 @@ class TestNuWav < Test::Unit::TestCase
67
81
 
68
82
  # data
69
83
  assert_equal 57040521, w.chunks[:data].size
84
+ memory_usage = `ps -o rss= -p #{Process.pid}`.to_i # in kilobytes
85
+ puts "end of test: #{memory_usage/1024} mb"
70
86
  end
71
87
 
72
88
  def unpad(str)
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nu_wav
3
3
  version: !ruby/object:Gem::Version
4
- hash: 19
4
+ hash: 17
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 3
9
- - 0
10
- version: 0.3.0
9
+ - 1
10
+ version: 0.3.1
11
11
  platform: ruby
12
12
  authors:
13
13
  - kookster
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-03-02 00:00:00 -05:00
18
+ date: 2011-05-28 00:00:00 -04:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency