You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

209 lines
5.9 KiB

  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, :audio, :unknown]
  25. IMAGE_FILE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif'].freeze
  26. VIDEO_FILE_EXTENSIONS = ['.webm', '.mp4', '.m4v'].freeze
  27. AUDIO_FILE_EXTENSIONS = ['.mp3', '.m4a', '.wav', '.ogg'].freeze
  28. IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif'].freeze
  29. VIDEO_MIME_TYPES = ['video/webm', 'video/mp4'].freeze
  30. AUDIO_MIME_TYPES = ['audio/mpeg', 'audio/mp4', 'audio/vnd.wav', 'audio/wav', 'audio/x-wav', 'audio/x-wave', 'audio/ogg',].freeze
  31. IMAGE_STYLES = { original: '1280x1280>', small: '400x400>' }.freeze
  32. AUDIO_STYLES = {
  33. original: {
  34. format: 'mp4',
  35. convert_options: {
  36. output: {
  37. filter_complex: '"[0:a]compand,showwaves=s=640x360:mode=line,format=yuv420p[v]"',
  38. map: '"[v]" -map 0:a',
  39. threads: 2,
  40. vcodec: 'libx264',
  41. acodec: 'aac',
  42. movflags: '+faststart',
  43. },
  44. },
  45. },
  46. }.freeze
  47. VIDEO_STYLES = {
  48. small: {
  49. convert_options: {
  50. output: {
  51. vf: 'scale=\'min(400\, iw):min(400\, ih)\':force_original_aspect_ratio=decrease',
  52. },
  53. },
  54. format: 'png',
  55. time: 0,
  56. },
  57. }.freeze
  58. belongs_to :account, inverse_of: :media_attachments
  59. belongs_to :status, inverse_of: :media_attachments
  60. has_attached_file :file,
  61. styles: ->(f) { file_styles f },
  62. processors: ->(f) { file_processors f },
  63. convert_options: { all: '-quality 90 -strip' }
  64. include Remotable
  65. validates_attachment_content_type :file, content_type: IMAGE_MIME_TYPES + VIDEO_MIME_TYPES + AUDIO_MIME_TYPES
  66. validates_attachment_size :file, less_than: 8.megabytes
  67. validates :account, presence: true
  68. validates :description, length: { maximum: 420 }, if: :local?
  69. scope :attached, -> { where.not(status_id: nil) }
  70. scope :unattached, -> { where(status_id: nil) }
  71. scope :local, -> { where(remote_url: '') }
  72. scope :remote, -> { where.not(remote_url: '') }
  73. default_scope { order(id: :asc) }
  74. def local?
  75. remote_url.blank?
  76. end
  77. def needs_redownload?
  78. file.blank? && remote_url.present?
  79. end
  80. def to_param
  81. shortcode
  82. end
  83. before_create :prepare_description, unless: :local?
  84. before_create :set_shortcode
  85. before_post_process :set_type_and_extension
  86. before_save :set_meta
  87. class << self
  88. private
  89. def file_styles(f)
  90. if f.instance.file_content_type == 'image/gif'
  91. {
  92. small: IMAGE_STYLES[:small],
  93. original: {
  94. format: 'mp4',
  95. convert_options: {
  96. output: {
  97. 'movflags' => 'faststart',
  98. 'pix_fmt' => 'yuv420p',
  99. 'vf' => 'scale=\'trunc(iw/2)*2:trunc(ih/2)*2\'',
  100. 'vsync' => 'cfr',
  101. 'b:v' => '1300K',
  102. 'maxrate' => '500K',
  103. 'bufsize' => '1300K',
  104. 'crf' => 18,
  105. },
  106. },
  107. },
  108. }
  109. elsif IMAGE_MIME_TYPES.include? f.instance.file_content_type
  110. IMAGE_STYLES
  111. elsif AUDIO_MIME_TYPES.include? f.instance.file_content_type
  112. AUDIO_STYLES
  113. else
  114. VIDEO_STYLES
  115. end
  116. end
  117. def file_processors(f)
  118. if f.file_content_type == 'image/gif'
  119. [:gif_transcoder]
  120. elsif VIDEO_MIME_TYPES.include? f.file_content_type
  121. [:video_transcoder]
  122. elsif AUDIO_MIME_TYPES.include? f.file_content_type
  123. [:audio_transcoder]
  124. else
  125. [:thumbnail]
  126. end
  127. end
  128. end
  129. private
  130. def set_shortcode
  131. self.type = :unknown if file.blank? && !type_changed?
  132. return unless local?
  133. loop do
  134. self.shortcode = SecureRandom.urlsafe_base64(14)
  135. break if MediaAttachment.find_by(shortcode: shortcode).nil?
  136. end
  137. end
  138. def prepare_description
  139. self.description = description.strip[0...420] unless description.nil?
  140. end
  141. def set_type_and_extension
  142. self.type = VIDEO_MIME_TYPES.include?(file_content_type) ? :video : AUDIO_MIME_TYPES.include?(file_content_type) ? :audio : :image
  143. extension = AUDIO_MIME_TYPES.include?(file_content_type) ? '.mp4' : appropriate_extension
  144. basename = Paperclip::Interpolations.basename(file, :original)
  145. file.instance_write :file_name, [basename, extension].delete_if(&:blank?).join('.')
  146. end
  147. def set_meta
  148. meta = populate_meta
  149. return if meta == {}
  150. file.instance_write :meta, meta
  151. end
  152. def populate_meta
  153. meta = {}
  154. file.queued_for_write.each do |style, file|
  155. begin
  156. geo = Paperclip::Geometry.from_file file
  157. meta[style] = {
  158. width: geo.width.to_i,
  159. height: geo.height.to_i,
  160. size: "#{geo.width.to_i}x#{geo.height.to_i}",
  161. aspect: geo.width.to_f / geo.height.to_f,
  162. }
  163. rescue Paperclip::Errors::NotIdentifiedByImageMagickError
  164. meta[style] = {}
  165. end
  166. end
  167. meta
  168. end
  169. def appropriate_extension
  170. mime_type = MIME::Types[file.content_type]
  171. extensions_for_mime_type = mime_type.empty? ? [] : mime_type.first.extensions
  172. original_extension = Paperclip::Interpolations.extension(file, :original)
  173. extensions_for_mime_type.include?(original_extension) ? original_extension : extensions_for_mime_type.first
  174. end
  175. end