Python client for Liberty Global Horizon settop boxes
Project description
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.
License
MIT License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file lghorizon-0.10.7.tar.gz.
File metadata
- Download URL: lghorizon-0.10.7.tar.gz
- Upload date:
- Size: 61.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f1992440452a4849d4a6ba560137b649a2b1fd003f2c51bbbe91cd63d85ec719
|
|
| MD5 |
f400615bc19f08267089598721b0c105
|
|
| BLAKE2b-256 |
4e420241ae2ae3beebff3bac93d0cb09ce4bc7a56086906871df4237f28d4060
|
Provenance
The following attestation bundles were made for lghorizon-0.10.7.tar.gz:
Publisher:
publish-to-pypi.yml on Sholofly/lghorizon-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
lghorizon-0.10.7.tar.gz -
Subject digest:
f1992440452a4849d4a6ba560137b649a2b1fd003f2c51bbbe91cd63d85ec719 - Sigstore transparency entry: 1384449327
- Sigstore integration time:
-
Permalink:
Sholofly/lghorizon-python@7e07f196e21ff5932d4d278697ed28f128766c04 -
Branch / Tag:
refs/tags/v0.10.7 - Owner: https://github.com/Sholofly
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-to-pypi.yml@7e07f196e21ff5932d4d278697ed28f128766c04 -
Trigger Event:
release
-
Statement type:
File details
Details for the file lghorizon-0.10.7-py3-none-any.whl.
File metadata
- Download URL: lghorizon-0.10.7-py3-none-any.whl
- Upload date:
- Size: 32.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6bd5992998dde40a8a94b0cd7ecd6041d9ef85bbffa83190edad69a7974508ed
|
|
| MD5 |
086f7883d8551c03760372b386e3d2ef
|
|
| BLAKE2b-256 |
40f02a57f6fca293f02c8387614ea2b08bedc2ef95d7a58a046f81e47163820f
|
Provenance
The following attestation bundles were made for lghorizon-0.10.7-py3-none-any.whl:
Publisher:
publish-to-pypi.yml on Sholofly/lghorizon-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
lghorizon-0.10.7-py3-none-any.whl -
Subject digest:
6bd5992998dde40a8a94b0cd7ecd6041d9ef85bbffa83190edad69a7974508ed - Sigstore transparency entry: 1384449342
- Sigstore integration time:
-
Permalink:
Sholofly/lghorizon-python@7e07f196e21ff5932d4d278697ed28f128766c04 -
Branch / Tag:
refs/tags/v0.10.7 - Owner: https://github.com/Sholofly
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-to-pypi.yml@7e07f196e21ff5932d4d278697ed28f128766c04 -
Trigger Event:
release
-
Statement type: