#!/usr/bin/python2.7 import sys import re import os import string import simplejson as json import random import time import urllib import urllib2 from subprocess import Popen,PIPE,call urlencode = urllib.urlencode urlopen = urllib2.urlopen Request = urllib2.Request MAX_SIZE = 1024 * 1024 * 1.2 * 1.5 WORKING_DIR = "/tmp" BIN_CONVERT = "/usr/bin/convert" BIN_COMPOSITE = "/usr/bin/composite" BIN_IDENTIFY = "/usr/bin/identify" THREEDROTATE = "./3Drotate" GRID = "./grid" DB_TAG = "grid"; DEFAULT_HEIGHT = 400 DEFAULT_WIDTH = 600 DEFAULT_LINE_COLOR = "silver" DEFAULT_FINALFORMAT = "png" #{{{Utility functions 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 class dotdict(dict): """dot.notation access to dictionary attributes""" def __getattr__(self, attr): return self.get(attr) __setattr__= dict.__setitem__ __delattr__= dict.__delitem__ def get_mimetype(f): try: mimetype = Popen( [BIN_IDENTIFY, f], stdout=PIPE ).communicate()[0].split(" ")[1].lower() return mimetype except Exception as e: sys.stderr.write(str(e)) raise; 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 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 Imgrid(): def __init__(self, params): self.params = {} self.tag = "imGrid" self.now = now() self.files_created = [] self.commands = []; self._required_keys = [ #{{{ required_keys "width", "height", "linethickness", "opacity", "linecolor", "spacing", "vlines", "hlines", "shadow", "bgimage", "bgcolor", "imageinstead", "planebgcolor", "planebgimage", "swing", "tilt", "roll", "zoom", "skycolor", "transition", "trim", "format", "username" #}}} ] for k in self._required_keys: if k in params: if k in [ 'bgimage', 'planebgimage', 'imageinstead' ] and bool_correct(params[k]): self.params[k] = {} self.params[k]['url'] = params[k] self.params[k]['filename'] = "IMGRIDTMP{}_{}".format(now(), k) self.params[k]['path'] = os.path.join(WORKING_DIR, self.params[k]['filename']) try: download(self.params[k]['url'], self.params[k]['path']) self.files_created.append(self.params[k]['path']) self.params[k]['mimetype'] = get_mimetype(self.params[k]['path']) frames = gif_frames(self.params[k]['path']) if len(frames) > 1: self.params[k]['path'] = random.choice(frames) except Exception as e: sys.stderr.write(str(e)) raise; elif k in [ 'bgcolor', 'planebgcolor' ]: self.params[k] = params[k] else: self.params[k] = bool_correct(sanitize(params[k])) else: self.params[k] = False; self.params = dotdict(self.params) self.basename = self._get_filename(); if not self.params.finalformat: self.params.finalformat = DEFAULT_FINALFORMAT self.filename = "{}.{}".format(self.basename, self.params.finalformat) #final filepath is stored in self.filepath self.filepath = os.path.join(WORKING_DIR, self.filename) def _get_filename(self): return "{}_{}_{}".format( self.tag, now(), self.params.username or "" ); 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)) #makes a canvas file...step 1 (if not bgimage) def _make_canvas(self): dimensions = "{}x{}".format( self.params.width or DEFAULT_WIDTH, self.params.height or DEFAULT_HEIGHT ) if self.params.bgimage: return bgcolor = "xc:{}".format(self.params.bgcolor or 'transparent') cmd = [ BIN_CONVERT, "-size", dimensions, bgcolor, self.filepath ] self._call_cmd(cmd) #2nd step-- run grid def _grid_command(self): cmd = [GRID] if self.params.spacing: if self.params.vlines: width = 2 * int(self.params.width or DEFAULT_WIDTH) cmd += ["-s","{},{}".format(self.params.spacing,width)] elif self.params.hlines: height = 2 * int(self.params.height or DEFAULT_HEIGHT) cmd += ["-s", "{},{}".format(height,self.params.spacing)] else: cmd += ["-s",self.params.spacing] cmd += [ "-c", self.params.linecolor or DEFAULT_LINE_COLOR] if self.params.linethickness: cmd += ['-t',self.params.linethickness] if self.params.opacity: cmd += ['-o',self.params.opacity] cmd += [ self.filepath, self.filepath ] self._call_cmd(cmd) def _shadow_cmd(self): #convert 1.png \( +clone -background black -shadow 110x1+9+9 \) +swap -background none -layers merge +repage 2.png cmd = [ BIN_CONVERT, self.filepath, "(","+clone","-background","black","-shadow","100x2+20+10",")", "+swap","-background","none","-layers","merge","+repage" , self.filepath ] self._call_cmd(cmd) def _threed_rotate_cmd (self): #3rd step--run 3Drotate cmd = [THREEDROTATE] if self.params.swing: cmd += ["pan={}".format(self.params.swing)] if self.params.tilt: cmd += ["tilt={}".format(self.params.tilt)] if self.params.roll: cmd += ["roll={}".format(self.params.roll)] if self.params.zoom: cmd += ["zoom={}".format(self.params.zoom)] if cmd == [THREEDROTATE]: #if nothing has been added return if self.params.planebgcolor and not self.params.planebgimage: cmd += ["bgcolor={}".format(self.params.planebgcolor)] else: cmd += ["bgcolor=none"] cmd += ["skycolor={}".format(self.params.skycolor or 'none')] if self.params.transition: cmd += ["vp={}",self.params.transition] cmd += [ self.filepath, self.filepath ] self._call_cmd(cmd) def _trim_cmd (self): cmd = [BIN_CONVERT, self.filepath, "-trim", "+repage", self.filepath] self._call_cmd(cmd) def _prepare_gridimage(self, image): if image['mimetype'] != 'png': cmd = [BIN_CONVERT, image['path'], self.filepath] else: cmd = ['cp', image['path'], self.filepath] self._call_cmd(cmd) def _overlay_planebgimage(self): cmd = [ BIN_COMPOSITE, "-compose", "Dst_Over", "-gravity", "center", self.params.planebgimage["path"], self.filepath, self.filepath ] self._call_cmd(cmd) def _cleanup(self): if not len(self.files_created): pass cmd = ["rm", "-f"] + self.files_created self._call_cmd(cmd) def create(self): if self.params.bgimage: self._prepare_gridimage(self.params.bgimage) self._grid_command() elif self.params.imageinstead: self._prepare_gridimage(self.params.imageinstead) else: self._make_canvas() self._grid_command() if self.params.shadow: self._shadow_cmd() self._threed_rotate_cmd() if self.params.planebgimage: self._overlay_planebgimage() if self.params.trim: self._trim_cmd() self._cleanup() if __name__ == "__main__": g = Imgrid({ 'bgimage' : 'http://i.asdf.us/im/1a/imBreak_1424909483_xx_abridged___.gif', 'planebgimage' : 'http://i.imgur.com/FICZtph.png', 'tilt' : '30', 'spacing' : '30', 'hlines' : 'true', 'roll' : '30', 'shadow' : 'true', 'trim' : 'true' }) g.create() print g.now print g.filepath print g.commands