summaryrefslogtreecommitdiff
path: root/pb
diff options
context:
space:
mode:
Diffstat (limited to 'pb')
-rw-r--r--pb/__init__.py0
-rwxr-xr-xpb/breaker.py268
-rw-r--r--pb/config.py20
-rwxr-xr-xpb/gradient.py216
-rwxr-xr-xpb/imgrid.py231
-rw-r--r--pb/lib/__init__.py0
-rwxr-xr-xpb/lib/db.py31
-rw-r--r--pb/lib/utils.py122
-rwxr-xr-xpb/pattern.py172
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()
+