uploads: move thumbnail generation code to MediaFile.

* Move image thumbnail generation code to MediaFile::Image.
* Move video thumbnail generation code to MediaFile::Video.
* Move ugoira->webm conversion code to MediaFile::Ugoira.

This separates thumbnail generation from the upload process so that it's
possible to generate thumbnails outside of uploads.
This commit is contained in:
evazion
2020-05-18 00:31:12 -05:00
parent 24c53172db
commit 45064853de
15 changed files with 269 additions and 423 deletions

View File

@@ -1,36 +0,0 @@
module DanbooruImageResizer
module_function
# Taken from ArgyllCMS 2.0.0 (see also: https://ninedegreesbelow.com/photography/srgb-profile-comparison.html)
SRGB_PROFILE = "#{Rails.root}/config/sRGB.icm"
# http://jcupitt.github.io/libvips/API/current/libvips-resample.html#vips-thumbnail
if Vips.at_least_libvips?(8, 8)
THUMBNAIL_OPTIONS = { size: :down, linear: false, no_rotate: true, export_profile: SRGB_PROFILE, import_profile: SRGB_PROFILE }
CROP_OPTIONS = { linear: false, no_rotate: true, export_profile: SRGB_PROFILE, import_profile: SRGB_PROFILE, crop: :attention }
else
THUMBNAIL_OPTIONS = { size: :down, linear: false, auto_rotate: false, export_profile: SRGB_PROFILE, import_profile: SRGB_PROFILE }
CROP_OPTIONS = { linear: false, auto_rotate: false, export_profile: SRGB_PROFILE, import_profile: SRGB_PROFILE, crop: :attention }
end
# http://jcupitt.github.io/libvips/API/current/VipsForeignSave.html#vips-jpegsave
JPEG_OPTIONS = { background: 255, strip: true, interlace: true, optimize_coding: true }
# https://github.com/jcupitt/libvips/wiki/HOWTO----Image-shrinking
# http://jcupitt.github.io/libvips/API/current/Using-vipsthumbnail.md.html
def resize(file, width, height, resize_quality = 90)
output_file = Tempfile.new
resized_image = Vips::Image.thumbnail(file.path, width, height: height, **THUMBNAIL_OPTIONS)
resized_image.jpegsave(output_file.path, Q: resize_quality, **JPEG_OPTIONS)
output_file
end
def crop(file, width, height, resize_quality = 90)
output_file = Tempfile.new
resized_image = Vips::Image.thumbnail(file.path, width, height: height, **CROP_OPTIONS)
resized_image.jpegsave(output_file.path, Q: resize_quality, **JPEG_OPTIONS)
output_file
end
end

View File

@@ -5,20 +5,20 @@ class MediaFile
# delegate all File methods to `file`. # delegate all File methods to `file`.
delegate *(File.instance_methods - MediaFile.instance_methods), to: :file delegate *(File.instance_methods - MediaFile.instance_methods), to: :file
def self.open(file) def self.open(file, **options)
file = Kernel.open(file, "r", binmode: true) unless file.respond_to?(:read) file = Kernel.open(file, "r", binmode: true) unless file.respond_to?(:read)
case file_ext(file) case file_ext(file)
when :jpg, :gif, :png when :jpg, :gif, :png
MediaFile::Image.new(file) MediaFile::Image.new(file, **options)
when :swf when :swf
MediaFile::Flash.new(file) MediaFile::Flash.new(file, **options)
when :webm, :mp4 when :webm, :mp4
MediaFile::Video.new(file) MediaFile::Video.new(file, **options)
when :zip when :zip
MediaFile::Ugoira.new(file) MediaFile::Ugoira.new(file, **options)
else else
MediaFile.new(file) MediaFile.new(file, **options)
end end
end end
@@ -45,7 +45,7 @@ class MediaFile
end end
end end
def initialize(file) def initialize(file, **options)
@file = file @file = file
end end
@@ -73,5 +73,29 @@ class MediaFile
file.size file.size
end end
def is_image?
file_ext.in?([:jpg, :png, :gif])
end
def is_video?
file_ext.in?([:webm, :mp4])
end
def is_ugoira?
file_ext == :zip
end
def is_flash?
file_ext == :swf
end
def preview(width, height, **options)
nil
end
def crop(width, height, **options)
nil
end
memoize :dimensions, :file_ext, :file_size, :md5 memoize :dimensions, :file_ext, :file_size, :md5
end end

View File

@@ -1,7 +1,7 @@
# Adapted from https://github.com/dim/ruby-imagespec/blob/f2f3ce8bb5b1b411f8658e66a891a095261d94c0/lib/image_spec/parser/swf.rb # Adapted from https://github.com/dim/ruby-imagespec/blob/f2f3ce8bb5b1b411f8658e66a891a095261d94c0/lib/image_spec/parser/swf.rb
# License: https://github.com/dim/ruby-imagespec/blob/master/LICENSE # License: https://github.com/dim/ruby-imagespec/blob/master/LICENSE
class MediaFile::Flash < MediaFile::Image class MediaFile::Flash < MediaFile
def dimensions def dimensions
# Read the entire stream into memory because the # Read the entire stream into memory because the
# dimensions aren't stored in a standard location # dimensions aren't stored in a standard location

View File

@@ -1,9 +1,46 @@
class MediaFile::Image < MediaFile class MediaFile::Image < MediaFile
def dimensions # Taken from ArgyllCMS 2.0.0 (see also: https://ninedegreesbelow.com/photography/srgb-profile-comparison.html)
image.size SRGB_PROFILE = "#{Rails.root}/config/sRGB.icm"
# http://jcupitt.github.io/libvips/API/current/VipsForeignSave.html#vips-jpegsave
JPEG_OPTIONS = { Q: 90, background: 255, strip: true, interlace: true, optimize_coding: true }
# http://jcupitt.github.io/libvips/API/current/libvips-resample.html#vips-thumbnail
if Vips.at_least_libvips?(8, 8)
THUMBNAIL_OPTIONS = { size: :down, linear: false, no_rotate: true, export_profile: SRGB_PROFILE, import_profile: SRGB_PROFILE }
CROP_OPTIONS = { crop: :attention, linear: false, no_rotate: true, export_profile: SRGB_PROFILE, import_profile: SRGB_PROFILE }
else
THUMBNAIL_OPTIONS = { size: :down, linear: false, auto_rotate: false, export_profile: SRGB_PROFILE, import_profile: SRGB_PROFILE }
CROP_OPTIONS = { crop: :attention, linear: false, auto_rotate: false, export_profile: SRGB_PROFILE, import_profile: SRGB_PROFILE }
end end
def dimensions
image.size
rescue Vips::Error
[0, 0]
end
# https://github.com/jcupitt/libvips/wiki/HOWTO----Image-shrinking
# 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)
MediaFile::Image.new(output_file)
end
def crop(width, height)
output_file = Tempfile.new(["image-crop", ".jpg"])
resized_image = image.thumbnail_image(width, height: height, **CROP_OPTIONS)
resized_image.jpegsave(output_file.path, **JPEG_OPTIONS)
MediaFile::Image.new(output_file)
end
private
def image def image
@image ||= Vips::Image.new_from_file(file.path) @image ||= Vips::Image.new_from_file(file.path, fail: true)
end end
end end

View File

@@ -1,13 +1,93 @@
class MediaFile::Ugoira < MediaFile class MediaFile::Ugoira < MediaFile
def dimensions extend Memoist
tempfile = Tempfile.new class Error < StandardError; end
folder = Zip::File.new(file.path) attr_reader :frame_data
folder.first.extract(tempfile.path) { true }
image_file = MediaFile.open(tempfile) def self.conversion_enabled?
image_file.dimensions system("ffmpeg -version > /dev/null") && system("mkvmerge --version > /dev/null")
ensure
image_file.close
tempfile.close!
end end
def initialize(file, frame_data: {}, **options)
super(file, **options)
@frame_data = frame_data
end
def close
file.close
zipfile.close
preview_frame.close
end
def dimensions
preview_frame.dimensions
end
def preview(width, height)
preview_frame.preview(width, height)
end
def crop(width, height)
preview_frame.crop(width, height)
end
# 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.conversion_enabled?
Dir.mktmpdir("ugoira-#{md5}") do |tmpdir|
output_file = Tempfile.new(["ugoira-conversion", ".webm"], binmode: true)
FileUtils.mkdir_p("#{tmpdir}/images")
zipfile.each do |entry|
path = File.join(tmpdir, "images", entry.name)
entry.extract(path)
end
# Duplicate last frame to avoid it being displayed only for a very short amount of time.
last_file_name = zipfile.entries.last.name
last_file_name =~ /\A(\d{6})(\.\w{,4})\Z/
new_last_index = $1.to_i + 1
file_ext = $2
new_last_filename = ("%06d" % new_last_index) + file_ext
path_from = File.join(tmpdir, "images", last_file_name)
path_to = File.join(tmpdir, "images", new_last_filename)
FileUtils.cp(path_from, path_to)
delay_sum = 0
timecodes_path = File.join(tmpdir, "timecodes.tc")
File.open(timecodes_path, "w+") do |f|
f.write("# timecode format v2\n")
frame_data.each do |img|
f.write("#{delay_sum}\n")
delay_sum += (img["delay"] || img["delay_msec"])
end
f.write("#{delay_sum}\n")
f.write("#{delay_sum}\n")
end
ext = zipfile.first.name.match(/\.(\w{,4})$/)[1]
ffmpeg_out, status = Open3.capture2e("ffmpeg -i #{tmpdir}/images/%06d.#{ext} -codec:v libvpx -crf 4 -b:v 5000k -an #{tmpdir}/tmp.webm")
raise Error, "ffmpeg failed: #{ffmpeg_out}" unless status.success?
mkvmerge_out, status = Open3.capture2e("mkvmerge -o #{output_file.path} --webm --timecodes 0:#{tmpdir}/timecodes.tc #{tmpdir}/tmp.webm")
raise Error, "mkvmerge failed: #{mkvmerge_out}" unless status.success?
MediaFile.open(output_file)
end
end
private
def zipfile
Zip::File.new(file.path)
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)
end
memoize :zipfile, :preview_frame
end end

View File

@@ -1,9 +1,29 @@
class MediaFile::Video < MediaFile class MediaFile::Video < MediaFile
extend Memoist
def dimensions def dimensions
[video.width, video.height] [video.width, video.height]
end end
def video def preview(max_width, max_height)
@video ||= FFMPEG::Movie.new(file.path) preview_frame.preview(max_width, max_height)
end end
def crop(max_width, max_height)
preview_frame.crop(max_width, max_height)
end
private
def video
FFMPEG::Movie.new(file.path)
end
def preview_frame
vp = Tempfile.new(["video-preview", ".jpg"], binmode: true)
video.screenshot(vp.path, seek_time: 0)
MediaFile.open(vp.path)
end
memoize :video, :preview_frame
end end

View File

@@ -1,90 +0,0 @@
class PixivUgoiraConverter
def self.enabled?
system("ffmpeg -version > /dev/null") && system("mkvmerge --version > /dev/null")
end
def self.generate_webm(ugoira_file, frame_data)
raise NotImplementedError, "can't convert ugoira to webm: ffmpeg or mkvmerge not installed" unless enabled?
folder = Zip::File.new(ugoira_file.path)
output_file = Tempfile.new(binmode: true)
write_path = output_file.path
Dir.mktmpdir do |tmpdir|
FileUtils.mkdir_p("#{tmpdir}/images")
folder.each_with_index do |file, i|
path = File.join(tmpdir, "images", file.name)
file.extract(path)
end
# Duplicate last frame to avoid it being displayed only for a very short amount of time.
last_file_name = folder.to_a.last.name
last_file_name =~ /\A(\d{6})(\.\w{,4})\Z/
new_last_index = $1.to_i + 1
file_ext = $2
new_last_filename = ("%06d" % new_last_index) + file_ext
path_from = File.join(tmpdir, "images", last_file_name)
path_to = File.join(tmpdir, "images", new_last_filename)
FileUtils.cp(path_from, path_to)
delay_sum = 0
timecodes_path = File.join(tmpdir, "timecodes.tc")
File.open(timecodes_path, "w+") do |f|
f.write("# timecode format v2\n")
frame_data.each do |img|
f.write("#{delay_sum}\n")
delay_sum += (img["delay"] || img["delay_msec"])
end
f.write("#{delay_sum}\n")
f.write("#{delay_sum}\n")
end
ext = folder.first.name.match(/\.(\w{,4})$/)[1]
ffmpeg_out, status = Open3.capture2e("ffmpeg -i #{tmpdir}/images/%06d.#{ext} -codec:v libvpx -crf 4 -b:v 5000k -an #{tmpdir}/tmp.webm")
if !status.success?
Rails.logger.error "[write_webm] ******************************"
Rails.logger.error "[write_webm] failed write_path=#{write_path}"
Rails.logger.error "[write_webm] ffmepg output:"
ffmpeg_out.split(/\n/).each do |line|
Rails.logger.error "[write_webm][ffmpeg] #{line}"
end
Rails.logger.error "[write_webm] ******************************"
end
mkvmerge_out, status = Open3.capture2e("mkvmerge -o #{write_path} --webm --timecodes 0:#{tmpdir}/timecodes.tc #{tmpdir}/tmp.webm")
if !status.success?
Rails.logger.error "[write_webm] ******************************"
Rails.logger.error "[write_webm] failed write_path=#{write_path}"
Rails.logger.error "[write_webm] mkvmerge output:"
mkvmerge_out.split(/\n/).each do |line|
Rails.logger.error "[write_webm][mkvmerge] #{line}"
end
Rails.logger.error "[write_webm] ******************************"
end
end
output_file
end
def self.generate_crop(ugoira_file)
file = Tempfile.new(["ugoira-crop", ".zip"], binmode: true)
zipfile = Zip::File.new(ugoira_file.path)
zipfile.entries.first.extract(file.path) { true } # 'true' means overwrite the existing tempfile.
DanbooruImageResizer.crop(file, Danbooru.config.small_image_width, Danbooru.config.small_image_width, 85)
ensure
file&.close!
end
def self.generate_preview(ugoira_file)
file = Tempfile.new(["ugoira-preview", ".zip"], binmode: true)
zipfile = Zip::File.new(ugoira_file.path)
zipfile.entries.first.extract(file.path) { true } # 'true' means overwrite the existing tempfile.
DanbooruImageResizer.resize(file, Danbooru.config.small_image_width, Danbooru.config.small_image_width, 85)
ensure
file.close!
end
end

View File

@@ -25,53 +25,25 @@ class UploadService
source =~ /^https?:\/\// source =~ /^https?:\/\//
end end
def generate_resizes(file, upload) def generate_resizes(media_file)
if upload.is_video? preview_file = media_file.preview(Danbooru.config.small_image_width, Danbooru.config.small_image_width)
video = FFMPEG::Movie.new(file.path) crop_file = media_file.crop(Danbooru.config.small_image_width, Danbooru.config.small_image_width)
crop_file = generate_video_crop_for(video, Danbooru.config.small_image_width)
preview_file = generate_video_preview_for(video, Danbooru.config.small_image_width, Danbooru.config.small_image_width)
elsif upload.is_ugoira? if media_file.is_ugoira?
preview_file = PixivUgoiraConverter.generate_preview(file) sample_file = media_file.convert
crop_file = PixivUgoiraConverter.generate_crop(file) elsif media_file.is_image? && media_file.width > Danbooru.config.large_image_width
sample_file = PixivUgoiraConverter.generate_webm(file, upload.context["ugoira"]["frame_data"]) sample_file = media_file.preview(Danbooru.config.large_image_width, nil)
else
elsif upload.is_image? sample_file = nil
preview_file = DanbooruImageResizer.resize(file, Danbooru.config.small_image_width, Danbooru.config.small_image_width, 85)
crop_file = DanbooruImageResizer.crop(file, Danbooru.config.small_image_width, Danbooru.config.small_image_width, 85)
if upload.image_width > Danbooru.config.large_image_width
sample_file = DanbooruImageResizer.resize(file, Danbooru.config.large_image_width, upload.image_height, 90)
end
end end
[preview_file, crop_file, sample_file] [preview_file, crop_file, sample_file]
end end
def generate_video_crop_for(video, width)
vp = Tempfile.new(["video-preview", ".jpg"], binmode: true)
video.screenshot(vp.path, :seek_time => 0, :resolution => "#{video.width}x#{video.height}")
crop = DanbooruImageResizer.crop(vp, width, width, 85)
vp.close
return crop
end
def generate_video_preview_for(video, width, height)
dimension_ratio = video.width.to_f / video.height
if dimension_ratio > 1
height = (width / dimension_ratio).to_i
else
width = (height * dimension_ratio).to_i
end
output_file = Tempfile.new(["video-preview", ".jpg"], binmode: true)
video.screenshot(output_file.path, :seek_time => 0, :resolution => "#{width}x#{height}")
output_file
end
def process_file(upload, file, original_post_id: nil) def process_file(upload, file, original_post_id: nil)
media_file = MediaFile.open(file)
upload.file = file upload.file = file
media_file = upload.media_file
upload.file_ext = media_file.file_ext.to_s upload.file_ext = media_file.file_ext.to_s
upload.file_size = media_file.file_size upload.file_size = media_file.file_size
upload.md5 = media_file.md5 upload.md5 = media_file.md5
@@ -81,7 +53,7 @@ class UploadService
upload.validate!(:file) upload.validate!(:file)
upload.tag_string = "#{upload.tag_string} #{Utils.automatic_tags(upload, file)}" upload.tag_string = "#{upload.tag_string} #{Utils.automatic_tags(upload, file)}"
preview_file, crop_file, sample_file = Utils.generate_resizes(file, upload) preview_file, crop_file, sample_file = Utils.generate_resizes(media_file)
begin begin
Utils.distribute_files(file, upload, :original, original_post_id: original_post_id) Utils.distribute_files(file, upload, :original, original_post_id: original_post_id)

View File

@@ -100,6 +100,10 @@ class Upload < ApplicationRecord
end end
module FileMethods module FileMethods
def media_file
@media_file ||= MediaFile.open(file, frame_data: context.to_h.dig("ugoira", "frame_data"))
end
def is_image? def is_image?
%w(jpg gif png).include?(file_ext) %w(jpg gif png).include?(file_ext)
end end

View File

@@ -17,7 +17,7 @@ FactoryBot.define do
factory(:ugoira_upload) do factory(:ugoira_upload) do
file do file do
f = Tempfile.new f = Tempfile.new
IO.copy_stream("#{Rails.root}/test/fixtures/ugoira.zip", f.path) IO.copy_stream("#{Rails.root}/test/files/ugoira.zip", f.path)
ActionDispatch::Http::UploadedFile.new(tempfile: f, filename: "ugoira.zip") ActionDispatch::Http::UploadedFile.new(tempfile: f, filename: "ugoira.zip")
end end
end end

7
test/files/ugoira.json Normal file
View File

@@ -0,0 +1,7 @@
[
{"file": "000000.jpg", "delay": 200},
{"file": "000001.jpg", "delay": 200},
{"file": "000002.jpg", "delay": 200},
{"file": "000003.jpg", "delay": 200},
{"file": "000004.jpg", "delay": 250}
]

View File

@@ -82,4 +82,47 @@ class MediaFileTest < ActiveSupport::TestCase
should "determine the correct filesize for a jpeg file" do should "determine the correct filesize for a jpeg file" do
assert_equal(28086, MediaFile.open("test/files/test.jpg").file_size) assert_equal(28086, MediaFile.open("test/files/test.jpg").file_size)
end end
context "#preview" do
should "generate a preview image" do
assert_equal([150, 101], MediaFile.open("test/files/test.jpg").preview(150, 150).dimensions)
assert_equal([113, 150], MediaFile.open("test/files/test.png").preview(150, 150).dimensions)
assert_equal([150, 150], MediaFile.open("test/files/test.gif").preview(150, 150).dimensions)
assert_equal([150, 150], MediaFile.open("test/files/test-512x512.webm").preview(150, 150).dimensions)
assert_equal([150, 150], MediaFile.open("test/files/test-300x300.mp4").preview(150, 150).dimensions)
end
should "be able to fit to width only" do
assert_equal([400, 268], MediaFile.open("test/files/test.jpg").preview(400, nil).dimensions)
end
end
context "#crop" do
should "generate a cropped preview image" do
assert_equal([150, 150], MediaFile.open("test/files/test.jpg").crop(150, 150).dimensions)
assert_equal([150, 150], MediaFile.open("test/files/test.png").crop(150, 150).dimensions)
assert_equal([150, 150], MediaFile.open("test/files/test.gif").crop(150, 150).dimensions)
assert_equal([150, 150], MediaFile.open("test/files/test-512x512.webm").crop(150, 150).dimensions)
assert_equal([150, 150], MediaFile.open("test/files/test-300x300.mp4").crop(150, 150).dimensions)
end
end
context "for a ugoira" do
setup do
skip unless MediaFile::Ugoira.conversion_enabled?
frame_data = JSON.parse(File.read("test/files/ugoira.json"))
@ugoira = MediaFile.open("test/files/ugoira.zip", frame_data: frame_data)
end
should "generate a preview" do
assert_equal([60, 60], @ugoira.preview(150, 150).dimensions)
assert_equal([150, 150], @ugoira.crop(150, 150).dimensions)
end
should "convert to a webm" do
webm = @ugoira.convert
assert_equal(:webm, webm.file_ext)
assert_equal([60, 60], webm.dimensions)
end
end
end end

View File

@@ -1,24 +0,0 @@
require "test_helper"
class PixivUgoiraConverterTest < ActiveSupport::TestCase
context "An ugoira converter" do
setup do
@zipfile = upload_file("test/fixtures/ugoira.zip").tempfile
@frame_data = [
{"file" => "000000.jpg", "delay" => 200},
{"file" => "000001.jpg", "delay" => 200},
{"file" => "000002.jpg", "delay" => 200},
{"file" => "000003.jpg", "delay" => 200},
{"file" => "000004.jpg", "delay" => 250}
]
end
should "output to webm" do
skip "ffmpeg is not installed" unless PixivUgoiraConverter.enabled?
sample_file = PixivUgoiraConverter.generate_webm(@zipfile, @frame_data)
preview_file = PixivUgoiraConverter.generate_preview(@zipfile)
assert_operator(sample_file.size, :>, 1_000)
assert_operator(preview_file.size, :>, 0)
end
end
end

View File

@@ -15,8 +15,6 @@ class UploadServiceTest < ActiveSupport::TestCase
} }
context "::Utils" do context "::Utils" do
subject { UploadService::Utils }
context "#get_file_for_upload" do context "#get_file_for_upload" do
context "for a non-source site" do context "for a non-source site" do
setup do setup do
@@ -26,10 +24,8 @@ class UploadServiceTest < ActiveSupport::TestCase
end end
should "work on a jpeg" do should "work on a jpeg" do
file = subject.get_file_for_upload(@upload) file = UploadService::Utils.get_file_for_upload(@upload)
assert_operator(File.size(file.path), :>, 0) assert_operator(File.size(file.path), :>, 0)
file.close file.close
end end
end end
@@ -43,13 +39,9 @@ class UploadServiceTest < ActiveSupport::TestCase
should "work on an ugoira url" do should "work on an ugoira url" do
begin begin
file = subject.get_file_for_upload(@upload) file = UploadService::Utils.get_file_for_upload(@upload)
assert_operator(File.size(file.path), :>, 0) assert_operator(File.size(file.path), :>, 0)
file.close file.close
rescue Net::OpenTimeout
skip "network problems"
end end
end end
end end
@@ -64,15 +56,12 @@ class UploadServiceTest < ActiveSupport::TestCase
end end
should "work on an ugoira url" do should "work on an ugoira url" do
skip unless PixivUgoiraConverter.enabled? file = UploadService::Utils.get_file_for_upload(@upload)
file = subject.get_file_for_upload(@upload)
assert_not_nil(@upload.context["ugoira"]) assert_not_nil(@upload.context["ugoira"])
assert_operator(File.size(file.path), :>, 0) assert_operator(File.size(file.path), :>, 0)
file.close file.close
rescue Net::OpenTimeout
skip "network failure"
end end
end end
end end
@@ -84,14 +73,14 @@ class UploadServiceTest < ActiveSupport::TestCase
context "with an original_post_id" do context "with an original_post_id" do
should "run" do should "run" do
subject.expects(:distribute_files).times(3) UploadService::Utils.expects(:distribute_files).times(3)
subject.process_file(@upload, @upload.file.tempfile, original_post_id: 12345) UploadService::Utils.process_file(@upload, @upload.file.tempfile, original_post_id: 12345)
end end
end end
should "run" do should "run" do
subject.expects(:distribute_files).times(3) UploadService::Utils.expects(:distribute_files).times(3)
subject.process_file(@upload, @upload.file.tempfile) UploadService::Utils.process_file(@upload, @upload.file.tempfile)
assert_equal("jpg", @upload.file_ext) assert_equal("jpg", @upload.file_ext)
assert_equal(28086, @upload.file_size) assert_equal(28086, @upload.file_size)
assert_equal("ecef68c44edb8a0d6a3070b5f8e8ee76", @upload.md5) assert_equal("ecef68c44edb8a0d6a3070b5f8e8ee76", @upload.md5)
@@ -99,184 +88,6 @@ class UploadServiceTest < ActiveSupport::TestCase
assert_equal(500, @upload.image_width) assert_equal(500, @upload.image_width)
end end
end end
context ".generate_resizes" do
context "for an ugoira" do
setup do
context = UGOIRA_CONTEXT
@file = upload_file("test/fixtures/ugoira.zip")
@upload = mock
@upload.stubs(:is_video?).returns(false)
@upload.stubs(:is_ugoira?).returns(true)
@upload.stubs(:context).returns(context)
end
should "generate a preview and a video" do
skip unless PixivUgoiraConverter.enabled?
preview, crop, sample = subject.generate_resizes(@file, @upload)
assert_operator(File.size(preview.path), :>, 0)
assert_operator(File.size(crop.path), :>, 0)
assert_operator(File.size(sample.path), :>, 0)
assert_equal(60, MediaFile.open(preview).width)
assert_equal(60, MediaFile.open(preview).height)
assert_equal(150, MediaFile.open(crop).width)
assert_equal(150, MediaFile.open(crop).height)
preview.close
preview.unlink
sample.close
sample.unlink
end
end
context "for a video" do
teardown do
@file.close
end
context "for an mp4" do
setup do
@file = upload_file("test/files/test-300x300.mp4")
@upload = mock
@upload.stubs(:is_video?).returns(true)
@upload.stubs(:is_ugoira?).returns(false)
end
should "generate a video" do
preview, crop, sample = subject.generate_resizes(@file, @upload)
assert_operator(File.size(preview.path), :>, 0)
assert_operator(File.size(crop.path), :>, 0)
assert_equal(150, MediaFile.open(preview).width)
assert_equal(150, MediaFile.open(preview).height)
assert_equal(150, MediaFile.open(crop).width)
assert_equal(150, MediaFile.open(crop).height)
preview.close
preview.unlink
crop.close
crop.unlink
end
end
context "for a webm" do
setup do
@file = upload_file("test/files/test-512x512.webm")
@upload = mock
@upload.stubs(:is_video?).returns(true)
@upload.stubs(:is_ugoira?).returns(false)
end
should "generate a video" do
preview, crop, sample = subject.generate_resizes(@file, @upload)
assert_operator(File.size(preview.path), :>, 0)
assert_operator(File.size(crop.path), :>, 0)
assert_equal(150, MediaFile.open(preview).width)
assert_equal(150, MediaFile.open(preview).height)
assert_equal(150, MediaFile.open(crop).width)
assert_equal(150, MediaFile.open(crop).height)
preview.close
preview.unlink
crop.close
crop.unlink
end
end
end
context "for an image" do
teardown do
@file.close
end
setup do
@upload = mock
@upload.stubs(:is_video?).returns(false)
@upload.stubs(:is_ugoira?).returns(false)
@upload.stubs(:is_image?).returns(true)
@upload.stubs(:image_width).returns(1200)
@upload.stubs(:image_height).returns(200)
end
context "for a jpeg" do
setup do
@file = upload_file("test/files/test.jpg")
end
should "generate a preview" do
preview, crop, sample = subject.generate_resizes(@file, @upload)
assert_operator(File.size(preview.path), :>, 0)
assert_operator(File.size(crop.path), :>, 0)
assert_operator(File.size(sample.path), :>, 0)
preview.close
preview.unlink
sample.close
sample.unlink
end
end
context "for a png" do
setup do
@file = upload_file("test/files/test.png")
end
should "generate a preview" do
preview, crop, sample = subject.generate_resizes(@file, @upload)
assert_operator(File.size(preview.path), :>, 0)
assert_operator(File.size(crop.path), :>, 0)
assert_operator(File.size(sample.path), :>, 0)
preview.close
preview.unlink
sample.close
sample.unlink
end
end
context "for a gif" do
setup do
@file = upload_file("test/files/test.png")
end
should "generate a preview" do
preview, crop, sample = subject.generate_resizes(@file, @upload)
assert_operator(File.size(preview.path), :>, 0)
assert_operator(File.size(crop.path), :>, 0)
assert_operator(File.size(sample.path), :>, 0)
preview.close
preview.unlink
sample.close
sample.unlink
end
end
end
end
context ".generate_video_preview_for" do
context "for an mp4" do
setup do
@path = "test/files/test-300x300.mp4"
@video = FFMPEG::Movie.new(@path)
end
should "generate a video" do
sample = subject.generate_video_preview_for(@video, 100, 100)
assert_operator(File.size(sample.path), :>, 0)
sample.close
sample.unlink
end
end
context "for a webm" do
setup do
@path = "test/files/test-512x512.webm"
@video = FFMPEG::Movie.new(@path)
end
should "generate a video" do
sample = subject.generate_video_preview_for(@video, 100, 100)
assert_operator(File.size(sample.path), :>, 0)
sample.close
sample.unlink
end
end
end
end end
context "::Preprocessor" do context "::Preprocessor" do
@@ -302,7 +113,7 @@ class UploadServiceTest < ActiveSupport::TestCase
end end
should "download the file" do should "download the file" do
@service = subject.new(source: @source, referer_url: @ref) @service = UploadService::Preprocessor.new(source: @source, referer_url: @ref)
@upload = @service.start! @upload = @service.start!
assert_equal("preprocessed", @upload.status) assert_equal("preprocessed", @upload.status)
assert_equal(9800, @upload.file_size) assert_equal(9800, @upload.file_size)
@@ -321,7 +132,7 @@ class UploadServiceTest < ActiveSupport::TestCase
should "download the file" do should "download the file" do
begin begin
@service = subject.new(source: @source, referer_url: @ref) @service = UploadService::Preprocessor.new(source: @source, referer_url: @ref)
@upload = @service.start! @upload = @service.start!
rescue Net::OpenTimeout rescue Net::OpenTimeout
skip "network failure" skip "network failure"
@@ -341,9 +152,9 @@ class UploadServiceTest < ActiveSupport::TestCase
end end
should "download the file" do should "download the file" do
skip unless PixivUgoiraConverter.enabled? skip unless MediaFile::Ugoira.conversion_enabled?
@service = subject.new(source: @source) @service = UploadService::Preprocessor.new(source: @source)
begin begin
@upload = @service.start! @upload = @service.start!
rescue Net::OpenTimeout rescue Net::OpenTimeout
@@ -364,7 +175,7 @@ class UploadServiceTest < ActiveSupport::TestCase
end end
should "download the file" do should "download the file" do
@service = subject.new(source: @source) @service = UploadService::Preprocessor.new(source: @source)
begin begin
@upload = @service.start! @upload = @service.start!
rescue Net::OpenTimeout rescue Net::OpenTimeout
@@ -386,7 +197,7 @@ class UploadServiceTest < ActiveSupport::TestCase
end end
should "work for a video" do should "work for a video" do
@service = subject.new(source: @source) @service = UploadService::Preprocessor.new(source: @source)
@upload = @service.start! @upload = @service.start!
assert_equal("preprocessed", @upload.status) assert_equal("preprocessed", @upload.status)
assert_not_nil(@upload.md5) assert_not_nil(@upload.md5)
@@ -405,7 +216,7 @@ class UploadServiceTest < ActiveSupport::TestCase
end end
should "leave the upload in an error state" do should "leave the upload in an error state" do
@service = subject.new(source: @source) @service = UploadService::Preprocessor.new(source: @source)
@upload = @service.start! @upload = @service.start!
assert_match(/error:/, @upload.status) assert_match(/error:/, @upload.status)
end end
@@ -413,7 +224,7 @@ class UploadServiceTest < ActiveSupport::TestCase
context "for an invalid content type" do context "for an invalid content type" do
should "fail" do should "fail" do
upload = subject.new(source: "http://www.example.com").start! upload = UploadService::Preprocessor.new(source: "http://www.example.com").start!
assert_match(/\Aerror:.*File ext is invalid/, upload.status) assert_match(/\Aerror:.*File ext is invalid/, upload.status)
end end
end end
@@ -429,7 +240,7 @@ class UploadServiceTest < ActiveSupport::TestCase
end end
should "overwrite the attributes" do should "overwrite the attributes" do
@service = subject.new(source: @source, rating: 'e') @service = UploadService::Preprocessor.new(source: @source, rating: 'e')
@upload = @service.start! @upload = @service.start!
@service.finish! @service.finish!
@upload.reload @upload.reload
@@ -737,7 +548,7 @@ class UploadServiceTest < ActiveSupport::TestCase
context "a post that is replaced by a ugoira" do context "a post that is replaced by a ugoira" do
should "save the frame data" do should "save the frame data" do
skip "ffmpeg not installed" unless PixivUgoiraConverter.enabled? skip unless MediaFile::Ugoira.conversion_enabled?
begin begin
as_user { @post.replace!(replacement_url: "http://www.pixiv.net/member_illust.php?mode=medium&illust_id=62247364") } as_user { @post.replace!(replacement_url: "http://www.pixiv.net/member_illust.php?mode=medium&illust_id=62247364") }
@post.reload @post.reload
@@ -751,8 +562,6 @@ class UploadServiceTest < ActiveSupport::TestCase
assert_equal("https://i.pximg.net/img-zip-ugoira/img/2017/04/04/08/57/38/62247364_ugoira1920x1080.zip", @post.source) assert_equal("https://i.pximg.net/img-zip-ugoira/img/2017/04/04/08/57/38/62247364_ugoira1920x1080.zip", @post.source)
assert_equal([{"delay" => 125, "file" => "000000.jpg"}, {"delay" => 125, "file" => "000001.jpg"}], @post.pixiv_ugoira_frame_data.data) assert_equal([{"delay" => 125, "file" => "000000.jpg"}, {"delay" => 125, "file" => "000001.jpg"}], @post.pixiv_ugoira_frame_data.data)
rescue Net::OpenTimeout
skip "Remote connection to Pixiv failed"
end end
end end
end end
@@ -760,7 +569,7 @@ class UploadServiceTest < ActiveSupport::TestCase
context "a post that is replaced to another file then replaced back to the original file" do context "a post that is replaced to another file then replaced back to the original file" do
should "not delete the original files" do should "not delete the original files" do
begin begin
skip unless PixivUgoiraConverter.enabled? skip unless MediaFile::Ugoira.conversion_enabled?
@post.unstub(:queue_delete_files) @post.unstub(:queue_delete_files)
# this is called thrice to delete the file for 62247364 # this is called thrice to delete the file for 62247364
@@ -802,7 +611,7 @@ class UploadServiceTest < ActiveSupport::TestCase
# swap the images between @post1 and @post2. # swap the images between @post1 and @post2.
begin begin
as_user do as_user do
skip unless PixivUgoiraConverter.enabled? skip unless MediaFile::Ugoira.conversion_enabled?
@post1.replace!(replacement_url: "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=62247350") @post1.replace!(replacement_url: "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=62247350")
@post2.replace!(replacement_url: "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=62247364") @post2.replace!(replacement_url: "https://www.pixiv.net/member_illust.php?mode=medium&illust_id=62247364")