Scoreboards and Video Routing in Python
Scorekeeper
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).
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).
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
(incl. VAT)
Buy Linux Magazine
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.
News
-
NVIDIA Released Driver for Upcoming NVIDIA 560 GPU for Linux
Not only has NVIDIA released the driver for its upcoming CPU series, it's the first release that defaults to using open-source GPU kernel modules.
-
OpenMandriva Lx 24.07 Released
If you’re into rolling release Linux distributions, OpenMandriva ROME has a new snapshot with a new kernel.
-
Kernel 6.10 Available for General Usage
Linus Torvalds has released the 6.10 kernel and it includes significant performance increases for Intel Core hybrid systems and more.
-
TUXEDO Computers Releases InfinityBook Pro 14 Gen9 Laptop
Sporting either AMD or Intel CPUs, the TUXEDO InfinityBook Pro 14 is an extremely compact, lightweight, sturdy powerhouse.
-
Google Extends Support for Linux Kernels Used for Android
Because the LTS Linux kernel releases are so important to Android, Google has decided to extend the support period beyond that offered by the kernel development team.
-
Linux Mint 22 Stable Delayed
If you're anxious about getting your hands on the stable release of Linux Mint 22, it looks as if you're going to have to wait a bit longer.
-
Nitrux 3.5.1 Available for Install
The latest version of the immutable, systemd-free distribution includes an updated kernel and NVIDIA driver.
-
Debian 12.6 Released with Plenty of Bug Fixes and Updates
The sixth update to Debian "Bookworm" is all about security mitigations and making adjustments for some "serious problems."
-
Canonical Offers 12-Year LTS for Open Source Docker Images
Canonical is expanding its LTS offering to reach beyond the DEB packages with a new distro-less Docker image.
-
Plasma Desktop 6.1 Released with Several Enhancements
If you're a fan of Plasma Desktop, you should be excited about this new point release.