This commit is contained in:
Seth Call 2015-09-04 13:11:42 -05:00
parent 3ee71634b3
commit ff01b6df0e
15 changed files with 829 additions and 1 deletions

View File

@ -302,4 +302,5 @@ jam_track_onboarding_enhancements.sql
jam_track_name_drop_unique.sql
jam_track_searchability.sql
harry_fox_agency.sql
jam_track_slug.sql
jam_track_slug.sql
mixdown.sql

41
db/up/mixdown.sql Normal file
View File

@ -0,0 +1,41 @@
CREATE TABLE jam_track_mixdowns (
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
jam_track_id VARCHAR(64) NOT NULL REFERENCES jam_tracks(id) ON DELETE CASCADE,
user_id VARCHAR(64) NOT NULL REFERENCES users(id) ON DELETE CASCADE,
settings JSON NOT NULL,
name VARCHAR(1000) NOT NULL,
description VARCHAR(1000),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE jam_track_mixdown_packages (
id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4(),
jam_track_mixdown_id VARCHAR(64) NOT NULL REFERENCES jam_track_mixdowns(id) ON DELETE SET NULL,
file_type VARCHAR NOT NULL ,
sample_rate INTEGER NOT NULL,
url VARCHAR(2048),
md5 VARCHAR,
length INTEGER,
downloaded_since_sign BOOLEAN NOT NULL DEFAULT FALSE,
last_signed_at TIMESTAMP,
download_count INTEGER NOT NULL DEFAULT 0,
signed_at TIMESTAMP,
downloaded_at TIMESTAMP,
signing_queued_at TIMESTAMP,
error_count INTEGER NOT NULL DEFAULT 0,
error_reason VARCHAR,
error_detail VARCHAR,
should_retry BOOLEAN NOT NULL DEFAULT FALSE,
packaging_steps INTEGER,
current_packaging_step INTEGER,
private_key VARCHAR,
signed BOOLEAN,
signing_started_at TIMESTAMP,
first_downloaded TIMESTAMP,
signing BOOLEAN NOT NULL DEFAULT FALSE,
encrypt_type VARCHAR,
version VARCHAR NOT NULL DEFAULT '1',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

View File

@ -82,6 +82,10 @@ message ClientMessage {
JAM_TRACK_SIGN_COMPLETE = 260;
JAM_TRACK_SIGN_FAILED = 261;
// jamtracks mixdown notifications
MIXDOWN_SIGN_COMPLETE = 270;
MIXDOWN_SIGN_FAILED = 271;
TEST_SESSION_MESSAGE = 295;
PING_REQUEST = 300;
@ -188,6 +192,10 @@ message ClientMessage {
optional JamTrackSignComplete jam_track_sign_complete = 260;
optional JamTrackSignFailed jam_track_sign_failed = 261;
// jamtrack mixdown notification
optional MixdownSignComplete mixdown_sign_complete = 270;
optional MixdownSignFailed mixdown_sign_failed = 271;
// Client-Session messages (to/from)
optional TestSessionMessage test_session_message = 295;
@ -612,6 +620,15 @@ message JamTrackSignFailed {
required int32 jam_track_right_id = 1; // jam track right id
}
message MixdownSignComplete {
required int32 mixdown_package_id = 1; // jam track mixdown package id
}
message MixdownSignFailed {
required int32 mixdown_package_id = 1; // jam track mixdown package id
}
message SubscriptionMessage {
optional string type = 1; // the type of the subscription
optional string id = 2; // data about what to subscribe to, specifically

View File

@ -64,6 +64,7 @@ require "jam_ruby/resque/scheduled/jam_tracks_cleaner"
require "jam_ruby/resque/scheduled/stats_maker"
require "jam_ruby/resque/scheduled/tally_affiliates"
require "jam_ruby/resque/jam_tracks_builder"
require "jam_ruby/resque/jam_track_mixdown_packager"
require "jam_ruby/resque/google_analytics_event"
require "jam_ruby/resque/batch_email_job"
require "jam_ruby/resque/long_running"
@ -209,6 +210,8 @@ require "jam_ruby/models/jam_track_track"
require "jam_ruby/models/jam_track_right"
require "jam_ruby/models/jam_track_tap_in"
require "jam_ruby/models/jam_track_file"
require "jam_ruby/models/jam_track_mixdown"
require "jam_ruby/models/jam_track_mixdown_package"
require "jam_ruby/models/genre_jam_track"
require "jam_ruby/app/mailers/async_mailer"
require "jam_ruby/app/mailers/batch_mailer"

View File

@ -51,4 +51,7 @@ module NotificationTypes
JAM_TRACK_SIGN_COMPLETE = "JAM_TRACK_SIGN_COMPLETE"
JAM_TRACK_SIGN_FAILED = "JAM_TRACK_SIGN_FAILED"
MIXDOWN_SIGN_COMPLETE = "MIXDOWN_SIGN_COMPLETE"
MIXDOWN_SIGN_FAILED = "MIXDOWN_SIGN_FAILED"
end

View File

@ -24,6 +24,14 @@ module JamRuby
def self.jam_track_signing_job_change(jam_track_right)
Notification.send_subscription_message('jam_track_right', jam_track_right.id.to_s, {signing_state: jam_track_right.signing_state, current_packaging_step: jam_track_right.current_packaging_step, packaging_steps: jam_track_right.packaging_steps}.to_json )
end
def self.mixdown_signing_job_change(jam_track_mixdown_package)
Notification.send_subscription_message('mixdown', jam_track_mixdown_package.id.to_s, {signing_state: jam_track_mixdown_package.signing_state, current_packaging_step: jam_track_mixdown_package.current_packaging_step, packaging_steps: jam_track_right.packaging_steps}.to_json )
end
def self.test
Notification.send_subscription_message('some_key', '1', {field1: 'field1', field2: 'field2'}.to_json)
end
end
end

View File

@ -736,6 +736,30 @@ module JamRuby
)
end
def mixdown_sign_complete(receiver_id, mixdown_package_id)
signed = Jampb::MixdownSignComplete.new(
:mixdown_package_id => mixdown_package_id
)
Jampb::ClientMessage.new(
:type => ClientMessage::Type::MIXDOWN_SIGN_COMPLETE,
:route_to => USER_TARGET_PREFIX + receiver_id, #:route_to => CLIENT_TARGET,
:mixdown_sign_complete => signed
)
end
def mixdown_sign_failed(receiver_id, mixdown_package_id)
signed = Jampb::MixdownSignFailed.new(
:mixdown_package_id => mixdown_package_id
)
Jampb::ClientMessage.new(
:type => ClientMessage::Type::MIXDOWN_SIGN_FAILED,
:route_to => USER_TARGET_PREFIX + receiver_id, #:route_to => CLIENT_TARGET,
:mixdown_sign_failed=> signed
)
end
def recording_master_mix_complete(receiver_id, recording_id, claimed_recording_id, band_id, msg, notification_id, created_at)
recording_master_mix_complete = Jampb::RecordingMasterMixComplete.new(

View File

@ -0,0 +1,44 @@
module JamRuby
# describes what users have rights to which tracks
class JamTrackMixdown < ActiveRecord::Base
@@log = Logging.logger[JamTrackMixdown]
belongs_to :user, class_name: "JamRuby::User" # the owner, or purchaser of the jam_track
belongs_to :jam_track, class_name: "JamRuby::JamTrack"
validates :name, presence: true, length: {maximum: 100}
validates :description, length: {maximum: 1000}
validates :user, presence: true
validates :jam_track, presence: true
validates :settings, presence: true
validates_uniqueness_of :user_id, scope: :jam_track_id
validate :verify_settings
def verify_settings
# TODO: validate settings
if false
errors.add(:settings, 'invalid settings')
end
end
def self.create(name, user, jam_track, settings)
mixdown = JamTrackMixdown.new
mixdown.name = name
mixdown.user = user
mixdown.jam_track = jam_track
mixdown.settings = settings.to_json # RAILS 4 CAN REMOVE .to_json
mixdown.save
mixdown
end
def will_pitch_shift?
self.settings["pitch"] != 0 || self.settings["speed"] != 0
end
end
end

View File

@ -0,0 +1,217 @@
module JamRuby
# describes what users have rights to which tracks
class JamTrackMixdownPackage < ActiveRecord::Base
include JamRuby::S3ManagerMixin
@@log = Logging.logger[JamTrackMixdownPackage]
# these are used as extensions for the files stored in s3
FILE_TYPE_OGG = 'ogg'
FILE_TYPE_AAC = 'aac'
FILE_TYPES = [FILE_TYPE_OGG, FILE_TYPE_AAC]
SAMPLE_RATE_44 = 44
SAMPLE_RATE_48 = 48
SAMPLE_RATES = [SAMPLE_RATE_44, SAMPLE_RATE_48]
ENCRYPT_TYPE_JKZ = 'jkz'
ENCRYPT_TYPES = [ENCRYPT_TYPE_JKZ, nil]
belongs_to :jam_track_mixdown, class_name: "JamRuby::JamTrackMixdown", dependent: :destroy
validates :jam_track_mixdown, presence: true
validates :file_type, inclusion: {in: FILE_TYPES}
validates :sample_rate, inclusion: {in: SAMPLE_RATES}
validates :encrypt_type, inclusion: {in: ENCRYPT_TYPES}
validates_uniqueness_of :file_type, scope: :sample_rate
validates :signing, presence: true
validates :signed, presence: true
validate :verify_download_count
before_destroy :delete_s3_files
MAX_JAM_TRACK_DOWNLOADS = 1000
def after_save
# try to catch major transitions:
# if just queue time changes, start time changes, or signed time changes, send out a notice
if signing_queued_at_was != signing_queued_at || signing_started_at_was != signing_started_at || last_signed_at_was != last_signed_at || current_packaging_step != current_packaging_step_was || packaging_steps != packaging_steps_was
SubscriptionMessage.mixdown_signing_job_change(self)
end
end
def self.create(mixdown, file_type, sample_rate, encrypt)
package = JamTrackMixdownPackage.new
package.jam_track_mixdown = mixdown
package.file_type = file_type
package.sample_rate = sample_rate
package.save
package
end
def verify_download_count
if (self.download_count < 0 || self.download_count > MAX_JAM_TRACK_DOWNLOADS) && !@current_user.admin
errors.add(:download_count, "must be less than or equal to #{MAX_JAM_TRACK_DOWNLOADS}")
end
end
def finish_errored(error_reason, error_detail)
self.last_signed_at = Time.now
self.error_count = self.error_count + 1
self.error_reason = error_reason
self.error_detail = error_detail
self.should_retry = self.error_count < 5
self.signing = false
if save
Notification.send_mixdown_sign_failed(self)
else
raise "Error sending notification #{self.errors}"
end
end
def finish_sign(url, private_key, length, md5)
self.url = url
self.private_key = private_key
self.signing_queued_at = nil # if left set, throws off signing_state on subsequent signing attempts
self.downloaded_since_sign = false
self.last_signed_at = Time.now
self.length = length
self.md5 = md5
self.signed = true
self.signing = false
self.error_count = 0
self.error_reason = nil
self.error_detail = nil
self.should_retry = false
save!
end
def store_dir
"jam_track_mixdowns/#{created_at.strftime('%m-%d-%Y')}/#{self.jam_track_mixdown.user_id}"
end
def filename
if encrypt_type
"#{id}.#{encrypt_type}"
else
"#{id}.#{file_type}"
end
end
# creates a short-lived URL that has access to the object.
# the idea is that this is used when a user who has the rights to this tries to download this JamTrack
# we would verify their rights (can_download?), and generates a URL in response to the click so that they can download
# but the url is short lived enough so that it wouldn't be easily shared
def sign_url(expiration_time = 120)
s3_manager.sign_url(self['url'], {:expires => expiration_time, :secure => true})
end
def enqueue
begin
JamTrackMixdownPackager.where(:id => self.id).update_all(:signing_queued_at => Time.now, :signing_started_at => nil, :last_signed_at => nil)
Resque.enqueue(JamTrackMixdownPackager, self.id)
true
rescue Exception => e
puts "e: #{e}"
# implies redis is down. we don't update started_at by bailing out here
false
end
end
# if the job is already signed, just queued up for signing, or currently signing, then don't enqueue... otherwise fire it off
def enqueue_if_needed
state = signing_state
if state == 'SIGNED' || state == 'SIGNING' || state == 'QUEUED'
false
else
enqueue
true
end
end
def ready?
self.signed && self.url.present?
end
# returns easy to digest state field
# SIGNED - the package is ready to be downloaded
# ERROR - the package was built unsuccessfully
# SIGNING_TIMEOUT - the package was kicked off to be signed, but it seems to have hung
# SIGNING - the package is currently signing
# QUEUED_TIMEOUT - the package signing job (JamTrackBuilder) was queued, but never executed
# QUEUED - the package is queued to sign
# QUIET - the jam_track_right exists, but no job has been kicked off; a job needs to be enqueued
def signing_state
state = nil
if signed
state = 'SIGNED'
elsif signing_started_at
# the maximum amount of time the packaging job can take is 10 seconds * num steps. For a 10 track song, this will be 110 seconds. It's a bit long.
# TODO: base this on the settings of the mix
signing_job_run_max_time = 100 # packaging_steps * 10
if Time.now - signing_started_at > signing_job_run_max_time
state = 'SIGNING_TIMEOUT'
elsif Time.now - last_step_at > APP_CONFIG.signing_step_max_time
state = 'SIGNING_TIMEOUT'
else
state = 'SIGNING'
end
elsif signing_queued_at
if Time.now - signing_queued_at > APP_CONFIG.signing_job_queue_max_time
state = 'QUEUED_TIMEOUT'
else
state = 'QUEUED'
end
elsif error_count > 0
state = 'ERROR'
else
state = 'QUIET' # needs to be poked to go build
end
state
end
def signed?
signed
end
def update_download_count(count=1)
self.download_count = self.download_count + count
self.last_downloaded_at = Time.now
if self.signed
self.downloaded_since_sign = true
end
end
def self.stats
stats = {}
result = JamTrackMixdownPackage.select('count(id) as total, count(CASE WHEN signing THEN 1 ELSE NULL END) as signing_count').first
stats['count'] = result['total'].to_i
stats['signing_count'] = result['signing_count'].to_i
stats
end
def delete_s3_files
s3_manager.delete(self.url) if self.url && s3_manager.exists?(self.url)
end
end
end

View File

@ -1265,6 +1265,30 @@ module JamRuby
#@@mq_router.publish_to_all_clients(msg)
end
def send_mixdown_sign_failed(jam_track_mixdown_package)
notification = Notification.new
notification.jam_track_right_id = jam_track_mixdown_package.id
notification.description = NotificationTypes::MIXDOWN_SIGN_FAILED
notification.target_user_id = jam_track_mixdown_package.jam_track_mixdown.user_id
notification.save!
msg = @@message_factory.mixdown_sign_failed(jam_track_mixdown_package.jam_track_mixdown.user_id, jam_track_mixdown_package.id)
@@mq_router.publish_to_user(jam_track_mixdown_package.jam_track_mixdown.user_id, msg)
end
def send_mixdown_sign_complete(jam_track_mixdown_package)
notification = Notification.new
notification.mixdown_package_id = jam_track_mixdown_package.id
notification.description = NotificationTypes::MIXDOWN_SIGN_COMPLETE
notification.target_user_id = jam_track_mixdown_package.jam_track_mixdown.user_id
notification.save!
msg = @@message_factory.mixdown_sign_complete(jam_track_mixdown_package.jam_track_mixdown.user_id, jam_track_mixdown_package.id)
@@mq_router.publish_to_user(jam_track_mixdown_package.jam_track_mixdown.user_id, msg)
end
def send_client_update(product, version, uri, size)
msg = @@message_factory.client_update( product, version, uri, size)

View File

@ -0,0 +1,395 @@
require 'json'
require 'resque'
require 'resque-retry'
require 'net/http'
require 'digest/md5'
module JamRuby
class JamTracksMixdownPackager
extend JamRuby::ResqueStats
include JamRuby::S3ManagerMixin
MAX_PAN = 90
MIN_PAN = -90
attr_accessor :mixdown_package_id, :settings, :mixdown_package, :mixdown, :steps
@queue = :jam_track_mixdown_packager
def log
@log || Logging.logger[JamTracksMixdownPackager]
end
def self.perform(mixdown_package_id, bitrate=48)
jam_track_builder = JamTracksMixdownPackager.new()
jam_track_builder.mixdown_package_id = mixdown_package_id
jam_track_builder.run
end
def compute_steps
@steps = 0
number_downloads = @track_settings.length
number_volume_adjustments = @track_settings.select { |track| should_alter_volume? track }
pitch_shift_steps = @mixdown.will_pitch_shift? ? 1 : 0
mix_step = 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 = @mixdown.settings
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 = Time.now
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)
# 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
# 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
stems.each do |stem|
# is this stem in the altered_tracks list?
altered_tracks.each do |alteration|
vol = 1.0
pan = 0
pitch = 0
speed = 0
if alteration["id"] == stem.id
if alteration["mute"] || alteration["vol"] == 0
next
else
vol = alteration["vol"] || vol
pan = alteration["pan"] || pan
end
end
@track_settings << {stem: stem, vol: vol, pan: pan}
end
@track_settings
end
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 = ((i * (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
puts @settings.inspect
puts @track_count
puts @track_settings
Dir.mktmpdir do |tmp_dir|
# download all files
@track_settings.each do |track|
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)
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
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]
file = track[:file]
if should_alter_volume? track
track[:volumed_file] = file
else
pan_l, pan_r = slider_to_pan(track[:pan])
# 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
volumed_file = File.join(tmp_dir, jam_track_track.id + '-volumed.ogg')
cmd("sox \"#{file}\" \"#{volumed_file}\" remix 1v#{channel_l} 2v#{channel_r}")
track[:volumed_file] = volumed_file
end
end
end
# output is @mix_file
def mix(tmp_dir)
bump_step(@mixdown_package)
# 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|
volumed_file = track[:volumed_file]
cmd << " -v #{mix_divide} \"#{volumed_file}\""
end
@mix_file = File.join(tmp_dir, "mix.ogg")
cmd << " \"#{@mix_file}\""
cmd(cmd)
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")
cmd "sbms \"#{@mix_file}\" \"#{@speed_mix_file}\" #{speed} #{pitch}"
end
end
def final_packaging tmp_dir
bump_step(@mixdown_package)
url = null
private_key = nil
md5 = nil
length = 0
output = null
if encrypted_file
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_pacakge.file_type}")
cmd("sox \"#{@speed_mix_file}\" \"#{output}\"")
output
end
end
def encrypt_jkz(tmp_dir)
py_root = APP_CONFIG.jamtracks_dir
step = 0
jam_file_opts = ""
jam_file_opts << " -i #{Shellwords.escape("#{track_filename}+#{jam_track_track.part}")}"
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
@@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("#{tmp_dir}/skey.pem")
end
return output, private_key
end
def cmd(cmd)
log.debug("executing #{cmd}")
output = `#{cmd}`
result_code = $?.to_i
if result_code == 0
output
else
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 = Time.now
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

View File

@ -31,6 +31,7 @@ module JamRuby
Stats.write('users', User.stats)
Stats.write('sessions', ActiveMusicSession.stats)
Stats.write('jam_track_rights', JamTrackRight.stats)
Stats.write('jam_track_mixdown_packages', JamTrackMixdownPackage.stats)
end
end

View File

@ -723,6 +723,18 @@ FactoryGirl.define do
sequence(:phone) { |n| "phone-#{n}" }
end
factory :jam_track_mixdown, :class => JamRuby::JamTrackMixdown do
association :user, factory: :user
association :jam_track, factory: :jam_track
sequence(:name) { |n| "mixdown-#{n}"}
settings '{}'
end
factory :jam_track_mixdown_pakage, :class => JamRuby::JamTrackMixdownPackage do
association :jam_track_mixdown, factory: :jam_track_mixdown
end
factory :jam_track, :class => JamRuby::JamTrack do
sequence(:name) { |n| "jam-track-#{n}" }
sequence(:description) { |n| "description-#{n}" }

View File

@ -0,0 +1,18 @@
require 'spec_helper'
describe JamTrackMixdownPackage do
include UsesTempFiles
it "can be created (factory girl)" do
package = FactoryGirl.create(:jam_track_mixdown_package)
end
it "can be created" do
mixdown= FactoryGirl.create(:jam_track_mixdown_package)
package = JamTrackMixdownPackage.create(mixdown, 'ogg', 48, true)
package.errors.any?.should == false
end
end

View File

@ -0,0 +1,20 @@
require 'spec_helper'
describe JamTrackMixdown do
let(:user) {FactoryGirl.create(:user)}
let(:jam_track) {FactoryGirl.create(:jam_track)}
it "can be created (factory girl)" do
mixdown = FactoryGirl.create(:jam_track_mixdown)
mixdown = JamTrackMixdown.find(mixdown.id)
mixdown.settings.should eq('{}')
end
it "can be created" do
mixdown = JamTrackMixdown.create('abc', user, jam_track, {})
mixdown.errors.any?.should == false
end
end