Scoreboards and Video Routing in Python

Scorekeeper

© Photo by Johnny Briggs on Unsplash

© Photo by Johnny Briggs on Unsplash

Article from Issue 273/2023
Author(s):

We look at a broadcast video system network that uses Python code to control a video router and check out another program that creates a scoreboard.

Each year my church hosts a basketball league, and several years ago we wanted to upgrade to digital scoreboards because our existing classic board was showing its age. The first version of this new scoreboard software was written in Python [1] and used the GTK toolkit to create the public display. A web page designed for an iPad allowed courtside control.

The gym is a shared space that also hosts a meeting room, party hall, and general-purpose room. To support these roles, two NUC small-form-factor computers drive the displays (100-inch LCD TVs). Windows was necessary to support all of the display software so that PowerPoint and ProPresenter would run natively.

In general, a video system has a hub-and-spoke-style network wherein each device has a dedicated home that runs to a video switcher (Figure 1). The video switcher, as its name implies, accepts all of the video inputs and allows the operator to pick which one should be displayed, overlaid, or otherwise presented to the final output (Figure 2).

Figure 1: Video system signal flow. Displays shown in blue can be seen in the control room photo (Figure 2).
Figure 2: The video control room is a converted classroom. From left to right, the first two monitors in the background are the main displays of the NUCs running the scoreboard software. The text window is the output of the Python program, and the scoreboard itself is on the secondary monitor output. The third monitor is routable from the video matrix to select what is shown. In the foreground are the matrix control touchscreen and the camera controller. The tall monitor is the switcher multiview. The NUCs and the matrix are visible at the very bottom of the rack under the table.

Displays are slightly more complicated because they need to select the switched video program from the video switcher or the scoreboards at any given time, which is accomplished by a matrix router. See the "Matrix Routers" box for more details.

Matrix Routers

In the video world, a router is a really helpful device. It accepts some number of video signals in and can send them to any of its destinations. This example uses a small four-input by four-output version, but other versions are available with up to thousands of I/O points.

Think of the router as a grid with sources (video signals – see the "Video Signals and Conversions" box) coming in on the left and going out along the bottom. Each column (an output or destination) is allowed one connection. Inputs can connect to as many outputs as needed. To say it another way, all of the outputs can display a single input, but each output can only display one thing at a time. When a selection is made, the selected input and output are connected as if they were wired together directly. These connections are easily changed throughout the day as the needs of the basketball game dictate.

Video Signals and Conversions

Many different video formats can carry live video down a wire. You're probably familiar with HDMI because it has been widely adopted since its introduction. The video system discussed here primarily uses a serial digital interface (SDI), which is a digital standard for broadcast video. Hardware converters between these formats also exist, so changing between them is as easy as connecting a cable of each type. The cost for this conversion is a slight delay in the video signal that is normally not noticeable; however, if you have stacked several conversions, the delays can start to add up and become visible. This scenario generally presents itself as audio out of sync with its associated video.

The Scoreboard

In addition to team points, a scoreboard also offers other game information, such as time remaining, period of play, time outs, or player numbers. Figure 3 shows the general flow of the program. At program startup, the graphics initialize and the initial state of the scoreboard renders. Concurrently, the web server starts and the operator brings this page up on a courtside computer (Figure 4).

Figure 3: The servers, clients, displays, and their associated libraries communicate and interact with each other.
Figure 4: The scorekeeper's scoreboard interface.

As the basketball game progresses, the operator uses the web interface to update the game score and other statistics. Each action on the web interface starts as a JavaScript function that runs in the browser, communicating with the CherryPy [2] server through background Ajax calls. When CherryPy receives these commands, it updates the graphics screen to reflect the change on the scoreboard itself and then sends the updated values back to the browser so that the operator's interface is also updated.

Listing 1 shows some of the code behind the scoreboard. (You can download the complete code online [3].) To begin, you must import the external libraries the program needs: The pygame, graphics library [4] draws the scoreboard; _thread allows multiple branches of the program to run at the same time, so you can run the scoreboard display and the web control interface at the same time; pymysql is the MySQL database library for Python; and cherrypy sets up the CherryPy framework.

Listing 1

scoreboard.py

 

The Web Class

The web class (lines 6-339) connects to a MySQL database that will be used to retrieve team names, determine which team plays which, and record game history. When the class is instantiated, the __init__ function is called automatically to set up a few things needed later. Line 8 creates a class variable self.scoreboard and saves the reference to the scoreboard class that's passed in as an argument.

The reconnect function is really only one call split up across multiple lines for easier readability (lines 12-18). pymysql.connect creates self.db, the local reference to the database. Each line supplies the appropriate credential.

The gameEvent function (lines 20-31) logs any change to the scoreboard. In a future version, this function will allow for recovering the scoreboard after a crash and exporting a game report. To begin, the code checks whether self.dbGameID (which was initialized to None in line 10) is set. If not, then you don't have a database ID, so you just return.

The self.db.ping convenience function, with an argument of True, checks whether the database connection is live and, if not, reconnects automatically in the next line. The two lines that follow get the game period and time in seconds. These two values pinpoint a unique time within the game.

Line 27 builds a SQL statement. The INSERT INTO command names the table followed by a list of columns separated by commas. The VALUES keyword assigns the values for each column, provided in the same order. Line 29 creates a database cursor that interacts with an SQL statement. In this case (line 30), it just executes the SQL line created in line 27, but it has many more capabilities, especially when retrieving records. Line 31 calls commit to confirm that you want to write the data.

The cherrypy decorator (lines 33-339) creates a small web server for controlling the scoreboard. Each Python function becomes a web address that returns its designated content to the browser that has called it.

The index function acts just like index.html in a traditional web server. In the absence of another address, it is the default item returned. Most of this function is a very large multiline string (not shown here, grab the full code online [3]) enclosed by triple quotes (""") that instructs Python to ignore any newlines or other special characters until it encounters another triple quote. The enclosed string is the HTML5 [5] and JavaScript of the control web page. After the massive text string, the Python format command inserts all of the current variables into the web page (lines 240-251).

The @cherrypy.expose decorator tells CherryPy that it should allow this function to be reachable through its web server. Without this decorator the function remains private from anyone on the web, which allows the program to be structured with additional functions as needed, with only those specifically designed to be web-accessible published. You'll see this decorator before each function in the web class, so it's only described once here.

The teams function (lines 257-267) is somewhat of a misnomer in that it generates a game selector. For the purposes of this database, a game is a pair of teams at a specific date and time on a specific court. This function retrieves all of the games and returns an HTML select widget.

The function begins by creating an SQL cursor and statement and then executing it (as in lines 29-31). However, now it's retrieving records, so the cursor has the results of the query, which is data instead of just a message that the query succeeded.

After starting the HTML select widget on line 262, the program loops over the SQL results. As the name implies, cursor.fetchall gives all of the results as an iterator. game will contain one row from the database for each time through the loop. Each pass creates an HTML option and lists the game date, time, and teams.

The processGameSelect function (lines 270-279) is called when the scoreboard operator selects a game from the drop-down just generated. It receives the gameID argument and uses that to get the game details from the database. Line 272 is an almost identical SQL statement to line 259, but with a WHERE clause added to the end with the gameID.

After doing the SQL dance one more time, line 274 calls cursor.fetchone. The last time, all of the records were retrieved, but this time only one is needed, which is saved into game. gameID is saved into a class variable, and self.teamName sets the names retrieved from the database.

The teamName function (lines 281-284) accepts a team number and name as arguments. Each of two if statements determines which team is being named and then saves the new name to a variable in the scoreboard class.

Housekeeping

The score, timeouts, and period functions (lines 286-312) accept the team1 and team2 arguments (period only accepts a period number) which are the amounts to change each value. Negative values are allowed so that values can be corrected or reset for the next game or period. Note also that the variables are preceded with int. All arguments from CherryPy come in as strings, so they have to be converted to integers before they can be used algebraically.

Finally, the two newly adjusted values are returned, separated by a colon, to go back to the JavaScript function and be split into the two scores so that the control screen is updated properly.

The JavaScript versions of these functions are generated in the index function (not included in the listing). The score function, for example, creates a JavaScript object and adds obj.team1 and obj.team2:

function score ( team1 , team2 )
{{
   obj = new Object();
   obj.team1 = team1
   obj.team2 = team2

These are the score deltas (amounts to change) for each team. jQuery [6] (represented by the $) then posts the object to the address score (the Python CherryPy function described above):

$.post ( "score" , obj , function ( data ) {{
    scoreParts = data.split ( ":" );
    $ ( "#team1score" ).html ( scoreParts [ 0 ] );
    $ ( "#team2score" ).html ( scoreParts [ 1 ] );
}} );

obj has the values to send that were set up earlier. When post finishes and receives a response, function ( data ) is called, where data is the returned string with the newly updated scores (or timeouts or period). First, the data is split into its two parts with data.split, and then the score on the control page is updated with jQuery.

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

  • Nerf Target Game

    A cool Nerf gun game for a neighborhood party provides a lesson in Python coding with multiple processors.

  • Panda3D

    Several free game engines are available for Linux users, but programming with them is often less than intuitive. Panda3D is an easy-to-use engine that is accessible enough for newcomers but still powerful enough for the pros at Disney Studios.

  • Matrix

    One tool to rule all online communication: one tool to find them, one tool to bring them all in, and the Matrix to bind them. An open standard for decentralized communication enters the scene.

  • Gaming for Godot

    Creating a game requires a wide set of skills to combine graphics, animations, sound, double-clicks, and meticulous coding. The free and open source Godot game engine provides you with all the tools you need to get started.

  • Gesture-Controlled Book

    Have you found yourself following instructions on a device for repairing equipment or been half-way through a recipe, up to your elbows in grime or ingredients, then needed to turn or scroll down a page? Wouldn't you rather your Raspberry Pi do the honors?

comments powered by Disqus
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters

Support Our Work

Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.

Learn More

News