Raspberry Pi as a Video Kiosk

2021-03-28 — 11 mins reading time

The Raspberry Pi is ideal for playing unattended videos such as often seen in shop windows or museums. In this article, you’ll find some tips on how to configure your Raspberry Pi so that it is really “plug-and-play”, even after powerloss.

The Goal

Let’s start with collecting the requirements - so that you know what to expect from this article:

  • We have a couple of different video files that shall be played in an endless loop.
  • We don’t want audio (if you need audio, you can still enable it).
  • We don’t want to see any techy command line messages.
  • The playback shall only happen during a predefined timeframe (e.g., during opening hours).
  • Optional: If the video has some subtitles, display them.
  • Optional: If the video has a title/headline, display it.
  • Optional: Ensure Wifi connectivity for remote SSH access (e.g., for fixing things).
  • Optional: Remotely update the video files (add/delete videos).

The Base

Choose any Raspi that is powerful enough to play your videos and install Raspberry Pi OS Lite on it. Connect any monitor/TV that supports automated standby if it looses the HDMI signal and which automatically powers on if there is a HDMI signal (basically, any PC monitor will do; some TVs might work as well). For configuration, connect a keyboard via USB to the Raspi. You won’t need it later. Make sure to connect your Raspi via Ethernet or Wifi for installing packages.

After the first start of your Raspi with the freshly installed Raspberry Pi OS Lite, start sudo raspi-config and adapt the following settings to suite your needs:

  • localisation
  • timezone
  • password (should be changed from the default)
  • hostname (so you may find it in the network more easily)
  • Wi-Fi
  • enable SSH (to be found in Interface Options)

Second, disable Avahi since it is not needed in our setup:

$ sudo systemctl disable avahi-daemon
$ sudo systemctl stop avahi-daemon

Third, upgrade all installed packages and install some additional ones:

$ sudo apt update
$ sudo apt dist-upgrade
$ sudo apt install omxplayer python3-pip libpng12-dev git imagemagick fonts-freefont-ttf vim tmux

Some explanations for the packages:

  • omxplayer - The video player that we will use
  • fonts-freefont-ttf - Needed by omxplayer for the subtitles
  • git - Needed to checkout vipy and pngview (more on pngview later)
  • libpng12-dev - Needed to compile pngview
  • imagemagick - Needed for creating the header/title PNGs
  • python3-pip - Needed to install some additional Python packages
  • tmux - Terminal multiplexer (handles multiple virtual terminals and keeps them open even if you disconnect from SSH)
  • vim - An alternative editor (if you don’t know it, you can omit it and use nano instead)

You may reboot the Raspi now if you like and reconnect to it via SSH. You can disconnect the keyboard but keep the monitor connected so you see how it behaves.

Silence!

When starting the Raspi, you’ll see three raspberries and a couple of boot messages. Let’s turn these things off:

sudo nano /boot/cmdline.txt
  • Replace console=tty1 with console=tty3. This redirects boot messages to the third console (use CTRL+ALT+F3 to switch to these messages and CTRL+ALT+F1 to go back to the main console).
  • Add logo.nologo to hide the raspberries.
  • Add quiet and splash to omit kernel messages.
  • Add vt.global_cursor_default=0 to disable the blinking cursor. Note that this will be very annoying if you still work on the Raspi directly with a keyboard connected. If you do so, I recommend that you change this setting as a last step when your setup is complete.

For added security, we won’t use auto-login as recommended by some guides. If that’s important to you, you might want to change the default password for the Raspi as well (see previous section).

As last silencing step, you need to disable the login prompt on tty1:

$ sudo systemctl disable getty@tty1

Do a reboot. Now, you should only see a blank screen, not even the cursor should be visible. If you need a console, you may use CTRL+ALT+F2 to switch to tty2. There, you will have a login prompt, but the cursor is still not blinking. Thus, this should be used in emergency situations only.

Action!

For the rest of the configuration, you might want to login via SSH since we mess around with the display a bit.

We will use the Raspberry Pi tool “tvservice” (already installed in Raspberry OS Lite) to switch on/off the HDMI signal. Try it out as follows:

$ tvservice -o # off
$ tvservice -p # on

For video playback, we will use Raspberry Pi’s default video player omxplayer, which works just fine when launching it via SSH and without any X server. It uses the Raspberry Pi’s DispmanX API and thus accesses the Raspi’s GPU more or less directly. In addition it supports many different video formats.

Load any video file on the Raspi for testing purposes. You have several options for doing so:

  • download it from an URL with wget
  • use scp to copy it via SSH
  • use any sftp tool (e.g., FileZilla on Windows)

Try the playback with omxplayer:

$ omxplayer video.mov

For displaying a logo or for adding some header/title to the playing video, we need another tool: pngview. You need to download and compile it from source:

$ git clone https://github.com/AndrewFromMelbourne/raspidmx
$ make -C raspidmx/lib
$ make -C raspidmx/pngview
$ sudo cp raspidmx/pngview/pngview /usr/local/bin/

Thus, we now have all the pieces and they just need to be put together into a script that

  • ensures that the HDMI signal is on when the script starts,
  • plays video files from a folder in a loop,
  • switches the HDMI signal off when the script ends.

I wrote a small Python script that does exactly this. It is called vi.py and it is available on Github.

vi.py

Download the script and its dependencies as follows:

$ git clone https://github.com/froschbach-io/vipy.git
$ pip3 install pid

In its simplest form, the script only expects a folder with video files as a parameter. It searches for a couple of file endings such as .avi, .mov etc. and plays one after the other. Assuming, you have stored all your videos in the folder videos, you can launch the script as follows:

$ python3 vipy/vi.py videos

For playback, the files are sorted alphabetically by vi.py: If you require a certain sequence, simply rename the files so that they start with some digits. I named my files after the following pattern:

  • 0010-Video XYZ.mov
  • 0020-Video ABC.mov

This way, it is easy to insert some videos in between. Plus, the script has a parameter called --start which expects a number. If all files start with a number, it chooses the file with a number >= start. --start 15 would play video 0020-Video ABC in the example above.

The script is designed to be robust: If a file name does not start with a number or if the video file is corrupt, it fails gracefully and simply does its best to keep going. Errors are logged, so you can investigate later what went wrong.

If vi.py finds a *.srt file with the same basename as the video file (filename without extension), it will use this *.srt filename as a subtitle parameter to omxplayer. For the above example:

  • 0010-Video XYZ.srt
  • 0020-Video ABC.srt

If vi.py finds a *.png file matching the video file’s basename, it uses this as an overlay using pngview. pngview also uses the DispmanX API and it can place a layer with the PNG file on top of the video (transparency is supported!). You can use this to add your own TV logo to the output or to display some headline / title for the video. Later in this article, you will find a section that explains how to create such PNGs easily from text.

The script creates a PID (process ID) file in /tmp/vipy.pid. Using kill, you can trigger some actions in the running script:

$ kill -SIGTERM `cat /tmp/vipy.pid`

Terminates the script gracefully. It will finish playing the current video. After that, it switches off the HDMI signal and quits itself.

$ kill -SIGHUP `cat /tmp/vipy.pid`

Reloads the current video folder. If you added/removed files from the folder, you can tell the script to re-scan the folder. It will do so after the current video finished playing. It will try to remember the current number of the video file (see file naming above) and start playing from the last number+1.

These signals can be used in cronjobs. More on that later.

Raising Banners

If you want to add a headline for the current video (e.g., its title), you can easily generate a PNG file from text with ImageMagick’s convert:

convert -size 1920x60 -background "#00000055" -fill white \
		-gravity center -font Utopia -pointsize 48 \
		label:"This is my headline" \
		"0010-Video XYZ.png"

This creates a banner in Full HD width with semi transparent background and white text.

The vi.py repository on Github contains a simple shell script which you can execute like this:

$ bash gen-overlay.sh *.txt

This will iterate over all *.txt files and generate *.png files from them.

With ImageMagick you can certainly go wild and add fancy logos as well.

Show time!

Normally, I use a cronjob to schedule the end of the playback (e.g., in the night) and another one to start the script again (e.g., in the morning). In between I have a rsync job running which syncs the latest videos to the Raspberry Pi. If you do update the video files while the script is running, use SIGHUP after the update is complete.

For stopping the playback, we create a cronjob that sends a SIGTERM to the script as mentioned above:

$ kill -SIGTERM `cat /tmp/vipy.pid`

Starting the script is a bit more tricky since we need to take into account that it might already be running. We use the PID file for this. And since there are a couple of corner cases and race conditions that could occur, vi.py (or rather a Python module) will take care of it: If you try to start a second instance, it will simply fail.

To configure the cronjobs, use crontab -e. Here is an example configuration that starts the script at 6 am and stops it at 11 pm. In addition, it will start the script right after a reboot:

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
SHELL=/bin/bash

# m h  dom mon dow   command
0   6  *   *   *     python3 /home/pi/vipy/vi.py /home/pi/videos >>/home/pi/vipy.log 2>&1
0  23  *   *   *     kill -SIGTERM `cat /tmp/vipy.pid`
@reboot python3 /home/pi/vipy/vi.py /home/pi/videos >>/home/pi/vipy.log 2>&1

The outout of vi.py is redirected to a log file so you can inspect it for problems later on. Note the PATH and SHELL environment variables: These are needed since cron normally only has a reduced path search list and uses a different shell.

Stay Connected

Networking is fun: Even if everything indicates that it should be working (interface is up, there is an IP address configured, we have a default gateway etc), there are still cases where you do not get any response from any server. After re-plugging the cable, everything is back to normal.

Replugging cables is not what we want to or can do with our unattended Raspi. Thus, we let a script regularly test the connection. If the test fails, we simply “re-plug the wifi cable” by restarting the interface.

We’ll follow Alex Bain’s approach which runs a regular test via a Cronjob. If that fails, we’ll restart the interface.

For completeness, I added the script also to the vi.py repository. You might want to change the server IP address to your router’s IP address or DNS name instead: It is sufficient to know whether we can reach our router. If the router fails to connect to the Internet, restarting the wifi interface on the Raspi doesn’t help much.

With this cron job, the wifi test script is run every 5 minutes (use crontab -e to edit the crontab):

# m h  dom mon dow   command
*/5 *  *   *   *     /home/pi/vipy/wifi-tester.sh >/dev/null

Remotely Up-to-Date

As mentioned before, I use rsync to sync a master folder with all the video files (located on a NAS) to the Raspi’s SD card. The rsync job is run via Cron from the NAS: This way we do not need to put any credentials on the Raspi (besides the Wifi credentials).

We put the SSH public key of the NAS user on the Raspi to allow password-less login from the NAS. Create the folder /home/pi/.ssh if it doesn’t exist and add the public key of your NAS user to the file authorized_keys (in a single line).

Use rsync from your NAS to sync the current folder to the videos folder of your Raspi:

$ rsync -rtv -e ssh ./ pi@<hostname-or-IP-of-Raspi>:/home/pi/videos/

Why not stream from a file share? It’s more reliable if you have the files locally. If your use case requires 4k videos, you may think of adding an external USB drive to the Raspi.