jam-cloud/ruby/lib/jam_ruby/models/jam_track_track.rb

238 lines
9.1 KiB
Ruby

module JamRuby
# describes an audio track (like the drums, or guitar) that comprises a JamTrack
class JamTrackTrack < ActiveRecord::Base
include JamRuby::S3ManagerMixin
include JamRuby::S3PublicManagerMixin
# there should only be one Master per JamTrack, but there can be N Track per JamTrack
TRACK_TYPE = %w{Track Master}
@@log = Logging.logger[JamTrackTrack]
before_destroy :delete_s3_files
# Because JamTrackImporter imports audio files now, and because also the mere presence of this causes serious issues when updating the model (because reset of url_44 to something bogus), I've removed these
#mount_uploader :url_48, JamTrackTrackUploader
#mount_uploader :url_44, JamTrackTrackUploader
attr_accessible :jam_track_id, :track_type, :instrument, :instrument_id, :position, :part, as: :admin
attr_accessible :url_44, :url_48, :md5_44, :md5_48, :length_44, :length_48, :preview_start_time_raw, as: :admin
attr_accessor :original_audio_s3_path, :skip_uploader, :preview_generate_error
before_destroy :delete_s3_files
validates :position, presence: true, numericality: {only_integer: true}, length: {in: 1..1000}
validates :part, length: {maximum: 25}
validates :track_type, inclusion: {in: TRACK_TYPE }
validates :preview_start_time, numericality: {only_integer: true}, length: {in: 1..1000}, :allow_nil => true
validates_uniqueness_of :part, scope: [:jam_track_id, :instrument_id]
# validates :jam_track, presence: true
belongs_to :instrument, class_name: "JamRuby::Instrument"
belongs_to :jam_track, class_name: "JamRuby::JamTrack"
has_many :recorded_jam_track_tracks, :class_name => "JamRuby::RecordedJamTrackTrack", :foreign_key => :jam_track_track_id, :dependent => :destroy
# create storage directory that will house this jam_track, as well as
def store_dir
"jam_track_tracks"
end
# create name of the file
def filename(original_name)
"#{store_dir}/#{jam_track.original_artist}/#{jam_track.name}/#{original_name}"
end
# create name of the preview file.
# md5-'ed because we cache forever
def preview_filename(md5, ext='ogg')
original_name = "#{File.basename(self["url_44"], ".ogg")}-preview-#{md5}.#{ext}"
"jam_track_previews/#{jam_track.original_artist}/#{jam_track.name}/#{original_name}"
end
def has_preview?
!self["preview_url"].nil? && !self['preview_mp3_url'].nil?
end
# generates a URL that points to a public version of the preview
def preview_public_url(media_type='ogg')
url = media_type == 'ogg' ? self[:preview_url] : self[:preview_mp3_url]
if url
s3_public_manager.public_url(url,{ :secure => true})
else
nil
end
end
def manually_uploaded_filename(mounted_as)
if track_type == 'Master'
filename("Master Mix-#{mounted_as == :url_48 ? '48000' : '44100'}.ogg")
else
filename("#{jam_track.name} Stem - #{instrument.description}-#{part}-#{mounted_as == :url_48 ? '48000' : '44100'}.ogg")
end
end
def master?
track_type == 'Master'
end
def url_by_sample_rate(sample_rate=48)
field_name = (sample_rate==48) ? "url_48" : "url_44"
self[field_name]
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, sample_rate=48)
s3_manager.sign_url(url_by_sample_rate(sample_rate), {:expires => expiration_time, :response_content_type => 'audio/ogg', :secure => true})
end
def can_download?(user)
# I think we have to make a special case for 'previews', but maybe that's just up to the controller to not check can_download?
jam_track.owners.include?(user)
end
def move_up
#normalize_position
if self.position > 1
# Switch with previous
previous_track = self.jam_track.jam_track_tracks.where("position=?", self.position-1).first
if previous_track
JamTrack.transaction do
previous_track.position,self.position = self.position,previous_track.position
previous_track.save(validate:false)
self.save(validate:false)
end
end
end
end
def move_down
count=normalize_position
if self.position < count
# Switch with next:
next_track = self.jam_track.jam_track_tracks.where("position=?", self.position+1).first
if next_track
next_track.position,self.position = self.position,next_track.position
next_track.save(validate:false)
self.save(validate:false)
end
end
end
def delete_s3_files
s3_manager.delete(self[:url_44]) if self[:url_44] && s3_manager.exists?(self[:url_44])
s3_manager.delete(self[:url_48]) if self[:url_48] && s3_manager.exists?(self[:url_48])
s3_public_manager.delete(self[:preview_url]) if self[:preview_url] && s3_public_manager.exists?(self[:preview_url])
s3_public_manager.delete(self[:preview_mp3_url]) if self[:preview_mp3_url] && s3_public_manager.exists?(self[:preview_mp3_url])
end
def generate_preview
begin
Dir.mktmpdir do |tmp_dir|
input = File.join(tmp_dir, 'in.ogg')
output = File.join(tmp_dir, 'out.ogg')
output_mp3 = File.join(tmp_dir, 'out.mp3')
start = self.preview_start_time.to_f / 1000
stop = start + 20
raise 'no track' unless self["url_44"]
s3_manager.download(self.url_by_sample_rate(44), input)
command = "sox \"#{input}\" \"#{output}\" trim #{sprintf("%.3f", start)} =#{sprintf("%.3f", stop)}"
@@log.debug("trimming using: " + command)
sox_output = `#{command}`
result_code = $?.to_i
if result_code != 0
@@log.debug("fail #{result_code}")
@preview_generate_error = "unable to execute cut command #{sox_output}"
else
# now create mp3 off of ogg preview
convert_mp3_cmd = "#{APP_CONFIG.ffmpeg_path} -i \"#{output}\" -ab 192k \"#{output_mp3}\""
@@log.debug("converting to mp3 using: " + convert_mp3_cmd)
convert_output = `#{convert_mp3_cmd}`
result_code = $?.to_i
if result_code != 0
@@log.debug("fail #{result_code}")
@preview_generate_error = "unable to execute mp3 convert command #{convert_output}"
else
ogg_digest = ::Digest::MD5.file(output)
mp3_digest = ::Digest::MD5.file(output_mp3)
self["preview_md5"] = ogg_md5 = ogg_digest.hexdigest
self["preview_mp3_md5"] = mp3_md5 = mp3_digest.hexdigest
@@log.debug("uploading ogg preview to #{self.preview_filename('ogg')}")
s3_public_manager.upload(self.preview_filename(ogg_md5, 'ogg'), output, content_type: 'audio/ogg', content_md5: ogg_digest.base64digest)
@@log.debug("uploading mp3 preview to #{self.preview_filename('mp3')}")
s3_public_manager.upload(self.preview_filename(mp3_md5, 'mp3'), output_mp3, content_type: 'audio/mpeg', content_md5: mp3_digest.base64digest)
self.skip_uploader = true
original_ogg_preview_url = self["preview_url"]
original_mp3_preview_url = self["preview_mp3_url"]
# and finally update the JamTrackTrack with the new info
self["preview_url"] = self.preview_filename(ogg_md5, 'ogg')
self["preview_length"] = File.new(output).size
# and finally update the JamTrackTrack with the new info
self["preview_mp3_url"] = self.preview_filename(mp3_md5, 'mp3')
self["preview_mp3_length"] = File.new(output_mp3).size
self.save!
# if all that worked, now delete old previews, if present
begin
s3_public_manager.delete(original_ogg_preview_url) if original_ogg_preview_url && original_ogg_preview_url != self["preview_url"]
s3_public_manager.delete(original_mp3_preview_url) if original_mp3_preview_url && original_mp3_preview_url != track["preview_mp3_url"]
rescue
puts "UNABLE TO CLEANUP OLD PREVIEW URL"
end
end
end
end
rescue Exception => e
@@log.error("error in sox command #{e.to_s}")
@preview_generate_error = e.to_s
end
end
private
def normalize_position
parent = self.jam_track
position = 0
if parent
JamTrack.transaction do
parent.jam_track_tracks.each do |jtt|
position += 1
if jtt.position != position
jtt.position = position
jtt.save(validate:false)
end
end
end
end
position
end # normalize_position
end # class
end # module