diff options
Diffstat (limited to 'pb')
| -rw-r--r-- | pb/__init__.py | 0 | ||||
| -rwxr-xr-x | pb/breaker.py | 268 | ||||
| -rw-r--r-- | pb/config.py | 20 | ||||
| -rwxr-xr-x | pb/gradient.py | 216 | ||||
| -rwxr-xr-x | pb/imgrid.py | 231 | ||||
| -rw-r--r-- | pb/lib/__init__.py | 0 | ||||
| -rwxr-xr-x | pb/lib/db.py | 31 | ||||
| -rw-r--r-- | pb/lib/utils.py | 122 | ||||
| -rwxr-xr-x | pb/pattern.py | 172 |
9 files changed, 1060 insertions, 0 deletions
diff --git a/pb/__init__.py b/pb/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/pb/__init__.py diff --git a/pb/breaker.py b/pb/breaker.py new file mode 100755 index 0000000..c982b14 --- /dev/null +++ b/pb/breaker.py @@ -0,0 +1,268 @@ +#!/usr/bin/python2.7 +import os +import sys +import random +import re +import pb.lib.utils as utils +import urllib +from pb.config import * + +DEFAULT_FINALFORMAT = "png"; +SUBTLE_BREAK_MARK = 'pron' +EXTREME_BREAK_MARK = 'sugar' + +HEADER_OFFSET = 2000 + +# 'CLASSIC':'jpg', +# 'REDUX':'pcds', +# 'BLURRY_BREAK':'viff', +# 'BLURRY_BREAK_2':'mat', +# 'SWIPE':'miff', +# 'RGB_WASH':'psd', +# 'RGB_WASH_2':'psb', +# 'NOISY_BREAK':'palm', +# 'NOISY_BREAK_2':'fig', +# 'BROKEN_VIGNETTE':'pbm', +# 'FAX_MACHINE':'cals', +# 'STRIPES':'exr', +# 'PHOTOCOPY':'art', + +class Breaker(): + def __init__(self, **kwargs): + self.params = {} + self.tag = "imBreak" + self.commands = []; + self._required_keys = [ + "url", + "breaktype", + "finalformat", + "breakmode", + "breakangle", + "username", + "expanded" + ] + self.now = utils.now() + self.files_created = [] + for k in self._required_keys: + if k in kwargs: + if k == 'breaktype': + self.params['breaktype'] = self._get_breaktype(kwargs[k]) + elif k == 'url': + self.params[k] = kwargs[k] + else: + self.params[k] = utils.bool_correct(utils.sanitize(kwargs[k])) + else: + self.params[k] = False; + + + self.params = utils.dotdict(self.params) + + self.basename, self._first_format = self._get_filename(); + self._downloaded_file = os.path.join(WORKING_DIR, "IMBREAKTMP{}.{}".format(self.basename, self._first_format)) # same here + + try: + utils.download(self.params.url, self._downloaded_file) + self.files_created.append(self._downloaded_file) + except Exception as e: + sys.stderr.write(str(e)) + raise; + self._gif_frames = utils.gif_frames(self._downloaded_file) + self._gif_frames = self._gif_frames if len(self._gif_frames) > 1 else False + self.width, self.height = utils.dimensions(self._downloaded_file) # same here + + if not self.params.finalformat: + self.params.finalformat = DEFAULT_FINALFORMAT + if self._gif_frames: + self.params.finalformat = 'gif' + if self.params.breaktype == 'miff': + self.params.finalformat = 'jpg' + self.params.breakmode = 'subtle' + #final filepath is stored in self.filepath + self.filename = "{}.{}".format(self.basename, self.params.finalformat) + self.filepath = os.path.join(WORKING_DIR, self.filename) + self._conversion_file = os.path.join(WORKING_DIR, "IMBREAKTMP{}.{}".format(self.basename, self.params.breaktype)) # this + + + def _call_cmd(self, cmd, error=""): + try: + utils.call_cmd(cmd) + self.commands.append(" ".join(cmd)); + except Exception: + raise Exception("Unable to call cmd {}".format(str(cmd))) + + def _get_breaktype(self, key): + #{{{ conversion table + breaktypeTranslate = { + 'CLASSIC':'jpg', + 'REDUX':'pcds', + 'BLURRY_BREAK':'viff', + 'BLURRY_BREAK_2':'mat', + 'SWIPE':'miff', + 'RGB_WASH':'psd', + 'RGB_WASH_2':'psb', + 'NOISY_BREAK':'palm', + 'NOISY_BREAK_2':'fig', + 'BROKEN_VIGNETTE':'pbm', + 'FAX_MACHINE':'cals', + 'STRIPES':'exr', + 'PHOTOCOPY':'art', + } + #}}} + return breaktypeTranslate[key] + + def _get_filename (self): + url = self.params.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 = utils.sanitize(parts[-2]) + file_format = utils.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 + +#{{{#########rotatefunctions####################################### + def _rotate(self): + cmd = [BIN_CONVERT,self._downloaded_file,"-rotate",self.params.breakangle,"+repage",self._downloaded_file] + self._call_cmd(cmd) + + def _rotate_back(self): + angle = str(360-int(self.params.breakangle)) + cmd = [BIN_CONVERT,self.filepath,"-rotate",angle,"+repage",self.filepath] + self._call_cmd(cmd) + if not self.params.expanded: + cmd = [BIN_CONVERT,self.filepath,"-gravity","Center","-crop","{}x{}+0+0".format( + self.width, self.height),"+repage",self.filepath] + self._call_cmd(cmd) +#}}} + def _subtle_break(self): + #assume the header is no longer than HEADER_OFFSET bytes + breakpoint = random.randint(HEADER_OFFSET, len(self.file_data)) + newfile = "" + newfile = self.file_data[0:breakpoint]; + newfile += SUBTLE_BREAK_MARK; + newfile += self.file_data[breakpoint:] + self.file_data = newfile[0:len(self.file_data)] + + def _extreme_break(self): + increment = len(self.file_data)/10; + i = 0 + newfile = ""; + for b in self.file_data: + if i > HEADER_OFFSET and not (i % increment): + b += EXTREME_BREAK_MARK + newfile += b + i += 1 + self.file_data = newfile[0:len(self.file_data)] + + def _choose_frame(self): + frame = random.choice(self._gif_frames) + cmd = [BIN_CONVERT, frame, self._downloaded_file] + self._call_cmd(cmd) + + def _enforce_jpg(self): + if self.params.breaktype in [ "exr", "bmp", "miff" ] and not re.match(r'jpe?g', self._first_format, re.IGNORECASE): + jpg_file = os.path.join(WORKING_DIR, "{}.{}".format(self.basename, "jpg")) + cmd = [BIN_CONVERT,self._downloaded_file,jpg_file] + self._call_cmd(cmd) + cmd = ["rm",self._downloaded_file] + self._call_cmd(cmd) + + + def _first_conversion(self): + if self._first_format == self.params.breaktype: + self._downloaded_file = self._conversion_file + return + cmd = [BIN_CONVERT, self._downloaded_file, self._conversion_file] + self._call_cmd(cmd) + self.files_created.append(self._conversion_file) + + def _read_data(self, filepath): + f = open(filepath, 'r'); + data = f.read() + f.close() + return data + + def _prepare_filedata(self): + if self._gif_frames: + self._choose_frame() + if self.params.breakangle: + self._rotate() + self._enforce_jpg(); + self._first_conversion(); + self.file_data = self._read_data(self._conversion_file) + if not self.file_data: + sys.stderr.write("Unable to get file_data") + raise; + + def _add_false_data(self, breakmode): + if breakmode == "subtle": + self._subtle_break() + elif breakmode == "extreme": + self._extreme_break() + f = open(self._conversion_file, 'w') + f.write(self.file_data) + f.close(); + +#{{{ SHRINK (UNUSED) + def _shrink(self): + cmd = [ BIN_CONVERT, "-resize", "500x500", self._downloaded_file, self._downloaded_file ]; + self._call_cmd(cmd) +#}}} + + def _final_conversion(self): + cmd = [BIN_CONVERT, self._conversion_file, self.filepath] + self._call_cmd(cmd) + def psd_psbfilepath(num): + return os.path.join(WORKING_DIR, "{}-{}.{}".format(self.basename, num, self.params.finalformat)) + if self.params.breaktype == 'psd': + cmd = ['mv', psd_psbfilepath(1), self.filepath] + self._call_cmd(cmd) + self.files_created.append(psd_psbfilepath(0)) + if self.params.breaktype == 'psb': + cmd = ['mv', psd_psbfilepath(0), self.filepath] + self._call_cmd(cmd) + self.files_created.append(psd_psbfilepath(1)) + + if self.params.breakangle: + self._rotate_back() + + def _cleanup(self): + cmd = ["rm"]+self.files_created + self._call_cmd(cmd) + + def create(self, breakmode=""): + if not breakmode: breakmode = self.params.breakmode + self._prepare_filedata(); + self._add_false_data(breakmode); + self._final_conversion() + self._cleanup() + +if __name__ == "__main__": + TEST_PARAMS = { + "url" : "http://i.asdf.us/im/27/1424816234661dumpfmpfifferkinggr_1424816412_pfifferking.gif" , + "breaktype" : "RGB_WASH", + "finalformat" : "png", + "breakmode" : "extreme", + "breakangle" : "10", + "username" : "donkey", + "expanded" : "false" + } + b = Breaker(**TEST_PARAMS) + b.create(); + print b.filepath diff --git a/pb/config.py b/pb/config.py new file mode 100644 index 0000000..b849b9e --- /dev/null +++ b/pb/config.py @@ -0,0 +1,20 @@ +MAX_SIZE = 1024 * 1024 * 1.2 * 1.5 + +#PATHS +BIN_CONVERT = "/usr/bin/convert" +BIN_COMPOSITE = "/usr/bin/composite" +BIN_IDENTIFY = "/usr/bin/identify" +THREEDROTATE = "./bin/3Drotate" +GRID = "./bin/grid" +BEVELBORDER = "./bin/bevelborder" + +DEFAULT_FINALFORMAT = "png"; + + +#mounted on tmpfs +WORKING_DIR = "/var/www/cache" + +#s3 +AWS_ACCESS_KEY_ID = 'AKIAIR53VPBXKJMXZIBA' +AWS_SECRET_ACCESS_KEY = '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' +BUCKET_NAME = 'i.asdf.us' diff --git a/pb/gradient.py b/pb/gradient.py new file mode 100755 index 0000000..724b37c --- /dev/null +++ b/pb/gradient.py @@ -0,0 +1,216 @@ +#!/usr/bin/python2.7
+import re
+import time
+from subprocess import call
+import simplejson as json
+import sys
+import os
+import sha
+import pb.lib.utils as utils
+from pb.config import *
+
+
+PARAM_LIST = [
+ "width", "height",
+ "color1", "color2",
+ "stripes",
+ "stripenumber", "stripeintensity",
+ "blurriness",
+ "contrast",
+ "brightness", "saturation", "hue",
+ "halftone",
+ "bevel", "percentbeveled",
+ "rotate", "flip", "flop", "tilt",
+ "filetype",
+ "gradienttype",
+ "username",
+]
+DEFAULT_FORMAT = "png"
+DEFAULT_COLORS = {
+ "color1" : "white",
+ "color2" : "black",
+};
+
+DEFAULT_WIDTH = "200"
+DEFAULT_HEIGHT = "200"
+DEFAULT_BEVEL_PERCENT = "12";
+
+HALFTONEVALUES = {
+ "checkeredfade": "h6x6a",
+ "etchedtransition": "o8x8",
+ "bendaydots": "h16x16o",
+ "smallerdots1": "h8x8o",
+ "smallerdots2": "c7x7w",
+ "flatstripes": "o2x2",
+ }
+
+
+class Gradient:
+ def __init__(self, **kwargs):
+ self.tag = "imGradient"
+ self.directory = WORKING_DIR
+ self.commands = []
+ self.filename = ""
+ self.filepath = ""
+ self.now = utils.now()
+
+ params = {}
+ for key in PARAM_LIST:
+ if key in kwargs:
+ if key in ['color1', 'color2']:
+ params[key] = utils.is_color(kwargs[key])
+ else:
+ params[key] = utils.sanitize(kwargs[key])
+
+ if key in ['rotate','tilt','blurriness','stripenumber','stripeintensity']:
+ params[key] = params[key] if utils.is_number(params[key]) else ""
+ elif key in ['brightness', 'contrast', 'hue']:
+ if not utils.is_number(params[key]) or params[key] == "100": params[key] = ""
+ else:
+ params[key] = ""
+ params['width'] = params['width'] if utils.is_number(params['width']) else DEFAULT_WIDTH
+ params['height'] = params['height'] if utils.is_number(params['height']) else DEFAULT_HEIGHT
+ params["color1"] = params["color1"] or DEFAULT_COLORS["color1"];
+ params["color2"] = params["color2"] or DEFAULT_COLORS["color2"];
+ self.params = params
+ if not self.params['percentbeveled']: self.params['percentbeveled'] = DEFAULT_BEVEL_PERCENT
+ self._bevelvalues = [
+ "flatout", "flatinner", "evenlyframed", "biginner",
+ "bigouter", "dramaticflatout", "dramaticflatinner",
+ ]
+
+ def newfilename(self):
+ return "{}{}-{}_{}_{}.{}".format(
+ self.tag,
+ self.params['color1'].replace('#','').replace('(','-').replace(')','-'),
+ self.params['color2'].replace('#','').replace('(','-').replace(')','-'),
+ self.now,
+ self.params['username'],
+ self.params['filetype'] or DEFAULT_FORMAT,
+ )
+
+ def _call_cmd(self, cmd):
+ try:
+ utils.call_cmd(cmd)
+ self.commands.append(" ".join(cmd));
+ except Exception:
+ raise Exception("Unable to call cmd {}".format(str(cmd)))
+
+
+ def _build_cmd(self):
+ cmd = [BIN_CONVERT]
+ cmd.extend([
+ '-size',
+ "{}x{}".format(self.params["width"],self.params["height"])
+ ])
+
+ if self.params['rotate']: cmd.extend(["-rotate", self.params["rotate"]])
+ if self.params['tilt']: cmd.extend(["-distort","SRT",self.params['tilt']])
+ if self.params['flip'] == "true": cmd.append("-flip")
+ if self.params['flop'] == "true": cmd.append("-flop")
+ if self.params['contrast']: cmd.extend(["-contrast-stretch", self.params['contrast']])
+ gradients = {
+ "canvas" : ["canvas:{}".format(self.params['color1'])],
+ "radial" : [
+ "radial-gradient:{}-{}".format( self.params['color1'], self.params['color2'])
+ ],
+ "colorspace" : [
+ "-colorspace",
+ "Gray",
+ "plasma:{}-{}".format(self.params['color1'], self.params['color2'])
+ ],
+ "mirrored" : [
+ "plasma:{}-{}".format(self.params['color1'], self.params['color2']),
+ "\(","+clone","-flop","\)",
+ "append"
+ ],
+ "plasmawash" : [
+ "plasma:{}-{}".format(self.params['color1'], self.params['color2']),
+ "-set","colorspace","HSB"
+ ],
+ "gradientwash" : [
+ "gradient:{}-{}".format(self.params['color1'], self.params['color2']),
+ "-set","colorspace","HSB"
+ ],
+ "noise" : ["xc:","+noise","Random","-virtual-pixel","tile"]
+ }
+ if self.params["gradienttype"] in gradients:
+ cmd.extend(gradients[self.params['gradienttype']])
+ else:
+ cmd.append("gradient:{}-{}".format(self.params['color1'], self.params['color2']))
+
+ if self.params['blurriness']:
+ cmd.extend(["-blur","0x{}".format(self.params["blurriness"]),"-auto-level"])
+
+ if self.params['stripes'] == "true" and len(self.params['stripenumber']):
+ cmd.extend(["-function","Sinusoid"])
+ if self.params['stripeintensity']:
+ cmd.append("{},{}".format(self.params['stripenumber'],self.params["stripeintensity"]))
+ else:
+ cmd.append(self.params['stripenumber'])
+ if self.params["halftone"] in HALFTONEVALUES:
+ cmd.extend([
+ "-ordered-dither",
+ HALFTONEVALUES[self.params["halftone"]]
+ ])
+ cmd += [
+ '-modulate',
+ "{},{},{}".format(
+ self.params['brightness'] or "100",
+ self.params['saturation'] or "100",
+ self.params['hue'] or "100")
+ ]
+ cmd.append(os.path.join(self.directory,self.filename));
+ self._call_cmd(cmd)
+
+ def _get_bevelvalue(self):
+ w, h = map(int, (self.params['width'], self.params['height']))
+ if h >= w:
+ bevpercentval = str(int(self.params['percentbeveled'])*0.005*int(h))
+ else:
+ bevpercentval = str(int(self.params['percentbeveled'])*0.005*int(w))
+ return {
+ "flatout": ["-s",bevpercentval,"-m","outer"],
+ "flatinner": ["-s",bevpercentval,"-m","inner"],
+ "evenlyframed": ["-s ",bevpercentval,"-m", "split"],
+ "biginner": ["-s",bevpercentval,"-m","outer","-c","50","-b","red","-a","25"],
+ "bigouter": ["-s",bevpercentval,"-m","split","-c","50","-b","red","-a","25"],
+ "dramaticflatout": ["-s",bevpercentval,"-m","outer","-a","25","-b","blue"],
+ "dramaticflatinner": ["-s",bevpercentval,"-m","outer","-a","25","-b","blue"],
+ }[self.params['bevel']]
+
+ def _make_bevel(self):
+ cmd = [BEVELBORDER]
+ cmd += self._get_bevelvalue()
+ cmd += [ os.path.join(self.directory,self.filename), os.path.join(self.directory, self.filename) ]
+ self._call_cmd(cmd)
+
+ def create(self):
+ self.filename = self.newfilename()
+ self.filepath = os.path.join(self.directory, self.filename)
+ self._build_cmd()
+ if self.params['bevel'] in self._bevelvalues:
+ self._make_bevel()
+
+if __name__ == "__main__":
+ TEST_FORM = {
+ "width" : "200",
+ "color1" : "#ffdead",
+ "color2" : "blue",
+ "stripes" : "true",
+ "stripenumber" : "20",
+ "gradienttype" : "radial",
+ "stripeintensity" : "20",
+ "halftone" : "checkeredfade",
+ "percentbeveled" : "30",
+ "flip" : "true",
+ "bevel" : "flatinner",
+ "rotate" : "20",
+ "height" : "200",
+ "filetype" : "jpg",
+ "username" : "whatever"
+ }
+ g = Gradient(**TEST_FORM);
+ g.create();
+ print " ".join(g.commands)
+ print g.filename
diff --git a/pb/imgrid.py b/pb/imgrid.py new file mode 100755 index 0000000..f75ec4a --- /dev/null +++ b/pb/imgrid.py @@ -0,0 +1,231 @@ +#!/usr/bin/python2.7 +import sys +import re +import os +import simplejson as json +import random +import pb.lib.utils as utils +from pb.config import * +import tempfile + +DEFAULT_HEIGHT = 400 +DEFAULT_WIDTH = 600 +DEFAULT_LINE_COLOR = "silver" + +class Imgrid(): + def __init__(self, **kwargs): + self.tag = "imGrid" + 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" +#}}} + ] + self.now = utils.now() + + #Work out params ... + #note, tmpfile lib is pretty much useless here, given imagemagick's behavior with gifs (it splits them) etc... + #instead we're just making our own in /var/www/cache (tmpfs mounted there) + self.params = {} + for k in self._required_keys: + if k in kwargs: + if k in [ 'bgimage', 'planebgimage', 'imageinstead' ] and utils.bool_correct(kwargs[k]): + self.params[k] = { + 'url' : kwargs[k], + 'filename' : self._make_tempname(k), + 'path' : os.path.join(WORKING_DIR, self._make_tempname(k)) , + } + try: + utils.download(self.params[k]['url'], self.params[k]['path']) + self.files_created.append(self.params[k]['path']) + self.params[k]['mimetype'] = utils.get_mimetype(self.params[k]['path']) + frames = utils.gif_frames(self.params[k]['path']) + if len(frames) > 1: + self.params[k]['path'] = random.choice(frames) + + except Exception: + sys.stderr.write(str(e)) + raise Exception ("BAD PARAMS"); + elif k in [ 'skycolor', 'bgcolor', 'planebgcolor','linecolor' ]: + try: + self.params[k] = is_color(params[k]) + except Exception: + raise Exception("Unable to process color for:\n{}".format(k)) + elif k == 'opacity': + self.params[k] = str(float(params[k])) + elif k == 'zoom': + self.params[k] = int(float(params[k])) + else: + self.params[k] = utils.bool_correct(utils.sanitize(kwargs[k])) + else: + self.params[k] = None; + + self.params = utils.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, + self.now, + self.params.username or "" + ); + + def _call_cmd(self, cmd): + try: + utils.call_cmd(cmd) + self.commands.append(" ".join(cmd)); + except Exception: + raise Exception("Unable to call cmd {}".format(str(cmd))) + + def _make_tempname(self, s): + return "IMGRIDTMP{}{}".format(self.now, s); + + + #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={}".format(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.commands diff --git a/pb/lib/__init__.py b/pb/lib/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/pb/lib/__init__.py diff --git a/pb/lib/db.py b/pb/lib/db.py new file mode 100755 index 0000000..2570b4f --- /dev/null +++ b/pb/lib/db.py @@ -0,0 +1,31 @@ +import MySQLdb +USER = "asdfus" +PASSWORD = "gTYgT&M6q" +DATABASE = "asdfus" + +class db: + def __init__ (self): + self.conn = None + self.cursor = None + self.connect() + + def connect (self): + self.conn = MySQLdb.connect (host = "localhost", + user = USER, + passwd = PASSWORD, + db = DATABASE + ) + self.cursor = self.conn.cursor () + + def execute (self,sql,args=()): + try: + self.cursor.execute(sql,args) + except MySQLdb.Error, e: + print "Error %d: %s" % (e.args[0], e.args[1]) + # sys.exit (1) + self.connect() + self.cursor.execute(sql,args) + + def lastinsertid (self): + return DB.conn.insert_id() + diff --git a/pb/lib/utils.py b/pb/lib/utils.py new file mode 100644 index 0000000..3630ba0 --- /dev/null +++ b/pb/lib/utils.py @@ -0,0 +1,122 @@ +import re +from pb.config import * +import time +import urllib +import urllib2 +import sys +from subprocess import Popen,PIPE,call +Request = urllib2.Request +urlencode = urllib.urlencode +urlopen = urllib2.urlopen + +def call_cmd(cmd, error=""): + try: + call(cmd) + except Exception as e: + raise (str(e)) + +def is_color(s): + if s == "": + return "transparent" + if re.match('(rgba?\([0-9]+,[0-9]+,[0-9]+\))|([a-zA-Z]+)|(\#[A-Ha-h0-9]+)', s): + return s.replace(' ', ''); + else: + sys.stderr.write("Not a color: {}\n".format(s)) + raise ValueError + +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 is_number(s): + try: + return int(s) + except (ValueError, TypeError): + return False + +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("couldn't determine mimetype") + 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("IMGRID couldn't determine file size") + 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("IMGRID couldn't get gif frames") + sys.stderr.write(str(e)) + raise; diff --git a/pb/pattern.py b/pb/pattern.py new file mode 100755 index 0000000..359af20 --- /dev/null +++ b/pb/pattern.py @@ -0,0 +1,172 @@ +#!/usr/bin/python2.7 +import os +import sys +import random +import re +import urllib +from pb.config import * +import lib.utils as utils + +import simplejson as json +from PIL import Image +import uuid + +FUSE_MODE="Pin_Light" + +class Pattern: + def __init__(self, **kwargs): + self.params = {} + self.tag = "imPattern"; + self._pid = str(os.getpid()) + self.commands = []; + self.now = utils.now() + 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 kwargs: + if k in [ 'pattern_url', 'image_url' ]: + self.params[k] = kwargs[k] + elif k == 'pattern_data': + self.params[k] = kwargs[k] #FIXME add conversion data + else: + self.params[k] = utils.sanitize(kwargs[k]) + else: + self.params[k] = False; + + if not self.params['image_url']: + sys.stderr.write('no image url'); + raise ValueError + self.params = utils.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 = utils.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: + utils.download(url, dest) + self.files_created.append(dest) + except Exception as e: + sys.stderr.write(str(e)) + raise; + + def _call_cmd(self, cmd): + try: + utils.call_cmd(cmd) + self.commands.append(" ".join(cmd)); + except Exception: + raise Exception("Unable to call cmd {}".format(str(cmd))) + + 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 = utils.sanitize(parts[-2]) + file_format = utils.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__": + 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", + } + p = Pattern(**TEST_PARAMS) + p.create() + |
