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:
@@ -0,0 +1,27 @@
|
||||
# Rails waits for ffmpeg however long it takes, and a video's preview is drawn inside the request that posts it.
|
||||
# This previewer gives ffmpeg a wall-clock limit, kills it past that, and reports a failed preview, so the message
|
||||
# is posted without one.
|
||||
class TimeLimitedVideoPreviewer < ActiveStorage::Previewer::VideoPreviewer
|
||||
TIME_LIMIT = 10 # seconds
|
||||
|
||||
private
|
||||
def capture(*argv, to:)
|
||||
to.binmode
|
||||
|
||||
open_tempfile do |err|
|
||||
IO.popen(argv, in: IO::NULL, err: err) do |out|
|
||||
Timeout.timeout(TIME_LIMIT) { IO.copy_stream(out, to) }
|
||||
rescue Timeout::Error
|
||||
Process.kill :KILL, out.pid
|
||||
raise ActiveStorage::PreviewError, "#{argv.first} took longer than #{TIME_LIMIT} seconds"
|
||||
end
|
||||
err.rewind
|
||||
|
||||
unless $?.success?
|
||||
raise ActiveStorage::PreviewError, "#{argv.first} failed (status #{$?.exitstatus}): #{err.read.to_s.chomp}"
|
||||
end
|
||||
end
|
||||
|
||||
to.rewind
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user