Initial commit
This commit is contained in:
110
app/models/account.rb
Normal file
110
app/models/account.rb
Normal file
@@ -0,0 +1,110 @@
|
||||
class Account < ApplicationRecord
|
||||
include Syncable
|
||||
include PgSearch
|
||||
has_many :account_auths
|
||||
has_many :users, through: :account_auths
|
||||
has_many :projects, dependent: :destroy
|
||||
has_many :videos, through: :projects
|
||||
has_many :contract_templates, through: :projects
|
||||
|
||||
validates :name, presence: true
|
||||
validates :slug, presence: true, uniqueness: true
|
||||
|
||||
before_validation :set_slug
|
||||
|
||||
scope :order_by_name, -> { order(name: :asc) }
|
||||
|
||||
has_one_attached :logo
|
||||
|
||||
pg_search_scope :search, {
|
||||
against: [:name, :slug],
|
||||
using: {
|
||||
tsearch: {any_word: true, prefix: true},
|
||||
trigram: {},
|
||||
dmetaphone: {any_word: true}
|
||||
}
|
||||
}
|
||||
|
||||
def managers
|
||||
users.where(account_auths: { role: :account_manager })
|
||||
end
|
||||
|
||||
def current_month_video_duration_total
|
||||
@current_month_video_duration_total ||=
|
||||
Video.with_attached_file.where(project: projects, created_at: Time.zone.now.beginning_of_month..Time.zone.now.end_of_month).sum { |video| video.file.blob.metadata.dig("duration").to_f }
|
||||
end
|
||||
|
||||
def video_duration_total
|
||||
@video_duration_total ||=
|
||||
Video.with_attached_file.where(project: projects).sum { |video| video.file.blob.metadata.dig("duration").to_f }
|
||||
end
|
||||
|
||||
def storage_total
|
||||
ActiveStorage::Blob.joins(:attachments).merge(
|
||||
ActiveStorage::Attachment.where(record: [
|
||||
AppearanceRelease.where(project: projects),
|
||||
TalentRelease.where(project: projects),
|
||||
MaterialRelease.where(project: projects),
|
||||
LocationRelease.where(project: projects),
|
||||
AcquiredMediaRelease.where(project: projects),
|
||||
Import.where(project: projects),
|
||||
MusicRelease.where(project: projects),
|
||||
Video.where(project: projects),
|
||||
Directory.where(project: projects),
|
||||
Download.where(project: projects),
|
||||
User.joins(:project_memberships).where(project_memberships: { project: projects }),
|
||||
Broadcast.where(project: projects),
|
||||
ZoomMeeting.where(project: projects),
|
||||
self
|
||||
])).sum(:byte_size).to_f
|
||||
end
|
||||
|
||||
def to_param
|
||||
slug
|
||||
end
|
||||
|
||||
def me_suite_enabled?
|
||||
plan_uid.to_s == "me_suite"
|
||||
end
|
||||
|
||||
def deliverme_enabled?
|
||||
plan_uid.to_s == "me_suite" || plan_uid.to_s == "deliverme"
|
||||
end
|
||||
|
||||
def directme_enabled?
|
||||
plan_uid.to_s == "me_suite" || plan_uid.to_s == "directme"
|
||||
end
|
||||
|
||||
def releaseme_enabled?
|
||||
plan_uid.to_s == "me_suite" || plan_uid.to_s == "releaseme"
|
||||
end
|
||||
|
||||
def plan_name
|
||||
case plan_uid.to_s
|
||||
when "deliverme"
|
||||
"DeliverME"
|
||||
when "directme"
|
||||
"DirectME"
|
||||
when "releaseme"
|
||||
"ReleaseME"
|
||||
when "me_suite"
|
||||
"ME Suite"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_slug
|
||||
return if name.blank? || slug.present?
|
||||
|
||||
counter = 1
|
||||
begin
|
||||
if counter == 1
|
||||
self.slug = name.parameterize
|
||||
else
|
||||
self.slug = [name.parameterize, counter].join("-")
|
||||
end
|
||||
counter += 1
|
||||
end while self.class.exists?(slug: self.slug)
|
||||
end
|
||||
end
|
||||
8
app/models/account_auth.rb
Normal file
8
app/models/account_auth.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
class AccountAuth < ApplicationRecord
|
||||
belongs_to :user
|
||||
belongs_to :account
|
||||
|
||||
validates :user_id, uniqueness: { scope: :account_id, message: "already added to account." }
|
||||
|
||||
enum role: { associate: 0, project_manager: 1, account_manager: 2 }
|
||||
end
|
||||
59
app/models/acquired_media_release.rb
Normal file
59
app/models/acquired_media_release.rb
Normal file
@@ -0,0 +1,59 @@
|
||||
class AcquiredMediaRelease < ApplicationRecord
|
||||
include Confirmable
|
||||
include Contractable
|
||||
include Exploitable
|
||||
include Notable
|
||||
include Releasable
|
||||
include Searchable
|
||||
include Taggable
|
||||
include Signable
|
||||
include Syncable
|
||||
include PersonName
|
||||
|
||||
has_many :file_infos, as: :releasable, dependent: :destroy
|
||||
|
||||
accepts_nested_attributes_for :file_infos
|
||||
|
||||
composed_of :person_address,
|
||||
class_name: "Address",
|
||||
mapping: [
|
||||
%w(person_address_street1 street1),
|
||||
%w(person_address_street2 street2),
|
||||
%w(person_address_city city),
|
||||
%w(person_address_state state),
|
||||
%w(person_address_zip zip),
|
||||
%w(person_address_country country)
|
||||
]
|
||||
|
||||
validates :name, presence: true
|
||||
validates :person_email, email: true, allow_blank: true
|
||||
|
||||
# These validations apply to releases created natively by the system (i.e. not imported from elsewhere)
|
||||
with_options on: :native do
|
||||
validates :signature, attached: true
|
||||
end
|
||||
|
||||
# These validations apply to releases imported to the system from an outside source
|
||||
with_options on: :non_native do
|
||||
validates :contract, attached: true
|
||||
end
|
||||
|
||||
searchable_on %i[
|
||||
name
|
||||
person_address_street1 person_address_street2 person_address_city person_address_state person_address_zip person_address_country
|
||||
]
|
||||
|
||||
CATEGORIES = ["Artwork", "Film Footage", "Video Footage", "Still Photograph"].freeze
|
||||
|
||||
def minor?
|
||||
false
|
||||
end
|
||||
|
||||
def contact_person
|
||||
@contact_person ||= Contact.new(person_name, person_address, person_email, person_phone)
|
||||
end
|
||||
|
||||
def uses_edl?
|
||||
true
|
||||
end
|
||||
end
|
||||
53
app/models/address.rb
Normal file
53
app/models/address.rb
Normal file
@@ -0,0 +1,53 @@
|
||||
class Address
|
||||
attr_accessor :street1, :street2, :city, :state, :zip, :country
|
||||
|
||||
def initialize(street1, street2, city, state, zip, country)
|
||||
@street1 = street1
|
||||
@street2 = street2
|
||||
@city = city
|
||||
@state = state
|
||||
@zip = zip
|
||||
@country = country
|
||||
end
|
||||
|
||||
def to_s(format: nil)
|
||||
case format
|
||||
when :full
|
||||
join_with_newline(
|
||||
street1,
|
||||
street2,
|
||||
join_with_comma(city, state_and_zip),
|
||||
country
|
||||
)
|
||||
when :condensed
|
||||
join_with_newline(
|
||||
join_with_comma(street1, street2),
|
||||
join_with_comma(city, state_and_zip, country),
|
||||
)
|
||||
else
|
||||
join_with_comma(street1, street2, city, state_and_zip, country)
|
||||
end
|
||||
end
|
||||
|
||||
def present?
|
||||
[street1, street2, city, state, zip, country].any?(&:present?)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def join_without_blanks(*parts, separator: ", ")
|
||||
parts.reject(&:blank?).join(separator)
|
||||
end
|
||||
|
||||
def join_with_comma(*parts)
|
||||
join_without_blanks(*parts)
|
||||
end
|
||||
|
||||
def join_with_newline(*parts)
|
||||
join_without_blanks(*parts, separator: "\n")
|
||||
end
|
||||
|
||||
def state_and_zip
|
||||
join_without_blanks(state, zip, separator: " ")
|
||||
end
|
||||
end
|
||||
14
app/models/admin_signed_in_constraint.rb
Normal file
14
app/models/admin_signed_in_constraint.rb
Normal file
@@ -0,0 +1,14 @@
|
||||
class AdminSignedInConstraint
|
||||
def matches?(request)
|
||||
signed_in?(request) && signed_in_user_is_admin?(request)
|
||||
end
|
||||
|
||||
def signed_in?(request)
|
||||
Oath::Constraints::SignedIn.new.matches?(request)
|
||||
end
|
||||
|
||||
def signed_in_user_is_admin?(request)
|
||||
warden = request.env['warden']
|
||||
warden && warden.user.admin?
|
||||
end
|
||||
end
|
||||
56
app/models/analysis_notification.rb
Normal file
56
app/models/analysis_notification.rb
Normal file
@@ -0,0 +1,56 @@
|
||||
class AnalysisNotification
|
||||
def self.build(type, job_id)
|
||||
case type.to_s
|
||||
when "audio"
|
||||
AudioAnalysisNotification.new(job_id)
|
||||
when "video"
|
||||
VideoAnalysisNotification.new(job_id)
|
||||
end
|
||||
end
|
||||
|
||||
class VideoAnalysisNotification
|
||||
attr_reader :job_id
|
||||
|
||||
def initialize(job_id)
|
||||
@job_id = job_id
|
||||
end
|
||||
|
||||
def video
|
||||
# TODO: add index for analysis_uid
|
||||
@video ||= Video.find_by!(analysis_uid: job_id)
|
||||
end
|
||||
|
||||
def success!
|
||||
video.analysis_success!
|
||||
ProjectsChannel.broadcast_video_analysis_update(video)
|
||||
end
|
||||
|
||||
def failure!
|
||||
video.analysis_failure!
|
||||
ProjectsChannel.broadcast_video_analysis_update(video)
|
||||
end
|
||||
end
|
||||
|
||||
class AudioAnalysisNotification
|
||||
attr_reader :job_id
|
||||
|
||||
def initialize(job_id)
|
||||
@job_id = job_id
|
||||
end
|
||||
|
||||
def video
|
||||
# TODO: add index for audio_analysis_uid
|
||||
@video ||= Video.find_by!(audio_analysis_uid: job_id)
|
||||
end
|
||||
|
||||
def success!
|
||||
video.audio_analysis_success!
|
||||
ProjectsChannel.broadcast_video_analysis_update(video)
|
||||
end
|
||||
|
||||
def failure!
|
||||
video.audio_analysis_failure!
|
||||
ProjectsChannel.broadcast_video_analysis_update(video)
|
||||
end
|
||||
end
|
||||
end
|
||||
59
app/models/app_host.rb
Normal file
59
app/models/app_host.rb
Normal file
@@ -0,0 +1,59 @@
|
||||
class AppHost
|
||||
attr_reader :env_vars, :current_env
|
||||
|
||||
def initialize(env_vars = ENV, current_env = Rails.env)
|
||||
@env_vars = env_vars
|
||||
@current_env = current_env
|
||||
end
|
||||
|
||||
def domain
|
||||
env_vars.fetch("DOMAIN") { default_domain }
|
||||
end
|
||||
|
||||
def port
|
||||
env_vars.fetch("WEB_PORT") { default_port }
|
||||
end
|
||||
|
||||
def domain_with_port
|
||||
[domain, port].compact.join(":")
|
||||
end
|
||||
|
||||
def protocol
|
||||
using_ssl? ? :https : :http
|
||||
end
|
||||
|
||||
def using_ssl?
|
||||
env_vars.fetch("USE_SSL") { default_use_ssl }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
DEFAULTS = {
|
||||
development: {
|
||||
host: "localhost",
|
||||
port: 3000,
|
||||
use_ssl: false,
|
||||
},
|
||||
test: {
|
||||
host: "localhost",
|
||||
port: 31337,
|
||||
use_ssl: false,
|
||||
},
|
||||
production: {
|
||||
host: "bigmedia.ai",
|
||||
use_ssl: true,
|
||||
}
|
||||
}
|
||||
|
||||
def default_domain
|
||||
DEFAULTS.dig(current_env.to_sym, :host)
|
||||
end
|
||||
|
||||
def default_port
|
||||
DEFAULTS.dig(current_env.to_sym, :port)
|
||||
end
|
||||
|
||||
def default_use_ssl
|
||||
DEFAULTS.dig(current_env.to_sym, :use_ssl)
|
||||
end
|
||||
end
|
||||
120
app/models/appearance_release.rb
Normal file
120
app/models/appearance_release.rb
Normal file
@@ -0,0 +1,120 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AppearanceRelease < ApplicationRecord
|
||||
include Confirmable
|
||||
include Contractable
|
||||
include Exploitable
|
||||
include Notable
|
||||
include Releasable
|
||||
include Searchable
|
||||
include Signable
|
||||
include Syncable
|
||||
include Taggable
|
||||
include PersonName
|
||||
include GuardianPhotoable
|
||||
include GuardianName
|
||||
|
||||
has_one_attached :person_photo
|
||||
|
||||
# These validations apply to all releases
|
||||
validates :person_email, email: true, allow_blank: true
|
||||
validates :person_first_name, :person_last_name, presence: true
|
||||
|
||||
scope :order_by_name, -> { order(:person_last_name) }
|
||||
|
||||
def self.random_contract_number
|
||||
rand(1_000_000..9_999_998) # random 7 digit number
|
||||
end
|
||||
|
||||
# We don't care for the argument but method WILL receive option name
|
||||
# when called from inside with_option block, hence * argument
|
||||
def self.face_photo_acceptable_content_types(*)
|
||||
['image/png', 'image/jpeg']
|
||||
end
|
||||
|
||||
def self.acceptable_import_file_extensions
|
||||
['.png', '.jpeg', '.jpg', '.pdf']
|
||||
end
|
||||
|
||||
# These validations apply to releases being signed by a minor
|
||||
with_options if: :minor? do
|
||||
validates :guardian_first_name, :guardian_last_name, presence: true
|
||||
end
|
||||
|
||||
validates :person_photo, content_type: face_photo_acceptable_content_types
|
||||
|
||||
# These validations apply to releases created natively by the system (i.e. not imported from elsewhere)
|
||||
with_options on: :native do
|
||||
validates :signature, attached: true
|
||||
validates :person_photo, attached: true, content_type: face_photo_acceptable_content_types
|
||||
validate :person_photo_is_acceptable, if: :new_record? # Only validate photos on new releases
|
||||
end
|
||||
|
||||
def contract_or_photo_is_attached
|
||||
return if person_photo.attached? || contract.attached?
|
||||
|
||||
errors[:base] << I18n.t('appearance_releases.index.imported_appearance_release_missing_attachment')
|
||||
end
|
||||
|
||||
# These validations apply to releases imported to the system from an outside source
|
||||
with_options on: :non_native do
|
||||
validate :contract_or_photo_is_attached
|
||||
validates :person_photo, content_type: face_photo_acceptable_content_types
|
||||
end
|
||||
|
||||
scope :with_person_photo, -> { joins(:person_photo_attachment) }
|
||||
scope :with_person_photo_and_contract, -> { joins(:person_photo_attachment, :contract_attachment) }
|
||||
scope :without_person_photo_or_contract, -> { where.not(id: with_person_photo_and_contract) }
|
||||
scope :complete, -> { with_person_photo_and_contract }
|
||||
scope :incomplete, -> { without_person_photo_or_contract }
|
||||
scope :having_no_person_photo, -> { left_joins(:person_photo_attachment).group(:id).having('COUNT(active_storage_attachments) = 0') }
|
||||
scope :with_person_name, ->(name) { where('person_first_name ILIKE ? OR person_last_name ILIKE ?', "%#{name}%") }
|
||||
|
||||
searchable_on %i[person_first_name person_last_name person_address person_email person_phone]
|
||||
|
||||
# All releases must respond to the following messages
|
||||
def name
|
||||
person_name
|
||||
end
|
||||
|
||||
def filename_suffix
|
||||
"#{person_last_name} #{person_first_name}"
|
||||
end
|
||||
|
||||
def photo
|
||||
person_photo
|
||||
end
|
||||
|
||||
def photos
|
||||
photo.attached? ? [photo] : []
|
||||
end
|
||||
|
||||
def contact_person
|
||||
@contact_person ||= Contact.new(name, person_address, person_email, person_phone)
|
||||
end
|
||||
|
||||
def uses_edl?
|
||||
true
|
||||
end
|
||||
|
||||
def contract_file_name
|
||||
"#{project.name.parameterize}_#{contract_template.release_type}_#{(signed_at || created_at).strftime('%Y.%m.%d')}_#{release_number}_#{filename_suffix.parameterize}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Validates the quality of the person photo
|
||||
def person_photo_is_acceptable
|
||||
return unless person_photo.attached?
|
||||
|
||||
# Call a remote API to perform the validation
|
||||
image_validation = BrayniacAI::Validation.create(bucket_name: ENV['AWS_BUCKET'], object_name: person_photo.key)
|
||||
|
||||
unless image_validation.valid
|
||||
errors.add(:person_photo, image_validation.error)
|
||||
end
|
||||
rescue ActiveResource::ConnectionError => e
|
||||
# TODO
|
||||
Rails.logger.error(e)
|
||||
end
|
||||
end
|
||||
4
app/models/applicable_medium.rb
Normal file
4
app/models/applicable_medium.rb
Normal file
@@ -0,0 +1,4 @@
|
||||
class ApplicableMedium < ApplicationRecord
|
||||
include Freeformable
|
||||
end
|
||||
|
||||
6
app/models/application_record.rb
Normal file
6
app/models/application_record.rb
Normal file
@@ -0,0 +1,6 @@
|
||||
class ApplicationRecord < ActiveRecord::Base
|
||||
self.abstract_class = true
|
||||
|
||||
scope :order_by_recent, -> { order(created_at: :desc) }
|
||||
scope :order_by_recently_updated, -> { order(updated_at: :desc) }
|
||||
end
|
||||
72
app/models/audio_analysis.rb
Normal file
72
app/models/audio_analysis.rb
Normal file
@@ -0,0 +1,72 @@
|
||||
class AudioAnalysis
|
||||
def initialize(video)
|
||||
@video = video
|
||||
end
|
||||
|
||||
# Use the custom hash to generate JSON format
|
||||
def as_json(*)
|
||||
to_hash
|
||||
end
|
||||
|
||||
def to_hash
|
||||
{
|
||||
edl_bucket_name: aws_bucket_name,
|
||||
edl_object_name: video.audio_only_edl_file.key,
|
||||
acquired_audio: acquired_audio_list,
|
||||
original_music: original_music_list,
|
||||
}
|
||||
end
|
||||
|
||||
def results
|
||||
response.results
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :video
|
||||
|
||||
def aws_bucket_name
|
||||
ENV["AWS_BUCKET"]
|
||||
end
|
||||
|
||||
def acquired_audio_list
|
||||
video.project.acquired_media_releases.flat_map do |acquired_media_release|
|
||||
acquired_media_release.file_infos.audio.map do |file_info|
|
||||
{ id: file_info.id, filename: file_info.filename }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def original_music_list
|
||||
video.project.music_releases.flat_map do |music_release|
|
||||
music_release.file_infos.map do |file_info|
|
||||
{ id: file_info.id, filename: file_info.filename, composers: music_release.composer_info, publishers: music_release.publisher_info }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def analysis_uid
|
||||
video.audio_analysis_uid
|
||||
end
|
||||
|
||||
def fps
|
||||
edl_event_gateway.fps
|
||||
end
|
||||
|
||||
def edl_offset_seconds
|
||||
edl_event_gateway.edl_offset_seconds
|
||||
end
|
||||
|
||||
def edl_event_gateway
|
||||
# TODO: Eventually cache this on the video itself to avoid an extra API call, but for now keep it simple
|
||||
@edl_event_gateway ||= begin
|
||||
files_for_request = AudioFilesForRequest.new(video, video.edl_timecode_start)
|
||||
EdlEventGateway.new(files_for_request, "00:00:00:00", "00:00:00:00")
|
||||
end
|
||||
end
|
||||
|
||||
def response
|
||||
@response ||= BrayniacAI::AudioRecognition.find(analysis_uid,
|
||||
params: { fps: fps, edl_offset_seconds: edl_offset_seconds })
|
||||
end
|
||||
end
|
||||
25
app/models/audio_confirmation.rb
Normal file
25
app/models/audio_confirmation.rb
Normal file
@@ -0,0 +1,25 @@
|
||||
class AudioConfirmation < ApplicationRecord
|
||||
belongs_to :video
|
||||
|
||||
validates :time_elapsed, presence: true
|
||||
validates :confirmation_type, :inclusion => { :in => ["original_music", "library_music"] }
|
||||
|
||||
scope :original, -> { where(confirmation_type: "original_music") }
|
||||
|
||||
def appears_at
|
||||
Timecode.from_seconds(time_elapsed.to_f).to_s
|
||||
end
|
||||
|
||||
def presented_source_file_name
|
||||
case (confirmation_type)
|
||||
when "original_music"
|
||||
"(O) #{source_file_name}"
|
||||
when "library_music"
|
||||
"(L) #{source_file_name}"
|
||||
end
|
||||
end
|
||||
|
||||
def confirmation_type_library?
|
||||
confirmation_type == "library_music"
|
||||
end
|
||||
end
|
||||
28
app/models/audio_files_for_request.rb
Normal file
28
app/models/audio_files_for_request.rb
Normal file
@@ -0,0 +1,28 @@
|
||||
class AudioFilesForRequest
|
||||
attr_reader :start_timecode_offset
|
||||
|
||||
def initialize(video, start_timecode_offset)
|
||||
@video = video
|
||||
@start_timecode_offset = start_timecode_offset
|
||||
end
|
||||
|
||||
def file_object_name
|
||||
video.file.key if video.file.attached?
|
||||
end
|
||||
|
||||
def edl_file_object_name
|
||||
video.audio_only_edl_file.key if video.audio_only_edl_file.attached?
|
||||
end
|
||||
|
||||
def aws_bucket_name
|
||||
ENV["AWS_BUCKET"]
|
||||
end
|
||||
|
||||
def job_id
|
||||
video.analysis_uid
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :video
|
||||
end
|
||||
5
app/models/big_media_time.rb
Normal file
5
app/models/big_media_time.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
class BigMediaTime
|
||||
def self.time_zone_now
|
||||
Time.zone.now
|
||||
end
|
||||
end
|
||||
68
app/models/blank_contract.rb
Normal file
68
app/models/blank_contract.rb
Normal file
@@ -0,0 +1,68 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class BlankContract
|
||||
def initialize(releasable, copies = 1)
|
||||
@releasable = releasable
|
||||
integer_number_of_copies = copies.to_i
|
||||
@copies = integer_number_of_copies.positive? ? integer_number_of_copies : 1
|
||||
end
|
||||
|
||||
def to_pdf
|
||||
kit = PDFKit.new(as_html)
|
||||
kit.to_file("tmp/#{filename}")
|
||||
end
|
||||
|
||||
def filename
|
||||
"blank_#{contract_template.release_type}_release.pdf"
|
||||
end
|
||||
|
||||
def render_attributes
|
||||
attributes = {
|
||||
layout: 'contract_pdf',
|
||||
template: 'blank_contracts/pdf',
|
||||
locals: { releasable: @releasable, contract_template: contract_template, copies: @copies }
|
||||
}
|
||||
add_logo_to_attributes attributes
|
||||
add_codes_to_attributes attributes
|
||||
attributes
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def contract_template
|
||||
@releasable.contract_template
|
||||
end
|
||||
|
||||
def project
|
||||
@releasable.project
|
||||
end
|
||||
|
||||
def locale
|
||||
@releasable.locale
|
||||
end
|
||||
|
||||
def as_html
|
||||
I18n.with_locale(locale) do
|
||||
ApplicationController.render render_attributes
|
||||
end
|
||||
end
|
||||
|
||||
def add_logo_to_attributes(attributes)
|
||||
logo = @releasable.project.account.logo
|
||||
attributes[:locals][:logo] = logo if logo.attached?
|
||||
end
|
||||
|
||||
def add_codes_to_attributes(attributes)
|
||||
attributes[:locals][:qr_codes] = []
|
||||
attributes[:locals][:serial_numbers] = []
|
||||
|
||||
@copies.times do
|
||||
random_number = SecureRandom.hex 4
|
||||
custom_url = "#{contract_template.to_global_id.to_s}/#{random_number}"
|
||||
qr_code = QrCode.new(custom_url)
|
||||
|
||||
attributes[:locals][:qr_codes] << qr_code.to_base64_png
|
||||
attributes[:locals][:serial_numbers] << random_number.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
11
app/models/bookmark.rb
Normal file
11
app/models/bookmark.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
class Bookmark < ApplicationRecord
|
||||
belongs_to :video
|
||||
|
||||
validates :time_elapsed, presence: true
|
||||
|
||||
enum category: { "Other": 0, "Audio": 1, "Graphics": 2, "Video": 3 }
|
||||
|
||||
def appears_at
|
||||
Timecode.from_seconds(time_elapsed.to_f).to_s
|
||||
end
|
||||
end
|
||||
62
app/models/broadcast.rb
Normal file
62
app/models/broadcast.rb
Normal file
@@ -0,0 +1,62 @@
|
||||
class Broadcast < ApplicationRecord
|
||||
include PgSearch
|
||||
|
||||
belongs_to :project
|
||||
has_many :broadcast_recordings, dependent: :destroy
|
||||
has_many_attached :files
|
||||
|
||||
has_secure_token
|
||||
|
||||
validates :name, presence: true
|
||||
|
||||
enum status: [:created, :active, :idle]
|
||||
enum streamer_status: [:idle, :connected, :recording, :disconnected], _prefix: "streamer"
|
||||
|
||||
# Should we use callbacks for this, or something else?
|
||||
after_create :create_mux_live_stream
|
||||
after_destroy :destroy_mux_live_stream
|
||||
|
||||
pg_search_scope :search, {
|
||||
against: [:name],
|
||||
using: {
|
||||
tsearch: { any_word: true, prefix: true },
|
||||
trigram: {},
|
||||
dmetaphone: { any_word: true }
|
||||
}
|
||||
}
|
||||
|
||||
def stream_playback_url
|
||||
# TODO: This may change when we start using signed playback policy
|
||||
"https://stream.mux.com/#{stream_playback_uid}.m3u8"
|
||||
end
|
||||
|
||||
def stream_server_url
|
||||
ENV['MUX_BROADCAST_SERVER_URL']
|
||||
end
|
||||
|
||||
def zoom_meeting_url
|
||||
project.zoom_meeting_url
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_mux_live_stream
|
||||
stream = MuxLiveStream.new
|
||||
|
||||
self.stream_uid = stream.id
|
||||
self.stream_key = stream.key
|
||||
self.stream_playback_uid = stream.playback_id
|
||||
self.save!
|
||||
end
|
||||
|
||||
def destroy_mux_live_stream
|
||||
begin
|
||||
stream = MuxLiveStream.new
|
||||
stream.destroy_stream(self.stream_uid)
|
||||
rescue MuxRuby::NotFoundError
|
||||
rescue MuxRuby::ApiError => e
|
||||
Rails.logger.error("Failed to delete live stream id #{stream_uid}\n" + e.message)
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
end
|
||||
end
|
||||
19
app/models/broadcast_recording.rb
Normal file
19
app/models/broadcast_recording.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
class BroadcastRecording < ApplicationRecord
|
||||
belongs_to :broadcast
|
||||
|
||||
delegate :name, to: :broadcast, prefix: :broadcast
|
||||
|
||||
validates :asset_uid, uniqueness: true
|
||||
|
||||
def download_url
|
||||
"https://stream.mux.com/#{asset_playback_uid}/#{file_name}?download=#{download_file_name}"
|
||||
end
|
||||
|
||||
def playback_url
|
||||
"https://stream.mux.com/#{asset_playback_uid}/#{file_name}"
|
||||
end
|
||||
|
||||
def download_file_name
|
||||
"#{broadcast_name}_Date_#{created_at.strftime("%Y-%m-%d")}_Time_#{created_at.strftime("%T")}".parameterize
|
||||
end
|
||||
end
|
||||
4
app/models/composer.rb
Normal file
4
app/models/composer.rb
Normal file
@@ -0,0 +1,4 @@
|
||||
class Composer < ApplicationRecord
|
||||
belongs_to :music_release
|
||||
validates :name, :affiliation, :percentage, presence: true
|
||||
end
|
||||
0
app/models/concerns/.keep
Normal file
0
app/models/concerns/.keep
Normal file
40
app/models/concerns/archivable.rb
Normal file
40
app/models/concerns/archivable.rb
Normal file
@@ -0,0 +1,40 @@
|
||||
module Archivable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
scope :active, -> { where("updated_at > ?", Config.active_threshold_date) }
|
||||
scope :inactive, -> { where(updated_at: Config.inactive_date_range) }
|
||||
scope :archived, -> { where("updated_at < ?", Config.archive_threshold_date) }
|
||||
|
||||
def archive_status
|
||||
if updated_at > Config.active_threshold_date
|
||||
:active
|
||||
elsif updated_at.between? Config.archive_threshold_date, Config.active_threshold_date
|
||||
:inactive
|
||||
else
|
||||
:archived
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
class Config
|
||||
ACTIVE_THRESHOLD_IN_DAYS = 90
|
||||
ARCHIVE_THRESHOLD_IN_DAYS = 365
|
||||
|
||||
class << self
|
||||
def active_threshold_date
|
||||
ACTIVE_THRESHOLD_IN_DAYS.days.ago.beginning_of_day
|
||||
end
|
||||
|
||||
def archive_threshold_date
|
||||
ARCHIVE_THRESHOLD_IN_DAYS.days.ago.end_of_day
|
||||
end
|
||||
|
||||
def inactive_date_range
|
||||
archive_threshold_date..active_threshold_date
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
10
app/models/concerns/confirmable.rb
Normal file
10
app/models/concerns/confirmable.rb
Normal file
@@ -0,0 +1,10 @@
|
||||
module Confirmable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_many :video_release_confirmations, as: :releasable, dependent: :destroy
|
||||
has_many :confirmed_videos, source: :video, through: :video_release_confirmations
|
||||
|
||||
scope :appearing_in, -> (video) { joins(:confirmed_videos).where(videos: { id: video }) }
|
||||
end
|
||||
end
|
||||
15
app/models/concerns/contractable.rb
Normal file
15
app/models/concerns/contractable.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
module Contractable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_one_attached :contract
|
||||
|
||||
validates :contract, content_type: ["application/pdf"]
|
||||
|
||||
scope :having_contract_attached, -> (release_ids) { left_joins(:contract_attachment).where(active_storage_attachments: { record_id: release_ids }).group(:id).having("COUNT(active_storage_attachments) > 0") }
|
||||
|
||||
def contract_file_name
|
||||
"#{project.name.parameterize}_#{contract_template.release_type}_#{(signed_at || created_at).strftime("%Y.%m.%d")}_#{release_number}_#{name.parameterize}"
|
||||
end
|
||||
end
|
||||
end
|
||||
24
app/models/concerns/exploitable.rb
Normal file
24
app/models/concerns/exploitable.rb
Normal file
@@ -0,0 +1,24 @@
|
||||
module Exploitable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
FIELDS = [:applicable_medium, :territory, :term, :restriction]
|
||||
|
||||
included do
|
||||
FIELDS.each do |field|
|
||||
belongs_to field, optional: true
|
||||
|
||||
define_method "#{field}_value" do
|
||||
if respond_to?(:contract_template) && contract_template.present?
|
||||
# If contract template is present, use the value from there
|
||||
contract_template.public_send("#{field}_value")
|
||||
else
|
||||
# Otherwise use the value of the label or the text
|
||||
field_assoc = public_send(field)
|
||||
if field_assoc
|
||||
field_assoc.other? ? public_send("#{field}_text") : field_assoc.label
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
45
app/models/concerns/filterable.rb
Normal file
45
app/models/concerns/filterable.rb
Normal file
@@ -0,0 +1,45 @@
|
||||
module Filterable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
class_methods do
|
||||
def filterable_by(*scopes)
|
||||
@@filterer = Filterer.new(scopes)
|
||||
end
|
||||
|
||||
def filter(name)
|
||||
@@filterer.filter(name, self)
|
||||
end
|
||||
|
||||
def filter!(name)
|
||||
@@filterer.filter!(name, self)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
class Filterer
|
||||
def initialize(scopes)
|
||||
@scopes = Array.wrap(scopes).map(&:to_s)
|
||||
end
|
||||
|
||||
# Returns the original scope if filter has not been whitelisted
|
||||
def filter(name, scope)
|
||||
return scope.all unless filter_is_whitelisted?(name)
|
||||
|
||||
scope.send(name)
|
||||
end
|
||||
|
||||
# Raises an exception unless filter has been whitelisted
|
||||
def filter!(name, scope)
|
||||
raise "Cannot filter #{scope} by `#{name}`" unless filter_is_whitelisted?(name)
|
||||
|
||||
scope.send(name)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filter_is_whitelisted?(name)
|
||||
@scopes.include?(name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
8
app/models/concerns/freeformable.rb
Normal file
8
app/models/concerns/freeformable.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
module Freeformable
|
||||
extend ActiveSupport::Concern
|
||||
included do
|
||||
def other?
|
||||
label == "Other"
|
||||
end
|
||||
end
|
||||
end
|
||||
20
app/models/concerns/guardian_name.rb
Normal file
20
app/models/concerns/guardian_name.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
module GuardianName
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
def guardian_name
|
||||
"#{guardian_first_name} #{guardian_last_name}".titleize
|
||||
end
|
||||
|
||||
def guardian_name=(value)
|
||||
if value.include?(' ')
|
||||
split = value.split(" ", 2)
|
||||
self.guardian_first_name = split.first
|
||||
self.guardian_last_name = split.last
|
||||
else
|
||||
self.guardian_first_name = value
|
||||
self.guardian_last_name = "(Not Given)"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
9
app/models/concerns/guardian_photoable.rb
Normal file
9
app/models/concerns/guardian_photoable.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
module GuardianPhotoable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_one_attached :guardian_photo
|
||||
|
||||
validates :guardian_photo, content_type: ["image/png", "image/jpeg"]
|
||||
end
|
||||
end
|
||||
7
app/models/concerns/notable.rb
Normal file
7
app/models/concerns/notable.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
module Notable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_many :notes, as: :notable, dependent: :destroy
|
||||
end
|
||||
end
|
||||
20
app/models/concerns/person_name.rb
Normal file
20
app/models/concerns/person_name.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
module PersonName
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
def person_name
|
||||
"#{person_first_name} #{person_last_name}".titleize
|
||||
end
|
||||
|
||||
def person_name=(value)
|
||||
if value.include?(' ')
|
||||
split = value.split(" ", 2)
|
||||
self.person_first_name = split.first
|
||||
self.person_last_name = split.last
|
||||
else
|
||||
self.person_first_name = value
|
||||
self.person_last_name = "(Not Given)"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
13
app/models/concerns/photoable.rb
Normal file
13
app/models/concerns/photoable.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
module Photoable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_many_attached :photos
|
||||
|
||||
validates :photos, content_type: ["image/png", "image/jpeg"]
|
||||
end
|
||||
|
||||
def photo
|
||||
MainPhoto.new(photos.first)
|
||||
end
|
||||
end
|
||||
70
app/models/concerns/releasable.rb
Normal file
70
app/models/concerns/releasable.rb
Normal file
@@ -0,0 +1,70 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Releasable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
belongs_to :project, touch: true
|
||||
belongs_to :contract_template, optional: true
|
||||
end
|
||||
|
||||
def release_number
|
||||
@release_number ||= ReleaseNumber.new(self).value
|
||||
end
|
||||
|
||||
def signed_on
|
||||
if respond_to?(:signed_at) && signed_at.present?
|
||||
signed_at.strftime('%D')
|
||||
else
|
||||
created_at.strftime('%D')
|
||||
end
|
||||
end
|
||||
|
||||
def fill_with_dummy_data(previewed_contract_template)
|
||||
self.contract_template = previewed_contract_template
|
||||
self.project_id = contract_template.project_id
|
||||
self.created_at = DateTime.current
|
||||
self.name = 'Dummy Name' if has_attribute? :name
|
||||
self.person_first_name = 'Dummy' if has_attribute? :person_first_name
|
||||
self.person_last_name = 'Person' if has_attribute? :person_last_name
|
||||
if has_attribute? :person_date_of_birth
|
||||
self.person_date_of_birth = DateTime.current
|
||||
end
|
||||
if has_attribute? :person_address_street1
|
||||
self.person_address_street1 = 'Street 1'
|
||||
end
|
||||
if has_attribute? :person_address_street2
|
||||
self.person_address_street2 = 'Street 2'
|
||||
end
|
||||
self.person_address_city = 'City' if has_attribute? :person_address_city
|
||||
self.person_address_state = 'State' if has_attribute? :person_address_state
|
||||
self.person_address_zip = 12_345 if has_attribute? :person_address_zip
|
||||
if has_attribute? :person_address_country
|
||||
self.person_address_country = 'Country'
|
||||
end
|
||||
if has_attribute? :person_address
|
||||
self.person_address = 'Street 1, Street 2, City, State 12345, Country'
|
||||
end
|
||||
self.person_phone = '00 111 222 333 4444' if has_attribute? :person_phone
|
||||
self.person_email = 'email@email.com' if has_attribute? :person_email
|
||||
self.description = 'Dummy description' if has_attribute? :description
|
||||
self.filming_started_on = DateTime.current if has_attribute? :filming_started_on
|
||||
self.filming_ended_on = DateTime.current if has_attribute? :filming_ended_on
|
||||
return if contract_template.guardian_clause.blank?
|
||||
|
||||
self.guardian_name = 'Guardian name' if has_attribute? :guardian_name
|
||||
if has_attribute? :guardian_address
|
||||
self.guardian_address = 'Street 3, Street 4, City-2, State-2 112233, Country-2'
|
||||
end
|
||||
self.guardian_address_street1 = 'Street 3' if has_attribute? :guardian_address_street1
|
||||
self.guardian_address_street2 = 'Street 4' if has_attribute? :guardian_address_street2
|
||||
self.guardian_address_city = 'City-2' if has_attribute? :guardian_address_city
|
||||
self.guardian_address_state = 'State-2' if has_attribute? :guardian_address_state
|
||||
self.guardian_address_zip = '112233' if has_attribute? :guardian_address_zip
|
||||
self.guardian_address_country = 'Country-2' if has_attribute? :guardian_address_country
|
||||
self.guardian_phone = '00 123 456 7890' if has_attribute? :guardian_phone
|
||||
self.guardian_email = 'guardian.email@mail.com' if has_attribute? :guardian_email
|
||||
self.minor = true if has_attribute? :minor
|
||||
|
||||
end
|
||||
end
|
||||
27
app/models/concerns/searchable.rb
Normal file
27
app/models/concerns/searchable.rb
Normal file
@@ -0,0 +1,27 @@
|
||||
module Searchable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
include PgSearch
|
||||
end
|
||||
|
||||
class_methods do
|
||||
def searchable_on(fields)
|
||||
search_opts = {
|
||||
against: fields,
|
||||
associated_against: {
|
||||
notes: [:content],
|
||||
tags: [:name],
|
||||
internal_tags: [:name]
|
||||
},
|
||||
using: {
|
||||
tsearch: { any_word: true, prefix: true },
|
||||
trigram: {},
|
||||
dmetaphone: { any_word: true },
|
||||
}
|
||||
}
|
||||
|
||||
send(:pg_search_scope, :search, search_opts)
|
||||
end
|
||||
end
|
||||
end
|
||||
30
app/models/concerns/signable.rb
Normal file
30
app/models/concerns/signable.rb
Normal file
@@ -0,0 +1,30 @@
|
||||
module Signable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
include ActiveStorageSupport::SupportForBase64
|
||||
|
||||
has_one_base64_attached :signature
|
||||
|
||||
scope :having_a_signature, -> { left_joins(:signature_attachment).group(:id).having("COUNT(active_storage_attachments) > 0") }
|
||||
scope :having_no_signature, -> { left_joins(:signature_attachment).group(:id).having("COUNT(active_storage_attachments) = 0") }
|
||||
|
||||
# Create some descriptive aliases for scopes above relating to native vs. non-native
|
||||
scope :native, -> { having_a_signature }
|
||||
scope :non_native, -> { having_no_signature }
|
||||
end
|
||||
|
||||
def native?
|
||||
signature.attached?
|
||||
end
|
||||
|
||||
def signature_base64
|
||||
return nil
|
||||
end
|
||||
|
||||
def signature_base64=(data_uri)
|
||||
return if data_uri.blank?
|
||||
|
||||
signature.attach(data: data_uri, filename: "signature.png", content_type: "image/png", identify: "false")
|
||||
end
|
||||
end
|
||||
19
app/models/concerns/syncable.rb
Normal file
19
app/models/concerns/syncable.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Syncable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def as_json(params = {})
|
||||
json = super(params)
|
||||
json.each do |key, value|
|
||||
if key == "id"
|
||||
json[key] = value.to_s
|
||||
end
|
||||
end
|
||||
{
|
||||
id: id.to_s,
|
||||
type: model_name.param_key,
|
||||
attributes: json
|
||||
}
|
||||
end
|
||||
end
|
||||
9
app/models/concerns/taggable.rb
Normal file
9
app/models/concerns/taggable.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
module Taggable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
acts_as_taggable_on :internal_tags, :tags
|
||||
|
||||
enum tagging_status: %i[ pending started finished failed ], _prefix: "tagging"
|
||||
end
|
||||
end
|
||||
10
app/models/contact.rb
Normal file
10
app/models/contact.rb
Normal file
@@ -0,0 +1,10 @@
|
||||
class Contact
|
||||
attr_accessor :name, :address, :email, :phone
|
||||
|
||||
def initialize(name, address, email, phone)
|
||||
@name = name
|
||||
@address = address
|
||||
@email = email
|
||||
@phone = phone
|
||||
end
|
||||
end
|
||||
55
app/models/contract.rb
Normal file
55
app/models/contract.rb
Normal file
@@ -0,0 +1,55 @@
|
||||
class Contract
|
||||
def initialize(releasable, preview = false)
|
||||
@releasable = releasable
|
||||
@preview = preview
|
||||
end
|
||||
|
||||
def to_pdf
|
||||
kit = PDFKit.new(as_html)
|
||||
kit.to_file("tmp/#{filename}")
|
||||
end
|
||||
|
||||
def filename(extension = "pdf")
|
||||
"#{@releasable.contract_file_name}.#{extension}"
|
||||
end
|
||||
|
||||
def render_attributes
|
||||
{
|
||||
layout: "contract_pdf",
|
||||
locals: { releasable: @releasable, contract_template: contract_template, preview: @preview },
|
||||
template: "contracts/pdf",
|
||||
}
|
||||
end
|
||||
|
||||
def render_attributes_with_logo
|
||||
{
|
||||
layout: "contract_pdf",
|
||||
locals: { releasable: @releasable, contract_template: contract_template, logo: @releasable.project.account.logo, preview: @preview },
|
||||
template: "contracts/pdf",
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def contract_template
|
||||
@releasable.contract_template
|
||||
end
|
||||
|
||||
def project
|
||||
@releasable.project
|
||||
end
|
||||
|
||||
def locale
|
||||
@releasable.locale
|
||||
end
|
||||
|
||||
def as_html
|
||||
I18n.with_locale(locale) do
|
||||
if @releasable.project.account.logo.attached?
|
||||
ApplicationController.render render_attributes_with_logo
|
||||
else
|
||||
ApplicationController.render render_attributes
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
56
app/models/contract_template.rb
Normal file
56
app/models/contract_template.rb
Normal file
@@ -0,0 +1,56 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ContractTemplate < ApplicationRecord
|
||||
include Exploitable
|
||||
include Syncable
|
||||
include PgSearch
|
||||
|
||||
belongs_to :project
|
||||
belongs_to :parent, class_name: 'ContractTemplate', optional: true
|
||||
has_many :duplicates, class_name: 'ContractTemplate', foreign_key: 'parent_id'
|
||||
has_many :talent_releases, dependent: :restrict_with_error
|
||||
has_many :appearance_releases, dependent: :restrict_with_error
|
||||
has_many :acquired_media_releases, dependent: :restrict_with_error
|
||||
has_many :location_releases, dependent: :restrict_with_error
|
||||
has_many :material_releases, dependent: :restrict_with_error
|
||||
|
||||
monetize :fee_cents
|
||||
has_rich_text :body
|
||||
has_rich_text :guardian_clause
|
||||
|
||||
validates :name, presence: true
|
||||
validates :release_type, presence: true
|
||||
validates :fee_cents, numericality: {
|
||||
greater_than_or_equal_to: 0,
|
||||
less_than_or_equal_to: 99_999_999_99
|
||||
}
|
||||
|
||||
pg_search_scope :search, {
|
||||
against: [:name, :release_type],
|
||||
associated_against: {project: [:name]},
|
||||
using: {
|
||||
tsearch: {any_word: true, prefix: true},
|
||||
trigram: {},
|
||||
dmetaphone: {any_word: true}
|
||||
}
|
||||
}
|
||||
|
||||
scope :non_archived, -> { where(archived_at: nil) }
|
||||
scope :order_by_name, -> { order(:name) }
|
||||
|
||||
def fee?
|
||||
!fee.zero?
|
||||
end
|
||||
|
||||
def releases
|
||||
public_send("#{release_type}_releases")
|
||||
end
|
||||
|
||||
def duplicated?
|
||||
parent.present?
|
||||
end
|
||||
|
||||
def archive
|
||||
update(archived_at: Time.zone.now)
|
||||
end
|
||||
end
|
||||
29
app/models/contract_template_preview.rb
Normal file
29
app/models/contract_template_preview.rb
Normal file
@@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ContractTemplatePreview
|
||||
def initialize(contract_template)
|
||||
@contract_template = contract_template
|
||||
end
|
||||
|
||||
def build_releasable
|
||||
template_release_type = @contract_template.release_type
|
||||
fill_ct_empty_fields_with_dummy_data
|
||||
releasable = "#{template_release_type}_release".classify.safe_constantize.new
|
||||
releasable.fill_with_dummy_data(@contract_template)
|
||||
releasable
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fill_ct_empty_fields_with_dummy_data
|
||||
if @contract_template.name.blank?
|
||||
@contract_template.name = 'Contract Template Name'
|
||||
end
|
||||
if @contract_template.body.blank?
|
||||
@contract_template.body = 'Contract Template Body text goes here'
|
||||
end
|
||||
if @contract_template.guardian_clause.blank?
|
||||
@contract_template.guardian_clause = 'Contract Template Guardian Clause text goes here'
|
||||
end
|
||||
end
|
||||
end
|
||||
16
app/models/current.rb
Normal file
16
app/models/current.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
class Current < ActiveSupport::CurrentAttributes
|
||||
attribute :account, :user
|
||||
|
||||
resets { Time.zone = nil }
|
||||
|
||||
def user=(user)
|
||||
super
|
||||
# overwritten later by the active account from session
|
||||
self.account = user.primary_account
|
||||
Time.zone = user.time_zone
|
||||
end
|
||||
|
||||
def account=(account)
|
||||
super
|
||||
end
|
||||
end
|
||||
24
app/models/custom_rank_order.rb
Normal file
24
app/models/custom_rank_order.rb
Normal file
@@ -0,0 +1,24 @@
|
||||
class CustomRankOrder
|
||||
def initialize(attribute, ordered_values)
|
||||
@attribute = attribute
|
||||
@ordered_values = ordered_values
|
||||
end
|
||||
|
||||
def sql
|
||||
Arel.sql case_statement
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_accessor :attribute, :ordered_values
|
||||
|
||||
def case_statement
|
||||
["CASE", case_conditions, "END"].join(" ")
|
||||
end
|
||||
|
||||
def case_conditions
|
||||
ordered_values.map.with_index do |release, index|
|
||||
"WHEN #{attribute}='#{release}' THEN '#{index+1}'"
|
||||
end.join(" ")
|
||||
end
|
||||
end
|
||||
19
app/models/directory.rb
Normal file
19
app/models/directory.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
class Directory < ApplicationRecord
|
||||
belongs_to :project
|
||||
belongs_to :user
|
||||
|
||||
has_many_attached :files
|
||||
|
||||
validates :name, presence: true, uniqueness: { scope: :project_id }
|
||||
|
||||
enum permissions: { "Everyone": 0, "Account Managers & Project Managers": 1, "Account Managers Only": 2 }
|
||||
enum category: { "Other": 0, "Finance": 1, "Scripts": 2, "Call Sheets": 3, "Photos": 4, "Videos": 5 }
|
||||
|
||||
scope :order_by_name, -> { order(name: :asc) }
|
||||
scope :for_associates, -> { where(permissions: "Everyone") }
|
||||
scope :for_project_managers, -> { where(permissions: ["Everyone", "Account Managers & Project Managers"]) }
|
||||
|
||||
def search_files(query)
|
||||
files_attachments.joins(:blob).where("active_storage_blobs.filename ILIKE ?", "%#{query}%")
|
||||
end
|
||||
end
|
||||
26
app/models/download.rb
Normal file
26
app/models/download.rb
Normal file
@@ -0,0 +1,26 @@
|
||||
class Download < ApplicationRecord
|
||||
include PgSearch
|
||||
|
||||
belongs_to :project
|
||||
|
||||
has_one_attached :file
|
||||
|
||||
enum status: { not_started: 0, pending: 1, success: 2, failure: 3 }
|
||||
|
||||
scope :unfinished_desc_order, -> { where(status: [:not_started, :pending]).order("created_at DESC") }
|
||||
|
||||
def self.searchable_on(fields)
|
||||
search_opts = {
|
||||
against: fields,
|
||||
using: {
|
||||
tsearch: { any_word: true, prefix: true },
|
||||
trigram: {},
|
||||
dmetaphone: { any_word: true },
|
||||
}
|
||||
}
|
||||
|
||||
send(:pg_search_scope, :search, search_opts)
|
||||
end
|
||||
|
||||
searchable_on %i[name release_type]
|
||||
end
|
||||
48
app/models/duration_timecode.rb
Normal file
48
app/models/duration_timecode.rb
Normal file
@@ -0,0 +1,48 @@
|
||||
class DurationTimecode
|
||||
def self.parse(timecode)
|
||||
if match = DURATION_TIMECODE_REGEX.match(timecode)
|
||||
hours = match[1].to_i
|
||||
minutes = match[2].to_i
|
||||
seconds = match[3].to_i
|
||||
new(hours, minutes, seconds)
|
||||
else
|
||||
raise ArgumentError.new("Must be HH:MM:SS format")
|
||||
end
|
||||
end
|
||||
|
||||
def self.from_seconds(total_seconds)
|
||||
hours = total_seconds / 60 / 60
|
||||
minutes = total_seconds / 60 % 60
|
||||
seconds = total_seconds % 60
|
||||
|
||||
DurationTimecode.new(hours, minutes, seconds)
|
||||
end
|
||||
|
||||
attr_reader :hours, :minutes, :seconds
|
||||
|
||||
def initialize(hours, minutes, seconds)
|
||||
@hours = hours
|
||||
@minutes = minutes
|
||||
@seconds = seconds
|
||||
end
|
||||
|
||||
def to_s
|
||||
[hours, minutes, seconds].map { |t| t.to_s.rjust(2,'0') }.join(':')
|
||||
end
|
||||
|
||||
def to_i
|
||||
[
|
||||
hours * 60 * 60,
|
||||
minutes * 60,
|
||||
seconds
|
||||
].sum
|
||||
end
|
||||
|
||||
def +(other)
|
||||
DurationTimecode.from_seconds(to_i + other.to_i)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
DURATION_TIMECODE_REGEX = /\A(\d+):(\d+):(\d+)\z/
|
||||
end
|
||||
21
app/models/edl_event.rb
Normal file
21
app/models/edl_event.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
class EdlEvent < Struct.new(
|
||||
:channel,
|
||||
:start_time,
|
||||
:timecode_in,
|
||||
:timecode_out,
|
||||
:duration,
|
||||
:source_file_name,
|
||||
:clip_name,
|
||||
:description,
|
||||
:matches,
|
||||
keyword_init: true
|
||||
)
|
||||
|
||||
def attributes
|
||||
to_h
|
||||
end
|
||||
|
||||
def public_attributes
|
||||
attributes.except(:start_time, :matches)
|
||||
end
|
||||
end
|
||||
75
app/models/edl_event_gateway.rb
Normal file
75
app/models/edl_event_gateway.rb
Normal file
@@ -0,0 +1,75 @@
|
||||
class EdlEventGateway
|
||||
def initialize(files_for_request, timecode_start, timecode_end, collection: {}, channel_filter: "")
|
||||
@files_for_request = files_for_request
|
||||
@timecode_start = timecode_start
|
||||
@timecode_end = timecode_end
|
||||
@collection = collection
|
||||
@channel_filter = channel_filter
|
||||
end
|
||||
|
||||
def edl_events
|
||||
@edl_events ||= response_results.
|
||||
filter { |response_edl_event| response_edl_event.channel.include?(channel_filter) }.
|
||||
map do |response_edl_event|
|
||||
EdlEvent.new(
|
||||
channel: response_edl_event.channel,
|
||||
start_time: response_edl_event.start_time,
|
||||
timecode_in: response_edl_event.timecode_in,
|
||||
timecode_out: response_edl_event.timecode_out,
|
||||
duration: response_edl_event.duration,
|
||||
source_file_name: response_edl_event.source_file_name,
|
||||
clip_name: response_edl_event.clip_name,
|
||||
description: response_edl_event.description,
|
||||
matches: response_edl_event.matches,
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def edl_timecode_start
|
||||
return if response.nil?
|
||||
|
||||
response.edl_timecode_start
|
||||
end
|
||||
|
||||
def fps
|
||||
return if response.nil?
|
||||
|
||||
response.fps
|
||||
end
|
||||
|
||||
def edl_offset_seconds
|
||||
return if response.nil?
|
||||
|
||||
response.edl_offset_seconds
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :files_for_request, :timecode_start, :timecode_end, :collection, :channel_filter
|
||||
|
||||
def as_json(*)
|
||||
{
|
||||
job_id: files_for_request.job_id,
|
||||
video_bucket_name: files_for_request.aws_bucket_name,
|
||||
video_object_name: files_for_request.file_object_name,
|
||||
edl_bucket_name: files_for_request.aws_bucket_name,
|
||||
edl_object_name: files_for_request.edl_file_object_name,
|
||||
timecode_start: timecode_start,
|
||||
timecode_end: timecode_end,
|
||||
collection: collection,
|
||||
edl_timecode_start: files_for_request.start_timecode_offset,
|
||||
}.compact
|
||||
end
|
||||
|
||||
def response_results
|
||||
return [] if response.nil?
|
||||
|
||||
response.results
|
||||
end
|
||||
|
||||
def response
|
||||
@response ||= BrayniacAI::EdlParse.create as_json
|
||||
rescue ActiveResource::ServerError => e
|
||||
nil
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,128 @@
|
||||
module ExcelReports
|
||||
module AudioReports
|
||||
class AudioConfirmationData
|
||||
def initialize(audio_confirmation)
|
||||
@audio_confirmation = audio_confirmation
|
||||
end
|
||||
|
||||
def cue_number
|
||||
""
|
||||
end
|
||||
|
||||
def timecode_in
|
||||
audio_confirmation.timecode_in
|
||||
end
|
||||
|
||||
def title_and_source_file_name
|
||||
[audio_confirmation.title, audio_confirmation.source_file_name].reject(&:blank?).join(" - ")
|
||||
end
|
||||
|
||||
def catalog
|
||||
audio_confirmation.catalog
|
||||
end
|
||||
|
||||
def use
|
||||
"#{audio_confirmation.music_type} #{audio_confirmation.music_category}"
|
||||
end
|
||||
|
||||
def interested_parties
|
||||
"Composers:\n#{audio_confirmation.composer_info}\nPublishers:\n#{audio_confirmation.publisher_info}"
|
||||
end
|
||||
|
||||
def composers
|
||||
converted_composers.map do |composer|
|
||||
"#{composer.name} (#{composer.affiliation})"
|
||||
end.join("\n")
|
||||
end
|
||||
|
||||
def composers_split
|
||||
converted_composers.map(&:percentage).join("\n")
|
||||
end
|
||||
|
||||
def composers_with_split
|
||||
converted_composers.map do |composer|
|
||||
"#{composer.name} (#{composer.affiliation}) #{composer.percentage}%"
|
||||
end.join("\n")
|
||||
end
|
||||
|
||||
def composers_cae_numbers
|
||||
converted_composers.map(&:cae_number).join("\n")
|
||||
end
|
||||
|
||||
def publishers
|
||||
converted_publishers.map do |publisher|
|
||||
"#{publisher.name} (#{publisher.affiliation})"
|
||||
end.join("\n")
|
||||
end
|
||||
|
||||
def publishers_split
|
||||
converted_publishers.map(&:percentage).join("\n")
|
||||
end
|
||||
|
||||
def publishers_with_split
|
||||
converted_publishers.map do |publisher|
|
||||
"#{publisher.name} (#{publisher.affiliation}) #{publisher.percentage}%"
|
||||
end.join("\n")
|
||||
end
|
||||
|
||||
def music_type
|
||||
audio_confirmation.music_type.to_s.first
|
||||
end
|
||||
|
||||
def music_category
|
||||
audio_confirmation.music_category.to_s.first
|
||||
end
|
||||
|
||||
def timecode_out
|
||||
audio_confirmation.timecode_out
|
||||
end
|
||||
|
||||
def duration
|
||||
audio_confirmation.duration
|
||||
end
|
||||
|
||||
def origin
|
||||
if audio_confirmation.confirmation_type_library?
|
||||
"Production Library (Non-affiliated)"
|
||||
else
|
||||
"Commissioned"
|
||||
end
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
audio_confirmation == other.audio_confirmation
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
attr_reader :audio_confirmation
|
||||
|
||||
def converted_composers
|
||||
composers = audio_confirmation.composer_info.split("|")
|
||||
composers.map do |composer_info|
|
||||
parts = composer_info.split(",")
|
||||
if cae_number_index = parts.index { |part| part.include?("$cae") }
|
||||
cae_number = parts.delete_at(cae_number_index)
|
||||
end
|
||||
Composer.new(
|
||||
name: parts[0...-2].join(","),
|
||||
affiliation: parts[-2].strip,
|
||||
percentage: parts.last,
|
||||
cae_number: cae_number.to_s.gsub("$cae:", "").strip
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def converted_publishers
|
||||
audio_confirmation.publisher_info.split("|").map do |publisher_info|
|
||||
parts = publisher_info.split(",")
|
||||
Publisher.new(
|
||||
name: parts[0...-2].join(","),
|
||||
affiliation: parts[-2].strip,
|
||||
percentage: parts.last,
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,25 @@
|
||||
module ExcelReports
|
||||
module AudioReports
|
||||
class BrayInnovationGroupMusicCueHeaderData
|
||||
def initialize(video)
|
||||
@video = video
|
||||
end
|
||||
|
||||
def title
|
||||
video.name
|
||||
end
|
||||
|
||||
def company_name
|
||||
video.project.producer_name
|
||||
end
|
||||
|
||||
def company_address
|
||||
video.project.producer_address
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :video
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,40 @@
|
||||
module ExcelReports
|
||||
module AudioReports
|
||||
class BrayInnovationGroupMusicCueReport
|
||||
def initialize(video)
|
||||
@video = video
|
||||
end
|
||||
|
||||
def to_xls
|
||||
BrayInnovationGroupMusicCueSheet.build(workbook, report_data, report_header_data)
|
||||
package.to_stream.read
|
||||
end
|
||||
|
||||
def filename
|
||||
"#{video.file.filename.to_s.parameterize}_big-cue-sheet.xlsx"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :video
|
||||
|
||||
def report_data
|
||||
video.audio_confirmations.order(timecode_in: :asc).map do |audio_confirmation|
|
||||
AudioConfirmationData.new(audio_confirmation)
|
||||
end
|
||||
end
|
||||
|
||||
def report_header_data
|
||||
BrayInnovationGroupMusicCueHeaderData.new(video)
|
||||
end
|
||||
|
||||
def workbook
|
||||
@workbook ||= package.workbook
|
||||
end
|
||||
|
||||
def package
|
||||
@package ||= Axlsx::Package.new
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,97 @@
|
||||
module ExcelReports
|
||||
module AudioReports
|
||||
class BrayInnovationGroupMusicCueSheet < ::ExcelReports::Worksheet
|
||||
def title
|
||||
"BiG Music Cue Sheet"
|
||||
end
|
||||
|
||||
def fill_content(sheet)
|
||||
sheet.add_row ["TITLE", header_data.title]
|
||||
sheet.add_row ["COMPANY NAME", header_data.company_name]
|
||||
sheet.add_row ["ADDRESS", header_data.company_address]
|
||||
sheet.add_row ["LENGTH", ""]
|
||||
sheet.add_row ["TYPE", ""]
|
||||
sheet.add_row
|
||||
sheet.add_row table_headers
|
||||
sheet.add_row table_subheaders
|
||||
|
||||
data.each do |datum|
|
||||
sheet.add_row [
|
||||
datum.cue_number,
|
||||
datum.title_and_source_file_name,
|
||||
datum.composers,
|
||||
datum.composers_split,
|
||||
datum.publishers,
|
||||
datum.publishers_split,
|
||||
datum.music_type,
|
||||
datum.music_category,
|
||||
datum.timecode_in,
|
||||
datum.timecode_out,
|
||||
datum.duration,
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
def format(sheet)
|
||||
sheet.merge_cells "G7:H7"
|
||||
sheet.column_widths *column_widths
|
||||
end
|
||||
|
||||
def style(sheet)
|
||||
sheet.add_style "A1:A5", Styles::PROGRAM_HEADER
|
||||
sheet.add_style "B1:B5", Styles::HEADER_DATA
|
||||
sheet.add_style "A7:K7", Styles::TABLE_HEADER
|
||||
|
||||
if data.any?
|
||||
sheet.add_style "A#{data_start_index}:K#{data_end_index}", Styles::TABLE_DATA
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def table_headers
|
||||
[
|
||||
"Cue #",
|
||||
"Cue Title",
|
||||
"Composer(s)/Affiliation",
|
||||
"%",
|
||||
"Publisher(s)/Affiliation",
|
||||
"%",
|
||||
"Use",
|
||||
"",
|
||||
"In Time",
|
||||
"Out Time",
|
||||
"Duration",
|
||||
]
|
||||
end
|
||||
|
||||
def table_subheaders
|
||||
[
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"I = Instr.\nV = Vocal",
|
||||
"B = Bckgrnd\nF = Feature\nT = Theme",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
]
|
||||
end
|
||||
|
||||
def column_widths
|
||||
[15, 40, 20, 10, 20, 10, 20, 20, 15, 15, 15]
|
||||
end
|
||||
|
||||
def data_start_index
|
||||
9
|
||||
end
|
||||
|
||||
def data_end_index
|
||||
(data_start_index + data.size) - 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,29 @@
|
||||
module ExcelReports
|
||||
module AudioReports
|
||||
class DiscoveryMusicCueHeaderData
|
||||
def initialize(video)
|
||||
@video = video
|
||||
end
|
||||
|
||||
def title
|
||||
video.name
|
||||
end
|
||||
|
||||
def company_name
|
||||
video.project.producer_name
|
||||
end
|
||||
|
||||
def client_name
|
||||
video.project.client_name
|
||||
end
|
||||
|
||||
def episode_number
|
||||
video.number
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :video
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,40 @@
|
||||
module ExcelReports
|
||||
module AudioReports
|
||||
class DiscoveryMusicCueReport
|
||||
def initialize(video)
|
||||
@video = video
|
||||
end
|
||||
|
||||
def to_xls
|
||||
DiscoveryMusicCueSheet.build(workbook, report_data, report_header_data)
|
||||
package.to_stream.read
|
||||
end
|
||||
|
||||
def filename
|
||||
"#{video.file.filename.to_s.parameterize}_discovery-cue-sheet.xlsx"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :video
|
||||
|
||||
def report_header_data
|
||||
DiscoveryMusicCueHeaderData.new(video)
|
||||
end
|
||||
|
||||
def report_data
|
||||
video.audio_confirmations.order(timecode_in: :asc).map do |audio_confirmation|
|
||||
AudioConfirmationData.new(audio_confirmation)
|
||||
end
|
||||
end
|
||||
|
||||
def workbook
|
||||
@workbook ||= package.workbook
|
||||
end
|
||||
|
||||
def package
|
||||
@package ||= Axlsx::Package.new
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,90 @@
|
||||
module ExcelReports
|
||||
module AudioReports
|
||||
class DiscoveryMusicCueSheet < ::ExcelReports::Worksheet
|
||||
def title
|
||||
"Discovery Music Cue Sheet"
|
||||
end
|
||||
|
||||
def fill_content(sheet)
|
||||
sheet.add_row ["Original Production Title", header_data.title, "Original Series Title", "", "Broadcaster", ""]
|
||||
sheet.add_row ["Alternative Production Title", "", "Alternative Series Title", "", "Production Company", header_data.company_name]
|
||||
sheet.add_row ["Local Production Title", "", "Local Series Title", "", "", ""]
|
||||
sheet.add_row ["Working Production Title", "", "Working Series Title", "", "Production Number", ""]
|
||||
sheet.add_row ["Version Production Title", "", "Version Series Title", "", "Director", ""]
|
||||
sheet.add_row ["Episode No.", header_data.episode_number, "Season No.", "", "Production Parent Identifier", ""]
|
||||
sheet.add_row ["Year", "", "Country", "", "Soundmouse Legacy Identifier", ""]
|
||||
sheet.add_row ["Duration", "", "Type", "", "Production ID", ""]
|
||||
sheet.add_row ["First Transmission", "", "Source", ""]
|
||||
sheet.add_row ["Product Name", ""]
|
||||
sheet.add_row ["Client Name", header_data.client_name]
|
||||
sheet.add_row ["Narrative", ""]
|
||||
sheet.add_row ["End Line", ""]
|
||||
sheet.add_row ["Clock Number", ""]
|
||||
sheet.add_row ["ISAN", ""]
|
||||
|
||||
sheet.add_row
|
||||
sheet.add_row table_headers
|
||||
|
||||
data.each_with_index do |datum, index|
|
||||
sheet.add_row [
|
||||
index + 1,
|
||||
datum.timecode_in,
|
||||
datum.title_and_source_file_name,
|
||||
datum.catalog,
|
||||
datum.use,
|
||||
datum.interested_parties,
|
||||
"",
|
||||
datum.duration,
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
def format(sheet)
|
||||
sheet.column_widths *column_widths
|
||||
end
|
||||
|
||||
def style(sheet)
|
||||
sheet.add_style "A1:A15", Styles::PROGRAM_HEADER
|
||||
sheet.add_style "C1:C9", Styles::PROGRAM_HEADER
|
||||
sheet.add_style "E1:E8", Styles::PROGRAM_HEADER
|
||||
|
||||
sheet.add_style "B1:B15", Styles::HEADER_DATA
|
||||
sheet.add_style "D1:D9", Styles::HEADER_DATA
|
||||
sheet.add_style "F1:F8", Styles::HEADER_DATA
|
||||
|
||||
sheet.add_style "A17:H17", Styles::TABLE_HEADER
|
||||
|
||||
if data.any?
|
||||
sheet.add_style "A#{data_start_index}:H#{data_end_index}", Styles::TABLE_DATA
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def table_headers
|
||||
[
|
||||
"No.",
|
||||
"Timecode",
|
||||
"Title",
|
||||
"Music Origin",
|
||||
"Use (Theme/Description)",
|
||||
"Interested Parties",
|
||||
"Identifiers",
|
||||
"Duration",
|
||||
]
|
||||
end
|
||||
|
||||
def column_widths
|
||||
[20, 20, 40, 20, 20, 20, 20, 20]
|
||||
end
|
||||
|
||||
def data_start_index
|
||||
18
|
||||
end
|
||||
|
||||
def data_end_index
|
||||
(data_start_index + data.size) - 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,36 @@
|
||||
module ExcelReports
|
||||
module AudioReports
|
||||
class NatGeoMusicCueSheet
|
||||
def initialize(video)
|
||||
@video = video
|
||||
end
|
||||
|
||||
def to_xls
|
||||
NatGeoMusicCueSheets::MainSheet.build(workbook, report_data)
|
||||
package.to_stream.read
|
||||
end
|
||||
|
||||
def filename
|
||||
"#{video.file.filename.to_s.parameterize}_music-cue-sheet.xlsx"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :video
|
||||
|
||||
def report_data
|
||||
video.audio_confirmations.order(timecode_in: :asc).map do |audio_confirmation|
|
||||
AudioConfirmationData.new(audio_confirmation)
|
||||
end
|
||||
end
|
||||
|
||||
def workbook
|
||||
@workbook ||= package.workbook
|
||||
end
|
||||
|
||||
def package
|
||||
@package ||= Axlsx::Package.new
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,97 @@
|
||||
module ExcelReports
|
||||
module AudioReports
|
||||
module NatGeoMusicCueSheets
|
||||
class MainSheet < Worksheet
|
||||
def title
|
||||
"Nat Geo Music Cue Sheet"
|
||||
end
|
||||
|
||||
def fill_content(sheet)
|
||||
sheet.add_row ["National Geographic", "", "", "", "", "", "", "", "", ""]
|
||||
sheet.add_row ["Music Cue Sheet", "", "", "", "", "", "", "", "", ""]
|
||||
sheet.add_row
|
||||
sheet.add_row table_headers
|
||||
|
||||
data.each_with_index do |datum, index|
|
||||
sheet.add_row [
|
||||
index + 1,
|
||||
datum.title_and_source_file_name,
|
||||
datum.timecode_in,
|
||||
datum.timecode_out,
|
||||
datum.composers_with_split,
|
||||
datum.publishers_with_split,
|
||||
datum.catalog,
|
||||
datum.origin,
|
||||
datum.use,
|
||||
datum.duration,
|
||||
]
|
||||
end
|
||||
|
||||
sheet.add_row ["", "", "", "", "", "", "", "", "Total Music Duration:", total_duration]
|
||||
end
|
||||
|
||||
def format(sheet)
|
||||
sheet.column_widths *column_widths
|
||||
cells_to_merge.each { |cell| sheet.merge_cells cell }
|
||||
end
|
||||
|
||||
def style(sheet)
|
||||
sheet.add_style "A1:J1", Styles::BOLD, { sz: 18 }
|
||||
sheet.add_style "A2:J2", Styles::BOLD, Styles::FULLY_CENTERED, { sz: 16 }
|
||||
sheet.add_style "A#{data_start_index-1}:J#{data_start_index-1}", Styles::BOLD, Styles::THIN_BLACK_BORDER, Styles::WRAP_TEXT
|
||||
|
||||
if data.any?
|
||||
sheet.add_style "A#{data_start_index}:J#{data_end_index}", Styles::THIN_BLACK_BORDER
|
||||
sheet.add_style "E#{data_start_index}:F#{data_end_index}", Styles::WRAP_TEXT
|
||||
sheet.add_style "I#{data_start_index}:I#{data_end_index}", Styles::WRAP_TEXT
|
||||
end
|
||||
|
||||
sheet.add_style "I#{data_end_index + 1}", Styles::BOLD, Styles::WRAP_TEXT
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def table_headers
|
||||
[
|
||||
"No.",
|
||||
"Title",
|
||||
"In",
|
||||
"Out",
|
||||
"Composer",
|
||||
"Publisher",
|
||||
"Record Label / Library",
|
||||
"Music Origin",
|
||||
"Use",
|
||||
"Duration",
|
||||
]
|
||||
end
|
||||
|
||||
def column_widths
|
||||
[10, 30, 20, 20, 50, 50, 20, 25, 25, 20]
|
||||
end
|
||||
|
||||
def cells_to_merge
|
||||
%w(A1:J1 A2:J2)
|
||||
end
|
||||
|
||||
def data_start_index
|
||||
5
|
||||
end
|
||||
|
||||
def data_end_index
|
||||
(data_start_index + data.size) - 1
|
||||
end
|
||||
|
||||
def total_duration
|
||||
data.map do |datum|
|
||||
begin
|
||||
DurationTimecode.parse(datum.duration)
|
||||
rescue ArgumentError
|
||||
DurationTimecode.new(0, 0, 0)
|
||||
end
|
||||
end.sum.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,36 @@
|
||||
module ExcelReports
|
||||
module AudioReports
|
||||
class NatGeoOriginalMusicLog
|
||||
def initialize(video)
|
||||
@video = video
|
||||
end
|
||||
|
||||
def to_xls
|
||||
NatGeoOriginalMusicLogs::MainSheet.build(workbook, report_data)
|
||||
package.to_stream.read
|
||||
end
|
||||
|
||||
def filename
|
||||
"#{video.file.filename.to_s.parameterize}_original-music-log.xlsx"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :video
|
||||
|
||||
def report_data
|
||||
video.audio_confirmations.original.order(timecode_in: :asc).map do |audio_confirmation|
|
||||
AudioConfirmationData.new(audio_confirmation)
|
||||
end
|
||||
end
|
||||
|
||||
def workbook
|
||||
@workbook ||= package.workbook
|
||||
end
|
||||
|
||||
def package
|
||||
@package ||= Axlsx::Package.new
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,74 @@
|
||||
module ExcelReports
|
||||
module AudioReports
|
||||
module NatGeoOriginalMusicLogs
|
||||
class MainSheet < Worksheet
|
||||
def title
|
||||
"Nat Geo Original Music Log"
|
||||
end
|
||||
|
||||
def fill_content(sheet)
|
||||
sheet.add_row ["National Geographic", "", "", "",]
|
||||
sheet.add_row ["National Geographic Music", "", "", "",]
|
||||
sheet.add_row ["Original Music Log", "", "", "",]
|
||||
sheet.add_row ["[PROGRAM]", "", "", ""]
|
||||
sheet.add_row ["[SEASON #]", "", "", ""]
|
||||
sheet.add_row [BigMediaTime.time_zone_now.to_date.strftime("%D"), "", "", ""]
|
||||
sheet.add_row table_headers
|
||||
|
||||
data.each_with_index do |datum, index|
|
||||
sheet.add_row [
|
||||
datum.title_and_source_file_name,
|
||||
datum.composers_with_split,
|
||||
datum.composers_cae_numbers,
|
||||
datum.publishers_with_split,
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
def format(sheet)
|
||||
sheet.column_widths *column_widths
|
||||
cells_to_merge.each { |cell| sheet.merge_cells cell }
|
||||
end
|
||||
|
||||
def style(sheet)
|
||||
sheet.add_style "A1:D1", Styles::BOLD, { sz: 18 }
|
||||
sheet.add_style "A2:D2", Styles::BOLD, Styles::FULLY_CENTERED, { sz: 16 }
|
||||
sheet.add_style "A3:D3", Styles::FULLY_CENTERED, { sz: 14 }
|
||||
sheet.add_style "A#{data_start_index-1}:D#{data_start_index-1}", Styles::BOLD, Styles::THIN_BLACK_BORDER, Styles::WRAP_TEXT, Styles::BG_GRAY
|
||||
|
||||
if data.any?
|
||||
sheet.add_style "A#{data_start_index}:D#{data_end_index}", Styles::THIN_BLACK_BORDER
|
||||
sheet.add_style "A#{data_start_index}:D#{data_end_index}", Styles::WRAP_TEXT
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def table_headers
|
||||
[
|
||||
"CUE TITLE",
|
||||
"COMPOSER(S), % SPLIT & PRO",
|
||||
"COMPOSER CAE/IPI#",
|
||||
"PUBLISHER(S), % SPLIT & PRO",
|
||||
]
|
||||
end
|
||||
|
||||
def column_widths
|
||||
[25, 45, 25, 80]
|
||||
end
|
||||
|
||||
def cells_to_merge
|
||||
%w(A1:D1 A2:D2 A3:D3)
|
||||
end
|
||||
|
||||
def data_start_index
|
||||
8
|
||||
end
|
||||
|
||||
def data_end_index
|
||||
(data_start_index + data.size) - 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,68 @@
|
||||
module ExcelReports
|
||||
module GraphicReports
|
||||
class DiscoveryGfxCueList
|
||||
def initialize(video)
|
||||
@video = video
|
||||
end
|
||||
|
||||
def to_xls
|
||||
DiscoveryGfxCueLists::TextedElementsSheet.build(workbook, graphics_elements_data, graphics_elements_header_data)
|
||||
|
||||
package.to_stream.read
|
||||
end
|
||||
|
||||
def filename(format = "xlsx")
|
||||
name = [@video.file.filename.to_s, "gfx-cue-list"].map(&:parameterize).join("_")
|
||||
[name, format].join(".")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :video
|
||||
|
||||
def graphics_elements_data
|
||||
video.graphics_elements.order(timecode_in: :asc).map do |graphics_element|
|
||||
GraphicsElementsData.new(
|
||||
graphics_element.text,
|
||||
graphics_element.timecode_in,
|
||||
graphics_element.timecode_out,
|
||||
graphics_element.duration,
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def graphics_elements_header_data
|
||||
GraphicsElementsHeaderData.new(
|
||||
video.name,
|
||||
video.number,
|
||||
video.project.client_name,
|
||||
video.project.account.name,
|
||||
)
|
||||
end
|
||||
|
||||
def workbook
|
||||
@workbook ||= package.workbook
|
||||
end
|
||||
|
||||
def package
|
||||
@package ||= Axlsx::Package.new
|
||||
end
|
||||
|
||||
class GraphicsElementsData < Struct.new(
|
||||
:text,
|
||||
:timecode_in,
|
||||
:timecode_out,
|
||||
:duration,
|
||||
)
|
||||
end
|
||||
|
||||
class GraphicsElementsHeaderData < Struct.new(
|
||||
:program_title,
|
||||
:episode,
|
||||
:network,
|
||||
:company,
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,56 @@
|
||||
module ExcelReports
|
||||
module GraphicReports
|
||||
module DiscoveryGfxCueLists
|
||||
class TextedElementsSheet < ::ExcelReports::Worksheet
|
||||
def title
|
||||
"Texted Elements List"
|
||||
end
|
||||
|
||||
def fill_content(sheet)
|
||||
sheet.add_row ["Texted Element / CG List"]
|
||||
sheet.add_row
|
||||
sheet.add_row ["Program Title", header_data.program_title]
|
||||
sheet.add_row ["Episode", header_data.episode]
|
||||
sheet.add_row ["Network", header_data.network]
|
||||
sheet.add_row ["Executive Producer", ""]
|
||||
sheet.add_row ["Production Company", header_data.company]
|
||||
sheet.add_row
|
||||
sheet.add_row ["Title/Subtitle/Graphics", "T/C IN", "T/C OUT", "TRT"]
|
||||
|
||||
data.each do |graphics_data|
|
||||
sheet.add_row [graphics_data.text, graphics_data.timecode_in, graphics_data.timecode_out, graphics_data.duration]
|
||||
end
|
||||
end
|
||||
|
||||
def format(sheet)
|
||||
sheet.column_widths *column_widths
|
||||
end
|
||||
|
||||
def style(sheet)
|
||||
sheet.add_style "A1", Styles::BOLD, { sz: 12 }
|
||||
sheet.add_style "A9:D9", Styles::TABLE_HEADER
|
||||
sheet.add_style "A3:A7", Styles::PROGRAM_HEADER
|
||||
sheet.add_style "B3:B7", Styles::HEADER_DATA
|
||||
|
||||
if data.any?
|
||||
sheet.add_style "A#{data_start_index}:D#{data_end_index}", Styles::TABLE_DATA
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def column_widths
|
||||
[60, 20, 20]
|
||||
end
|
||||
|
||||
def data_start_index
|
||||
10
|
||||
end
|
||||
|
||||
def data_end_index
|
||||
(data_start_index + data.size) - 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,42 @@
|
||||
module ExcelReports
|
||||
module GraphicReports
|
||||
class NatGeoTextGraphicsLog
|
||||
def initialize(video)
|
||||
@video = video
|
||||
end
|
||||
|
||||
def to_xls
|
||||
NatGeoTextGraphicsLogs::InternalProgramGfxLogSheet.build(
|
||||
workbook, graphics_elements_data, graphics_elements_header_data
|
||||
)
|
||||
|
||||
package.to_stream.read
|
||||
end
|
||||
|
||||
def filename(format = "xlsx")
|
||||
name = [@video.file.filename.to_s, "text-graphics-log"].map(&:parameterize).join("_")
|
||||
[name, format].join(".")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :video
|
||||
|
||||
def graphics_elements_data
|
||||
video.graphics_elements.order(timecode_in: :asc)
|
||||
end
|
||||
|
||||
def graphics_elements_header_data
|
||||
video
|
||||
end
|
||||
|
||||
def workbook
|
||||
@workbook ||= package.workbook
|
||||
end
|
||||
|
||||
def package
|
||||
@package ||= Axlsx::Package.new
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,78 @@
|
||||
module ExcelReports
|
||||
module GraphicReports
|
||||
module NatGeoTextGraphicsLogs
|
||||
class InternalProgramGfxLogSheet < Worksheet
|
||||
def title
|
||||
"Internal Program GFX Log"
|
||||
end
|
||||
|
||||
def fill_content(sheet)
|
||||
sheet.add_row ["NATIONAL GEOGRAPHIC", "", "", "", "", "", ""]
|
||||
sheet.add_row ["Text-Graphics Log", "", "", "", "", "", ""]
|
||||
sheet.add_row ["(Title Sequence, Maps, Lower Thirds, CGI, Subtitles, Captions, Text Identifiers, Chyrons, Credits etc. )", "", "", "", "", "", ""]
|
||||
sheet.add_row
|
||||
sheet.add_row ["", "Series Name:", "", "", "", "", ""]
|
||||
sheet.add_row ["", "Episode Title:", header_data.name, "", "", "", ""]
|
||||
sheet.add_row ["", "Episode Number:", header_data.number, "", "", "", ""]
|
||||
sheet.add_row ["", "Traffic Code(s):", "", "", "", "", ""]
|
||||
sheet.add_row ["", "Date:", BigMediaTime.time_zone_now.to_date.strftime("%D"), "", "", "", ""]
|
||||
sheet.add_row
|
||||
sheet.add_row table_headers
|
||||
|
||||
data.each do |graphics_data|
|
||||
sheet.add_row [graphics_data.graphic_type, graphics_data.text, graphics_data.timecode_in, graphics_data.timecode_out, "", "", ""]
|
||||
end
|
||||
|
||||
sheet.add_row(["*Clean Graphics should appear at the end of the program master. If all shots do not fit on the program master, a separate graphics master should be delivered."])
|
||||
sheet.add_row(["*If delivering textless masters & no clean graphics at the end, denote N/A in the clean scene timecode section."])
|
||||
end
|
||||
|
||||
def format(sheet)
|
||||
sheet.column_widths *column_widths
|
||||
cells_to_merge.each { |cell| sheet.merge_cells cell }
|
||||
end
|
||||
|
||||
def style(sheet)
|
||||
sheet.add_style "A1:A2", Styles::BOLD, Styles::FULLY_CENTERED, { sz: 12 }
|
||||
sheet.add_style "A3", Styles::FULLY_CENTERED, { sz: 12 }
|
||||
sheet.add_style "B5:B9", Styles::BOLD, Styles::HORIZONTAL_RIGHT
|
||||
sheet.add_style "A11:G11", Styles::BOLD, Styles::FULLY_CENTERED, Styles::THIN_BLACK_BORDER, Styles::WRAP_TEXT
|
||||
|
||||
if data.any?
|
||||
sheet.add_style "A#{data_start_index}:G#{data_end_index}", Styles::THIN_BLACK_BORDER
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def table_headers
|
||||
[
|
||||
"Element\n(Ex: Title, Lower Third, Subtitle, Graphic, Credits, etc.)",
|
||||
"Description",
|
||||
"Program Timecode In",
|
||||
"Program Timecode Out",
|
||||
"Clean Scene Timecode In",
|
||||
"Clean Scene Timecode Out",
|
||||
"Font Information\n(Include Font Name, Style, Color, & Opacity)"
|
||||
]
|
||||
end
|
||||
|
||||
def column_widths
|
||||
[25, 36, 18, 18, 18, 18, 23]
|
||||
end
|
||||
|
||||
def cells_to_merge
|
||||
%w(A1:G1 A2:G2 A3:G3 C5:E5 C6:E6 C7:E7 C8:E8 C9:E9)
|
||||
end
|
||||
|
||||
def data_start_index
|
||||
12
|
||||
end
|
||||
|
||||
def data_end_index
|
||||
(data_start_index + data.size) - 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,33 @@
|
||||
module ExcelReports
|
||||
module IssuesAndConcernsReports
|
||||
class IssuesAndConcernsReport
|
||||
def initialize(video)
|
||||
@video = video
|
||||
end
|
||||
|
||||
def to_xls
|
||||
IssuesAndConcernsWorksheet.new(
|
||||
workbook,
|
||||
IssuesAndConcernsReportPresenter.new(video)
|
||||
).build
|
||||
package.to_stream.read
|
||||
end
|
||||
|
||||
def filename
|
||||
"#{video.file.filename.to_s.parameterize}_issues-and-concerns.xlsx"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :video
|
||||
|
||||
def workbook
|
||||
@workbook ||= package.workbook
|
||||
end
|
||||
|
||||
def package
|
||||
@package ||= Axlsx::Package.new
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,58 @@
|
||||
module ExcelReports
|
||||
module IssuesAndConcernsReports
|
||||
class IssuesAndConcernsWorksheet < ::ExcelReports::Worksheet
|
||||
def title
|
||||
"Issues and Concerns Report"
|
||||
end
|
||||
|
||||
def fill_content(sheet)
|
||||
sheet.add_row ["ISSUES AND CONCERNS REPORT"]
|
||||
sheet.add_row ["Project Name", data.project_name]
|
||||
sheet.add_row ["Video Name", data.video_name]
|
||||
sheet.add_row ["Date of Report", data.date_of_report]
|
||||
|
||||
sheet.add_row []
|
||||
sheet.add_row ["Track", "TC In", "TC Out", "Clip Name", "Source File Name", "Notes"]
|
||||
data.unreleased_appearances.each do |unreleased_appearance|
|
||||
sheet.add_row [
|
||||
unreleased_appearance.channel,
|
||||
unreleased_appearance.timecode_in,
|
||||
unreleased_appearance.timecode_out,
|
||||
unreleased_appearance.clip_name,
|
||||
unreleased_appearance.source_file_name,
|
||||
unreleased_appearance.note_text
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
def format(sheet)
|
||||
sheet.column_widths *column_widths
|
||||
end
|
||||
|
||||
def style(sheet)
|
||||
sheet.add_style "A1", Styles::BOLD, { sz: 14 }
|
||||
sheet.add_style "A2:A4", Styles::PROGRAM_HEADER
|
||||
sheet.add_style "B2:B4", Styles::HEADER_DATA
|
||||
sheet.add_style "A6:F6", Styles::TABLE_HEADER
|
||||
|
||||
if data.unreleased_appearances.any?
|
||||
sheet.add_style "A#{data_start_index}:F#{data_end_index}", Styles::TABLE_DATA
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def data_start_index
|
||||
7
|
||||
end
|
||||
|
||||
def data_end_index
|
||||
(data_start_index + data.unreleased_appearances.size) - 1
|
||||
end
|
||||
|
||||
def column_widths
|
||||
[15, 15, 15, 15, 15, 50]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,74 @@
|
||||
module ExcelReports
|
||||
module VideoReports
|
||||
class DiscoveryProductionElementsLog
|
||||
attr_reader :video
|
||||
|
||||
def initialize(video)
|
||||
@video = video
|
||||
end
|
||||
|
||||
def to_xls
|
||||
build
|
||||
package.to_stream.read
|
||||
end
|
||||
|
||||
def filename(format = "xlsx")
|
||||
name = [video.file.filename.to_s, "production-elements-log"].map(&:parameterize).join("_")
|
||||
[name, format].join(".")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def talent_data
|
||||
@talent_data ||= data_for("TalentRelease")
|
||||
end
|
||||
|
||||
def appearance_data
|
||||
@appearance_data ||= data_for("AppearanceRelease")
|
||||
end
|
||||
|
||||
def location_data
|
||||
@location_data ||= data_for("LocationRelease")
|
||||
end
|
||||
|
||||
def acquired_media_data
|
||||
@acquired_media_data ||= data_for("AcquiredMediaRelease")
|
||||
end
|
||||
|
||||
def music_data
|
||||
@music_data ||= data_for("MusicRelease")
|
||||
end
|
||||
|
||||
def material_data
|
||||
@material_data ||= data_for("MaterialRelease")
|
||||
end
|
||||
|
||||
def data_for(release_type)
|
||||
video.
|
||||
video_release_confirmations.
|
||||
where(releasable_type: release_type).
|
||||
order(timecode_in: :asc).
|
||||
map { |confirmation| ReleasableDataAdapter.new(confirmation) }
|
||||
end
|
||||
|
||||
def build
|
||||
DiscoveryProductionElementsLogs::MediaRightsCertificationSheet.build(workbook)
|
||||
DiscoveryProductionElementsLogs::AcquiredFootageAndStillsSheet.build(workbook, acquired_media_data)
|
||||
DiscoveryProductionElementsLogs::MusicSheet.build(workbook, music_data)
|
||||
DiscoveryProductionElementsLogs::TalentSheet.build(workbook, talent_data)
|
||||
DiscoveryProductionElementsLogs::AppearanceSheet.build(workbook, appearance_data)
|
||||
DiscoveryProductionElementsLogs::LocationSheet.build(workbook, location_data)
|
||||
DiscoveryProductionElementsLogs::NameProductLogoSheet.build(workbook, material_data)
|
||||
DiscoveryProductionElementsLogs::ProductIntegrationSheet.build(workbook)
|
||||
end
|
||||
|
||||
def workbook
|
||||
@workbook ||= package.workbook
|
||||
end
|
||||
|
||||
def package
|
||||
@package ||= Axlsx::Package.new
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,104 @@
|
||||
module ExcelReports
|
||||
module VideoReports
|
||||
module DiscoveryProductionElementsLogs
|
||||
class AcquiredFootageAndStillsSheet < ::ExcelReports::Worksheet
|
||||
def title
|
||||
"Acquired Footage & Stills"
|
||||
end
|
||||
|
||||
def fill_content(sheet)
|
||||
sheet.add_row ["ACQUIRED FOOTAGE/STILLS/ PUBLIC DOMAIN LOG (for all episodes/programs)"]
|
||||
sheet.add_row [instructions]
|
||||
sheet.add_row
|
||||
sheet.add_row table_headers
|
||||
sheet.add_row table_subheaders
|
||||
|
||||
data.each do |confirmation_data|
|
||||
sheet.add_row [
|
||||
confirmation_data.episode_number,
|
||||
confirmation_data.episode_title,
|
||||
confirmation_data.source_file_and_clip_names,
|
||||
confirmation_data.timecode_in,
|
||||
confirmation_data.timecode_out,
|
||||
confirmation_data.duration,
|
||||
confirmation_data.description,
|
||||
licensor_with_phone_number(confirmation_data),
|
||||
confirmation_data.applicable_media,
|
||||
confirmation_data.territory,
|
||||
confirmation_data.term,
|
||||
confirmation_data.restrictions,
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
def format(sheet)
|
||||
sheet.column_widths *column_widths
|
||||
cells_to_merge.each { |cell| sheet.merge_cells cell }
|
||||
end
|
||||
|
||||
def style(sheet)
|
||||
sheet.add_style "A1", Styles::BOLD, { sz: 14 }
|
||||
sheet.add_style "A2", { sz: 10 }
|
||||
sheet.add_style "A4:L4", Styles::TABLE_HEADER
|
||||
sheet.add_style "B5:L5", Styles::TABLE_HEADER
|
||||
sheet.add_style "A4", Styles::WRAP_TEXT
|
||||
sheet.add_style "L4", Styles::WRAP_TEXT
|
||||
|
||||
if data.any?
|
||||
sheet.add_style "A#{data_start_index}:L#{data_end_index}", Styles::TABLE_DATA
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def licensor_with_phone_number(confirmation)
|
||||
[
|
||||
confirmation.name,
|
||||
confirmation.confirmation.releasable.person_phone
|
||||
].reject(&:blank?)
|
||||
.join("\n")
|
||||
end
|
||||
|
||||
def data_start_index
|
||||
6
|
||||
end
|
||||
|
||||
def data_end_index
|
||||
(data_start_index + data.size) - 1
|
||||
end
|
||||
|
||||
def instructions
|
||||
<<~INSTRUCTIONS
|
||||
All licenses must conform to the DCI contractual requirements and must be fully executed.
|
||||
|
||||
Releases, licenses and agreements should only be logged once, based on the order of their appearance in the program.
|
||||
|
||||
If an image does not appear in the final Program, log that release after those in the final Program; indicate on the Log sheet “NOT IN FINAL PROGRAM” and list the camera tape only.
|
||||
|
||||
Refer to the source of all third party footage in the exact form as it appears on the release.
|
||||
|
||||
Please note that if it is not possible to deliver an English language agreement, an English language translation must accompany any agreement delivered in a foreign language (if applicable).
|
||||
INSTRUCTIONS
|
||||
end
|
||||
|
||||
def table_headers
|
||||
["EPISODE NUMBER", "EPISODE TITLE", "CLIP #", "PROGRAM MASTER TC", "", "TOTAL TIME", "BRIEF VIDEO DESCRIPTION", "LICENSOR\n(incl phone number)", "EXPLOITABLE RIGHTS", "", "", "DCL Rights Waiver Uploaded?"]
|
||||
end
|
||||
|
||||
def table_subheaders
|
||||
["", "", "", "IN", "OUT", "", "", "", "MEDIA", "TERRITORY", "TERM", ""]
|
||||
end
|
||||
|
||||
def column_widths
|
||||
[10, 20, 25, 15, 15, 15, 20, 15, 15, 15, 15, 10]
|
||||
end
|
||||
|
||||
def cells_to_merge
|
||||
%w(
|
||||
A1:M1 A2:M2 A4:A5 B4:B5 C4:C5 D4:E4 F4:F5 G4:G5 H4:H5 I4:K4 L4:L5
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,77 @@
|
||||
module ExcelReports
|
||||
module VideoReports
|
||||
module DiscoveryProductionElementsLogs
|
||||
class AppearanceSheet < ::ExcelReports::Worksheet
|
||||
def title
|
||||
"Appearance"
|
||||
end
|
||||
|
||||
def fill_content(sheet)
|
||||
sheet.add_row ["APPEARANCE LOG (for all episodes/programs)", "", "", "", "", ""]
|
||||
sheet.add_row [instructions]
|
||||
sheet.add_row
|
||||
sheet.add_row ["EPISODE NUMBER(S) or LIST \"ALL\"", "EPISODE TITLE(S) or LIST \"ALL\"", "TIMECODE IN", "SOURCE TAPE / HARD DRIVE NUMBER", "NAME", "BRIEF VIDEO DESCRIPTION"]
|
||||
|
||||
data.each do |confirmation|
|
||||
sheet.add_row [
|
||||
confirmation.episode_number,
|
||||
confirmation.episode_title,
|
||||
confirmation.timecode_in,
|
||||
confirmation.source_file_and_clip_names,
|
||||
confirmation.name,
|
||||
confirmation.description
|
||||
]
|
||||
end
|
||||
|
||||
sheet.add_row
|
||||
sheet.add_row ["*On co-productions, releases & logs retained by Producer. Deliver upon request."]
|
||||
end
|
||||
|
||||
def format(sheet)
|
||||
sheet.column_widths *column_widths
|
||||
cells_to_merge.each { |cell| sheet.merge_cells cell }
|
||||
end
|
||||
|
||||
def style(sheet)
|
||||
sheet.add_style "A1", Styles::BOLD, { sz: 14 }
|
||||
sheet.add_style "A2", sz: 9
|
||||
sheet.add_style "A4:F4", Styles::TABLE_HEADER
|
||||
|
||||
if data.any?
|
||||
sheet.add_style "A#{data_start_index}:F#{data_end_index}", Styles::TABLE_DATA
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def instructions
|
||||
<<~INSTRUCTIONS
|
||||
All licenses must conform to the DCI contractual requirements and must be fully executed.
|
||||
|
||||
Releases, licenses and agreements should only be logged once, based on the order of their appearance in the program.
|
||||
|
||||
If an image does not appear in the final Program, log that release after those in the final Program; indicate on the Log sheet “NOT IN FINAL PROGRAM” and list the camera tape only.
|
||||
|
||||
Please note that if it is not possible to deliver an English language agreement, an English language translation must accompany any agreement delivered in a foreign language (if applicable)."
|
||||
INSTRUCTIONS
|
||||
end
|
||||
|
||||
def data_start_index
|
||||
5
|
||||
end
|
||||
|
||||
def data_end_index
|
||||
(data_start_index + data.size) - 1
|
||||
end
|
||||
|
||||
def column_widths
|
||||
[20, 25, 20, 20, 25, 30]
|
||||
end
|
||||
|
||||
def cells_to_merge
|
||||
%w[ A2:F2 ]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,74 @@
|
||||
module ExcelReports
|
||||
module VideoReports
|
||||
module DiscoveryProductionElementsLogs
|
||||
class LocationSheet < ::ExcelReports::Worksheet
|
||||
def title
|
||||
"Location"
|
||||
end
|
||||
|
||||
def fill_content(sheet)
|
||||
sheet.add_row ["LOCATION LOG (for all episodes/programs)"]
|
||||
sheet.add_row [instructions]
|
||||
sheet.add_row
|
||||
sheet.add_row table_headers
|
||||
|
||||
data.each do |confirmation|
|
||||
sheet.add_row [
|
||||
confirmation.episode_number,
|
||||
confirmation.episode_title,
|
||||
confirmation.timecode_in,
|
||||
confirmation.source_file_and_clip_names,
|
||||
[confirmation.name, confirmation.description].join(" ")
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
def format(sheet)
|
||||
sheet.column_widths *column_widths
|
||||
end
|
||||
|
||||
def style(sheet)
|
||||
sheet.add_style "A1", Styles::BOLD, { sz: 14 }
|
||||
sheet.add_style "A2", { sz: 9 }
|
||||
sheet.add_style "A4:E4", Styles::TABLE_HEADER
|
||||
sheet.add_style "A4:B4", Styles::WRAP_TEXT
|
||||
sheet.add_style "D4", Styles::WRAP_TEXT
|
||||
|
||||
if data.any?
|
||||
sheet.add_style "A#{data_start_index}:E#{data_end_index}", Styles::TABLE_DATA
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def instructions
|
||||
<<~INSTRUCTIONS
|
||||
All licenses must conform to the DCI contractual requirements and must be fully executed.
|
||||
|
||||
Releases, licenses and agreements should only be logged once, based on the order of their appearance in the program.
|
||||
|
||||
If an image does not appear in the final Program, log that release after those in the final Program; indicate on the Log sheet "NOT IN FINAL PROGRAM" and list the camera tape only.
|
||||
|
||||
Please note that if it is not possible to deliver an English language agreement, an English language translation must accompany any agreement delivered in a foreign language (if applicable).
|
||||
INSTRUCTIONS
|
||||
end
|
||||
|
||||
def table_headers
|
||||
["EPISODE NUMBER(S) or LIST \"ALL\"", "EPISODE TITLE(S) or LIST \"ALL\"", "TIMECODE IN", "SOURCE TAPE / HARD DRIVE NUMBER", "LOCATION NAME and BRIEF VIDEO DESCRIPTION"]
|
||||
end
|
||||
|
||||
def column_widths
|
||||
[20, 20, 15, 20, 40]
|
||||
end
|
||||
|
||||
def data_start_index
|
||||
5
|
||||
end
|
||||
|
||||
def data_end_index
|
||||
(data_start_index + data.size) - 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,61 @@
|
||||
module ExcelReports
|
||||
module VideoReports
|
||||
module DiscoveryProductionElementsLogs
|
||||
class MediaRightsCertificationSheet < ::ExcelReports::Worksheet
|
||||
def title
|
||||
"Media Rights Certification"
|
||||
end
|
||||
|
||||
def fill_content(sheet)
|
||||
sheet.add_row ["MEDIA RIGHTS CERTIFICATION (Required for final payment)", "", "", "", "", "", "", "", "", ""]
|
||||
sheet.add_row ["By uploading this file I certify that:"]
|
||||
sheet.add_row ["1) Check one"]
|
||||
sheet.add_row ["", "Rights waivers have been submitted and approved by DCL for all Program elements with any restriction or limitation not permitted by the Programming Agreement (including, without limitation, all music, footage, photographic stills, graphics, talent, and releases)."]
|
||||
sheet.add_row ["", "OR"]
|
||||
sheet.add_row ["", "No rights waivers are required because all the Program elements are fully cleared or subject only to the restrictions or limitations permitted by the Programming Agreement."]
|
||||
sheet.add_row ["AND"]
|
||||
sheet.add_row ["2) Check one\n\nIn the event the Programming Agreement permits that third-party stills or stock footage can be licensed with certain term or media restrictions (e.g., 10 year license or no theatrical rights or US standard television rights), it is further certified that this Program (check one):"]
|
||||
sheet.add_row ["", "Does contain third-party stills or stock footage secured subject to those restrictions."]
|
||||
sheet.add_row ["", "OR"]
|
||||
sheet.add_row ["", "Does not contain third-party stills or stock footage secured subject to those restrictions."]
|
||||
sheet.add_row ["", "OR"]
|
||||
sheet.add_row ["", "This Program does NOT contain any third-party stills or stock footage."]
|
||||
sheet.add_row ["Submitted By _____________________________"]
|
||||
sheet.add_row ["Uploading this document to the Producer’s Portal constitutes electronic signature"]
|
||||
end
|
||||
|
||||
def format(sheet)
|
||||
sheet.column_widths *column_widths
|
||||
cells_to_merge.each { |cell| sheet.merge_cells cell }
|
||||
end
|
||||
|
||||
def style(sheet)
|
||||
sheet.add_style "A1", { sz: 18 }, Styles::BOLD, Styles::UNDERLINE
|
||||
sheet.add_style "A2", { sz: 16 }, Styles::BOLD
|
||||
sheet.add_style "A2", { sz: 12 }, Styles::BOLD
|
||||
sheet.add_style "A7", { sz: 12 }, Styles::BOLD
|
||||
sheet.add_style "A14", { sz: 12 }, Styles::BOLD
|
||||
sheet.add_style "A15", { sz: 12 }, Styles::ITALIC, Styles::BOLD, Styles::COLOR_RED
|
||||
sheet.add_style "B5", Styles::COLOR_BLUE
|
||||
sheet.add_style "B10", Styles::COLOR_BLUE
|
||||
sheet.add_style "B12", Styles::COLOR_BLUE
|
||||
sheet.add_style "A8", Styles::WRAP_TEXT
|
||||
sheet.add_style "B4", Styles::WRAP_TEXT
|
||||
sheet.add_style "B6", Styles::WRAP_TEXT
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def column_widths
|
||||
[10, 20, 10, 10, 10, 10, 10, 10, 10, 10]
|
||||
end
|
||||
|
||||
def cells_to_merge
|
||||
%w[
|
||||
A1:J1 A2:J2 A3:J3 B4:J4 B5:J5 B6:J6 A8:J8 B9:J9 B10:J10 B11:J11 B12:J12 B13:J13 A14:J14 A15:J15
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,67 @@
|
||||
module ExcelReports
|
||||
module VideoReports
|
||||
module DiscoveryProductionElementsLogs
|
||||
class MusicSheet < ::ExcelReports::Worksheet
|
||||
def title
|
||||
"Music"
|
||||
end
|
||||
|
||||
def fill_content(sheet)
|
||||
sheet.add_row ["MUSIC LOG (for all episodes/programs) for commissioned work-for-hire music only"]
|
||||
sheet.add_row ["Not applicable if program contains 100% library"]
|
||||
sheet.add_row ["Episode Title(s) or list \"All\"", "TRACK TITLE", "COMPOSER", "PUBLISHER"]
|
||||
|
||||
data.each do |confirmation_data|
|
||||
sheet.add_row [
|
||||
confirmation_data.episode_title,
|
||||
confirmation_data.source_file_name,
|
||||
composers(confirmation_data),
|
||||
publishers(confirmation_data),
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
def format(sheet)
|
||||
sheet.column_widths *column_widths
|
||||
end
|
||||
|
||||
def style(sheet)
|
||||
sheet.add_style "A1", Styles::BOLD, { sz: 14 }
|
||||
sheet.add_style "A2", { sz: 14 }
|
||||
sheet.add_style "A3:D3", Styles::TABLE_HEADER
|
||||
sheet.add_style "A3", Styles::WRAP_TEXT
|
||||
|
||||
if has_data?
|
||||
sheet.add_style "A#{data_start_index}:D#{data_end_index}", Styles::TABLE_DATA
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def column_widths
|
||||
[20, 20, 30, 60]
|
||||
end
|
||||
|
||||
def has_data?
|
||||
data.any?
|
||||
end
|
||||
|
||||
def data_start_index
|
||||
4
|
||||
end
|
||||
|
||||
def data_end_index
|
||||
(data_start_index + data.size) - 1
|
||||
end
|
||||
|
||||
def composers(confirmation_data)
|
||||
confirmation_data.confirmation.composer_info.split("|").join("\n")
|
||||
end
|
||||
|
||||
def publishers(confirmation_data)
|
||||
confirmation_data.confirmation.publisher_info.split("|").join("\n")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,74 @@
|
||||
module ExcelReports
|
||||
module VideoReports
|
||||
module DiscoveryProductionElementsLogs
|
||||
class NameProductLogoSheet < ::ExcelReports::Worksheet
|
||||
def title
|
||||
"Name Product Logo"
|
||||
end
|
||||
|
||||
def fill_content(sheet)
|
||||
sheet.add_row ["NAME/PRODUCT/LOGO LOG (for all episodes/programs)"]
|
||||
sheet.add_row [instructions]
|
||||
sheet.add_row
|
||||
sheet.add_row table_headers
|
||||
|
||||
data.each do |confirmation|
|
||||
sheet.add_row [
|
||||
confirmation.episode_number,
|
||||
confirmation.episode_title,
|
||||
confirmation.timecode_in,
|
||||
confirmation.source_file_and_clip_names,
|
||||
confirmation.name
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
def format(sheet)
|
||||
sheet.column_widths *column_widths
|
||||
end
|
||||
|
||||
def style(sheet)
|
||||
sheet.add_style "A1", Styles::BOLD, { sz: 14 }
|
||||
sheet.add_style "A2", { sz: 9 }
|
||||
sheet.add_style "A4:E4", Styles::TABLE_HEADER
|
||||
sheet.add_style "A4:B4", Styles::WRAP_TEXT
|
||||
sheet.add_style "D4", Styles::WRAP_TEXT
|
||||
|
||||
if data.any?
|
||||
sheet.add_style "A#{data_start_index}:E#{data_end_index}", Styles::TABLE_DATA
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def instructions
|
||||
<<~INSTRUCTIONS
|
||||
All licenses must conform to the DCI contractual requirements and must be fully executed.
|
||||
|
||||
Releases, licenses and agreements should only be logged once, based on the order of their appearance in the program.
|
||||
|
||||
If an image does not appear in the final Program, log that release after those in the final Program; indicate on the Log sheet "NOT IN FINAL PROGRAM" and list the camera tape only.
|
||||
|
||||
Please note that if it is not possible to deliver an English language agreement, an English language translation must accompany any agreement delivered in a foreign language (if applicable).
|
||||
INSTRUCTIONS
|
||||
end
|
||||
|
||||
def table_headers
|
||||
["EPISODE NUMBER(S) or LIST \"ALL\"", "EPISODE TITLE(S) or LIST \"ALL\"", "TIMECODE IN", "SOURCE TAPE / HARD DRIVE NUMBER", "PRODUCT/LOGO NAME"]
|
||||
end
|
||||
|
||||
def column_widths
|
||||
[15, 25, 15, 20, 40]
|
||||
end
|
||||
|
||||
def data_start_index
|
||||
5
|
||||
end
|
||||
|
||||
def data_end_index
|
||||
(data_start_index + data.size) - 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,56 @@
|
||||
module ExcelReports
|
||||
module VideoReports
|
||||
module DiscoveryProductionElementsLogs
|
||||
class ProductIntegrationSheet < ::ExcelReports::Worksheet
|
||||
def title
|
||||
"Product Integration"
|
||||
end
|
||||
|
||||
def fill_content(sheet)
|
||||
sheet.add_row ["Product Integration / Trade-Out Log"]
|
||||
sheet.add_row [instructions]
|
||||
sheet.add_row
|
||||
sheet.add_row table_headers
|
||||
sheet.add_row table_headers2
|
||||
end
|
||||
|
||||
def format(sheet)
|
||||
sheet.column_widths *column_widths
|
||||
cells_to_merge.each { |cell| sheet.merge_cells cell }
|
||||
end
|
||||
|
||||
def style(sheet)
|
||||
sheet.add_style "A1", Styles::BOLD, { sz: 14 }
|
||||
sheet.add_style "A2", Styles::BOLD, { sz: 12 }
|
||||
sheet.add_style "A4:J4", Styles::TABLE_HEADER
|
||||
sheet.add_style "A5:J5", Styles::TABLE_HEADER
|
||||
sheet.add_style "A4:J4", Styles::WRAP_TEXT
|
||||
sheet.add_style "A5:J5", Styles::WRAP_TEXT
|
||||
sheet.add_style "I4:J4", Styles::BG_YELLOW
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def instructions
|
||||
"All contract agreements with companies providing trade-outs must be uploaded to the Producer's Portal"
|
||||
end
|
||||
|
||||
def table_headers
|
||||
["EPISODE #\nTitle\n(if applicable)", "PRODUCT ITEM(S)\n(ex: free airfare)", "COMPANY PROVIDING TRADE-OUT\n(ex: United Airlines)", "PRODUCT VALUE", "BUDGETED VALUE\nItem & Category in production budget", "OBLIGATIONS\n(ex: \"thanks to\" credit)", "AGREEMENT WITH COMPANY EXECUTION DATE", "DATE APPROVED BY DCI", "SELECT \"A\" OR \"B\"", ""]
|
||||
end
|
||||
|
||||
def table_headers2
|
||||
["", "", "", "", "", "", "", "", "A\nTrade will be savings to program List amount", "B\nTrade will be added value to program List amount"]
|
||||
end
|
||||
|
||||
def column_widths
|
||||
[15, 15, 15, 15, 15, 15, 15, 15, 15, 15]
|
||||
end
|
||||
|
||||
def cells_to_merge
|
||||
%w[ A4:A5 B4:B5 C4:C5 D4:D5 E4:E5 F4:F5 G4:G5 H4:H5 I4:J4 ]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,49 @@
|
||||
module ExcelReports
|
||||
module VideoReports
|
||||
module DiscoveryProductionElementsLogs
|
||||
class TalentSheet < ::ExcelReports::Worksheet
|
||||
def title
|
||||
"Talent"
|
||||
end
|
||||
|
||||
def fill_content(sheet)
|
||||
sheet.add_row ["TALENT AGREEMENT LOG (for all episodes/programs)"]
|
||||
sheet.add_row
|
||||
sheet.add_row ["Episode Number(s) or list \"All\"", "Episode Title(s) or list \"All\"", "SERVICES", "NAME/COMPANY"]
|
||||
|
||||
data.each do |confirmation|
|
||||
sheet.add_row [confirmation.episode_number, confirmation.episode_title, "", confirmation.name]
|
||||
end
|
||||
end
|
||||
|
||||
def format(sheet)
|
||||
sheet.column_widths *column_widths
|
||||
end
|
||||
|
||||
def style(sheet)
|
||||
sheet.add_style "A1", Styles::BOLD, { sz: 14 }
|
||||
sheet.add_style "A3:D3", Styles::TABLE_HEADER
|
||||
sheet.add_style "A3", Styles::WRAP_TEXT
|
||||
|
||||
if data.any?
|
||||
sheet.add_style "A#{data_start_index}:D#{data_end_index}", Styles::TABLE_DATA
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def column_widths
|
||||
[20, 20, 30, 60]
|
||||
end
|
||||
|
||||
def data_start_index
|
||||
4
|
||||
end
|
||||
|
||||
def data_end_index
|
||||
(data_start_index + data.size) - 1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,61 @@
|
||||
module ExcelReports
|
||||
module VideoReports
|
||||
class NatGeoLegalBinderLog
|
||||
attr_reader :video
|
||||
|
||||
def initialize(video)
|
||||
@video = video
|
||||
end
|
||||
|
||||
def to_xls
|
||||
build
|
||||
package.to_stream.read
|
||||
end
|
||||
|
||||
def filename
|
||||
name = [video.file.filename.to_s, "legal-binder-log"].map(&:parameterize).join("_")
|
||||
[name, "xlsx"].join(".")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def appearance_data
|
||||
@appearance_data ||= data_for("AppearanceRelease")
|
||||
end
|
||||
|
||||
def location_data
|
||||
@location_data ||= data_for("LocationRelease")
|
||||
end
|
||||
|
||||
def acquired_media_data
|
||||
@acquired_media_data ||= data_for("AcquiredMediaRelease")
|
||||
end
|
||||
|
||||
def data_for(release_type)
|
||||
video.
|
||||
video_release_confirmations.
|
||||
where(releasable_type: release_type).
|
||||
order(timecode_in: :asc).
|
||||
map { |confirmation| ExcelReports::VideoReports::ReleasableDataAdapter.new(confirmation) }
|
||||
end
|
||||
|
||||
|
||||
def build
|
||||
NatGeoLegalBinderLogs::LegalBinderChecklistSheet.build(workbook, [], video)
|
||||
NatGeoLegalBinderLogs::AppearanceReleaseLogSheet.build(workbook, appearance_data)
|
||||
NatGeoLegalBinderLogs::LocationReleaseLogSheet.build(workbook, location_data)
|
||||
NatGeoLegalBinderLogs::AcquiredFootageLogSheet.build(workbook, acquired_media_data)
|
||||
NatGeoLegalBinderLogs::ThirdPartyContractLogSheet.build(workbook, [])
|
||||
NatGeoLegalBinderLogs::ProductionPersonnelLogSheet.build(workbook, [])
|
||||
end
|
||||
|
||||
def workbook
|
||||
@workbook ||= package.workbook
|
||||
end
|
||||
|
||||
def package
|
||||
@package ||= Axlsx::Package.new
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,78 @@
|
||||
module ExcelReports
|
||||
module VideoReports
|
||||
module NatGeoLegalBinderLogs
|
||||
class AcquiredFootageLogSheet < Worksheet
|
||||
def title
|
||||
"Acquired Footage-Stills Log"
|
||||
end
|
||||
|
||||
def fill_content(sheet)
|
||||
sheet.add_row
|
||||
sheet.add_row
|
||||
sheet.add_row(["", "", "", "", "", "RIGHTS OBTAINED", "", "", ""])
|
||||
sheet.add_row(table_headers)
|
||||
|
||||
data.each_with_index do |confirmation, index|
|
||||
sheet.add_row [
|
||||
index + 1,
|
||||
confirmation.description,
|
||||
confirmation.timecode_in,
|
||||
confirmation.timecode_out,
|
||||
[confirmation.name, confirmation.contact_address].join("\n"),
|
||||
confirmation.applicable_media,
|
||||
confirmation.territory,
|
||||
confirmation.term,
|
||||
confirmation.restrictions,
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
def format(sheet)
|
||||
sheet.column_widths *column_widths
|
||||
cells_to_merge.each { |cell| sheet.merge_cells cell }
|
||||
end
|
||||
|
||||
def style(sheet)
|
||||
sheet.add_style "F3", Styles::BOLD, Styles::FULLY_CENTERED, Styles::BG_GRAY, Styles::THIN_BLACK_BORDER
|
||||
sheet.add_style "A4:I4", Styles::BOLD, Styles::FULLY_CENTERED, Styles::THIN_BLACK_BORDER, Styles::WRAP_TEXT
|
||||
|
||||
if data.any?
|
||||
sheet.add_style "A#{data_start_index}:I#{data_end_index}", Styles::THIN_BLACK_BORDER
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def table_headers
|
||||
[
|
||||
"Contract Number",
|
||||
"Brief description of footage or stills",
|
||||
"Program Master Timecode IN",
|
||||
"Program Master Timecode OUT",
|
||||
"Vendor Name and Contact Information",
|
||||
"Media",
|
||||
"Territory",
|
||||
"Term",
|
||||
"Restrictions",
|
||||
]
|
||||
end
|
||||
|
||||
def data_start_index
|
||||
5
|
||||
end
|
||||
|
||||
def data_end_index
|
||||
(data_start_index + data.size) - 1
|
||||
end
|
||||
|
||||
def column_widths
|
||||
[12, 25, 22, 22, 28, 18, 22, 22, 25]
|
||||
end
|
||||
|
||||
def cells_to_merge
|
||||
%w(F3:I3)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,82 @@
|
||||
module ExcelReports
|
||||
module VideoReports
|
||||
module NatGeoLegalBinderLogs
|
||||
class AppearanceReleaseLogSheet < Worksheet
|
||||
def title
|
||||
"Appearance Release Log"
|
||||
end
|
||||
|
||||
def fill_content(sheet)
|
||||
instructions1 = "PLEASE ARRANGE CONTRACTS IN ORDER OF FIRST APPEARANCE IN FINAL PROGRAM."
|
||||
instructions2 = "PLEASE INCLUDE ALL CONTRACTS SECURED FOR THE PROGRAM INCLUDING THOSE FOR ALL DIGITAL SOCIAL CONTENT."
|
||||
instructions3 = "IF INDIVIDUAL DID NOT APPEAR IN THE FINAL SHOW, PLEASE INDICATE \"NOT IN FINAL PROGRAM\" UNDER TIME CODE COLUMN & LIST ALPHABETICALLY."
|
||||
|
||||
sheet.add_row([instructions1])
|
||||
sheet.add_row([instructions2])
|
||||
sheet.add_row([instructions3])
|
||||
sheet.add_row
|
||||
sheet.add_row(["", "", "", "RIGHTS OBTAINED", "", "", "", "", ""])
|
||||
sheet.add_row(table_headers)
|
||||
|
||||
data.each_with_index do |confirmation, index|
|
||||
sheet.add_row [
|
||||
index + 1,
|
||||
confirmation.timecode_in,
|
||||
[confirmation.name, confirmation.contact_address].join("\n"),
|
||||
confirmation.applicable_media,
|
||||
confirmation.territory,
|
||||
confirmation.term,
|
||||
"",
|
||||
confirmation.restrictions,
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
def format(sheet)
|
||||
sheet.column_widths *column_widths
|
||||
cells_to_merge.each { |cell| sheet.merge_cells cell }
|
||||
end
|
||||
|
||||
def style(sheet)
|
||||
sheet.add_style "D5", Styles::BOLD, Styles::FULLY_CENTERED, Styles::BG_GRAY, Styles::THIN_BLACK_BORDER
|
||||
sheet.add_style "A6:H6", Styles::BOLD, Styles::FULLY_CENTERED, Styles::THIN_BLACK_BORDER, Styles::WRAP_TEXT
|
||||
|
||||
if data.any?
|
||||
sheet.add_style "A#{data_start_index}:H#{data_end_index}", Styles::THIN_BLACK_BORDER
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def table_headers
|
||||
[
|
||||
"Contract Number",
|
||||
"Program Master Timecode",
|
||||
"Person's Name and Contact Information",
|
||||
"Media",
|
||||
"Territory",
|
||||
"Term",
|
||||
"Program Specific Y/N",
|
||||
"Restrictions"
|
||||
]
|
||||
end
|
||||
|
||||
def data_start_index
|
||||
7
|
||||
end
|
||||
|
||||
def data_end_index
|
||||
(data_start_index + data.size) - 1
|
||||
end
|
||||
|
||||
def column_widths
|
||||
[12, 25, 30, 30, 30, 30, 15, 30]
|
||||
end
|
||||
|
||||
def cells_to_merge
|
||||
%w(A1:F1 A2:F2 A3:F3 D5:H5)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,50 @@
|
||||
module ExcelReports
|
||||
module VideoReports
|
||||
module NatGeoLegalBinderLogs
|
||||
class LegalBinderChecklistSheet < Worksheet
|
||||
def title
|
||||
"Legal Binder Checklist"
|
||||
end
|
||||
|
||||
def fill_content(sheet)
|
||||
sheet.add_row ["SERIES TITLE:", ""]
|
||||
sheet.add_row ["EPISODE NUMBER:", header_data.number]
|
||||
sheet.add_row ["EPISODE TITLE:", header_data.name]
|
||||
sheet.add_row
|
||||
sheet.add_row
|
||||
sheet.add_row ["", "INCLUDED", "PENDING", "N/A", "COMMENTS"]
|
||||
sheet.add_row ["COPY OF CREDIT LIST", "", "", "", ""]
|
||||
sheet.add_row ["COPY OF MUSIC CUE SHEET", "", "", "", ""]
|
||||
sheet.add_row ["COPY OF E&O CERTIFICATE", "", "", "", ""]
|
||||
sheet.add_row ["APPEARANCE RELEASES + LOG", "", "", "", ""]
|
||||
sheet.add_row ["LOCATION RELEASES/PERMITS + LOG", "", "", "", ""]
|
||||
sheet.add_row ["ACQUIRED FOOTAGE/STILLS CONTRACTS + LOG", "", "", "", ""]
|
||||
sheet.add_row ["THIRD PARTY CONTRACTS (GRAPHICS, COMPOSER, MUSIC LIBRARY, NARRATOR, TALENT) + LOG", "", "", "", ""]
|
||||
sheet.add_row ["PRODUCTION PERSONNEL LOG", "", "", "", ""]
|
||||
end
|
||||
|
||||
def format(sheet)
|
||||
sheet.column_widths *column_widths
|
||||
cells_to_merge.each { |cell| sheet.merge_cells cell }
|
||||
end
|
||||
|
||||
def style(sheet)
|
||||
sheet.add_style "A1:A3", Styles::BOLD, { sz: 14 }
|
||||
sheet.add_style "B6:E6", Styles::BOLD, Styles::THIN_BLACK_BORDER, Styles::FULLY_CENTERED, { sz: 10 }
|
||||
sheet.add_style "A7:A14", Styles::BOLD, Styles::THIN_BLACK_BORDER, Styles::WRAP_TEXT, { sz: 10 }
|
||||
sheet.add_style "B7:E14", Styles::THIN_BLACK_BORDER
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def column_widths
|
||||
[30, 12, 12, 10, 25]
|
||||
end
|
||||
|
||||
def cells_to_merge
|
||||
%w(B1:D1 B2:D2 B3:D3)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,84 @@
|
||||
module ExcelReports
|
||||
module VideoReports
|
||||
module NatGeoLegalBinderLogs
|
||||
class LocationReleaseLogSheet < Worksheet
|
||||
def title
|
||||
"Location Release Log"
|
||||
end
|
||||
|
||||
def fill_content(sheet)
|
||||
instructions1 = "PLEASE ARRANGE CONTRACTS IN ORDER OF FIRST APPEARANCE IN FINAL PROGRAM."
|
||||
instructions2 = "PLEASE INCLUDE ALL CONTRACTS SECURED FOR THE PROGRAM INCLUDING THOSE FOR ALL DIGITAL SOCIAL CONTENT."
|
||||
instructions3 = "IF INDIVIDUAL DID NOT APPEAR IN THE FINAL SHOW, PLEASE INDICATE \"NOT IN FINAL PROGRAM\" UNDER TIME CODE COLUMN & LIST ALPHABETICALLY."
|
||||
|
||||
sheet.add_row([instructions1])
|
||||
sheet.add_row([instructions2])
|
||||
sheet.add_row([instructions3])
|
||||
sheet.add_row
|
||||
sheet.add_row(["", "", "", "RIGHTS OBTAINED", "", "", "", "", ""])
|
||||
sheet.add_row(table_headers)
|
||||
|
||||
data.each_with_index do |confirmation, index|
|
||||
sheet.add_row [
|
||||
index + 1,
|
||||
confirmation.timecode_in,
|
||||
[confirmation.name, confirmation.confirmation.releasable.address].join("\n"),
|
||||
"",
|
||||
confirmation.applicable_media,
|
||||
confirmation.territory,
|
||||
confirmation.term,
|
||||
"",
|
||||
confirmation.restrictions,
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
def format(sheet)
|
||||
sheet.column_widths *column_widths
|
||||
cells_to_merge.each { |cell| sheet.merge_cells cell }
|
||||
end
|
||||
|
||||
def style(sheet)
|
||||
sheet.add_style "D5", Styles::BOLD, Styles::FULLY_CENTERED, Styles::BG_GRAY, Styles::THIN_BLACK_BORDER
|
||||
sheet.add_style "A6:I6", Styles::BOLD, Styles::FULLY_CENTERED, Styles::THIN_BLACK_BORDER, Styles::WRAP_TEXT
|
||||
|
||||
if data.any?
|
||||
sheet.add_style "A#{data_start_index}:I#{data_end_index}", Styles::THIN_BLACK_BORDER
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def table_headers
|
||||
[
|
||||
"Contract Number",
|
||||
"Program Master Timecode",
|
||||
"Location's Name and Contact Information",
|
||||
"Permit and/or Location Cost",
|
||||
"Media",
|
||||
"Territory",
|
||||
"Term",
|
||||
"Program Specific Y/N",
|
||||
"Restrictions"
|
||||
]
|
||||
end
|
||||
|
||||
def data_start_index
|
||||
7
|
||||
end
|
||||
|
||||
def data_end_index
|
||||
(data_start_index + data.size) - 1
|
||||
end
|
||||
|
||||
def column_widths
|
||||
[12, 25, 35, 35, 30, 30, 30, 15, 30]
|
||||
end
|
||||
|
||||
def cells_to_merge
|
||||
%w(A1:F1 A2:F2 A3:F3 D5:I5)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,50 @@
|
||||
module ExcelReports
|
||||
module VideoReports
|
||||
module NatGeoLegalBinderLogs
|
||||
class ProductionPersonnelLogSheet < Worksheet
|
||||
def title
|
||||
"Production Personnel Log"
|
||||
end
|
||||
|
||||
def fill_content(sheet)
|
||||
sheet.add_row
|
||||
sheet.add_row
|
||||
sheet.add_row(table_headers)
|
||||
sheet.add_row(["Producer", "", "", ""])
|
||||
sheet.add_row(["Director", "", "", ""])
|
||||
sheet.add_row(["Writer", "", "", ""])
|
||||
sheet.add_row(["Editor", "", "", ""])
|
||||
sheet.add_row(["Assistant Producer", "", "", ""])
|
||||
sheet.add_row(["Production Manager", "", "", ""])
|
||||
sheet.add_row(["Camera", "", "", ""])
|
||||
sheet.add_row(["Audio", "", "", ""])
|
||||
sheet.add_row(["Local Coordinator/Location Manager", "", "", ""])
|
||||
end
|
||||
|
||||
def format(sheet)
|
||||
sheet.column_widths *column_widths
|
||||
end
|
||||
|
||||
def style(sheet)
|
||||
sheet.add_style "A3:D3", Styles::BOLD, Styles::FULLY_CENTERED, Styles::THIN_BLACK_BORDER
|
||||
sheet.add_style "A4:D12", Styles::FULLY_CENTERED, Styles::THIN_BLACK_BORDER, Styles::WRAP_TEXT
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def table_headers
|
||||
[
|
||||
"Title",
|
||||
"Name",
|
||||
"Company",
|
||||
"Contact Info (Address/Email/Phone)",
|
||||
]
|
||||
end
|
||||
|
||||
def column_widths
|
||||
[30, 30, 35, 35]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,61 @@
|
||||
module ExcelReports
|
||||
module VideoReports
|
||||
module NatGeoLegalBinderLogs
|
||||
class ThirdPartyContractLogSheet < Worksheet
|
||||
def title
|
||||
"Third Party Contract Log"
|
||||
end
|
||||
|
||||
def fill_content(sheet)
|
||||
sheet.add_row
|
||||
sheet.add_row
|
||||
sheet.add_row(["", "", "RIGHTS OBTAINED", "", "", "", ""])
|
||||
sheet.add_row(table_headers)
|
||||
sheet.add_row(["Graphics", "", "", "", "", "", ""])
|
||||
sheet.add_row(["Composer", "", "", "", "", "", ""])
|
||||
sheet.add_row(["Music Library", "", "", "", "", "", ""])
|
||||
sheet.add_row(["Narrator", "", "", "", "", "", ""])
|
||||
sheet.add_row(["Talent", "", "", "", "", "", ""])
|
||||
sheet.add_row(["Equipment and/or Vehicle Rental (only applicable if equipment/vehicle appears on camera and contract contains rights language)", "", "", "", "", "", ""])
|
||||
sheet.add_row(["Production Facility (only applicable if contract contains rights language)", "", "", "", "", "", ""])
|
||||
sheet.add_row(["Post Production Facility (only applicable if contract contains rights language)", "", "", "", "", "", ""])
|
||||
sheet.add_row(["Other Facilities (only applicable if contract contains rights language)", "", "", "", "", "", ""])
|
||||
end
|
||||
|
||||
def format(sheet)
|
||||
sheet.column_widths *column_widths
|
||||
cells_to_merge.each { |cell| sheet.merge_cells cell }
|
||||
end
|
||||
|
||||
def style(sheet)
|
||||
sheet.add_style "C3", Styles::BOLD, Styles::FULLY_CENTERED, Styles::BG_GRAY, Styles::THIN_BLACK_BORDER
|
||||
sheet.add_style "A4:G4", Styles::BOLD, Styles::FULLY_CENTERED, Styles::THIN_BLACK_BORDER
|
||||
sheet.add_style "A5:A13", Styles::FULLY_CENTERED, Styles::THIN_BLACK_BORDER, Styles::WRAP_TEXT
|
||||
sheet.add_style "B5:G13", Styles::FULLY_CENTERED, Styles::THIN_BLACK_BORDER
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def table_headers
|
||||
[
|
||||
"Brief Description of Services",
|
||||
"3rd Party Name and Contact Information",
|
||||
"Media",
|
||||
"Territory",
|
||||
"Term",
|
||||
"Program Specific Y/N",
|
||||
"Restrictions",
|
||||
]
|
||||
end
|
||||
|
||||
def column_widths
|
||||
[25, 30, 20, 20, 20, 15, 20]
|
||||
end
|
||||
|
||||
def cells_to_merge
|
||||
%w(C3:G3)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,73 @@
|
||||
module ExcelReports
|
||||
module VideoReports
|
||||
class ReleasableDataAdapter
|
||||
attr_reader :confirmation
|
||||
|
||||
delegate :video, to: :confirmation
|
||||
|
||||
def initialize(release_confirmation)
|
||||
@confirmation = release_confirmation
|
||||
end
|
||||
|
||||
def description
|
||||
confirmation.description || confirmation.video.project.description
|
||||
end
|
||||
|
||||
def episode_number
|
||||
video.number
|
||||
end
|
||||
|
||||
def episode_title
|
||||
video.name
|
||||
end
|
||||
|
||||
def name
|
||||
confirmation.releasable.name
|
||||
end
|
||||
|
||||
def source_file_name
|
||||
confirmation.source_file_name
|
||||
end
|
||||
|
||||
def clip_name
|
||||
confirmation.clip_name
|
||||
end
|
||||
|
||||
def source_file_and_clip_names
|
||||
[source_file_name, clip_name].reject(&:blank?).join(" - ")
|
||||
end
|
||||
|
||||
def timecode_in
|
||||
confirmation.timecode_in
|
||||
end
|
||||
|
||||
def timecode_out
|
||||
confirmation.timecode_out
|
||||
end
|
||||
|
||||
def duration
|
||||
confirmation.duration
|
||||
end
|
||||
|
||||
def contact_address
|
||||
confirmation.releasable.contact_person.address.to_s
|
||||
end
|
||||
|
||||
def applicable_media
|
||||
confirmation.releasable.applicable_medium_value
|
||||
end
|
||||
|
||||
def territory
|
||||
confirmation.releasable.territory_value
|
||||
end
|
||||
|
||||
def term
|
||||
confirmation.releasable.term_value
|
||||
end
|
||||
|
||||
def restrictions
|
||||
confirmation.releasable.restriction_value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
53
app/models/excel_reports/worksheet.rb
Normal file
53
app/models/excel_reports/worksheet.rb
Normal file
@@ -0,0 +1,53 @@
|
||||
module ExcelReports
|
||||
class Worksheet
|
||||
attr_accessor :workbook, :data, :header_data
|
||||
|
||||
def self.build(workbook, data = nil, header_data = nil)
|
||||
new(workbook, data, header_data).tap do |worksheet|
|
||||
worksheet.build
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(workbook, data = nil, header_data = nil)
|
||||
@workbook = workbook
|
||||
@data = data
|
||||
@header_data = header_data
|
||||
end
|
||||
|
||||
def build
|
||||
workbook.add_worksheet(name: title) do |sheet|
|
||||
fill_content(sheet)
|
||||
format(sheet)
|
||||
style(sheet)
|
||||
end
|
||||
end
|
||||
|
||||
module Styles
|
||||
def self.merge_all(*styles)
|
||||
styles.each_with_object({}) { |style, combined| combined.deep_merge!(style) }
|
||||
end
|
||||
|
||||
BOLD = { b: true }
|
||||
ITALIC = { i: true }
|
||||
UNDERLINE = { u: true }
|
||||
BG_GRAY = { bg_color: "C0C0C0" }
|
||||
BG_LIGHT_BLUE = { bg_color: "97CBFC" }
|
||||
BG_YELLOW = { bg_color: "FFFF9E" }
|
||||
COLOR_WHITE = { fg_color: "FFFFFF" }
|
||||
COLOR_BLUE = { fg_color: "0000FF" }
|
||||
COLOR_RED = { fg_color: "FF0000" }
|
||||
THIN_BLACK_BORDER = { border: { style: :thin, color: "000000" } }
|
||||
THICK_BLACK_BORDER = { border: { style: :thick, color: "000000" } }
|
||||
VERTICAL_CENTER = { alignment: { vertical: :center } }
|
||||
HORIZONTAL_CENTER = { alignment: { horizontal: :center } }
|
||||
HORIZONTAL_LEFT = { alignment: { horizontal: :left } }
|
||||
HORIZONTAL_RIGHT = { alignment: { horizontal: :right } }
|
||||
FULLY_CENTERED = merge_all(HORIZONTAL_CENTER, VERTICAL_CENTER)
|
||||
WRAP_TEXT = { alignment: { wrap_text: true } }
|
||||
TABLE_HEADER = merge_all(Styles::BG_LIGHT_BLUE, Styles::BOLD, Styles::THICK_BLACK_BORDER, Styles::FULLY_CENTERED, Styles::WRAP_TEXT, { sz: 8 })
|
||||
TABLE_DATA = merge_all(Styles::THICK_BLACK_BORDER, Styles::WRAP_TEXT, { sz: 10 })
|
||||
PROGRAM_HEADER = merge_all(Styles::BG_LIGHT_BLUE, Styles::BOLD, Styles::THICK_BLACK_BORDER, Styles::HORIZONTAL_CENTER, Styles::WRAP_TEXT, { sz: 12 })
|
||||
HEADER_DATA = merge_all(Styles::THICK_BLACK_BORDER, Styles::WRAP_TEXT, Styles::HORIZONTAL_LEFT, { sz: 10 })
|
||||
end
|
||||
end
|
||||
end
|
||||
12
app/models/file_info.rb
Normal file
12
app/models/file_info.rb
Normal file
@@ -0,0 +1,12 @@
|
||||
class FileInfo < ApplicationRecord
|
||||
belongs_to :releasable, polymorphic: true
|
||||
|
||||
scope :audio, -> { where("content_type ILIKE ?", "%audio%") }
|
||||
scope :video, -> { where("content_type ILIKE ?", "%video%") }
|
||||
scope :photo, -> { where("content_type ILIKE ?", "%image%") }
|
||||
scope :other, -> { where("NOT content_type ILIKE ?", "%(image|video|audio)%") }
|
||||
|
||||
def self.search_filename(query)
|
||||
where("filename ILIKE ?", "%#{query}%")
|
||||
end
|
||||
end
|
||||
28
app/models/files_for_request.rb
Normal file
28
app/models/files_for_request.rb
Normal file
@@ -0,0 +1,28 @@
|
||||
class FilesForRequest
|
||||
def initialize(video)
|
||||
@video = video
|
||||
end
|
||||
|
||||
def start_timecode_offset
|
||||
end
|
||||
|
||||
def file_object_name
|
||||
video.file.key if video.file.attached?
|
||||
end
|
||||
|
||||
def edl_file_object_name
|
||||
video.edl_file.key if video.edl_file.attached?
|
||||
end
|
||||
|
||||
def aws_bucket_name
|
||||
ENV["AWS_BUCKET"]
|
||||
end
|
||||
|
||||
def job_id
|
||||
video.analysis_uid
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :video
|
||||
end
|
||||
21
app/models/filter_set.rb
Normal file
21
app/models/filter_set.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
class FilterSet
|
||||
def initialize(filters, current: nil, default: nil)
|
||||
@filters = filters
|
||||
@current = current
|
||||
@default = default
|
||||
end
|
||||
|
||||
def active?(filter)
|
||||
current_filter == filter
|
||||
end
|
||||
|
||||
def current_filter
|
||||
@current || @default
|
||||
end
|
||||
|
||||
def name_and_values(include_default: true)
|
||||
@filters.map { |filter| [filter.titleize, filter] }.tap do |result|
|
||||
result.prepend [@default.titleize, @default] if include_default
|
||||
end
|
||||
end
|
||||
end
|
||||
11
app/models/graphics_element.rb
Normal file
11
app/models/graphics_element.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
class GraphicsElement < ApplicationRecord
|
||||
belongs_to :video
|
||||
|
||||
validates :graphic_type, presence: true
|
||||
validates :text, presence: true
|
||||
validates :time_elapsed, presence: true
|
||||
|
||||
def appears_at
|
||||
Timecode.from_seconds(time_elapsed.to_f).to_s
|
||||
end
|
||||
end
|
||||
28
app/models/graphics_files_for_request.rb
Normal file
28
app/models/graphics_files_for_request.rb
Normal file
@@ -0,0 +1,28 @@
|
||||
class GraphicsFilesForRequest
|
||||
attr_reader :start_timecode_offset
|
||||
|
||||
def initialize(video, start_timecode_offset)
|
||||
@video = video
|
||||
@start_timecode_offset = start_timecode_offset
|
||||
end
|
||||
|
||||
def file_object_name
|
||||
video.file.key if video.file.attached?
|
||||
end
|
||||
|
||||
def edl_file_object_name
|
||||
video.graphics_only_edl_file.key if video.graphics_only_edl_file.attached?
|
||||
end
|
||||
|
||||
def aws_bucket_name
|
||||
ENV["AWS_BUCKET"]
|
||||
end
|
||||
|
||||
def job_id
|
||||
video.analysis_uid
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :video
|
||||
end
|
||||
44
app/models/headshot_collection.rb
Normal file
44
app/models/headshot_collection.rb
Normal file
@@ -0,0 +1,44 @@
|
||||
# Represents a collection of releases with photos
|
||||
class HeadshotCollection
|
||||
attr_reader :collection_uid, :releasables
|
||||
|
||||
def self.for_project(project)
|
||||
appearance_releases_with_photo = project.appearance_releases.with_person_photo
|
||||
|
||||
new(project.headshot_collection_uid, appearance_releases_with_photo + project.talent_releases)
|
||||
end
|
||||
|
||||
def initialize(collection_uid, releasables)
|
||||
@collection_uid = collection_uid
|
||||
@releasables = releasables
|
||||
end
|
||||
|
||||
# Use the custom hash to generate JSON format
|
||||
def as_json(*)
|
||||
to_hash
|
||||
end
|
||||
|
||||
def to_hash
|
||||
{
|
||||
collection_uid: collection_uid.to_s,
|
||||
bucket_name: aws_bucket_name,
|
||||
ids_to_images: map_ids_to_images,
|
||||
}.reject { |_, v| v.blank? }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def aws_bucket_name
|
||||
ENV["AWS_BUCKET"]
|
||||
end
|
||||
|
||||
def map_ids_to_images
|
||||
releasables.each_with_object({}) do |release, hash|
|
||||
hash[release_id(release)] = [release.photo.key] # An array of images is expected, even if there's only one image
|
||||
end
|
||||
end
|
||||
|
||||
def release_id(release)
|
||||
[release.model_name.param_key, release.id].join("_")
|
||||
end
|
||||
end
|
||||
10
app/models/import.rb
Normal file
10
app/models/import.rb
Normal file
@@ -0,0 +1,10 @@
|
||||
class Import < ApplicationRecord
|
||||
belongs_to :project
|
||||
belongs_to :releasable, polymorphic: true, optional: true
|
||||
|
||||
enum status: %i[ pending started finished failed ]
|
||||
|
||||
has_one_attached :file
|
||||
|
||||
validates :file, content_type: ["application/pdf"]
|
||||
end
|
||||
70
app/models/location_release.rb
Normal file
70
app/models/location_release.rb
Normal file
@@ -0,0 +1,70 @@
|
||||
class LocationRelease < ApplicationRecord
|
||||
include Confirmable
|
||||
include Contractable
|
||||
include Exploitable
|
||||
include Notable
|
||||
include Photoable
|
||||
include Releasable
|
||||
include Searchable
|
||||
include Signable
|
||||
include Syncable
|
||||
include Taggable
|
||||
include PersonName
|
||||
|
||||
composed_of :address,
|
||||
mapping: [
|
||||
%w(address_street1 street1),
|
||||
%w(address_street2 street2),
|
||||
%w(address_city city),
|
||||
%w(address_state state),
|
||||
%w(address_zip zip),
|
||||
%w(address_country country)
|
||||
]
|
||||
composed_of :person_address,
|
||||
class_name: "Address",
|
||||
mapping: [
|
||||
%w(person_address_street1 street1),
|
||||
%w(person_address_street2 street2),
|
||||
%w(person_address_city city),
|
||||
%w(person_address_state state),
|
||||
%w(person_address_zip zip),
|
||||
%w(person_address_country country)
|
||||
]
|
||||
|
||||
validates :name, presence: true
|
||||
validates :person_email, email: true, allow_blank: true
|
||||
validate :end_date_after_start_date
|
||||
|
||||
with_options on: :native do
|
||||
validates :person_first_name, :person_last_name, presence: true
|
||||
validates :signature, attached: true
|
||||
end
|
||||
|
||||
searchable_on %i[
|
||||
name
|
||||
address_street1 address_street2 address_city address_state address_zip address_country
|
||||
person_address_street1 person_address_street2 person_address_city person_address_state person_address_zip person_address_country
|
||||
]
|
||||
|
||||
def contact_person
|
||||
@contact_person ||= Contact.new(person_name, person_address, person_email, person_phone)
|
||||
end
|
||||
|
||||
def minor?
|
||||
false
|
||||
end
|
||||
|
||||
def uses_edl?
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def end_date_after_start_date
|
||||
return true if filming_ended_on.blank? || filming_started_on.blank?
|
||||
|
||||
if filming_ended_on < filming_started_on
|
||||
errors.add(:filming_ended_on, "must be after the filming started on date")
|
||||
end
|
||||
end
|
||||
end
|
||||
6
app/models/main_photo.rb
Normal file
6
app/models/main_photo.rb
Normal file
@@ -0,0 +1,6 @@
|
||||
# A class that allows a single item from a collection of attachments to function as a single attachment
|
||||
class MainPhoto < SimpleDelegator
|
||||
def attached?
|
||||
__getobj__.present?
|
||||
end
|
||||
end
|
||||
49
app/models/material_release.rb
Normal file
49
app/models/material_release.rb
Normal file
@@ -0,0 +1,49 @@
|
||||
class MaterialRelease < ApplicationRecord
|
||||
include Confirmable
|
||||
include Contractable
|
||||
include Exploitable
|
||||
include Notable
|
||||
include Photoable
|
||||
include Releasable
|
||||
include Searchable
|
||||
include Signable
|
||||
include Syncable
|
||||
include Taggable
|
||||
include PersonName
|
||||
|
||||
composed_of :person_address,
|
||||
class_name: "Address",
|
||||
mapping: [
|
||||
%w(person_address_street1 street1),
|
||||
%w(person_address_street2 street2),
|
||||
%w(person_address_city city),
|
||||
%w(person_address_state state),
|
||||
%w(person_address_zip zip),
|
||||
%w(person_address_country country)
|
||||
]
|
||||
|
||||
validates :name, presence: true
|
||||
validates :person_email, email: true, allow_blank: true
|
||||
|
||||
with_options on: :native do
|
||||
validates :person_first_name, :person_last_name, presence: true
|
||||
validates :signature, attached: true
|
||||
end
|
||||
|
||||
searchable_on %i[
|
||||
name
|
||||
person_address_street1 person_address_street2 person_address_city person_address_state person_address_zip person_address_country
|
||||
]
|
||||
|
||||
def contact_person
|
||||
@contact_person ||= Contact.new(person_name, person_address, person_email, person_phone)
|
||||
end
|
||||
|
||||
def minor?
|
||||
false
|
||||
end
|
||||
|
||||
def uses_edl?
|
||||
true
|
||||
end
|
||||
end
|
||||
88
app/models/music_release.rb
Normal file
88
app/models/music_release.rb
Normal file
@@ -0,0 +1,88 @@
|
||||
class MusicRelease < ApplicationRecord
|
||||
include Confirmable
|
||||
include Contractable
|
||||
include Exploitable
|
||||
include Notable
|
||||
include Releasable
|
||||
include Searchable
|
||||
include Taggable
|
||||
include PersonName
|
||||
|
||||
has_many :file_infos, as: :releasable, dependent: :destroy
|
||||
has_many :composers, dependent: :destroy
|
||||
has_many :publishers, dependent: :destroy
|
||||
|
||||
accepts_nested_attributes_for :file_infos
|
||||
accepts_nested_attributes_for :composers, reject_if: :all_blank
|
||||
accepts_nested_attributes_for :publishers, reject_if: :all_blank
|
||||
|
||||
composed_of :person_address,
|
||||
class_name: "Address",
|
||||
mapping: [
|
||||
%w(person_address_street1 street1),
|
||||
%w(person_address_street2 street2),
|
||||
%w(person_address_city city),
|
||||
%w(person_address_state state),
|
||||
%w(person_address_zip zip),
|
||||
%w(person_address_country country)
|
||||
]
|
||||
|
||||
validates :name, presence: true
|
||||
validates :person_email, email: true, allow_blank: true
|
||||
validates :composers, :length => { :minimum => 1, :message => "at least 1 required" }
|
||||
validates :publishers, :length => { :minimum => 1, :message => "at least 1 required" }
|
||||
validate :publisher_percentages_add_up_to_100
|
||||
validate :composer_percentages_add_up_to_100
|
||||
|
||||
searchable_on %i[
|
||||
name
|
||||
person_address_street1 person_address_street2 person_address_city person_address_state person_address_zip person_address_country
|
||||
]
|
||||
|
||||
def contact_person
|
||||
@contact_person ||= Contact.new(person_name, person_address, person_email, person_phone)
|
||||
end
|
||||
|
||||
def uses_edl?
|
||||
true
|
||||
end
|
||||
|
||||
def composer_info
|
||||
composers.map do |composer|
|
||||
[
|
||||
composer.name,
|
||||
composer.affiliation,
|
||||
composer.percentage,
|
||||
("$cae:#{composer.cae_number}" if composer.cae_number.present?),
|
||||
].compact.join(", ")
|
||||
end.join("|")
|
||||
end
|
||||
|
||||
def publisher_info
|
||||
publishers.map do |publisher|
|
||||
[
|
||||
publisher.name,
|
||||
publisher.affiliation,
|
||||
publisher.percentage,
|
||||
].join(", ")
|
||||
end.join("|")
|
||||
end
|
||||
|
||||
def native?
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def publisher_percentages_add_up_to_100
|
||||
if publishers.size > 0 && publishers.sum(&:percentage).to_f != 100.0
|
||||
errors.add(:base, "Publisher percentages must add up to 100%")
|
||||
end
|
||||
end
|
||||
|
||||
def composer_percentages_add_up_to_100
|
||||
if composers.size > 0 && composers.sum(&:percentage).to_f != 100.0
|
||||
errors.add(:base, "Composer percentages must add up to 100%")
|
||||
end
|
||||
end
|
||||
end
|
||||
63
app/models/mux_live_stream.rb
Normal file
63
app/models/mux_live_stream.rb
Normal file
@@ -0,0 +1,63 @@
|
||||
class MuxLiveStream
|
||||
def id
|
||||
live_stream.data.id
|
||||
end
|
||||
|
||||
def key
|
||||
live_stream.data.stream_key
|
||||
end
|
||||
|
||||
def playback_id
|
||||
playback.data.id
|
||||
end
|
||||
|
||||
def destroy_stream(stream_uid)
|
||||
client.delete_live_stream(stream_uid)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def live_stream
|
||||
@live_stream ||= create_live_stream
|
||||
end
|
||||
|
||||
def playback
|
||||
@playback ||= create_playback
|
||||
end
|
||||
|
||||
# Source: https://github.com/muxinc/mux-ruby/blob/1.4.0/examples/video/exercise-live-streams.rb
|
||||
|
||||
def create_live_stream
|
||||
create_asset_request = MuxRuby::CreateAssetRequest.new
|
||||
create_asset_request.playback_policy = [MuxRuby::PlaybackPolicy::PUBLIC]
|
||||
create_asset_request.mp4_support = "standard"
|
||||
create_live_stream_request = MuxRuby::CreateLiveStreamRequest.new
|
||||
create_live_stream_request.new_asset_settings = create_asset_request
|
||||
create_live_stream_request.playback_policy = [MuxRuby::PlaybackPolicy::PUBLIC]
|
||||
create_live_stream_request.reduced_latency = reduced_latency_enabled?
|
||||
create_live_stream_request.test = test_mode_enabled?
|
||||
|
||||
client.create_live_stream(create_live_stream_request)
|
||||
end
|
||||
|
||||
def create_playback
|
||||
create_playback_id_request = MuxRuby::CreatePlaybackIDRequest.new
|
||||
# TODO: Use signed policy in the future
|
||||
# create_playback_id_request.policy = MuxRuby::PlaybackPolicy::SIGNED
|
||||
create_playback_id_request.policy = MuxRuby::PlaybackPolicy::PUBLIC
|
||||
|
||||
client.create_live_stream_playback_id(live_stream.data.id, create_playback_id_request)
|
||||
end
|
||||
|
||||
def reduced_latency_enabled?
|
||||
ENV["MUX_REDUCED_LATENCY_ENABLED"].present?
|
||||
end
|
||||
|
||||
def test_mode_enabled?
|
||||
!ENV["MUX_TEST_MODE_DISABLED"].present?
|
||||
end
|
||||
|
||||
def client
|
||||
@client ||= MuxRuby::LiveStreamsApi.new
|
||||
end
|
||||
end
|
||||
7
app/models/note.rb
Normal file
7
app/models/note.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
class Note < ApplicationRecord
|
||||
include Syncable
|
||||
belongs_to :user, optional: true
|
||||
belongs_to :notable, polymorphic: true
|
||||
|
||||
validates :content, presence: true
|
||||
end
|
||||
13
app/models/null_project.rb
Normal file
13
app/models/null_project.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
class NullProject
|
||||
def account
|
||||
Account.new(plan_uid: "me_suite")
|
||||
end
|
||||
|
||||
def name
|
||||
"Projects"
|
||||
end
|
||||
|
||||
def persisted?
|
||||
false
|
||||
end
|
||||
end
|
||||
121
app/models/pending_analysis.rb
Normal file
121
app/models/pending_analysis.rb
Normal file
@@ -0,0 +1,121 @@
|
||||
class PendingAnalysis
|
||||
def self.poll
|
||||
PendingVideoAnalysis.poll
|
||||
PendingAudioAnalysis.poll
|
||||
end
|
||||
|
||||
def self.expire(age_threshold)
|
||||
PendingAudioAnalysis.expire(age_threshold)
|
||||
PendingVideoAnalysis.expire(age_threshold)
|
||||
end
|
||||
|
||||
class PendingVideoAnalysis
|
||||
def initialize(analysis_uid)
|
||||
@analysis_uid = analysis_uid
|
||||
end
|
||||
|
||||
def self.poll
|
||||
Video.
|
||||
analysis_pending.
|
||||
pluck(:analysis_uid).
|
||||
each { |uid| new(uid).poll }
|
||||
end
|
||||
|
||||
def self.expire(age)
|
||||
Video.
|
||||
analysis_pending.
|
||||
video_analysis_started_before(age).
|
||||
pluck(:analysis_uid).
|
||||
each { |video| new(video).expire }
|
||||
end
|
||||
|
||||
def poll
|
||||
if results_available?
|
||||
notification.success!
|
||||
end
|
||||
end
|
||||
|
||||
def expire
|
||||
if results_available?
|
||||
notification.success!
|
||||
else
|
||||
notification.failure!
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :analysis_uid
|
||||
|
||||
def notification
|
||||
@notification ||= AnalysisNotification.build(:video, analysis_uid)
|
||||
end
|
||||
|
||||
def results_available?
|
||||
facial_recognition = BrayniacAI::FacialRecognition.find(analysis_uid)
|
||||
facial_recognition.respond_to?(:results)
|
||||
rescue ActiveResource::ResourceNotFound => e
|
||||
false
|
||||
rescue ActiveResource::ServerError => e
|
||||
message = "-- Video Analysis polling for #{analysis_uid} failed due to: #{e}"
|
||||
puts message
|
||||
Rails.logger.error(message)
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
class PendingAudioAnalysis
|
||||
def self.poll
|
||||
Video.
|
||||
audio_analysis_pending.
|
||||
pluck(:audio_analysis_uid).
|
||||
each { |uid| new(uid).poll }
|
||||
end
|
||||
|
||||
def self.expire(age)
|
||||
Video.
|
||||
audio_analysis_pending.
|
||||
audio_analysis_started_before(age).
|
||||
pluck(:audio_analysis_uid).
|
||||
each { |uid| new(uid).expire }
|
||||
end
|
||||
|
||||
def initialize(analysis_uid)
|
||||
@analysis_uid = analysis_uid
|
||||
end
|
||||
|
||||
def poll
|
||||
if results_available?
|
||||
notification.success!
|
||||
end
|
||||
end
|
||||
|
||||
def expire
|
||||
if results_available?
|
||||
notification.success!
|
||||
else
|
||||
notification.failure!
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :analysis_uid
|
||||
|
||||
def notification
|
||||
@notification ||= AnalysisNotification.build(:audio, analysis_uid)
|
||||
end
|
||||
|
||||
def results_available?
|
||||
audio_recognition = BrayniacAI::AudioRecognition.find(analysis_uid)
|
||||
audio_recognition.respond_to?(:results)
|
||||
rescue ActiveResource::ResourceNotFound => e
|
||||
false
|
||||
rescue ActiveResource::ServerError => e
|
||||
message = "-- Audio Analysis polling for #{analysis_uid} failed due to: #{e}"
|
||||
puts message
|
||||
Rails.logger.error(message)
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
126
app/models/project.rb
Normal file
126
app/models/project.rb
Normal file
@@ -0,0 +1,126 @@
|
||||
class Project < ApplicationRecord
|
||||
include Archivable
|
||||
include Filterable
|
||||
include Syncable
|
||||
|
||||
SIGNABLE_RELEASE_TYPES = %w(talent appearance acquired_media location material)
|
||||
AVAILABLE_RELEASE_TYPES = %w(appearance location material acquired_media talent music)
|
||||
|
||||
belongs_to :account
|
||||
has_many :acquired_media_releases, dependent: :destroy
|
||||
has_many :appearance_releases, dependent: :destroy
|
||||
has_many :location_releases, dependent: :destroy
|
||||
has_many :material_releases, dependent: :destroy
|
||||
has_many :music_releases, dependent: :destroy
|
||||
has_many :talent_releases, dependent: :destroy
|
||||
has_many :videos, dependent: :destroy
|
||||
has_many :imports, dependent: :destroy
|
||||
has_many :contract_templates, dependent: :destroy
|
||||
has_many :project_memberships, dependent: :destroy
|
||||
has_many :users, through: :project_memberships
|
||||
has_many :directories, dependent: :destroy
|
||||
has_many :downloads, dependent: :destroy
|
||||
has_many :broadcasts, dependent: :destroy
|
||||
has_many :zoom_meetings, dependent: :destroy
|
||||
|
||||
accepts_nested_attributes_for :project_memberships
|
||||
|
||||
has_settings do |s|
|
||||
s.key :features, defaults: {
|
||||
acquired_media_release: false,
|
||||
appearance_release: true,
|
||||
location_release: false,
|
||||
material_release: false,
|
||||
music_release: false,
|
||||
talent_release: false,
|
||||
video_analysis: false,
|
||||
}
|
||||
end
|
||||
|
||||
validates :name, presence: true, uniqueness: { scope: :account_id }
|
||||
|
||||
filterable_by :active, :inactive, :archived
|
||||
|
||||
scope :order_by_name, -> { order(name: :asc) }
|
||||
|
||||
def all_releases_count
|
||||
AVAILABLE_RELEASE_TYPES.sum { |name| public_send("#{name}_releases").count }
|
||||
end
|
||||
|
||||
def members
|
||||
users + account.managers
|
||||
end
|
||||
|
||||
attr_reader :predefined_client_name
|
||||
def predefined_client_name=(value)
|
||||
@predefined_client_name = value
|
||||
|
||||
case value.to_s
|
||||
when "discovery"
|
||||
self.client_name = value.titleize
|
||||
self.settings(:features).attributes = {
|
||||
acquired_media_release: true,
|
||||
appearance_release: true,
|
||||
location_release: true,
|
||||
material_release: true,
|
||||
music_release: true,
|
||||
talent_release: true,
|
||||
video_analysis: true,
|
||||
}
|
||||
when "nat_geo"
|
||||
self.client_name = value.titleize
|
||||
self.settings(:features).attributes = {
|
||||
acquired_media_release: true,
|
||||
appearance_release: true,
|
||||
location_release: true,
|
||||
material_release: true,
|
||||
music_release: true,
|
||||
talent_release: true,
|
||||
video_analysis: true,
|
||||
}
|
||||
else
|
||||
# Do nothing - the features will be set manually
|
||||
end
|
||||
end
|
||||
|
||||
def features_settings=(values)
|
||||
return if self.predefined_client_name.to_s != "other"
|
||||
settings(:features).attributes = values.transform_values { |v| !v.to_i.zero? }
|
||||
end
|
||||
|
||||
def feature_enabled?(name)
|
||||
settings(:features).send(name)
|
||||
end
|
||||
|
||||
def import_contract_templates(template_ids)
|
||||
ContractTemplate.where(id: template_ids).each do |contract_template|
|
||||
contract_templates << contract_template.dup.tap do |imported_contract_template|
|
||||
imported_contract_template.parent = contract_template
|
||||
|
||||
# Rich text fields must be manually duplicated because they are associations
|
||||
if contract_template.body.present?
|
||||
imported_contract_template.body.body = contract_template.body.body.dup
|
||||
end
|
||||
if contract_template.guardian_clause.present?
|
||||
imported_contract_template.guardian_clause.body = contract_template.guardian_clause.body.dup
|
||||
end
|
||||
end
|
||||
end
|
||||
save
|
||||
end
|
||||
|
||||
def zoom_meeting_url
|
||||
zoom_meeting.meeting_url
|
||||
end
|
||||
|
||||
def zoom_meeting
|
||||
current_zoom_meeting = zoom_meetings.active.first
|
||||
|
||||
unless current_zoom_meeting.present?
|
||||
zoom_user = ZoomUser.free.first || ZoomUser.create
|
||||
current_zoom_meeting = ZoomMeeting.create(zoom_user: zoom_user, project: self)
|
||||
end
|
||||
|
||||
current_zoom_meeting
|
||||
end
|
||||
end
|
||||
47
app/models/project_membership.rb
Normal file
47
app/models/project_membership.rb
Normal file
@@ -0,0 +1,47 @@
|
||||
class ProjectMembership < ApplicationRecord
|
||||
belongs_to :project
|
||||
belongs_to :user
|
||||
|
||||
validates :user_id, uniqueness: { scope: :project_id, message: "already belongs to this Project" }
|
||||
validate :account_manager_is_prohibited
|
||||
|
||||
scope :with_account_auth, -> (account) { joins(user: :account_auths).where(account_auths: { account_id: account }) }
|
||||
scope :order_by_user_role_and_user_email, -> (account) { with_account_auth(account).order("account_auths.role DESC, users.email") }
|
||||
|
||||
attr_accessor :user_email
|
||||
|
||||
def new_user?
|
||||
@user_is_new
|
||||
end
|
||||
|
||||
def save_and_update_account_membership
|
||||
ApplicationRecord.transaction do
|
||||
if User.exists?(email: user_email)
|
||||
@user_is_new = false
|
||||
self.user = User.find_by(email: user_email)
|
||||
else
|
||||
@user_is_new = true
|
||||
self.user = Oath::Services::SignUp.new(email: user_email, password: SecureRandom.hex).perform
|
||||
end
|
||||
|
||||
account_auth = user.account_auths.find_or_initialize_by(account: project.account)
|
||||
|
||||
if !(user.save && account_auth.save && save)
|
||||
raise ActiveRecord::Rollback
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def account_manager_is_prohibited
|
||||
return if project.nil?
|
||||
|
||||
if user.account_manager?(project.account)
|
||||
errors.add(:user, "is already an account manager")
|
||||
end
|
||||
end
|
||||
end
|
||||
4
app/models/publisher.rb
Normal file
4
app/models/publisher.rb
Normal file
@@ -0,0 +1,4 @@
|
||||
class Publisher < ApplicationRecord
|
||||
belongs_to :music_release
|
||||
validates :name, :affiliation, :percentage, presence: true
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user