require 'json' require 'resque' require 'resque-retry' require 'net/http' require 'digest/md5' module JamRuby # executes a mix of tracks, creating a final output mix class QuickMixer @queue = :quick_mixer @@log = Logging.logger[QuickMixer] attr_accessor :quick_mix_id, :quick_mix, :output_filename, :error_out_filename, :postback_mp3_url, :error_reason, :error_detail def self.perform(quick_mix_id, postback_mp3_url) JamWebEventMachine.run_wait_stop do audiomixer = QuickMixer.new audiomixer.postback_mp3_url = postback_mp3_url audiomixer.quick_mix_id = quick_mix_id audiomixer.run end end def self.find_jobs_needing_retry &blk QuickMix.find_each(:conditions => "completed = FALSE AND (should_retry = TRUE OR (started_at IS NOT NULL AND NOW() - started_at > '1 hour'::INTERVAL))", :batch_size => 100) do |mix| blk.call(mix) end end def self.queue_jobs_needing_retry find_jobs_needing_retry do |mix| mix.enqueue end end def initialize @s3_manager = S3Manager.new(APP_CONFIG.aws_bucket, APP_CONFIG.aws_access_key_id, APP_CONFIG.aws_secret_access_key) end def run @@log.info("quickmixer job starting. quick_mix_id #{quick_mix_id}") @quick_mix = QuickMix.find(quick_mix_id) begin # bailout check if @quick_mix.completed @@log.debug("quick mix is already completed. bailing") return end fetch_audio_files create_mp3(@input_ogg_filename, @output_mp3_filename) postback post_success(@quick_mix) @@log.info("audiomixer job successful. mix_id #{quick_mix_id}") rescue Exception => e puts "EEOUOU #{e}" post_error(@quick_mix, e) raise end end def postback @@log.debug("posting mp3 mix to #{@postback_mp3_url}") uri = URI.parse(@postback_mp3_url) http = Net::HTTP.new(uri.host, uri.port) request = Net::HTTP::Put.new(uri.request_uri) response = nil File.open(@output_mp3_filename,"r") do |f| request.body_stream=f request["Content-Type"] = "audio/mpeg" request.add_field('Content-Length', File.size(@output_mp3_filename)) response = http.request(request) end response_code = response.code.to_i unless response_code >= 200 && response_code <= 299 @error_reason = "postback-mp3-mix-to-s3" raise "unable to put to url: #{@postback_mp3_url}, status: #{response.code}, body: #{response.body}" end end def post_success(mix) mp3_length = File.size(@output_mp3_filename) mp3_md5 = Digest::MD5.new File.open(@output_mp3_filename, 'rb').each {|line| mp3_md5.update(line)} mix.finish(mp3_length, mp3_md5.to_s) end def post_error(mix, e) begin # if error_reason is null, assume this is an unhandled error unless @error_reason @error_reason = "unhandled-job-exception" @error_detail = e.to_s end mix.errored(error_reason, error_detail) rescue Exception => e @@log.error "unable to post back to the database the error #{e}" end end def fetch_audio_files @input_ogg_filename = Dir::Tmpname.make_tmpname( ["#{Dir.tmpdir}/quick_mixer_#{@quick_mix.id}}", '.ogg'], nil) @output_mp3_filename = Dir::Tmpname.make_tmpname( ["#{Dir.tmpdir}/quick_mixer_#{@quick_mix.id}}", '.mp3'], nil) @s3_manager.download(@quick_mix[:ogg_url], @input_ogg_filename) end private def create_mp3(input_ogg_filename, output_mp3_filename) ffmpeg_cmd = "#{APP_CONFIG.ffmpeg_path} -i \"#{input_ogg_filename}\" -ab 128k -metadata JamRecordingId=#{@quick_mix.recording_id} -metadata JamQuickMixId=#{@quick_mix_id} -metadata JamType=QuickMix \"#{output_mp3_filename}\"" system(ffmpeg_cmd) unless $? == 0 @error_reason = 'ffmpeg-failed' @error_detail = $?.to_s error_msg = "ffmpeg failed status=#{$?} error_reason=#{@error_reason} error_detail=#{@error_detail}" @@log.info(error_msg) raise error_msg end end end end