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 +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
|