For many years, the Swanage webcams page on our website Virtual Swanage has linked to third-party webcams located in our hometown and surrounding area. You can find the webcams page at

We have recently added a new webcam located on the roof of our home so we can have our own webcam feed on the website.

History of the Virtual Swanage Webcams

In the early days of the website in the late 1990s, we had the first webcam in the town located at Rutherfords Photographic, situated in the High Street owned by a friend Andy Andy Farrer). The webcam was a simple USB camera with a resolution of 640x480 and connected to a PC using Webcam32 software. It uploaded an image every 5 minutes to the website.

The shop only had dial-up internet, and broadband connections would not appear until several years later. This restricted us to static images as there was no bandwidth to stream video.

The webcam proved very popular and greatly increased the traffic to the website and was featured in the local press.

Our previous home-based webcams

In 2005 we purchased a used Axis IP external security camera with built-in FTP (File Transfer Protocol), so it could take a picture and upload it to our web server for the website without needing a computer running 24 hours a day.

Our internet service does not have a very good upload speed, so we did not want continuous video stream uploading as it would use most of our available bandwidth.

Hosting a live steaming camera with a third-party provider is another monthly expense. As our income from the Virtual Swanage website does not cover the hosting fees, we did not want to add additional monthly bills.

The Axis camera lasted a few years before failing, and as the cost of the externally rated cameras with built-in FTP upload was very high, we decided not to replace it.

TP Link Camera

Tp-link Tapo TC65 Outdoor Security Wi-Fi Camera Purchase

This summer, we decided it was time to look again at putting an external camera on our roof to use on the Virtual Swanage website. After looking at various options, we ordered a tp-link TC65 Outdoor Security Wi-Fi Camera, which cost £45.

The camera specifications are:

The camera does not have the option to upload an image directly to our server but supports the Real Time Streaming Protocol (RTSP), which allows the capture of the video stream by another device on the network.

The camera has connections for power and an RJ45 network connection. We do not want to connect another network device on the roof after the lightning strike a few years ago, which destroyed all of our networked devices and computers.

We mounted the new camera to the bracket which holds our TV antenna, and the power cable is routed into the loft space via one of the window frames. This goes into the supplied PSU adapter to power the camera.

Setting up the Tapo Camera for RTSP streaming

To use the RTSP protocol, you must set a username and password in the camera and find its IP address.

The camera uses the following URL format to access the video stream:


Your internet router assigns the camera's IP address; you can find it on your router's DHCP list or in the Tapo app.

Finding the IP address in the Tapo app

  1. On the app's home page, select your camera model/name.
  2. Tap the settings icon (cog) at the top-right of the app.
  3. Tap the camera name at the top of the Camera Settings screen.
  4. The network settings will be shown, including the IP address and MAC address of the device.

Setting the username and password

  1. On the app's home page, select your camera model/name.
  2. Tap the settings icon (cog) at the top-right of the app.
  3. Tap Advanced Settings, then select Device Account
  4. Enter your new username and password and save.

Capturing the Images

OpenCV Logo

We have a small Linux server running 24/7 in our network cupboard, and with help from Chat GPT, we created a Python script to access the video feed via its RTSP URL and OpenCV.

OpenCV is a library of programming functions mainly for real-time computer vision and processing.

The Python script uses a threading timer to capture a new frame every 30 seconds. 

The capture function first opens the video stream and captures the current frame. If this succeeds, it crops the image to a 1920 x 1080 size.

There are windows visible in the houses directly in front of the camera, which are blurred using the GaussianBlur function in OpenCV.

The image is then saved to a folder on the server with a timestamp, and a separate script uploads the latest image to the server every 30 seconds.

In the future if our internet provider decides to upgrade us to fibre broadband we will look at setting up a video stream and upgrading to a better quality 4K camera.

Image from camera

The capture and save Python script is listed below:



from __future__ import absolute_import, division, print_function, \
import time
import datetime as dt
import os
import cv2
import traceback
import threading

# Example usage
rtsp_url = "rtsp://username:password@ipaddress/stream1"
image_folder = "/home/user/webcam/images"
video_folder = "/home/user/webcam/videos"
capture_interval = 30  # seconds

error_log_file = "/home/user/webcam/webcamerror.log"

blur1_x = 0  # X-coordinate of the top-left corner of the blur region
blur1_y = 1000  # Y-coordinate of the top-left corner of the blur region
blur1_width = 200  # Width of the blur region
blur1_height = 80  # Height of the blur region

blur2_x = 254  # X-coordinate of the top-left corner of the blur region
blur2_y = 936  # Y-coordinate of the top-left corner of the blur region
blur2_width = 30  # Width of the blur region
blur2_height = 50  # Height of the blur region

blur3_x = 519  # X-coordinate of the top-left corner of the blur region
blur3_y = 936  # Y-coordinate of the top-left corner of the blur region
blur3_width = 52  # Width of the blur region
blur3_height = 80  # Height of the blur region

blur4_x = 598  # X-coordinate of the top-left corner of the blur region
blur4_y = 936  # Y-coordinate of the top-left corner of the blur region
blur4_width = 52  # Width of the blur region
blur4_height = 80  # Height of the blur region

blur5_x = 684  # X-coordinate of the top-left corner of the blur region
blur5_y = 936  # Y-coordinate of the top-left corner of the blur region
blur5_width = 52  # Width of the blur region
blur5_height = 80  # Height of the blur region

cap = None

def blur_region(frame, x, y, width, height):
    # Create a region of interest (ROI) for blurring
    roi = frame[y:y+height, x:x+width]

    # Apply Gaussian blur to the ROI
    blurred_roi = cv2.GaussianBlur(roi, (99, 99), 0)

    # Replace the ROI with the blurred version
    frame[y:y+height, x:x+width] = blurred_roi

    return frame

def get_image():

    print("Opening video stream")
    cap = cv2.VideoCapture(rtsp_url)

    # Check if the video stream is opened successfully
    if cap.isOpened():       
        # Get the original frame size
        frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))

        # Calculate the crop coordinates
        crop_x = 0
        crop_y = 0
        crop_width = min(frame_width, 1920)
        crop_height = min(frame_height, 1080)

        # Read the current frame from the video stream
        ret, frame =

        # Check if the frame was read successfully
        if ret:
            # Crop the frame
            frame = frame[crop_y:crop_y + crop_height, crop_x:crop_x + crop_width]

            # Blur the specified region of the frame
            frame = blur_region(frame, blur1_x, blur1_y, blur1_width, blur1_height)
            frame = blur_region(frame, blur2_x, blur2_y, blur2_width, blur2_height) 
            frame = blur_region(frame, blur3_x, blur3_y, blur3_width, blur3_height) 
            frame = blur_region(frame, blur4_x, blur4_y, blur4_width, blur4_height)
            frame = blur_region(frame, blur5_x, blur5_y, blur5_width, blur5_height) 

            # Save the frame as a JPEG image
            timestamp = time.strftime("%Y%m%d%H%M%S")

            filename = f"{image_folder}/webcam-{timestamp}.jpeg"
            cv2.imwrite(filename, frame, [cv2.IMWRITE_JPEG_QUALITY, 95])
            print(f"Saved frame {filename}")

            print("Error reading frame")

        print("Error opening video stream")



def restart_thread():
    st = threading.Timer(capture_interval, get_image)
    st.daemon = True

def main():
    Main program function
    global cap


    while (True):

if __name__ == "__main__":