nu_wav 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
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