#!/usr/bin/python2.7 from subprocess import call, Popen, PIPE import urllib import urllib2 import os import sys import random import re import time urlencode = urllib.urlencode urlopen = urllib2.urlopen Request = urllib2.Request import simplejson as json from PIL import Image import uuid WORKING_DIR = "/tmp" #WORKING_DIR = "/var/www/cache" BIN_COMPOSITE = "/usr/bin/composite" BIN_CONVERT = "/usr/bin/convert" BIN_IDENTIFY = "/usr/bin/identify" DEFAULT_FINALFORMAT = "png"; MAX_SIZE = 1024 * 1024 * 1.2 * 1.5 #FIXME make test params FUSE_MODE="Pin_Light" TEST_PARAMS = { # "pattern_url" : "http://asdf.us/impattern/patterns/1.png", "pattern_data" : '{"matrix":[["0","0","0","0","0","1","0","0","0","0"],["0","0","0","0","1","1","1","0","0","0"],["0","0","1","1","1","0","1","0","0","0"],["0","1","1","0","0","0","0","0","0","0"],["0","1","0","0","1","0","0","0","0","0"],["0","1","0","0","1","0","0","0","1","0"],["0","1","0","0","1","1","0","0","1","0"],["0","1","0","0","0","1","1","1","1","0"],["0","1","1","1","1","0","0","0","0","0"],["0","0","0","0","1","0","0","0","0","0"]],"width":"10","height":"10"}', # "username" : "garfield", # "image_url" : "http://i.asdf.us/im/be/PinkHijab_1425078647_reye.gif", } def bool_correct(s): if re.match(r'^false$', s, re.IGNORECASE): return False elif re.match(r'^true$', s, re.IGNORECASE): return True else: return s #{{{Utility functions class dotdict(dict): """dot.notation access to dictionary attributes""" def __getattr__(self, attr): return self.get(attr) __setattr__= dict.__setitem__ __delattr__= dict.__delitem__ def sanitize (str): return re.sub(r'\W+', '', str) def now(): return int(time.time()) def browser_request (url, data=None): headers = { 'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)', 'Accept': '*/*', } try: req = Request(url, data, headers) response = urlopen(req) except IOError, e: if hasattr(e, 'code'): sys.stderr.write( '%s - ERROR %s' % (url, e.code) ) raise; return None else: return response def download(url, destination, max_size=MAX_SIZE): response = browser_request(url, None) rawimg = response.read() if len(rawimg) == 0: sys.stderr.write("got zero-length file") raise; if len(rawimg) > max_size: sys.stderr.write("file too big: max size {} KB / {} is {} KB".format( str(MAX_SIZE/1024), destination, str(len(rawimg)/1024) )) raise; f = open(destination, "w") f.write(rawimg) f.close() def dimensions (filepath): #works in lieu of a mimetype check (it reads the header as well) ident = (Popen([BIN_IDENTIFY, filepath], stdout=PIPE).communicate()[0]).split(" ") return ident[2].split("x") def file_size (filepath): try: return os.stat(file)[6] except Exception as e: sys.stderr.write(str(e)) raise; def gif_frames(filepath): try: info = Popen([BIN_IDENTIFY,filepath], stdout=PIPE).communicate()[0] frames = filter((lambda x: x), map( (lambda x: x.split(" ")[0]), (info).split('\n') )) return frames except Exception as e: sys.stderr.write(str(e)) raise; #}}} class Pattern: def __init__(self, params): self.params = {} self.now = now() self.tag = "imPattern"; self._pid = str(os.getpid()) self.commands = []; self.height = "" self.width = "" self._required_keys = [ #FIXME change name to username in js #FIXME change js api "pattern_url", "pattern_data", "username", "image_url", ] self.files_created = [] for k in self._required_keys: if k in params: if k in [ 'pattern_url', 'image_url' ]: self.params[k] = params[k] elif k == 'pattern_data': self.params[k] = params[k] #FIXME add conversion data else: self.params[k] = sanitize(params[k]) else: self.params[k] = False; if not self.params['image_url']: sys.stderr.write('no image url'); raise ValueError self.params = dotdict(self.params) self.basename, self._format = self._get_filename(); #FIXME omit file extension for downloaded files self._downloaded_file = os.path.join(WORKING_DIR, "IMPATTERNTMP_DL{}_{}.{}".format(self.basename, self._pid, self._format)) # same here #lets go back to this in a second self._pattern_file = os.path.join(WORKING_DIR, "IMPATTERNTMP_PTN{}_{}.{}".format(self.basename, self._pid, self._format)) # this self._download(self.params.image_url, self._downloaded_file) self.width, self.height = dimensions(self._downloaded_file) # same here self.filename = "{}.{}".format(self.basename, self._format) self.filepath = os.path.join(WORKING_DIR, self.filename) if self.params['pattern_url']: self._download(self.params['pattern_url'], self._pattern_file) elif self.params['pattern_data']: self._from_pattern_data() else: sys.stderr.write("pattern must be supplied as json array or as a png url") raise ValueError; def _download(self, url, dest): try: download(url, dest) self.files_created.append(dest) except Exception as e: sys.stderr.write(str(e)) raise; def _call_cmd(self, cmd, error=""): try: call(cmd) self.commands.append(" ".join(cmd)); except Exception as e: if error: sys.stderr.write(error) else: sys.stderr.write(str(e)) def _from_pattern_data(self): def boolToColor(boolean): if boolean: return (0,0,0,255); else: return (255,255,255,255) specs = json.loads(self.params.pattern_data); if int(specs['width']) > 100 or int(specs['height']) > 100: raise ValueError sys.stderr.write("height and width need to be less than 100 px") img = Image.new('RGBA', (int(specs['width']), int(specs['height']))); pixels = img.load(); for i in range(0, len(specs['matrix'])): for j in range(0, len(specs['matrix'][i])): pixels[j,i] = boolToColor(int(specs['matrix'][i][j])); img.save(self._pattern_file, "PNG") def _get_filename (self): url = self.params.image_url name_part = ""; file_format = ""; if "?" in url: url = url.split("?")[0] if "/" in url: url = urllib.unquote(url).replace(" ","") name_part = url.split("/")[-1] try: parts = name_part.split(".") name_part = sanitize(parts[-2]) file_format = sanitize(parts[-1]) if not name_part or not file_format: sys.stderr.write( "Incompatible input file type") raise; except Exception as e: sys.stderr.write( "Incompatible input file type") raise; else: sys.stderr.write( "Incompatible url") raise; if (len(name_part) > 20): name_part = name_part[:-20] return "{}{}_{}_{}".format(self.tag, name_part, self.now, self.params.username or ""), file_format def _cleanup(self): cmd = ["rm"]+self.files_created self._call_cmd(cmd) #first step def _make_canvas(self): cmd = [BIN_CONVERT,"-size",self.width+"x"+self.height,"canvas:transparent", self.filepath] self._call_cmd(cmd) #second step use the Canvas as a background def _make_mask(self): #tile the pattern pattern on the canvas cmd = [BIN_COMPOSITE,"-tile", self._pattern_file, self.filepath, self.filepath]; self._call_cmd(cmd) #fuse the tiled file to create a mask #convert thebg.gif -compose Dst_In null: thefile.gif -matte -layers composite new.gif cmd = [BIN_CONVERT, self.filepath, "-compose", "Dst_In", "null:", self._downloaded_file, "-matte", "-layers", "composite", self.filepath] self._call_cmd(cmd) #third step def _fuse_mask(self, fuse_mode=FUSE_MODE): cmd = [BIN_CONVERT, "-dispose", "2", self.filepath, "null:", self._downloaded_file, "-matte", "-compose", fuse_mode, "-layers", "composite", self.filepath] self._call_cmd(cmd) def create(self): self._make_canvas(); self._make_mask() self._fuse_mask(); if __name__ == "__main__": p = Pattern(TEST_PARAMS) p.create()