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 +15 -0
- data/.gitignore +19 -0
- data/Gemfile +4 -0
- data/LICENSE +2 -0
- data/Rakefile +2 -46
- data/lib/nu_wav.rb +5 -608
- data/lib/nu_wav/chunk.rb +331 -0
- data/lib/nu_wav/version.rb +3 -0
- data/lib/nu_wav/wave_file.rb +286 -0
- data/nu_wav.gemspec +20 -45
- data/test/files/test.mp2 +0 -0
- data/test/files/test_basic.wav +0 -0
- data/test/files/test_bwf.wav +0 -0
- data/test/test_nu_wav.rb +40 -25
- metadata +81 -64
- data/VERSION +0 -1
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
data/Gemfile
ADDED
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
|
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 =
|
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
|