diff --git a/Gemfile b/Gemfile index b3ec1246e..e11c0099b 100644 --- a/Gemfile +++ b/Gemfile @@ -43,6 +43,7 @@ gem 'capistrano' gem 'capistrano-ext' gem 'radix62', '~> 1.0.1' gem 'streamio-ffmpeg' +gem 'rubyzip' # needed for looser jpeg header compat gem 'ruby-imagespec', :require => "image_spec", :git => "https://github.com/r888888888/ruby-imagespec.git", :branch => "exif-fixes" diff --git a/Gemfile.lock b/Gemfile.lock index d3fe392bb..a7c0bdcde 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -160,6 +160,7 @@ GEM ref (1.0.5) rmagick (2.13.3) ruby-prof (0.14.2) + rubyzip (1.1.6) safe_yaml (1.0.2) sanitize (2.1.0) nokogiri (>= 1.4.4) @@ -263,6 +264,7 @@ DEPENDENCIES rmagick ruby-imagespec! ruby-prof + rubyzip sanitize sass-rails (~> 4.0.0) shoulda diff --git a/app/logical/pixiv_ugoira_converter.rb b/app/logical/pixiv_ugoira_converter.rb new file mode 100644 index 000000000..4a8c12e96 --- /dev/null +++ b/app/logical/pixiv_ugoira_converter.rb @@ -0,0 +1,122 @@ +class PixivUgoiraConverter + attr_reader :agent, :url, :write_path + + def initialize(agent, url, write_path) + @agent = agent + @url = url + @write_path = write_path + end + + def process(format) + folder = unpack(fetch_zipped_body) + + if format == :gif + write_gif(folder) + elsif format == :webm + write_webm(folder) + elsif format == :apng + write_apng(folder) + end + end + + def write_gif(folder) + anim = Magick::ImageList.new + delay_sum = 0 + folder.each_with_index do |file, i| + image_blob = file.get_input_stream.read + image = Magick::Image.from_blob(image_blob).first + image.ticks_per_second = 1000 + delay = frame_data[i]["delay"] + rounded_delay = (delay_sum + delay).round(-1) - delay_sum.round(-1) + image.delay = rounded_delay + delay_sum += delay + anim << image + end + + anim = anim.optimize_layers(Magick::OptimizeTransLayer) + anim.write(write_path) + end + + def write_webm(folder) + Dir.mktmpdir do |tmpdir| + FileUtils.mkdir_p("#{tmpdir}/images") + folder.each_with_index do |file, i| + path = File.join(tmpdir, file.name) + image_blob = file.get_input_stream.read + File.open(path, "wb") do |f| + f.write(image_blob) + end + end + + 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"] + end + f.write("#{delay_sum}\n") + end + + ext = folder.first.name.match(/\.(.+)$/)[1] + system("ffmpeg -i #{tmpdir}/images/%06d.#{ext} -codec:v libvpx -crf 4 -b:v 5000k -an #{tmpdir}/tmp.webm") + system("mkvmerge -o #{tmpdir}/output.webm --timecodes 0:#{tmpdir}/timecodes.tc #{tmpdir}/tmp.webm") + + FileUtils.rm_rf(tmpdir) + end + end + + def write_apng(folder) + FileUtils.mkdir_p("pixiv_anim_#{pixiv_id}") + folder.each_with_index do |file, i| + frame_path = File.join("pixiv_anim_#{pixiv_id}", "frame#{"%03d" % i}.png") + delay_path = File.join("pixiv_anim_#{pixiv_id}", "frame#{"%03d" % i}.txt") + image_blob = file.get_input_stream.read + delay = frame_data[i]["delay"] + image = Magick::Image.from_blob(image_blob).first + image.format="PNG" + image.write(frame_path) + File.open(delay_path, "wb") do |f| + f.write("delay=#{delay}/1000") + end + end + system("apngasm pixiv_anim_#{pixiv_id}.png pixiv_anim_#{pixiv_id}/frame*.png") + FileUtils.rm_rf("pixiv_anim_#{pixiv_id}") + + puts "Animation successfully created as pixiv_anim_#{pixiv_id}.png" + end + + def unpack(zipped_body) + Zip::CentralDirectory.new.read_from_stream(StringIO.new(zipped_body)) + end + + def fetch_zipped_body + zip_uri, frame_data = fetch_frames + + zip_body = Net::HTTP.start(zip_uri.host) do |http| + http.get(zip_uri.path, {"Referer" => "http://pixiv.net"}).body + end + end + + def fetch_frames + agent.get(url) do |page| + # Get the zip url and frame delay by parsing javascript contained in a