I decided it would be cool to display a time-lapse video using my Blink security camera and ended up over-complicating it to all hell because… why not? There are many, WAY easier, ways to do this. But developing a system to pass arbitrary condiments is way more fun
My setup
I’m running home-assistant in a docker container on UnRAID and have a fairly beefy server (I have like 20+ containers running various things, plus two or three VMs). My full home-assistant config can be found here for those interested, but I’ll recreate the important bits in this post.
Automatically take pictures
The first step here was to create an automation to automatically take a picture using my blink camera. This was pretty straight-forward, but required some fiddling to get the right interval (without destroying the battery). I settled on a picture every twenty minutes.
I also set a condition here. Since my blink camera does not have an IR LED for night vision (I’m using an indoor camera outdoors with the illuminator turned off) I really can’t get any usable pictures at night. So I stop taking pictures between ~midnight and either 4am or 30 minutes prior to sunrise, whichever is later (almost always sunrise, but whatever).
I then call a script to take a picture with the camera (we’ll get to that in a minute) and then use the camera.snapshot service to save the camera to a file. My /images directory is just a mounted volume to somewhere else on my server (again, we’ll get to that later). The filename format just ensures that I have a unique, and useful, filename for each image I take.
alias: Blink Automatically Take Picture
trigger:
- platform: time
minutes: '/20'
seconds: 00
condition:
condition: and
conditions:
- condition: time
before: '00:10:00'
after: '04:10:00'
- condition: sun
after: sunrise
after_offset: '-00:30:00'
action:
- service: script.blink_trigger_camera
- delay: 00:00:10
- service: camera.snapshot
data:
entity_id: camera.blink_living_room
filename: /images/raw_images/blink_living_room_{{now().year}}{{now().month}}{{now().day}}_{{now().hour}}{{now().minute}}{{now().second}}.jpg
The script is even more simple than the automation. I just call the blink.trigger_camera service to tell my camera (which is named “Living Room” despite living outside… I was too lazy to change the name) followed by a brief delay to ensure the command completed, and then I force an update of the blink information within home assistant.
blink_trigger_camera:
alias: Script - Blink Trigger Camera
sequence:
- service: blink.trigger_camera
data:
name: "Living Room"
- delay: 00:00:05
- service: blink.blink_update
Ok, now what?
Neat. So now I have pictures every twenty minutes…how do I turn it into a gif? What I do here is over-engineer the crap out of it. I figured I’d need to use a tool like ffmpeg or imagemagick to create a gif which meant I’d need to run them in a separate docker container. Continuing this thought, I figured i may be useful in the future to have a generic container that could run any python script I’d ever want, which I could then easily configure with cron (without physically editing the crontab). So, naturally, I made my own generic python container with configurable scheduling… like I said, I over-complicated it, but it works great
Enter pycron
The docker container I made is dubbed pycron and the source repository is here: fronzbot/docker-pycron. It’s (in my opinion) very easy to use. You simply map a /work volume where you add a config.yaml folder that hold all the scripts you want scheduled AND when you want them to be scheduled. Any edits to your scripts do not require a restart to take effect, although editing the schedule does require a container restart. In addition to this, installing custom pip modules can be done at container start by adding them to the my_requirements.txt file in your /work volume. There is also a /share volume that I added specifically to share among other docker containers. So for home-assistant, I created a directory called /share/hass that I then mapped to home-assistant as /images (which is the directory from the automation earlier).
My configuration for this can be found here. I’ll refrain from posting the python scripts since it’ll just take up too much space on the post, but feel free to check them out.
First up is my configuration file. Here I have two scripts: one to create my gif, the other to back it up. I want to create a new gif every hour (more frequently than that wasn’t very useful) and I wanted to save one every day so I could eventually string them together for a yearly timelapse (or something). Thus, my pycron config.yaml file looks like this:
make_gif:
schedule:
hours: 1
save_gif:
schedule:
days: 1
As for the scripts, in the make_gif script I designate a maximum number of images to retain (anything over is backup up to a different directory) and I give it a target length of 10 seconds. I use the gifsicle package to optimize the gif which helps reduce the file size by about 5x from the original.
It’s worth adding how I actually create the gif. The first thing I do i retrieve all the files in my save directory and sort them based on the timestamp (so they are absolutely in chronological order). Then I use imageio to create the gif and use a gifsicle command to optimize. The following is a simplified version of what can be found in my script.
import os
import imageio
IMAGE_DIR = '/share/hass/raw_images'
BACKUP_DIR = '/share/hass/backup'
SAVE_DIR = '/share/hass/gifs'
# Sort the files
files = os.listdir(IMAGE_DIR)
fullfiles = [os.path.join(IMAGE_DIR, name) for name in files]
sortedfiles = sorted(fullfiles, key=os.path.getmtime)
output = os.path.join(SAVE_DIR, 'temp.gif')
images = []
# Calculate framerate for 10 second gif
framerate = int(len(sortedfiles) / 10)
# Create the gif
with imageio.get_writer(output, mode='I', fps=framerate) as writer:
for filename in sortedfiles:
image = imageio.imread(filename)
writer.append_data(image)
# And now optimize the gif
final = os.path.join(SAVE_DIR, 'output.gif')
command = "gifsicle -O3 --colors 128 --resize-width 512 {} > {}".format(output, final)
os.system(command)
os.remove(output)
Displaying the gif in Home-Assistant
So I have my gif saved, but now I need to display it in home-assistant. For this, I use the local file camera platform. I’ve already mapped the directory where I save my gif to home-assistant, so I have the following in my home-assistant configuration.yaml file:
camera:
- platform: local_file
name: blink_gif
file_path: /images/gifs/output.gif
And here is an example gif that is created (this one is actually at 30mins/picture rather than 20, but you get the idea).
But… why?
Valid question. Originally I didn’t really want to make a generic container to run and schedule my python script, but I started seeing potential benefits to it long term (mostly unrelated to home-assistant). On top of that, I’m very much a docker n00b so I figured it would be a good learning experience in creating and managing a container. I’m pretty happy with how it turned out, despite the seemingly roundabout way I got there… I’m like one step away from this being a Rube Goldberg machine.
Anyways, hopefully someone else enjoys this or finds it useful. Even if your conclusion is “this is the stupidest way to accomplish that task”, at least you now know one way not to do it!