#!/usr/bin/env python
#
# "blogava" - blogger avatar fetch, and gradient data URI generator
# v1 2.11.2011
# v2 ..
# v3 11.11.2011
# v4 14.11.2011
# ?c1=f8dd99&c2=eeaa00&w=1&h=20 create gradient png
# optional: ?download or ?js[=variablename]
# v4.01 14.12.2011 -
# blogger new profile page default profile without server-part,
# added code to deal with that
# v4.02 18.12.2011 -
# increased timeout 5->15 in blogger profile pages, handle downloaderrors
# v5 11.3.2012 -
# uses now python27 and lxml/etree/xpath to parse profile pages (is much
# better that regular expressions)
#
# -----------------------------------------------------------------------------
#
# fetch profile image from blogger user id. usage:
# http://avafavico.appspot.com/?userid=01234567890
#
# See http://yabtb.blogspot.com/2011/11/google-app-engine-python-application.html
#
# This is like my first python app, so it may not be too shiny... but it works
#
# - MS-potilas
#
import sys
import cgi
import re
import base64
from google.appengine.api import images
from google.appengine.api import urlfetch
from google.appengine.api import memcache
from google.appengine.runtime import DeadlineExceededError
from lxml import etree
#
# png & gradient code based on:
# http://jtauber.com/blog/2008/05/18/creating_gradients_programmatically_in_python/
#
#########################################
def make_png(width, height, rgba_func):
import zlib
import struct
import array
rotate = False
if height < width:
width, height = height, width
rotate = True
def make_chunk(chunk_type, data):
chu = struct.pack(\'!I\', len(data)) + chunk_type + data
checksum = zlib.crc32(data, zlib.crc32(chunk_type))
chu += struct.pack(\'!I\', 0xFFFFFFFF & checksum)
return chu
def frange(x):
for i in xrange(x):
yield i/float(x)
def get_data(width, height, rgba_func):
data = array.array(\'B\')
for y in frange(height):
data.append(0)
for x in frange(width):
data.extend(int(round(v * 255)) for v in rgba_func(x, y))
return zlib.compress(data)
out = array.array(\'B\', [137, 80, 78, 71, 13, 10, 26, 10]).tostring() # PNG signature
color_type = 6 if len(list(rgba_func(0,0))) == 4 else 2
out += make_chunk(\'IHDR\', struct.pack(\'!2I5B\', width, height, 8, color_type, 0, 0, 0))
out += make_chunk(\'IDAT\', get_data(width, height, rgba_func))
out += make_chunk(\'IEND\', \'\')
img = images.Image(out)
img.rotate(270) # rotate it around to optimize
if not rotate: # not rotate -> do full circle
img.rotate(90)
result = img.execute_transforms(output_encoding=images.PNG)
return result
def linear_gradient(start_value, stop_value, start_offset=0.0, stop_offset=1.0):
return lambda offset: (start_value + ((offset - start_offset) / (stop_offset - start_offset) * (stop_value - start_value))) / 255.0
def gradient(segments):
def gradient_function(x, y):
segment_start = 0.0
for segment_end, start, end in segments:
if y < segment_end:
return (linear_gradient(start[i], end[i], segment_start, segment_end)(y) for i in xrange(len(start)))
segment_start = segment_end
return gradient_function
#########################################
def getFavico(domain):
# get favico for domain. first check cache
result = memcache.get(key=domain)
if result is None:
result = fetchUrl("http://www.google.com/s2/favicons?domain=" + domain)
memcache.add(key=domain, value=result, time=14400)
return result
def fetchUrl(url):
url = re.sub("^//", "http://", url)
dline = 5
if re.match("http:\\/\\/www\\.blogger.com\\/profile\\/.+", url):
dline = 15
try:
result = urlfetch.fetch(url, deadline=dline)
except urlfetch.DownloadError:
return None
if result.status_code == 200:
return result.content
return None
#########################################
def main():
form = cgi.FieldStorage(keep_blank_values = True)
c1 = form.getfirst("c1")
c2 = form.getfirst("c2")
w = form.getfirst("w")
h = form.getfirst("h")
if w is None or not re.match(\'\\d+$\', w):
w = 0
if h is None or not re.match(\'\\d+$\', h):
h = 0
if int(w)*int(h) > 4096:
print "Status: 400 Bad Request"
print
print "dimensions (w x h) too large"
return 1
if c1 is not None and c2 is not None:
if re.match(\'[a-f0-9]{6}$\', c1, re.I) and re.match(\'[a-f0-9]{6}$\', c2, re.I) and int(w) > 0 and int(h) > 0:
c1r = int(c1[:2],16)
c1g = int(c1[2:][:2],16)
c1b = int(c1[-2:],16)
c2r = int(c2[:2],16)
c2g = int(c2[2:][:2],16)
c2b = int(c2[-2:],16)
else:
print "Status: 400 Bad Request"
print
print "invalid parameters"
return 1
img = make_png(int(w), int(h), gradient([ (1.0, (c1r, c1g, c1b), (c2r, c2g, c2b)), ]))
if form.getfirst("download") is not None:
print "Content-Type: image/png"
print "Cache-Control: public, max-age=86400"
print
print img
return 0
imgenc = base64.b64encode(img)
if form.getfirst("js") is not None:
var = form.getfirst("js")
if not re.match(\'[a-zA-Z]+$\', var):
var = "result"
print "Content-Type: text/javascript"
print "Cache-Control: public, max-age=86400"
print
print \'var \' + var + \'="\'+imgenc+\'";\'
return 0
imgenc = \'data:image/png;base64,\' + imgenc
print "Content-Type: text/html"
print "Cache-Control: public, max-age=86400"
print
print \'<html><head><link rel="shortcut icon" href="/favicon.ico" /><title>Gradient PNG data URI generator</title></head><body style="text-align:center;">\'
print \'Gradient from #\' + c1 + \' to #\' + c2 + \', width=\'+w+\', height=\'+h+\':<br />\'
print \'<textarea id="pngarea" rows="\' + str(round(len(imgenc)/64+2))+ \'" cols="64">\'+imgenc+\'</textarea>\'
print \'<script>document.getElementById("pngarea").focus();document.getElementById("pngarea").select();</script>\'
print \'<div style="margin:10px"><a title="Click to open data URI" target="_top" href="\'+imgenc+\'"><img src="\'+imgenc+\'" style="padding:10px;border:1px solid #d0d0d0;" /></a></div>\'
print \'Use it for example like this in CSS:<br /><div style="margin-left:auto;margin-right:auto;width:520px;text-align:left;font-family:monospace;word-wrap:break-word;font-size:90%;margin-bottom:12px;">\'
print \'background:url(\'+imgenc+\') 100% 100%;\'
print \'</div>\'
print \'<hr />\'
print \'<div style="font-size:85%">by MS-potilas 2011-2012, see <a target="_top" href="http://yabtb.blogspot.com/2011/11/gradient-png-data-uri-maker-reference.html">yabtb.blogspot.com</a>.</div>\'
print \'</body></html>\'
return 0
userid = form.getfirst("userid")
if userid is None:
print "Content-Type: text/html"
print "Cache-Control: public, max-age=14400"
print
print \'<html><head><link rel="shortcut icon" href="/favicon.ico" /><title>Fetch Blogger Avatar</title></head><body style="text-align:center;">Application to return small icon from Blogger profile. Usage: ?userid=USERID.<br /><hr /><div style="font-size:85%">by MS-potilas 2011-2012, see <a href="http://yabtb.blogspot.com/2011/11/python-tool-to-get-blogger-avatar.html">yabtb.blogspot.com</a>.</div><br />\'
print \'</body></html>\'
return 0
if not re.match(\'\\d+$\', userid):
print "Status: 400 Bad Request"
print
print "invalid userid"
return 1
# first check cache
thedata = memcache.get(key=userid)
if thedata is None:
domain = "www.blogger.com" # fallback favico
url = "http://www.blogger.com/profile/"+userid
result = fetchUrl(url)
if result is not None:
tree = etree.HTML(result)
r=tree.xpath("//img[@id=\'profile-photo\']/@src")
if len(r) == 0:
r=tree.xpath("//img[@class and contains(concat(\' \',normalize-space(@class),\' \'),\' photo \')]/@src")
if len(r) > 0:
found = r[0]
# if profile photo not found, search person\'s first blog\'s address (rel="contributor-to")
if found is None:
r=tree.xpath("//a[@rel and contains(concat(\' \',normalize-space(@rel),\' \'),\' contributor-to \')]/@href")
if len(r) > 0:
found = r[0]
if found is not None:
domain = re.search(\'(http://){0,1}(.+?)[$/]\', found).group(2)
found = None
if found is None:
result = getFavico(domain)
else:
found = re.sub(\'^//\',"http://", found)
if not re.match(\'http\', found):
found = "http://www.blogger.com" + found
result = fetchUrl(found)
# if loading has failed, fallback to domain\'s favico
if result is None:
result = getFavico(domain)
if result is not None:
img = images.Image(result)
if img.width > img.height*1.67:
img.crop(0.2,0.0,0.8,1.0)
elif img.height > img.width*1.67:
img.crop(0.0,0.2,1.0,0.8)
elif img.width > img.height*1.25:
img.crop(0.1,0.0,0.9,1.0)
elif img.height > img.width*1.25:
img.crop(0.0,0.1,1.0,0.9)
img.resize(32,32)
thedata=img.execute_transforms(output_encoding=images.PNG)
memcache.add(key=userid, value=thedata, time=14400)
else:
print "Status: 404 Not Found"
print
print "not found"
return 1 #all fetches failed
print "Content-Type: image/png"
print "Cache-Control: public, max-age=14400"
print
print thedata
return 0
if __name__ == \'__main__\':
main()