* wip
This commit is contained in:
parent
3ee71634b3
commit
ff01b6df0e
|
|
@ -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
|
||||
|
|
@ -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
|
||||
);
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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}" }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
Loading…
Reference in New Issue