nu_wav 0.3.4 → 0.4.2

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.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZWUxNmI2ZmJhYThlYTMwNGQ3MzczOTY1NzdhNTNmMTQzMzc5MDhjZA==
5
+ data.tar.gz: !binary |-
6
+ MzQxZWE3MThhZjNiODBiMDk4N2FhZjVlZGRhMmEzMTU3NjIyMjg4NQ==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ MmNkYTA1YTdjOTgwZmMzODA3ZWQyOTYxZWJhNzAzZTNjOWMzMzRiYmYyN2Mw
10
+ Zjg1MzdiODUzZDE5MmQyZmY1YWM4NjRiZTdiNTY1ZDRlMDZiMDA1MmE4OTAw
11
+ NDZhODE0OTAyYWVmYjJlMzJkYTkxZjI5MjljYzQ0NDkzNzJiZTk=
12
+ data.tar.gz: !binary |-
13
+ MzU4YTYxN2YxZDY3NGE2ODdkOTExOTU3YjRlYTQ4NTYwMDNkZmRhNmY4N2Q1
14
+ ZDdiY2NkZGExMzlkY2YxYzA2OTU1NzUxZmIxYzAyNWFhMmNiMmQ1ZGY0YjJk
15
+ YzBiNzAyYWViNzRiM2VmY2Q5ZTU4MDEwOGViZWIyMTdjYTk2NjU=
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ .DS_Store
2
+ .DS_Store
3
+ *.gem
4
+ *.rbc
5
+ .bundle
6
+ .config
7
+ .yardoc
8
+ Gemfile.lock
9
+ InstalledFiles
10
+ _yardoc
11
+ coverage
12
+ doc/
13
+ lib/bundler/man
14
+ pkg
15
+ rdoc
16
+ spec/reports
17
+ test/tmp
18
+ test/version_tmp
19
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in nu_wav.gemspec
4
+ gemspec
data/LICENSE CHANGED
@@ -1,5 +1,7 @@
1
1
  Copyright (c) 2010 Andrew Kuklewicz (kookster)
2
2
 
3
+ MIT License
4
+
3
5
  Permission is hereby granted, free of charge, to any person obtaining
4
6
  a copy of this software and associated documentation files (the
5
7
  "Software"), to deal in the Software without restriction, including
data/Rakefile CHANGED
@@ -1,55 +1,11 @@
1
- require 'rubygems'
1
+ require "bundler/gem_tasks"
2
2
  require 'rake'
3
-
4
- begin
5
- require 'jeweler'
6
- Jeweler::Tasks.new do |gem|
7
- gem.name = "nu_wav"
8
- gem.summary = %Q{NuWav is a pure ruby audio WAV file parser and writer.}
9
- gem.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.}
10
- gem.email = "andrew@beginsinwonder.com"
11
- gem.homepage = "http://github.com/kookster/nu_wav"
12
- gem.authors = ["kookster"]
13
- gem.add_dependency('ruby-mp3info', '>= 0.6.13')
14
- gem.files.exclude ".document"
15
- gem.files.exclude ".gitignore"
16
- gem.files.exclude "test/files/**/*"
17
- end
18
- Jeweler::GemcutterTasks.new
19
- rescue LoadError
20
- puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
21
- end
22
-
23
3
  require 'rake/testtask'
4
+
24
5
  Rake::TestTask.new(:test) do |test|
25
6
  test.libs << 'lib' << 'test'
26
7
  test.pattern = 'test/**/test_*.rb'
27
8
  test.verbose = true
28
9
  end
29
10
 
30
- begin
31
- require 'rcov/rcovtask'
32
- Rcov::RcovTask.new do |test|
33
- test.libs << 'test'
34
- test.pattern = 'test/**/test_*.rb'
35
- test.verbose = true
36
- end
37
- rescue LoadError
38
- task :rcov do
39
- abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
40
- end
41
- end
42
-
43
- task :test => :check_dependencies
44
-
45
11
  task :default => :test
46
-
47
- require 'rake/rdoctask'
48
- Rake::RDocTask.new do |rdoc|
49
- version = File.exist?('VERSION') ? File.read('VERSION') : ""
50
-
51
- rdoc.rdoc_dir = 'rdoc'
52
- rdoc.title = "nu_wav #{version}"
53
- rdoc.rdoc_files.include('README*')
54
- rdoc.rdoc_files.include('lib/**/*.rb')
55
- end
data/lib/nu_wav.rb CHANGED
@@ -10,9 +10,13 @@ require 'date'
10
10
  require 'tempfile'
11
11
  require 'fileutils'
12
12
 
13
+ require "nu_wav/version"
14
+ require "nu_wav/chunk"
15
+ require "nu_wav/wave_file"
16
+
13
17
  module NuWav
14
18
 
15
- DEBUG = false
19
+ DEBUG = ENV['NU_WAV_DEBUG']
16
20
 
17
21
  # 1 is standard integer based, 3 is the floating point PCM
18
22
  PCM_INTEGER_COMPRESSION = 1
@@ -38,612 +42,5 @@ module NuWav
38
42
 
39
43
  class NotRIFFFormat < StandardError; end
40
44
  class NotWAVEFormat < StandardError; end
41
-
42
- class WaveFile
43
-
44
- attr_accessor :header, :chunks
45
-
46
- def self.parse(wave_file)
47
- NuWav::WaveFile.new.parse(wave_file)
48
- end
49
-
50
- def initialize
51
- self.chunks = {}
52
- end
53
-
54
- def parse(wave_file)
55
- NuWav::WaveFile.log "Processing wave file #{wave_file.inspect}...."
56
- wave_file_size = File.size(wave_file)
57
-
58
- File.open(wave_file, File::RDWR) do |f|
59
-
60
- #only for windows, make sure we are operating in binary mode
61
- f.binmode
62
- #start at the very beginning, a very good place to start
63
- f.seek(0)
64
-
65
- riff, riff_length = read_chunk_header(f)
66
- NuWav::WaveFile.log "riff: #{riff}"
67
- NuWav::WaveFile.log "riff_length: #{riff_length}"
68
- NuWav::WaveFile.log "wave_file_size: #{wave_file_size}"
69
-
70
- raise NotRIFFFormat unless riff == 'RIFF'
71
- riff_end = [f.tell + riff_length, wave_file_size].min
72
-
73
- riff_type = f.read(4)
74
- raise NotWAVEFormat unless riff_type == 'WAVE'
75
-
76
- @header = RiffChunk.new(riff, riff_length, riff_type)
77
-
78
- while (f.tell + 8) <= riff_end
79
- NuWav::WaveFile.log "while #{f.tell} < #{riff_end}"
80
- chunk_name, chunk_length = read_chunk_header(f)
81
- fpos = f.tell
82
-
83
- NuWav::WaveFile.log "found chunk: '#{chunk_name}', size #{chunk_length}"
84
-
85
- if chunk_name && chunk_length
86
-
87
- self.chunks[chunk_name.to_sym] = chunk_class(chunk_name).parse(chunk_name, chunk_length, f)
88
-
89
- NuWav::WaveFile.log "about to do a seek..."
90
- NuWav::WaveFile.log "f.seek #{fpos} + #{self.chunks[chunk_name.to_sym].size}"
91
- f.seek(fpos + self.chunks[chunk_name.to_sym].size)
92
- NuWav::WaveFile.log "seek done"
93
- else
94
- NuWav::WaveFile.log "chunk or length was off - remainder of file does not parse properly: #{riff_end} - #{fpos} = #{riff_end - fpos}"
95
- f.seek(riff_end)
96
- end
97
- end
98
- end
99
- @chunks.each{|k,v| NuWav::WaveFile.log "#{k}: #{v}\n\n" unless k.to_s == 'data'}
100
- NuWav::WaveFile.log "parse done"
101
- self
102
- end
103
-
104
- def duration
105
- fmt = @chunks[:fmt]
106
-
107
- if (PCM_COMPRESSION.include?(fmt.compression_code.to_i))
108
- data = @chunks[:data]
109
- data.size / (fmt.sample_rate * fmt.number_of_channels * (fmt.sample_bits / 8))
110
- elsif (fmt.compression_code.to_i == MPEG_COMPRESSION)
111
- # <chunk type:fact samples_number:78695424 />
112
- fact = @chunks[:fact]
113
- fact.samples_number / fmt.sample_rate
114
- else
115
- raise "Duration implemented for PCM and MEPG files only."
116
- end
117
- end
118
-
119
- def is_mpeg?
120
- (@chunks[:fmt] && (@chunks[:fmt].compression_code.to_i == MPEG_COMPRESSION))
121
- end
122
-
123
- def is_pcm?
124
- (@chunks[:fmt] && (PCM_COMPRESSION.include?(@chunks[:fmt].compression_code.to_i)))
125
- end
126
-
127
- def to_s
128
- out = "NuWav:#{@header}\n"
129
- out = [:fmt, :fact, :mext, :bext, :cart, :data ].inject(out) do |s, chunk|
130
- s += "#{self.chunks[chunk]}\n" if self.chunks[chunk]
131
- s
132
- end
133
- end
134
-
135
- def to_file(file_name, add_extension=false)
136
- if add_extension && !(file_name =~ /\.wav/)
137
- file_name += ".wav"
138
- end
139
- NuWav::WaveFile.log "NuWav::WaveFile.to_file: file_name = #{file_name}"
140
-
141
- #get all the chunks together to get final length
142
- chunks_out = [:fmt, :fact, :mext, :bext, :cart, :data].inject([]) do |list, chunk|
143
- if self.chunks[chunk]
144
- out = self.chunks[chunk].to_binary
145
- NuWav::WaveFile.log out.length
146
- list << out
147
- end
148
- list
149
- end
150
-
151
- # TODO: handle other chunks not in the above list, but that might have been in a parsed wav
152
-
153
- riff_length = chunks_out.inject(0){|sum, chunk| sum += chunk.size}
154
- NuWav::WaveFile.log "NuWav::WaveFile.to_file: riff_length = #{riff_length}"
155
-
156
- #open file for writing
157
- open(file_name, "wb") do |o|
158
- #write the header
159
- o << "RIFF"
160
- o << [(riff_length + 4)].pack('V')
161
- o << "WAVE"
162
- #write the chunks
163
- chunks_out.each{|c| o << c}
164
- end
165
-
166
- end
167
-
168
- def write_data_file(file_name)
169
- open(file_name, "wb") do |o|
170
- o << chunks[:data].data
171
- end
172
- end
173
-
174
-
175
- # method to create a wave file using the
176
- def self.from_mpeg(file_name)
177
- # read and display infos & tags
178
- NuWav::WaveFile.log "NuWav::from_mpeg::file_name:#{file_name}"
179
- mp3info = Mp3Info.open(file_name)
180
- NuWav::WaveFile.log mp3info
181
- file = File.open(file_name)
182
- wave = WaveFile.new
183
-
184
- # data chunk
185
- data = DataChunk.new_from_file(file)
186
- wave.chunks[:data] = data
187
-
188
- # fmt chunk
189
- fmt = FmtChunk.new
190
- fmt.compression_code = MPEG_COMPRESSION
191
- fmt.number_of_channels = (mp3info.channel_mode == "Single Channel") ? 1 : 2
192
- fmt.sample_rate = mp3info.samplerate
193
- fmt.byte_rate = mp3info.bitrate / 8 * 1000
194
- fmt.block_align = calculate_mpeg_frame_size(mp3info)
195
- fmt.sample_bits = 65535
196
- fmt.extra_size = 22
197
- fmt.head_layer = ACM_LAYERS[mp3info.layer.to_i-1]
198
- fmt.head_bit_rate = mp3info.bitrate * 1000
199
- fmt.head_mode = CHANNEL_MODES[mp3info.channel_mode]
200
- # fmt.head_mode_ext = (mp3info.channel_mode == "JStereo") ? 2**mp3info.mode_extension : 0
201
- fmt.head_mode_ext = (mp3info.channel_mode == "JStereo") ? 2**mp3info.header[:mode_extension] : 0
202
- # fmt.head_emphasis = mp3info.emphasis + 1
203
- fmt.head_emphasis = mp3info.header[:emphasis] + 1
204
- fmt.head_flags = calculate_mpeg_head_flags(mp3info)
205
- fmt.pts_low = 0
206
- fmt.pts_high = 0
207
- wave.chunks[:fmt] = fmt
208
- # NuWav::WaveFile.log "fmt: #{fmt}"
209
-
210
- # fact chunk
211
- fact = FactChunk.new
212
- fact.samples_number = calculate_mpeg_samples_number(file, mp3info)
213
- wave.chunks[:fact] = fact
214
- # NuWav::WaveFile.log "fact: #{fact}"
215
-
216
- #mext chunk
217
- mext = MextChunk.new
218
- mext.sound_information = 5
219
- mext.sound_information += 2 if mp3info.header[:padding]
220
- mext.frame_size = calculate_mpeg_frame_size(mp3info)
221
- mext.ancillary_data_length = 0
222
- mext.ancillary_data_def = 0
223
- wave.chunks[:mext] = mext
224
- # NuWav::WaveFile.log "mext: #{mext}"
225
-
226
-
227
- #bext chunk
228
- bext = BextChunk.new
229
- bext.time_reference_high = 0
230
- bext.time_reference_low = 0
231
- bext.version = 1
232
- bext.coding_history = "A=MPEG1L#{mp3info.layer},F=#{mp3info.samplerate},B=#{mp3info.bitrate},M=#{CODING_HISTORY_MODE[mp3info.channel_mode]},T=PRX\r\n\0\0"
233
- wave.chunks[:bext] = bext
234
- # NuWav::WaveFile.log "bext: #{bext}"
235
-
236
- #cart chunk
237
- cart = CartChunk.new
238
- now = Time.now
239
- today = Date.today
240
- later = today << 12
241
- cart.version = '0101'
242
- cart.title = File.basename(file_name) # this is just a default
243
- cart.start_date = today.strftime("%Y-%m-%d")
244
- cart.start_time = now.strftime("%H:%M:%S")
245
- cart.end_date = later.strftime("%Y-%m-%d")
246
- cart.end_time = now.strftime("%H:%M:%S")
247
- cart.producer_app_id = 'PRX'
248
- cart.producer_app_version = '3.0'
249
- cart.level_reference = 0
250
- cart.tag_text = "\r\n"
251
- wave.chunks[:cart] = cart
252
- # NuWav::WaveFile.log "cart: #{cart}"
253
- wave
254
- end
255
-
256
- def self.calculate_mpeg_samples_number(file, info)
257
- (File.size(file.path) / calculate_mpeg_frame_size(info)) * Mp3Info::SAMPLES_PER_FRAME[info.layer][info.mpeg_version]
258
- end
259
-
260
- def self.calculate_mpeg_head_flags(info)
261
- flags = 0
262
- flags += 1 if (info.header[:private_bit])
263
- flags += 2 if (info.header[:copyright])
264
- flags += 4 if (info.header[:original])
265
- flags += 8 if (info.header[:error_protection])
266
- flags += 16 if (info.mpeg_version > 0)
267
- flags
268
- end
269
-
270
- def self.calculate_mpeg_frame_size(info)
271
- samples_per_frame = Mp3Info::SAMPLES_PER_FRAME[info.layer][info.mpeg_version]
272
- ((samples_per_frame / 8) * (info.bitrate * 1000))/info.samplerate
273
- end
274
-
275
- protected
276
-
277
- def read_chunk_header(file)
278
- hdr = file.read(8)
279
- # puts "hdr: #{hdr}"
280
- chunkName, chunkLen = hdr.unpack("A4V") rescue [nil, nil]
281
- # puts "chunkName: '#{chunkName}', chunkLen: '#{chunkLen}'"
282
- [chunkName, chunkLen]
283
- end
284
-
285
- def chunk_class(name)
286
- begin
287
- constantize("NuWav::#{camelize("#{name}_chunk")}")
288
- rescue NameError
289
- NuWav::Chunk
290
- end
291
-
292
- end
293
-
294
- # File vendor/rails/activesupport/lib/active_support/inflector.rb, line 147
295
- def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
296
- if first_letter_in_uppercase
297
- lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
298
- else
299
- lower_case_and_underscored_word.first + camelize(lower_case_and_underscored_word)[1..-1]
300
- end
301
- end
302
-
303
- # File vendor/rails/activesupport/lib/active_support/inflector.rb, line 252
304
- def constantize(camel_cased_word)
305
- unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ camel_cased_word
306
- raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!"
307
- end
308
- Object.module_eval("::#{$1}", __FILE__, __LINE__)
309
- end
310
-
311
- def self.log(m)
312
- if NuWav::DEBUG
313
- puts "#{Time.now}: NuWav: #{m}"
314
- end
315
- end
316
-
317
- end
318
-
319
- class Chunk
320
- attr_accessor :id, :size, :raw
321
-
322
- def self.parse(id, size, file)
323
- raw = file.read(size)
324
- chunk = self.new(id, size, raw)
325
- chunk.parse
326
- return chunk
327
- end
328
45
 
329
- def initialize(id=nil, size=nil, raw=nil)
330
- @id, @size, @raw = id, size, raw
331
- end
332
-
333
- def parse
334
- end
335
-
336
- def read_dword(start)
337
- @raw[start..(start+3)].unpack('V').first
338
- end
339
-
340
- def read_word(start)
341
- @raw[start..(start+1)].unpack('v').first
342
- end
343
-
344
- def read_char(start, length=(@raw.length-start))
345
- (@raw[start..(start+length-1)] || '').strip
346
- end
347
-
348
- def write_dword(val)
349
- val ||= 0
350
- [val].pack('V')
351
- end
352
-
353
- def write_word(val)
354
- val ||= 0
355
- [val].pack('v')
356
- end
357
-
358
- def write_char(val, length=nil)
359
- val ||= ''
360
- val = val.to_s
361
- length ||= val.length
362
- # NuWav::WaveFile.log "length:#{length} val.length:#{val.length} val:#{val}"
363
- padding = "\0" * [(length - val.length), 0].max
364
- out = val[0,length] + padding
365
- # NuWav::WaveFile.log out
366
- out
367
- end
368
-
369
- def to_binary
370
- end
371
- end
372
-
373
-
374
- class RiffChunk
375
- attr_accessor :id, :size, :riff_type
376
-
377
- def initialize(riff_name, riff_length, riff_type)
378
- @id, @size, @riff_type = riff_name, riff_length, riff_type
379
- end
380
-
381
- def to_s
382
- "<chunk type:riff id:#{@id} size:#{@size} type:#{@riff_type} />"
383
- end
384
-
385
- end
386
-
387
- class FmtChunk < Chunk
388
-
389
- attr_accessor :compression_code, :number_of_channels, :sample_rate, :byte_rate, :block_align, :sample_bits, :extra_size, :extra,
390
- :head_layer, :head_bit_rate, :head_mode, :head_mode_ext, :head_emphasis, :head_flags, :pts_low, :pts_high
391
-
392
- def parse
393
- NuWav::WaveFile.log "@raw.size = #{@raw.size}"
394
- @compression_code = read_word(0)
395
- @number_of_channels = read_word(2)
396
- @sample_rate = read_dword(4)
397
- @byte_rate = read_dword(8)
398
- @block_align = read_word(12)
399
- @sample_bits = read_word(14)
400
- @extra_size = read_word(16)
401
-
402
- if (@compression_code.to_i == MPEG_COMPRESSION)
403
- @head_layer = read_word(18)
404
- @head_bit_rate = read_dword(20)
405
- @head_mode = read_word(24)
406
- @head_mode_ext = read_word(26)
407
- @head_emphasis = read_word(28)
408
- @head_flags = read_word(30)
409
- @pts_low = read_dword(32)
410
- @pts_high = read_dword(36)
411
- end
412
- end
413
-
414
- def to_binary
415
- out = ''
416
- out += write_word(@compression_code)
417
- out += write_word(@number_of_channels)
418
- out += write_dword(@sample_rate)
419
- out += write_dword(@byte_rate)
420
- out += write_word(@block_align)
421
- out += write_word(@sample_bits)
422
- out += write_word(@extra_size)
423
-
424
- if (@compression_code.to_i == MPEG_COMPRESSION)
425
- out += write_word(@head_layer)
426
- out += write_dword(@head_bit_rate)
427
- out += write_word(@head_mode)
428
- out += write_word(@head_mode_ext)
429
- out += write_word(@head_emphasis)
430
- out += write_word(@head_flags)
431
- out += write_dword(@pts_low)
432
- out += write_dword(@pts_high)
433
- end
434
- "fmt " + write_dword(out.size) + out
435
- end
436
-
437
- def to_s
438
- extra = if (@compression_code.to_i == MPEG_COMPRESSION)
439
- ", head_layer:#{head_layer}, head_bit_rate:#{head_bit_rate}, head_mode:#{head_mode}, head_mode_ext:#{head_mode_ext}, head_emphasis:#{head_emphasis}, head_flags:#{head_flags}, pts_low:#{pts_low}, pts_high:#{pts_high}"
440
- else
441
- ""
442
- end
443
- "<chunk type:fmt compression_code:#{compression_code}, number_of_channels:#{number_of_channels}, sample_rate:#{sample_rate}, byte_rate:#{byte_rate}, block_align:#{block_align}, sample_bits:#{sample_bits}, extra_size:#{extra_size} #{extra} />"
444
- end
445
- end
446
-
447
- class FactChunk < Chunk
448
- attr_accessor :samples_number
449
-
450
- def parse
451
- @samples_number = read_dword(0)
452
- end
453
-
454
- def to_s
455
- "<chunk type:fact samples_number:#{@samples_number} />"
456
- end
457
-
458
- def to_binary
459
- "fact" + write_dword(4) + write_dword(@samples_number)
460
- end
461
-
462
- end
463
-
464
- class MextChunk < Chunk
465
- attr_accessor :sound_information, :frame_size, :ancillary_data_length, :ancillary_data_def, :reserved
466
-
467
- def parse
468
- @sound_information = read_word(0)
469
- @frame_size = read_word(2)
470
- @ancillary_data_length = read_word(4)
471
- @ancillary_data_def = read_word(6)
472
- @reserved = read_char(8,4)
473
- end
474
-
475
- def to_s
476
- "<chunk type:mext sound_information:(#{sound_information}) #{(0..15).inject(''){|s,x| "#{s}#{sound_information[x]}"}}, frame_size:#{frame_size}, ancillary_data_length:#{ancillary_data_length}, ancillary_data_def:#{(0..15).inject(''){|s,x| "#{s}#{ancillary_data_def[x]}"}}, reserved:'#{reserved}' />"
477
- end
478
-
479
- def to_binary
480
- out = "mext" + write_dword(12)
481
- out += write_word(@sound_information)
482
- out += write_word(@frame_size)
483
- out += write_word(@ancillary_data_length)
484
- out += write_word(@ancillary_data_def)
485
- out += write_char(@reserved, 4)
486
- out
487
- end
488
- end
489
-
490
- class BextChunk < Chunk
491
- attr_accessor :description, :originator, :originator_reference, :origination_date, :origination_time, :time_reference_low, :time_reference_high,
492
- :version, :umid, :reserved, :coding_history
493
-
494
- def parse
495
- @description = read_char(0,256)
496
- @originator = read_char(256,32)
497
- @originator_reference = read_char(288,32)
498
- @origination_date = read_char(320,10)
499
- @origination_time = read_char(330,8)
500
- @time_reference_low = read_dword(338)
501
- @time_reference_high = read_dword(342)
502
- @version = read_word(346)
503
- @umid = read_char(348,64)
504
- @reserved = read_char(412,190)
505
- @coding_history = read_char(602)
506
- end
507
-
508
- def to_s
509
- "<chunk type:bext description:'#{description}', originator:'#{originator}', originator_reference:'#{originator_reference}', origination_date:'#{origination_date}', origination_time:'#{origination_time}', time_reference_low:#{time_reference_low}, time_reference_high:#{time_reference_high}, version:#{version}, umid:#{umid}, reserved:'#{reserved}', coding_history:#{coding_history} />"
510
- end
511
-
512
- def to_binary
513
- out = "bext" + write_dword(602 + @coding_history.length )
514
- out += write_char(@description, 256)
515
- out += write_char(@originator, 32)
516
- out += write_char(@originator_reference, 32)
517
- out += write_char(@origination_date, 10)
518
- out += write_char(@origination_time, 8)
519
- out += write_dword(@time_reference_low)
520
- out += write_dword(@time_reference_high)
521
- out += write_word(@version)
522
- out += write_char(@umid, 64)
523
- out += write_char(@reserved, 190)
524
- out += write_char(@coding_history)
525
- # make sure coding history ends in '\r\n'
526
- out
527
- end
528
-
529
- end
530
-
531
- class CartChunk < Chunk
532
- attr_accessor :version, :title, :artist, :cut_id, :client_id, :category, :classification, :out_cue, :start_date, :start_time, :end_date, :end_time,
533
- :producer_app_id, :producer_app_version, :user_def, :level_reference, :post_timer, :reserved, :url, :tag_text
534
-
535
- def parse
536
- @version = read_char(0,4)
537
- @title = read_char(4,64)
538
- @artist = read_char(68,64)
539
- @cut_id = read_char(132,64)
540
- @client_id = read_char(196,64)
541
- @category = read_char(260,64)
542
- @classification = read_char(324,64)
543
- @outcue = read_char(388,64)
544
- @start_date = read_char(452,10)
545
- @start_time = read_char(462,8)
546
- @end_date = read_char(470,10)
547
- @end_time = read_char(480,8)
548
- @producer_app_id = read_char(488,64)
549
- @producer_app_version = read_char(552,64)
550
- @user_def = read_char(616,64)
551
- @level_reference = read_dword(680)
552
- @post_timer = read_char(684,64)
553
- @reserved = read_char(748,276)
554
- @url = read_char(1024,1024)
555
- @tag_text = read_char(2048)
556
- end
557
-
558
- def to_s
559
- "<chunk type:cart version:#{version}, title:#{title}, artist:#{artist}, cut_id:#{cut_id}, client_id:#{client_id}, category:#{category}, classification:#{classification}, out_cue:#{out_cue}, start_date:#{start_date}, start_time:#{start_time}, end_date:#{end_date}, end_time:#{end_time}, producer_app_id:#{producer_app_id}, producer_app_version:#{producer_app_version}, user_def:#{user_def}, level_reference:#{level_reference}, post_timer:#{post_timer}, reserved:#{reserved}, url:#{url}, tag_text:#{tag_text} />"
560
- end
561
-
562
- def to_binary
563
- out = "cart" + write_dword(2048 + @tag_text.length )
564
- out += write_char(@version,4)
565
- out += write_char(@title,64)
566
- out += write_char(@artist,64)
567
- out += write_char(@cut_id,64)
568
- out += write_char(@client_id,64)
569
- out += write_char(@category,64)
570
- out += write_char(@classification,64)
571
- out += write_char(@outcue,64)
572
- out += write_char(@start_date,10)
573
- out += write_char(@start_time,8)
574
- out += write_char(@end_date,10)
575
- out += write_char(@end_time,8)
576
- out += write_char(@producer_app_id,64)
577
- out += write_char(@producer_app_version,64)
578
- out += write_char(@user_def,64)
579
- out += write_dword(@level_reference)
580
- out += write_char(@post_timer,64)
581
- out += write_char(@reserved,276)
582
- out += write_char(@url,1024)
583
- out += write_char(@tag_text)
584
- out
585
- end
586
-
587
- end
588
-
589
- class DataChunk < Chunk
590
- attr_accessor :tmp_data_file
591
-
592
- def self.parse(id, size, file)
593
-
594
- # tmp_data = File.open('./data_chunk.mp2', 'wb')
595
- tmp_data = Tempfile.open('data_chunk')
596
- tmp_data.binmode
597
-
598
- remaining = size
599
- while (remaining > 0 && !file.eof?)
600
- read_bytes = [128, remaining].min
601
- tmp_data << file.read(read_bytes)
602
- remaining -= read_bytes
603
- end
604
- tmp_data.rewind
605
- chunk = self.new(id, size, tmp_data)
606
-
607
- return chunk
608
- end
609
-
610
- def self.new_from_file(file)
611
- tmp_data = Tempfile.open('data_chunk')
612
- tmp_data.binmode
613
- FileUtils.cp(file.path, tmp_data.path)
614
- tmp_data.rewind
615
- self.new('data', File.size(tmp_data.path).to_s, tmp_data)
616
- end
617
-
618
- def initialize(id=nil, size=nil, tmp_data_file=nil)
619
- @id, @size, @tmp_data_file = id, size, tmp_data_file
620
- end
621
-
622
- def data
623
- f = ''
624
- if self.tmp_data_file
625
- NuWav::WaveFile.log "we have a tmp_data_file!"
626
- self.tmp_data_file.rewind
627
- f = self.tmp_data_file.read
628
- self.tmp_data_file.rewind
629
- else
630
- NuWav::WaveFile.log "we have NO tmp_data_file!"
631
- end
632
- f
633
- end
634
-
635
- def to_s
636
- "<chunk type:data (size:#{data.size})/>"
637
- end
638
-
639
- def to_binary
640
- NuWav::WaveFile.log "data chunk to_binary"
641
- d = self.data
642
- NuWav::WaveFile.log "got data size = #{d.size} #{d[0,10]}"
643
- out = "data" + write_dword(d.size) + d
644
- out
645
- end
646
-
647
- end
648
-
649
46
  end