/
Stream.py
268 lines (190 loc) · 8.16 KB
/
Stream.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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
from SimpleCV.base import *
_jpegstreamers = {}
class JpegStreamHandler(SimpleHTTPRequestHandler):
"""
The JpegStreamHandler handles requests to the threaded HTTP server.
Once initialized, any request to this port will receive a multipart/replace
jpeg.
"""
def do_GET(self):
global _jpegstreamers
if (self.path == "/" or not self.path):
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write("""
<html>
<head>
<style type=text/css>
body { background-image: url(/stream); background-repeat:no-repeat; background-position:center top; background-attachment:fixed; height:100% }
</style>
</head>
<body>
</body>
</html>
""")
return
elif (self.path == "/stream"):
self.send_response(200)
self.send_header("Connection", "close")
self.send_header("Max-Age", "0")
self.send_header("Expires", "0")
self.send_header("Cache-Control", "no-cache, private")
self.send_header("Pragma", "no-cache")
self.send_header("Content-Type", "multipart/x-mixed-replace; boundary=--BOUNDARYSTRING")
self.end_headers()
(host, port) = self.server.socket.getsockname()[:2]
count = 0
timeout = 0.75
lasttimeserved = 0
while (1):
if (_jpegstreamers[port].refreshtime > lasttimeserved or time.time() - timeout > lasttimeserved):
try:
self.wfile.write("--BOUNDARYSTRING\r\n")
self.send_header("Content-type", "image/jpeg")
self.send_header("Content-Length", str(len(_jpegstreamers[port].jpgdata.getvalue())))
self.end_headers()
self.wfile.write(_jpegstreamers[port].jpgdata.getvalue() + "\r\n")
lasttimeserved = time.time()
except socket.error, e:
return
except IOError, e:
return
count = count + 1
time.sleep(_jpegstreamers[port].sleeptime)
class JpegTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
allow_reuse_address = True
daemon_threads = True
#factory class for jpegtcpservers
class JpegStreamer():
"""
The JpegStreamer class allows the user to stream a jpeg encoded file
to a HTTP port. Any updates to the jpg file will automatically be pushed
to the browser via multipart/replace content type.
To initialize:
js = JpegStreamer()
to update:
img.save(js)
to open a browser and display:
import webbrowser
webbrowser.open(js.url)
Note 3 optional parameters on the constructor:
- port (default 8080) which sets the TCP port you need to connect to
- sleep time (default 0.1) how often to update. Above 1 second seems to cause dropped connections in Google chrome
Once initialized, the buffer and sleeptime can be modified and will function properly -- port will not.
"""
server = ""
host = ""
port = ""
sleeptime = ""
framebuffer = ""
counter = 0
refreshtime = 0
def __init__(self, hostandport = 8080, st=0.1 ):
global _jpegstreamers
if (type(hostandport) == int):
self.port = hostandport
self.host = "localhost"
elif (isinstance(hostandport, basestring) and re.search(":", hostandport)):
(self.host, self.port) = hostandport.split(":")
self.port = int(self.port)
elif (type(hostandport) == tuple):
(self.host, self.port) = hostandport
self.sleeptime = st
self.server = JpegTCPServer((self.host, self.port), JpegStreamHandler)
self.server_thread = threading.Thread(target = self.server.serve_forever)
_jpegstreamers[self.port] = self
self.server_thread.daemon = True
self.server_thread.start()
self.framebuffer = self #self referential, ugh. but gives some bkcompat
def url(self):
"""
Returns the JpegStreams Webbrowser-appropriate URL, if not provided in the constructor, it defaults to "http://localhost:8080"
"""
return "http://" + self.host + ":" + str(self.port) + "/"
def streamUrl(self):
"""
Returns the URL of the MJPEG stream. If host and port are not set in the constructor, defaults to "http://localhost:8080/stream/"
"""
return self.url() + "stream"
class VideoStream():
"""
The VideoStream lets you save video files in a number of different formats.
You can initialize it by specifying the file you want to output::
vs = VideoStream("hello.avi")
You can also specify a framerate, and if you want to "fill" in missed frames.
So if you want to record a realtime video you may want to do this::
vs = VideoStream("myvideo.avi", 25, True) #note these are default values
Where if you want to do a stop-motion animation, you would want to turn fill off::
vs_animation = VideoStream("cartoon.avi", 15, False)
If you select a fill, the VideoStream will do its best to stay close to "real time" by duplicating frames or dropping frames when the clock doesn't sync up
with the file writes.
You can save a frame to the video by using the Image.save() function::
my_camera.getImage().save(vs)
"""
fps = 25
filename = ""
writer = ""
fourcc = ""
framefill = True
videotime = 0.0
starttime = 0.0
framecount = 0
def __init__(self, filename, fps = 25, framefill = True):
(revextension, revname) = filename[::-1].split(".")
extension = revextension[::-1]
self.filename = filename
self.fps = fps
self.framefill = framefill
#if extension == "mpg":
self.fourcc = cv.CV_FOURCC('I', 'Y', 'U', 'V')
#self.fourcc = 0
#else:
# logger.warning(extension + " is not supported for video writing on this platform, sorry");
# return False
def initializeWriter(self, size):
self.writer = cv.CreateVideoWriter(self.filename, self.fourcc, self.fps, size, 1)
self.videotime = 0.0
self.starttime = time.time()
def writeFrame(self, img):
"""
This writes a frame to the display object
this is automatically called by image.save() but you can
use this function to save just the bitmap as well so
image markup is not implicit,typically you use image.save() but
this allows for more finer control
"""
if not self.writer:
self.initializeWriter(img.size())
self.lastframe = img
frametime = 1.0 / float(self.fps)
targettime = self.starttime + frametime * self.framecount
realtime = time.time()
if self.framefill:
#see if we need to do anything to adjust to real time
if (targettime > realtime + frametime):
#if we're more than one frame ahead
#save the lastframe, but don't write to videoout
self.lastframe = img
return
elif (targettime < realtime - frametime):
#we're at least one frame behind
framesbehind = int((realtime - targettime) * self.fps) + 1
#figure out how many frames behind we are
lastframes = framesbehind / 2
for i in range(0, lastframes):
self.framecount += 1
cv.WriteFrame(self.writer, self.lastframe.getBitmap())
theseframes = framesbehind - lastframes
for i in range(0, theseframes):
self.framecount += 1
cv.WriteFrame(self.writer, img.getBitmap())
#split missing frames evenly between the prior and current frame
else: #we are on track
self.framecount += 1
cv.WriteFrame(self.writer, img.getBitmap())
else:
cv.WriteFrame(self.writer, img.getBitmap())
self.framecount += 1
self.lastframe = img