Reputation: 573
I'm trying to serve a webcam image using Tornado library but the only way I found is to save the image first and then return the image name.
Is there a way to serve the image without saving to disk?
import tornado.ioloop
import tornado.web
import pygame.camera
import pygame.image
from time import time
from io import StringIO
pygame.camera.init()
cam = pygame.camera.Camera(pygame.camera.list_cameras()[0])
cam.start()
class MainHandler(tornado.web.RequestHandler):
def get(self):
img = cam.get_image()
name = str( round( time() ) )
name = name + '.jpg'
pygame.image.save(img, name)
self.write('<img src="' + name + '">')
application = tornado.web.Application([
(r"/", MainHandler),
(r'/(.*)', tornado.web.StaticFileHandler, {'path': ''})
])
if __name__ == "__main__":
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
Upvotes: 2
Views: 8612
Reputation: 3298
Yes, you can serve images without dealing with File I/O
. I have a python3
application that tracks the users by sending a 1x1 pixel using Tornado
I store the image as such in the source code:
# 1x1 Transparent Pixel in HEX Format
pixel_GIF = [0x47,0x49,0x46,0x38,0x39,0x61,
0x01,0x00,0x01,0x00,0x80,0x00,
0x00,0x00,0x00,0x00,0xff,0xff,
0xff,0x21,0xf9,0x04,0x01,0x00,
0x00,0x00,0x00,0x2c,0x00,0x00,
0x00,0x00,0x01,0x00,0x01,0x00,
0x00,0x02,0x01,0x44,0x00,0x3b]
Then, I convert the image in HEX
format to binary by using the following function:
def pack_into_binary(data):
"""Convert given data into binary form"""
packed = str()
for datum in data:
packed += struct.pack('B', datum).decode("ISO-8859-1")
return packed
pixel_binary = pack_into_binary(pixel_GIF)
After that I set the headers appropriately and serve the image:
#1x1 Transparent Tracking Pixel
self.set_header("Content-Length", 42)
self.set_header("Content-Type", "image/gif")
self.set_header("Pragma", "no-cache")
self.set_header("Cache-Control",
"no-store, "
"no-cache=Set-Cookie, "
"proxy-revalidate, "
"max-age=0, "
"post-check=0, pre-check=0"
)
self.set_header("Expires", "Wed, 2 Dec 1837 21:00:12 GMT")
self.write(self.pixel_binary)
Without doing any File I/O
.
Note: In your case if you have the image in memory you can serve it by converting the format to binary and using write
method. The difference is your image is not pre-determined unlike mine.
Edit: Check below for the example conversion from RGB Surface
to binary
encoding
from StringIO import StringIO
from PIL import Image
data = pygame.image.tostring(cam.get_image(),"RGB")
img = Image.fromstring('RGBA', (100,200), data) # 100 x 200 example surface
zdata = StringIO()
img.save(zdata, 'JPEG')
self.write(zdata.getvalue())
Then you can serve image
using Tornado
Upvotes: 1
Reputation: 94891
It doesn't look like pygame supports saving an image to a file-like object, so you won't be able to use it directly. However, it does have a tostring
method. The documentation for that notes that it allows interopability with other image libraries:
Creates a string that can be transferred with the ‘fromstring’ method in other Python imaging packages
So, you can use tostring
to turn your image into a string, then use another Python library which supports writing an image to a file-like object, and use its fromstring
method,
Here's an example using pillow
as the alternative image library.
import tornado.ioloop
import tornado.web
from PIL import Image
import cStringIO as StringIO
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("<img src='http://localhost:8888/img'>")
class ImgHandler(tornado.web.RequestHandler):
img_name = "bg.jpg"
img = pygame.image.load(img_name)
str_img = pygame.image.tostring(img, "RGB")
size = img.get_size()
fimg = Image.frombytes("RGB", size, str_img, "raw")
fobj = StringIO.StringIO()
fimg.save(fobj, format="png") #jpeg encoder isn't available in my install...
for line in fobj.getvalue():
self.write(line)
self.set_header("Content-type", "image/png")
application = tornado.web.Application([
(r"/", MainHandler),
(r"/img", ImgHandler),
#(r'/(.*)', tornado.web.StaticFileHandler, {'path': ''})
])
if __name__ == "__main__":
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
Both localhost:8888
and localhost:8888/img
will display the image.
Upvotes: 4
Reputation: 24007
Sure! Something like:
class ImageHandler(tornado.web.RequestHandler):
def get(self):
img = cam.get_image()
self.set_header('Content-Type', 'image/jpg')
self.write(img.contents)
application = tornado.web.Application([
(r"/image.jpg", ImageHandler),
])
I don't know exactly how to get the image's contents as bytes, but you get the idea.
Upvotes: -1