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/.
#!/usr/bin/env python import bottle import cairo import cStringIO import base64 import datetime import time bottle.debug(True) def time_page(a_page): def _time_page(*args, **kwargs): start = time.time() retval = a_page(*args, **kwargs) end = time.time() if bottle.DEBUG is True: print "%s, %0.2fms" % (repr(a_page), (end - start) * 1000) return retval return _time_page def cache_page(a_page_cache, timeout): def _cache_page(a_page): def __cache_page(*args, **kwargs): page_id = repr(a_page) if page_id not in a_page_cache or a_page_cache[page_id][0] <= time.time(): if bottle.DEBUG is True: print "cache miss or refresh (%s)" % (page_id,) retval = a_page(*args, **kwargs) a_page_cache[page_id] = (time.time() + timeout, retval) return retval else: if bottle.DEBUG is True: print "cache hit (%s)" % (page_id,) return a_page_cache[page_id][1] return __cache_page return _cache_page a_page_cache = dict() def svg_stream(width, height): def _svg_stream(a_page): def __svg_stream(*args, **kwargs): an_svg_file = cStringIO.StringIO() a_surface = cairo.SVGSurface(an_svg_file, width, height) a_context = cairo.Context(a_surface) a_page(a_context, *args, **kwargs) a_surface.finish() bottle.response.content_type = 'image/svg+xml' return "data:image/svg+xml;base64,%s" % (base64.b64encode(an_svg_file.getvalue()),) return __svg_stream return _svg_stream @bottle.route('/window.svg') @time_page @cache_page(a_page_cache, 0.5) @svg_stream(640, 480) def window(a_context): a_context.set_source_rgba(0.99607, 1.0, 0.76, 1.0) a_context.select_font_face("sans") a_context.set_font_size(32) a_context.move_to(140, 170) a_context.show_text("window.svg") @bottle.route('/clock.svg') @time_page @cache_page(a_page_cache, 0.5) @svg_stream(640, 480) def clock(a_context): the_time = datetime.datetime.now() a_context.set_line_width(6.0) # (degrees / second) * (pi / 180) ~= (360 / 60) * (3.14159 / 180) ~= 0.1047196 a_context.set_source_rgba(0.57, 0.48, 0.34, 1.0) a_context.arc(240, 180, 30, (the_time.second + 1) * 0.1047196, (the_time.second - 1) * 0.1047196) a_context.stroke() a_context.set_source_rgba(0.71, 0.73, 0.42, 1.0) a_context.arc(240, 180, 30, (the_time.second - 0.3)* 0.1047196, (the_time.second + 0.3) * 0.1047196) a_context.stroke() a_context.set_line_width(10.0) a_context.set_source_rgba(0.49, 0.16, 0.21, 1.0) a_context.arc(240, 180, 43, (the_time.minute + 1.7) * 0.1047196, (the_time.minute - 1.7) * 0.1047196) a_context.stroke() a_context.set_source_rgba(0.8, 0.57, 0.35, 1.0) a_context.arc(240, 180, 43, (the_time.minute - 0.6)* 0.1047196, (the_time.minute + 0.6) * 0.1047196) a_context.stroke() a_context.set_line_width(11.0) a_context.set_source_rgba(0.71, 0.73, 0.42, 1.0) a_context.arc(240, 180, 60, (the_time.hour + 1.7) * 0.1047196, (the_time.hour - 1.7) * 0.1047196) a_context.stroke() a_context.set_source_rgba(0.99607, 1.0, 0.76, 1.0) a_context.arc(240, 180, 60, (the_time.hour - 0.6)* 0.1047196, (the_time.hour + 0.6) * 0.1047196) a_context.stroke() a_context.select_font_face("sans") a_context.set_font_size(32) a_context.move_to(320, 340) a_context.show_text("%02d:%02d:%02d" % (the_time.hour, the_time.minute, the_time.second)) @bottle.route('/:a_file') @time_page def static_file(a_file): return bottle.static_file(a_file, root='./') bottle.run(host='127.0.0.1', port=8080)
You need to login to post a comment.
