audio_monster 1.0.0
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 +7 -0
- data/.gitignore +15 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +38 -0
- data/Rakefile +9 -0
- data/audio_monster.gemspec +29 -0
- data/lib/audio_monster/configuration.rb +143 -0
- data/lib/audio_monster/monster.rb +951 -0
- data/lib/audio_monster/version.rb +5 -0
- data/lib/audio_monster.rb +18 -0
- data/test/audio_monster_test.rb +20 -0
- data/test/files/test.flac +0 -0
- data/test/files/test.ogg +0 -0
- data/test/files/test_long.mp2 +0 -0
- data/test/files/test_long.mp3 +0 -0
- data/test/files/test_long.wav +0 -0
- data/test/files/test_long_2.mp2 +0 -0
- data/test/files/test_short.mp2 +0 -0
- data/test/files/test_short.wav +0 -0
- data/test/files/test_stereo.wav +0 -0
- data/test/minitest_helper.rb +24 -0
- data/test/monster_test.rb +111 -0
- metadata +177 -0
@@ -0,0 +1,951 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require "audio_monster/version"
|
4
|
+
|
5
|
+
require 'open3'
|
6
|
+
require 'timeout'
|
7
|
+
require 'mp3info'
|
8
|
+
require 'logger'
|
9
|
+
require 'nu_wav'
|
10
|
+
require 'tempfile'
|
11
|
+
require 'mimemagic'
|
12
|
+
require 'active_support/all'
|
13
|
+
|
14
|
+
module AudioMonster
|
15
|
+
|
16
|
+
class Monster
|
17
|
+
include Configuration
|
18
|
+
include ::NuWav
|
19
|
+
|
20
|
+
def initialize(options={})
|
21
|
+
apply_configuration(options)
|
22
|
+
end
|
23
|
+
|
24
|
+
def tone_detect(path, tone, threshold=0.05, min_time=0.5)
|
25
|
+
ranges = []
|
26
|
+
|
27
|
+
tone = tone.to_i
|
28
|
+
threshold = threshold.to_f
|
29
|
+
min_time = min_time.to_f
|
30
|
+
normalized_wav_dat = nil
|
31
|
+
|
32
|
+
begin
|
33
|
+
|
34
|
+
normalized_wav_dat = create_temp_file(path + '.dat')
|
35
|
+
normalized_wav_dat.close
|
36
|
+
|
37
|
+
command = "#{bin(:sox)} '#{path}' '#{normalized_wav_dat.path}' channels 1 rate 200 bandpass #{tone} 3 gain 6"
|
38
|
+
out, err = run_command(command)
|
39
|
+
current_range = nil
|
40
|
+
|
41
|
+
File.foreach(normalized_wav_dat.path) do |row|
|
42
|
+
next if row[0] == ';'
|
43
|
+
|
44
|
+
data = row.split.map(&:to_f)
|
45
|
+
time = data[0]
|
46
|
+
energy = data[1].abs()
|
47
|
+
|
48
|
+
if energy >= threshold
|
49
|
+
if !current_range
|
50
|
+
current_range = {start: time, finish: time, min: energy, max: energy}
|
51
|
+
else
|
52
|
+
current_range[:finish] = time
|
53
|
+
current_range[:min] = [current_range[:min], energy].min
|
54
|
+
current_range[:max] = [current_range[:max], energy].max
|
55
|
+
end
|
56
|
+
else
|
57
|
+
if current_range && ((current_range[:finish] + min_time.to_f) < time)
|
58
|
+
ranges << current_range
|
59
|
+
current_range = nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
if current_range
|
65
|
+
ranges << current_range
|
66
|
+
end
|
67
|
+
|
68
|
+
ensure
|
69
|
+
normalized_wav_dat.close rescue nil
|
70
|
+
normalized_wav_dat.unlink rescue nil
|
71
|
+
end
|
72
|
+
|
73
|
+
ranges
|
74
|
+
end
|
75
|
+
|
76
|
+
def silence_detect(path, threshold=0.001, min_time=2.0)
|
77
|
+
ranges = []
|
78
|
+
# puts "\n#{Time.now} tone_detect(path, tone): #{path}, #{tone}\n"
|
79
|
+
|
80
|
+
threshold = threshold.to_f
|
81
|
+
min_time = min_time.to_f
|
82
|
+
normalized_wav_dat = nil
|
83
|
+
|
84
|
+
begin
|
85
|
+
|
86
|
+
normalized_wav_dat = create_temp_file(path + '.dat')
|
87
|
+
normalized_wav_dat.close
|
88
|
+
|
89
|
+
command = "#{bin(:sox)} '#{path}' '#{normalized_wav_dat.path}' channels 1 rate 1000 norm"
|
90
|
+
out, err = run_command(command)
|
91
|
+
|
92
|
+
current_range = nil
|
93
|
+
|
94
|
+
File.foreach(normalized_wav_dat.path) do |row|
|
95
|
+
next if row[0] == ';'
|
96
|
+
|
97
|
+
data = row.split.map(&:to_f)
|
98
|
+
time = data[0]
|
99
|
+
energy = data[1].abs()
|
100
|
+
|
101
|
+
if energy < threshold
|
102
|
+
if !current_range
|
103
|
+
current_range = {start: time, finish: time, min: energy, max: energy}
|
104
|
+
else
|
105
|
+
current_range[:finish] = time
|
106
|
+
current_range[:min] = [current_range[:min], energy].min
|
107
|
+
current_range[:max] = [current_range[:max], energy].max
|
108
|
+
end
|
109
|
+
else
|
110
|
+
next unless current_range
|
111
|
+
ranges << current_range if ((current_range[:finish] - current_range[:start]) > min_time.to_f)
|
112
|
+
current_range = nil
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
if current_range && ((current_range[:finish] - current_range[:start]) > min_time.to_f)
|
117
|
+
ranges << current_range
|
118
|
+
end
|
119
|
+
|
120
|
+
ensure
|
121
|
+
normalized_wav_dat.close rescue nil
|
122
|
+
normalized_wav_dat.unlink rescue nil
|
123
|
+
end
|
124
|
+
|
125
|
+
ranges
|
126
|
+
end
|
127
|
+
|
128
|
+
def encode_wav_pcm_from_mpeg(original_path, wav_path, options={})
|
129
|
+
logger.info "encode_wav_pcm_from_mpeg: #{original_path}, #{wav_path}, #{options.inspect}"
|
130
|
+
# check to see if there is an original
|
131
|
+
check_local_file(original_path)
|
132
|
+
|
133
|
+
logger.debug "encode_wav_pcm_from_mpeg: start"
|
134
|
+
command = "#{bin(:madplay)} -Q -i --output=wave:'#{wav_path}' '#{original_path}'"
|
135
|
+
|
136
|
+
out, err = run_command(command)
|
137
|
+
|
138
|
+
# check to see if there is a file created, or don't go on.
|
139
|
+
check_local_file(wav_path)
|
140
|
+
return [out, err]
|
141
|
+
end
|
142
|
+
|
143
|
+
def encode_wav_pcm_from_flac(original_path, wav_path, options={})
|
144
|
+
logger.info "encode_wav_pcm_from_flac: #{original_path}, #{wav_path}, #{options.inspect}"
|
145
|
+
# check to see if there is an original
|
146
|
+
check_local_file(original_path)
|
147
|
+
|
148
|
+
logger.debug "encode_wav_pcm_from_mpeg: start"
|
149
|
+
command = "#{bin(:flac)} -s -f --decode '#{original_path}' --output-name='#{wav_path}'"
|
150
|
+
out, err = run_command(command)
|
151
|
+
|
152
|
+
# check to see if there is a file created, or don't go on.
|
153
|
+
check_local_file(wav_path)
|
154
|
+
return [out, err]
|
155
|
+
end
|
156
|
+
|
157
|
+
alias encode_wav_pcm_from_mp2 encode_wav_pcm_from_mpeg
|
158
|
+
alias encode_wav_pcm_from_mp3 encode_wav_pcm_from_mpeg
|
159
|
+
|
160
|
+
# experimental...should work on any ffmpeg compatible file
|
161
|
+
def decode_audio(original_path, wav_path, options={})
|
162
|
+
# check to see if there is an original
|
163
|
+
logger.info "decode_audio: #{original_path}, #{wav_path}, #{options.inspect}"
|
164
|
+
check_local_file(original_path)
|
165
|
+
|
166
|
+
# if the file extension is banged up, try to get from options, or guess at 'mov'
|
167
|
+
input_format = ''
|
168
|
+
if options[:source_format] || (File.extname(original_path).length != 4)
|
169
|
+
input_format = options[:source_format] ? "-f #{options[:source_format]}" : '-f mov'
|
170
|
+
end
|
171
|
+
|
172
|
+
channels = options[:channels] ? options[:channels].to_i : 2
|
173
|
+
sample_rate = options[:sample_rate] ? options[:sample_rate].to_i : 44100
|
174
|
+
logger.debug "decode_audio: start"
|
175
|
+
command = "#{bin(:ffmpeg)} -nostats -loglevel warning -vn -i '#{original_path}' -acodec pcm_s16le -ac #{channels} -ar #{sample_rate} -y -f wav '#{wav_path}'"
|
176
|
+
|
177
|
+
out, err = run_command(command)
|
178
|
+
|
179
|
+
# check to see if there is a file created, or don't go on.
|
180
|
+
check_local_file(wav_path)
|
181
|
+
return [out, err]
|
182
|
+
end
|
183
|
+
|
184
|
+
def info_for_mpeg(mpeg_path, info = nil)
|
185
|
+
logger.debug "info_for_mpeg: start"
|
186
|
+
length = audio_file_duration(mpeg_path)
|
187
|
+
info ||= Mp3Info.new(mpeg_path)
|
188
|
+
result = {
|
189
|
+
:size => File.size(mpeg_path),
|
190
|
+
:content_type => 'audio/mpeg',
|
191
|
+
:channel_mode => info.channel_mode,
|
192
|
+
:bit_rate => info.bitrate,
|
193
|
+
:length => [info.length.to_i, length.to_i].max,
|
194
|
+
:sample_rate => info.samplerate,
|
195
|
+
:version => info.mpeg_version, # mpeg specific
|
196
|
+
:layer => info.layer # mpeg specific
|
197
|
+
}
|
198
|
+
|
199
|
+
# indicate this can be GC'd
|
200
|
+
info = nil
|
201
|
+
|
202
|
+
result
|
203
|
+
end
|
204
|
+
|
205
|
+
alias info_for_mp2 info_for_mpeg
|
206
|
+
alias info_for_mp3 info_for_mpeg
|
207
|
+
|
208
|
+
def info_for_wav(wav_file_path)
|
209
|
+
wf = WaveFile.parse(wav_file_path)
|
210
|
+
fmt = wf.chunks[:fmt]
|
211
|
+
{
|
212
|
+
:size => File.size(wav_file_path),
|
213
|
+
:content_type => 'audio/vnd.wave',
|
214
|
+
:channel_mode => fmt.number_of_channels <= 1 ? 'Mono' : 'Stereo',
|
215
|
+
:bit_rate => (fmt.byte_rate * 8) / 1000, #kilo bytes per sec
|
216
|
+
:length => wf.duration,
|
217
|
+
:sample_rate => fmt.sample_rate
|
218
|
+
}
|
219
|
+
end
|
220
|
+
|
221
|
+
def info_for_audio(path)
|
222
|
+
{
|
223
|
+
:size => File.size(path),
|
224
|
+
:content_type => (MimeMagic.by_path(path) || MimeMagic.by_magic(path)).to_s,
|
225
|
+
:channel_mode => audio_file_channels(path) <= 1 ? 'Mono' : 'Stereo',
|
226
|
+
:bit_rate => audio_file_bit_rate(path),
|
227
|
+
:length => audio_file_duration(path),
|
228
|
+
:sample_rate => audio_file_sample_rate(path)
|
229
|
+
}
|
230
|
+
end
|
231
|
+
|
232
|
+
def audio_file_duration(path)
|
233
|
+
audio_file_info(path, 'D').to_f
|
234
|
+
end
|
235
|
+
|
236
|
+
def audio_file_channels(path)
|
237
|
+
audio_file_info(path, 'c').to_i
|
238
|
+
end
|
239
|
+
|
240
|
+
def audio_file_sample_rate(path)
|
241
|
+
audio_file_info(path, 'r').to_i
|
242
|
+
end
|
243
|
+
|
244
|
+
def audio_file_bit_rate(path)
|
245
|
+
audio_file_info(path, 'B').to_i
|
246
|
+
end
|
247
|
+
|
248
|
+
def audio_file_info(path, flag)
|
249
|
+
check_local_file(path)
|
250
|
+
out, err = run_command("#{bin(:soxi)} -V0 -#{flag} '#{path}'", :nice=>'n', :echo_return=>false)
|
251
|
+
out.chomp
|
252
|
+
end
|
253
|
+
|
254
|
+
# valid options
|
255
|
+
# :sample_rate
|
256
|
+
# :bit_rate
|
257
|
+
# :per_channel_bit_rate
|
258
|
+
# :channel_mode
|
259
|
+
# :protect
|
260
|
+
# :copyright
|
261
|
+
# :original
|
262
|
+
# :emphasis
|
263
|
+
def encode_mp2_from_wav(original_path, mp2_path, options={})
|
264
|
+
check_local_file(original_path)
|
265
|
+
|
266
|
+
options.to_options!
|
267
|
+
# parse the wave to see what values to use if not overridden by the options
|
268
|
+
wf = WaveFile.parse(original_path)
|
269
|
+
fmt = wf.chunks[:fmt]
|
270
|
+
|
271
|
+
wav_sample_size = fmt.sample_bits
|
272
|
+
|
273
|
+
# twolame can only handle up to 16 for floating point (seems to convert to 16 internaly anyway)
|
274
|
+
# "Note: the 32-bit samples are currently scaled down to 16-bit samples internally."
|
275
|
+
# libtwolame.h twolame_encode_buffer_float32 http://www.twolame.org/doc/twolame_8h.html#8e77eb0f22479f8ec1bd4f1b042f9cd9
|
276
|
+
if (fmt.compression_code.to_i == PCM_FLOATING_COMPRESSION && fmt.sample_bits > 32)
|
277
|
+
wav_sample_size = 16
|
278
|
+
end
|
279
|
+
|
280
|
+
# input options
|
281
|
+
prefix_command = ''
|
282
|
+
raw_input = ''
|
283
|
+
sample_rate = "--samplerate #{fmt.sample_rate}"
|
284
|
+
sample_bits = "--samplesize #{wav_sample_size}"
|
285
|
+
channels = "--channels #{fmt.number_of_channels}"
|
286
|
+
input_path = "'#{original_path}'"
|
287
|
+
|
288
|
+
# output options
|
289
|
+
mp2_sample_rate = if MP2_SAMPLE_RATES.include?(options[:sample_rate].to_s)
|
290
|
+
options[:sample_rate]
|
291
|
+
elsif MP2_SAMPLE_RATES.include?(fmt.sample_rate.to_s)
|
292
|
+
fmt.sample_rate.to_s
|
293
|
+
else
|
294
|
+
'44100'
|
295
|
+
end
|
296
|
+
|
297
|
+
if mp2_sample_rate.to_i != fmt.sample_rate.to_i
|
298
|
+
prefix_command = "#{bin(:sox)} '#{original_path}' -t raw -r #{mp2_sample_rate} - | "
|
299
|
+
input_path = '-'
|
300
|
+
raw_input = '--raw-input'
|
301
|
+
end
|
302
|
+
|
303
|
+
mode = if TWOLAME_MODES.include?(options[:channel_mode])
|
304
|
+
options[:channel_mode] #use the channel mode from the options if specified
|
305
|
+
elsif fmt.number_of_channels <= 1
|
306
|
+
'm' # default to monoaural for 1 channel input
|
307
|
+
else
|
308
|
+
's' # default to joint stereo for 2 channel input
|
309
|
+
end
|
310
|
+
channel_mode = "--mode #{mode}"
|
311
|
+
|
312
|
+
kbps = if options[:per_channel_bit_rate]
|
313
|
+
options[:per_channel_bit_rate].to_i * ((mode == 'm') ? 1 : 2)
|
314
|
+
elsif options[:bit_rate]
|
315
|
+
options[:bit_rate].to_i
|
316
|
+
else
|
317
|
+
0
|
318
|
+
end
|
319
|
+
|
320
|
+
kbps = if MP2_BITRATES.include?(kbps)
|
321
|
+
kbps
|
322
|
+
elsif mode == 'm' || (mode =='a' && fmt.number_of_channels <= 1)
|
323
|
+
128 # default for monoaural is 128 kbps
|
324
|
+
else
|
325
|
+
256 # default for stereo/dual channel is 256 kbps
|
326
|
+
end
|
327
|
+
bit_rate = "--bitrate #{kbps}"
|
328
|
+
|
329
|
+
downmix = (mode == 'm' && fmt.number_of_channels > 1) ? '--downmix' : ''
|
330
|
+
|
331
|
+
# default these headers when options not present
|
332
|
+
protect = (options.key?(:protect) && !options[:protect] ) ? '' : '--protect'
|
333
|
+
copyright = (options.key?(:copyright) && !options[:copyright] ) ? '' : '--copyright'
|
334
|
+
original = (options.key?(:original) && !options[:original] ) ? '--non-original' : '--original'
|
335
|
+
emphasis = (options.key?(:emphasis)) ? "--deemphasis #{options[:emphasis]}" : '--deemphasis n'
|
336
|
+
|
337
|
+
##
|
338
|
+
# execute the command
|
339
|
+
##
|
340
|
+
input_options = "#{raw_input} #{sample_rate} #{sample_bits} #{channels}"
|
341
|
+
output_options = "#{channel_mode} #{bit_rate} #{downmix} #{protect} #{copyright} #{original} #{emphasis}"
|
342
|
+
|
343
|
+
command = "#{prefix_command} #{bin(:twolame)} -t 0 #{input_options} #{output_options} #{input_path} '#{mp2_path}'"
|
344
|
+
out, err = run_command(command)
|
345
|
+
unless out.split("\n").last =~ TWOLAME_SUCCESS_RE
|
346
|
+
raise "encode_mp2_from_wav - twolame response on transcoding was not recognized as a success, #{out}, #{err}"
|
347
|
+
end
|
348
|
+
|
349
|
+
# make sure there is a file at the end of this
|
350
|
+
check_local_file(mp2_path)
|
351
|
+
|
352
|
+
true
|
353
|
+
end
|
354
|
+
|
355
|
+
# valid options
|
356
|
+
# :sample_rate
|
357
|
+
# :bit_rate
|
358
|
+
# :channel_mode
|
359
|
+
def encode_mp3_from_wav(original_path, mp3_path, options={})
|
360
|
+
logger.info "encode_mp3_from_wav: #{original_path}, #{mp3_path}, #{options.inspect}"
|
361
|
+
|
362
|
+
check_local_file(original_path)
|
363
|
+
|
364
|
+
options.to_options!
|
365
|
+
# parse the wave to see what values to use if not overridden by the options
|
366
|
+
wf = WaveFile.parse(original_path)
|
367
|
+
fmt = wf.chunks[:fmt]
|
368
|
+
|
369
|
+
input_path = '-'
|
370
|
+
|
371
|
+
mp3_sample_rate = if MP3_SAMPLE_RATES.include?(options[:sample_rate].to_s)
|
372
|
+
options[:sample_rate].to_s
|
373
|
+
elsif MP3_SAMPLE_RATES.include?(fmt.sample_rate.to_s)
|
374
|
+
logger.debug "sample_rate: fmt.sample_rate = #{fmt.sample_rate}"
|
375
|
+
fmt.sample_rate.to_s
|
376
|
+
else
|
377
|
+
'44100'
|
378
|
+
end
|
379
|
+
logger.debug "mp3_sample_rate: #{options[:sample_rate]}, #{fmt.sample_rate}"
|
380
|
+
|
381
|
+
mode = if LAME_MODES.include?(options[:channel_mode])
|
382
|
+
options[:channel_mode] #use the channel mode from the options if specified
|
383
|
+
elsif fmt.number_of_channels <= 1
|
384
|
+
'm' # default to monoaural for 1 channel input
|
385
|
+
else
|
386
|
+
'j' # default to joint stereo for 2 channel input
|
387
|
+
end
|
388
|
+
channel_mode = "-m #{mode}"
|
389
|
+
|
390
|
+
# if mono selected, but input is in stereo, need to specify downmix to 1 channel for sox
|
391
|
+
downmix = (mode == 'm' && fmt.number_of_channels > 1) ? '-c 1' : ''
|
392
|
+
|
393
|
+
# if sample rate different, change that as well in sox before piping to lame
|
394
|
+
resample = (mp3_sample_rate.to_i != fmt.sample_rate.to_i) ? "-r #{mp3_sample_rate} " : ''
|
395
|
+
logger.debug "resample: #{resample} from comparing #{mp3_sample_rate} #{fmt.sample_rate}"
|
396
|
+
|
397
|
+
# output to wav (-t wav) has a warning
|
398
|
+
# '/usr/local/bin/sox wav: Length in output .wav header will be wrong since can't seek to fix it'
|
399
|
+
# that messsage can safely be ignored, wa output is easier/safer for lame to recognize, so worth ignoring this message
|
400
|
+
prefix_command = "#{bin(:sox)} '#{original_path}' -t wav #{resample} #{downmix} - | "
|
401
|
+
|
402
|
+
kbps = if options[:per_channel_bit_rate]
|
403
|
+
options[:per_channel_bit_rate].to_i * ((mode == 'm') ? 1 : 2)
|
404
|
+
elsif options[:bit_rate]
|
405
|
+
options[:bit_rate].to_i
|
406
|
+
else
|
407
|
+
0
|
408
|
+
end
|
409
|
+
|
410
|
+
kbps = if MP3_BITRATES.include?(kbps)
|
411
|
+
kbps
|
412
|
+
elsif mode == 'm'
|
413
|
+
128 # default for monoaural is 128 kbps
|
414
|
+
else
|
415
|
+
256 # default for stereo/dual channel is 256 kbps
|
416
|
+
end
|
417
|
+
bit_rate = "--cbr -b #{kbps}"
|
418
|
+
|
419
|
+
##
|
420
|
+
# execute the command
|
421
|
+
##
|
422
|
+
output_options = "#{channel_mode} #{bit_rate}"
|
423
|
+
|
424
|
+
command = "#{prefix_command} #{bin(:lame)} -S #{output_options} #{input_path} '#{mp3_path}'"
|
425
|
+
|
426
|
+
out, err = run_command(command)
|
427
|
+
|
428
|
+
unless out.split("\n")[-1] =~ LAME_SUCCESS_RE
|
429
|
+
raise "encode_mp3_from_wav - lame completion unsuccessful: #{out}"
|
430
|
+
end
|
431
|
+
|
432
|
+
err.split("\n").each do |l|
|
433
|
+
if l =~ LAME_ERROR_RE
|
434
|
+
raise "encode_mp3_from_wav - lame response had fatal error: #{l}"
|
435
|
+
end
|
436
|
+
end
|
437
|
+
logger.debug "encode_mp3_from_wav: end!"
|
438
|
+
|
439
|
+
check_local_file(mp3_path)
|
440
|
+
|
441
|
+
true
|
442
|
+
end
|
443
|
+
|
444
|
+
def encode_ogg_from_wav(original_path, result_path, options={})
|
445
|
+
logger.info("encode_ogg_from_wav: original_path: #{original_path}, result_path: #{result_path}, options: #{options.inspect}")
|
446
|
+
|
447
|
+
check_local_file(original_path)
|
448
|
+
|
449
|
+
options.to_options!
|
450
|
+
# parse the wave to see what values to use if not overridden by the options
|
451
|
+
wf = WaveFile.parse(original_path)
|
452
|
+
fmt = wf.chunks[:fmt]
|
453
|
+
|
454
|
+
sample_rate = if MP3_SAMPLE_RATES.include?(options[:sample_rate].to_s)
|
455
|
+
options[:sample_rate].to_s
|
456
|
+
elsif MP3_SAMPLE_RATES.include?(fmt.sample_rate.to_s)
|
457
|
+
logger.debug "sample_rate: fmt.sample_rate = #{fmt.sample_rate}"
|
458
|
+
fmt.sample_rate.to_s
|
459
|
+
else
|
460
|
+
'44100'
|
461
|
+
end
|
462
|
+
logger.debug "sample_rate: #{options[:sample_rate]}, #{fmt.sample_rate}"
|
463
|
+
|
464
|
+
mode = if LAME_MODES.include?(options[:channel_mode])
|
465
|
+
options[:channel_mode] #use the channel mode from the options if specified
|
466
|
+
elsif fmt.number_of_channels <= 1
|
467
|
+
'm' # default to monoaural for 1 channel input
|
468
|
+
else
|
469
|
+
'j' # default to joint stereo for 2 channel input
|
470
|
+
end
|
471
|
+
|
472
|
+
# can directly set # of channels, 16 or less
|
473
|
+
# otherwise fallback on the mode, like mpegs
|
474
|
+
# or 2 if all else fails
|
475
|
+
channels = if (options[:channels].to_i > 0 )
|
476
|
+
[options[:channels].to_i, 16].min
|
477
|
+
else
|
478
|
+
(mode && (mode == 'm')) ? 1 : 2
|
479
|
+
end
|
480
|
+
|
481
|
+
kbps = if options[:per_channel_bit_rate]
|
482
|
+
options[:per_channel_bit_rate].to_i * channels
|
483
|
+
elsif options[:bit_rate]
|
484
|
+
options[:bit_rate].to_i
|
485
|
+
else
|
486
|
+
0
|
487
|
+
end
|
488
|
+
|
489
|
+
bit_rate = (MP3_BITRATES.include?(kbps) ? kbps : 96).to_s + "k"
|
490
|
+
|
491
|
+
command = "#{bin(:ffmpeg)} -nostats -loglevel warning -vn -i '#{original_path}' -acodec libvorbis -ac #{channels} -ar #{sample_rate} -ab #{bit_rate} -y -f ogg '#{result_path}'"
|
492
|
+
|
493
|
+
out, err = run_command(command)
|
494
|
+
|
495
|
+
check_local_file(result_path)
|
496
|
+
|
497
|
+
return true
|
498
|
+
end
|
499
|
+
|
500
|
+
# need start_at, ends_on
|
501
|
+
def create_wav_wrapped_mpeg(mpeg_path, result_path, options={})
|
502
|
+
options.to_options!
|
503
|
+
|
504
|
+
start_at = get_datetime_for_option(options[:start_at])
|
505
|
+
end_at = get_datetime_for_option(options[:end_at])
|
506
|
+
|
507
|
+
wav_wrapped_mpeg = NuWav::WaveFile.from_mpeg(mpeg_path)
|
508
|
+
cart = wav_wrapped_mpeg.chunks[:cart]
|
509
|
+
cart.title = options[:title] || File.basename(mpeg_path)
|
510
|
+
cart.artist = options[:artist]
|
511
|
+
cart.cut_id = options[:cut_id]
|
512
|
+
cart.producer_app_id = options[:producer_app_id] if options[:producer_app_id]
|
513
|
+
cart.start_date = start_at.strftime(PRSS_DATE_FORMAT)
|
514
|
+
cart.start_time = start_at.strftime(AES46_2002_TIME_FORMAT)
|
515
|
+
cart.end_date = end_at.strftime(PRSS_DATE_FORMAT)
|
516
|
+
cart.end_time = end_at.strftime(AES46_2002_TIME_FORMAT)
|
517
|
+
|
518
|
+
# pass in the options used by NuWav -
|
519
|
+
# :no_pad_byte - when true, will not add the pad byte to the data chunk
|
520
|
+
nu_wav_options = options.slice(:no_pad_byte)
|
521
|
+
wav_wrapped_mpeg.to_file(result_path, nu_wav_options)
|
522
|
+
|
523
|
+
check_local_file(result_path)
|
524
|
+
|
525
|
+
return true
|
526
|
+
end
|
527
|
+
|
528
|
+
def get_datetime_for_option(d)
|
529
|
+
return DateTime.now unless d
|
530
|
+
d.respond_to?(:strftime) ? d : DateTime.parse(d.to_s)
|
531
|
+
end
|
532
|
+
|
533
|
+
alias create_wav_wrapped_mp2 create_wav_wrapped_mpeg
|
534
|
+
alias create_wav_wrapped_mp3 create_wav_wrapped_mpeg
|
535
|
+
|
536
|
+
def add_cart_chunk_to_wav(wave_path, result_path, options={})
|
537
|
+
wave = NuWav::WaveFile.parse(wave_path)
|
538
|
+
unless wave.chunks[:cart]
|
539
|
+
cart = CartChunk.new
|
540
|
+
now = Time.now
|
541
|
+
today = Date.today
|
542
|
+
later = today << 12
|
543
|
+
|
544
|
+
cart.title = options[:title] || File.basename(wave_path)
|
545
|
+
cart.artist = options[:artist]
|
546
|
+
cart.cut_id = options[:cut_id]
|
547
|
+
|
548
|
+
cart.version = options[:version] || '0101'
|
549
|
+
cart.producer_app_id = options[:producer_app_id] || 'ContentDepot'
|
550
|
+
cart.producer_app_version = options[:producer_app_version] || '1.0'
|
551
|
+
cart.level_reference = options[:level_reference] || 0
|
552
|
+
cart.tag_text = options[:tag_text] || "\r\n"
|
553
|
+
cart.start_date = (options[:start_at] || today).strftime(PRSS_DATE_FORMAT)
|
554
|
+
cart.start_time = (options[:start_at] || now).strftime(AES46_2002_TIME_FORMAT)
|
555
|
+
cart.end_date = (options[:end_at] || later).strftime(PRSS_DATE_FORMAT)
|
556
|
+
cart.end_time = (options[:end_at] || now).strftime(AES46_2002_TIME_FORMAT)
|
557
|
+
|
558
|
+
wave.chunks[:cart] = cart
|
559
|
+
end
|
560
|
+
|
561
|
+
wave.to_file(result_path)
|
562
|
+
|
563
|
+
check_local_file(result_path)
|
564
|
+
|
565
|
+
return true
|
566
|
+
end
|
567
|
+
|
568
|
+
def slice_wav(wav_path, out_path, start, length)
|
569
|
+
check_local_file(wav_path)
|
570
|
+
|
571
|
+
wav_info = info_for_wav(wav_path)
|
572
|
+
logger.debug "slice_wav: wav_info:#{wav_info.inspect}"
|
573
|
+
|
574
|
+
command = "#{bin(:sox)} -t wav '#{wav_path}' -t wav '#{out_path}' trim #{start} #{length}"
|
575
|
+
out, err = run_command(command)
|
576
|
+
response = out + err
|
577
|
+
response.split("\n").each{ |out| raise("slice_wav: cut file error: '#{response}' on:\n #{command}") if out =~ SOX_ERROR_RE }
|
578
|
+
|
579
|
+
check_local_file(out_path)
|
580
|
+
out_path
|
581
|
+
end
|
582
|
+
|
583
|
+
def cut_wav(wav_path, out_path, length, fade=5)
|
584
|
+
logger.info "cut_wav: wav_path:#{wav_path}, length:#{length}, fade:#{fade}"
|
585
|
+
|
586
|
+
wav_info = info_for_wav(wav_path)
|
587
|
+
logger.debug "cut_wav: wav_info:#{wav_info.inspect}"
|
588
|
+
|
589
|
+
new_length = [wav_info[:length].to_i, length].min
|
590
|
+
fade_length = [wav_info[:length].to_i, fade].min
|
591
|
+
|
592
|
+
# find out if the wav file is stereo or mono as this needs to match the starting wav
|
593
|
+
channels = wav_info[:channel_mode] == 'Mono' ? 1 : 2
|
594
|
+
sample_rate = wav_info[:sample_rate]
|
595
|
+
|
596
|
+
command = "#{bin(:sox)} -t wav '#{wav_path}' -t raw -s -b 16 -c #{channels} - trim 0 #{new_length} | #{bin(:sox)} -t raw -r #{sample_rate} -s -b 16 -c #{channels} - -t wav '#{out_path}' fade h 0 #{new_length} #{fade_length}"
|
597
|
+
out, err = run_command(command)
|
598
|
+
response = out + err
|
599
|
+
response.split("\n").each{ |out| raise("cut_wav: cut file error: '#{response}' on:\n #{command}") if out =~ SOX_ERROR_RE }
|
600
|
+
end
|
601
|
+
|
602
|
+
def concat_wavs(in_paths, out_path)
|
603
|
+
first_wav_info = info_for_wav(in_paths.first)
|
604
|
+
channels = first_wav_info[:channel_mode] == 'Mono' ? 1 : 2
|
605
|
+
sample_rate = first_wav_info[:sample_rate]
|
606
|
+
tmp_files = []
|
607
|
+
|
608
|
+
concat_paths = in_paths.inject("") {|cmd, path|
|
609
|
+
concat_path = path
|
610
|
+
wav_info = info_for_wav(concat_path)
|
611
|
+
current_channels = wav_info[:channel_mode] == 'Mono' ? 1 : 2
|
612
|
+
current_sample_rate = wav_info[:sample_rate]
|
613
|
+
if current_channels != channels || current_sample_rate != sample_rate
|
614
|
+
|
615
|
+
concat_file = create_temp_file(path)
|
616
|
+
concat_file.close
|
617
|
+
|
618
|
+
concat_path = concat_file.path
|
619
|
+
command = "#{bin(:sox)} -t wav #{path} -t wav -c #{channels} -r #{sample_rate} '#{concat_path}'"
|
620
|
+
out, err = run_command(command)
|
621
|
+
response = out + err
|
622
|
+
response.split("\n").each{ |out| raise("concat_wavs: create temp file error: '#{response}' on:\n #{command}") if out =~ SOX_ERROR_RE }
|
623
|
+
tmp_files << concat_file
|
624
|
+
end
|
625
|
+
cmd << "-t wav '#{concat_path}' "
|
626
|
+
}
|
627
|
+
command = "#{bin(:sox)} #{concat_paths} -t wav '#{out_path}'"
|
628
|
+
out, err = run_command(command)
|
629
|
+
|
630
|
+
response = out + err
|
631
|
+
response.split("\n").each{ |out| raise("concat_wavs: concat files error: '#{response}' on:\n #{command}") if out =~ SOX_ERROR_RE }
|
632
|
+
ensure
|
633
|
+
tmp_files.each do |tf|
|
634
|
+
tf.close rescue nil
|
635
|
+
tf.unlink rescue nil
|
636
|
+
end
|
637
|
+
tmp_files = nil
|
638
|
+
end
|
639
|
+
|
640
|
+
def append_wav_to_wav(wav_path, append_wav_path, out_path, add_length, fade_length=5)
|
641
|
+
append_wav_info = info_for_wav(append_wav_path)
|
642
|
+
raise "append wav is not sufficiently long enough (#{append_wav_info[:length]}) to add length (#{add_length})" if append_wav_info[:length].to_i < add_length
|
643
|
+
|
644
|
+
append_length = [append_wav_info[:length].to_i, (add_length - 1)].min
|
645
|
+
|
646
|
+
append_fade_length = [append_wav_info[:length].to_i, fade_length].min
|
647
|
+
|
648
|
+
# find out if the wav file is stereo or mono as this needs to match the starting wav
|
649
|
+
wav_info = info_for_wav(wav_path)
|
650
|
+
channels = wav_info[:channel_mode] == 'Mono' ? 1 : 2
|
651
|
+
sample_rate = wav_info[:sample_rate]
|
652
|
+
append_file = nil
|
653
|
+
|
654
|
+
begin
|
655
|
+
append_file = create_temp_file(append_wav_path)
|
656
|
+
append_file.close
|
657
|
+
|
658
|
+
# create the wav to append
|
659
|
+
command = "#{bin(:sox)} -t wav '#{append_wav_path}' -t raw -s -b 16 -c #{channels} - trim 0 #{append_length} | #{bin(:sox)} -t raw -r #{sample_rate} -s -b 16 -c #{channels} - -t raw - fade h 0 #{append_length} #{append_fade_length} | #{bin(:sox)} -t raw -r #{sample_rate} -s -b 16 -c #{channels} - -t wav '#{append_file.path}' pad 1 0"
|
660
|
+
out, err = run_command(command)
|
661
|
+
response = out + err
|
662
|
+
response.split("\n").each{ |out| raise("append_wav_to_wav: create append file error: '#{response}' on:\n #{command}") if out =~ SOX_ERROR_RE }
|
663
|
+
|
664
|
+
# append the files to out_file
|
665
|
+
command = "#{bin(:sox)} -t wav '#{wav_path}' -t wav '#{append_file.path}' -t wav '#{out_path}'"
|
666
|
+
out, err = run_command(command)
|
667
|
+
response = out + err
|
668
|
+
response.split("\n").each{ |out| raise("append_wav_to_wav: create append file error: '#{response}' on:\n #{command}") if out =~ SOX_ERROR_RE }
|
669
|
+
ensure
|
670
|
+
append_file.close rescue nil
|
671
|
+
append_file.unlink rescue nil
|
672
|
+
end
|
673
|
+
|
674
|
+
return true
|
675
|
+
end
|
676
|
+
|
677
|
+
def append_mp3_to_wav(wav_path, mp3_path, out_path, add_length, fade_length=5)
|
678
|
+
# raise "append_mp3_to_wav: Can't find file to create mp3 preview of: #{mp3_path}" unless File.exist?(mp3_path)
|
679
|
+
|
680
|
+
mp3info = Mp3Info.new(mp3_path)
|
681
|
+
raise "mp3 is not sufficiently long enough (#{mp3info.length.to_i}) to add length (#{add_length})" if mp3info.length.to_i < add_length
|
682
|
+
append_length = [mp3info.length.to_i, (add_length - 1)].min
|
683
|
+
append_fade_length = [mp3info.length.to_i, fade_length].min
|
684
|
+
|
685
|
+
|
686
|
+
# find out if the wav file is stereo or mono as this meeds to match the wav from the mp3
|
687
|
+
wavinfo = info_for_wav(wav_path)
|
688
|
+
channels = wavinfo[:channel_mode] == 'Mono' ? 1 : 2
|
689
|
+
sample_rate = wavinfo[:sample_rate]
|
690
|
+
append_file = nil
|
691
|
+
|
692
|
+
begin
|
693
|
+
append_file = create_temp_file(mp3_path)
|
694
|
+
append_file.close
|
695
|
+
|
696
|
+
# create the mp3 to append
|
697
|
+
command = "#{bin(:madplay)} -q -o wave:- '#{mp3_path}' - | #{bin(:sox)} -t wav - -t raw -s -b 16 -c #{channels} - trim 0 #{append_length} | #{bin(:sox)} -t raw -r #{sample_rate} -s -b 16 -c #{channels} - -t wav - fade h 0 #{append_length} #{append_fade_length} | #{bin(:sox)} -t wav - -t wav '#{append_file.path}' pad 1 0"
|
698
|
+
out, err = run_command(command)
|
699
|
+
response = out + err
|
700
|
+
response.split("\n").each{ |out| raise("append_mp3_to_wav: create append file error: '#{response}' on:\n #{command}") if out =~ SOX_ERROR_RE }
|
701
|
+
|
702
|
+
# append the files to out_filew
|
703
|
+
command = "#{bin(:sox)} -t wav '#{wav_path}' -t wav '#{append_file.path}' -t wav '#{out_path}'"
|
704
|
+
out, err = run_command(command)
|
705
|
+
response = out + err
|
706
|
+
response.split("\n").each{ |out| raise("append_mp3_to_wav: create append file error: '#{response}' on:\n #{command}") if out =~ SOX_ERROR_RE }
|
707
|
+
ensure
|
708
|
+
append_file.close rescue nil
|
709
|
+
append_file.unlink rescue nil
|
710
|
+
end
|
711
|
+
|
712
|
+
return true
|
713
|
+
end
|
714
|
+
|
715
|
+
def normalize_wav(wav_path, out_path, level=-9)
|
716
|
+
logger.info "normalize_wav: wav_path:#{wav_path}, level:#{level}"
|
717
|
+
command = "#{bin(:sox)} -t wav '#{wav_path}' -t wav '#{out_path}' gain -n #{level.to_i}"
|
718
|
+
out, err = run_command(command)
|
719
|
+
response = out + err
|
720
|
+
response.split("\n").each{ |out| raise("normalize_wav: normalize audio file error: '#{response}' on:\n #{command}") if out =~ SOX_ERROR_RE }
|
721
|
+
end
|
722
|
+
|
723
|
+
def validate_mpeg(audio_file_path, options)
|
724
|
+
@errors = {}
|
725
|
+
|
726
|
+
options = HashWithIndifferentAccess.new(options)
|
727
|
+
|
728
|
+
info = mp3info_validation(audio_file_path, options)
|
729
|
+
|
730
|
+
# there are condtions where this spews output uncontrollably - so lose it for now: AK on 20080915
|
731
|
+
# e.g. mpck:/home/app/mediajoint/tmp/audio_monster/prxfile-66097_111955868219902-0:3366912:read error
|
732
|
+
# mpck_validation(audio_file_path, errors) if errors.size <= 0
|
733
|
+
|
734
|
+
# if the format seems legit, check the audio itself
|
735
|
+
mp3val_validation(audio_file_path, options)
|
736
|
+
|
737
|
+
return @errors, info
|
738
|
+
end
|
739
|
+
|
740
|
+
alias validate_mp2 validate_mpeg
|
741
|
+
alias validate_mp3 validate_mpeg
|
742
|
+
|
743
|
+
def create_temp_file(base_file_name=nil, bin_mode=true)
|
744
|
+
file_name = File.basename(base_file_name)
|
745
|
+
file_ext = File.extname(base_file_name)
|
746
|
+
FileUtils.mkdir_p(tmp_dir) unless File.exists?(tmp_dir)
|
747
|
+
tmp = Tempfile.new([file_name, file_ext], tmp_dir)
|
748
|
+
tmp.binmode if bin_mode
|
749
|
+
tmp
|
750
|
+
end
|
751
|
+
|
752
|
+
protected
|
753
|
+
|
754
|
+
# Validation methods
|
755
|
+
def add_error(attribute, message)
|
756
|
+
@errors ||= {}
|
757
|
+
@errors[attribute] = [] unless @errors[attribute]
|
758
|
+
@errors[attribute] << message
|
759
|
+
end
|
760
|
+
|
761
|
+
def valid_operator(op)
|
762
|
+
[">=", "<=", "==", "=", ">", "<"].include?(op) ? (op == "=" ? "==" : op) : ">="
|
763
|
+
end
|
764
|
+
|
765
|
+
def files_validation(audio_file_path, errors)
|
766
|
+
response = run_command("#{FILE} '#{audio_file_path}'", :echo_return=>false).chomp
|
767
|
+
logger.debug("'file' on #{audio_file_path}. Response: #{response}")
|
768
|
+
unless response =~ FILE_SUCCESS
|
769
|
+
response =~ /.*: /
|
770
|
+
add_error(:file, "is not a valid mp2 file, we think it's a '#{$'}'")
|
771
|
+
end
|
772
|
+
end
|
773
|
+
|
774
|
+
def mp3info_validation(audio_file_path, options)
|
775
|
+
info = nil
|
776
|
+
|
777
|
+
begin
|
778
|
+
info = Mp3Info.new(audio_file_path)
|
779
|
+
rescue Mp3InfoError => err
|
780
|
+
add_error(:file, "is not a valid mpeg audio file.")
|
781
|
+
return
|
782
|
+
end
|
783
|
+
|
784
|
+
if options[:version]
|
785
|
+
version = options[:version].to_i
|
786
|
+
mpeg_version = info.mpeg_version.to_i
|
787
|
+
add_error(:version, "must be mpeg version #{version}, but audio version is #{mpeg_version}") unless mpeg_version == version
|
788
|
+
end
|
789
|
+
|
790
|
+
if options[:layer]
|
791
|
+
layer = options[:layer].to_i
|
792
|
+
mpeg_layer = info.layer.to_i
|
793
|
+
add_error(:layer, "must be mpeg layer #{layer}, but audio layer is #{mpeg_layer}") unless mpeg_layer == layer
|
794
|
+
end
|
795
|
+
|
796
|
+
if options[:channel_mode]
|
797
|
+
cm_list = options[:channel_mode].to_a
|
798
|
+
add_error(:channel_mode, "channel mode must be one of (#{cm_list.to_sentence})") unless cm_list.include?(info.channel_mode)
|
799
|
+
end
|
800
|
+
|
801
|
+
if options[:channels]
|
802
|
+
channels = options[:channels].to_i
|
803
|
+
mpeg_channels = "Single Channel" == info.channel_mode ? 1 : 2
|
804
|
+
add_error(:channels, "must have channel count of #{channels}, but audio is #{mpeg_channels}") unless mpeg_channels == channels
|
805
|
+
end
|
806
|
+
|
807
|
+
# only certain rates are valid for different layer/versions, but don't add that right now
|
808
|
+
if options[:sample_rate]
|
809
|
+
sample_rate = 44100
|
810
|
+
op = ">="
|
811
|
+
mpeg_sample_rate = info.samplerate.to_i
|
812
|
+
if options[:sample_rate].match(' ')
|
813
|
+
op, sample_rate = options[:sample_rate].split(' ')
|
814
|
+
sample_rate = sample_rate.to_i
|
815
|
+
op = valid_operator(op)
|
816
|
+
else
|
817
|
+
sample_rate = options[:sample_rate].to_i
|
818
|
+
end
|
819
|
+
add_error(:sample_rate, "sample rate should be #{op} #{sample_rate}, but is #{mpeg_sample_rate}") unless eval("#{mpeg_sample_rate} #{op} #{sample_rate}")
|
820
|
+
end
|
821
|
+
|
822
|
+
if options[:bit_rate]
|
823
|
+
bit_rate = 128
|
824
|
+
op = ">="
|
825
|
+
mpeg_bit_rate = info.bitrate.to_i
|
826
|
+
if options[:bit_rate].match(' ')
|
827
|
+
op, bit_rate = options[:bit_rate].split(' ')
|
828
|
+
bit_rate = bit_rate.to_i
|
829
|
+
op = valid_operator(op)
|
830
|
+
else
|
831
|
+
bit_rate = options[:bit_rate].to_i
|
832
|
+
end
|
833
|
+
add_error(:bit_rate, "bit rate should be #{op} #{bit_rate}, but is #{mpeg_bit_rate}") unless eval("#{mpeg_bit_rate} #{op} #{bit_rate}")
|
834
|
+
end
|
835
|
+
|
836
|
+
if options[:per_channel_bit_rate]
|
837
|
+
per_channel_bit_rate = 128
|
838
|
+
op = ">="
|
839
|
+
mpeg_channels = "Single Channel" == info.channel_mode ? 1 : 2
|
840
|
+
mpeg_per_channel_bit_rate = info.bitrate.to_i / mpeg_channels
|
841
|
+
|
842
|
+
if options[:per_channel_bit_rate].match(' ')
|
843
|
+
op, per_channel_bit_rate = options[:per_channel_bit_rate].split(' ')
|
844
|
+
per_channel_bit_rate = per_channel_bit_rate.to_i
|
845
|
+
op = valid_operator(op)
|
846
|
+
else
|
847
|
+
per_channel_bit_rate = options[:per_channel_bit_rate].to_i
|
848
|
+
end
|
849
|
+
add_error(:per_channel_bit_rate, "per channel bit rate should be #{op} #{per_channel_bit_rate}, but is #{mpeg_per_channel_bit_rate}, and channels = #{mpeg_channels}") unless eval("#{mpeg_per_channel_bit_rate} #{op} #{per_channel_bit_rate}")
|
850
|
+
end
|
851
|
+
info_for_mpeg(audio_file_path, info)
|
852
|
+
end
|
853
|
+
|
854
|
+
def mp3val_validation(audio_file_path, options)
|
855
|
+
warning = false
|
856
|
+
error = false
|
857
|
+
out, err = run_command("#{bin(:mp3val)} -si '#{audio_file_path}'", :echo_return=>false)
|
858
|
+
lines = out.split("\n")
|
859
|
+
lines.each { |o|
|
860
|
+
if (o =~ MP3VAL_IGNORE_RE)
|
861
|
+
next
|
862
|
+
elsif (o =~ MP3VAL_WARNING_RE)
|
863
|
+
add_error(:file, "is not a valid mpeg file, there were serious warnings when validating the audio.") unless warning
|
864
|
+
warning = true
|
865
|
+
elsif (o =~ MP3VAL_ERROR_RE)
|
866
|
+
add_error(:file, "is not a valid mpeg file, there were errors when validating the audio.") unless error
|
867
|
+
error = true
|
868
|
+
else
|
869
|
+
next
|
870
|
+
end
|
871
|
+
}
|
872
|
+
end
|
873
|
+
|
874
|
+
# Pass the command to run, and a timeout
|
875
|
+
def run_command(command, options={})
|
876
|
+
timeout = options[:timeout] || 7200
|
877
|
+
|
878
|
+
# default to adding a nice 13 if nothing specified
|
879
|
+
nice = if options.key?(:nice)
|
880
|
+
(options[:nice] == 'n') ? '' : "nice -n #{options[:nice]} "
|
881
|
+
else
|
882
|
+
'nice -n 19 '
|
883
|
+
end
|
884
|
+
|
885
|
+
echo_return = (options.key?(:echo_return) && !options[:echo_return]) ? '' : '; echo $?'
|
886
|
+
|
887
|
+
cmd = "#{nice}#{command}#{echo_return}"
|
888
|
+
|
889
|
+
logger.info "run_command: #{cmd}"
|
890
|
+
begin
|
891
|
+
result = Timeout::timeout(timeout) {
|
892
|
+
Open3::popen3(cmd) do |i,o,e|
|
893
|
+
out_str = ""
|
894
|
+
err_str = ""
|
895
|
+
i.close # important!
|
896
|
+
o.sync = true
|
897
|
+
e.sync = true
|
898
|
+
o.each{|line|
|
899
|
+
out_str << line
|
900
|
+
line.chomp!
|
901
|
+
logger.debug "stdout: #{line}"
|
902
|
+
}
|
903
|
+
e.each { |line|
|
904
|
+
err_str << line
|
905
|
+
line.chomp!
|
906
|
+
logger.debug "stderr: #{line}"
|
907
|
+
}
|
908
|
+
return out_str, err_str
|
909
|
+
end
|
910
|
+
}
|
911
|
+
rescue Timeout::Error => toe
|
912
|
+
logger.error "run_command:Timeout Error - running command, took longer than #{timeout} seconds to execute: '#{cmd}'"
|
913
|
+
raise toe
|
914
|
+
end
|
915
|
+
end
|
916
|
+
|
917
|
+
def mpck_validation(audio_file_path, options)
|
918
|
+
errors= []
|
919
|
+
# validate using mpck
|
920
|
+
response = run_command("nice -n 19 #{bin(:mpck)} #{audio_file_path}")
|
921
|
+
response.split("\n").each { |o|
|
922
|
+
if ((o =~ MPCK_ERROR_RE) && !(o =~ MPCK_IGNORE_RE))
|
923
|
+
errors << "is not a valid mp2 file. The file is bad according to the 'mpck' audio check."
|
924
|
+
end
|
925
|
+
}
|
926
|
+
|
927
|
+
errors
|
928
|
+
end
|
929
|
+
|
930
|
+
def method_missing(name, *args, &block)
|
931
|
+
if name.to_s.starts_with?('encode_wav_pcm_from_')
|
932
|
+
decode_audio(*args)
|
933
|
+
elsif name.to_s.starts_with?('info_for_')
|
934
|
+
info_for_audio(*args)
|
935
|
+
else
|
936
|
+
super
|
937
|
+
end
|
938
|
+
end
|
939
|
+
|
940
|
+
protected
|
941
|
+
|
942
|
+
def check_local_file(file_path)
|
943
|
+
raise "File missing or 0 length: #{file_path}" unless (File.size?(file_path).to_i > 0)
|
944
|
+
end
|
945
|
+
|
946
|
+
def get_lame_channel_mode(channel_mode)
|
947
|
+
["Stereo", "JStereo"].include?(channel_mode) ? "j" : "m"
|
948
|
+
end
|
949
|
+
|
950
|
+
end
|
951
|
+
end
|