davideisinger.com

My personal website
Log | Files | Refs | README

dither.rb (2301B)


      1 require "sinatra"
      2 require "mini_magick"
      3 require "digest"
      4 require "fileutils"
      5 require "tempfile"
      6 
      7 MiniMagick.logger.level = Logger::DEBUG
      8 
      9 ROOT = ENV.fetch("ROOT")
     10 KEY = ENV.fetch("KEY")
     11 DITHER = ENV["DITHER"] != "0"
     12 FORMAT = "png"
     13 CACHE_DIR = ENV["DITHER_CACHE_DIR"]
     14 
     15 def cache_path_for(path, geometry)
     16   return unless CACHE_DIR
     17 
     18   source_path = "#{ROOT}/#{path}.enc"
     19   cache_key = Digest::SHA256.hexdigest([
     20     path,
     21     Digest::SHA256.file(source_path).hexdigest,
     22     geometry,
     23     DITHER,
     24     FORMAT,
     25     1
     26   ].join("\0"))
     27 
     28   FileUtils.mkdir_p(CACHE_DIR)
     29   File.join(CACHE_DIR, "#{cache_key}.#{FORMAT}")
     30 end
     31 
     32 get "/*" do |path|
     33   halt 404, {"Content-Type" => "text/plain"}, "not found" unless File.exist?("#{ROOT}/#{path}.enc")
     34 
     35   content_type "image/#{FORMAT}"
     36 
     37   geometry = params["geo"] unless params["geo"] == ""
     38   geometry.gsub!(/\d+/) { |n| n.to_i * 2 } if geometry && !DITHER
     39 
     40   cached_path = cache_path_for(path, geometry)
     41   if cached_path && File.exist?(cached_path)
     42     return File.binread(cached_path)
     43   end
     44 
     45   decrypted = Tempfile.new(["dither", File.extname(path)])
     46   decrypted.binmode
     47   decrypted.close
     48 
     49   begin
     50     openssl_ok = system(
     51       "openssl",
     52       "aes-256-cbc",
     53       "-d",
     54       "-in", "#{ROOT}/#{path}.enc",
     55       "-out", decrypted.path,
     56       "-pass", "file:#{KEY}",
     57       "-iter", "1000000"
     58     )
     59     halt 500, {"Content-Type" => "text/plain"}, "decrypt failed" unless openssl_ok
     60 
     61     convert = MiniMagick::Tool::Convert.new
     62     convert.limit("memory", "128MiB")
     63     convert.limit("map", "256MiB")
     64     convert.limit("disk", "1GiB")
     65     convert << decrypted.path
     66     convert.background("white")
     67     convert.layers("flatten")
     68     convert.strip
     69     convert.auto_orient
     70 
     71     if geometry
     72       if geometry.match?(/^\d+x\d+$/)
     73         convert.resize "#{geometry}^"
     74         convert.gravity "center"
     75         convert.extent geometry
     76       else
     77         convert.resize geometry
     78       end
     79     end
     80 
     81     if DITHER
     82       convert.ordered_dither "o8x8"
     83       convert.monochrome
     84     end
     85 
     86     convert << "#{FORMAT.upcase}:-"
     87     output = convert.call
     88 
     89     if cached_path
     90       temp_cache_path = "#{cached_path}.tmp-#{Process.pid}"
     91       File.binwrite(temp_cache_path, output)
     92       FileUtils.mv(temp_cache_path, cached_path)
     93     end
     94 
     95     output
     96   ensure
     97     decrypted.unlink
     98   end
     99 end