Home » Tutorials » How to Create a Secure Video Streaming Server and Client in Python

How to Create a Secure Video Streaming Server and Client in Python

Ever thought about how real-time video streaming actually works? In a world where video calls and live streams are part of our daily lives, getting a grip on the basics of video streaming is super useful. Maybe you’re dreaming of building your own surveillance system, setting up a remote monitoring tool, or just diving into the exciting realm of network programming. Whatever your goal, this tutorial is here to guide you through every step of the process.

In today’s article, we are going to write two Python scripts (server and client) that create a one-way video streaming system. The client captures video from its camera and streams it in real-time to the server. The server then receives the video data, displays it, and saves it to a file. We’ll cover building, streaming, and securing video transmission using Python.

Let’s get started!

Table of Contents

Disclaimer

Please note: This tutorial is for educational purposes only. Use the code and techniques responsibly and only on networks and devices you own or have permission to use. Unauthorized access to systems or data is illegal.

Necessary Libraries

Let’s get everything set up before we dive into the code part. So, make sure to install the opencv-python and numpy libraries for the code to function properly:

$ pip install opencv-python 
$ pip install numpy 

Server Side

Imports

import socket
import cv2
import numpy as np
import threading

As you all should know, before heading out on any project, you should arm yourself with the appropriate tools, which is what we are going to do. So, without further ado, let us start with the imports:

  • First up is socket. This handy library will enable smooth communication between our server and client, acting as the backbone of our network interactions.
  • Next, we have cv2. This powerhouse is responsible for all our image processing and video stream handling. It’s like having a magic wand for anything visual!
  • Third on the list is numpy. Known for its prowess in numerical computations, it will help us efficiently handle frame data, making our processing tasks a breeze.
  • The last import is threading. This one will allow us to multitask like pros, ensuring our project runs smoothly and efficiently by handling multiple tasks simultaneously.

The Receive Video Function

def receive_video(conn, video_writer, stop_event):
   while not stop_event.is_set():
       try:
           data = conn.recv(4)
           if not data:
               break


           length = int.from_bytes(data, byteorder='big')
           frame_data = b""
           while len(frame_data) < length:
               packet = conn.recv(length - len(frame_data))
               if not packet:
                   break
               frame_data += packet


           if len(frame_data) != length:
               break


           frame = np.frombuffer(frame_data, dtype=np.uint8)
           frame = cv2.imdecode(frame, cv2.IMREAD_COLOR)
           if frame is None:
               break


           video_writer.write(frame)


           cv2.imshow('Receiving Video', frame)
           if cv2.waitKey(1) & 0xFF == ord('q'):
               break
       except Exception as e:
           print(f"Error receiving video: {e}")
           break


   if video_writer:
       video_writer.release()
   cv2.destroyAllWindows()

Welcome to the heartbeat of this system. Now, you must be wondering why this function is so important that it’s called the heartbeat. Well, it’s because it constantly receives and decodes video frames, ensures data integrity, displays the video in real-time, handles errors, and manages resources. Impressive, right Let’s dive into the details.

This function’s job is to keep the video stream flowing smoothly from the client. Here’s how it works:

  • First, it receives the initial bytes (conn.recv(4)) that tell us the length of the upcoming frame data. This data is then converted into an integer to get the frame size. The function then collects all this data in chunks (frame_data), which are converted into a NumPy array. This array is like raw material that gets decoded into an image format.
  • Once we have the image, it’s written to the output video file and displayed in real-time using cv2.imshow(). This means you can watch the video as it’s being recorded!
  • But that’s not all. This function is also on the lookout for a stop command. If it detects one, it gracefully stops receiving video from the client and cleans up all resources. And if anything goes wrong, the try-except block ensures that any exception triggers an error message, keeping you informed.

Server Program Setup

Next, before issuing any commands to the client, we need to set up a command center. If we’re going to give commands, we need to establish a connection first. So, buckle up!

First, we need to establish a connection, and for that, we need an IP address for our server. This IP address acts like the neighborhood the client needs to visit. Next, we set a port number for our server to listen on—this is like giving the client the exact door number to knock on once they reach the neighborhood.

Now, the exciting part: creating the server. We start by creating a socket object, which is like our communication device. Then, we bind this device to our IP address and port using server_socket.bind((host, port)). This binding process is what turns our setup into a fully operational server, ready to listen for and accept incoming connections.

With this setup, our command center is up and running, ready to take on any client connections that come its way!

def server_program():
   host = '127.0.0.1'
   port = 65432


   server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
   server_socket.bind((host, port))
   server_socket.listen(1)


   print(f"Server listening on {host}:{port}")
   conn, addr = server_socket.accept()
   print(f"Connected by {addr}")

Handling Commands

With our command center established, it is time for us to take charge and start issuing commands to the client. What are those commands, and how will they be handled? Let’s dive into it, so pay close attention:

First, this command-handling loop starts by prompting the user to input a command: “start”, “stop” or “quit”. Once the command is entered, it is read, processed, and sent to the client using conn.send(). What happens next depends on the type of command:

  • If the command is “start”: The server_program() function initializes the video_writer to save the received video in our Python file. It also clears the stop_event, creates, and starts a thread that runs the receive_video() function.
  • If the command is “stop”: It sets the stop_event to stop video reception, waits for the video thread to finish completely, and then resets the video_writer to None so that the “start” command can be initiated again.
  • If the command is “quit”: It sets the stop_event and waits for the video thread to finish. Once that’s done, it breaks out of the loop, effectively stopping the connection with the client.

If any exception occurs during this loop, an error message will be displayed.

   video_writer = None
   stop_event = threading.Event()
   video_thread = None


   try:
       while True:
           command = input("Enter command (start/stop/quit): ").strip().lower()
           if command in ('start', 'stop', 'quit'):
               conn.send(command.encode())
               if command == 'start':
                   if video_writer is None:
                       video_writer = cv2.VideoWriter('output.avi', cv2.VideoWriter_fourcc(*'XVID'), 20, (640, 480))
                   stop_event.clear()
                   video_thread = threading.Thread(target=receive_video, args=(conn, video_writer, stop_event))
                   video_thread.start()
               elif command == 'stop':
                   stop_event.set()
                   if video_thread and video_thread.is_alive():
                       video_thread.join()
                   video_writer = None  # Reset video_writer to create a new file on next start command
               elif command == 'quit':
                   stop_event.set()
                   if video_thread and video_thread.is_alive():
                       video_thread.join()
                   break
   except Exception as e:
       print(f"Error: {e}")
   finally:
       if video_writer:
           video_writer.release()
       conn.close()
       server_socket.close()
       cv2.destroyAllWindows()

Main Entry Point

Here’s the grand finale! This part makes sure our script runs only when we execute it directly and not when it’s imported as a module. Plus, it kicks off the server_program() function.

if __name__ == "__main__":
   server_program()

Server Full Code

import socket
import cv2
import numpy as np
import threading


def receive_video(conn, video_writer, stop_event):
   while not stop_event.is_set():
       try:
           data = conn.recv(4)
           if not data:
               break


           length = int.from_bytes(data, byteorder='big')
           frame_data = b""
           while len(frame_data) < length:
               packet = conn.recv(length - len(frame_data))
               if not packet:
                   break
               frame_data += packet


           if len(frame_data) != length:
               break


           frame = np.frombuffer(frame_data, dtype=np.uint8)
           frame = cv2.imdecode(frame, cv2.IMREAD_COLOR)
           if frame is None:
               break


           video_writer.write(frame)


           cv2.imshow('Receiving Video', frame)
           if cv2.waitKey(1) & 0xFF == ord('q'):
               break
       except Exception as e:
           print(f"Error receiving video: {e}")
           break


   if video_writer:
       video_writer.release()
   cv2.destroyAllWindows()


def server_program():
   host = '127.0.0.1'
   port = 65432


   server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
   server_socket.bind((host, port))
   server_socket.listen(1)


   print(f"Server listening on {host}:{port}")
   conn, addr = server_socket.accept()
   print(f"Connected by {addr}")


   video_writer = None
   stop_event = threading.Event()
   video_thread = None


   try:
       while True:
           command = input("Enter command (start/stop/quit): ").strip().lower()
           if command in ('start', 'stop', 'quit'):
               conn.send(command.encode())
               if command == 'start':
                   if video_writer is None:
                       video_writer = cv2.VideoWriter('output.avi', cv2.VideoWriter_fourcc(*'XVID'), 20, (640, 480))
                   stop_event.clear()
                   video_thread = threading.Thread(target=receive_video, args=(conn, video_writer, stop_event))
                   video_thread.start()
               elif command == 'stop':
                   stop_event.set()
                   if video_thread and video_thread.is_alive():
                       video_thread.join()
                   video_writer = None  # Reset video_writer to create a new file on next start command
               elif command == 'quit':
                   stop_event.set()
                   if video_thread and video_thread.is_alive():
                       video_thread.join()
                   break
   except Exception as e:
       print(f"Error: {e}")
   finally:
       if video_writer:
           video_writer.release()
       conn.close()
       server_socket.close()
       cv2.destroyAllWindows()


if __name__ == "__main__":
   server_program()

Now that we’ve finished explaining the server code that displays and saves the video, let’s move on to the client code, which will receive the server commands and stream the video:

Client Side

Necessary Imports

Well, if you want to build or create anything, you need the appropriate tools, and this script is no different. Since we will be streaming video to the server, we need to establish a connection, which is why we import socket. Notice the word “stream“; it means we need to handle video capture and encoding, so we import cv2 from OpenCV.

Last but not least, we import threading to manage multiple tasks simultaneously, such as listening for commands from the server while sending the video.

import socket
import cv2
import threading

The Send Video Function

This function is the one that performs the main objective of this script, which is to send the video. You can think of it as the heart of the script. So, how does it work? Let me explain:

First, the function uses cv2 to start the camera and begin capturing video. Then, while the stop_event is not set, it captures a frame from the camera and encodes it as a JPEG image. Each frame is then sent over the network using socket. The length of the frame is sent first, followed by the frame itself.

Naturally, if any error occurs during this process, an error message will be printed.

def send_video(client_socket, stop_event):
   cap = cv2.VideoCapture(0)
   try:
       while not stop_event.is_set():
           ret, frame = cap.read()
           if not ret:
               break


           _, frame_encoded = cv2.imencode('.jpg', frame)
           frame_data = frame_encoded.tobytes()


           length = len(frame_data)
           client_socket.sendall(length.to_bytes(4, byteorder='big') + frame_data)
   except Exception as e:
       print(f"Error sending video: {e}")
   finally:
       cap.release()

Building the Command Receiver

Now, let’s dive into the command central (client_program() function). This is where we receive orders from the server. But first, we need to establish a connection with the server. We use socket to connect to the server’s IP address and port number, effectively telling the server we are ready to receive orders (client_socket.recv).

Here’s how the commands work:

  • If the command issued by the server is “start” video transmission begins. We clear the stop_event and create a new thread to run the send_video() function.
  • If the command is “stop” video transmission to the server stops. We set the stop_event and ensure the thread has finished executing.
  • If the command is “quit” we set the stop_event and end the program, effectively closing the connection.

If any exception occurs during this process, an error message will be printed.

def client_program():
   host = '127.0.0.1'  # Replace with the server's IP address
   port = 65432


   client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
   try:
       client_socket.connect((host, port))
       print("Connected to server. Waiting for commands...")


       stop_event = threading.Event()
       video_thread = None


       while True:
           try:
               command = client_socket.recv(1024).decode()
               if command == 'start':
                   stop_event.clear()
                   video_thread = threading.Thread(target=send_video, args=(client_socket, stop_event))
                   video_thread.start()
               elif command == 'stop':
                   stop_event.set()
                   if video_thread and video_thread.is_alive():
                       video_thread.join()
               elif command == 'quit':
                   stop_event.set()
                   if video_thread and video_thread.is_alive():
                       video_thread.join()
                   break
           except Exception as e:
               print(f"Error receiving command: {e}")
               break
   except Exception as e:
       print(f"Connection error: {e}")
   finally:
       client_socket.close()

Starting the Client Program

Lastly, this part is essentially the switch that starts the entire script by triggering the client_program() function. It also ensures that the script is run directly and not imported as a module.

if __name__ == "__main__":
   client_program()

Client Full Code

import socket
import cv2
import threading


def send_video(client_socket, stop_event):
   cap = cv2.VideoCapture(0)
   try:
       while not stop_event.is_set():
           ret, frame = cap.read()
           if not ret:
               break


           _, frame_encoded = cv2.imencode('.jpg', frame)
           frame_data = frame_encoded.tobytes()


           length = len(frame_data)
           client_socket.sendall(length.to_bytes(4, byteorder='big') + frame_data)
   except Exception as e:
       print(f"Error sending video: {e}")
   finally:
       cap.release()


def client_program():
   host = '127.0.0.1'  # Replace with the server's IP address
   port = 65432


   client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
   try:
       client_socket.connect((host, port))
       print("Connected to server. Waiting for commands...")


       stop_event = threading.Event()
       video_thread = None


       while True:
           try:
               command = client_socket.recv(1024).decode()
               if command == 'start':
                   stop_event.clear()
                   video_thread = threading.Thread(target=send_video, args=(client_socket, stop_event))
                   video_thread.start()
               elif command == 'stop':
                   stop_event.set()
                   if video_thread and video_thread.is_alive():
                       video_thread.join()
               elif command == 'quit':
                   stop_event.set()
                   if video_thread and video_thread.is_alive():
                       video_thread.join()
                   break
           except Exception as e:
               print(f"Error receiving command: {e}")
               break
   except Exception as e:
       print(f"Connection error: {e}")
   finally:
       client_socket.close()


if __name__ == "__main__":
   client_program()

Example

I ran this code on Windows as shown below in the image:

Also on Linux system:

Conclusion

Congrats! You’ve just built a secure video streaming server and client in Python. Now you know how to capture video, stream it in real-time, and securely transmit it between the client and server. This is just the beginning—there’s so much more you can do with this knowledge. Keep experimenting and see where your creativity takes you in the world of network programming!

Happy Coding!

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top