diff options
| -rwxr-xr-x | htmljs/gallery/gallery | 706 | ||||
| -rw-r--r-- | htmljs/gallery/gallery_main.css | 245 | ||||
| -rw-r--r-- | htmljs/gallery/gallery_main.js | 156 | ||||
| -rw-r--r-- | htmljs/gallery/index.html | 97 | ||||
| -rw-r--r-- | install/database/database_create_statements | 3 | ||||
| -rw-r--r-- | install/database/database_schema.sql | 15 | ||||
| -rwxr-xr-x | pb/Breaker/__init__.py | 268 | ||||
| -rw-r--r-- | pb/Config/__init__.py | 20 | ||||
| -rwxr-xr-x | pb/Generate/__init__.py | 231 | ||||
| -rwxr-xr-x | pb/Gradient/__init__.py | 216 | ||||
| -rwxr-xr-x | pb/Imgrid/__init__.py | 232 | ||||
| -rwxr-xr-x | pb/Imlandscape/__init__.py | 101 | ||||
| -rwxr-xr-x | pb/Imlandscape/landscape | 163 | ||||
| -rwxr-xr-x | pb/Pattern/__init__.py | 172 | ||||
| -rw-r--r--[-rwxr-xr-x] | pb/lib/Db/__init__.py (renamed from pb/lib/db.py) | 1 | ||||
| -rw-r--r-- | pb/lib/Utils/__init__.py (renamed from pb/lib/utils.py) | 2 | ||||
| -rwxr-xr-x | pbserver.py | 215 | ||||
| -rw-r--r-- | tests/jsonptest.sh | 2 |
18 files changed, 2844 insertions, 1 deletions
diff --git a/htmljs/gallery/gallery b/htmljs/gallery/gallery new file mode 100755 index 0000000..2b891f2 --- /dev/null +++ b/htmljs/gallery/gallery @@ -0,0 +1,706 @@ +#!/usr/bin/python2.7 +import os +import cgi +import re + +import random + +import db +db = db.db() + +BASE_HREF = "http://i.asdf.us/im/" +PARAMLIST = "addr start limit name interface random tag" +SQL_LIMIT = 20 +QUERY_LIMIT = 20; + +def get_params (paramlist): + paramkeys = paramlist.split() + form = {} + try: + qs = os.environ['QUERY_STRING'] + except: + qs = "" + if len(qs): + pairs = qs.replace("&", "&").split("&") + for pair in pairs: + k,v = pair.split("=", 1) + form[k] = v + params = {} + for key in paramkeys: + if key in form: + if key == "random": + params[key] = sanitizeInteger(form["random"]) + else: + params[key] = sanitize(form[key]) + else: + params[key] = None + return params + +def tagTranslate(s): + table = { + "grid" : "imGrid", + "gradient" : "imGradient", + "break" : "imBreak" + } + if s in table: + return table[s] + else: + return s + +def sanitize (str): + return re.sub(r'\W+', '', str) +def sanitizeInteger (str): + return re.sub(r'\D+', '', str) +def get_files (params): + sql = "SELECT * FROM im_cmd " + args = [] + where = [] + + if params['start'] is not None: + where.append("id < %s") + args.append(params['start']) + if params['name'] is not None: + where.append("name=%s") + args.append(params['name']) + if params['tag'] is not None: + where.append("tag=%s") + args.append(tagTranslate(params['tag'])) + if len(where): + sql += "WHERE " + sql += " AND ".join(where) + sql += " " + + if params['random'] is not None: + if params['random'] == '1': + sql += "ORDER BY RAND(" + str(random.randint(1,2**63)) + ") " + else: + sql += "ORDER BY RAND(" + params['random'] + ") " + else: + sql += "ORDER BY id DESC " + sql += "LIMIT %s" + + if params['limit'] is not None: + args.append( int(params['limit']) ) + else: + args.append( SQL_LIMIT ) + db.execute(sql, args) + rows = db.cursor.fetchall () + return rows + +def is_image (img): + if "jpeg" in img: + return True + if "jpg" in img: + return True + if "gif" in img: + return True + if "png" in img: + return True + return False + + +params = get_params(PARAMLIST) + +titlephrase = random.choice([ + 'Keep on pickin\' on..', + 'Pickolaus Pickleby by Charles Pickens!', + 'You pick potato and I pick potahto...', + 'Take your piq!', + 'Show em what you got', + 'I sure know how to pick \'em', + 'Jus pick somethin already!', + 'You can\'t pick your friends...', + 'Select your image my liege', + 'There\'s a time to choose...', + 'gimme a choice! gimme lil\' choice-a-that...', + 'You choose you lose', + 'novels by James CHOICE...', + 'Choose away, chooser-man...', + ]) + +print "Content-type: text/html" +print "Pragma: no-cache" +print +print """ + +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> +<head> +""" +print "<title>"+titlephrase+"</title>" +print""" +<script type="text/javascript" src="/js/jquery.js"></script> +<script type="text/javascript" src="/js/titleScrambler.js"></script> +<script type="text/javascript"> +var imagedata = [ """ + +files = get_files(params) +count = 0 +lowest_id = 999999999999 +for f in files: + # url = BASE_HREF + f.replace(" ","%20") + url = BASE_HREF + f[5] + "/" + f[7] + username = f[3] + print ' ["%s", "%s"],' % (url, username); + lowest_id = min(f[0], lowest_id) +print " []" #putting this here to handle the last "," which causes a crash in ie +#print "<div><img src='%s' username='%s' class='pb' /></div>" % (url, username) +#print "</div>" +print """] +$(function(){ + for (var i=0; i< (imagedata.length - 1); i++){ + var newDiv = document.createElement("div"); + var newImage = document.createElement("img"); + newImage.src = imagedata[i][0]; + newImage.className = "pb"; + newDiv.appendChild(newImage); +// consider calling something like isotope add here...and ONLY APPENDING the image to the parent div once it has loaded +// $(newImage).load(function(){ +// console.log("like a true playa"); +// }) + $("#images").append(newDiv); + } +}); +</script> +<script type="text/javascript" src="/js/jquery.isotope.min.js"></script> +<script type="text/javascript" src="/js/gallery_isotope_config.js?v=3"></script> +<style type="text/css"> + +html,body{width:100%;height:100%;margin:0;padding:0;} + """ +if params['interface'] is not None and params['interface'] == "off": + print """ +body + { + background-color: white; + padding: 0; margin: 0; + } +#images + { + margin: 12px 0 0 8px; + } +div, div img + { + padding: 0; margin: 0; + margin-left: -4px; + margin-top: -4px; + } +""" +else: + print """ +body + { + background-color: #eee; + background-image: -webkit-gradient( linear, left bottom, left top, color-stop(0.32, rgb(245,238,235)), color-stop(0.66, rgb(252,252,252))); + background-image: -moz-linear-gradient( center bottom, rgb(245,238,235) 32%, rgb(252,252,252) 66%); overflow-y: scroll; + } +""" +print """ +html + { + padding-bottom: 200px; + } +#images + { + width: 100%; + margin-top:70px; + } +#images div + { + width: 200px; + display: inline-block; + } +div img + { + max-width: 200px; + max-height: 200px; + border: 0; + } +#images img + { + cursor: pointer; + display: none; + } +#dump + { + position: fixed; + left: 0; + bottom: 10px; + padding: 10px; + width: 100%; + border-bottom: 2px solid #000; + background-color: #f8f8f8; + border-top: 1px solid #888; + z-index: 1000; + } +#dump #rebus + { + clear: right; + width: 90%; + max-height: 700px; + overflow-y: scroll; + background-color: #fff; + padding-bottom: 5px; + border-bottom: 1px solid #ddd; + margin-bottom: 5px; + } +#dump #rebus img + { + cursor: pointer; + display: inline; + max-width: 400px; + max-height: 400px; + margin-right: -4px; + } +#dump #urlz + { + width: 90%; + font-size: 15px; + } +#actions + { + position: fixed; + top: 10px; + left: 10px; + cursor: pointer; + text-align: left; + font-family: sans-serif; + z-index:1000; + } +#tags + { + position: fixed; + top: 10px; + left: 200px; + cursor: pointer; + text-align: left; + font-family: sans-serif; + z-index:1000; + } +#help + { + position: fixed; + top: 10px; + right: 10px; + cursor: pointer; + text-align: right; + font-family: sans-serif; + z-index:1000; + } +#help b { + text-align: right; +} +#help div{ + background: rgba(200,200,200,0.8); +} +#keys div{ + background: none; +} +#help .small{ + font-size: 11px; +} +#help b, #actions b, #tags b + { + padding: 5px; + background-color: #f8f4fb; + display: block; + max-width: 160px; + } +#help b:hover, #actions b:hover, #tags b:hover + { + color: cyan; + + } +#help #keys, #sorting-optionsContainer, #tag-optionsContainer + { + clear: both; + padding: 5px; + display: none; + min-width: 120px; + font-size: 14px; + } +.sorting-options, .tag-options { + color:black; + cursor: pointer; + background: rgba(200,200,200,0.8); +} +.tag-clear{ + color:#333; + cursor: pointer; + background: rgba(200,200,200,0.8); + +} +.sorting-optionsContainer div:hover{ + color: red; +} +.tag-optionsContainer div:hover{ + color: pink; +} +.sorting-options:hover, .tag-options:hover, .tag-clear:hover { +background: gold; +} +#nextpage + { + position: fixed; + right: 190px; + font-family: sans-serif; + top: 10px; + padding: 5px; + background-color: rgba(255,255,255,0.7); + z-index:9999 + } +#nextpage a + { + color: #33f; + } +.rtlink + { + display: block; + width: 100%; + text-align: right; + } +#d_clip_container + { + display: inline-block; + } +#d_clip_button, #clear + { + font-family: Lucida Sans Unicode, Lucida Grande, sans-serif; + color: #333; + font-size: 13px; + line-height: 13px; + text-align: center; + margin: 2px; padding: 5px; + display: inline-block; + border-top: 1px solid #888; + border-left: 1px solid #888; + border-right: 1px solid #555; + border-bottom: 1px solid #333; + background-image: linear-gradient(bottom, rgb(235,235,235) 0%, rgb(250,250,250) 53%); + background-image: -o-linear-gradient(bottom, rgb(235,235,235) 0%, rgb(250,250,250) 53%); + background-image: -moz-linear-gradient(bottom, rgb(235,235,235) 0%, rgb(250,250,250) 53%); + background-image: -webkit-linear-gradient(bottom, rgb(235,235,235) 0%, rgb(250,250,250) 53%); + background-image: -ms-linear-gradient(bottom, rgb(235,235,235) 0%, rgb(250,250,250) 53%); + background-image: -webkit-gradient( linear, left bottom, left top, color-stop(0, rgb(235,235,235)), color-stop(0.53, rgb(250,250,250))); + } +#d_clip_button.hover, #clear:hover + { + color: #555; + background-image: linear-gradient(bottom, rgb(245,240,245) 0%, rgb(255,255,255) 78%); + background-image: -o-linear-gradient(bottom, rgb(245,240,245) 0%, rgb(255,255,255) 78%); + background-image: -moz-linear-gradient(bottom, rgb(245,240,245) 0%, rgb(255,255,255) 78%); + background-image: -webkit-linear-gradient(bottom, rgb(245,240,245) 0%, rgb(255,255,255) 78%); + background-image: -ms-linear-gradient(bottom, rgb(245,240,245) 0%, rgb(255,255,255) 78%); + background-image: -webkit-gradient( linear, left bottom, left top, color-stop(0, rgb(245,240,245)), color-stop(0.78, rgb(255,255,255))); + } +#d_clip_button.active, #clear:active + { + color: #111; + border-top: 1px solid #333; + border-left: 1px solid #555; + border-right: 1px solid #555; + border-bottom: 1px solid #333; + background-image: linear-gradient(bottom, rgb(194,194,194) 0%, rgb(235,235,235) 53%); + background-image: -o-linear-gradient(bottom, rgb(194,194,194) 0%, rgb(235,235,235) 53%); + background-image: -moz-linear-gradient(bottom, rgb(194,194,194) 0%, rgb(235,235,235) 53%); + background-image: -webkit-linear-gradient(bottom, rgb(194,194,194) 0%, rgb(235,235,235) 53%); + background-image: -ms-linear-gradient(bottom, rgb(194,194,194) 0%, rgb(235,235,235) 53%); + background-image: -webkit-gradient( linear, left bottom, left top, color-stop(0, rgb(194,194,194)), color-stop(0.53, rgb(235,235,235))); + } + +.pulsate_and_grow { + -webkit-animation: pulsate_and_grow 0.5s ease-out; + -webkit-animation-iteration-count: 3; + opacity: 1.0; +} +@-webkit-keyframes pulsate_and_grow { + 0% {-webkit-transform: scale(0.1, 0.1); opacity: 0.1;} + 50% {opacity: 1.0;} + 100% {-webkit-transform: scale(1.0, 1.0); opacity: 0.1;} +} +.tag-options:active, .tag-clear:active { + -webkit-animation: pulsate_opacity 0.5s ease-out; + -webkit-animation-iteration-count: 3; + opacity: 1.0; + +} +.sorting-options:active { + -webkit-animation: pulsate_opacity 0.5s ease-out; + -webkit-animation-iteration-count: 3; + opacity: 1.0; + +} +@-webkit-keyframes pulsate_opacity{ + 0% { opacity: 0.1;} + 50% {opacity: 1.0;} + 100% {opacity: 0.1;} +} +</style> +<link href="/im/gallery_style.css" type="text/css" rel="stylesheet" /> +<script type="text/javascript"> +$(function(){ + $("b").addClass("pulsate_and_grow"); + $(".sorting-options").click(function(){ + + $(".sorting-options").click(function(){ + console.log("wazzup"); + if ($(this).hasClass("pulsate_opacity")){ + $(this).removeClass("pulsate_opacity"); + } + $(this).addClass("pulsate_opacity"); + + }); + }); +}); + +</script> +</head> +<body> +<div id="help"> + <b>key controls</b> + <div id="keys"> + <br/> + <div class="small"><i>when composer is launched...</i></div> + <div>ESC toggle</div> + <div>C clear</div> + <div>R reverse</div> + <br/> + <div class="small"><i>in the gallery...</i></div> + <div>BACKSPACE delete</div> + <div>LEFT ARROW newer</div> + <div>RIGHT ARROW older</div> + </div> +</div> +<div id="actions"> + <b>sort</b> + <div id="sorting-optionsContainer"> + <div class="sorting-options" id="date">date</div> + <div class="sorting-options" id="username">username</div> + <div class="sorting-options" id="height">height</div> + <div class="sorting-options" id="width">width</div> + <div class="sorting-options" id="gif">gif</div> + <div class="sorting-options" id="shuffle">shuffle</div> + <div class="sorting-options" id="lombada">lombada</div> + </div> +</div> +<div id="tags"> + <b>tags</b> + <div id="tag-optionsContainer"> + + + <div class="tag-clear" ><i>remove tag</i></div> + <div class="tag-options" id="transparent">transparent</div> + <div class="tag-options" id="break">break</div> + <div class="tag-options" id="gradient">gradient</div> + <div class="tag-options" id="grid">grid</div> + <div class="tag-options" id="shader">shader</div> + <div class="tag-options" id="Over">Over</div> + <div class="tag-options" id="ATop">ATop</div> + <div class="tag-options" id="Dst_Over">Dst_Over</div> + <div class="tag-options" id="Dst_In">Dst_In</div> + <div class="tag-options" id="Dst_Out">Dst_Out</div> + <div class="tag-options" id="Multiply">Multiply</div> + <div class="tag-options" id="Screen">Screen</div> + <div class="tag-options" id="Divide">Divide</div> + <div class="tag-options" id="Plus">Plus</div> + <div class="tag-options" id="Difference">Difference</div> + <div class="tag-options" id="Exclusion">Exclusion</div> + <div class="tag-options" id="Lighten">Lighten</div> + <div class="tag-options" id="Darken">Darken</div> + <div class="tag-options" id="Overlay">Overlay</div> + <div class="tag-options" id="Hard_Light">Hard_Light</div> + <div class="tag-options" id="Soft_Light">Soft_Light</div> + <div class="tag-options" id="Pegtop_Light">Pegtop_Light</div> + <div class="tag-options" id="Linear_Light">Linear_Light</div> + <div class="tag-options" id="Vivid_Light">Vivid_Light</div> + <div class="tag-options" id="Pin_Light">Pin_Light</div> + <div class="tag-options" id="Linear_Dodge">Linear_Dodge</div> + <div class="tag-options" id="Linear_Burn">Linear_Burn</div> + <div class="tag-options" id="Color_Dodge">Color_Dodge</div> + <div class="tag-options" id="Color_Burn">Color_Burn</div> + + </div> +</div> +<div id="dump"> + <div id="rebus"></div> + <input id="urlz" type="text" /> + <div id="d_clip_container" style="position:relative"> + <div id="d_clip_button">copy</div> + </div> + <button id="clear">clear</button> +</div> +<div id="images"> +""" + + +previous_id = lowest_id + (SQL_LIMIT * 2) +back = ["start=%d" % lowest_id, "limit=%d" % SQL_LIMIT] +newer = ["start=%d" % previous_id, "limit=%d" % SQL_LIMIT] +random_time = "" +if params['name'] is not None: + back.append("name=%s" % params['name']) + newer.append("name=%s" % params['name']) + random_time = "name=%s" % params['name'] +if params['tag'] is not None: + back.append("tag=%s" % params['tag']) + newer.append("tag=%s" % params['tag']) + random_time = "tag=%s" % params['tag'] + +newer_QS = "&".join(newer) +back_QS = "&".join(back) +random_time_QS = ""; +if random_time: + random_time_QS = "&%s" % random_time; +print "<div id='nextpage'>" +print "<a href='/im/'>editor</a>" +print "|" +print "<a href='?%s'>← newer</a>" % newer_QS; +print "|" +if params['random'] is not None and params['random'] == '1': + print "<a href='?random=%d%s'>random</a>" % (random.randint(1,2**63), random_time_QS); +else: + print "<a href='?random=%d%s'>random</a>" % (1, random_time_QS); +print "|" +print "<a href='?%s'>older →</a>" % back_QS; +print "</div>" +print """ +</body> +<script type="text/javascript" src="/js/ZeroClipboard.js"></script> +<script type="text/javascript" src="http://asdf.us/js/pbembed.js"></script> +<script type="text/javascript"> +ZeroClipboard.setMoviePath( 'http://asdf.us/swf/ZeroClipboard10.swf' ); +var clip = new ZeroClipboard.Client(); +clip.glue( 'd_clip_button' ); +var Dump = + { + pick: function () + { + Dump.pickUrl( $(this).attr("src") ) + }, + pickUrl: function (url) + { + $("#rebus").append ($ ("<img>").attr ("src", url)) + $("#rebus").show() + var theDump = $("#urlz").val() + " " + url + $("#urlz").val( theDump ) + clip.setText( theDump ) + return false + }, + clear: function () + { + $("#rebus").html("") + $("#urlz").val("") + clip.setText("") + }, + backspace: function () + { + $("#rebus img:last").remove() + var urllist = $("#urlz").val().split(" ") + urllist.pop() + $("#urlz").val( urllist.join(" ") ) + }, + reverse: function () + { + urllist = $("#urlz").val().split(" ") + Dump.clear() + for (i in urllist.reverse()) + if (urllist[i]) + Dump.pickUrl(urllist[i]) + }, + showNewer: function() + { + window.location.href = '?"""+newer_QS+"""' + }, + showOlder: function() + { + window.location.href = '?"""+back_QS+"""' + } + } +function applyTag(tagname){ + tag_regex = /&tag=[^&]*/; + if (document.URL.match(tag_regex)){ + return document.URL.replace(tag_regex, "&tag="+tagname); + }else if(document.URL.match(/\/$/)){ + return document.URL.replace(/\/$/, "?tag="+tagname); + } + else{ + return document.URL+"&tag="+tagname; + } +} +var Main = + { + editing: false, + kp: function (event) + { + console.log(event.keyCode); + switch (event.keyCode) + { + // BS + case 8: + if (! Main.editing) + Dump.backspace() + return false + // C + case 67: + if (! Main.editing) + Dump.clear() + break + // R + case 82: + if (! Main.editing) + Dump.reverse() + break + // ESC + case 27: + // H + case 72: + if (! Main.editing) + $("#rebus").toggle() + break + // LEFT ARROW + case 37: + if (! Main.editing) + Dump.showNewer() + break + // RIGHT ARROW + case 39: + if (! Main.editing) + Dump.showOlder() + break + } + return true + }, + poll: function () + { + }, + pollCallback: function () + { + }, + init: function () + { + $(document).keydown(Main.kp) + $("#urlz").focus(function(){ Main.editing = true }) + $("#urlz").blur(function(){ Main.editing = false }) + $("#clear").live("click", Dump.clear) + $("#help").click(function(){ $("#keys").slideToggle() }) + $("#actions b").click(function(){ $("#sorting-optionsContainer").slideToggle() }) + $("#tags b").click(function(){ $("#tag-optionsContainer").slideToggle() }) + $(".tag-options").click(function(){document.location.href= applyTag(this.id)}); + $(".tag-clear").click(function(){ document.location.href = document.URL.replace(/&?tag=[^&]*/ ,"").replace(/\?$/,"")}); + $("div img").live("click", Dump.pick) + Dump.clear() + } + } +""" +if params['interface'] is not None and params['interface'] == "off": + print """ +$("#nextpage,#help,#dump").hide() +$("body").css({"margin": 0, "padding": 0, "overflow": hidden, "background-color": white}) +""" +else: + print "Main.init()" +print """ +</script> +</html> +""" + diff --git a/htmljs/gallery/gallery_main.css b/htmljs/gallery/gallery_main.css new file mode 100644 index 0000000..3f06b19 --- /dev/null +++ b/htmljs/gallery/gallery_main.css @@ -0,0 +1,245 @@ +body + { + background-color: #eee; + background-image: -webkit-gradient( linear, left bottom, left top, color-stop(0.32, rgb(245,238,235)), color-stop(0.66, rgb(252,252,252))); + background-image: -moz-linear-gradient( center bottom, rgb(245,238,235) 32%, rgb(252,252,252) 66%); overflow-y: scroll; + } +html + { + padding-bottom: 200px; + } +#images + { + width: 100%; + margin-top:70px; + } +#images div + { + width: 200px; + display: inline-block; + } +div img + { + max-width: 200px; + max-height: 200px; + border: 0; + } +#images img + { + cursor: pointer; + display: none; + } +#dump + { + position: fixed; + left: 0; + bottom: 10px; + padding: 10px; + width: 100%; + border-bottom: 2px solid #000; + background-color: #f8f8f8; + border-top: 1px solid #888; + z-index: 1000; + } +#dump #rebus + { + clear: right; + width: 90%; + max-height: 700px; + overflow-y: scroll; + background-color: #fff; + padding-bottom: 5px; + border-bottom: 1px solid #ddd; + margin-bottom: 5px; + } +#dump #rebus img + { + cursor: pointer; + display: inline; + max-width: 400px; + max-height: 400px; + margin-right: -4px; + } +#dump #urlz + { + width: 90%; + font-size: 15px; + } +#actions + { + position: fixed; + top: 10px; + left: 10px; + cursor: pointer; + text-align: left; + font-family: sans-serif; + z-index:1000; + } +#tags + { + position: fixed; + top: 10px; + left: 200px; + cursor: pointer; + text-align: left; + font-family: sans-serif; + z-index:1000; + } +#help + { + position: fixed; + top: 10px; + right: 10px; + cursor: pointer; + text-align: right; + font-family: sans-serif; + z-index:1000; + } +#help b { + text-align: right; +} +#help div{ + background: rgba(200,200,200,0.8); +} +#keys div{ + background: none; +} +#help .small{ + font-size: 11px; +} +#help b, #actions b, #tags b + { + padding: 5px; + background-color: #f8f4fb; + display: block; + max-width: 160px; + } +#help b:hover, #actions b:hover, #tags b:hover + { + color: cyan; + + } +#help #keys, #sorting-optionsContainer, #tag-optionsContainer + { + clear: both; + padding: 5px; + display: none; + min-width: 120px; + font-size: 14px; + } +.sorting-options, .tag-options { + color:black; + cursor: pointer; + background: rgba(200,200,200,0.8); +} +.tag-clear{ + color:#333; + cursor: pointer; + background: rgba(200,200,200,0.8); + +} +.sorting-optionsContainer div:hover{ + color: red; +} +.tag-optionsContainer div:hover{ + color: pink; +} +.sorting-options:hover, .tag-options:hover, .tag-clear:hover { +background: gold; +} +#nextpage + { + position: fixed; + right: 190px; + font-family: sans-serif; + top: 10px; + padding: 5px; + background-color: rgba(255,255,255,0.7); + z-index:9999 + } +#nextpage a + { + color: #33f; + } +.rtlink + { + display: block; + width: 100%; + text-align: right; + } +#d_clip_container + { + display: inline-block; + } +#d_clip_button, #clear + { + font-family: Lucida Sans Unicode, Lucida Grande, sans-serif; + color: #333; + font-size: 13px; + line-height: 13px; + text-align: center; + margin: 2px; padding: 5px; + display: inline-block; + border-top: 1px solid #888; + border-left: 1px solid #888; + border-right: 1px solid #555; + border-bottom: 1px solid #333; + background-image: linear-gradient(bottom, rgb(235,235,235) 0%, rgb(250,250,250) 53%); + background-image: -o-linear-gradient(bottom, rgb(235,235,235) 0%, rgb(250,250,250) 53%); + background-image: -moz-linear-gradient(bottom, rgb(235,235,235) 0%, rgb(250,250,250) 53%); + background-image: -webkit-linear-gradient(bottom, rgb(235,235,235) 0%, rgb(250,250,250) 53%); + background-image: -ms-linear-gradient(bottom, rgb(235,235,235) 0%, rgb(250,250,250) 53%); + background-image: -webkit-gradient( linear, left bottom, left top, color-stop(0, rgb(235,235,235)), color-stop(0.53, rgb(250,250,250))); + } +#d_clip_button.hover, #clear:hover + { + color: #555; + background-image: linear-gradient(bottom, rgb(245,240,245) 0%, rgb(255,255,255) 78%); + background-image: -o-linear-gradient(bottom, rgb(245,240,245) 0%, rgb(255,255,255) 78%); + background-image: -moz-linear-gradient(bottom, rgb(245,240,245) 0%, rgb(255,255,255) 78%); + background-image: -webkit-linear-gradient(bottom, rgb(245,240,245) 0%, rgb(255,255,255) 78%); + background-image: -ms-linear-gradient(bottom, rgb(245,240,245) 0%, rgb(255,255,255) 78%); + background-image: -webkit-gradient( linear, left bottom, left top, color-stop(0, rgb(245,240,245)), color-stop(0.78, rgb(255,255,255))); + } +#d_clip_button.active, #clear:active + { + color: #111; + border-top: 1px solid #333; + border-left: 1px solid #555; + border-right: 1px solid #555; + border-bottom: 1px solid #333; + background-image: linear-gradient(bottom, rgb(194,194,194) 0%, rgb(235,235,235) 53%); + background-image: -o-linear-gradient(bottom, rgb(194,194,194) 0%, rgb(235,235,235) 53%); + background-image: -moz-linear-gradient(bottom, rgb(194,194,194) 0%, rgb(235,235,235) 53%); + background-image: -webkit-linear-gradient(bottom, rgb(194,194,194) 0%, rgb(235,235,235) 53%); + background-image: -ms-linear-gradient(bottom, rgb(194,194,194) 0%, rgb(235,235,235) 53%); + background-image: -webkit-gradient( linear, left bottom, left top, color-stop(0, rgb(194,194,194)), color-stop(0.53, rgb(235,235,235))); + } + +.pulsate_and_grow { + -webkit-animation: pulsate_and_grow 0.5s ease-out; + -webkit-animation-iteration-count: 3; + opacity: 1.0; +} +@-webkit-keyframes pulsate_and_grow { + 0% {-webkit-transform: scale(0.1, 0.1); opacity: 0.1;} + 50% {opacity: 1.0;} + 100% {-webkit-transform: scale(1.0, 1.0); opacity: 0.1;} +} +.tag-options:active, .tag-clear:active { + -webkit-animation: pulsate_opacity 0.5s ease-out; + -webkit-animation-iteration-count: 3; + opacity: 1.0; + +} +.sorting-options:active { + -webkit-animation: pulsate_opacity 0.5s ease-out; + -webkit-animation-iteration-count: 3; + opacity: 1.0; + +} +@-webkit-keyframes pulsate_opacity{ + 0% { opacity: 0.1;} + 50% {opacity: 1.0;} + 100% {opacity: 0.1;} +} diff --git a/htmljs/gallery/gallery_main.js b/htmljs/gallery/gallery_main.js new file mode 100644 index 0000000..dedc9eb --- /dev/null +++ b/htmljs/gallery/gallery_main.js @@ -0,0 +1,156 @@ +var imagedata = [ ]; //FIXME post request here +$(function(){ + for (var i=0; i< (imagedata.length - 1); i++){ + var newDiv = document.createElement("div"); + var newImage = document.createElement("img"); + newImage.src = imagedata[i][0]; + newImage.className = "pb"; + newDiv.appendChild(newImage); +// consider calling something like isotope add here...and ONLY APPENDING the image to the parent div once it has loaded +// $(newImage).load(function(){ +// console.log("like a true playa"); +// }) + $("#images").append(newDiv); + } +}); + +$(function(){ + $("b").addClass("pulsate_and_grow"); + $(".sorting-options").click(function(){ + + $(".sorting-options").click(function(){ + console.log("wazzup"); + if ($(this).hasClass("pulsate_opacity")){ + $(this).removeClass("pulsate_opacity"); + } + $(this).addClass("pulsate_opacity"); + + }); + }); +}); +$(function(){ +ZeroClipboard.setMoviePath( 'http://asdf.us/swf/ZeroClipboard10.swf' ); +var clip = new ZeroClipboard.Client(); +clip.glue( 'd_clip_button' ); +var Dump = + { + pick: function () + { + Dump.pickUrl( $(this).attr("src") ) + }, + pickUrl: function (url) + { + $("#rebus").append ($ ("<img>").attr ("src", url)) + $("#rebus").show() + var theDump = $("#urlz").val() + " " + url + $("#urlz").val( theDump ) + clip.setText( theDump ) + return false + }, + clear: function () + { + $("#rebus").html("") + $("#urlz").val("") + clip.setText("") + }, + backspace: function () + { + $("#rebus img:last").remove() + var urllist = $("#urlz").val().split(" ") + urllist.pop() + $("#urlz").val( urllist.join(" ") ) + }, + reverse: function () + { + urllist = $("#urlz").val().split(" ") + Dump.clear() + for (i in urllist.reverse()) + if (urllist[i]) + Dump.pickUrl(urllist[i]) + }, + showNewer: function() + { + window.location.href = //FIXME + }, + showOlder: function() + { + window.location.href = //FIXME + } + } +function applyTag(tagname){ + tag_regex = /&tag=[^&]*/; + if (document.URL.match(tag_regex)){ + return document.URL.replace(tag_regex, "&tag="+tagname); + }else if(document.URL.match(/\/$/)){ + return document.URL.replace(/\/$/, "?tag="+tagname); + } + else{ + return document.URL+"&tag="+tagname; + } +} +var Main = + { + editing: false, + kp: function (event) + { + console.log(event.keyCode); + switch (event.keyCode) + { + // BS + case 8: + if (! Main.editing) + Dump.backspace() + return false + // C + case 67: + if (! Main.editing) + Dump.clear() + break + // R + case 82: + if (! Main.editing) + Dump.reverse() + break + // ESC + case 27: + // H + case 72: + if (! Main.editing) + $("#rebus").toggle() + break + // LEFT ARROW + case 37: + if (! Main.editing) + Dump.showNewer() + break + // RIGHT ARROW + case 39: + if (! Main.editing) + Dump.showOlder() + break + } + return true + }, + poll: function () + { + }, + pollCallback: function () + { + }, + init: function () + { + $(document).keydown(Main.kp) + $("#urlz").focus(function(){ Main.editing = true }) + $("#urlz").blur(function(){ Main.editing = false }) + $("#clear").live("click", Dump.clear) + $("#help").click(function(){ $("#keys").slideToggle() }) + $("#actions b").click(function(){ $("#sorting-optionsContainer").slideToggle() }) + $("#tags b").click(function(){ $("#tag-optionsContainer").slideToggle() }) + $(".tag-options").click(function(){document.location.href= applyTag(this.id)}); + $(".tag-clear").click(function(){ document.location.href = document.URL.replace(/&?tag=[^&]*/ ,"").replace(/\?$/,"")}); + $("div img").live("click", Dump.pick) + Dump.clear() + } + } + Main.init() +}) diff --git a/htmljs/gallery/index.html b/htmljs/gallery/index.html new file mode 100644 index 0000000..5cbc43f --- /dev/null +++ b/htmljs/gallery/index.html @@ -0,0 +1,97 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> +<head> +<title>PHOTOBLASTER GALLERY</title> +<script type="text/javascript" src="/js/jquery.js"></script> +<script type="text/javascript" src="/js/titleScrambler.js"></script> +<script type="text/javascript" src="/js/gallery_main.js"> </script> +<script type="text/javascript" src="/js/jquery.isotope.min.js"></script> +<script type="text/javascript" src="/js/gallery_isotope_config.js?v=3"></script> +<link href="/im/gallery_main.css" type="text/css" rel="stylesheet" /> +<link href="/im/gallery_style.css" type="text/css" rel="stylesheet" /> +</head> +<body> +<div id="help"> + <b>key controls</b> + <div id="keys"> + <br/> + <div class="small"><i>when composer is launched...</i></div> + <div>ESC toggle</div> + <div>C clear</div> + <div>R reverse</div> + <br/> + <div class="small"><i>in the gallery...</i></div> + <div>BACKSPACE delete</div> + <div>LEFT ARROW newer</div> + <div>RIGHT ARROW older</div> + </div> +</div> +<div id="actions"> + <b>sort</b> + <div id="sorting-optionsContainer"> + <div class="sorting-options" id="date">date</div> + <div class="sorting-options" id="username">username</div> + <div class="sorting-options" id="height">height</div> + <div class="sorting-options" id="width">width</div> + <div class="sorting-options" id="gif">gif</div> + <div class="sorting-options" id="shuffle">shuffle</div> + <div class="sorting-options" id="lombada">lombada</div> + </div> +</div> +<div id="tags"> + <b>tags</b> + <div id="tag-optionsContainer"> + + + <div class="tag-clear" ><i>remove tag</i></div> + <div class="tag-options" id="transparent">transparent</div> + <div class="tag-options" id="break">break</div> + <div class="tag-options" id="gradient">gradient</div> + <div class="tag-options" id="grid">grid</div> + <div class="tag-options" id="shader">shader</div> + <div class="tag-options" id="Over">Over</div> + <div class="tag-options" id="ATop">ATop</div> + <div class="tag-options" id="Dst_Over">Dst_Over</div> + <div class="tag-options" id="Dst_In">Dst_In</div> + <div class="tag-options" id="Dst_Out">Dst_Out</div> + <div class="tag-options" id="Multiply">Multiply</div> + <div class="tag-options" id="Screen">Screen</div> + <div class="tag-options" id="Divide">Divide</div> + <div class="tag-options" id="Plus">Plus</div> + <div class="tag-options" id="Difference">Difference</div> + <div class="tag-options" id="Exclusion">Exclusion</div> + <div class="tag-options" id="Lighten">Lighten</div> + <div class="tag-options" id="Darken">Darken</div> + <div class="tag-options" id="Overlay">Overlay</div> + <div class="tag-options" id="Hard_Light">Hard_Light</div> + <div class="tag-options" id="Soft_Light">Soft_Light</div> + <div class="tag-options" id="Pegtop_Light">Pegtop_Light</div> + <div class="tag-options" id="Linear_Light">Linear_Light</div> + <div class="tag-options" id="Vivid_Light">Vivid_Light</div> + <div class="tag-options" id="Pin_Light">Pin_Light</div> + <div class="tag-options" id="Linear_Dodge">Linear_Dodge</div> + <div class="tag-options" id="Linear_Burn">Linear_Burn</div> + <div class="tag-options" id="Color_Dodge">Color_Dodge</div> + <div class="tag-options" id="Color_Burn">Color_Burn</div> + + </div> +</div> +<div id="dump"> + <div id="rebus"></div> + <input id="urlz" type="text" /> + <div id="d_clip_container" style="position:relative"> + <div id="d_clip_button">copy</div> + </div> + <button id="clear">clear</button> +</div> +<div id="images"> +<div id='nextpage'> + <a href='/im/'>editor</a> + <a href='?%s'>← newer</a> + <a href='?random=%d%s'>random</a> + <a href='?%s'>older →</a> +</div> +</body> +<script type="text/javascript" src="/js/ZeroClipboard.js"></script> +<script type="text/javascript" src="http://asdf.us/js/pbembed.js"></script> +</html> diff --git a/install/database/database_create_statements b/install/database/database_create_statements new file mode 100644 index 0000000..9fa9e39 --- /dev/null +++ b/install/database/database_create_statements @@ -0,0 +1,3 @@ +create database if not exists asdfus; +grant all privileges on asdfus.* to asdfus@localhost identified by 'gTYgT&M6q'; +flush privileges; diff --git a/install/database/database_schema.sql b/install/database/database_schema.sql new file mode 100644 index 0000000..802aa49 --- /dev/null +++ b/install/database/database_schema.sql @@ -0,0 +1,15 @@ +CREATE TABLE if not exists `im_cmd` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `date` int(11) DEFAULT NULL, + `remote_addr` varchar(16) DEFAULT NULL, + `name` varchar(16) DEFAULT NULL, + `url` varchar(256) DEFAULT NULL, + `dir` varchar(2) DEFAULT NULL, + `oldfile` varchar(256) DEFAULT NULL, + `newfile` varchar(256) DEFAULT NULL, + `cmd` blob, + `dataobj` blob, + `tag` varchar(50) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=MyISAM AUTO_INCREMENT=386739 DEFAULT CHARSET=utf8; + diff --git a/pb/Breaker/__init__.py b/pb/Breaker/__init__.py new file mode 100755 index 0000000..9f5d31b --- /dev/null +++ b/pb/Breaker/__init__.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/__init__.py b/pb/Config/__init__.py new file mode 100644 index 0000000..b849b9e --- /dev/null +++ b/pb/Config/__init__.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/Generate/__init__.py b/pb/Generate/__init__.py new file mode 100755 index 0000000..fbb74e3 --- /dev/null +++ b/pb/Generate/__init__.py @@ -0,0 +1,231 @@ +#!/usr/bin/python2.7 +import sys +import os + +from pb.Config import * +import pb.lib.Utils as utils + +#FIXME these guys can do stuff wider than 1000 +LIKE_A_BOSS = "ryz pepper seamonkey JAMES".split(" ") +DEFAULT_FINALFORMAT = "gif" +DEFAULT_TAG = "im"; + +GRAVITY_PARAMS = ["NorthWest","North","NorthEast","West","Center","East","SouthWest","South","SouthEast"] +GRAVITY_DEFAULT = "Center" +FORMAT_PARAMS = ["jpg", "gif", "png"] +COMPOSE_PARAMS = [ "Over", "ATop", "Dst_Over", "Dst_In", "Dst_Out", "Multiply", + "Screen", "Divide", "Plus", "Difference", "Exclusion", + "Lighten", "Darken", "Overlay", "Hard_Light", "Soft_Light", + "Linear_Dodge", "Linear_Burn", "Color_Dodge", "Color_Burn" ] +DISPOSE_PARAMS = ["None","Previous","Background"] +DISPOSE_DEFAULT = "None" + +def debuglog(s): + sys.stderr.write(str(s) + "\n"); + +class Generate(): + def __init__(self, **kwargs): + self.params = {} + self.now = utils.now() + self.files_created = [] + self.commands = []; + self._required_keys = [ +#{{{ required_keys + #IMAGES + "url", + "background", + + #BOOLS + "coalesce", + "dispose", + "nearest", + "merge_early", + "flip", + "flop", + "tile", + "transparent", + + #COLORS + "black", + "white", + "subtract", + + #INTS + "fuzz", + "width", + "height", + "brightness", + "contrast", + "saturation", + "rotate", + "hue", + + #ENUMS + "compose", + "gravity", + "format", + + #STRINGS + "username", + "callback", +#}}} + ] + for k in self._required_keys: + if k in kwargs: + if k in [ 'url', 'background' ] and kwargs[k] != "" and kwargs[k] != None: + 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']) + except Exception as e: + sys.stderr.write(str(e)) + raise Exception ("BAD PARAMS"); + elif k in [ 'black', 'white', 'subtract' ]: + try: + self.params[k] = utils.is_color(kwargs[k]) + except Exception: + raise Exception("Unable to process color for:\n{}".format(k)) + elif k in [ + "coalesce", "dispose", "nearest", "merge_early", + "flip", "flop", "tile", "transparent", + ]: + self.params[k] = utils.bool_correct(utils.sanitize(kwargs[k])) + elif k == 'gravity' and self._test_enum(kwargs[k], GRAVITY_PARAMS): + self.params[k] = kwargs[k] + elif k == 'format' and self._test_enum(kwargs[k], FORMAT_PARAMS): + self.params[k] = kwargs[k] + elif k == 'compose' and self._test_enum(kwargs[k], COMPOSE_PARAMS): + self.params[k] = kwargs[k] + elif k == 'dispose' and self._test_enum(kwargs[k], DISPOSE_PARAMS): + self.params[k] = kwargs[k] + elif k in [ "fuzz", "width", "height", "brightness", "contrast", "saturation", "rotate", "hue" ]: + if kwargs[k] == '': + self.params[k] = None + else: + try: + self.params[k] = str(int(kwargs[k])) + except Exception as e: + raise Exception("Problem with param {}:\n".format(k) + str(e)) + else: + + + self.params[k] = utils.sanitize(kwargs[k]) + + if self.params.get('background'): + self.tag = self.params.get('compose') + else: + self.tag = self.params.get('transparent', DEFAULT_TAG) + + self.basename = self._get_filename(); + self.filename = "{}.{}".format(self.basename, self.params.get('format', DEFAULT_FINALFORMAT)) + self.filepath = os.path.join(WORKING_DIR, self.filename) + + def _make_tempname(self, s): + return "PBTMP{}{}".format(self.now, s); + + def _test_enum(self, e, arr): + if e in arr: return True + raise Exception ("Bad value: {}".format(e)) + + def _get_filename(self): + return "{}_{}_{}".format( + self.tag, + self.now, + self.params.get('username',"") + ); + + 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 _cleanup(self): + if not len(self.files_created): + pass + cmd = ["rm", "-f"] + self.files_created + self._call_cmd(cmd) + + def _composite (self): + cmd = [ + BIN_CONVERT, self.params['background']['path'], + "null:", self.filepath, "-matte", + "-dispose", self.params.get('dispose', DISPOSE_DEFAULT), + "-gravity", self.params.get("gravity",GRAVITY_DEFAULT), + "-compose", self.params['compose'], "-layers", "composite", + self.filepath ] + self._call_cmd(cmd); + + def _convert(self): + cmd = [BIN_CONVERT, self.params['url']['path'] ] + if self.params.get('rotate'): cmd += ["-rotate", self.params['rotate'] ] + if self.params.get('flip'): cmd += ["-flip"] + if self.params.get('flop'): cmd += ["-flop"] + if self.params.get('transparent'): + if self.params.get('fuzz'): + cmd += ["-fuzz", "{}%".format(self.params['fuzz']) ] + cmd += [ "-transparent", self.params.get('subtract', "white") ] + if self.params.get('width') or self.params.get('height'): + if self.params.get('nearest') and self.params.get('format') == "gif": + cmd += [ "-coalesce","+map","-interpolate","Nearest","-interpolative-resize" ] + else: + cmd.append("-resize") + cmd += [ "{}x{}".format(self.params.get('width',"") or "", self.params.get('height',"") or "") ] + if self.params.get('black') != "black" or self.params.get('white') != 'white': + cmd += [ "+level-colors" , "{},{}".format(self.params.get('black','black'), self.params.get('white', 'white')) ] + if self.params.get('contrast'): cmd += [ '-contrast-stretch', self.params['contrast'] ] + if any( e in self.params.keys() for e in ['brightness', 'saturation', 'hue' ]): + cmd += [ + "-modulate", "{},{},{}".format( + (self.params.get('brightness', 100) or 100), + (self.params.get('contrast', 100) or 100), + (self.params.get('hue', 100) or 100) + )] + cmd.append("-coalesce"); #why? #FIXME + cmd += [ self.filepath ]; + self._call_cmd(cmd); + + def create(self): + self._convert() + if self.params.get('background'): + self._composite() + self._cleanup(); + +if __name__ == "__main__": + + TEST_PARAMS = { + 'nearest': 'true', +# 'height': None, + 'compose': 'Soft_Light', + 'coalesce': 'true', + 'dispose': 'None', + 'gravity': 'Center', + 'width': '200', + 'black': 'black', + 'tile': 'true', + 'white': 'white', + 'contrast': '100', + 'hue': '90', + 'saturation': '100', + 'merge_early': 'true', + 'format': 'gif', + 'background': 'http://i.asdf.us/im/bc/new_1430440747.gif', + 'subtract': '#EE7AE9', + 'transparent': 'true', +# 'rotate': None, + 'name': 'yo', +# 'brightness': None, + 'url': 'http://asdf.us/im/new.gif', + 'flop': 'true', + 'flip': 'false', + 'callback': 'jsonp1430442384162', + 'fuzz': '5' + } + g = Generate(**TEST_PARAMS); + g.create() diff --git a/pb/Gradient/__init__.py b/pb/Gradient/__init__.py new file mode 100755 index 0000000..aeb4d21 --- /dev/null +++ b/pb/Gradient/__init__.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/__init__.py b/pb/Imgrid/__init__.py new file mode 100755 index 0000000..d74029d --- /dev/null +++ b/pb/Imgrid/__init__.py @@ -0,0 +1,232 @@ +#!/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] = utils.is_color(kwargs[k]) + except Exception: + sys.stderr.write(kwargs[k] + "\n") + raise Exception("Unable to process color for:\n{}".format(k)) + elif k == 'opacity': + self.params[k] = str(float(kwargs[k])) + elif k == 'zoom': + self.params[k] = int(float(kwargs[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/Imlandscape/__init__.py b/pb/Imlandscape/__init__.py new file mode 100755 index 0000000..24cee8d --- /dev/null +++ b/pb/Imlandscape/__init__.py @@ -0,0 +1,101 @@ +#!/usr/bin/python2.7 +import os +import sys +import random +import re +import pb.lib.Utils as utils +import urllib +import urlparse +from pb.Config import * + +import base64 +import time +import string +import urllib +from subprocess import Popen, PIPE +import sha +import simplejson as json + +import mimetypes + +class Imlandscape(object): + def __init__(self, **kwargs): + self.params = {} + self.tag = "imlandscape" + self.commands = []; + self._required_keys = [ + "heightmap", + "imgdata", + "texture", + "name", + ] + self.now = utils.now() + self.files_created = [] + for k in self._required_keys: + if k in kwargs: + self.params[k] = kwargs[k] + else: + self.params[k] = utils.bool_correct(utils.sanitize(kwargs[k])) + + def _filename_from_url (self, url, name=""): + if "?" in url: + url = url.split("?")[0] + if "/" in url: + url = urllib.unquote(url).replace(" ","") + filename = url.split("/")[-1] + filetype = "png" + filename = sanitize(filename[:-4]) + else: + filename = "" + if name != "": + name = name+"_" + return "{}_{}{}_{}.{}".format("imlandscape", name, filename,self.now, "png") + + def _saveImgData(self, imgdata, filename): + try: +# up = urlparse.urlparse(url) +# head, data = imgdata.split(',', 1) +# bits = head.split(';') + parts = imgdata.split(';') + mime_type = parts[0] if parts[0] else 'text/plain' + data = parts[1] + charset, b64 = 'ASCII', False + for part in parts[1]: + if part.startswith('charset='): + charset = part[8:] + elif part == 'base64': + b64 = True + + # Do something smart with charset and b64 instead of assuming + if b64: + plaindata = base64.b64decode(data) + else: + plaindata = data + with open(filename, 'wb') as f: + f.write(plaindata) + except Exception as e: + sys.stderr.write("ERROR: {}\n".format(str(e))); + +# def _cleanup(self): +# cmd = ["rm"]+self.files_created +# self._call_cmd(cmd) + + def create(self, breakmode=""): + self.filepath = self._filename_from_url(self.params.get('texture',""), self.params.get('name',"")); + self._saveImgData(self.params.get('imgdata'), self.filepath); + +if __name__ == "__main__": + f = open("/tmp/base64img") + data = f.read() + f.close() + TEST_PARAMS = { + "heightmap" : "https%3A%2F%2Fwww.google.com%2Fimages%2Fsrpr%2Flogo11w.png" , + "imgdata" : data, + "texture" : "https%3A%2F%2Fwww.google.com%2Fimages%2Fsrpr%2Flogo11w.png", + "name" : "pepper", + } + b = Imlandscape(**TEST_PARAMS) + b.create(); + print b.filepath + + diff --git a/pb/Imlandscape/landscape b/pb/Imlandscape/landscape new file mode 100755 index 0000000..10e8ede --- /dev/null +++ b/pb/Imlandscape/landscape @@ -0,0 +1,163 @@ +#!/usr/bin/python2.7 +import cgi +import sys +import os +import re +import time +import string +import urllib +from subprocess import Popen, PIPE +import sha +import simplejson as json + +import mimetypes +import s3 + +import db +DB = db.db () + +import base64 +import urlparse + + +AWS_ACCESS_KEY_ID = 'AKIAIR53VPBXKJMXZIBA' +AWS_SECRET_ACCESS_KEY = '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' +BUCKET_NAME = 'i.asdf.us' +BASE_PATH = "/var/www/asdf.us/httpdocs/imlandscape" +BASE_URL = "http://i.asdf.us/" +PARAM_LIST = "heightmap texture name imgdata filename" +BIN_IDENTIFY = "/usr/bin/identify" + +print "Content-type: text/plain" +print "" +def insert_cmd (dir, newfile, name, texture, dataobj): + if texture == "": + texture = "NULL" + try: + sql = "INSERT INTO im_cmd (date,remote_addr,name,url,dir,oldfile,newfile,cmd, dataobj, tag) VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s, %s)" + + args = (now(), "NULL", name, texture, dir, "NULL", newfile, "NULL", dataobj, "imlandscape") + DB.execute(sql, args) + except (): + return + +def hash_dir (s): + return sha.new(s).hexdigest()[:2] + +def bin_identify (filename): + ident = Popen([BIN_IDENTIFY, filename], stdout=PIPE).communicate()[0] + partz = ident.split(" ") + width,height = partz[2].split("x") + return width, height + +def get_params (paramlist): + paramkeys = paramlist.split() + form = cgi.FieldStorage() + params = {} + for key in paramkeys: + if key in form: + if key == 'heightmap': + params[key] = form[key].value + elif key == 'imgdata': + params[key] = form[key].value + elif key == 'texture': + params[key] = form[key].value + else: + params[key] = sanitize(form[key].value) + else: + params[key] = None + return params + +def error (e): + print "#@imlandscape" + print "ERROR\t"+e + sys.exit() + +def now (): + return int(time.mktime(time.localtime())) + +def sanitize (str): + return re.sub(r'\W+', '', str) + +def filename_from_url (url, name=""): + if "?" in url: + url = url.split("?")[0] + if "/" in url: + url = urllib.unquote(url).replace(" ","") + filename = url.split("/")[-1] + filetype = "png" + filename = sanitize(filename[:-4]) + else: + filename = "" + if name != "": + name = name+"_" + return "{}_{}{}_{}.{}".format("imlandscape", name, filename,now(), "png") + +def saveImgData(url, filename): + try: + up = urlparse.urlparse(url) + head, data = up.path.split(',', 1) + bits = head.split(';') + mime_type = bits[0] if bits[0] else 'text/plain' + charset, b64 = 'ASCII', False + for bit in bits[1]: + if bit.startswith('charset='): + charset = bit[8:] + elif bit == 'base64': + b64 = True + + # Do something smart with charset and b64 instead of assuming + plaindata = base64.b64decode(data) + + with open(filename, 'wb') as f: + f.write(plaindata) + except Exception as e: + error(str(e)); + +def file_size (file): + return os.stat(file)[6] + +def moveToS3(filename,objectname): + conn = s3.AWSAuthConnection(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) + sys.stderr.write( "Uploading %s" % filename) + filedata = open(filename, 'rb').read() + content_type = mimetypes.guess_type(filename)[0] + if not content_type: + content_type = 'text/plain' + conn.put(BUCKET_NAME, objectname, s3.S3Object(filedata), + {'x-amz-acl': 'public-read', 'Content-Type': content_type, 'x-amz-storage-class': 'REDUCED_REDUNDANCY'}) + +param = get_params(PARAM_LIST) +if param['imgdata'] is None: + error("no imgdata") +url = param['imgdata'] +if param['texture'] is None: + param['texture'] = ""; +if param['heightmap'] is None: + param['heightmap'] = ""; +if param['name'] is None: + param['name'] = ""; + +dataobj = json.dumps({ + 'texture' : param['texture'], + 'heightmap' : param['heightmap'], + 'name' : param['name'] +}) + +dir = hash_dir(param['imgdata']); + +filename = filename_from_url(param['texture'], param['name']); + +tag = "imlandscape" +objectname = "im/"+dir+"/"+filename + +saveImgData(param['imgdata'], filename); + +print "#@imlandscape" +#print ", ".join([k+"="+str(v) for k,v in param.iteritems()]) +print file_size (filename) +print bin_identify (filename) +print BASE_URL+objectname +insert_cmd(dir, filename, param['name'], param['texture'], dataobj); +moveToS3(filename, objectname); +os.remove(filename); diff --git a/pb/Pattern/__init__.py b/pb/Pattern/__init__.py new file mode 100755 index 0000000..b1fb9af --- /dev/null +++ b/pb/Pattern/__init__.py @@ -0,0 +1,172 @@ +#!/usr/bin/python2.7 +import os +import sys +import random +import re +import urllib +from pb.Config import * +import pb.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() + diff --git a/pb/lib/db.py b/pb/lib/Db/__init__.py index 66db35d..91b0fcf 100755..100644 --- a/pb/lib/db.py +++ b/pb/lib/Db/__init__.py @@ -1,4 +1,5 @@ import MySQLdb +import time USER = "asdfus" PASSWORD = "gTYgT&M6q" DATABASE = "asdfus" diff --git a/pb/lib/utils.py b/pb/lib/Utils/__init__.py index 5ca5b3e..1d5a708 100644 --- a/pb/lib/utils.py +++ b/pb/lib/Utils/__init__.py @@ -1,5 +1,5 @@ import re -from pb.config import * +from pb.Config import * import time import urllib import urllib2 diff --git a/pbserver.py b/pbserver.py new file mode 100755 index 0000000..a0068e9 --- /dev/null +++ b/pbserver.py @@ -0,0 +1,215 @@ +#!/usr/bin/python2.7 +from bottle import route, run, post, request, static_file + +from pb.Gradient import Gradient +from pb.Imgrid import Imgrid +from pb.Breaker import Breaker +from pb.Pattern import Pattern +from pb.Generate import Generate +from pb.Imlandscape import Imlandscape + +from pb.Config import AWS_SECRET_ACCESS_KEY, AWS_ACCESS_KEY_ID, BUCKET_NAME, BIN_IDENTIFY +import pb.lib.Utils as utils + +import os +import sys +from pb.lib.Db import Db + +import sha +from subprocess import call, Popen, PIPE +import simplejson as json + +from boto.s3.connection import S3Connection +from boto.s3.key import Key +# +try: + db = Db(); +except Exception as e: + sys.stderr.write("Could not connect to db:\n{}".format(e)) + sys.exit(1); +BASE_URL = "http://i.asdf.us" + +def hashdir(filename): + return sha.new(filename).hexdigest()[:2] + +def bin_identify (filepath): + ident = Popen([BIN_IDENTIFY, filepath], stdout=PIPE).communicate()[0] + partz = ident.split(" ") + width,height = partz[2].split("x") + return [ width, height ] + +def cleanup(filepath): + try: + call(['rm', filepath]) + except Exception as e: + sys.stderr.write(str(e)) + raise + +def s3move(filename,objectname): + try: + conn = S3Connection(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, is_secure=False) + b = conn.get_bucket(BUCKET_NAME) + k = Key(b) + k.key = objectname + k.set_contents_from_filename(filename) + k.set_acl('public-read') + k.storage_class = 'REDUCED_REDUNDANCY' + except Exception as e: + sys.stderr.write(str(e)); + raise(e) + +def format_im_data(im, insert_url="NULL"): + directory = hashdir(im.filename) + dimensions = bin_identify(im.filepath) + size = utils.file_size(im.filepath) + objectname = "im/{}/{}".format(directory, im.filename) + try: + s3move(im.filepath, objectname) + cleanup(im.filepath) + db.insert_cmd( + date=im.now, + remote_addr=request.environ.get('REMOTE_ADDR', "NULL"), + username=im.params.get('username', "NULL"), + url=insert_url, + directory=directory, + oldfile="NULL", + newfile=im.filename, + dataobj=";".join(im.commands), + cmd=json.dumps(im.params), + tag=im.tag, + ) + return json.dumps({ + 'url' : "{}/{}".format(BASE_URL, objectname), + 'size' : size, + 'width' : "{}px".format(dimensions[0]), + 'height' : "{}px".format(dimensions[1]), + }) + except Exception as e: + sys.stderr.write(str(e)) + raise; + + +def return_image(im, insert_url="NULL"): + return format_im_data(im, insert_url) + + +def return_jsonp(im, insert_url="NULL"): + return "{}({})".format(im.get("callback"), format_im_data(im, insert_url)) + + +@post('/im/api/imgradient') +def gradient(): + try: + im = Gradient(**(dict(request.forms))) + im.create(); + return return_image(im) + except Exception as e: + sys.stderr.write("imgradient failure\n") + sys.stderr.write("params:\n") + for i in request.forms: + sys.stderr.write("{}:{}\n".format(i, request.forms[i])) + raise; + return json.dumps({ 'error' : 'Request could not be processed' }) + +@post('/im/api/imgrid') +def imgrid(): + try: + im = Imgrid(**(dict(request.forms))) + im.create(); + url= "NULL" + for elem in [ im.params.imageinstead , im.params.bgimage, im.params.planebgimage ]: + if elem: + url = elem['url'] + break + return return_image(im, url) + except Exception as e: + sys.stderr.write(str(e)) + return json.dumps({ 'error' : 'Request could not be processed' }) + +@post('/im/api/generate') +def generate(): + try: + im = Generate(**(dict(request.forms))) + im.create(); + return return_image(im) + except Exception as e: + sys.stderr.write(str(e)) + return json.dumps({ 'error' : 'Request could not be processed' }) + +@post('/im/api/imbreak') +def breaker(): + try: + im = Breaker(**(dict(request.forms))) + im.create(); + return return_image(im, im.params['url']) + except Exception as e: + sys.stderr.write(str(e)) + return json.dumps({ 'error' : 'Request could not be processed' }) + +@post('/im/api/impattern') +def pattern(): + try: + im = Pattern(**(dict(request.forms))) + im.create(); + return return_image(im, im.params['image_url']) + except Exception as e: + sys.stderr.write(str(e)+"\n") + for i in request.forms: + sys.stderr.write("{}:{}\n".format(i, request.forms[i])) + raise; + return json.dumps({ 'error' : 'Request could not be processed' }) + +@post('/im/api/imlandscape') +def imlandscape(): + try: + im = Imlandscape(**(dict(request.forms))) + im.create(); + sys.stderr.write(str(im.params)) + return return_image(im, im.params['texture']) + except Exception as e: + sys.stderr.write(str(e)) + return json.dumps({ 'error' : 'Request could not be processed' }) + + + +#static routes +@route('/im/<filename>') +def server_static(filename): + return static_file(filename, root='frontend/im/') +@route('/im') +def server_static(): + return static_file("index.html", root='frontend/im/') +@route('/imgrid') +def server_static(): + return static_file("index.html", root='frontend/imgrid/') +@route('/imgradient') +def server_static(): + return static_file("index.html", root='frontend/imgradient/') +@route('/imlandscape') +def server_static(): + return static_file("index.html", root='frontend/imlandscape/') +@route('/impattern') +def server_static(): + return static_file("index.html", root='frontend/impattern/') +@route('/imbreak') +def server_static(): + return static_file("index.html", root='frontend/imbreak/') +@route('/') +def server_static(): + return static_file("index.html", root='frontend/im/') +@route('/css/<filename>') +def server_static(filename): + return static_file(filename, root='frontend/css/') +@route('/js/<filename>') +def server_static(filename): + return static_file(filename, root='frontend/js/') +@route('/img/<filename>') +def server_static(filename): + return static_file(filename, root='frontend/img/') + + +#run(host='0.0.0.0', server='flup', port=8999, debug=True) +run(host='0.0.0.0', port=8999, debug=True) + + + diff --git a/tests/jsonptest.sh b/tests/jsonptest.sh new file mode 100644 index 0000000..8539853 --- /dev/null +++ b/tests/jsonptest.sh @@ -0,0 +1,2 @@ +#!/bin/bash +curl 'http://asdf.us/cgi-bin/im/generate?callback=jsonp1431361693303&url=http%3A%2F%2Fwww.maskworld.com%2Fpix%2Fmasks%2F025-party-face-gesicht-fasching-karneval-carnival-halloween-latex-film-movie-larp-theatre-theater-horror-rubber-gummi-mask-masks-maske-masken.jpg&name=test&transparent=true&fuzz=5' -H 'Accept-Encoding: gzip, deflate, sdch' -H 'Accept-Language: en-US,en;q=0.8' -H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36' -H 'Accept: */*' -H 'Referer: http://carbonpictures.com/pb/' -H 'Cookie: imname=yo' -H 'Connection: keep-alive' --compressed |
