Skip to content

Commit

Permalink
Audio conversion and segmenting now pretty much done, and tested.
Browse files Browse the repository at this point in the history
modified:   lib/modules/audio.rb
modified:   lib/modules/audio_ffmpeg.rb
modified:   lib/modules/audio_mp3splt.rb
modified:   lib/modules/audio_sox.rb
modified:   lib/modules/audio_wavpack.rb
modified:   lib/modules/file_cacher.rb
renamed:    spec/models/module_audio_test.rb -> spec/models/module_audio_spec.rb
modified:   spec/spec_helper.rb
modified:   spec/support/devise.rb
modified:   spec/support/helpers.rb
new file:   test/fixtures/audio/06Sibylla.asf
new file:   test/fixtures/audio/06Sibylla.wma
  • Loading branch information
Mark Cottman-Fields committed Jan 17, 2013
1 parent 07c8b4c commit f16f7cb
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 47 deletions.
41 changes: 23 additions & 18 deletions lib/modules/audio.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require 'open3'
require './lib/modules/string'

# the audio
module Audio
include AudioSox, AudioMp3splt, AudioWavpack, AudioFfmpeg

Expand Down Expand Up @@ -34,38 +35,42 @@ def self.info(source)
def self.modify(source, target, modify_parameters)
raise ArgumentError, "Source does not exist: #{File.basename(source)}" unless File.exists? source
raise ArgumentError, "Target exists: #{File.basename(target)}" unless !File.exists? target
raise ArgumentError "Source and Target are the same file." unless source != target

if source.match(/\.wv$/) && target.match(/\.wav$/)
raise ArgumentError, "Source must be a wavpack file: #{File.basename(source)}." unless source.match(/\.wv$/)
raise ArgumentError, "Target must be a wav file: #{File.basename(target)}." unless target.match(/\.wav$/)
raise ArgumentError, "Target must be a wav file, given #{modify_parameters[:format]}." unless modify_parameters[:format] == 'wav'

AudioWavpack::modify_wavpack(source, target, modify_parameters)

elsif source.match(/\.wv$/) && target.match(/\.mp3$/)
raise ArgumentError "Source and Target are the same file: #{File.basename(target)}" unless source != target

if source.match(/\.wv$/) && (modify_parameters.include?(:start_offset) || modify_parameters.include?(:end_offset))
# convert to wav, then to target file (might need to change channels or sample rate or format)
# put the wav file in a temp location
temp_wav_file = temp_file('wav')
AudioWavpack::modify_wavpack(source, temp_wav_file, modify_parameters)

# remove start and end offset from modify_parameters (otherwise it will be done again!)
wav_to_mp3_modify_params = modify_parameters.clone
wav_to_mp3_modify_params.delete :start_offset
wav_to_mp3_modify_params.delete :end_offset
wav_to_modify_params = modify_parameters.clone
wav_to_modify_params.delete :start_offset
wav_to_modify_params.delete :end_offset

AudioSox::modify_sox(temp_wav_file, target, wav_to_mp3_modify_params)
Audio::modify(temp_wav_file, target, wav_to_modify_params)

elsif source.match(/\.wav$/)&& target.match(/\.mp3$/)
File.delete temp_wav_file

AudioSox::modify_sox(source, target, modify_parameters)
elsif source.match(/\.mp3$/) && (modify_parameters.include?(:start_offset) || modify_parameters.include?(:end_offset))
# segment the mp3, then to target file (might need to change channels or sample rate or format)
# put the mp3 file in a temp location
temp_wav_file = temp_file('mp3')
AudioMp3splt::modify_mp3splt(source, temp_wav_file, modify_parameters)

elsif source.match(/\.mp3$/) && target.match(/\.mp3$/)
# remove start and end offset from modify_parameters (otherwise it will be done again!)
wav_to_modify_params = modify_parameters.clone
wav_to_modify_params.delete :start_offset
wav_to_modify_params.delete :end_offset

Audio::modify(temp_wav_file, target, wav_to_modify_params)

File.delete temp_wav_file

elsif source.match(/\.wav$/)&& target.match(/\.mp3$/)
AudioSox::modify_sox(source, target, modify_parameters)

else

AudioFfmpeg::modify_ffmpeg(source, target, modify_parameters)

end
Expand Down
67 changes: 54 additions & 13 deletions lib/modules/audio_ffmpeg.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,25 @@ module AudioFfmpeg
:codec_long_name => 'MP3 (MPEG audio layer 3)',
:codec_type => 'audio',
:format_long_name => 'MP2/3 (MPEG audio layer 2/3)'
},
:webma => {
:codec_name => 'vorbis',
:codec_long_name => 'Vorbis',
:codec_type => 'audio',
:format_long_name => 'Matroska / WebM'
},
:oga => {
:codec_name => 'vorbis',
:codec_long_name => 'Vorbis',
:codec_type => 'audio',
:format_long_name => 'Ogg'
},
:asf => {
:codec_name => 'wmav2',
:codec_long_name => 'Windows Media Audio 2',
:codec_type => 'audio',
:format_long_name => 'ASF (Advanced / Active Streaming Format)'
}
# webm, webma
# ogg, oga
}

# public methods
Expand Down Expand Up @@ -55,14 +71,14 @@ def self.info_ffmpeg(source)
def self.modify_ffmpeg(source, target, modify_parameters = {})
# ffmpeg is the catch-all, so it will do anything specified in modify_parameters.

raise ArgumentError, "Source does not exist: #{File.basename(source)}" unless File.exists? source
raise ArgumentError, "Target exists: #{File.basename(target)}" unless !File.exists? target
raise ArgumentError, "Source is a wavpack file, use wavpack to convert to .wav first instead: #{File.basename(source)}" if source.match(/\.wv$/)

result = {}
raise ArgumentError, "Source does not exist: #{File.basename(source)}" unless File.exists? source
raise ArgumentError, "Target exists: #{File.basename(target)}" if File.exists? target
raise ArgumentError "Source and Target are the same file: #{File.basename(target)}" unless source != target

arguments = ''


# start offset
# -ss Seek to given time position in seconds. hh:mm:ss[.xxx] syntax is also supported.
if modify_parameters.include? :start_offset
Expand Down Expand Up @@ -95,17 +111,30 @@ def self.modify_ffmpeg(source, target, modify_parameters = {})
# set the right codec if we know it
# -acodec Force audio codec to codec. Use the copy special value to specify that the raw codec data must be copied as is.
# output file. extension used to determine filetype.
ext_to_copy_to = ''
vorbis_codec = 'libvorbis -aq 80' # ogg container vorbis encoder at quality level of 80
codec = ''
case File.extname(target).upcase!
case File.extname(target).upcase!.reverse.chomp('.').reverse
when 'WAV'
codec = "pcm_s16le"; # pcm signed 16-bit little endian - compatible with CDDA
codec = "pcm_s16le" # pcm signed 16-bit little endian - compatible with CDDA
when 'MP3'
codec = "libmp3lame"; # needs to be specified, different codecs for encoding and decoding
when 'OGG', 'OGA', 'WEBMA', 'WEBMA'
codec = "libvorbis -aq 80"; # ogg container vorbis encoder at quality level of 80
codec = "libmp3lame" # needs to be specified, different codecs for encoding and decoding
when 'OGG'
codec = vorbis_codec
when 'OGA'
codec = vorbis_codec
target = target.chomp(File.extname(target))+'.ogg'
ext_to_copy_to = 'oga'
when 'WEBM'
codec = vorbis_codec
when 'WEBMA'
codec = vorbis_codec
target = target.chomp(File.extname(target))+'.webm'
ext_to_copy_to = 'webma'
else
codec = 'copy'
end
arguments += " -acodec #{codec}"

if modify_parameters.include? :channel
# help... not sure how to do this
Expand All @@ -115,15 +144,27 @@ def self.modify_ffmpeg(source, target, modify_parameters = {})
ffmpeg_command = "#@ffmpeg_path -i \"#{source}\" #{arguments} \"#{target}\""
ffmpeg_stdout_str, ffmpeg_stderr_str, ffmpeg_status = Open3.capture3(ffmpeg_command)


Logging::logger.debug "Ffmpeg command #{ffmpeg_command}"

if ffmpeg_status.exitstatus != 0 || !File.exists?(target)
Logging::logger.error "Ffmpeg exited with an error: #{ffmpeg_stderr_str}"
end

result
if ext_to_copy_to
new_target = target.chomp(File.extname(target))+'.'+ext_to_copy_to
FileUtils.copy target, new_target
end

{
info: {
ffmpeg: {
command: ffmpeg_command,
source: source,
target: target,
parameters: modify_parameters
}
}
}
end

# returns the duration in seconds (and fractions if present)
Expand Down
22 changes: 14 additions & 8 deletions lib/modules/audio_mp3splt.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,7 @@ def self.modify_mp3splt(source, target, modify_parameters = {})
raise ArgumentError, "Target is not a mp3 file: : #{File.basename(target)}" unless target.match(/\.mp3$/)
raise ArgumentError, "Source does not exist: #{File.basename(source)}" unless File.exists? source
raise ArgumentError, "Target exists: #{File.basename(target)}" if File.exists? target

result = {
:info => { :wavpack => {} },
:error => { :wavpack => {} }
}
raise ArgumentError "Source and Target are the same file: #{File.basename(target)}" unless source != target

# mp3splt needs the file extension removed
target_dirname = File.dirname target
Expand All @@ -44,15 +40,25 @@ def self.modify_mp3splt(source, target, modify_parameters = {})
arguments += ' EOF '
end

mp3splt_command = "#@mp3splt_path #{arguments}" # commands to get info from audio file
mp3splt_stdout_str, mp3splt_stderr_str, mp3splt_status = Open3.capture3(mp3splt_command) # run the commands and wait for the result
mp3splt_command = "#{File.basename(@mp3splt_path)} #{arguments}" # commands to get info from audio file ( -D )
working_dir = File.dirname(File.expand_path(@mp3splt_path, Rails.root))
mp3splt_stdout_str, mp3splt_stderr_str, mp3splt_status = Open3.capture3(mp3splt_command, :chdir => working_dir) # run the commands and wait for the result

Logging::logger.debug"mp3splt info return status #{mp3splt_status.exitstatus}. Command: #{mp3splt_command}"

if mp3splt_status.exitstatus != 0 || !File.exists?(target)
Logging::logger.error "Mp3splt exited with an error: #{mp3splt_stderr_str}"
end

result
{
info: {
mp3splt: {
command: mp3splt_command,
source: source,
target: target,
parameters: modify_parameters
}
}
}
end
end
9 changes: 8 additions & 1 deletion lib/modules/audio_sox.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@ def self.info_sox(source)

if sox_status.exitstatus == 0
# sox std out contains info (separate on first colon(:))
sox_stdout_str.strip.split(/\r?\n|\r/).each { |line| result[:info][:sox][ line[0,line.index(':')].strip] = line[line.index(':')+1,line.length].strip }
sox_stdout_str.strip.split(/\r?\n|\r/).each { |line|
if line.include?(':')
colon_index = line.index(':')
new_value = line[colon_index+1,line.length].strip
new_key = line[0,colon_index].strip
result[:info][:sox][new_key] = new_value
end
}
# sox_stderr_str is empty
else
Logging::logger.error "Sox info error. Return status #{sox_status.exitstatus}. Command: #{sox_command}"
Expand Down
18 changes: 11 additions & 7 deletions lib/modules/audio_wavpack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,7 @@ def self.info_wavpack(source)
def self.modify_wavpack(source, target, modify_parameters = {})
raise ArgumentError, "Source is not a wavpack file: #{File.basename(source)}" unless source.match(/\.wv$/)
raise ArgumentError, "Target is not a wav file: : #{File.basename(target)}" unless target.match(/\.wav$/)

result = {
:info => { :wavpack => {} },
:error => { :wavpack => {} }
}
raise ArgumentError "Source and Target are the same file: #{File.basename(target)}" unless source != target

if File.exists? target
return result
Expand Down Expand Up @@ -82,7 +78,15 @@ def self.modify_wavpack(source, target, modify_parameters = {})
Logging::logger.error "Wvunpack command #{wvunpack_command} exited with an error: #{wvunpack_stderr_str}"
end

result
{
info: {
wavpack: {
command: wvunpack_command,
source: source,
target: target,
parameters: modify_parameters
}
}
}
end

end
2 changes: 2 additions & 0 deletions lib/modules/file_cacher.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
require 'digest'
require 'digest/md5'

# This class know about which audio tools to use to convert/segment audio.
# this way the intermediate files can be put in the right spots, rather than temp files.
module FileCacher
include Cache, Spectrogram, Audio, Exceptions

Expand Down

0 comments on commit f16f7cb

Please sign in to comment.