class MediaFile < ApplicationRecord # Base class for all media files (Video, Audio, etc.) # Uses Single Table Inheritance (STI) via the 'type' column self.table_name = 'videos' self.abstract_class = true include Streamable include Processable # Common JSON stores for flexible metadata store :fingerprints, accessors: [:xxhash64, :md5, :oshash, :phash] store :media_metadata, accessors: [:duration, :codec, :bit_rate, :format] # Common associations belongs_to :work belongs_to :storage_location has_many :playback_sessions, dependent: :destroy # Common validations validates :filename, presence: true validates :xxhash64, presence: true, uniqueness: { scope: :storage_location_id } # Common scopes scope :web_compatible, -> { where(web_compatible: true) } scope :needs_transcoding, -> { where(web_compatible: false) } scope :recent, -> { order(created_at: :desc) } # Common delegations delegate :display_title, to: :work, prefix: true, allow_nil: true # Common instance methods def display_title work&.display_title || filename end def full_file_path File.join(storage_location.path, filename) end def format_duration return "Unknown" unless duration hours = (duration / 3600).to_i minutes = ((duration % 3600) / 60).to_i seconds = (duration % 60).to_i if hours > 0 "%d:%02d:%02d" % [hours, minutes, seconds] else "%d:%02d" % [minutes, seconds] end end # Template method for subclasses to override def web_stream_path # Default implementation - subclasses can override for specific behavior if transcoded_permanently? && transcoded_path && File.exist?(transcoded_full_path) return transcoded_full_path end if transcoded_path && File.exist?(temp_transcoded_full_path) return temp_transcoded_full_path end full_file_path if web_compatible? end def transcoded_full_path return nil unless transcoded_path File.join(storage_location.path, transcoded_path) end def temp_transcoded_full_path return nil unless transcoded_path File.join(Rails.root, 'tmp', 'transcodes', storage_location.id.to_s, transcoded_path) end end