From 3bb06c2be4d6b0d955595f56038558d0b8af865e Mon Sep 17 00:00:00 2001 From: r888888888 Date: Thu, 9 Oct 2014 17:05:47 -0700 Subject: [PATCH] integrate ugoiras into zip+webm+preview --- .../stylesheets/specific/comments.css.scss | 2 +- .../stylesheets/specific/posts.css.scss | 2 +- app/logical/downloads/file.rb | 57 +- .../downloads/rewrite_strategies/base.rb | 4 +- .../rewrite_strategies/deviant_art.rb | 4 +- .../downloads/rewrite_strategies/moebooru.rb | 4 +- .../rewrite_strategies/nico_seiga.rb | 4 +- .../downloads/rewrite_strategies/pixiv.rb | 13 +- .../downloads/rewrite_strategies/tumblr.rb | 4 +- .../downloads/rewrite_strategies/twitpic.rb | 4 +- app/logical/pixiv_ugoira_converter.rb | 109 +- app/logical/pixiv_ugoira_service.rb | 21 + app/logical/pixiv_web_agent.rb | 24 + app/logical/sources/site.rb | 2 +- app/logical/sources/strategies/pixiv.rb | 54 +- app/models/pixiv_ugoira_frame_data.rb | 4 + app/models/post.rb | 13 +- app/models/upload.rb | 61 +- ...09231234_create_pixiv_ugoira_frame_data.rb | 11 + db/structure.sql | 56 + test/fixtures/ugoira.zip | Bin 0 -> 6663 bytes .../vcr_cassettes/ugoira-converter.yml | 1834 +++++++++-------- test/unit/downloads/file_test.rb | 3 +- test/unit/downloads/pixiv_test.rb | 21 + test/unit/pixiv_ugoira_converter_test.rb | 38 +- test/unit/sources/pixiv_test.rb | 22 + test/unit/upload_test.rb | 33 +- vendor/assets/javascripts/ugoira_player.js | 521 +++++ 28 files changed, 1800 insertions(+), 1125 deletions(-) create mode 100644 app/logical/pixiv_ugoira_service.rb create mode 100644 app/logical/pixiv_web_agent.rb create mode 100644 app/models/pixiv_ugoira_frame_data.rb create mode 100644 db/migrate/20141009231234_create_pixiv_ugoira_frame_data.rb create mode 100644 test/fixtures/ugoira.zip create mode 100644 vendor/assets/javascripts/ugoira_player.js diff --git a/app/assets/stylesheets/specific/comments.css.scss b/app/assets/stylesheets/specific/comments.css.scss index 0b4347e85..c90fdda17 100644 --- a/app/assets/stylesheets/specific/comments.css.scss +++ b/app/assets/stylesheets/specific/comments.css.scss @@ -98,7 +98,7 @@ div#c-comments { } div.post-preview { - &[data-tags~=animated], &[data-file-ext=swf], &[data-file-ext=webm] { + &[data-tags~=animated], &[data-file-ext=swf], &[data-file-ext=webm], &[data-file-ext=zip] { div.preview { position: relative; diff --git a/app/assets/stylesheets/specific/posts.css.scss b/app/assets/stylesheets/specific/posts.css.scss index cf7843a46..25eb1853e 100644 --- a/app/assets/stylesheets/specific/posts.css.scss +++ b/app/assets/stylesheets/specific/posts.css.scss @@ -22,7 +22,7 @@ article.post-preview { margin: auto; } - &[data-tags~=animated]:before, &[data-file-ext=swf]:before, &[data-file-ext=webm]:before { + &[data-tags~=animated]:before, &[data-file-ext=swf]:before, &[data-file-ext=webm]:before, &[data-file-ext=zip]:before { content: "►"; position: absolute; width: 20px; diff --git a/app/logical/downloads/file.rb b/app/logical/downloads/file.rb index 566e5bf0e..807e2df70 100644 --- a/app/logical/downloads/file.rb +++ b/app/logical/downloads/file.rb @@ -2,45 +2,49 @@ module Downloads class File class Error < Exception ; end - attr_reader :tries + attr_reader :data attr_accessor :source, :content_type, :file_path - def initialize(source, file_path) + def initialize(source, file_path, options = {}) + # source can potentially get rewritten in the course + # of downloading a file, so check it again @source = source + + # where to save the download @file_path = file_path - @tries = 0 + + # we sometimes need to capture data from the source page + @data = {:is_ugoira => options[:is_ugoira]} end def download! - http_get_streaming do |response| + @source, @data = http_get_streaming(@source, @data) do |response| self.content_type = response["Content-Type"] - ::File.open(file_path, "wb") do |out| + ::File.open(@file_path, "wb") do |out| response.read_body(out) end end - after_download + @source = after_download(@source) end - def before_download(url, headers) + def before_download(url, headers, datums) RewriteStrategies::Base.strategies.each do |strategy| - url, headers = strategy.new.rewrite(url, headers) + url, headers, datums = strategy.new.rewrite(url, headers, datums) end - return [url, headers] + return [url, headers, datums] end - def after_download - fix_image_board_sources + def after_download(src) + fix_image_board_sources(src) end - def url - URI.parse(source) - end - - def http_get_streaming(options = {}) + def http_get_streaming(src, datums = {}, options = {}) max_size = options[:max_size] || Danbooru.config.max_file_size max_size = nil if max_size == 0 # unlimited limit = 4 + tries = 0 + url = URI.parse(src) while true unless url.is_a?(URI::HTTP) || url.is_a?(URI::HTTPS) @@ -50,7 +54,8 @@ module Downloads headers = { "User-Agent" => "#{Danbooru.config.safe_app_name}/#{Danbooru.config.version}" } - @source, headers = before_download(source, headers) + src, headers, datums = before_download(src, headers, datums) + url = URI.parse(src) begin Net::HTTP.start(url.host, url.port, :use_ssl => url.is_a?(URI::HTTPS)) do |http| @@ -63,13 +68,13 @@ module Downloads raise Error.new("File is too large (#{len} bytes)") if len && len.to_i > max_size end yield(res) - return + return [src, datums] when Net::HTTPRedirection then if limit == 0 then raise Error.new("Too many redirects") end - @source = res["location"] + src = res["location"] limit -= 1 else @@ -78,19 +83,23 @@ module Downloads end # http.request_get end # http.start rescue Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EIO, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, IOError => x - @tries += 1 - if @tries < 3 + tries += 1 + if tries < 3 retry else raise end end end # while + + [src, datums] end # def - def fix_image_board_sources - if source =~ /i\.4cdn\.org|\/src\/\d{12,}|urnc\.yi\.org|yui\.cynthia\.bne\.jp/ - @source = "Image board" + def fix_image_board_sources(src) + if src =~ /i\.4cdn\.org|\/src\/\d{12,}|urnc\.yi\.org|yui\.cynthia\.bne\.jp/ + "Image board" + else + src end end end diff --git a/app/logical/downloads/rewrite_strategies/base.rb b/app/logical/downloads/rewrite_strategies/base.rb index 1b840854d..feffca3ab 100644 --- a/app/logical/downloads/rewrite_strategies/base.rb +++ b/app/logical/downloads/rewrite_strategies/base.rb @@ -5,8 +5,8 @@ module Downloads [Pixiv, NicoSeiga, Twitpic, DeviantArt, Tumblr, Moebooru] end - def rewrite(url, headers) - return [url, headers] + def rewrite(url, headers, data = {}) + return [url, headers, data] end protected diff --git a/app/logical/downloads/rewrite_strategies/deviant_art.rb b/app/logical/downloads/rewrite_strategies/deviant_art.rb index db21cbf5a..e8d7ae24f 100644 --- a/app/logical/downloads/rewrite_strategies/deviant_art.rb +++ b/app/logical/downloads/rewrite_strategies/deviant_art.rb @@ -1,13 +1,13 @@ module Downloads module RewriteStrategies class DeviantArt < Base - def rewrite(url, headers) + def rewrite(url, headers, data = {}) if url =~ /https?:\/\/(?:.+?\.)?deviantart\.(?:com|net)/ url, headers = rewrite_html_pages(url, headers) url, headers = rewrite_thumbnails(url, headers) end - return [url, headers] + return [url, headers, data] end protected diff --git a/app/logical/downloads/rewrite_strategies/moebooru.rb b/app/logical/downloads/rewrite_strategies/moebooru.rb index 2901916f3..6338f1dee 100644 --- a/app/logical/downloads/rewrite_strategies/moebooru.rb +++ b/app/logical/downloads/rewrite_strategies/moebooru.rb @@ -3,12 +3,12 @@ module Downloads class Moebooru < Base DOMAINS = '(?:[^.]+\.)?yande\.re|konachan\.com' - def rewrite(url, headers) + def rewrite(url, headers, data = {}) if url =~ %r{https?://(?:#{DOMAINS})} url, headers = rewrite_jpeg_versions(url, headers) end - return [url, headers] + return [url, headers, data] end protected diff --git a/app/logical/downloads/rewrite_strategies/nico_seiga.rb b/app/logical/downloads/rewrite_strategies/nico_seiga.rb index f226a51ff..67795830a 100644 --- a/app/logical/downloads/rewrite_strategies/nico_seiga.rb +++ b/app/logical/downloads/rewrite_strategies/nico_seiga.rb @@ -1,14 +1,14 @@ module Downloads module RewriteStrategies class NicoSeiga < Base - def rewrite(url, headers) + def rewrite(url, headers, data = {}) if url =~ %r{https?://lohas\.nicoseiga\.jp} || url =~ %r{https?://seiga\.nicovideo\.jp} url, headers = rewrite_headers(url, headers) url, headers = rewrite_html_pages(url, headers) url, headers = rewrite_thumbnails(url, headers) end - return [url, headers] + return [url, headers, data] end protected diff --git a/app/logical/downloads/rewrite_strategies/pixiv.rb b/app/logical/downloads/rewrite_strategies/pixiv.rb index 316f0c503..71c7336de 100644 --- a/app/logical/downloads/rewrite_strategies/pixiv.rb +++ b/app/logical/downloads/rewrite_strategies/pixiv.rb @@ -1,16 +1,16 @@ module Downloads module RewriteStrategies class Pixiv < Base - def rewrite(url, headers) + def rewrite(url, headers, data = {}) if url =~ /https?:\/\/(?:\w+\.)?pixiv\.net/ url, headers = rewrite_headers(url, headers) url, headers = rewrite_cdn(url, headers) - url, headers = rewrite_html_pages(url, headers) + url, headers, data = rewrite_html_pages(url, headers, data) url, headers = rewrite_thumbnails(url, headers) url, headers = rewrite_old_small_manga_pages(url, headers) end - return [url, headers] + return [url, headers, data] end protected @@ -31,9 +31,12 @@ module Downloads if url =~ /illust_id=\d+/i || url =~ %r!pixiv\.net/img-inf/img/!i source = ::Sources::Strategies::Pixiv.new(url) source.get - return [source.image_url, headers] + data[:ugoira_frame_data] = source.ugoira_frame_data + data[:ugoira_width] = source.ugoira_width + data[:ugoira_height] = source.ugoira_height + return [source.file_url, headers, data] else - return [url, headers] + return [url, headers, data] end end diff --git a/app/logical/downloads/rewrite_strategies/tumblr.rb b/app/logical/downloads/rewrite_strategies/tumblr.rb index 2d9dc1434..c38216d05 100644 --- a/app/logical/downloads/rewrite_strategies/tumblr.rb +++ b/app/logical/downloads/rewrite_strategies/tumblr.rb @@ -1,13 +1,13 @@ module Downloads module RewriteStrategies class Tumblr < Base - def rewrite(url, headers) + def rewrite(url, headers, data = {}) if url =~ %r{^https?://.*tumblr\.com} url, headers = rewrite_cdn(url, headers) url, headers = rewrite_thumbnails(url, headers) end - return [url, headers] + return [url, headers, data] end protected diff --git a/app/logical/downloads/rewrite_strategies/twitpic.rb b/app/logical/downloads/rewrite_strategies/twitpic.rb index 47f95fda7..2a1c97422 100644 --- a/app/logical/downloads/rewrite_strategies/twitpic.rb +++ b/app/logical/downloads/rewrite_strategies/twitpic.rb @@ -1,13 +1,13 @@ module Downloads module RewriteStrategies class Twitpic < Base - def rewrite(url, headers) + def rewrite(url, headers, data = {}) if url =~ %r{https?://twitpic\.com} || url =~ %r{^https?://d3j5vwomefv46c\.cloudfront\.net} url, headers = rewrite_html_pages(url, headers) url, headers = rewrite_thumbnails(url, headers) end - return [url, headers] + return [url, headers, data] end protected diff --git a/app/logical/pixiv_ugoira_converter.rb b/app/logical/pixiv_ugoira_converter.rb index 8579cb310..77170451b 100644 --- a/app/logical/pixiv_ugoira_converter.rb +++ b/app/logical/pixiv_ugoira_converter.rb @@ -1,43 +1,11 @@ class PixivUgoiraConverter - attr_reader :agent, :url, :write_path, :format - - def initialize(url, write_path, format) - @url = url - @write_path = write_path - @format = format + def convert(source_path, output_path, preview_path, frame_data) + folder = unpack(File.open(source_path)) + write_webm(folder, output_path, frame_data) + write_preview(folder, preview_path) end - def process! - 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("gif:" + write_path) - end - - def write_webm(folder) + def write_webm(folder, write_path, frame_data) Dir.mktmpdir do |tmpdir| FileUtils.mkdir_p("#{tmpdir}/images") folder.each_with_index do |file, i| @@ -62,7 +30,7 @@ class PixivUgoiraConverter 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| + frame_data.each do |img| f.write("#{delay_sum}\n") delay_sum += img["delay"] end @@ -71,68 +39,21 @@ class PixivUgoiraConverter end ext = folder.first.name.match(/\.(\w{,4})$/)[1] - system("ffmpeg -i #{tmpdir}/images/%06d.#{ext} -codec:v libvpx -crf 4 -b:v 5000k -an #{tmpdir}/tmp.webm") - system("mkvmerge -o #{write_path} --webm --timecodes 0:#{tmpdir}/timecodes.tc #{tmpdir}/tmp.webm") + system("ffmpeg -loglevel quiet -i #{tmpdir}/images/%06d.#{ext} -codec:v libvpx -crf 4 -b:v 5000k -an #{tmpdir}/tmp.webm") + system("mkvmerge -q -o #{write_path} --webm --timecodes 0:#{tmpdir}/timecodes.tc #{tmpdir}/tmp.webm") end end - def write_apng(folder) - Dir.mktmpdir do |tmpdir| - folder.each_with_index do |file, i| - frame_path = File.join(tmpdir, "frame#{"%03d" % i}.png") - delay_path = File.join(tmpdir, "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 -o -F #{write_path} #{tmpdir}/frame*.png") - end + def write_preview(folder, path) + file = folder.first + image_blob = file.get_input_stream.read + image = Magick::Image.from_blob(image_blob).first + image.write(path) end - def unpack(zipped_body) + def unpack(zip_file) folder = Zip::CentralDirectory.new - folder.read_from_stream(StringIO.new(zipped_body)) + folder.read_from_stream(zip_file) folder end - - def fetch_zipped_body - zip_body = nil - zip_url, @frame_data = fetch_frames - - Downloads::File.new(zip_url, nil).http_get_streaming do |response| - zip_body = response.body - end - - zip_body - end - - def agent - @agent ||= Sources::Strategies::Pixiv.new(url).agent - end - - def fetch_frames - agent.get(url) do |page| - # Get the zip url and frame delay by parsing javascript contained in a