Fix #3400: Smarter thumbnail generation for videos

This commit is contained in:
evazion
2021-09-05 05:43:59 -05:00
parent 52847e4ce9
commit ef28576673
8 changed files with 50 additions and 17 deletions

25
app/logical/ffmpeg.rb Normal file
View File

@@ -0,0 +1,25 @@
class FFmpeg
attr_reader :file
# Operate on a file with FFmpeg.
# @param file [File, String] a webm, mp4, gif, or apng file
def initialize(file)
@file = file.is_a?(String) ? File.open(file) : file
end
# Generate a .jpg preview image for a video or animation. Generates
# thumbnails intelligently by avoiding blank frames.
#
# @return [MediaFile] the preview image
def smart_video_preview
vp = Tempfile.new(["video-preview", ".jpg"], binmode: true)
# https://ffmpeg.org/ffmpeg.html#Main-options
# https://ffmpeg.org/ffmpeg-filters.html#thumbnail
ffmpeg_out, status = Open3.capture2e("ffmpeg -i #{file.path} -vf thumbnail=300 -frames:v 1 -y #{vp.path}")
raise "ffmpeg failed: #{ffmpeg_out}" if !status.success?
Rails.logger.debug(ffmpeg_out)
MediaFile.open(vp)
end
end

View File

@@ -38,11 +38,15 @@ class MediaFile::Image < MediaFile
# @see https://github.com/jcupitt/libvips/wiki/HOWTO----Image-shrinking
# @see http://jcupitt.github.io/libvips/API/current/Using-vipsthumbnail.md.html
def preview(width, height)
output_file = Tempfile.new(["image-preview", ".jpg"])
resized_image = image.thumbnail_image(width, height: height, **THUMBNAIL_OPTIONS)
resized_image.jpegsave(output_file.path, **JPEG_OPTIONS)
if is_animated?
FFmpeg.new(file).smart_video_preview
else
output_file = Tempfile.new(["image-preview", ".jpg"])
resized_image = image.thumbnail_image(width, height: height, **THUMBNAIL_OPTIONS)
resized_image.jpegsave(output_file.path, **JPEG_OPTIONS)
MediaFile::Image.new(output_file)
MediaFile::Image.new(output_file)
end
end
def crop(width, height)

View File

@@ -36,6 +36,7 @@ class MediaFile::Ugoira < MediaFile
# XXX should take width and height and resize image
def convert
raise NotImplementedError, "can't convert ugoira to webm: ffmpeg or mkvmerge not installed" unless self.class.videos_enabled?
raise RuntimeError, "can't convert ugoira to webm: no ugoira frame data was provided" unless frame_data.present?
Dir.mktmpdir("ugoira-#{md5}") do |tmpdir|
output_file = Tempfile.new(["ugoira-conversion", ".webm"], binmode: true)
@@ -87,10 +88,8 @@ class MediaFile::Ugoira < MediaFile
end
def preview_frame
tempfile = Tempfile.new("ugoira-preview", binmode: true)
zipfile.entries.first.extract(tempfile.path) { true } # 'true' means overwrite the existing tempfile.
MediaFile.open(tempfile)
FFmpeg.new(convert).smart_video_preview
end
memoize :zipfile, :preview_frame, :dimensions
memoize :zipfile, :preview_frame, :dimensions, :convert
end

View File

@@ -32,9 +32,7 @@ class MediaFile::Video < MediaFile
end
def preview_frame
vp = Tempfile.new(["video-preview", ".jpg"], binmode: true)
video.screenshot(vp.path, seek_time: 0)
MediaFile.open(vp.path)
FFmpeg.new(file).smart_video_preview
end
memoize :video, :preview_frame, :dimensions, :duration, :has_audio?