---
title: "A programme guide generator for cable TV"
description: "A Python and Rust service that turns a TV schedule from a database into DVB tables and multicast streams, so set-top boxes can show the guide, and it follows the clock change by itself."
date: 2026-11-05T12:20:00+01:00
lastmod: 2026-11-05T13:51:01+01:00
url: https://klimek.link/projects/epg-generator/
author: "Marcin Klimek"
source: https://klimek.link/projects/epg-generator/
---

# A programme guide generator for cable TV

When you press the guide button on a set-top box, the schedule you see was sent to it inside the broadcast signal. Someone has to build that data, and for a cable network I wrote a service that does it. It takes the programme schedule from a MySQL database and encodes it as DVB service information tables, EIT for the programmes, NIT for the network and channel numbers, SDT for service names, and TDT and TOT for the time and time zone. The tables go out as MPEG-2 transport streams over UDP multicast to a remuxer, which injects them into the signal the subscribers receive.

It handles both flavours of DVB. For DVB-T it makes about 23 streams of 400 kbit/s each, one per transport stream, and for DVB-C one stream of 10 Mbit/s with the EIT of every stream on its own PIDs.

## Why I rewrote it

The earlier version has run in one cable network for several years, under Python 2.7, and twice a year someone had to open the code and comment a line in or out to follow the daylight-saving change. It also opened a database connection roughly 110 times every 30 seconds, built SQL from strings, and cut an event list off when it got too long.

A new version is coming soon. It moves from Python to Rust, fixes places where the old one didn't follow the standard, and is much lighter on resources and system load. That's the plan, and the repository is in the middle of it: today there's a Python 3.13 daemon for time zones and table building and a sender in Rust that packs the transport stream packets, paces them at a constant bitrate and sends them, sharing a snapshot in shared memory. The table encoder, the plan snapshot and the sender exist and are tested. The database layer doesn't exist yet.

The time problem is handled by doing the conversion in one place. Times in the database are local and every table needs UTC, so the time module converts once and no date arithmetic happens in SQL. That module is written, and its tests run through both clock changes of a year, including the hour that doesn't exist in spring and the one that happens twice in autumn.

## Checking it

Unit tests catch arithmetic mistakes but not misunderstandings of the spec, so changes to the encoder or the sender also go through `tsduck`, which analyses the stream and reports table errors. The Python and Rust sides must produce byte-identical packets for a set of contract vectors. A bug I fixed in October is a good example of what these tests are for: every time a new schedule was published, the sender restarted its carousel from the beginning, so when schedules arrived faster than one carousel cycle, the tail of the list was never broadcast. The telemetry hid that, because it reset too.

Version 2 is unfinished. The encoder, the time module, the plan snapshot and the sender are written and tested, and the move of the remaining Python parts to Rust is still ahead. Also to do are the database layer (which first needs a check of how the production database stores times), an operator interface in the terminal, deployment, and trials with a real box.

Source: https://klimek.link/projects/epg-generator/
