Posted By

weilawei on 05/07/12


Tagged

server streaming timer cache svg Cairo


Versions (?)

Simple SVG Streaming Server


 / Published in: Python
 

This is a simple SVG streaming server, running on the bottle microframework. It has 3 utility functions which are used as decorators on routes. They enable timing a route, caching a route, and creating a route which provides a Cairo context which is then converted to a Base64 encoded data URL with an SVG image. In another snippet, I give the client-side implementation using HTML, Javascript/jQuery, and Canvas.

This sample can currently display a clock (clock.svg) (incorrectly rotated 90 degrees...but it wasn't enough of a priority to fix. it's just an example.), show a static line of text (window.svg), or serve static files (the necessary client-side JS and HTML).

The client is at http://snipplr.com/view/64803/simple-svg-streaming-client/.

  1. #!/usr/bin/env python
  2.  
  3. import bottle
  4. import cairo
  5. import cStringIO
  6. import base64
  7. import datetime
  8. import time
  9.  
  10. bottle.debug(True)
  11.  
  12. def time_page(a_page):
  13. def _time_page(*args, **kwargs):
  14. start = time.time()
  15. retval = a_page(*args, **kwargs)
  16. end = time.time()
  17. if bottle.DEBUG is True: print "%s, %0.2fms" % (repr(a_page), (end - start) * 1000)
  18. return retval
  19. return _time_page
  20.  
  21. def cache_page(a_page_cache, timeout):
  22. def _cache_page(a_page):
  23. def __cache_page(*args, **kwargs):
  24. page_id = repr(a_page)
  25. if page_id not in a_page_cache or a_page_cache[page_id][0] <= time.time():
  26. if bottle.DEBUG is True: print "cache miss or refresh (%s)" % (page_id,)
  27. retval = a_page(*args, **kwargs)
  28. a_page_cache[page_id] = (time.time() + timeout, retval)
  29. return retval
  30. else:
  31. if bottle.DEBUG is True: print "cache hit (%s)" % (page_id,)
  32. return a_page_cache[page_id][1]
  33. return __cache_page
  34. return _cache_page
  35.  
  36. a_page_cache = dict()
  37.  
  38. def svg_stream(width, height):
  39. def _svg_stream(a_page):
  40. def __svg_stream(*args, **kwargs):
  41. an_svg_file = cStringIO.StringIO()
  42. a_surface = cairo.SVGSurface(an_svg_file, width, height)
  43. a_context = cairo.Context(a_surface)
  44. a_page(a_context, *args, **kwargs)
  45. a_surface.finish()
  46. bottle.response.content_type = 'image/svg+xml'
  47. return "data:image/svg+xml;base64,%s" % (base64.b64encode(an_svg_file.getvalue()),)
  48. return __svg_stream
  49. return _svg_stream
  50.  
  51. @bottle.route('/window.svg')
  52. @time_page
  53. @cache_page(a_page_cache, 0.5)
  54. @svg_stream(640, 480)
  55. def window(a_context):
  56. a_context.set_source_rgba(0.99607, 1.0, 0.76, 1.0)
  57. a_context.select_font_face("sans")
  58. a_context.set_font_size(32)
  59. a_context.move_to(140, 170)
  60. a_context.show_text("window.svg")
  61.  
  62. @bottle.route('/clock.svg')
  63. @time_page
  64. @cache_page(a_page_cache, 0.5)
  65. @svg_stream(640, 480)
  66. def clock(a_context):
  67. the_time = datetime.datetime.now()
  68.  
  69. a_context.set_line_width(6.0)
  70.  
  71. # (degrees / second) * (pi / 180) ~= (360 / 60) * (3.14159 / 180) ~= 0.1047196
  72. a_context.set_source_rgba(0.57, 0.48, 0.34, 1.0)
  73. a_context.arc(240, 180, 30, (the_time.second + 1) * 0.1047196, (the_time.second - 1) * 0.1047196)
  74. a_context.stroke()
  75.  
  76. a_context.set_source_rgba(0.71, 0.73, 0.42, 1.0)
  77. a_context.arc(240, 180, 30, (the_time.second - 0.3)* 0.1047196, (the_time.second + 0.3) * 0.1047196)
  78. a_context.stroke()
  79.  
  80. a_context.set_line_width(10.0)
  81.  
  82. a_context.set_source_rgba(0.49, 0.16, 0.21, 1.0)
  83. a_context.arc(240, 180, 43, (the_time.minute + 1.7) * 0.1047196, (the_time.minute - 1.7) * 0.1047196)
  84. a_context.stroke()
  85.  
  86. a_context.set_source_rgba(0.8, 0.57, 0.35, 1.0)
  87. a_context.arc(240, 180, 43, (the_time.minute - 0.6)* 0.1047196, (the_time.minute + 0.6) * 0.1047196)
  88. a_context.stroke()
  89.  
  90. a_context.set_line_width(11.0)
  91.  
  92. a_context.set_source_rgba(0.71, 0.73, 0.42, 1.0)
  93. a_context.arc(240, 180, 60, (the_time.hour + 1.7) * 0.1047196, (the_time.hour - 1.7) * 0.1047196)
  94. a_context.stroke()
  95.  
  96. a_context.set_source_rgba(0.99607, 1.0, 0.76, 1.0)
  97. a_context.arc(240, 180, 60, (the_time.hour - 0.6)* 0.1047196, (the_time.hour + 0.6) * 0.1047196)
  98. a_context.stroke()
  99.  
  100. a_context.select_font_face("sans")
  101. a_context.set_font_size(32)
  102. a_context.move_to(320, 340)
  103. a_context.show_text("%02d:%02d:%02d" % (the_time.hour, the_time.minute, the_time.second))
  104.  
  105. @bottle.route('/:a_file')
  106. @time_page
  107. def static_file(a_file):
  108. return bottle.static_file(a_file, root='./')
  109.  
  110. bottle.run(host='127.0.0.1', port=8080)

Report this snippet  

You need to login to post a comment.