├── .gitignore ├── README.md ├── consumer ├── consumer.py └── templates │ └── index.html ├── producer └── producer.py └── videos └── Countdown1.mp4 /.gitignore: -------------------------------------------------------------------------------- 1 | kafkaenv/ 2 | env/ 3 | .vscode/ 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Distributed Streaming with Python and Kafka 2 | Source code for a project detailed [here](https://medium.com/@kevin.michael.horan/distributed-video-streaming-with-python-and-kafka-551de69fe1dd) in my blog. 3 | -------------------------------------------------------------------------------- /consumer/consumer.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from flask import Flask, Response, render_template 3 | from kafka import KafkaConsumer 4 | 5 | # Fire up the Kafka Consumer 6 | topic = "distributed-video1" 7 | 8 | consumer = KafkaConsumer( 9 | topic, 10 | bootstrap_servers=['localhost:9092']) 11 | 12 | 13 | # Set the consumer in a Flask App 14 | app = Flask(__name__) 15 | 16 | @app.route('/') 17 | def index(): 18 | return render_template('index.html') 19 | 20 | @app.route('/video_feed', methods=['GET']) 21 | def video_feed(): 22 | """ 23 | This is the heart of our video display. Notice we set the mimetype to 24 | multipart/x-mixed-replace. This tells Flask to replace any old images with 25 | new values streaming through the pipeline. 26 | """ 27 | return Response( 28 | get_video_stream(), 29 | mimetype='multipart/x-mixed-replace; boundary=frame') 30 | 31 | def get_video_stream(): 32 | """ 33 | Here is where we recieve streamed images from the Kafka Server and convert 34 | them to a Flask-readable format. 35 | """ 36 | for msg in consumer: 37 | yield (b'--frame\r\n' 38 | b'Content-Type: image/jpg\r\n\r\n' + msg.value + b'\r\n\r\n') 39 | 40 | if __name__ == "__main__": 41 | app.run(host='0.0.0.0', debug=True) 42 | -------------------------------------------------------------------------------- /consumer/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Video Streaming Demonstration 4 | 5 | 6 |

Video Streaming Demonstration

7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /producer/producer.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import cv2 4 | from kafka import KafkaProducer 5 | 6 | topic = "distributed-video1" 7 | 8 | def publish_video(video_file): 9 | """ 10 | Publish given video file to a specified Kafka topic. 11 | Kafka Server is expected to be running on the localhost. Not partitioned. 12 | 13 | :param video_file: path to video file 14 | """ 15 | # Start up producer 16 | producer = KafkaProducer(bootstrap_servers='localhost:9092') 17 | 18 | # Open file 19 | video = cv2.VideoCapture(video_file) 20 | 21 | print('publishing video...') 22 | 23 | while(video.isOpened()): 24 | success, frame = video.read() 25 | 26 | # Ensure file was read successfully 27 | if not success: 28 | print("bad read!") 29 | break 30 | 31 | # Convert image to png 32 | ret, buffer = cv2.imencode('.jpg', frame) 33 | 34 | # Convert to bytes and send to kafka 35 | producer.send(topic, buffer.tobytes()) 36 | 37 | time.sleep(0.2) 38 | video.release() 39 | print('publish complete') 40 | 41 | def publish_camera(): 42 | """ 43 | Publish camera video stream to specified Kafka topic. 44 | Kafka Server is expected to be running on the localhost. Not partitioned. 45 | """ 46 | 47 | # Start up producer 48 | producer = KafkaProducer(bootstrap_servers='localhost:9092') 49 | 50 | 51 | camera = cv2.VideoCapture(0) 52 | try: 53 | while(True): 54 | success, frame = camera.read() 55 | 56 | ret, buffer = cv2.imencode('.jpg', frame) 57 | producer.send(topic, buffer.tobytes()) 58 | 59 | # Choppier stream, reduced load on processor 60 | time.sleep(0.2) 61 | 62 | except: 63 | print("\nExiting.") 64 | sys.exit(1) 65 | 66 | 67 | camera.release() 68 | 69 | 70 | if __name__ == '__main__': 71 | """ 72 | Producer will publish to Kafka Server a video file given as a system arg. 73 | Otherwise it will default by streaming webcam feed. 74 | """ 75 | if(len(sys.argv) > 1): 76 | video_path = sys.argv[1] 77 | publish_video(video_path) 78 | else: 79 | print("publishing feed!") 80 | publish_camera() -------------------------------------------------------------------------------- /videos/Countdown1.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmhoran/DistributedStreamingWithPythonAndKafka/975ed60c95fbf6a20a34e273ea166be8939252c9/videos/Countdown1.mp4 --------------------------------------------------------------------------------