Skip to content

Instantly share code, notes, and snippets.

@TheFern2
Created September 15, 2023 01:41
Show Gist options
  • Save TheFern2/09fcb9d7d31559d9987c36cb30e28f8d to your computer and use it in GitHub Desktop.
Save TheFern2/09fcb9d7d31559d9987c36cb30e28f8d to your computer and use it in GitHub Desktop.
ring-mqtt motion snapshots with ffmpeg (NO SUBSCRIPTION) - home assistant ring doorbell or any rtsp camera
import os
import time
import datetime
import shutil
@service
def take_snapshot(uri=None, save_path=None, count=3):
IMAGE_COUNT = count
ffmpeg = f"/usr/bin/ffmpeg -y -i {uri}"
paths = []
time.sleep(3)
for i in range(IMAGE_COUNT):
now_time = datetime.datetime.now()
now_time_img_txt = now_time.strftime("%Y-%m-%d %H:%M:%S")
now_time_save_path = now_time.strftime("%Y%m%d-%H%M%S")
target_save_path = f"{save_path}/{now_time_save_path}.jpg"
os.system(f"{ffmpeg} -vframes 1 {target_save_path}")
paths.append(target_save_path)
_stamp_snapshot(target_save_path, now_time_img_txt)
time.sleep(2)
# remove images without stamp
for img in paths:
if os.path.exists(img):
os.remove(img)
# save last pic to latest dir OPTIONAL
# I use this latest/motion.jpg to send a HA notification and also show it
# in the dashboard as a local_file camera
latest_path = f"/config/www/frontdoor/latest"
if not os.path.exists(latest_path):
os.makedirs(latest_path)
last_img = paths[-1]
last_img_no_extension = last_img.split(".")[0]
last_img_path = f"{last_img_no_extension}_stamp.jpg"
# shutil.move(last_img_path, f"{save_path}/latest/motion.jpg")
shutil.move(last_img_path, f"{latest_path}/motion.jpg")
def _stamp_snapshot(img_path=None, text_stamp=None, font_path=None):
import PIL.Image as Image
import PIL.ImageDraw as ImageDraw
import PIL.ImageFont as ImageFont
TEST_FONT = "/usr/local/lib/python3.11/site-packages/aioslimproto/font/DejaVu-Sans-Condensed-Bold-Oblique.ttf"
img_no_file_ext = img_path.split(".")[0]
# img = Image.open("2023-09-14 09:02:43.245998.jpg")
img = Image.open(img_path)
w, h = img.size
draw = ImageDraw.Draw(img)
font = ImageFont.truetype(TEST_FONT, 25)
text_length = draw.textlength(text_stamp, font)
# draw.text((60, 50), text_stamp, fill=(0, 0, 0), font=font)
# draw.text(((w - text_length) // 2, h - 50), text_stamp, (255, 255, 255), font=font)
draw.text(((w - text_length) - 10, h - 50), text_stamp, (255, 255, 255), font=font)
img.save(f"{img_no_file_ext}_stamp.jpg")

Ring doorbell free snapshot notifications

For anyone interested in doing something similar, this is what I ended up doing for now, with no ring subscription. My goal is to get rid of every single subscription in the next few years.

  • detect motion with ring doorbell (via ring-mqtt)
  • call a pyscript service to take a few snapshots with ffmpeg (via pyscript service, custom script)
  • I save all snapshots under a directory, and the last one I save in a directory called latest
  • latest/motion.jpg is used to send a mobile ha notification, and also update a dashboard local file camera

NB: This guide will not cover mqtt broker setup, ring-mqtt install, ha notification, and local file camera. There are extensive guides for those items.

First, POC scripts for testing out functionality on your local machine before deploying to hass container.

NB: Make sure you have python3, and ffmpeg installed on your local machine. I tested this with homeassistant docker container, and it already had python3, ffmpeg, and pillow installed

ffmpeg_test.py:

import os
import time

RTSP_URL = "rtsp://..."
IMAGE_COUNT = 5

# ffplay = f"/opt/homebrew/bin/ffplay -rtsp_transport tcp {RTSP_URL} -autoexit -nodisp"
# os.system(ffplay)

# ffmpeg = f"/opt/homebrew/bin/ffmpeg -y -i {RTSP_URL}"
ffmpeg = f"/usr/bin/ffmpeg -y -i {RTSP_URL}"

time.sleep(3)
for i in range(IMAGE_COUNT):
    os.system(f"{ffmpeg} -vframes 1 test_{IMAGE_COUNT}.jpg")
    time.sleep(2)

When the script is finished, you should see test_N.jpg images in the directory the script was ran. The script is pretty straight forward it opens up the stream, waits a bit then takes a few snapshots. For a 24/7 rtsp camera, the sleeps are probably not necessary but this stream isn't open all the time, and you don't want it too or your internet usage could skyrocket.

The next piece of nice information to get on a screenshot is to have a timestamp. Many cameras do this timestamp overlay by default, here we have to do it ourselves.

timestamp_test.py:

from PIL import Image, ImageFont, ImageDraw
import datetime

img = Image.open("2023-09-14 09:02:43.245998.jpg")
w, h = img.size
draw = ImageDraw.Draw(img)
font = ImageFont.truetype('/Library/Fonts/Arial Unicode.ttf', 25)
now_time = datetime.datetime.now()
now_time_img_txt = now_time.strftime("%Y-%m-%d %H:%M:%S")
text_length = draw.textlength(now_time_img_txt, font)
# draw.text((60, 50), now_time_img_txt,fill=(0, 0, 0), font=font)  # Top/left
# draw.text(((w - text_length) // 2, h - 50), now_time_img_txt, (255,255,255), font=font)  # Bottom middle
draw.text(((w - text_length) - 10, h - 50), now_time_img_txt, (255,255,255), font=font)  # Bottom/right
img.save("test_timestamp.png")

Here we're using pillow to add text to the image. The one issue I found was the default font size is super tiny for a big image. There is an issue open to fix this but is been opened since 2017 so don't hold your breath. So I had to look for a font on my system, and I just picked one on my mac, and I picked another on my hass container:

find / -name "*ttf"

Pick one you like, aioslimproto package had a bunch of them inside my hass container. Other than that the script just loads a pic, calculates img and text size, then I generate the datetime timestamp string and add it to the image, and lastly save the image. This is just a POC, you can of course add more text, your own brand or whatever. Also my ring stream produces 1280x720, so make sure to adjust those draw.text hardcoded values as needed.

Once you have this two POC scripts done to your liking, is time to create the service script. Load the script to hass container /config/pyscript/ffmpeg_snapshot.py

Then you can test the service in the developer tools, here I tested with one of my local streams provided by ring-mqtt

uri: rtsp://192.168.0.46:8554/54e3184b42fb_live
save_path: /media/camera/frontdoor/
count: 4

Once you know it works, you can call the service from an automation and use the ring doorbell motion as a trigger. Then also send notification.

https://www.home-assistant.io/integrations/local_file

https://companion.home-assistant.io/docs/notifications/notification-attachments/

Conclusion

With this setup I get almost instant notification snapshots, your mileage might vary depending on the camera and network latency but with this script I clocked 2-5secs between realtime vs stamp. My next goal is to use those snapshots and run them through a tensorflow lite model for object detection.

image

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment