2014-11-04 20:55:12 +00:00
module JamRuby
# describes an audio track (like the drums, or guitar) that comprises a JamTrack
class JamTrackTrack < ActiveRecord :: Base
include JamRuby :: S3ManagerMixin
2015-03-31 18:36:49 +00:00
include JamRuby :: S3PublicManagerMixin
2014-11-04 20:55:12 +00:00
# there should only be one Master per JamTrack, but there can be N Track per JamTrack
2015-11-30 11:07:47 +00:00
TRACK_TYPE = %w{ Track Master Click }
2014-11-04 20:55:12 +00:00
2015-03-16 22:50:38 +00:00
@@log = Logging . logger [ JamTrackTrack ]
2015-04-20 15:42:00 +00:00
before_destroy :delete_s3_files
2015-03-16 22:50:38 +00:00
2015-03-17 15:55:16 +00:00
# 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
2014-11-04 20:55:12 +00:00
2015-02-25 23:19:03 +00:00
attr_accessible :jam_track_id , :track_type , :instrument , :instrument_id , :position , :part , as : :admin
2015-03-16 22:50:38 +00:00
attr_accessible :url_44 , :url_48 , :md5_44 , :md5_48 , :length_44 , :length_48 , :preview_start_time_raw , as : :admin
2014-11-04 20:55:12 +00:00
2015-12-20 03:21:59 +00:00
attr_accessor :original_audio_s3_path , :skip_uploader , :preview_generate_error , :wav_file , :tmp_duration , :skip_inst_part_uniq
2015-03-09 14:44:12 +00:00
2015-04-20 18:56:05 +00:00
before_destroy :delete_s3_files
2014-11-04 20:55:12 +00:00
validates :position , presence : true , numericality : { only_integer : true } , length : { in : 1 .. 1000 }
2015-08-13 16:09:54 +00:00
validates :part , length : { maximum : 35 }
2014-11-04 20:55:12 +00:00
validates :track_type , inclusion : { in : TRACK_TYPE }
2015-03-16 22:50:38 +00:00
validates :preview_start_time , numericality : { only_integer : true } , length : { in : 1 .. 1000 } , :allow_nil = > true
2015-12-20 03:21:59 +00:00
validates_uniqueness_of :part , scope : [ :jam_track_id , :instrument_id ] , unless : :skip_inst_part_uniq
2014-11-04 20:55:12 +00:00
# validates :jam_track, presence: true
belongs_to :instrument , class_name : " JamRuby::Instrument "
belongs_to :jam_track , class_name : " JamRuby::JamTrack "
2015-02-25 16:43:21 +00:00
has_many :recorded_jam_track_tracks , :class_name = > " JamRuby::RecordedJamTrackTrack " , :foreign_key = > :jam_track_track_id , :dependent = > :destroy
2015-10-16 19:01:18 +00:00
has_one :jam_track_right , class_name : 'JamRuby::JamTrackRight' , foreign_key : 'last_stem_id' , inverse_of : :last_stem
2015-02-25 16:43:21 +00:00
2014-11-04 20:55:12 +00:00
# create storage directory that will house this jam_track, as well as
def store_dir
2015-03-09 14:44:12 +00:00
" jam_track_tracks "
2014-11-04 20:55:12 +00:00
end
2015-11-20 12:57:32 +00:00
def licensor_suffix
suffix = ''
if jam_track . licensor
raise " no licensor name " if jam_track . licensor . name . nil?
suffix = " - #{ jam_track . licensor . name } "
end
suffix
end
2014-11-04 20:55:12 +00:00
# create name of the file
2015-03-09 14:44:12 +00:00
def filename ( original_name )
2015-11-20 12:57:32 +00:00
" #{ store_dir } / #{ jam_track . original_artist } / #{ jam_track . name } #{ licensor_suffix } / #{ original_name } "
2015-03-09 14:44:12 +00:00
end
2015-03-31 18:36:49 +00:00
# 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 } "
2015-09-30 00:57:31 +00:00
" #{ preview_directory } / #{ original_name } "
end
def preview_directory
2015-11-20 12:57:32 +00:00
" jam_track_previews/ #{ jam_track . original_artist } / #{ jam_track . name } #{ licensor_suffix } "
2015-03-16 22:50:38 +00:00
end
def has_preview?
2015-03-30 02:51:14 +00:00
! self [ " preview_url " ] . nil? && ! self [ 'preview_mp3_url' ] . nil?
2015-03-16 22:50:38 +00:00
end
2015-03-31 18:36:49 +00:00
# generates a URL that points to a public version of the preview
def preview_public_url ( media_type = 'ogg' )
2015-09-30 00:57:31 +00:00
case media_type
when 'ogg'
url = self [ :preview_url ]
when 'mp3'
url = self [ :preview_mp3_url ]
when 'aac'
url = self [ :preview_aac_url ]
else
raise " unknown media_type #{ media_type } "
end
2015-03-31 18:36:49 +00:00
if url
2015-05-11 21:52:58 +00:00
s3_public_manager . public_url ( url , { :secure = > true } )
2015-03-31 18:36:49 +00:00
else
nil
end
2015-03-16 22:50:38 +00:00
end
2015-10-16 19:01:18 +00:00
def display_name
if track_type == 'Master'
'Master Mix'
else
display_part = ''
if part
display_part = " -( #{ part } ) "
end
" #{ instrument . description } #{ display_part } "
end
end
2015-03-09 14:44:12 +00:00
def manually_uploaded_filename ( mounted_as )
if track_type == 'Master'
2015-03-09 21:35:39 +00:00
filename ( " Master Mix- #{ mounted_as == :url_48 ? '48000' : '44100' } .ogg " )
2015-03-09 14:44:12 +00:00
else
2015-03-09 21:35:39 +00:00
filename ( " #{ jam_track . name } Stem - #{ instrument . description } - #{ part } - #{ mounted_as == :url_48 ? '48000' : '44100' } .ogg " )
2015-03-09 14:44:12 +00:00
end
2014-11-04 20:55:12 +00:00
end
2015-03-31 20:17:43 +00:00
def master?
track_type == 'Master'
end
2015-03-09 14:44:12 +00:00
def url_by_sample_rate ( sample_rate = 48 )
field_name = ( sample_rate == 48 ) ? " url_48 " : " url_44 "
self [ field_name ]
end
2014-11-04 20:55:12 +00:00
# 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
2015-03-09 14:44:12 +00:00
def sign_url ( expiration_time = 120 , sample_rate = 48 )
2015-05-11 21:52:58 +00:00
s3_manager . sign_url ( url_by_sample_rate ( sample_rate ) , { :expires = > expiration_time , :response_content_type = > 'audio/ogg' , :secure = > true } )
2014-11-04 20:55:12 +00:00
end
2015-10-16 19:01:18 +00:00
def web_download_sign_url ( expiration_time = 120 , type = 'mp3' , content_type = nil , response_content_disposition = nil )
options = { :expires = > expiration_time , :secure = > true }
options [ :response_content_type ] = content_type if content_type
options [ :response_content_disposition ] = response_content_disposition if response_content_disposition
url_field = self [ 'url_' + type + '_48' ]
url_field = self [ 'url_48' ] if type == 'ogg' # ogg has different column format in database
s3_manager . sign_url ( url_field , options )
end
2015-02-25 23:19:03 +00:00
2014-11-04 20:55:12 +00:00
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
2014-12-05 21:03:40 +00:00
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
2015-04-20 15:42:00 +00:00
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 ] )
2015-04-20 18:55:33 +00:00
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 ] )
2015-04-20 15:42:00 +00:00
end
2015-05-04 22:48:14 +00:00
2015-08-09 18:37:43 +00:00
2015-05-04 22:48:14 +00:00
def generate_preview
begin
Dir . mktmpdir do | tmp_dir |
input = File . join ( tmp_dir , 'in.ogg' )
raise 'no track' unless self [ " url_44 " ]
s3_manager . download ( self . url_by_sample_rate ( 44 ) , input )
2015-08-09 18:37:43 +00:00
process_preview ( input , tmp_dir )
end
rescue Exception = > e
@@log . error ( " error in sox command #{ e . to_s } " )
@preview_generate_error = e . to_s
end
end
# input is the original ogg file for the track. tmp_dir is where this code can safely generate output stuff and have it cleaned up later
def process_preview ( input , tmp_dir )
uuid = SecureRandom . uuid
output = File . join ( tmp_dir , " #{ uuid } .ogg " )
output_mp3 = File . join ( tmp_dir , " #{ uuid } .mp3 " )
2015-11-20 12:57:32 +00:00
output_aac = File . join ( tmp_dir , " #{ uuid } .aac " )
2015-05-04 22:48:14 +00:00
2015-08-09 18:37:43 +00:00
start = self . preview_start_time . to_f / 1000
stop = start + 20
2015-05-04 22:48:14 +00:00
2015-08-09 18:37:43 +00:00
command = " sox \" #{ input } \" \" #{ output } \" trim #{ sprintf ( " %.3f " , start ) } = #{ sprintf ( " %.3f " , stop ) } "
2015-05-04 22:48:14 +00:00
2015-08-09 18:37:43 +00:00
@@log . debug ( " trimming using: " + command )
2015-05-04 22:48:14 +00:00
2015-08-09 18:37:43 +00:00
sox_output = ` #{ command } `
2015-05-04 22:48:14 +00:00
2015-08-09 18:37:43 +00:00
result_code = $? . to_i
2015-05-04 22:48:14 +00:00
2015-08-09 18:37:43 +00:00
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
2015-05-04 22:48:14 +00:00
2015-08-09 18:37:43 +00:00
convert_mp3_cmd = " #{ APP_CONFIG . ffmpeg_path } -i \" #{ output } \" -ab 192k \" #{ output_mp3 } \" "
@@log . debug ( " converting to mp3 using: " + convert_mp3_cmd )
2015-05-04 22:48:14 +00:00
2015-08-09 18:37:43 +00:00
convert_output = ` #{ convert_mp3_cmd } `
2015-05-04 22:48:14 +00:00
2015-08-09 18:37:43 +00:00
result_code = $? . to_i
2015-05-04 22:48:14 +00:00
2015-08-09 18:37:43 +00:00
if result_code != 0
@@log . debug ( " fail #{ result_code } " )
@preview_generate_error = " unable to execute mp3 convert command #{ convert_output } "
else
2015-11-20 12:57:32 +00:00
convert_aac_cmd = " #{ APP_CONFIG . ffmpeg_path } -i \" #{ output } \" -c:a libfdk_aac -b:a 192k \" #{ output_aac } \" "
@@log . debug ( " converting to aac using: " + convert_aac_cmd )
convert_output = ` #{ convert_aac_cmd } `
result_code = $? . to_i
if result_code != 0
@@log . debug ( " fail #{ result_code } " )
@preview_generate_error = " unable to execute aac convert command #{ convert_output } "
else
ogg_digest = :: Digest :: MD5 . file ( output )
mp3_digest = :: Digest :: MD5 . file ( output_mp3 )
aac_digest = :: Digest :: MD5 . file ( output_aac )
self [ " preview_md5 " ] = ogg_md5 = ogg_digest . hexdigest
self [ " preview_mp3_md5 " ] = mp3_md5 = mp3_digest . hexdigest
self [ " preview_aac_md5 " ] = aac_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 )
@@log . debug ( " uploading aac preview to #{ self . preview_filename ( 'aac' ) } " )
s3_public_manager . upload ( self . preview_filename ( aac_md5 , 'aac' ) , output_aac , content_type : 'audio/aac' , content_md5 : aac_digest . base64digest )
self . skip_uploader = true
original_ogg_preview_url = self [ " preview_url " ]
original_mp3_preview_url = self [ " preview_mp3_url " ]
original_aac_preview_url = self [ " preview_aac_url " ]
self [ " preview_url " ] = self . preview_filename ( ogg_md5 , 'ogg' )
self [ " preview_length " ] = File . new ( output ) . size
self [ " preview_mp3_url " ] = self . preview_filename ( mp3_md5 , 'mp3' )
self [ " preview_mp3_length " ] = File . new ( output_mp3 ) . size
self [ " preview_aac_url " ] = self . preview_filename ( aac_md5 , 'aac' )
self [ " preview_aac_length " ] = File . new ( output_aac ) . 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 " ]
s3_public_manager . delete ( original_aac_preview_url ) if original_aac_preview_url && original_aac_preview_url != track [ " preview_aac_url " ]
rescue
puts " UNABLE TO CLEANUP OLD PREVIEW URL "
end
2015-05-04 22:48:14 +00:00
end
2015-08-09 18:37:43 +00:00
2015-05-04 22:48:14 +00:00
end
end
end
private
2014-12-05 21:03:40 +00:00
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