Skip to content

Instantly share code, notes, and snippets.

@ToksT ToksT/pixanim.rb Secret
Last active Aug 29, 2015

Embed
What would you like to do?
require 'zip' # install with "gem install rubyzip"
require 'mechanize'
require 'json'
# usage: ruby pixanim.rb ID FORMAT, where ID is the pixiv id number and FORMAT is gif, webm, or apng
# test posts:
# http://www.pixiv.net/member_illust.php?mode=medium&illust_id=44305721
# http://www.pixiv.net/member_illust.php?mode=medium&illust_id=44303110
# http://www.pixiv.net/member_illust.php?mode=medium&illust_id=44353714
# http://www.pixiv.net/member_illust.php?mode=medium&illust_id=44349996
# http://www.pixiv.net/member_illust.php?mode=medium&illust_id=44315772
# http://www.pixiv.net/member_illust.php?mode=medium&illust_id=44303325
# http://www.pixiv.net/member_illust.php?mode=medium&illust_id=44343783
if ARGV[0].nil?
raise "Must specify pixiv illustration id number"
end
pixiv_id = ARGV[0]
if ARGV[1].nil?
raise "Must specify output format"
end
format = ARGV[1].downcase
mech = Mechanize.new
source_url = "http://www.pixiv.net/member_illust.php?mode=medium&illust_id=#{pixiv_id}"
zip_url = ""
frame_data = []
mech.get(source_url) do |page|
# Get the zip url and frame delay by parsing javascript contained in a <script> tag on the page.
# Not an neat solution, but I haven't found any other location that has the frame delays listed.
scripts = page.search("body script").find_all do |node|
node.text =~ /_ugoira600x600\.zip/
end
if scripts.any?
javascript = scripts.first.text
json = javascript.match(/;pixiv\.context\.ugokuIllustData\s+=\s+(\{.+\});(?:$|pixiv\.context)/)[1]
data = JSON.parse(json)
zip_url = data["src"].sub("_ugoira600x600.zip", "_ugoira1920x1080.zip")
frame_data = data["frames"]
else
raise "Can't find javascript with frame data"
end
end
zip_uri = URI(zip_url)
zip_blob = ""
Net::HTTP.start(zip_uri.host) do |http|
resp = http.get(zip_uri.path, {"Referer" => "http://pixiv.net"})
zip_blob = resp.body
end
folder = Zip::CentralDirectory.new
folder.read_from_stream(StringIO.new(zip_blob))
case format
when "gif"
require 'rmagick'
anim = Magick::ImageList.new
delay_sum = 0
folder.each_with_index do |file, i|
# puts file.name
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("./pixiv_anim_#{pixiv_id}.gif")
puts "Animation successfully created as pixiv_anim_#{pixiv_id}.gif"
when "webm"
FileUtils.mkdir_p("pixiv_anim_#{pixiv_id}")
folder.each_with_index do |file, i|
path = File.join("pixiv_anim_#{pixiv_id}", file.name)
image_blob = file.get_input_stream.read
File.open(path, "wb") do |f|
f.write(image_blob)
end
end
# Duplicate last frame to avoid it being displayed only for a very short amount of time.
folder.to_a.last.name =~ /\A(\d{6})(\..+)\Z/
new_last_index = $1.to_i + 1
file_ext = $2
new_last_filename = ("%06d" % new_last_index) + file_ext
path_from = File.join("pixiv_anim_#{pixiv_id}", folder.to_a.last.name)
path_to = File.join("pixiv_anim_#{pixiv_id}", new_last_filename)
FileUtils.cp(path_from, path_to)
delay_sum = 0
File.open("pixiv_anim_#{pixiv_id}-timecodes.tc", "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")
f.write("#{delay_sum}\n")
end
ext = folder.first.name.match(/\.(.+)$/)[1]
system("ffmpeg -i pixiv_anim_#{pixiv_id}/%06d.#{ext} -codec:v libvpx -crf 4 -b:v 5000k -an pixiv_anim_#{pixiv_id}-tmp.webm")
system("mkvmerge -o pixiv_anim_#{pixiv_id}.webm --timecodes 0:pixiv_anim_#{pixiv_id}-timecodes.tc pixiv_anim_#{pixiv_id}-tmp.webm")
FileUtils.rm_rf("pixiv_anim_#{pixiv_id}")
File.delete("pixiv_anim_#{pixiv_id}-timecodes.tc")
File.delete("pixiv_anim_#{pixiv_id}-tmp.webm")
puts "Animation successfully created as pixiv_anim_#{pixiv_id}.webm"
when "apng"
require 'rmagick'
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"
else
raise "Invalid format"
end
@Type-kun

This comment has been minimized.

Copy link

commented Jun 29, 2014

A few notes if someone decides to run it standalone under windows, like I did:

  1. Requires rubyzip gem to work
  2. Installing rmagick under windows is tricky - see top answer at http://stackoverflow.com/questions/4451213/ruby-1-9-2-how-to-install-rmagick-on-windows , also you'll need to use imagemagick version 6.7.9-9 according to https://github.com/rmagick/rmagick/wiki . The steps and parameters past step 2 described in the wiki weren't necessary.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.