mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-10-08 07:40:08 +09:00
Bound the work of previewing an attachment
A video's preview and a picture's thumbnail are made inside the request that posts the message, and nothing bounded how long either could take. - The video preview filter also selects any frame from 5 seconds on. Rails' filter takes the second frame it selects, which a video with a single keyframe and no scene change only gives at its end, so ffmpeg decoded all of it. - TimeLimitedVideoPreviewer gives ffmpeg 10 seconds of wall-clock time, kills it past that, and reports a failed preview, so the message is posted without one. - Pictures and videos above 250 megapixels, or whose size couldn't be read, get no preview: decoding costs in proportion to the pixels, however small the file. - The view shows a preview only if it was made when the message was posted. Its URL used to make it on view, so a preview that failed or was skipped would be attempted again on every view. The cached presentation's version goes up, so cached messages pick this up. - A video's poster is made, when the message is posted, at the size the view shows it. The full-size WebP made until now wasn't shown anywhere, and encoding it costs in proportion to the frame's pixels. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bj8KnxpTf9sj2Ysa8aLAVa
This commit is contained in:
@@ -34,7 +34,68 @@ class Message::AttachmentTest < ActiveSupport::TestCase
|
||||
assert_not message.attachment.preview(format: :webp).image.attached?
|
||||
end
|
||||
|
||||
test "creating a message makes the video poster that the room shows" do
|
||||
message = create_attachment_message("alpha-centuri.mov", "video/quicktime")
|
||||
|
||||
assert_no_difference -> { ActiveStorage::VariantRecord.count } do
|
||||
message.reload.attachment.preview(:poster).processed
|
||||
end
|
||||
end
|
||||
|
||||
test "video previews are drawn by the previewer with a time limit, from a frame within the first 5 seconds" do
|
||||
assert_includes ActiveStorage.previewers, TimeLimitedVideoPreviewer
|
||||
assert_not_includes ActiveStorage.previewers, ActiveStorage::Previewer::VideoPreviewer
|
||||
assert_includes ActiveStorage.video_preview_arguments, "gte(t\\,5)"
|
||||
end
|
||||
|
||||
test "creating a message gives up on a video preview that takes longer than the time limit" do
|
||||
started = nil
|
||||
message = nil
|
||||
|
||||
with_ffmpeg_sleeping(5.seconds) do
|
||||
stub_const(TimeLimitedVideoPreviewer, :TIME_LIMIT, 0.2) do
|
||||
started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
||||
message = create_attachment_message("alpha-centuri.mov", "video/quicktime")
|
||||
end
|
||||
end
|
||||
|
||||
assert_operator Process.clock_gettime(Process::CLOCK_MONOTONIC) - started, :<, 2
|
||||
assert_not message.reload.attachment.preview(format: :webp).image.attached?
|
||||
end
|
||||
|
||||
test "creating a message makes no thumbnail of an image with more pixels than the limit" do
|
||||
moon = Vips::Image.new_from_file(file_fixture("moon.jpg").to_s)
|
||||
|
||||
stub_const(Message::Attachment, :THUMBNAIL_MAX_PIXELS, moon.width * moon.height) do
|
||||
assert create_attachment_message("moon.jpg", "image/jpeg").attachment.representation(:thumb).processed?
|
||||
end
|
||||
|
||||
stub_const(Message::Attachment, :THUMBNAIL_MAX_PIXELS, moon.width * moon.height - 1) do
|
||||
assert_not create_attachment_message("moon.jpg", "image/jpeg").attachment.representation(:thumb).processed?
|
||||
end
|
||||
end
|
||||
|
||||
test "creating a message makes no preview of a video with more pixels than the limit" do
|
||||
stub_const(Message::Attachment, :THUMBNAIL_MAX_PIXELS, 1) do
|
||||
message = create_attachment_message("alpha-centuri.mov", "video/quicktime")
|
||||
assert_not message.reload.attachment.preview(format: :webp).image.attached?
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def with_ffmpeg_sleeping(duration)
|
||||
Tempfile.create("ffmpeg") do |ffmpeg|
|
||||
ffmpeg.write "#!/bin/sh\n[ \"$1\" = \"-version\" ] && exit 0\nexec sleep #{duration.to_i}\n"
|
||||
ffmpeg.close
|
||||
File.chmod 0o755, ffmpeg.path
|
||||
|
||||
previous, ActiveStorage.paths[:ffmpeg] = ActiveStorage.paths[:ffmpeg], ffmpeg.path
|
||||
yield
|
||||
ensure
|
||||
ActiveStorage.paths[:ffmpeg] = previous
|
||||
end
|
||||
end
|
||||
|
||||
def create_attachment_message(file, content_type)
|
||||
rooms(:hq).messages.create_with_attachment! \
|
||||
creator: users(:david),
|
||||
|
||||
Reference in New Issue
Block a user