mspotilas

avafavico GAE app, new version with python27 and lxml

Mar 12th, 2012
1,771
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 9.46 KB | None | 0 0
  1. #!/usr/bin/env python
  2. #
  3. # "blogava" - blogger avatar fetch, and gradient data URI generator
  4. # v1 2.11.2011
  5. # v2 ..
  6. # v3 11.11.2011
  7. # v4 14.11.2011
  8. #    ?c1=f8dd99&c2=eeaa00&w=1&h=20 create gradient png
  9. #    optional: ?download or ?js[=variablename]
  10. # v4.01 14.12.2011 -
  11. #    blogger new profile page default profile without server-part,
  12. #    added code to deal with that
  13. # v4.02 18.12.2011 -
  14. #    increased timeout 5->15 in blogger profile pages, handle downloaderrors
  15. # v5 11.3.2012 -
  16. #    uses now python27 and lxml/etree/xpath to parse profile pages (is much
  17. #    better that regular expressions)
  18. #
  19. # -----------------------------------------------------------------------------
  20. #
  21. # fetch profile image from blogger user id. usage:
  22. # http://avafavico.appspot.com/?userid=01234567890
  23. #
  24. # See http://yabtb.blogspot.com/2011/11/google-app-engine-python-application.html
  25. #
  26. # This is like my first python app, so it may not be too shiny... but it works
  27. #
  28. #   - MS-potilas
  29. #
  30.  
  31. import sys
  32. import cgi
  33. import re
  34. import base64
  35. from google.appengine.api import images
  36. from google.appengine.api import urlfetch
  37. from google.appengine.api import memcache
  38. from google.appengine.runtime import DeadlineExceededError
  39. from lxml import etree
  40.  
  41. #
  42. # png & gradient code based on:
  43. # http://jtauber.com/blog/2008/05/18/creating_gradients_programmatically_in_python/
  44. #
  45.  
  46. #########################################
  47.  
  48. def make_png(width, height, rgba_func):
  49.   import zlib
  50.   import struct
  51.   import array
  52.  
  53.   rotate = False
  54.   if height < width:
  55.     width, height = height, width
  56.     rotate = True
  57.  
  58.   def make_chunk(chunk_type, data):
  59.     chu = struct.pack('!I', len(data)) + chunk_type + data
  60.     checksum = zlib.crc32(data, zlib.crc32(chunk_type))
  61.     chu += struct.pack('!I', 0xFFFFFFFF & checksum)
  62.     return chu
  63.  
  64.   def frange(x):
  65.     for i in xrange(x):
  66.       yield i/float(x)
  67.  
  68.   def get_data(width, height, rgba_func):
  69.     data = array.array('B')
  70.     for y in frange(height):
  71.       data.append(0)
  72.       for x in frange(width):
  73.         data.extend(int(round(v * 255)) for v in rgba_func(x, y))
  74.     return zlib.compress(data)
  75.  
  76.   out = array.array('B', [137, 80, 78, 71, 13, 10, 26, 10]).tostring() # PNG signature
  77.   color_type = 6 if len(list(rgba_func(0,0))) == 4 else 2
  78.   out += make_chunk('IHDR', struct.pack('!2I5B', width, height, 8, color_type, 0, 0, 0))
  79.   out += make_chunk('IDAT', get_data(width, height, rgba_func))
  80.   out += make_chunk('IEND', '')
  81.   img = images.Image(out)
  82.    
  83.   img.rotate(270)     # rotate it around to optimize
  84.   if not rotate:      # not rotate -> do full circle
  85.     img.rotate(90)
  86.   result = img.execute_transforms(output_encoding=images.PNG)
  87.   return result
  88.  
  89. def linear_gradient(start_value, stop_value, start_offset=0.0, stop_offset=1.0):
  90.   return lambda offset: (start_value + ((offset - start_offset) / (stop_offset - start_offset) * (stop_value - start_value))) / 255.0
  91.  
  92. def gradient(segments):
  93.   def gradient_function(x, y):
  94.     segment_start = 0.0
  95.     for segment_end, start, end in segments:
  96.       if y < segment_end:
  97.         return (linear_gradient(start[i], end[i], segment_start, segment_end)(y) for i in xrange(len(start)))
  98.       segment_start = segment_end
  99.   return gradient_function
  100.  
  101. #########################################
  102.  
  103. def getFavico(domain):
  104. # get favico for domain. first check cache
  105.   result = memcache.get(key=domain)
  106.   if result is None:
  107.     result = fetchUrl("http://www.google.com/s2/favicons?domain=" + domain)
  108.     memcache.add(key=domain, value=result, time=14400)
  109.   return result
  110.  
  111. def fetchUrl(url):
  112.   url = re.sub("^//", "http://", url)
  113.   dline = 5
  114.   if re.match("http:\/\/www\.blogger.com\/profile\/.+", url):
  115.     dline = 15
  116.   try:
  117.     result = urlfetch.fetch(url, deadline=dline)
  118.   except urlfetch.DownloadError:
  119.     return None
  120.   if result.status_code == 200:
  121.     return result.content
  122.   return None
  123.  
  124. #########################################
  125.  
  126. def main():
  127.   form = cgi.FieldStorage(keep_blank_values = True)
  128.   c1 = form.getfirst("c1")
  129.   c2 = form.getfirst("c2")
  130.   w = form.getfirst("w")
  131.   h = form.getfirst("h")
  132.  
  133.   if w is None or not re.match('\d+$', w):
  134.     w = 0
  135.   if h is None or not re.match('\d+$', h):
  136.     h = 0
  137.   if int(w)*int(h) > 4096:
  138.     print "Status: 400 Bad Request"
  139.     print
  140.     print "dimensions (w x h) too large"
  141.     return 1
  142.  
  143.   if c1 is not None and c2 is not None:
  144.     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:
  145.       c1r = int(c1[:2],16)
  146.       c1g = int(c1[2:][:2],16)
  147.       c1b = int(c1[-2:],16)
  148.       c2r = int(c2[:2],16)
  149.       c2g = int(c2[2:][:2],16)
  150.       c2b = int(c2[-2:],16)
  151.     else:
  152.       print "Status: 400 Bad Request"
  153.       print
  154.       print "invalid parameters"
  155.       return 1
  156.  
  157.     img = make_png(int(w), int(h), gradient([ (1.0, (c1r, c1g, c1b), (c2r, c2g, c2b)), ]))
  158.  
  159.     if form.getfirst("download") is not None:
  160.       print "Content-Type: image/png"
  161.       print "Cache-Control: public, max-age=86400"
  162.       print
  163.       print img
  164.       return 0
  165.     imgenc = base64.b64encode(img)
  166.     if form.getfirst("js") is not None:
  167.       var = form.getfirst("js")
  168.       if not re.match('[a-zA-Z]+$', var):
  169.         var = "result"
  170.      
  171.       print "Content-Type: text/javascript"
  172.       print "Cache-Control: public, max-age=86400"
  173.       print
  174.       print 'var ' + var + '="'+imgenc+'";'
  175.       return 0
  176.  
  177.     imgenc = 'data:image/png;base64,' + imgenc
  178.     print "Content-Type: text/html"
  179.     print "Cache-Control: public, max-age=86400"
  180.     print
  181.     print '<html><head><link rel="shortcut icon" href="/favicon.ico" /><title>Gradient PNG data URI generator</title></head><body style="text-align:center;">'
  182.     print 'Gradient from #' + c1 + ' to #' + c2 + ', width='+w+', height='+h+':<br />'
  183.     print '<textarea id="pngarea" rows="' + str(round(len(imgenc)/64+2))+ '" cols="64">'+imgenc+'</textarea>'
  184.     print '<script>document.getElementById("pngarea").focus();document.getElementById("pngarea").select();</script>'
  185.     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>'
  186.     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;">'
  187.     print 'background:url('+imgenc+') 100% 100%;'
  188.     print '</div>'
  189.     print '<hr />'
  190.     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>'
  191.     print '</body></html>'
  192.     return 0
  193.  
  194.   userid = form.getfirst("userid")
  195.  
  196.   if userid is None:
  197.     print "Content-Type: text/html"
  198.     print "Cache-Control: public, max-age=14400"
  199.     print
  200.     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 />'
  201.     print '</body></html>'
  202.     return 0
  203.  
  204.   if not re.match('\d+$', userid):
  205.     print "Status: 400 Bad Request"
  206.     print
  207.     print "invalid userid"
  208.     return 1
  209.  
  210.   # first check cache
  211.   thedata = memcache.get(key=userid)
  212.   if thedata is None:
  213.     domain = "www.blogger.com" # fallback favico
  214.     url = "http://www.blogger.com/profile/"+userid
  215.     result = fetchUrl(url)
  216.     if result is not None:
  217.       tree = etree.HTML(result)
  218.       r=tree.xpath("//img[@id='profile-photo']/@src")
  219.       if len(r) == 0:
  220.         r=tree.xpath("//img[@class and contains(concat(' ',normalize-space(@class),' '),' photo ')]/@src")
  221.       if len(r) > 0:
  222.         found = r[0]
  223.       # if profile photo not found, search person's first blog's address (rel="contributor-to")
  224.       if found is None:
  225.         r=tree.xpath("//a[@rel and contains(concat(' ',normalize-space(@rel),' '),' contributor-to ')]/@href")
  226.         if len(r) > 0:
  227.           found = r[0]
  228.         if found is not None:
  229.           domain = re.search('(http://){0,1}(.+?)[$/]', found).group(2)
  230.           found = None
  231.       if found is None:
  232.         result = getFavico(domain)
  233.       else:
  234.         found = re.sub('^//',"http://", found)
  235.         if not re.match('http', found):
  236.           found = "http://www.blogger.com" + found
  237.         result = fetchUrl(found)
  238.     # if loading has failed, fallback to domain's favico
  239.     if result is None:
  240.       result = getFavico(domain)
  241.     if result is not None:
  242.       img = images.Image(result)
  243.  
  244.       if img.width > img.height*1.67:
  245.         img.crop(0.2,0.0,0.8,1.0)
  246.       elif img.height > img.width*1.67:
  247.         img.crop(0.0,0.2,1.0,0.8)
  248.       elif img.width > img.height*1.25:
  249.         img.crop(0.1,0.0,0.9,1.0)
  250.       elif img.height > img.width*1.25:
  251.         img.crop(0.0,0.1,1.0,0.9)
  252.  
  253.       img.resize(32,32)
  254.       thedata=img.execute_transforms(output_encoding=images.PNG)
  255.       memcache.add(key=userid, value=thedata, time=14400)
  256.     else:
  257.       print "Status: 404 Not Found"
  258.       print
  259.       print "not found"
  260.       return 1 #all fetches failed
  261.  
  262.   print "Content-Type: image/png"
  263.   print "Cache-Control: public, max-age=14400"
  264.   print
  265.   print thedata
  266.   return 0
  267.  
  268. if __name__ == '__main__':
  269.   main()
Add Comment
Please, Sign In to add comment