mirror of
https://github.com/basecamp/once-campfire.git
synced 2026-10-08 07:40:08 +09:00
9912e63d69
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
28 lines
960 B
Ruby
28 lines
960 B
Ruby
# 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
|