LG Horizon API Python Library
A Python library to interact with and control LG Horizon set-top boxes (Ziggo, Telenet, Virgin Media, UPC, BASE TV). Provides authentication, real-time device monitoring via MQTT, and remote control capabilities.
Supported Providers
| Code | Provider | Country |
|---|---|---|
nl |
Ziggo | Netherlands |
be-nl |
Telenet | Belgium |
be-basetv |
BASE TV | Belgium |
ch |
UPC Switzerland | Switzerland |
gb |
Virgin Media | United Kingdom |
ie |
Virgin Media | Ireland |
pl |
UPC | Poland |
Features
Authentication
- Username/password and refresh token authentication
- Automatic access token refreshing
- Token refresh callback for persisting new tokens
- Support for provider-specific auth flows
Device Management
- Discover all set-top boxes on your account
- Device info: manufacturer, model, platform type
- Real-time availability monitoring (online/standby/offline)
Real-time Status via MQTT
- Live device state changes via callback
- Playback info: channel, show title, episode, season/episode numbers
- Source types: linear TV, replay, VOD, nDVR, localDVR, review buffer, apps
- Media types: channel, movie, episode, app
- Playback position, duration, speed, paused state
- Channel and program images
- Automatic MQTT reconnection with exponential backoff
Channel Information
- Full channel list with logos and stream images
- Channel number, radio flag, linear products
- Replay pre/post padding info
- Profile-specific favorite channels
Recording Management
- List all recordings (single, season, show)
- Recording states: recorded, ongoing
- Episode details for season/show recordings
- Recording quota and usage percentage
- Play recordings on a set-top box
Device Control
- Power on/off
- Play, pause, stop
- Rewind, fast forward
- Channel up/down and direct channel selection
- Record current program
- Set player position (seek)
- Send any remote control key press
- Display custom messages on the TV screen
Installation
pip install lghorizon
Requirements: Python 3.10+, aiohttp, paho-mqtt, backoff
Quick Start
Create a secrets.json file:
{
"username": "your_username",
"password": "your_password",
"country": "nl",
"timezone": "Europe/Amsterdam"
}
For providers with refresh token auth (Telenet, UPC CH, Virgin Media GB), use
"refresh_token"instead of username/password.
Basic usage
import asyncio
import aiohttp
from lghorizon import LGHorizonApi, LGHorizonAuth
async def main():
async with aiohttp.ClientSession() as session:
auth = LGHorizonAuth(session, "nl", username="user", password="pass")
api = LGHorizonApi(auth, profile_id=None)
try:
await api.initialize()
devices = await api.get_devices()
# Print all devices
for device in devices.values():
print(f"{device.device_friendly_name} ({device.manufacturer} {device.model})")
print(f" State: {device.device_state.state.value}")
print(f" Available: {device.is_available}")
# Get channels
channels = await api.get_profile_channels()
for ch in channels.values():
print(f" {ch.channel_number} - {ch.title}")
# Monitor state changes
async def on_state_change(device_id: str):
device = devices[device_id]
s = device.device_state
print(f"{device.device_friendly_name}: {s.channel_name} - {s.show_title}")
print(f" Source: {s.source_type.value}, Position: {s.position}/{s.duration}")
for device in devices.values():
await device.set_callback(on_state_change)
# Keep running to receive MQTT updates
await asyncio.Event().wait()
finally:
await api.disconnect()
asyncio.run(main())
Device control
device = devices["device-id"]
# Power
await device.turn_on()
await device.turn_off()
# Playback
await device.play()
await device.pause()
await device.stop()
await device.rewind()
await device.fast_forward()
# Channels
await device.next_channel()
await device.previous_channel()
await device.set_channel("NPO 1")
# Recording
await device.record()
await device.play_recording("recording-id")
# Position (milliseconds)
await device.set_player_position(60000)
# Display message on screen
await device.display_message("linear", "Hello from Python!")
Recordings & quota
if api.has_cloud_recording:
# Quota
quota = await api.get_recording_quota()
print(f"Used: {quota.occupied}/{quota.quota} MB ({quota.percentage_used:.1f}%)")
# All recordings
recordings = await api.get_all_recordings()
for rec in recordings.recordings:
print(f"[{rec.type.value}] {rec.title} ({rec.recording_state.value})")
# Episodes of a show recording
episodes = await api.get_show_recording_episodes("show-recording-id")
for ep in episodes.recordings:
print(f" S{ep.season_number}E{ep.episode_number}: {ep.episode_title}")
Token refresh callback
async def on_token_refresh(new_token: str):
# Persist the new refresh token for next session
save_to_storage(new_token)
await api.set_token_refresh_callback(on_token_refresh)
Device State Properties
When monitoring a device, device.device_state exposes:
| Property | Type | Description |
|---|---|---|
state |
LGHorizonRunningState |
ONLINE_RUNNING, ONLINE_STANDBY, OFFLINE, etc. |
ui_state_type |
LGHorizonUIStateType |
MAINUI, APPS, UNKNOWN |
source_type |
LGHorizonSourceType |
LINEAR, VOD, NDVR, LOCALDVR, REPLAY, REVIEWBUFFER |
media_type |
LGHorizonMediaType |
CHANNEL, MOVIE, EPISODE, APP |
channel_id |
str | None |
Current channel ID |
channel_name |
str | None |
Current channel name |
show_title |
str | None |
Current show/movie/app title |
episode_title |
str | None |
Current episode title |
season_number |
int | None |
Season number |
episode_number |
int | None |
Episode number |
position |
int | None |
Playback position in seconds |
duration |
int | None |
Content duration in seconds |
start_time |
int | None |
Program start (Unix timestamp) |
end_time |
int | None |
Program end (Unix timestamp) |
speed |
int | None |
Playback speed (0 = paused, 1 = normal) |
paused |
bool |
Whether playback is paused |
image |
str | None |
Content/channel image URL |
app_name |
str | None |
Active app name (when source is APPS) |
Error Handling
from lghorizon import (
LGHorizonApiError, # Base exception
LGHorizonApiConnectionError, # Network/connection issues
LGHorizonApiUnauthorizedError, # Invalid credentials
LGHorizonApiLockedError, # Account locked
)
try:
await api.initialize()
except LGHorizonApiLockedError:
print("Account is locked, try again later")
except LGHorizonApiUnauthorizedError:
print("Invalid credentials")
except LGHorizonApiConnectionError:
print("Could not connect to the API")
except LGHorizonApiError as e:
print(f"API error: {e}")
Development
Setup
git clone https://github.com/Sholofly/lghorizon-python.git
cd lghorizon-python
pip install -e .
pip install pytest pytest-asyncio
Running tests
python -m pytest tests/ -v
Running the demo script
- Create a
secrets.json(see Quick Start) - Run
python main.py
The demo script prints all profiles, devices, channels, recordings, and then monitors live state changes with a visual progress bar. It also provides an interactive command prompt to control your set-top boxes (send messages, switch channels, etc.). Type help for a list of available commands.
Web-based Test UI
A browser-based interface for testing all library features interactively:
python web.py
Then open http://localhost:8080 in your browser.
Features:
- Login with your provider credentials (username/password or refresh token, depending on provider)
- Live dashboard showing all set-top boxes with real-time state updates via WebSocket
- Full device control: power, playback, channel switching, message display, raw key input
- Event log with timestamped state changes
- Optional "Remember credentials" to save your provider and username between sessions (passwords are never stored)
Note: No additional dependencies are required —
web.pyuses the sameaiohttpthat the library already depends on.
License
MIT License
Release files for lghorizon 0.12.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| lghorizon-0.12.2.tar.gz | 87.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| lghorizon-0.12.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 124.1 kB
Release files / lghorizon-0.12.2.tar.gz
| Download URL | lghorizon-0.12.2.tar.gz |
|---|---|
| Size | 87.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6675cc5338b7a8383f5e77551896e6c623e5d078cc69bb013b006990caa04464
|
|
BLAKE2b-256 checksum How to use checksums |
470e863082b84bfd830bdc2175787b051acb08c4132af593d1029bccd1576f10
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 26, 2026.
Transparency logRelease files / lghorizon-0.12.2-py3-none-any.whl
| Download URL | lghorizon-0.12.2-py3-none-any.whl |
|---|---|
| Size | 37.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
edc973897d96e22d34dd3b65aa1899c6ad9445d206b688db928b10eb2fd19876
|
|
BLAKE2b-256 checksum How to use checksums |
e16cf142a2e5d8d60339e987c7278df520b7cc582f81683fe356dd03e0af5017
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 26, 2026.
Transparency log