How to stream video (motion jpeg) using falcon server? - python-3.x

Desired output
Input: Camera feed using OpenCV or from a REST camera url. (Not a concern for this question)
Output: Streaming jpeg images after doing some OpenCV processing
So far I have done the following based on Falcon tutorial
Input: Image file as a POST request
Output: GET request endpoint with the path to the image
import mimetypes
import os
import re
import uuid
import cv2
import io
import falcon
from falcon import media
import json
import msgpack
class Collection(object):
def __init__(self, image_store):
self._image_store = image_store
def on_get(self, req, resp):
# TODO: Modify this to return a list of href's based on
# what images are actually available.
doc = {
'images': [
{
'href': '/images/1eaf6ef1-7f2d-4ecc-a8d5-6e8adba7cc0e.png'
}
]
}
resp.data = msgpack.packb(doc, use_bin_type=True)
resp.content_type = falcon.MEDIA_MSGPACK
resp.status = falcon.HTTP_200
def on_post(self, req, resp):
name = self._image_store.save(req.stream, req.content_type)
# Unnecessary Hack to read the saved file in OpenCV
image = cv2.imread("images/" + name)
new_image = do_something_with_image(image)
_ = cv2.imwrite("images/" + name, new_image)
resp.status = falcon.HTTP_201
resp.location = '/images/' + name
class Item(object):
def __init__(self, image_store):
self._image_store = image_store
def on_get(self, req, resp, name):
resp.content_type = mimetypes.guess_type(name)[0]
resp.stream, resp.stream_len = self._image_store.open(name)
class ImageStore(object):
_CHUNK_SIZE_BYTES = 4096
_IMAGE_NAME_PATTERN = re.compile(
'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.[a-z]{2,4}$'
)
def __init__(self, storage_path, uuidgen=uuid.uuid4, fopen=io.open):
self._storage_path = storage_path
self._uuidgen = uuidgen
self._fopen = fopen
def save(self, image_stream, image_content_type):
ext = mimetypes.guess_extension(image_content_type) # Issue with this code, Not returning the extension so hard coding it in next line
ext = ".jpg"
name = '{uuid}{ext}'.format(uuid=self._uuidgen(), ext=ext)
image_path = os.path.join(self._storage_path, name)
with self._fopen(image_path, 'wb') as image_file:
while True:
chunk = image_stream.read(self._CHUNK_SIZE_BYTES)
if not chunk:
break
image_file.write(chunk)
return name
def open(self, name):
# Always validate untrusted input!
if not self._IMAGE_NAME_PATTERN.match(name):
raise IOError('File not found')
image_path = os.path.join(self._storage_path, name)
stream = self._fopen(image_path, 'rb')
stream_len = os.path.getsize(image_path)
return stream, stream_len
def create_app(image_store):
api = falcon.API()
api.add_route('/images', Collection(image_store))
api.add_route('/images/{name}', Item(image_store))
api.add_sink(handle_404, '')
return api
def get_app():
storage_path = os.environ.get('LOOK_STORAGE_PATH', './images')
image_store = ImageStore(storage_path)
return create_app(image_store)
The response is something like this:
HTTP/1.1 201 Created
Connection: close
Date: Mon, 03 Dec 2018 13:08:14 GMT
Server: gunicorn/19.7.1
content-length: 0
content-type: application/json; charset=UTF-8
location: /images/e69a83ee-b369-47c3-8b1c-60ab7bf875ec.jpg
There are 2 problems with the above code:
I first get the data stream and save it in a file and then read it in OpenCV to do some other operations which is pretty overkill and should be easily fixed but I don't know how
This service does not stream the JPGs. All I have a GET request url which I can open in browser to see the image which is not ideal
So, how can I read req.stream data as numpy array? And more importantly what changes I need to make to stream images from this service?
P.S. Apologies for a long post

I have found a solution which works perfectly fine. For more on this check out this beautiful code.
def gen(camera):
while True:
image = camera.get_frame()
new_image = do_something_with_image(image)
ret, jpeg = cv2.imencode('.jpg', new_image)
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + jpeg.tobytes() + b'\r\n\r\n')
class StreamResource(object):
def on_get(self, req, resp):
labeled_frame = gen(VideoCamera())
resp.content_type = 'multipart/x-mixed-replace; boundary=frame'
resp.stream = labeled_frame
def get_app():
api = falcon.API()
api.add_route('/feed', StreamResource())
return api

Related

bithumb Api error -{'status': '5100', 'message': 'Bad Request.(Auth Data)'}

I wanted to make a program where I can check the order details of my order at Bithumb Exchange.
So I looked at the docs(https://api.bithumb.com/info/orders) and made it, but the same error code kept coming out, so I don't know what to do.
import time
import math
import base64
import hmac, hashlib
import urllib.parse
import requests
class XCoinAPI:
api_url = "https://api.bithumb.com";
api_key = "";
api_secret = "";
def __init__(self, api_key, api_secret):
self.api_key = api_key;
self.api_secret = api_secret;
def body_callback(self, buf):
self.contents = buf;
def microtime(self, get_as_float = False):
if get_as_float:
return time.time()
else:
return '%f %d' % math.modf(time.time())
def usecTime(self) :
mt = self.microtime(False)
mt_array = mt.split(" ")[:2];
return mt_array[1] + mt_array[0][2:5];
def xcoinApiCall(self, endpoint, rgParams):
# 1. Api-Sign and Api-Nonce information generation.
# 2. Request related information from the Bithumb API server.
#
# - nonce: it is an arbitrary number that may only be used once.
# - api_sign: API signature information created in various combinations values.
endpoint_item_array = {
"endpoint" : endpoint
}
uri_array = dict(endpoint_item_array, **rgParams) # Concatenate the two arrays.
str_data = urllib.parse.urlencode(uri_array)
nonce = self.usecTime()
data = endpoint + chr(1) + str_data + chr(1) + nonce
utf8_data = data.encode('utf-8')
key = self.api_secret
utf8_key = key.encode('utf-8')
h = hmac.new(bytes(utf8_key), utf8_data, hashlib.sha512)
hex_output = h.hexdigest()
utf8_hex_output = hex_output.encode('utf-8')
api_sign = base64.b64encode(utf8_hex_output)
utf8_api_sign = api_sign.decode('utf-8')
headers = {
"Accept": "application/json",
"Content-Type": "application/x-www-form-urlencoded",
"Api-Key": self.api_key,
"Api-Nonce": nonce,
"Api-Sign": utf8_api_sign
}
url = self.api_url + endpoint
r = requests.post(url, headers=headers, data=uri_array)
return r.json()
a = XCoinAPI(api_key="MYKEY1c", api_secret="MYKEY2")
aa= a.xcoinApiCall("/info/orders",{"order_currency":"LN","payment_currency":"BTC"})
print(aa)
{'status': '5100', 'message': 'Bad Request.(Auth Data)'}
Process finished with exit code 0
The error code 5100, bad request keeps coming up(https://apidocs.bithumb.com/docs/api-%EC%A3%BC%EC%9A%94-%EC%97%90%EB%9F%AC-%EC%BD%94%EB%93%9C)
I really don't know which code to modify.
I think it's a parameter problem with XcoinApiCall, but I don't know.

Http message is not callable porting python2 to python3 download script

i'am trying to port to python 3 this script :
import re
from os.path import basename
import os
from urllib.parse import urlparse,urlsplit
from urllib.request import urlopen,Request
import urllib
def url2name(url):
return basename(urlsplit(url)[2])
def download(url, out_path="."):
localName = url2name(url)
req = Request(url)
r = urlopen(req)
if r.info().has_key('Content-Disposition'):
# If the response has Content-Disposition, we take file name from it
localName = r.info()['Content-Disposition'].split('filename=')[1]
if localName[0] == '"' or localName[0] == "'":
localName = localName[1:-1]
elif r.url != url:
# if we were redirected, the real file name we take from the final URL
localName = url2name(r.url)
localName = os.path.join(out_path, localName)
f = open(localName, 'wb')
f.write(r.read())
f.close()
but i have a :
'HTTPMessage' object is not callable
r.info() seems to have problems
how to get the header info in python 3 ?
Try with this, you should use context managers:
def download(url, out_path="."):
localName = url2name(url)
req = Request(url)
with urlopen(req) as f:
content_disp = f.getheader('Content-Disposition')
if content_disp:
# If the response has Content-Disposition, we take file name from it
localName = content_disp.split('filename=')[1]
if localName[0] == '"' or localName[0] == "'":
localName = localName[1:-1]
elif f.url != url:
# if we were redirected, the real file name we take from the final URL
localName = url2name(f.url)
localName = os.path.join(out_path, localName)
with open(localName, 'wb') as fp:
fp.write(f.read())

Uploading images to a Flask API using FlaskClient

I have the following endpoint in my Flask API:
#api.route("/files", methods=Method.POST)
def upload_file() -> str:
enquirer_id = request.get_json()['id']
try:
logging.info(request.files)
uploaded_file = request.files['image']
except KeyError:
abort(HttpStatusCode.BAD_REQUEST, "Please provide image to upload")
if uploaded_file.content_type != ContentType.JPEG:
abort(HttpStatusCode.BAD_REQUEST, "Only JPEG images are allowed")
filename = str(enquirer_id)
destination_file = os.path.join(current_app.config[AppCfg.DIRECTORY], filename)
uploaded_file.save(destination_file)
return f'Successfully upload file {filename}.'
But I am unable to test this endpoint using the FlaskClient:
class APITestUploadDownload(APITestBase):
url = '/api/v1/files'
my_file = 'some_picture.jpg'
def setUp(self):
self.client = self.app.test_client()
def test_upload(self):
with open(file=self.my_file, mode='rb') as file:
data = {
'image': io.BytesIO(file.read()),
'id': 1
}
response = self.client.post(self.url, data=json.dumps(data), headers={}, content_type='multipart/form-data',)
self.assertEqual(response.status_code, HttpStatusCode.OK)
But this fails with the error message TypeError: Object of type BytesIO is not JSON serializable.
If i make the request without using json.dump(data) using response = self.client.post(self.url, data=data, headers={}, content_type='multipart/form-data', ) I get another error which tells me thatresponse.data is None.
So how can I fix my file upload endpoint and the correspondig test?

sending compressed numpy array (zlib) to flask server with post request [python]

I am trying to send a compressed numpy array (compressed with zlib) to the flask server with post request, but the compressed bytes object is getting changed in the server end. How to properly send the bytes object with requests post request so that I can decompress on the server end?
server.py
from flask import Flask
from flask_restful import Resource, Api, reqparse
import json
import numpy as np
import base64
# compression
import zlib
app = Flask(__name__)
api = Api(app)
parser = reqparse.RequestParser()
parser.add_argument('imgb64')
class Predict(Resource):
def post(self):
data = parser.parse_args()
if data['imgb64'] == "":
return {
'data':'',
'message':'No file found',
'status':'error'
}
img = data['imgb64']
print('rec')
# decompress
print(type(img))
print(img)
dec = zlib.decompress(img) # this gives me error
if img:
pass
return json.dumps({
'data': 'done',
'message':'darknet processed',
'status':'success'
})
return {
'data':'',
'message':'Something when wrong',
'status':'error'
}
api.add_resource(Predict,'/predict')
if __name__ == '__main__':
app.run(debug=True, host = '0.0.0.0', port = 5000, threaded=True)
client.py
import numpy as np
import base64
import zlib
import requests
frame = np.random.randint(0,255,(5,5,3)) # dummy rgb image
# compress
data = zlib.compress(frame)
print('b64 encoded')
print(data)
print(len(data))
print(type(data))
r = requests.post("http://127.0.0.1:5000/predict", data={'imgb64' : data}) # sending compressed numpy array
This gives me the following error:
TypeError: a bytes-like object is required, not 'str'
So, I tried to convert the string to bytes object:
dec = zlib.decompress(img.encode()) # this gives me error
But, this one also gives me an error:
zlib.error: Error -3 while decompressing data: incorrect header check
I tried with other encodings, they also failed.
One thing I noticed is, when I print the compressed bytes in the client end, it reads:
b'x\x9c-\xcf?J\x82q\x00\x06\xe0\x0ftrVP\\C\xf4\x06\x9268\t\xcdR\x8ej.F\xa0\xe0\xd0\xa6\xa3\xe0V\x07\x10\x1cL\xc8\xd1\x03\xd4\xe4\t\x0c\x12\x84\xb6D\x0c#\xbc\x80O\xf0\x1b\x9e\xf5\xfdS\x89\xa2h\xcf\x9a\x03\xef\xc4\xf8cF\x92\r\xbf4i\x11g\xc83\x0f\x8c\xb9\xa2#\x8e\x1bn\xd91g\xc0\x91%\xd7\xdc\xf3M\x83<i:L\xa8\xf1\x19\xfa\xffw\xfd\xf0\xc5\x94:O\x9cH\x85\xcc6#\x1e\xc3\xf6\x05\xe5\xa0\xc7\x96\x04]J\\\x90\xa1\x1f~Ty\xe1\x8d\x15w|P\xe4\x95K\xb2!\xe3\x0cw)%I'
But on the server end, the received string is completely different:
�4ig�3���#�n�1g��%���M�<i:L����w��Ŕ:O�H��6#���ǖ]J\��~Ty�w|P�K�!�w)%I
I also tried to send the bytes as string, by
r = requests.post("http://127.0.0.1:5000/predict", data={'imgb64' : str(data)})
But, I can't decompress the data on the server end.
It seems, I can't send the zlib compressed bytes directly, so I used base64 to encode the data into ascii string.
So, in summary this worked for me, numpy array/any non-string data -> zlib compression -> base64 encode -> post request -> flask -> base64 decode -> zlib decompress
client.py
import numpy as np
import base64
import zlib
import requests
frame = np.random.randint(0,255,(5,5,3)) # dummy rgb image
# compress
data = zlib.compress(frame)
data = base64.b64encode(data)
data_send = data
data2 = base64.b64decode(data)
data2 = zlib.decompress(data2)
fdata = np.frombuffer(data2, dtype=np.uint8)
print(fdata)
r = requests.post("http://127.0.0.1:5000/predict", data={'imgb64' : data_send})
server.py
from flask import Flask
from flask_restful import Resource, Api, reqparse
import json
import numpy as np
import base64
# compression
import zlib
import codecs
app = Flask(__name__)
api = Api(app)
parser = reqparse.RequestParser()
parser.add_argument('imgb64', help = 'type error')
class Predict(Resource):
def post(self):
data = parser.parse_args()
#print(data)
if data['imgb64'] == "":
return {
'data':'',
'message':'No file found',
'status':'error'
}
#img = open(data['imgb64'], 'r').read() # doesn't work
img = data['imgb64']
data2 = img.encode()
data2 = base64.b64decode(data2)
data2 = zlib.decompress(data2)
fdata = np.frombuffer(data2, dtype=np.uint8)
print(fdata)
if img:
return json.dumps({
'data': 'done',
'message':'darknet processed',
'status':'success'
})
return {
'data':'',
'message':'Something when wrong',
'status':'error'
}
api.add_resource(Predict,'/predict')
if __name__ == '__main__':
app.run(debug=True, host = '0.0.0.0', port = 5000, threaded=True)

File upload using Flask

I am trying to implement a python API in order to upload a file on my server but for an unknown reason, it doesn't run.
From my understanding, the app.py is not recognised
Here is my API.py
from flask_cors import CORS
from flask_restful import Api, Resource, reqparse
import sqlite3
import uuid
import os
import csv
import urllib.request
import threading
import queue as Queue
import subprocess
import json
import re
import datetime
app = Flask(__name__)
api = Api(app)
CORS(app)
class upload(Resource):
def post(self):
ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'])
def allowed_file(filename):
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
def upload_file():
# check if the post request has the file part
if 'file' not in request.files:
resp = jsonify({'message' : 'No file part in the request'})
resp.status_code = 400
return resp
file = request.files['file']
int = str(request.form['int']) #true or false
if file.filename == '':
resp = jsonify({'message' : 'No file selected for uploading'})
resp.status_code = 400
return resp
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return json.dumps(data), 200
else:
resp = jsonify({'message' : 'Allowed file types are doc, etc.'})
resp.status_code = 400
return resp
api.add_resource(upload, "/api/v1/upload")
app.run(host='0.0.0.0', debug=True)
Here is my app.py
UPLOAD_FOLDER = '/home/xxxx/xxx/upload'
app = Flask(__name__)
#app.secret_key = "secret key"
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
Can you please help? Any suggestions?
As per the comment, it looks like you have two sepearate applications here.
I would just stick with the first one API.py, but you'll need to move the lines where you set the config variables into API.py:
So after this line in API.py:
app = Flask(__name__)
Immediately set the config values:
UPLOAD_FOLDER = '/home/xxxx/xxx/upload'
#app.secret_key = "something super secret"
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
Then execute with:
python API.py

Resources