logo

mastofe

My custom branche(s) on git.pleroma.social/pleroma/mastofe git clone https://hacktivis.me/git/mastofe.git

media_attachment.rb (6039B)


  1. # frozen_string_literal: true
  2. # == Schema Information
  3. #
  4. # Table name: media_attachments
  5. #
  6. # id :integer not null, primary key
  7. # status_id :integer
  8. # file_file_name :string
  9. # file_content_type :string
  10. # file_file_size :integer
  11. # file_updated_at :datetime
  12. # remote_url :string default(""), not null
  13. # created_at :datetime not null
  14. # updated_at :datetime not null
  15. # shortcode :string
  16. # type :integer default("image"), not null
  17. # file_meta :json
  18. # account_id :integer
  19. # description :text
  20. #
  21. require 'mime/types'
  22. class MediaAttachment < ApplicationRecord
  23. self.inheritance_column = nil
  24. enum type: [:image, :gifv, :video, :unknown]
  25. IMAGE_FILE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif'].freeze
  26. VIDEO_FILE_EXTENSIONS = ['.webm', '.mp4', '.m4v'].freeze
  27. IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif'].freeze
  28. VIDEO_MIME_TYPES = ['video/webm', 'video/mp4'].freeze
  29. IMAGE_STYLES = {
  30. original: {
  31. geometry: '1280x1280>',
  32. file_geometry_parser: FastGeometryParser,
  33. },
  34. small: {
  35. geometry: '400x400>',
  36. file_geometry_parser: FastGeometryParser,
  37. },
  38. }.freeze
  39. VIDEO_STYLES = {
  40. small: {
  41. convert_options: {
  42. output: {
  43. vf: 'scale=\'min(400\, iw):min(400\, ih)\':force_original_aspect_ratio=decrease',
  44. },
  45. },
  46. format: 'png',
  47. time: 0,
  48. },
  49. }.freeze
  50. LIMIT = 8.megabytes
  51. belongs_to :account, inverse_of: :media_attachments, optional: true
  52. belongs_to :status, inverse_of: :media_attachments, optional: true
  53. has_attached_file :file,
  54. styles: ->(f) { file_styles f },
  55. processors: ->(f) { file_processors f },
  56. convert_options: { all: '-quality 90 -strip' }
  57. validates_attachment_content_type :file, content_type: IMAGE_MIME_TYPES + VIDEO_MIME_TYPES
  58. validates_attachment_size :file, less_than: LIMIT
  59. remotable_attachment :file, LIMIT
  60. validates :account, presence: true
  61. validates :description, length: { maximum: 420 }, if: :local?
  62. scope :attached, -> { where.not(status_id: nil) }
  63. scope :unattached, -> { where(status_id: nil) }
  64. scope :local, -> { where(remote_url: '') }
  65. scope :remote, -> { where.not(remote_url: '') }
  66. default_scope { order(id: :asc) }
  67. def local?
  68. remote_url.blank?
  69. end
  70. def needs_redownload?
  71. file.blank? && remote_url.present?
  72. end
  73. def to_param
  74. shortcode
  75. end
  76. def focus=(point)
  77. return if point.blank?
  78. x, y = (point.is_a?(Enumerable) ? point : point.split(',')).map(&:to_f)
  79. meta = file.instance_read(:meta) || {}
  80. meta['focus'] = { 'x' => x, 'y' => y }
  81. file.instance_write(:meta, meta)
  82. end
  83. def focus
  84. x = file.meta['focus']['x']
  85. y = file.meta['focus']['y']
  86. "#{x},#{y}"
  87. end
  88. before_create :prepare_description, unless: :local?
  89. before_create :set_shortcode
  90. before_post_process :set_type_and_extension
  91. before_save :set_meta
  92. class << self
  93. private
  94. def file_styles(f)
  95. if f.instance.file_content_type == 'image/gif'
  96. {
  97. small: IMAGE_STYLES[:small],
  98. original: {
  99. format: 'mp4',
  100. convert_options: {
  101. output: {
  102. 'movflags' => 'faststart',
  103. 'pix_fmt' => 'yuv420p',
  104. 'vf' => 'scale=\'trunc(iw/2)*2:trunc(ih/2)*2\'',
  105. 'vsync' => 'cfr',
  106. 'c:v' => 'h264',
  107. 'b:v' => '500K',
  108. 'maxrate' => '1300K',
  109. 'bufsize' => '1300K',
  110. 'crf' => 18,
  111. },
  112. },
  113. },
  114. }
  115. elsif IMAGE_MIME_TYPES.include? f.instance.file_content_type
  116. IMAGE_STYLES
  117. else
  118. VIDEO_STYLES
  119. end
  120. end
  121. def file_processors(f)
  122. if f.file_content_type == 'image/gif'
  123. [:gif_transcoder]
  124. elsif VIDEO_MIME_TYPES.include? f.file_content_type
  125. [:video_transcoder]
  126. else
  127. [:thumbnail]
  128. end
  129. end
  130. end
  131. private
  132. def set_shortcode
  133. self.type = :unknown if file.blank? && !type_changed?
  134. return unless local?
  135. loop do
  136. self.shortcode = SecureRandom.urlsafe_base64(14)
  137. break if MediaAttachment.find_by(shortcode: shortcode).nil?
  138. end
  139. end
  140. def prepare_description
  141. self.description = description.strip[0...420] unless description.nil?
  142. end
  143. def set_type_and_extension
  144. self.type = VIDEO_MIME_TYPES.include?(file_content_type) ? :video : :image
  145. extension = appropriate_extension
  146. basename = Paperclip::Interpolations.basename(file, :original)
  147. file.instance_write :file_name, [basename, extension].delete_if(&:blank?).join('.')
  148. end
  149. def set_meta
  150. meta = populate_meta
  151. return if meta == {}
  152. file.instance_write :meta, meta
  153. end
  154. def populate_meta
  155. meta = file.instance_read(:meta) || {}
  156. file.queued_for_write.each do |style, file|
  157. meta[style] = style == :small || image? ? image_geometry(file) : video_metadata(file)
  158. end
  159. meta
  160. end
  161. def image_geometry(file)
  162. width, height = FastImage.size(file.path)
  163. return {} if width.nil?
  164. {
  165. width: width,
  166. height: height,
  167. size: "#{width}x#{height}",
  168. aspect: width.to_f / height.to_f,
  169. }
  170. end
  171. def video_metadata(file)
  172. movie = FFMPEG::Movie.new(file.path)
  173. return {} unless movie.valid?
  174. {
  175. width: movie.width,
  176. height: movie.height,
  177. frame_rate: movie.frame_rate,
  178. duration: movie.duration,
  179. bitrate: movie.bitrate,
  180. }
  181. end
  182. def appropriate_extension
  183. mime_type = MIME::Types[file.content_type]
  184. extensions_for_mime_type = mime_type.empty? ? [] : mime_type.first.extensions
  185. original_extension = Paperclip::Interpolations.extension(file, :original)
  186. extensions_for_mime_type.include?(original_extension) ? original_extension : extensions_for_mime_type.first
  187. end
  188. end