Add Danbooru::Archive library for handling .zip and .rar files.

Introduce a new Danbooru::Archive library. This is a wrapper around libarchive that lets us extract
.zip, .rar, .7z, and other archive formats. Replace the rubyzip library in MediaFile::Ugoira with
the new Danbooru::Archive library.

This is a step towards fixing #5340: Add support for extracting archive attachments from certain sources.

This adds a new dependency on libarchive. Downstream users should `apt-get install libarchive13` if
they're not using Docker.

https://github.com/chef/ffi-libarchive
https://github.com/libarchive/libarchive
https://www.rubydoc.info/gems/ffi-libarchive/0.4.2
https://github.com/libarchive/libarchive/wiki/Examples#a-complete-extractor
This commit is contained in:
evazion
2022-11-14 13:36:09 -06:00
parent f942768ce8
commit 0c1e9a1618
6 changed files with 361 additions and 27 deletions

View File

@@ -18,7 +18,6 @@ class MediaFile::Ugoira < MediaFile
def close
file.close
zipfile.close
preview_frame.close
end
@@ -52,24 +51,15 @@ class MediaFile::Ugoira < MediaFile
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_delays.present?
Dir.mktmpdir("ugoira-#{md5}") do |tmpdir|
Danbooru::Archive.extract!(file) do |tmpdir, filenames|
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)
last_file_name = File.basename(filenames.last)
last_index, file_ext = last_file_name.split(".")
new_last_filename = "#{"%06d" % (last_index.to_i + 1)}.#{file_ext}"
path_from = File.join(tmpdir, last_file_name)
path_to = File.join(tmpdir, new_last_filename)
FileUtils.cp(path_from, path_to)
delay_sum = 0
@@ -84,11 +74,10 @@ class MediaFile::Ugoira < MediaFile
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-vp9 -crf 12 -b:v 0 -an -threads 8 -tile-columns 2 -tile-rows 1 -row-mt 1 -pass 1 -passlogfile #{tmpdir}/ffmpeg2pass -f null /dev/null")
ffmpeg_out, status = Open3.capture2e("ffmpeg -i #{tmpdir}/%06d.#{file_ext} -codec:v libvpx-vp9 -crf 12 -b:v 0 -an -threads 8 -tile-columns 2 -tile-rows 1 -row-mt 1 -pass 1 -passlogfile #{tmpdir}/ffmpeg2pass -f null /dev/null")
raise Error, "ffmpeg failed: #{ffmpeg_out}" unless status.success?
ffmpeg_out, status = Open3.capture2e("ffmpeg -i #{tmpdir}/images/%06d.#{ext} -codec:v libvpx-vp9 -crf 12 -b:v 0 -an -threads 8 -tile-columns 2 -tile-rows 1 -row-mt 1 -pass 2 -passlogfile #{tmpdir}/ffmpeg2pass #{tmpdir}/tmp.webm")
ffmpeg_out, status = Open3.capture2e("ffmpeg -i #{tmpdir}/%06d.#{file_ext} -codec:v libvpx-vp9 -crf 12 -b:v 0 -an -threads 8 -tile-columns 2 -tile-rows 1 -row-mt 1 -pass 2 -passlogfile #{tmpdir}/ffmpeg2pass #{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")
@@ -100,13 +89,9 @@ class MediaFile::Ugoira < MediaFile
private
def zipfile
Zip::File.new(file.path)
end
def preview_frame
FFmpeg.new(convert).smart_video_preview
end
memoize :zipfile, :preview_frame, :dimensions, :convert, :metadata
memoize :preview_frame, :dimensions, :convert, :metadata
end