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 first step is to write some code that checks the camera location. For this i’m going to use a simple ping test. In my case the most likely reason for downtime is to check whether or not the router/modem responds. The camera itself is very stable and has been running non-stop for about a year.

Imports and variables

import os
import time
import ffmpeg

# Variables
stream_key = 'your-you-tube-stream-key' # change
host = '192.168.1.1' # change
timer_main_seconds = 900
timer_main_interval = 5

Ping

Ping will be handled by the os library and is just a simple function taking in the host ip/dns name and the number of pings to be sent. Returns True if a ping response has been received.

def ping_host(host, count):
    response = os.system(f"ping -c {count} {host}")

    if response == 0:
        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 (ping) the host connection 5 times. When a ping response is received it will reset the timer to 900 seconds and check again (etc). If no ping 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 ping response from the host.

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

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

            else:
                print('[*] Connection OK')

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.