2011-02-01 09:57:29 +08:00
#
# Copyright (C) 2011 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
class MediaObject < ActiveRecord :: Base
include Workflow
belongs_to :user
belongs_to :context , :polymorphic = > true
belongs_to :attachment
2011-03-31 07:04:34 +08:00
belongs_to :root_account , :class_name = > 'Account'
2013-01-16 05:14:29 +08:00
validates_presence_of :media_id
2011-02-01 09:57:29 +08:00
after_create :retrieve_details_later
after_save :update_title_on_kaltura_later
serialize :data
2011-05-14 00:49:23 +08:00
2011-09-28 02:15:28 +08:00
attr_accessible :media_id , :title , :context , :user
2011-05-14 00:49:23 +08:00
2011-02-10 01:26:42 +08:00
attr_accessor :podcast_associated_asset
2011-02-01 09:57:29 +08:00
def user_entered_title = ( val )
@push_user_title = true
write_attribute ( :user_entered_title , val )
end
2012-11-16 23:42:07 +08:00
2011-02-01 09:57:29 +08:00
def update_title_on_kaltura_later
send_later ( :update_title_on_kaltura ) if @push_user_title
@push_user_title = nil
end
2012-11-16 23:42:07 +08:00
2011-09-26 11:31:10 +08:00
def self . find_by_media_id ( media_id )
unless Rails . env . production?
raise " Do not look up MediaObjects by media_id - use the scope by_media_id instead to support migrated content. "
end
super
end
2012-01-13 00:52:41 +08:00
2011-04-24 08:51:53 +08:00
# if wait_for_completion is true, this will wait SYNCHRONOUSLY for the bulk
# upload to complete. Wrap it in a timeout if you ever want it to give up
# waiting.
def self . add_media_files ( attachments , wait_for_completion )
2011-04-25 22:28:40 +08:00
return unless Kaltura :: ClientV3 . config
2011-02-01 09:57:29 +08:00
attachments = Array ( attachments )
client = Kaltura :: ClientV3 . new
client . startSession ( Kaltura :: SessionType :: ADMIN )
files = [ ]
2012-02-02 00:32:24 +08:00
root_account_id = attachments . map { | a | a . root_account_id } . compact . first
2011-02-01 09:57:29 +08:00
attachments . select { | a | ! a . media_object } . each do | attachment |
files << {
:name = > attachment . display_name ,
2012-01-23 15:09:38 +08:00
:url = > attachment . cacheable_s3_download_url ,
2011-02-01 09:57:29 +08:00
:media_type = > ( attachment . content_type || " " ) . match ( / \ Avideo / ) ? 'video' : 'audio' ,
:id = > attachment . id
}
end
res = client . bulkUploadAdd ( files )
2011-04-24 08:51:53 +08:00
2011-02-01 09:57:29 +08:00
if ! res [ :ready ]
2011-04-24 08:51:53 +08:00
if wait_for_completion
bulk_upload_id = res [ :id ]
Rails . logger . debug " waiting for bulk upload id: #{ bulk_upload_id } "
while ! res [ :ready ]
sleep ( 1 . minute . to_i )
res = client . bulkUploadGet ( bulk_upload_id )
end
else
2012-05-11 00:39:33 +08:00
MediaObject . send_later_enqueue_args ( :refresh_media_files , { :run_at = > 1 . minute . from_now , :priority = > Delayed :: LOW_PRIORITY } , res [ :id ] , attachments . map ( & :id ) , root_account_id )
2011-04-24 08:51:53 +08:00
end
end
if res [ :ready ]
2011-03-31 07:04:34 +08:00
build_media_objects ( res , root_account_id )
2011-02-01 09:57:29 +08:00
end
res
end
2012-11-16 23:42:07 +08:00
2011-03-31 07:04:34 +08:00
def self . bulk_migration ( csv , root_account_id )
client = Kaltura :: ClientV3 . new
client . startSession ( Kaltura :: SessionType :: ADMIN )
res = client . bulkUploadCsv ( csv )
if ! res [ :ready ]
2012-05-11 00:39:33 +08:00
MediaObject . send_later_enqueue_args ( :refresh_media_files , { :run_at = > 1 . minute . from_now , :priority = > Delayed :: LOW_PRIORITY } , res [ :id ] , [ ] , root_account_id )
2011-03-31 07:04:34 +08:00
else
build_media_objects ( res , root_account_id )
end
res
end
2012-11-16 23:42:07 +08:00
2011-03-31 07:04:34 +08:00
def self . migration_csv ( media_objects )
FasterCSV . generate do | csv |
media_objects . each do | mo |
mo . retrieve_details unless mo . data [ :download_url ]
if mo . data [ :download_url ]
row = [ ]
row << mo . title
row << " "
row << " old_id_ #{ mo . media_id } "
row << mo . data [ :download_url ]
row << mo . media_type . capitalize
csv << row
end
end
end
end
2012-01-13 00:52:41 +08:00
2011-03-31 07:04:34 +08:00
def self . build_media_objects ( data , root_account_id )
root_account = Account . find_by_id ( root_account_id )
2011-02-01 09:57:29 +08:00
data [ :entries ] . each do | entry |
2011-04-27 11:55:24 +08:00
attachment = Attachment . find_by_id ( entry [ :originalId ] ) if entry [ :originalId ] . present?
2011-03-31 07:04:34 +08:00
mo = MediaObject . find_or_initialize_by_media_id ( entry [ :entryId ] )
mo . root_account || = root_account || Account . default
mo . title || = entry [ :name ]
2011-02-01 09:57:29 +08:00
if attachment
mo . user_id || = attachment . user_id
2011-03-31 07:04:34 +08:00
mo . context = attachment . context
2011-02-01 09:57:29 +08:00
mo . attachment_id = attachment . id
attachment . update_attribute ( :media_entry_id , entry [ :entryId ] )
2012-01-13 00:52:41 +08:00
# check for attachments that were created temporarily, just to import a media object
if attachment . full_path . starts_with? ( File . join ( Folder :: ROOT_FOLDER_NAME , CC :: CCHelper :: MEDIA_OBJECTS_FOLDER ) + '/' )
attachment . destroy ( false )
end
2011-02-01 09:57:29 +08:00
end
2011-03-31 07:04:34 +08:00
mo . context || = mo . root_account
mo . save
2011-02-01 09:57:29 +08:00
end
end
2012-01-13 00:52:41 +08:00
2011-03-31 07:04:34 +08:00
def self . refresh_media_files ( bulk_upload_id , attachment_ids , root_account_id , attempt = 0 )
2011-02-01 09:57:29 +08:00
client = Kaltura :: ClientV3 . new
client . startSession ( Kaltura :: SessionType :: ADMIN )
res = client . bulkUploadGet ( bulk_upload_id )
if ! res [ :ready ]
2012-01-13 00:52:41 +08:00
if attempt < Setting . get ( 'media_object_bulk_refresh_max_attempts' , '5' ) . to_i
wait_period = Setting . get ( 'media_object_bulk_refresh_wait_period' , '30' ) . to_i
2012-05-11 00:39:33 +08:00
MediaObject . send_later_enqueue_args ( :refresh_media_files , { :run_at = > wait_period . minutes . from_now , :priority = > Delayed :: LOW_PRIORITY } , bulk_upload_id , attachment_ids , root_account_id , attempt + 1 )
2011-02-01 09:57:29 +08:00
else
# if it fails, then the attachment should no longer consider itself kalturable
2011-03-31 07:04:34 +08:00
Attachment . update_all ( { :media_entry_id = > nil } , " id IN ( #{ attachment_ids . join ( " , " ) } ) OR root_attachment_id IN ( #{ attachment_ids . join ( " , " ) } ) " ) unless attachment_ids . empty?
2011-02-01 09:57:29 +08:00
end
res
else
2011-03-31 07:04:34 +08:00
build_media_objects ( res , root_account_id )
2011-02-01 09:57:29 +08:00
end
end
2012-07-14 05:51:26 +08:00
def self . media_id_exists? ( media_id )
client = Kaltura :: ClientV3 . new
client . startSession ( Kaltura :: SessionType :: ADMIN )
info = client . mediaGet ( media_id )
return ! ! info [ :id ]
end
def self . ensure_media_object ( media_id , create_opts = { } )
if ! by_media_id ( media_id ) . any?
self . send_later_enqueue_args ( :create_if_id_exists , { :priority = > Delayed :: LOW_PRIORITY } , media_id , create_opts )
end
end
# typically call this in a delayed job, since it has to contact kaltura
def self . create_if_id_exists ( media_id , create_opts = { } )
if media_id_exists? ( media_id ) && ! by_media_id ( media_id ) . any?
create! ( create_opts . merge ( :media_id = > media_id ) )
end
end
2011-02-01 09:57:29 +08:00
def update_title_on_kaltura
client = Kaltura :: ClientV3 . new
client . startSession ( Kaltura :: SessionType :: ADMIN )
res = client . mediaUpdate ( self . media_id , :name = > self . user_entered_title )
if ! res [ :error ]
self . title = self . user_entered_title
self . save
end
res
end
2012-11-16 23:42:07 +08:00
2011-02-01 09:57:29 +08:00
def retrieve_details_later
send_later ( :retrieve_details_ensure_codecs )
end
2013-01-16 05:14:29 +08:00
def media_sources
Kaltura :: ClientV3 . new . media_sources ( self . media_id )
end
2012-11-16 23:42:07 +08:00
2011-02-01 09:57:29 +08:00
def retrieve_details_ensure_codecs ( attempt = 0 )
retrieve_details
if ( ! self . data || ! self . data [ :extensions ] || ! self . data [ :extensions ] [ :flv ] ) && self . created_at > 6 . hours . ago
if ( attempt < 10 )
send_at ( ( 5 * attempt ) . minutes . from_now , :retrieve_details_ensure_codecs , attempt + 1 )
else
2011-05-12 00:31:07 +08:00
ErrorReport . log_error ( :default , {
2011-02-01 09:57:29 +08:00
:message = > " Kaltura flavor retrieval failed " ,
:object = > self . inspect . to_s ,
} )
end
end
end
2012-11-16 23:42:07 +08:00
2011-02-01 09:57:29 +08:00
def name
self . title
end
2012-11-16 23:42:07 +08:00
2011-02-01 09:57:29 +08:00
def retrieve_details
return unless self . media_id
# From Kaltura, retrieve the title (if it's not already set)
# and the list of valid flavors along with their id's.
# Might as well confirm the media type while you're at it.
client = Kaltura :: ClientV3 . new
client . startSession ( Kaltura :: SessionType :: ADMIN )
self . data || = { }
entry = client . mediaGet ( self . media_id )
if entry
self . title = entry [ :name ]
self . media_type = client . mediaTypeToSymbol ( entry [ :mediaType ] ) . to_s
self . duration = entry [ :duration ] . to_i
2011-03-31 04:42:52 +08:00
self . data [ :plays ] = entry [ :plays ] . to_i
2011-03-31 07:04:34 +08:00
self . data [ :download_url ] = entry [ :downloadUrl ]
tags = ( entry [ :tags ] || " " ) . split ( / , / ) . map ( & :strip )
old_id = tags . detect { | t | t . match ( / old_id_ / ) }
self . old_media_id = old_id . sub ( / old_id_ / , '' ) if old_id
2011-02-01 09:57:29 +08:00
end
assets = client . flavorAssetGetByEntryId ( self . media_id )
self . data [ :extensions ] || = { }
assets . each do | asset |
2011-03-31 07:04:34 +08:00
asset [ :fileExt ] = " none " if asset [ :fileExt ] . blank?
2011-02-01 09:57:29 +08:00
self . data [ :extensions ] [ asset [ :fileExt ] . to_sym ] = asset #.slice(:width, :height, :id, :entryId, :status, :containerFormat, :fileExt, :size
if asset [ :size ]
self . max_size = [ self . max_size || 0 , asset [ :size ] . to_i ] . max
end
end
self . total_size = [ self . max_size || 0 , assets . map { | a | ( a [ :size ] || 0 ) . to_i } . sum ] . max
self . save
self . data
end
2012-11-16 23:42:07 +08:00
2011-02-01 09:57:29 +08:00
def podcast_format_details
data = self . data && self . data [ :extensions ] && self . data [ :extensions ] [ :mp3 ]
data || = self . data && self . data [ :extensions ] && self . data [ :extensions ] [ :mp4 ]
if ! data
self . retrieve_details
data || = self . data && self . data [ :extensions ] && self . data [ :extensions ] [ :mp3 ]
data || = self . data && self . data [ :extensions ] && self . data [ :extensions ] [ :mp4 ]
end
data
end
2012-11-16 23:42:07 +08:00
2011-03-31 04:42:52 +08:00
def delete_from_remote
return unless self . media_id
client = Kaltura :: ClientV3 . new
client . startSession ( Kaltura :: SessionType :: ADMIN )
client . mediaDelete ( self . media_id )
end
2012-11-16 23:42:07 +08:00
2011-02-01 09:57:29 +08:00
alias_method :destroy! , :destroy
def destroy
2011-03-31 04:42:52 +08:00
self . workflow_state = 'deleted'
2011-02-01 09:57:29 +08:00
self . attachment . destroy if self . attachment
save!
end
2012-11-16 23:42:07 +08:00
2011-03-31 04:42:52 +08:00
def data
self . read_attribute ( :data ) || self . write_attribute ( :data , { } )
end
def viewed!
send_later ( :updated_viewed_at_and_retrieve_details , Time . now ) if ! self . data [ :last_viewed_at ] || self . data [ :last_viewed_at ] > 1 . hour . ago
true
end
2012-11-16 23:42:07 +08:00
2011-03-31 04:42:52 +08:00
def updated_viewed_at_and_retrieve_details ( time )
self . data [ :last_viewed_at ] = [ time , self . data [ :last_viewed_at ] ] . compact . max
self . retrieve_details
end
2012-11-16 23:42:07 +08:00
2011-02-01 09:57:29 +08:00
def destroy_without_destroying_attachment
self . workflow_state = 'deleted'
self . attachment_id = nil
save!
end
2012-11-16 23:42:07 +08:00
2011-02-01 09:57:29 +08:00
named_scope :active , lambda {
{ :conditions = > [ 'media_objects.workflow_state != ?' , 'deleted' ] }
}
2012-11-16 23:42:07 +08:00
2011-09-26 11:31:10 +08:00
named_scope :by_media_id , lambda { | media_id |
{ :conditions = > [ 'media_objects.media_id = ? OR media_objects.old_media_id = ?' , media_id , media_id ] }
}
2012-11-16 23:42:07 +08:00
2011-09-26 11:31:10 +08:00
named_scope :by_media_type , lambda { | media_type |
{ :conditions = > [ 'media_objects.media_type = ?' , media_type ] }
}
2012-11-16 23:42:07 +08:00
2011-02-01 09:57:29 +08:00
workflow do
state :active
state :deleted
end
end