QutBioacousticsResearchGroup/bioacoustic-workbench

View on GitHub
lib/modules/audio_sox.rb

Summary

Maintainability
A
3 hrs
Test Coverage
require './lib/modules/logger'

module AudioSox
include OS, Logging
  @sox_path = if OS.windows? then "./vendor/bin/sox/windows/sox.exe" else "sox" end

  # public methods
  public

  def self.info_sox(source)
    result = {
        :info => { :sox => {} },
        :error => { :sox => {} }
    }

    sox_arguments_info = "--info"
    sox_command = "#@sox_path #{sox_arguments_info} \"#{source}\"" # commands to get info from audio file
    sox_stdout_str, sox_stderr_str, sox_status = Open3.capture3(sox_command) # run the commands and wait for the result

    Logging::logger.debug  "sox info return status #{sox_status.exitstatus}. Command: #{sox_command}"

    if sox_status.exitstatus == 0
      # sox std out contains info (separate on first colon(:))
      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}"
      result[:error][:sox][:stderror] = sox_stderr_str
    end

    result
  end

  def self.modify_sox(source, target, modify_parameters = {})
    raise ArgumentError, "Source is not a mp3 or wav file: #{File.basename(source)}" unless source.match(/\.mp3|\.wav$/)
    raise ArgumentError, "Target is not a mp3 or wav file: : #{File.basename(target)}" unless target.match(/\.mp3|\.wav$/)
    raise ArgumentError, "Source does not exist: #{File.basename(source)}" unless File.exists? source
    raise ArgumentError, "Target exists: #{File.basename(target)}" unless !File.exists? target

    result = {}

    # order matters!
    arguments = ''

    # start and end offset
    if modify_parameters.include? :start_offset
      start_offset_formatted = Time.at(modify_parameters[:start_offset]).utc.strftime('%H:%M:%S.%2N')
      arguments += "trim =#{start_offset_formatted}"
    end

    if modify_parameters.include? :end_offset
      end_offset_formatted = Time.at(modify_parameters[:end_offset]).utc.strftime('%H:%M:%S.%2N')
      if modify_parameters.include? :start_offset
        arguments += " =#{end_offset_formatted}"
      else
        # if start offset was not included, include audio from the start of the file.
        arguments += "trim 0 #{end_offset_formatted}"
      end
    end

    # resample quality: medium (m), high (h), veryhigh (v)
    if modify_parameters.include? :sample_rate
      arguments += " rate -v -s -a #{modify_parameters[:sample_rate]}"
    end

=begin
      Where a range of channels is specified, the channel numbers to the left and right of the hyphen are
      optional and default to 1 and to the number of input channels respectively. Thus
      sox input.wav output.wav remix −
      performs a mix-down of all input channels to mono.
=end
    if modify_parameters.include? :channel
      # help... not sure how to do this
      # HACK: WARNING this will always mix down to mono
      arguments += ' remix - '
    end

    sox_command = "#@sox_path -V4 \"#{source}\" \"#{target}\" #{arguments}" # commands to get info from audio file
    sox_stdout_str, sox_stderr_str, sox_status = Open3.capture3(sox_command) # run the commands and wait for the result

    Logging::logger.debug  "Sox command #{sox_command}"

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

    result
  end

end