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

653 lines
21 KiB
Ruby
Raw Permalink Normal View History

# -*- coding: utf-8 -*-
module JamRuby
class JamTrack < ActiveRecord::Base
include JamRuby::S3ManagerMixin
TIME_SIGNATURES = %w{4/4 3/4 2/4 6/8 5/8'}
STATUS = %w{Staging Production Retired}
RECORDING_TYPE = %w{Cover Original}
PRO = %w{ASCAP BMI SESAC}
SALES_REGION = ['United States', 'Worldwide']
PRODUCT_TYPE = 'JamTrack'
2015-03-09 14:44:12 +00:00
@@log = Logging.logger[JamTrack]
attr_accessor :uploading_preview
attr_accessible :name, :description, :bpm, :time_signature, :status, :recording_type,
:original_artist, :songwriter, :publisher, :licensor, :licensor_id, :pro, :genres_jam_tracks_attributes, :sales_region, :price,
:reproduction_royalty, :public_performance_royalty, :reproduction_royalty_amount,
:licensor_royalty_amount, :pro_royalty_amount, :plan_code, :initial_play_silence, :jam_track_tracks_attributes,
:jam_track_tap_ins_attributes, :genre_ids, :version, :jmep_json, :jmep_text, :pro_ascap, :pro_bmi, :pro_sesac, :duration,
2016-08-03 01:46:15 +00:00
:server_fixation_date, :hfa_license_status, :hfa_license_desired, :alternative_license_status, :hfa_license_number, :hfa_song_code, :album_title, :year, :allow_free, as: :admin
validates :name, presence: true, length: {maximum: 200}
2015-03-09 14:44:12 +00:00
validates :plan_code, presence: true, uniqueness: true, length: {maximum: 50 }
validates :description, length: {maximum: 1000}
validates :time_signature, inclusion: {in: [nil] + [''] + TIME_SIGNATURES} # the empty string is needed because of activeadmin
validates :status, inclusion: {in: [nil] + STATUS}
validates :recording_type, inclusion: {in: [nil] + RECORDING_TYPE}
validates :original_artist, length: {maximum: 200}
validates :songwriter, length: {maximum: 1000}
validates :publisher, length: {maximum: 1000}
validates :sales_region, inclusion: {in: [nil] + SALES_REGION}
validates_format_of :price, with: /\A\d+\.*\d{0,2}\z/
2015-02-27 17:51:45 +00:00
validates :version, presence: true
validates :pro_ascap, inclusion: {in: [true, false]}
validates :pro_bmi, inclusion: {in: [true, false]}
validates :pro_sesac, inclusion: {in: [true, false]}
validates :public_performance_royalty, inclusion: {in: [nil, true, false]}
validates :reproduction_royalty, inclusion: {in: [nil, true, false]}
validates :public_performance_royalty, inclusion: {in: [nil, true, false]}
validates :duration, numericality: {only_integer: true}, :allow_nil => true
validates :hfa_license_status, inclusion: {in: [true, false]}
validates :hfa_license_desired, inclusion: {in: [true, false]}
validates :alternative_license_status, inclusion: {in: [true, false]}
validates :hfa_license_number, numericality: {only_integer: true}, :allow_nil => true
validates :hfa_song_code, length: {maximum: 200}
validates :album_title, length: {maximum: 200}
validates :slug, uniqueness: true
validates_format_of :reproduction_royalty_amount, with: /\A\d+\.*\d{0,4}\z/, :allow_blank => true
validates_format_of :licensor_royalty_amount, with: /\A\d+\.*\d{0,4}\z/, :allow_blank => true
2015-08-09 22:27:30 +00:00
belongs_to :licensor , class_name: 'JamRuby::JamTrackLicensor', foreign_key: 'licensor_id', :inverse_of => :jam_tracks
has_many :genres_jam_tracks, :class_name => "JamRuby::GenreJamTrack", :foreign_key => "jam_track_id", inverse_of: :jam_track
has_many :genres, :through => :genres_jam_tracks, :class_name => "JamRuby::Genre", :source => :genre
has_many :jam_track_tracks, -> { order('track_type ASC, position ASC, part ASC, instrument_id ASC' )},:class_name => "JamRuby::JamTrackTrack"
has_many :jam_track_tap_ins, -> { order('offset_time ASC')}, :class_name => "JamRuby::JamTrackTapIn"
has_many :jam_track_files, :class_name => "JamRuby::JamTrackFile"
has_many :jam_track_rights, :class_name => "JamRuby::JamTrackRight" #, inverse_of: 'jam_track', :foreign_key => "jam_track_id" # '
has_many :owners, :through => :jam_track_rights, :class_name => "JamRuby::User", :source => :user
2016-08-03 01:46:15 +00:00
has_many :playing_sessions, :class_name => "JamRuby::ActiveMusicSession", :dependent => :destroy
2016-08-03 01:46:15 +00:00
has_many :recordings, :class_name => "JamRuby::Recording", :dependent => :destroy
2015-02-27 17:51:45 +00:00
# VRFS-2916 jam_tracks.id is varchar: REMOVE
# has_many :plays, :class_name => "JamRuby::PlayablePlay", :foreign_key => :jam_track_id, :dependent => :destroy
# VRFS-2916 jam_tracks.id is varchar: ADD
has_many :plays, :class_name => "JamRuby::PlayablePlay", :as => :playable, :dependent => :destroy
2016-01-04 03:38:30 +00:00
has_many :jam_track_session, :class_name => "JamRuby::JamTrackSession"
# when we know what JamTrack this refund is related to, these are associated
belongs_to :recurly_transactions, class_name: 'JamRuby::RecurlyTransactionWebHook'
accepts_nested_attributes_for :jam_track_tracks, allow_destroy: true
accepts_nested_attributes_for :jam_track_tap_ins, allow_destroy: true
# we can make sure a few things stay in sync here.
# 1) the reproduction_royalty_amount has to stay in sync based on duration
# 2) the onboarding_exceptions JSON column
after_save :sync_reproduction_royalty
after_save :sync_onboarding_exceptions
2015-12-01 03:21:27 +00:00
def increment_version!
2015-11-30 23:54:17 +00:00
self.version = version.to_i + 1
save!
end
def sync_reproduction_royalty
# reproduction royalty table based on duration
# The statutory mechanical royalty rate for permanent digital downloads is:
# 9.10¢ per copy for songs 5 minutes or less, or
# 1.75¢ per minute or fraction thereof, per copy for songs over 5 minutes.
# So the base rate is 9.1 cents for anything up to 5 minutes.
# 5.01 to 6 minutes should be 10.5 cents.
# 6.01 to 7 minutes should be 12.25 cents.
# Etc.
royalty = nil
if self.duration
minutes = (self.duration - 1) / 60
extra_minutes = minutes - 4
extra_minutes = 0 if extra_minutes < 0
royalty = (0.091 + (0.0175 * extra_minutes)).round(5)
end
self.update_column(:reproduction_royalty_amount, royalty)
true
end
def sync_onboarding_exceptions
exceptions = {}
if self.duration.nil?
exceptions[:no_duration] = true
end
if self.genres.count == 0
exceptions[:no_genres] = true
end
if self.year.nil?
exceptions[:no_year] = true
end
if self.licensor.nil?
exceptions[:no_licensor] = true
end
if self.missing_instrument_info?
exceptions[:unknown_instrument] = true
end
if self.master_track.nil?
exceptions[:no_master] = true
end
if missing_previews?
exceptions[:missing_previews] = true
end
if duplicate_positions?
exceptions[:duplicate_positions] = true
end
if exceptions.keys.length == 0
self.update_column(:onboarding_exceptions, nil)
else
self.update_column(:onboarding_exceptions, exceptions)
end
true
end
2017-02-05 20:42:51 +00:00
def sale_display(variant = nil)
if variant == ShoppingCart::JAMTRACK_FULL
variant_desc = 'FULL'
elsif variant == ShoppingCart::JAMTRACK_DOWNLOAD
variant_desc = 'UPRGADE'
elsif variant == ShoppingCart::JAMTRACK_STREAM
variant_desc = 'FOR USE ONLY WITHIN APP'
else
variant_desc = 'UNKNOWN'
end
"JamTrack: #{name} - #{variant_desc}"
2015-11-29 19:58:10 +00:00
end
2017-02-05 20:42:51 +00:00
def duplicate_positions?
counter = {}
jam_track_tracks.each do |track|
count = counter[track.position]
if count.nil?
count = 0
end
counter[track.position] = count + 1
end
duplicate = false
counter.each do|position, count|
if count > 1
duplicate = true
break
end
end
duplicate
end
def missing_instrument_info?
missing_instrument_info = false
self.jam_track_tracks.each do |track|
if track.instrument_id == 'other' && (track.part == nil || track.part.start_with?('Other'))
missing_instrument_info = true
break
end
end
missing_instrument_info
end
def missing_previews?
missing_preview = false
self.jam_track_tracks.each do |track|
unless track.has_preview?
missing_preview = true
break
end
end
missing_preview
end
def onboard_warnings
warnings = []
warnings << 'POSITIONS' if duplicate_positions?
warnings << 'PREVIEWS'if missing_previews?
warnings << 'DURATION' if duration.nil?
warnings << 'JMEP' if jmep_json.blank?
warnings.join(',')
end
2015-03-31 21:17:17 +00:00
def band_jam_track_count
JamTrack.where(original_artist: original_artist).count
end
class << self
# @return array[artist_name(string)]
def all_artists
JamTrack.select("original_artist").
group("original_artist").
order('original_artist').
collect{|jam_track|jam_track.original_artist}
end
# @return array[JamTrack] for given artist_name
def tracks_for_artist(artist_name)
JamTrack.where("original_artist=?", artist_name).all
end
# special case of index
def autocomplete(options, user)
if options[:match].blank?
return {artists: [], songs: []}
end
options[:show_purchased_only] = options[:show_purchased_only]
options[:limit] = options[:limit] || 5
options[:artist_search] = options[:match]
artists, pager = artist_index(options, user)
options.delete(:artist_search)
options[:song_search] = options[:match]
options[:sort_by] = 'jamtrack'
songs, pager = index(options, user)
{artists: artists, songs:songs}
end
2016-03-17 12:30:44 +00:00
def purchase_stubs(user)
2016-03-16 21:11:10 +00:00
JamTrack.
2016-03-16 23:14:43 +00:00
select(['jam_tracks.id', :name, :original_artist, :year, 'jam_track_rights.created_at AS purchased_at']).
2016-03-16 21:11:10 +00:00
joins(:jam_track_rights).
where("jam_track_rights.user_id = ?", user.id).
includes(:genres).
order([:original_artist, :name])
end
def index(options, user)
if options[:page]
page = options[:page].to_i
per_page = options[:per_page].to_i
2015-03-27 14:54:01 +00:00
if per_page == 0
# try and see if limit was specified
limit = options[:limit]
limit ||= 20
limit = limit.to_i
per_page = limit
2015-03-27 14:54:01 +00:00
else
limit = per_page
end
start = (page -1 )* per_page
else
limit = options[:limit]
limit ||= 20
limit = limit.to_i
start = options[:start].presence
start = start.to_i || 0
page = 1 + start/limit
per_page = limit
end
query = JamTrack.joins(:jam_track_tracks)
.paginate(page: page, per_page: per_page)
if options[:show_purchased_only]
query = query.joins(:jam_track_rights)
query = query.where("jam_track_rights.user_id = ?", user.id)
end
if options[:search]
tsquery = Search.create_tsquery(options[:search])
if tsquery
query = query.where("(search_tsv @@ to_tsquery('jamenglish', ?))", tsquery)
end
end
if options[:artist_search]
tsquery = Search.create_tsquery(options[:artist_search])
if tsquery
query = query.where("(artist_tsv @@ to_tsquery('jamenglish', ?))", tsquery)
end
end
if options[:song_search]
tsquery = Search.create_tsquery(options[:song_search])
if tsquery
query = query.where("(name_tsv @@ to_tsquery('jamenglish', ?))", tsquery)
end
end
if options[:artist].present?
artist_param = options[:artist]
# todo: add licensor option
if artist_param == 'Stockton Helbing'
licensor = JamTrackLicensor.find_by_name('Stockton Helbing')
if licensor
query = query.where(licensor_id: licensor.id)
end
else
query = query.where("original_artist=?", options[:artist])
end
end
if options[:song].present?
query = query.where("name=?", options[:song])
end
if options[:id].present?
query = query.where("jam_tracks.id=?", options[:id])
end
if options[:group_artist]
query = query.select("original_artist, array_agg(jam_tracks.id) AS id, MIN(name) AS name, MIN(description) AS description, MIN(recording_type) AS recording_type, MIN(original_artist) AS original_artist, MIN(songwriter) AS songwriter, MIN(publisher) AS publisher, MIN(sales_region) AS sales_region, MIN(price) AS price, MIN(version) AS version")
query = query.group("original_artist")
query = query.order('jam_tracks.original_artist')
query = query.includes([{ jam_track_tracks: :instrument }, { genres_jam_tracks: :genre }])
else
query = query.group("jam_tracks.id")
if options[:sort_by] == 'jamtrack'
query = query.order('jam_tracks.name')
else
query = query.order('jam_tracks.original_artist, jam_tracks.name')
end
end
2016-02-09 11:00:39 +00:00
if (! user.try(:admin) && 'development' != Rails.env)
query = query.where("jam_tracks.status = ?", 'Production')
end
unless options[:genre].blank?
query = query.joins(:genres)
query = query.where('genre_id = ? ', options[:genre])
end
2015-11-30 23:54:17 +00:00
query = query.where("jam_track_tracks.instrument_id = '#{options[:instrument]}' and jam_track_tracks.track_type = 'Track'") unless options[:instrument].blank?
query = query.where("jam_tracks.sales_region = '#{options[:availability]}'") unless options[:availability].blank?
# FIXME: n+1 queries for rights and genres
# query = query.includes([{ jam_track_tracks: :instrument },
# :jam_track_tap_ins,
# :jam_track_rights,
# :genres])
# { genres_jam_tracks: :genre },
2015-12-02 21:01:18 +00:00
# query = query.includes([{ jam_track_tracks: :instrument },
# { genres_jam_tracks: :genre }])
count = query.total_entries
if count == 0
[query, nil, count]
2015-11-08 23:02:18 +00:00
elsif query.length < limit
[query, nil, count]
else
[query, start + limit, count]
end
end
# provides artist names and how many jamtracks are available for each
def artist_index(options, user)
if options[:page]
page = options[:page].to_i
per_page = options[:per_page].to_i
if per_page == 0
# try and see if limit was specified
limit = options[:limit]
limit ||= 100
limit = limit.to_i
else
limit = per_page
end
start = (page -1 )* per_page
limit = per_page
else
limit = options[:limit]
limit ||= 100
limit = limit.to_i
start = options[:start].presence
start = start.to_i || 0
page = 1 + start/limit
per_page = limit
end
query = JamTrack.paginate(page: page, per_page: per_page)
2025-02-12 09:37:57 +00:00
query = query.select("original_artist, original_artist_slug, count(original_artist) AS song_count")
query = query.group("original_artist, original_artist_slug")
query = query.order('jam_tracks.original_artist')
query = query.where("jam_tracks.status = ?", 'Production') unless user.admin
if options[:show_purchased_only]
query = query.joins(:jam_track_rights)
query = query.where("jam_track_rights.user_id = ?", user.id)
end
if options[:artist_search]
tsquery = Search.create_tsquery(options[:artist_search])
if tsquery
query = query.where("(artist_tsv @@ to_tsquery('jamenglish', ?))", tsquery)
end
end
unless options[:genre].blank?
query = query.joins(:genres)
query = query.where('genre_id = ? ', options[:genre])
end
query = query.where("jam_track_tracks.instrument_id = '#{options[:instrument]}'") unless options[:instrument].blank?
query = query.where("jam_tracks.sales_region = '#{options[:availability]}'") unless options[:availability].blank?
if query.length == 0
[query, nil]
elsif query.length < limit
[query, nil]
else
[query, start + limit]
end
end
end
2015-11-30 11:07:47 +00:00
def click_track_file
JamTrackFile.where(jam_track_id: self.id).where(file_type: 'ClickWav').first
end
2015-11-18 21:22:06 +00:00
def click_track
2015-11-30 11:07:47 +00:00
JamTrackTrack.where(jam_track_id: self.id).where(track_type: 'Click').first
2015-11-18 21:22:06 +00:00
end
2015-12-04 14:32:18 +00:00
def has_count_in?
has_count_in = false
if jmep_json
jmep = jmep_json
2015-12-04 14:32:18 +00:00
if jmep["Events"]
events = jmep["Events"]
metronome = nil
events.each do |event|
if event.has_key?("metronome")
metronome = event["metronome"]
break
end
end
if metronome
has_count_in = true
end
end
end
has_count_in
end
def master_track
JamTrackTrack.where(jam_track_id: self.id).where(track_type: 'Master').first
end
def stem_tracks
JamTrackTrack.where(jam_track_id: self.id).where("track_type = 'Track' or track_type = 'Click'")
end
def can_download?(user)
owners.include?(user)
end
2017-02-05 20:42:51 +00:00
def right_for_user(user, variant = nil)
query = jam_track_rights.where("user_id=?", user)
if variant
if variant == ShoppingCart::JAMTRACK_DOWNLOAD
query = query.where('can_download', true)
elsif variant == ShoppingCart::JAMTRACK_FULL
query = query.where('can_download', true)
elsif variant == ShoppingCart::JAMTRACK_STREAM
else
throw 'unknown variant ' + variant
end
end
query.first
end
2017-02-05 20:42:51 +00:00
2015-09-10 11:08:29 +00:00
def mixdowns_for_user(user)
JamTrackMixdown.where(user_id: user.id).where(jam_track_id: self.id)
2015-09-10 11:08:29 +00:00
end
2017-02-05 20:42:51 +00:00
def upgrade_price
variant_price('download')
end
def variant_price(variant)
if variant == 'full'
download_price
elsif variant == 'download'
download_price - price
else
price
end
end
2015-05-28 13:20:14 +00:00
def short_plan_code
prefix = 'jamtrack-'
plan_code[prefix.length..-1]
end
# http://stackoverflow.com/questions/4308377/ruby-post-title-to-slug
def sluggarize(field)
field.downcase.strip.gsub(' ', '-').gsub(/[^\w-]/, '')
end
def generate_slug
self.slug = sluggarize(original_artist) + '-' + sluggarize(name)
2015-11-20 12:57:32 +00:00
2015-12-18 21:32:40 +00:00
if licensor && licensor.slug.present?
#raise "no slug on licensor #{licensor.id}" if licensor.slug.nil?
2015-11-20 12:57:32 +00:00
self.slug << "-" + licensor.slug
end
end
def gen_plan_code
# remove all non-alphanumeric chars from artist as well as name
artist_code = original_artist.gsub(/[^0-9a-z]/i, '').downcase
name_code = name.gsub(/[^0-9a-z]/i, '').downcase
self.plan_code = "jamtrack-#{artist_code[0...20]}-#{name_code}"
if licensor && licensor.slug
2015-11-20 12:57:32 +00:00
raise "no slug on licensor #{licensor.id}" if licensor.slug.nil?
self.plan_code << "-" + licensor.slug
end
self.plan_code = self.plan_code[0...50] # make sure it's a max of 50 long
end
2015-10-28 13:10:03 +00:00
def to_s
"#{self.name} (#{self.original_artist})"
end
def self.latestPurchase(user_id)
JamTrackRight
.select('created_at')
.where(user_id: user_id)
.order('created_at DESC')
.limit(1)
.first
.try(:created_at)
.to_i
end
2015-11-18 21:22:06 +00:00
attr_accessor :preview_generate_error
before_save :jmep_json_generate
validate :jmep_text_validate
def jmep_text_validate
begin
JmepManager.execute(self.jmep_text)
rescue ArgumentError => err
errors.add(:jmep_text, err.to_s)
end
end
def jmep_json_generate
self.licensor_id = nil if self.licensor_id == ''
self.jmep_json = nil if self.jmep_json == ''
self.time_signature = nil if self.time_signature == ''
begin
self[:jmep_json] = JmepManager.execute(self.jmep_text)
rescue ArgumentError => err
#errors.add(:jmep_text, err.to_s)
end
end
# used in mobile simulate purchase
def self.forsale(user)
sql =<<SQL
SELECT jt.* FROM jam_tracks jt
WHERE jt.id NOT IN (
SELECT jt.id
FROM jam_tracks jt
JOIN jam_track_rights AS jtr ON jtr.jam_track_id = jt.id
WHERE jtr.user_id = '#{user.id}'
)
LIMIT 1
SQL
self.find_by_sql(sql).first
end
2016-03-16 21:11:10 +00:00
def genre_name
self.genres.first.try(:description)
end
end
end