-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathpicamserver.py
executable file
·174 lines (155 loc) · 7.88 KB
/
picamserver.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
#! /usr/bin/env python3
"""
picamserver.py
Raspberry Pi camera server for exposing camera module over http.
(c) David Palmer
This code licensed under GPLv2
Started 2013-09-11
A similar project is BerryCam from fotosynlabs, which talks to
an iPad Application. See
https://bitbucket.org/fotosyn/fotosynlabs.git
"""
from __future__ import print_function
import logging
logging.basicConfig(level=logging.DEBUG)
import sys
import os
import argparse
import re
import subprocess
if sys.version_info.major == 2 :
logging.warning("Python 3 is prefered for this program")
import urlparse
import SimpleHTTPServer as httpserver
import SocketServer as socketserver
else :
import urllib.parse as urlparse
import http.server as httpserver
import socketserver
defaultPort = 8001 # Change here or use the --port argument
defaultFileroot = os.path.dirname(__file__) # change here or use --fileroot argument
class PiCamHandler(httpserver.SimpleHTTPRequestHandler):
raspiPath = "/usr/bin/raspistill"
# Take the help output and convert it to this by
# ^-([a-z]+), *--([a-z]+)\s:\s*(.*)$ ---> ['\2', '\1'], # \3
argnames = [ # Arguments to raspistill, first as long form, then as short
['help', '?'], # This help information
['width','w'], # Set image width <size>
['height', 'h'], # Set image height <size>
['quality', 'q'], # Set jpeg quality <0 to 100>
['raw', 'r'], # Add raw bayer data to jpeg metadata
['output', 'o'], # Output filename <filename> (to write to stdout, use '-o -'). If not specified, no file is saved
['latest', 'l'], # Link latest complete image to filename <filename>
['verbose', 'v'], # Output verbose information during run
['timeout', 't'], # Time (in ms) before takes picture and shuts down (if not specified, set to 5s)
['thumb', 'th'], # Set thumbnail parameters (x:y:quality)
['demo', 'd'], # Run a demo mode (cycle through range of camera options, no capture)
['encoding', 'e'], # Encoding to use for output file (jpg, bmp, gif, png)
['exif', 'x'], # EXIF tag to apply to captures (format as 'key=value')
['timelapse', 'tl'], # Timelapse mode. Takes a picture every <t>ms
['fullpreview', 'fp'], # Run the preview using the still capture resolution (may reduce preview fps)
['preview', 'p'], # Preview window settings <'x,y,w,h'>
['fullscreen', 'f'], # Fullscreen preview mode
['opacity', 'op'], # Preview window opacity (0-255)
['nopreview', 'n'], # Do not display a preview window
['sharpness', 'sh'], # Set image sharpness (-100 to 100)
['contrast', 'co'], # Set image contrast (-100 to 100)
['brightness', 'br'], # Set image brightness (0 to 100)
['saturation', 'sa'], # Set image saturation (-100 to 100)
['ISO', 'ISO'], # Set capture ISO
['vstab', 'vs'], # Turn on video stablisation
['ev', 'ev'], # Set EV compensation
['exposure', 'ex'], # Set exposure mode (see Notes)
['shutter', 'ss'], # set shutter speed in microseconds
['awb', 'awb'], # Set AWB mode (see Notes)
['imxfx', 'ifx'], # Set image effect (see Notes)
['colfx', 'cfx'], # Set colour effect (U:V)
['metering', 'mm'], # Set metering mode (see Notes)
['rotation', 'rot'], # Set image rotation (0-359)
['hflip', 'hf'], # Set horizontal flip
['vflip', 'vf'], # Set vertical flip
['roi', 'roi'], # Set region of interest (x,y,w,d as normalised coordinates [0.0-1.0])
]
# Which arguments are flags (i.e. that don't take the next command line argument
# use flag=0 or flag=1 in the URL to set or reset
flagargs = ['help','raw','verbose','demo','fullpreview','fullscreen','nopreview','vstab','hflip','vflip']
short2long = dict([(s,l) for l,s, in argnames])
defaultargs = {'output':'-', 'timeout':'500','nopreview':'1'}
def do_GET(self) :
logging.info("GET request: %s" % (self.path,))
parsedParams = urlparse.urlparse(self.path)
queryParsed = urlparse.parse_qs(parsedParams.query)
logging.debug("parsedParams: %s" % (parsedParams,))
logging.debug("queryParsed: %s" % (queryParsed,))
if parsedParams.path == "/" :
# default return for empty URL
path = "/file/default.html"
else :
path = parsedParams.path
splitpath = os.path.split(path)
logging.debug("splitPath = %s" % (splitpath,))
if parsedParams.path == "/camera" :
# Code 304 Not Modified could be used for too-rapid image requests
self.image,self.diagnostic = self.runCommand(queryParsed)
self.send_response(200)
self.send_header("Content-type", "image/jpeg")
self.send_header("Content-length", len(self.image))
self.send_header("Cache-Control", "no-cache")
self.send_header("Expires","Thu, 01 Dec 1994 16:00:00 GMT")
self.end_headers()
self.wfile.write(self.image)
elif splitpath[0] == '/file' :
if len(splitpath) > 2 :
raise RuntimeError("Only one directory level allowed")
filename = os.path.join(defaultFileroot,splitpath[1])
self.send_response(200)
#self.send_header("Content-type", self.guess_type(filename))
self.end_headers()
self.wfile.write(open(filename,"rb").read())
else :
self.send_error(501,"Only /camera request is supported now")
def runCommand(self, queryParsed) :
"""From the parsed params, make and execute the RaspiStill command line"""
# Convert URL to a dict of args
args = self.defaultargs.copy()
residual_args = {}
for arg,value in queryParsed.items() :
if arg in self.short2long :
args[self.short2long[arg]] = value[0]
else :
# IMPROVEME, allow unique prefixes
if arg in self.short2long.values() :
args[arg] = value[0]
else :
residual_args[arg] = value
if args['output'] != '-' or 'latest' in args :
raise RuntimeError("File output not yet supported")
# Convert dict of args to a command line array
cmd = [self.raspiPath]
for arg,value in args.items() :
if arg in self.flagargs :
if value == '1' :
cmd.append('--'+arg) # Add the flag
else :
cmd.append('--'+arg)
cmd.append(value)
logging.info("Raspistill command is broken down as %s" % (cmd,))
if residual_args :
logging.info("Unused arguments are: %s" % (residual_args,))
# FIXME doesn't return verbose output and other diagnostics yet
image = subprocess.check_output(cmd,stderr=sys.stderr)
return (image,"unused args: %s" % (residual_args,))
def sanitizeFile(self, filename) :
"""Make sure the filename isn't toxic"""
if re.findall("(\.\.)|(^~)",filename) :
logging.critical("IP : %s tried to write to file %s.\n"
"Full GET: %s\n"
"I no longer feel safe and am shutting down."
% (self.client_address[0], filename,self.path))
raise RuntimeError("Attempted file system violation")
def main(argv) :
httpd = socketserver.TCPServer(("", defaultPort), PiCamHandler)
logging.info("picamserver -- Listening on port %d", defaultPort)
httpd.serve_forever()
if __name__ == "__main__" :
main(sys.argv)