Florian Wesch

Using the REST API for automating video wall configuration

Posted Feb 20 2018 by Florian Wesch

Using the REST API unleashes the true power of info-beamer hosted. This blog post describes how it can be used to automatically create configurations for synced video walls.

Introduction

Here is a video wall powered by info-beamer running at a customers site during a demo.

If you look closely you can see that there's two different pieces of content on the video wall: The right side shows the content on 16 screens arranged in a 4x4 grid. On the left side you see a sidebar showing an overview of the content on 4 screens arranged vertically.

The flexible video wall package is the package used for both the left and right side of the complete video wall.

Syncing two video walls

The customer created two different setups based on the flexible video wall package. One setup is configured as a 1x4 video wall. Its playlist contains different images describing the content. It is the Sidebar Video Wall.

The second setup is the Content Video Wall and is configured as a 4x4 grid. The playlist contains multiple videos and images with different durations.

If the total duration of both playlists is exactly the same, the flexible video wall package makes sure that both playlists "play in sync". So both playlists start at the exact same time.

Of course one could configure both playlists manually. That works pretty well if you only want to show a limited number of images and videos on the Content Video Wall. You could manually add up the duration of all assets, then create or edit the playlist of the Sidebar Video Wall to match that duration.

While this works, it's error prone and doesn't scale well. The customer added a total of almost 400 different images and videos, often with very short duration to the Content Video Wall. Additionally multiple consecutive images and videos together formed different logical sections within the complete Content Video Wall playlist that should each be handled by only a single image on the Sidebar Video Wall. Summing up those different pieces of a playlist with 400 items really should be automated. That's when the info-beamer REST API becomes useful.

Using the API

The info-beamer API allows you to automate everything. Starting from simple things like monitoring your screens to building setups automatically. We'll write our tool in Python so the code should be fairly easy to follow. Of course you can use any other language that has an HTTP client available.

The tool takes a given video wall setup and sums up the playtime of all items within its playlist and then reconfigures a different setup to match the durations.

We want to split up the Content Video Wall playlist into different sections automatically. For that we use separator images with filenames that starts with Sep_. For each section we want to create an item in the Sidebar Video Wall playlist with its duration set to the total time of all items in that section.

Here's a visualization that hopefully helps to understand this:

The above Content Video Wall playlist contains a total of 9 items, two of which are separator images named Sep_Image. The first three items and separator image form the first section. In the Sidebar Video Wall playlist we want to create a single item (drawn red in the above image) for that with its playtime set to 10 seconds. Our tool should continue until it created all three sections in the Content Video Wall playlist.

The result for the above example should be three playlist items in the Sidebar Video Wall with 10s, 10s and 5s duration. The API and a bit of Python code can solve this. Here's the complete code for that:

import requests, sys
from requests.auth import HTTPBasicAuth

if len(sys.argv) == 4:
    api_key, setup_id, target_setup_id = sys.argv[1:]
else:
    print '%s <api_key> <setup_id> <target_setup_id>' % sys.argv[0]
    sys.exit(1)

r = requests.get('https://info-beamer.com/api/v1/asset/list',
    auth=HTTPBasicAuth('', api_key),
)
assets = {a['id']: a for a in r.json()['assets']}

r = requests.get('https://info-beamer.com/api/v1/setup/%s' % setup_id,
    auth=HTTPBasicAuth('', api_key),
)
playlist = r.json()['config']['']['playlist']
#                              ^-- top level node

target_playlist = []
section_duration = 0

for item in playlist:
    asset_name = item['file']
    if isinstance(asset_name, int): # it's an asset_id
        asset_name = assets[asset_name]['filename']
    duration = item['duration']

    section_duration += duration

    if asset_name.startswith('Seq_'):
        print 'section playtime %10f' % (section_duration,)
        target_playlist.append({'duration': section_duration})
        section_duration = 0

if section_duration > 0: 
    print 'section playtime %10f' % (section_duration,)
    target_playlist.append({'duration': section_duration})

if raw_input('apply to setup %s [y/N]>> ' % target_setup_id) != 'y':
    sys.exit(0)

r = requests.post('https://info-beamer.com/api/v1/setup/%s' % target_setup_id,
    params={
        'config': json.dumps({
            '': {
                "playlist": target_playlist,
            }
        }),
        'mode': 'update',
    },
    auth=HTTPBasicAuth('', api_key),
)

This script is meant to be called by specifying an API key and two setup_ids of a setup based on the flexible video wall package. An example invocation might look like follows. In the example the API_KEY environment variable is set to your info-beamer hosted API key, 1234 is the setup_id of your Content Video Wall setup and 3456 is the setup_id of your Sidebar Video Wall setup.

$ python sync-walls.py $API_KEY 1234 3456
section playtime  10.000000
section playtime  10.000000
section playtime   5.000000
apply to setup 3456 [y/N]>> y
$

Let's walk through the code step by step to see what's going on.

After getting the command line arguments, the code calls the info-beamer asset list API to retrieve a list of all assets in the account.

r = requests.get('https://info-beamer.com/api/v1/asset/list',
    auth=HTTPBasicAuth('', api_key),
)

The returned data structure is just a list and the next line transforms it into a dictionary so you can easily lookup all assets infos using their id:

assets = {a['id']: a for a in r.json()['assets']}

The next snippet calls the API a second time to get all setup details of the Content Video Wall setup.

r = requests.get('https://info-beamer.com/api/v1/setup/%s' % setup_id,
    auth=HTTPBasicAuth('', api_key),
)
playlist = r.json()['config']['']['playlist']
#                              ^-- top level node

The current configuration for the setup can be found in the 'config' top-level key. Inside we find another dictionary for all nodes of our setup. In this case we're interested in the root node, which is always named '' (the empty string).

Next we look at how the flexible video wall package defines its configuration (node.json specification):

{
    "title": "Playlist",
    "name": "playlist",
    "type": "list",
    "itemname": "Item",
    "items": [{
        "title": "Asset",
        "name": "file",
        "type": "resource",
        "valid": ["image", "video"],
        "default": "empty.png"
    }, {
        "title": "Play time",
        "name": "duration",
        "type": "duration",
        "default": 5
    }]
}

The configuration of the playlist only defines a single configuration item named 'playlist', so we fetch that with ['playlist'].

As a result, we get a list of playlist items in the playlist variable that might look like this:

[
    {'file': 2, 'duration': 2.5},
    {'file':10, 'duration': 2.5},
    {'file': 8, 'duration': 2.5},
    {'file': 4, 'duration': 2.5},
    ...
]

The duration value is the configured display time of each individual item in the playlist. Each file value is an asset specifier. If it's a numerical value, it references an asset by id. An asset specifier can also be a string. In that case it references a package local asset file. Using that knowledge we can now iterate over all items in the playlist and extract both the duration and asset filename for each of them. For numerical assets we use the previously generated dictionary assets to get the file name of that asset.

for item in playlist:
    asset_name = item['file']
    if isinstance(asset_name, int): # it's an asset_id
        asset_name = assets[asset_name]['filename']
    duration = item['duration']

Then we add the duration of the current item to the section_duration variable. That way we keep track of how long the current section is up to this point:

section_duration += duration

Next we use the asset_name from earlier and check if it starts with the string 'Seq_', which we defined as the filename pattern for our section separators. Each time we hit a separator, we output the current section duration, add a new entry to the target_playlist with that duration and finally reset it to 0.

if asset_name.startswith('Seq_'):
    print 'section playtime %10f' % (section_duration,)
    target_playlist.append({'duration': section_duration})
    section_duration = 0

Finally, once we're done looking at all items we output the duration of any remaining sections after the last separator and also add an item to the target_playlist:

if section_duration > 0: 
    print 'section playtime %10f' % (section_duration,)
    target_playlist.append({'duration': section_duration})

If we output the target_playlist variable at this point it'll look like this for our example:

[
    {'duration': 10},
    {'duration': 10},
    {'duration': 5}
]

This basically looks like the playlist array we've seen before. The great thing is that we can directly set this array as a playlist for our target setup!

There are two different ways a setup can be configured: You can either set a completely new configuration, thereby overwriting any existing values or, and very useful in this case, you can merge an existing setup configuration with a particially specified configuration. Existing values will be unchanged and only the updated values will be set. We use this mode since we don't want to reconfigure the video wall parameters of the target setup. We only want to update the playlist.

Additionally the file value in our playlist is unspecified. If we merge this playlist with an existing playlist, only the durations of each item will be updated. Manually specified playlist assets won't be updated and if the existing playlist is shorter than the new playlist, the default asset empty.png (Look at the node.json file again to see where this is defined) is used.

Updating the target playlist is pretty simple as a result:

r = requests.post('https://info-beamer.com/api/v1/setup/%s' % target_setup_id,
    params={
        'config': json.dumps({
            '': {
                "playlist": target_playlist,
            }
        }),
        'mode': 'update',
    },
    auth=HTTPBasicAuth('', api_key),
)

We specify our new partial configuration in the config parameter and then use the 'update' mode which merges our configuration with the existing configuration.

That's it. Both the source playlist and the target playlist now have the same total duration. All that's left to do is to go to the target setup to select the assets to display.

Use the API!

The API is one of features of info-beamer hosted that makes the system unique. If you invest a bit of time in learning the API and how it integrates and interacts with the rest of the system you can build unique digital signs. It allows you to fully automate every aspect of your info-beamer installation. Often with only a few calls.

Every info-beamer account can use the API so you should try it out today!


Read more...


info-beamer.com offers the most advanced digital signage platform for the Raspberry Pi. Fully hosted, programmable and easy to use. Learn more...


Get started for free!

Trying out the best digital signage solution for the Raspberry Pi is totally free: Use one device and 1GB of storage completely free of charge. No credit card required.


Follow @infobeamer on twitter to get notified of new blog posts and other related info-beamer news. It's very low traffic so just give it a try.

You can also subscribe to the RSS Feed RSS feed.


Share this post:

Share using Twitter


Questions or comments?
Get in contact!