# frozen_string_literal: true class E621ThumbnailJob < ApplicationJob queue_as :default def perform(entry) infile = Tempfile.new(%W[e621-thumbnail-#{entry.stripped_md5} .webm]) outfile = Tempfile.new(%W[e621-thumbnail-#{entry.stripped_md5} .#{entry.filetype}]) cutfile = Tempfile.new(%W[e621-thumbnail-#{entry.stripped_md5} .cut.webm]) palettefile = Tempfile.new(%W[e621-thumbnail-#{entry.stripped_md5} .palette.png]) Timeout.timeout(120) do infile.binmode io = FileDownload.new("https://static1.e621.net/data/#{entry.stripped_md5[0..1]}/#{entry.stripped_md5[2..3]}/#{entry.stripped_md5}.webm").download! IO.copy_stream(io, infile) duration = `ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 #{infile.path}`.to_f offset = duration > 10 ? rand(0..(duration - 10)) : duration / 2 if entry.filetype == "gif" `ffmpeg -y -i #{infile.path} -ss #{offset} -t 3 -c:v copy -an #{cutfile.path} 1>&2` `ffmpeg -y -i #{cutfile.path} -filter_complex "[0:v] palettegen" #{palettefile.path} 1>&2` `ffmpeg -y -i #{cutfile.path} -i #{palettefile.path} -filter_complex "[0:v][1:v] paletteuse" #{outfile.path} 1>&2` else `ffmpeg -y -i #{infile.path} -ss #{offset} -vframes 1 #{outfile.path} 1>&2` end ImageOptim.new.optimize_image!(outfile.path) Websites.config.e621_thumbnails_storage.upload("/#{entry.stripped_md5}.#{entry.filetype}", outfile) entry.update!(status: "complete") execute_webhook(entry, title: "Thumbnail Generated (#{entry.filetype})") end rescue Timeout::Error entry.update!(status: "timeout") execute_webhook(entry, title: "Thumbnail Generation Timed Out (#{entry.filetype})", error: true) rescue StandardError => e code = Requests::Pastebin.default.create(title: "E621 Thumbnail Generation Error (#{entry.stripped_md5})", content: "#{e}\n#{e.backtrace&.join("\n") || ''}") entry.update!(status: "error", error_code: code) execute_webhook(entry, title: "Thumbnail Generation Errored (#{entry.filetype})", body: "Backtrace: https://passtebin.com/#{code}", error: true) raise(e) ensure [infile, outfile, palettefile, cutfile].each do |file| file&.close file&.unlink end end def execute_webhook(entry, title:, body: "", error: false) if error entry.update(expires_at: 5.minutes.from_now) E621ThumbnailErrorCleanupJob.set(wait: 5.minutes).perform_later(entry) end thumb = {} if entry.complete? thumb = { thumbnail: { url: entry.url, }, } end Websites.config.e621_thumbnails_webhook.execute({ embeds: [ { title: title, description: <<~DESC, Key: ##{entry.api_key_id} (`#{entry.api_key.application_name}`) Post: [##{entry.post_id}](https://e621.net/posts/#{entry.post_id}) #{body} DESC color: error ? 0xDC143C : 0x008000, timestamp: Time.now.iso8601, **thumb, }, ], }) end end