forked from AmurSU/python-mjpeg-over-http-client
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhttp_mjpeg_client.py
149 lines (138 loc) · 5.55 KB
/
http_mjpeg_client.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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Name: Python M-JPEG Over HTTP Client Module
Version: 0.1
Purpose: This module parses an MJPEG stream and retrieves actual images.
Author: Sergey Lalov
Date: 2011-03-30
License: GPL
Usage: Connect twisted's reactor with MJPEGFactory(config)
Config
sample: {'request': '/?action=stream',
'login': 'admin',
'password': 'admin',
'callback': processImage}
login and password are optional. callback is a function that takes
one string argument - image data.
Target: Cross Platform
Require: Python 2.6+. Modules: zope.interface, twisted
"""
from twisted.internet.protocol import Protocol, ClientFactory
from base64 import b64encode
import re
debug = 0
class MJPEGClient(Protocol):
def __init__(self):
# A place for configuration parameters
self.config = {}
# I we are connected to a web server
self.isConnected = False
# The boundary in multipart stream
self.boundary = ''
# Actual image data goes here
self.img = ''
# Size of the image frame being downloaded
self.next_img_size = 0
# Indicates that currently parsing a header
self.isHeader = False
def connectionMade(self):
# Implement basic authorization
if self.config['login']:
authstring = 'Authorization: Basic ' + b64encode(self.config['login']+':'+self.config['password']) + '\r\n'
else:
authstring = ''
# Form proper HTTP request with header
to_send = 'GET ' + self.config['request'] + ' HTTP/1.0\r\n' + \
authstring + \
'User-Agent: Python M-JPEG Client\r\n' + \
'Keep-Alive: 300\r\n' + \
'Connection: keep-alive\r\n\r\n'
# Send it
self.transport.write(to_send)
if debug:
print 'We say:\n', to_send
def dataReceived(self, data):
if debug:
print 'Server said:\n', len(data), 'bytes of data.'
if not self.isConnected:
# Response header goes before empty line
data_sp = data.strip().split('\r\n\r\n', 1)
header = data_sp[0].splitlines()
# Parse header
for line in header:
if line.endswith('200 OK'): # Connection went fine
self.isConnected = True
if debug: print 'Connected'
self.checkForBoundary(line)
# If we got more data, find a JPEG there
if len(data_sp) == 2:
self.findJPEG(data_sp[1])
else:
# If connection is alredy made find a JPEG right away
self.findJPEG(data)
def checkForBoundary(self, line):
if line.startswith('Content-Type: multipart'): # Got multipart
r = re.search(r'boundary="?(.*)"?', line)
self.boundary = r.group(1) # Extract boundary
if debug: print 'Got boundary:', self.boundary
def findJPEG(self, data):
hasMoreThanHeader = False
# If we know next image size, than image header is already parsed
if not self.next_img_size:
# Otherwise it should be a header first
for line in data.splitlines():
if not len(self.boundary): self.checkForBoundary(line)
if line == '--'+self.boundary:
self.isHeader = True
if debug: print 'Got frame header'
elif line == '':
if self.isHeader:
# If we might have more data after a header in a buffer
hasMoreThanHeader = True
self.isHeader = False
elif self.isHeader:
# Here we can parse all the header information
# But we are really interesed only in one
if line.startswith('Content-Length:'):
self.next_img_size = int(line.split(' ')[1])
if debug: print 'Next frame size:', self.next_img_size
else:
# How many bytes left to read
remains = self.next_img_size - len(self.img)
self.img += data[:remains]
# We got the whole image
if len(self.img) == self.next_img_size:
if debug: print 'Got a frame!'
# Run a callback function
self.config['callback'](self.img)
# Reset variables
self.img = ''
self.next_img_size = 0
# If something left in a buffer
if data[remains:]:
self.findJPEG(data[remains:])
if hasMoreThanHeader:
data_sp = data.split('\r\n\r\n', 1)
# If there is something after a header in a buffer
if len(data_sp) == 2:
self.findJPEG(data_sp[1])
def connectionLost(self, reason):
print 'Connection lost, reconnecting'
self.isConnected = False
self.img = ''
self.next_img_size = 0
self.isHeader = 0
self.boundary = ''
class MJPEGFactory(ClientFactory):
def __init__(self, config):
self.protocol = MJPEGClient
self.config = config
def buildProtocol(self, addr):
prot = ClientFactory.buildProtocol(self, addr)
# Weird way to pass the config parametrs to the protocol
prot.config = self.config
return prot
def clientConnectionLost(self, connector, reason):
# Automatic reconnection
connector.connect()