nu_wav 0.3.4 → 0.4.2

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