Youtube Livestream – Keep Online

Intro

Nearly one year I’ve kept my camera livestream online using FFmpeg as a drop-in for downtime i manually detected or did maintenance work on the camera. Until last week, when the ISP did major maintenance at 3 in the morning. Although Youtube can cope with short dropouts, being offline for nearly 12 hours meant that the stream was cut. Unfortunately this results in a new URL which in turn means updating every embedded instance. This is not what i wanted so i wrote a little Python script that monitors the camera location and fires up FFmpeg from another location with a ‘Camera Down’ feed to Youtube.

Creating a video loop

The first step i took was to download a short clip from the camera using RTSP. This clip is going to serve as a loop for FFmpeg. When the Python script detects a connection dropout on the camera location.

FFmpeg

Using FFmpeg i grabbed a 10 minute clip directly from the camera using RTSP. (The URL i’m using applies to most Dahua camera’s)

ffmpeg -rtsp_transport tcp -i "rtsp://user:pass@ip-address:554/cam/realmonitor?channel=1&subtype=0" -c copy -map 0 offline.mp4

This clip needs to be trimmed and modified. For that i’m going to use Adobe Premiere Pro 2022

Adobe Premiere Pro

The camera, and therefor the clip, has a resolution of 2560x1440px with H264 video and AAC audio codec. I wanted to keep the clip exactly the same so that Youtube would’t complain about a change in resolution or codec.

Not going to go through the whole process how I’ve edited this clip in Adobe Premiere Pro but there are a couple steps I’ve taken to get my offline clip

  • Trim the clip so that you create a loop
  • Apply black and white effect to the video
  • Apply camera blur (5) effect to the video
  • Insert a rectangle. (no fill, stroke 15)
  • Insert text (250px)

When finished export the clip using setting which best resemble the original feed settings

  • Video codec: H264
  • Video bitrate: CBR 10M
  • Audio code: AAC
  • Audio bitrate: 300K

Which would result in something comparable to this screencap

This part is done. What we ended up with is a video loop with the exact same settings the camera feed delivers to Youtube

Python

We have our looped clip ready to go, now we can proceed to writing some code that pushes this clip to Youtube when the camera connection is lost. The best way to see if the connection from the camera to Youtube is running is to check whether or not a remote login is possible to the camera. If so, we can safely assume that the connection to the internet is functioning and thus streaming is working.

Imports and variables

import ffmpeg
import requests
from time import sleep
from requests.auth import HTTPDigestAuth
from requests.exceptions import ConnectTimeout

# Variables
stream_key = 'your-you-tube-stream-key' # change
host = '192.168.1.1' # change
username = 'user' #change
password = 'pass1234' #change
timer_main_seconds = 900
timer_main_interval = 5
timeout = 3

HTTP API

Dahua camera’s can be remotely controlled by the HTTP API. This is a CGI based API that can control the camera, modify settings and read out statistics for example. I was hoping there would be a value that could tell me if RTMP was functioning but unfortunately it does not have that capability. The easiest method to check if the camera is up was to just try to log in. If i’d get a HTTP 200 response, login was successful and i can assume the stream is running as well. Not that eloquent but it works and checks for several crucial steps

  • Internet working at the remote location
  • Local network functioning at remote location
  • Camera functioning

For logging in and accessing the HTTP API i’m using the Python requests library

def check_connection(host, count):
    session = requests.Session()
    session.auth = HTTPDigestAuth(username, password)

    successCount = 0

    for i in range(count):
        try:
            response = session.get('http://{host}/cgi-bin/magicBox.cgi?action=getSerialNo'.format(host=host), timeout=timeout)
            if response.status_code == 200:
                successCount += 1
                sleep(1)
        except ConnectTimeout:
            sleep(1)

    if successCount >= 1:
        return True
    else:
        return False

Timer

Next i created a simple countdown timer which takes in the number of seconds to count down from and an interval. Returns True if the countdown timer is finished.

def countdown_timer(seconds, interval):
    print(f'[*] Starting timer for {seconds if seconds <= 60 else round(seconds / 60)} {"seconds" if seconds <= 60 else "minutes"} with a {interval} second interval')
    while seconds > 0:
        time.sleep(interval)
        seconds -= interval
        if seconds % 100 == 0:
            print(f'[*] Timer running: {seconds} seconds left')

    return True

Python-FFmpeg

Python-ffmpeg library is used to launch FFmpeg in an asynchronous thread we can communicate with using stdin. Since the input clip has the exact same resolution and codec as the original feed i could use vcodec/acodec copy which omits the use of transcoding and has very little impact on the CPU.

process = (
    ffmpeg
    .input('offline.mp4', stream_loop='-1', re=None)
    .output('rtmp://a.rtmp.youtube.com/live2/' + stream_key, vcodec='copy', acodec='copy', format='flv')
)

Main thread

And finally the main thread triggering all of the above components. It starts up entering a while loop starting the countdown timer. When it reaches 0 from 900 seconds (15 minutes) it starts to check (http 200) the host connection 5 times. When a response is received the timer will reset to 900 seconds and check again (etc). If no response is received it assumes the connection is down and launches FFmpeg. FFmpeg loops through the offline.mp4 clip indefinitely until it receives a successful response from the camera.

if __name__ == "__main__":
    print(f'[*] Monitoring connection: {host}')

    try:
        while True:
            if countdown_timer(timer_main_seconds, timer_main_interval) == True:
                print('[*] Checking connection')
                if check_connection(host, 5) == False:
                    print('[!] Connection failed. Starting FFmpeg')
                    process = process.run_async(pipe_stdin=True)
                    print('Starting FFMpeg')
                    while countdown_timer(5, 1):
                        if check_connection(host, 5) == True:
                            print('[*] Connection restored. Stopping FFmpeg')
                            process.communicate(str.encode('q'))
                            sleep(3)
                            process.terminate()
                            break
                        else:
                            print('[*] Connection still down')

                else:
                    print('[*] Connection OK')
    except KeyboardInterrupt:
        print('[!] Quitting program')
        process.communicate(str.encode('q'))
        sleep(3)
        process.terminate()

Crontab

Use crontab to launch the script at startup. (i’m running this script in a virtual environment)

crontab -e

And insert this line (yours will differ)

@reboot /path/python3 /path/check_connection.py > /path/connection.log 2>&1

Demonstration

In the video below i’ll demonstrate on one of my testing locations how the program behaves. For demonstration purposes have set the countdown timer to 10 seconds and will manually pull the camera’s network cable from the switch.

PayPal

If you like my work, please consider supporting.