155 lines
4.0 KiB
Ruby
155 lines
4.0 KiB
Ruby
# typed: strict
|
|
class BlobEntriesController < ApplicationController
|
|
skip_before_action :authenticate_user!, only: [:show]
|
|
|
|
sig { void }
|
|
def show
|
|
thumb = params[:thumb]
|
|
raise("invalid thumb #{thumb}") if thumb.present? && !thumb_params(thumb)
|
|
|
|
expires_dur = 1.year
|
|
response.headers["Expires"] = expires_dur.from_now.httpdate
|
|
expires_in expires_dur, public: true
|
|
|
|
sha256 = params[:sha256]
|
|
etag = sha256
|
|
etag += "-#{thumb}" if thumb
|
|
return unless stale?(last_modified: Time.at(0), strong_etag: etag)
|
|
|
|
if show_blob_file(sha256, thumb)
|
|
return
|
|
elsif BlobFile.migrate_sha256!(sha256) && show_blob_file(sha256, thumb)
|
|
return
|
|
else
|
|
raise ActiveRecord::RecordNotFound
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
sig { params(sha256: String, thumb: T.nilable(String)).returns(T::Boolean) }
|
|
def show_blob_file(sha256, thumb)
|
|
if thumb
|
|
filename = "thumb-#{thumb}-#{sha256}"
|
|
filename = T.must(filename[..File.extname(filename).length])
|
|
filename += ".jpeg"
|
|
cache_key = "vips:thumbnail:#{sha256}:#{thumb}"
|
|
thumb_params = thumb_params(thumb)
|
|
if thumb_params.nil?
|
|
raise ActionController::BadRequest.new("invalid thumbnail: #{thumb}")
|
|
end
|
|
width, height = thumb_params
|
|
thumb_data =
|
|
Rack::MiniProfiler.step("vips: load from cache") do
|
|
Rails
|
|
.cache
|
|
.fetch(cache_key, expires_in: 1.day) do
|
|
blob_file = BlobFile.find_by(sha256: HexUtil.hex2bin(sha256))
|
|
if blob_file
|
|
content_type =
|
|
blob_file.content_type || "application/octet-stream"
|
|
if helpers.is_renderable_video_type?(content_type)
|
|
thumbnail_video_file(blob_file, width, height)
|
|
elsif helpers.is_renderable_image_type?(content_type)
|
|
thumbnail_image_file(blob_file, width, height)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if !thumb_data
|
|
Rails.cache.delete(cache_key)
|
|
return false
|
|
end
|
|
|
|
send_data(
|
|
thumb_data,
|
|
type: "image/jpg",
|
|
disposition: "inline",
|
|
filename: filename,
|
|
)
|
|
else
|
|
blob_file = BlobFile.find_by(sha256: HexUtil.hex2bin(sha256))
|
|
return false if !blob_file
|
|
|
|
content_type = blob_file.content_type || "application/octet-stream"
|
|
send_file(
|
|
blob_file.absolute_file_path,
|
|
type: content_type,
|
|
disposition: "inline",
|
|
)
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
sig do
|
|
params(blob_file: BlobFile, width: Integer, height: Integer).returns(
|
|
T.nilable(String),
|
|
)
|
|
end
|
|
def thumbnail_video_file(blob_file, width, height)
|
|
video_file = blob_file.absolute_file_path
|
|
temp_thumb_file = Tempfile.new(%w[video-thumb .png])
|
|
process_result =
|
|
system(
|
|
"ffmpegthumbnailer",
|
|
"-f", # overlay video strip indicator
|
|
"-i",
|
|
video_file,
|
|
"-o",
|
|
T.must(temp_thumb_file.path),
|
|
"-s",
|
|
"#{width}",
|
|
"-c",
|
|
"jpeg",
|
|
)
|
|
if !process_result
|
|
temp_thumb_file.unlink
|
|
return nil
|
|
end
|
|
|
|
thumb_data_tmp = File.read(T.must(temp_thumb_file.path), mode: "rb")
|
|
temp_thumb_file.unlink
|
|
thumb_data_tmp
|
|
end
|
|
|
|
sig do
|
|
params(blob_file: BlobFile, width: Integer, height: Integer).returns(
|
|
T.nilable(String),
|
|
)
|
|
end
|
|
def thumbnail_image_file(blob_file, width, height)
|
|
image_buffer =
|
|
Rack::MiniProfiler.step("vips: load image") do
|
|
T.unsafe(Vips::Image).thumbnail(
|
|
blob_file.absolute_file_path,
|
|
width,
|
|
height: height,
|
|
)
|
|
end
|
|
|
|
Rack::MiniProfiler.step("vips: thumbnail image") do
|
|
image_buffer.jpegsave_buffer
|
|
end
|
|
end
|
|
|
|
sig { params(thumb: String).returns(T.nilable([Integer, Integer])) }
|
|
def thumb_params(thumb)
|
|
case thumb
|
|
when "32-avatar"
|
|
[32, 32]
|
|
when "64-avatar"
|
|
[64, 64]
|
|
when "tiny"
|
|
[100, 100]
|
|
when "small"
|
|
[400, 300]
|
|
when "medium"
|
|
[800, 600]
|
|
when "content-container"
|
|
[2048, 2048]
|
|
end
|
|
end
|
|
end
|