772 lines
22 KiB
Ruby
772 lines
22 KiB
Ruby
require 'json'
|
|
require 'resque'
|
|
require 'resque-retry'
|
|
require 'net/http'
|
|
require 'digest/md5'
|
|
|
|
module JamRuby
|
|
class JamTrackMixdownPackager
|
|
extend JamRuby::ResqueStats
|
|
|
|
include JamRuby::S3ManagerMixin
|
|
|
|
TAP_IN_PADDING = 2
|
|
|
|
MAX_PAN = 90
|
|
MIN_PAN = -90
|
|
KNOCK_SECONDS = 0.035
|
|
|
|
attr_accessor :mixdown_package_id, :settings, :mixdown_package, :mixdown, :step
|
|
@queue = :jam_track_mixdown_packager
|
|
|
|
def log
|
|
@log || Logging.logger[JamTrackMixdownPackager]
|
|
end
|
|
|
|
def self.perform(mixdown_package_id, bitrate=48)
|
|
jam_track_builder = JamTrackMixdownPackager.new()
|
|
jam_track_builder.mixdown_package_id = mixdown_package_id
|
|
jam_track_builder.run
|
|
end
|
|
|
|
def compute_steps
|
|
@step = 0
|
|
number_downloads = @track_settings.length
|
|
number_volume_adjustments = (@track_settings.select { |track| should_alter_volume? track }).length
|
|
|
|
pitch_shift_steps = @mixdown.will_pitch_shift? ? 1 : 0
|
|
mix_steps = 1
|
|
package_steps = 1
|
|
|
|
number_downloads + number_volume_adjustments + pitch_shift_steps + mix_steps + package_steps
|
|
end
|
|
|
|
def run
|
|
begin
|
|
log.info("Mixdown job starting. mixdown_packager_id #{mixdown_package_id}")
|
|
begin
|
|
@mixdown_package = JamTrackMixdownPackage.find(mixdown_package_id)
|
|
|
|
|
|
# bailout check
|
|
if @mixdown_package.signed?
|
|
log.debug("package is already signed. bailing")
|
|
return
|
|
end
|
|
|
|
@mixdown = @mixdown_package.jam_track_mixdown
|
|
@settings = JSON.parse(@mixdown.settings)
|
|
|
|
process_jmep
|
|
|
|
track_settings
|
|
|
|
# compute the step count
|
|
total_steps = compute_steps
|
|
|
|
# track that it's started ( and avoid db validations )
|
|
signing_started_at = Time.now
|
|
last_step_at = Time.now
|
|
#JamTrackMixdownPackage.where(:id => @mixdown_package.id).update_all(:signing_started_at => signing_started_at, :should_retry => false, packaging_steps: total_steps, current_packaging_step: 0, last_step_at: last_step_at, :signing => true)
|
|
|
|
# because we are skipping 'after_save', we have to keep the model current for the notification. A bit ugly...
|
|
|
|
@mixdown_package.current_packaging_step = 0
|
|
@mixdown_package.packaging_steps = total_steps
|
|
@mixdown_package.signing_started_at = signing_started_at
|
|
@mixdown_package.signing = true
|
|
@mixdown_package.should_retry = false
|
|
@mixdown_package.last_step_at = last_step_at
|
|
@mixdown_package.queued = false
|
|
@mixdown_package.save
|
|
|
|
SubscriptionMessage.mixdown_signing_job_change(@mixdown_package)
|
|
|
|
package
|
|
|
|
log.info "Signed mixdown package to #{@mixdown_package[:url]}"
|
|
|
|
rescue Exception => e
|
|
# record the error in the database
|
|
post_error(e)
|
|
|
|
#SubscriptionMessage.mixdown_signing_job_change(@mixdown_package)
|
|
# and let the job fail, alerting ops too
|
|
raise
|
|
end
|
|
end
|
|
end
|
|
|
|
def should_alter_volume? track
|
|
|
|
# short cut is possible if vol = 1.0 and pan = 0
|
|
vol = track[:vol]
|
|
pan = track[:pan]
|
|
|
|
vol != 1.0 || pan != 0
|
|
end
|
|
|
|
def process_jmep
|
|
@start_points = []
|
|
@initial_padding = 0.0
|
|
@tap_in_initial_silence = 0
|
|
|
|
speed = @settings['speed'] || 0
|
|
|
|
@speed_factor = 1.0 + (-speed.to_f / 100.0)
|
|
@inverse_speed_factor = 1 - (-speed.to_f / 100)
|
|
|
|
log.info("speed factor #{@speed_factor}")
|
|
|
|
jmep = @mixdown.jam_track.jmep_json
|
|
if jmep
|
|
jmep = JSON.parse(jmep)
|
|
end
|
|
|
|
if jmep.nil?
|
|
log.debug("no jmep")
|
|
return
|
|
end
|
|
|
|
events = jmep["Events"]
|
|
|
|
return if events.nil? || events.length == 0
|
|
|
|
metronome = nil
|
|
events.each do |event|
|
|
if event.has_key?("metronome")
|
|
metronome = event["metronome"]
|
|
break
|
|
end
|
|
end
|
|
|
|
if metronome.nil? || metronome.length == 0
|
|
log.debug("no metronome events for jmep", jmep)
|
|
return
|
|
end
|
|
|
|
@start_points = metronome.select { |x| puts x.inspect; x["action"] == "start" }
|
|
|
|
log.debug("found #{@start_points.length} metronome start points")
|
|
|
|
start_point = @start_points[0]
|
|
|
|
if start_point
|
|
start_time = parse_time(start_point["ts"])
|
|
|
|
if start_time < 2.0
|
|
padding = start_time - 2.0
|
|
@initial_padding = padding.abs
|
|
@initial_tap_in = start_time
|
|
end
|
|
end
|
|
|
|
if @speed_factor != 1.0
|
|
metronome.length.times do |count|
|
|
|
|
# we expect to find metronome start/stop grouped
|
|
if count % 2 == 0
|
|
|
|
start = metronome[count]
|
|
stop = metronome[count + 1]
|
|
|
|
if start["action"] != "start" || stop["action"] != "stop"
|
|
# bail out
|
|
log.error("found de-coupled metronome events #{start.to_json} | #{stop.to_json}")
|
|
next
|
|
end
|
|
|
|
bpm = start["bpm"].to_f
|
|
stop_time = parse_time(stop['ts'])
|
|
ticks = stop['ticks'].to_i
|
|
|
|
|
|
new_bpm = bpm * @inverse_speed_factor
|
|
new_stop_time = stop_time * @speed_factor
|
|
new_start_time = new_stop_time - (60.0/new_bpm * ticks)
|
|
|
|
log.info("original bpm:#{bpm} start: #{parse_time(start["ts"])} stop: #{stop_time}")
|
|
log.info("updated bpm:#{new_bpm} start: #{new_start_time} stop: #{new_stop_time}")
|
|
|
|
stop["ts"] = new_stop_time
|
|
start["ts"] = new_start_time
|
|
start["bpm"] = new_bpm
|
|
stop["bpm"] = new_bpm
|
|
|
|
@tap_in_initial_silence = (@initial_tap_in + @initial_padding) * @speed_factor
|
|
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
@start_points = metronome.select { |x| puts x.inspect; x["action"] == "start" }
|
|
|
|
end
|
|
|
|
# format like: "-0:00:02:820"
|
|
def parse_time(ts)
|
|
|
|
if ts.is_a?(Float)
|
|
return ts
|
|
end
|
|
|
|
time = 0.0
|
|
negative = false
|
|
|
|
if ts.start_with?('-')
|
|
negative = true
|
|
end
|
|
|
|
# parse time_format
|
|
bits = ts.split(':').reverse
|
|
|
|
bit_position = 0
|
|
bits.each do |bit|
|
|
if bit_position == 0
|
|
# milliseconds
|
|
milliseconds = bit.to_f
|
|
time += milliseconds/1000
|
|
elsif bit_position == 1
|
|
# seconds
|
|
time += bit.to_f
|
|
elsif bit_position == 2
|
|
# minutes
|
|
time += 60 * bit.to_f
|
|
elsif bit_position == 3
|
|
# hours
|
|
# not bothering
|
|
end
|
|
|
|
bit_position += 1
|
|
end
|
|
|
|
if negative
|
|
time = 0.0 - time
|
|
end
|
|
|
|
time
|
|
end
|
|
|
|
def path_to_resources
|
|
File.join(File.dirname(File.expand_path(__FILE__)), '../../../lib/jam_ruby/app/assets/sounds')
|
|
end
|
|
|
|
def knock_file
|
|
if long_sample_rate == 44100
|
|
knock = File.join(path_to_resources, 'knock44.wav')
|
|
else
|
|
knock = File.join(path_to_resources, 'knock48.wav')
|
|
end
|
|
knock
|
|
end
|
|
|
|
def create_silence(tmp_dir, segment_count, duration)
|
|
file = File.join(tmp_dir, "#{segment_count}.wav")
|
|
|
|
# -c 2 means stereo
|
|
cmd("sox -n -r #{long_sample_rate} -c 2 #{file} trim 0.0 #{duration}", "silence")
|
|
|
|
file
|
|
end
|
|
|
|
def create_tapin_track(tmp_dir)
|
|
|
|
return nil if @start_points.length == 0
|
|
|
|
segment_count = 0
|
|
|
|
|
|
#initial_silence = @initial_tap_in + @initial_padding
|
|
|
|
initial_silence = @tap_in_initial_silence
|
|
|
|
#log.info("tapin data: initial_tap_in: #{@initial_tap_in}, initial_padding: #{@initial_padding}, initial_silence: #{initial_silence}")
|
|
|
|
time_points = []
|
|
files = []
|
|
if initial_silence > 0
|
|
|
|
files << create_silence(tmp_dir, segment_count, initial_silence)
|
|
|
|
time_points << {type: :silence, ts: initial_silence}
|
|
segment_count += 1
|
|
end
|
|
|
|
|
|
time_cursor = nil
|
|
@start_points.each do |start_point|
|
|
tap_time = parse_time(start_point["ts"])
|
|
if !time_cursor.nil?
|
|
between_silence = tap_time - time_cursor
|
|
files << create_silence(tmp_dir, segment_count, between_silence)
|
|
time_points << {type: :silence, ts: between_silence}
|
|
end
|
|
time_cursor = tap_time
|
|
bpm = start_point["bpm"].to_f
|
|
|
|
tick_silence = 60.0/bpm - KNOCK_SECONDS
|
|
|
|
ticks = start_point["ticks"].to_i
|
|
|
|
ticks.times do |tick|
|
|
files << knock_file
|
|
files << create_silence(tmp_dir, segment_count, tick_silence)
|
|
time_points << {type: :knock, ts: KNOCK_SECONDS}
|
|
time_points << {type: :silence, ts: tick_silence}
|
|
time_cursor + 60.0/bpm
|
|
segment_count += 1
|
|
end
|
|
end
|
|
|
|
log.info("time points for tap-in: #{time_points.inspect}")
|
|
# do we need to pad with time? not sure
|
|
|
|
sequence_cmd = "sox "
|
|
files.each do |file|
|
|
sequence_cmd << "\"#{file}\" "
|
|
end
|
|
|
|
count_in = File.join(tmp_dir, "count-in.wav")
|
|
sequence_cmd << "\"#{count_in}\""
|
|
|
|
cmd(sequence_cmd, "count_in")
|
|
@count_in_file = count_in
|
|
count_in
|
|
end
|
|
|
|
# creates a list of tracks to actually mix
|
|
def track_settings
|
|
altered_tracks = @settings["tracks"] || []
|
|
|
|
@track_settings = []
|
|
|
|
#void slider2Pan(int i, float *f);
|
|
|
|
stems = @mixdown.jam_track.stem_tracks
|
|
@track_count = stems.length
|
|
|
|
@include_count_in = @settings["count-in"] && @start_points.length > 0 && @mixdown_package.encrypt_type.nil?
|
|
|
|
# temp
|
|
# @include_count_in = true
|
|
|
|
if @include_count_in
|
|
@track_count += 1
|
|
end
|
|
|
|
stems.each do |stem|
|
|
|
|
vol = 1.0
|
|
pan = 0
|
|
match = false
|
|
skipped = false
|
|
# is this stem in the altered_tracks list?
|
|
altered_tracks.each do |alteration|
|
|
|
|
if alteration["id"] == stem.id
|
|
if alteration["mute"] || alteration["vol"] == 0
|
|
log.debug("leaving out track because muted or 0 volume #{alteration.inspect}")
|
|
skipped = true
|
|
next
|
|
else
|
|
vol = alteration["vol"] || vol
|
|
pan = alteration["pan"] || pan
|
|
end
|
|
@track_settings << {stem: stem, vol: vol, pan: pan}
|
|
match = true
|
|
break
|
|
end
|
|
end
|
|
|
|
# if we didn't deliberately skip this one, and if there was no 'match' (meaning user did not specify), then we leave this in unchanged
|
|
if !skipped && !match
|
|
@track_settings << {stem: stem, vol: vol, pan: pan}
|
|
end
|
|
end
|
|
|
|
if @include_count_in
|
|
@track_settings << {count_in: true, vol: 1.0, pan: 0}
|
|
end
|
|
|
|
@track_settings
|
|
end
|
|
|
|
def slider_to_pan(pan)
|
|
# transpose MIN_PAN to MAX_PAN to
|
|
# 0-1.0 range
|
|
#assumes abs(MIN_PAN) == abs(MAX_PAN)
|
|
# k = f(i) = (i)/(2*MAX_PAN) + 0.5
|
|
# so f(MIN_PAN) = -0.5 + 0.5 = 0
|
|
|
|
k = ((pan * (1.0))/ (2.0 * MAX_PAN)) + 0.5
|
|
l, r = 0
|
|
|
|
if k == 0
|
|
l = 0.0
|
|
r = 1.0
|
|
else
|
|
l = Math.sqrt(k)
|
|
r = Math.sqrt(1-k)
|
|
end
|
|
|
|
[l, r]
|
|
end
|
|
|
|
def package
|
|
|
|
log.info("Settings: #{@settings.to_json}")
|
|
|
|
Dir.mktmpdir do |tmp_dir|
|
|
|
|
# download all files
|
|
@track_settings.each do |track|
|
|
|
|
if track[:count_in]
|
|
file = create_tapin_track(tmp_dir)
|
|
bump_step(@mixdown_package)
|
|
else
|
|
jam_track_track = track[:stem]
|
|
|
|
file = File.join(tmp_dir, jam_track_track.id + '.ogg')
|
|
|
|
bump_step(@mixdown_package)
|
|
|
|
# download each track needed
|
|
s3_manager.download(jam_track_track.url_by_sample_rate(@mixdown_package.sample_rate), file)
|
|
end
|
|
|
|
track[:file] = file
|
|
end
|
|
|
|
audio_process tmp_dir
|
|
end
|
|
end
|
|
|
|
def audio_process(tmp_dir)
|
|
# use sox remix to apply mute, volume, pan settings
|
|
|
|
|
|
# step 1: apply pan and volume per track. mute and vol of 0 has already been handled, by virtue of those tracks not being present in @track_settings
|
|
# step 2: mix all tracks into single track, dividing by constant number of jam tracks, which is same as done by client backend
|
|
# step 3: apply pitch and speed (if applicable)
|
|
# step 4: encrypt with jkz (if applicable)
|
|
|
|
apply_vol_and_pan tmp_dir
|
|
|
|
create_silence_padding tmp_dir
|
|
|
|
mix tmp_dir
|
|
|
|
pitch_speed tmp_dir
|
|
|
|
final_packaging tmp_dir
|
|
end
|
|
|
|
# output is :volumed_file in each track in @track_settings
|
|
def apply_vol_and_pan(tmp_dir)
|
|
@track_settings.each do |track|
|
|
|
|
jam_track_track = track[:stem]
|
|
count_in = track[:count_in]
|
|
file = track[:file]
|
|
|
|
unless should_alter_volume? track
|
|
track[:volumed_file] = file
|
|
else
|
|
pan_l, pan_r = slider_to_pan(track[:pan])
|
|
|
|
vol = track[:vol]
|
|
|
|
# short
|
|
channel_l = pan_l * vol
|
|
channel_r = pan_r * vol
|
|
|
|
bump_step(@mixdown_package)
|
|
|
|
# sox claps.wav claps-remixed.wav remix 1v1.0 2v1.0
|
|
|
|
if count_in
|
|
volumed_file = File.join(tmp_dir, 'count-in' + '-volumed.ogg')
|
|
else
|
|
volumed_file = File.join(tmp_dir, jam_track_track.id + '-volumed.ogg')
|
|
end
|
|
|
|
cmd("sox \"#{file}\" \"#{volumed_file}\" remix 1v#{channel_r} 2v#{channel_l}", 'vol_pan')
|
|
|
|
track[:volumed_file] = volumed_file
|
|
end
|
|
end
|
|
end
|
|
|
|
def create_silence_padding(tmp_dir)
|
|
if @initial_padding > 0 && @include_count_in
|
|
|
|
@padding_file = File.join(tmp_dir, "initial_padding.ogg")
|
|
|
|
# -c 2 means stereo
|
|
cmd("sox -n -r #{long_sample_rate} -c 2 #{@padding_file} trim 0.0 #{@initial_padding}", "initial_padding")
|
|
|
|
@track_settings.each do |track|
|
|
|
|
next if track[:count_in]
|
|
|
|
input = track[:volumed_file]
|
|
output = input[0..-5] + '-padded.ogg'
|
|
|
|
padd_cmd = "sox '#{@padding_file}' '#{input}' '#{output}'"
|
|
|
|
cmd(padd_cmd, "pad_track_with_silence")
|
|
track[:volumed_file] = output
|
|
end
|
|
end
|
|
end
|
|
|
|
# output is @mix_file
|
|
def mix(tmp_dir)
|
|
|
|
bump_step(@mixdown_package)
|
|
|
|
@mix_file = File.join(tmp_dir, "mix.ogg")
|
|
|
|
|
|
pitch = @settings['pitch'] || 0
|
|
speed = @settings['speed'] || 0
|
|
|
|
|
|
real_count = @track_settings.count
|
|
real_count -= 1 if @include_count_in
|
|
|
|
# if there is only one track to mix, we need to skip mixing (sox will barf if you try to mix one file), but still divide by number of tracks
|
|
if real_count <= 1
|
|
mix_divide = 1.0/@track_count
|
|
cmd = "sox -v #{mix_divide} \"#{@track_settings[0][:volumed_file]}\" \"#{@mix_file}\""
|
|
cmd(cmd, 'volume_adjust')
|
|
else
|
|
# sox -m will divide by number of inputs by default. But we purposefully leave out tracks that are mute/no volume (to save downloading/processing time in this job)
|
|
# so we need to tell sox to divide by how many tracks there are as a constant, because this is how the client works today
|
|
#sox -m -v 1/n file1 -v 1/n file2 out
|
|
cmd = "sox -m"
|
|
mix_divide = 1.0/@track_count
|
|
@track_settings.each do |track|
|
|
|
|
# if pitch/shifted, we lay the tap-in after pitch/speed shift
|
|
# next if (pitch != 0 || speed != 0) && track[:count_in]
|
|
next if track[:count_in]
|
|
|
|
volumed_file = track[:volumed_file]
|
|
cmd << " -v #{mix_divide} \"#{volumed_file}\""
|
|
end
|
|
|
|
|
|
cmd << " \"#{@mix_file}\""
|
|
cmd(cmd, 'mix_adjust')
|
|
end
|
|
|
|
|
|
end
|
|
|
|
def long_sample_rate
|
|
sample_rate = 48000
|
|
if @mixdown_package.sample_rate != 48
|
|
sample_rate = 44100
|
|
end
|
|
sample_rate
|
|
end
|
|
|
|
# output is @speed_mix_file
|
|
def pitch_speed tmp_dir
|
|
|
|
# # usage
|
|
# This app will take an ogg, wav, or mp3 file (for the uploads) as its input and output an ogg file.
|
|
# Usage:
|
|
# sbsms path-to-input.ogg path-to-output.ogg TimeStrech PitchShift
|
|
|
|
# input is @mix_file, created by mix()
|
|
# output is @speed_mix_file
|
|
|
|
pitch = @settings['pitch'] || 0
|
|
speed = @settings['speed'] || 0
|
|
|
|
# if pitch and speed are 0, we do nothing here
|
|
if pitch == 0 && speed == 0
|
|
@speed_mix_file = @mix_file
|
|
else
|
|
bump_step(@mixdown_package)
|
|
|
|
@speed_mix_file = File.join(tmp_dir, "speed_mix_file.ogg")
|
|
|
|
# usage: sbsms infile<.wav|.aif|.mp3|.ogg> outfile<.ogg> rate[0.01:100] halfsteps[-48:48] outSampleRateInHz
|
|
|
|
sample_rate = long_sample_rate
|
|
|
|
# rate comes in as a percent (like 5, -5 for 5%, -5%). We need to change that to 1.05/
|
|
sbsms_speed = speed/100.0
|
|
sbsms_speed = 1.0 + sbsms_speed
|
|
|
|
sbsms_pitch = pitch
|
|
cmd("sbsms \"#{@mix_file}\" \"#{@speed_mix_file}\" #{sbsms_speed} #{sbsms_pitch} #{sample_rate}", 'speed_pitch_shift')
|
|
end
|
|
|
|
if @include_count_in
|
|
# lay the tap-ins over the recording
|
|
layered = File.join(tmp_dir, "layered_speed_mix.ogg")
|
|
cmd("sox -m '#{@count_in_file}' '#{@speed_mix_file}' '#{layered}'", "layer_tap_in")
|
|
@speed_mix_file = layered
|
|
end
|
|
end
|
|
|
|
def final_packaging tmp_dir
|
|
|
|
bump_step(@mixdown_package)
|
|
|
|
url = nil
|
|
private_key = nil
|
|
md5 = nil
|
|
length = 0
|
|
output = nil
|
|
|
|
if @mixdown_package.encrypt_type
|
|
output, private_key = encrypt_jkz tmp_dir
|
|
else
|
|
# create output file to correct output format
|
|
output = convert tmp_dir
|
|
end
|
|
|
|
# upload output to S3
|
|
s3_url = "#{@mixdown_package.store_dir}/#{@mixdown_package.filename}"
|
|
s3_manager.upload(s3_url, output)
|
|
|
|
length = File.size(output)
|
|
computed_md5 = Digest::MD5.new
|
|
File.open(output, 'rb').each { |line| computed_md5.update(line) }
|
|
md5 = computed_md5.to_s
|
|
|
|
@mixdown_package.finish_sign(s3_url, private_key, length, md5.to_s)
|
|
end
|
|
|
|
# returns output destination, converting if necessary
|
|
def convert(tmp_dir)
|
|
# if the file already ends with the desired file type, call it a win
|
|
if @speed_mix_file.end_with?(@mixdown_package.file_type)
|
|
@speed_mix_file
|
|
else
|
|
# otherwise we need to convert from lastly created file to correct
|
|
output = File.join(tmp_dir, "output.#{@mixdown_package.file_type}")
|
|
|
|
cmd("#{APP_CONFIG.normalize_ogg_path} --bitrate 192 \"#{@speed_mix_file}\"", 'normalize')
|
|
|
|
if @mixdown_package.file_type == JamTrackMixdownPackage::FILE_TYPE_AAC
|
|
cmd("ffmpeg -i \"#{@speed_mix_file}\" -c:a libfdk_aac -b:a 128k \"#{output}\"", 'convert_aac')
|
|
elsif @mixdown_package.file_type == JamTrackMixdownPackage::FILE_TYPE_MP3
|
|
cmd("ffmpeg -i \"#{@speed_mix_file}\" -ab 192k \"#{output}\"", 'convert_mp3')
|
|
else
|
|
raise 'unknown file_type'
|
|
end
|
|
|
|
|
|
output
|
|
end
|
|
end
|
|
|
|
def encrypt_jkz(tmp_dir)
|
|
py_root = APP_CONFIG.jamtracks_dir
|
|
step = 0
|
|
|
|
private_key = nil
|
|
# we need to make the id of the custom mix be the name of the file (ID.ogg)
|
|
custom_mix_name = File.join(tmp_dir, "#{@mixdown.id}.ogg")
|
|
FileUtils.mv(@speed_mix_file, custom_mix_name)
|
|
jam_file_opts = ""
|
|
jam_file_opts << " -i #{Shellwords.escape("#{custom_mix_name}+mixdown")}"
|
|
|
|
sku = @mixdown_package.id
|
|
title = @mixdown.name
|
|
output = File.join(tmp_dir, "#{title.parameterize}.jkz")
|
|
py_file = File.join(py_root, "jkcreate.py")
|
|
version = @mixdown_package.version
|
|
|
|
right = @mixdown.jam_track.right_for_user(@mixdown.user)
|
|
|
|
if @mixdown_package.sample_rate == 48
|
|
private_key = right.private_key_48
|
|
else
|
|
private_key = right.private_key_44
|
|
end
|
|
|
|
unless private_key
|
|
@error_reason = 'no_private_key'
|
|
@error_detail = 'user needs to generate JamTrack for given sample rate'
|
|
raise @error_reason
|
|
end
|
|
|
|
private_key_file = File.join(tmp_dir, 'skey.pem')
|
|
File.open(private_key_file, 'w') { |f| f.write(private_key) }
|
|
|
|
log.debug("PRIVATE KEY")
|
|
log.debug(private_key)
|
|
log.info "Executing python source in #{py_file}, outputting to #{tmp_dir} (#{output})"
|
|
|
|
cli = "python #{py_file} -D -k #{sku} -p #{Shellwords.escape(tmp_dir)}/pkey.pem -s #{Shellwords.escape(tmp_dir)}/skey.pem #{jam_file_opts} -o #{Shellwords.escape(output)} -t #{Shellwords.escape(title)} -V #{Shellwords.escape(version)}"
|
|
|
|
Open3.popen3(cli) do |stdin, stdout, stderr, wait_thr|
|
|
pid = wait_thr.pid
|
|
exit_status = wait_thr.value
|
|
err = stderr.read(1000)
|
|
out = stdout.read(1000)
|
|
#puts "stdout: #{out}, stderr: #{err}"
|
|
raise ArgumentError, "Error calling python script: #{err}" if err.present?
|
|
raise ArgumentError, "Error calling python script: #{out}" if out && (out.index("No track files specified") || out.index("Cannot find file"))
|
|
|
|
private_key = File.read(private_key_file)
|
|
end
|
|
return output, private_key
|
|
end
|
|
|
|
def cmd(cmd, type)
|
|
|
|
log.debug("executing #{cmd}")
|
|
|
|
output = `#{cmd}`
|
|
|
|
result_code = $?.to_i
|
|
|
|
if result_code == 0
|
|
output
|
|
else
|
|
@error_reason = type + "_fail"
|
|
@error_detail = "#{cmd}, #{output}"
|
|
raise "command `#{cmd}` failed."
|
|
end
|
|
end
|
|
|
|
# increment the step, which causes a notification to be sent to the client so it can keep the UI fresh as the packaging step goes on
|
|
def bump_step(mixdown_package)
|
|
step = @step
|
|
last_step_at = Time.now
|
|
mixdown_package.current_packaging_step = step
|
|
mixdown_package.last_step_at = last_step_at
|
|
JamTrackMixdownPackage.where(:id => mixdown_package.id).update_all(last_step_at: last_step_at, current_packaging_step: step)
|
|
SubscriptionMessage.mixdown_signing_job_change(mixdown_package)
|
|
|
|
@step = step + 1
|
|
end
|
|
|
|
# set @error_reason before you raise an exception, and it will be sent back as the error reason
|
|
# otherwise, the error_reason will be unhandled-job-exception
|
|
def post_error(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
|
|
@mixdown_package.finish_errored(@error_reason, @error_detail)
|
|
|
|
rescue Exception => e
|
|
log.error "unable to post back to the database the error #{e}"
|
|
end
|
|
end
|
|
end
|
|
end
|