I have a simple flask application that demonstrates how to stream webcam video using the client's webcam. When I run the application locally everything runs fine but when I try to deploy the application using docker on render.com and gunicorn(for production) I get the following errors:
flask-client-camera |
flask-client-camera | Error: class uri 'eventlet' invalid or not found:
flask-client-camera |
flask-client-camera | [Traceback (most recent call last):
flask-client-camera | File "/usr/local/lib/python3.8/site-packages/gunicorn/util.py", line 124, in load_class
flask-client-camera | return pkg_resources.load_entry_point("gunicorn",
flask-client-camera | File "/usr/local/lib/python3.8/site-packages/pkg_resources/__init__.py", line 534, in load_entry_point
flask-client-camera | return get_distribution(dist).load_entry_point(group, name)
flask-client-camera | File "/usr/local/lib/python3.8/site-packages/pkg_resources/__init__.py", line 2930, in load_entry_point
flask-client-camera | return ep.load()
flask-client-camera | File "/usr/local/lib/python3.8/site-packages/pkg_resources/__init__.py", line 2517, in load
flask-client-camera | return self.resolve()
flask-client-camera | File "/usr/local/lib/python3.8/site-packages/pkg_resources/__init__.py", line 2523, in resolve
flask-client-camera | module = __import__(self.module_name, fromlist=['__name__'], level=0)
flask-client-camera | File "/usr/local/lib/python3.8/site-packages/gunicorn/workers/geventlet.py", line 18
flask-client-camera | from gunicorn.workers.async import AsyncWorker
flask-client-camera | ^
flask-client-camera | SyntaxError: invalid syntax
flask-client-camera | ]
This is my Dockerfile:
FROM python:3.8.13-slim-bullseye
WORKDIR /app
RUN apt-get -y update && apt-get install -y \
wget \
ffmpeg \
libsm6 \
libxext6
RUN pip install --upgrade setuptools
COPY requirements.txt .
RUN pip install -r requirements.txt
ADD . .
CMD gunicorn --worker-class eventlet -w 1 app:app
Flask app.py file:
import base64
import os
import cv2
import numpy as np
from flask import Flask, render_template, send_from_directory
from flask_socketio import SocketIO, emit
app = Flask(__name__, static_folder="./templates/static")
app.config["SECRET_KEY"] = "secret!"
socketio = SocketIO(app)
@app.route("/favicon.ico")
def favicon():
"""
The favicon function serves the favicon.ico file from the static directory.
:return: A favicon
"""
return send_from_directory(
os.path.join(app.root_path, "static"),
"favicon.ico",
mimetype="image/vnd.microsoft.icon",
)
def base64_to_image(base64_string):
"""
The base64_to_image function accepts a base64 encoded string and returns an image.
The function extracts the base64 binary data from the input string, decodes it, converts
the bytes to numpy array, and then decodes the numpy array as an image using OpenCV.
:param base64_string: Pass the base64 encoded image string to the function
:return: An image
"""
base64_data = base64_string.split(",")[1]
image_bytes = base64.b64decode(base64_data)
image_array = np.frombuffer(image_bytes, dtype=np.uint8)
image = cv2.imdecode(image_array, cv2.IMREAD_COLOR)
return image
@socketio.on("connect")
def test_connect():
"""
The test_connect function is used to test the connection between the client and server.
It sends a message to the client letting it know that it has successfully connected.
:return: A 'connected' string
"""
print("Connected")
emit("my response", {"data": "Connected"})
@socketio.on("image")
def receive_image(image):
"""
The receive_image function takes in an image from the webcam, converts it to grayscale, and then emits
the processed image back to the client.
:param image: Pass the image data to the receive_image function
:return: The image that was received from the client
"""
# Decode the base64-encoded image data
image = base64_to_image(image)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
frame_resized = cv2.resize(gray, (640, 360))
encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), 90]
result, frame_encoded = cv2.imencode(".jpg", frame_resized, encode_param)
processed_img_data = base64.b64encode(frame_encoded).decode()
b64_src = "data:image/jpg;base64,"
processed_img_data = b64_src + processed_img_data
emit("processed_image", processed_img_data)
@app.route("/")
def index():
"""
The index function returns the index.html template, which is a landing page for users.
:return: The index
"""
return render_template("index.html")
if __name__ == "__main__":
socketio.run(app, port=os.getenv("PORT", default=5000), debug=os.getenv("DEBUG", default=True), host='0.0.0.0')
Folder Structure:
├── app.py
├── Dockerfile
├── LICENSE.md
├── README.md
├── render.yaml
├── requirements.txt
└── templates
├── index.html
└── static
├── favicon.ico
└── script.js
requirements.txt:
Flask-SocketIO==4.3.1
python-engineio==3.13.2
python-socketio==4.6.0
Flask==2.0.3
Werkzeug==2.0.3
opencv_python==4.7.0.68
numpy==1.24.2
gunicorn==18.0
eventlet==0.33.3
The project can be found here on Github: https://github.com/Nneji123/flask-client-camera
You need to upgrade Gunicorn to a more recent version. The version that you are using is not compatible with Python 3.8, which considers the word async
a reserved keyword.