Skip to content

Instantly share code, notes, and snippets.

@ToksT

ToksT/pixanim.rb Secret

Last active August 29, 2015 14:03
  • Star 3 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save ToksT/e778c6080feb9725368c to your computer and use it in GitHub Desktop.
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
Copy link

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