lghorizon 0.9.0__tar.gz → 0.9.0.dev1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. {lghorizon-0.9.0 → lghorizon-0.9.0.dev1}/.github/workflows/build-on-pr.yml +1 -1
  2. {lghorizon-0.9.0 → lghorizon-0.9.0.dev1}/.github/workflows/publish-to-pypi.yml +1 -1
  3. lghorizon-0.9.0.dev1/PKG-INFO +41 -0
  4. lghorizon-0.9.0.dev1/README.md +3 -0
  5. lghorizon-0.9.0.dev1/lghorizon/__init__.py +36 -0
  6. lghorizon-0.9.0/lghorizon/lghorizon_device_state_processor.py → lghorizon-0.9.0.dev1/lghorizon/device_state_processor.py +34 -98
  7. lghorizon-0.9.0.dev1/lghorizon/legacy/lghorizon_api.py +469 -0
  8. lghorizon-0.9.0.dev1/lghorizon/legacy/models.py +768 -0
  9. lghorizon-0.9.0/lghorizon/lghorizon_api.py → lghorizon-0.9.0.dev1/lghorizon/lghorizonapi.py +27 -57
  10. lghorizon-0.9.0/lghorizon/lghorizon_message_factory.py → lghorizon-0.9.0.dev1/lghorizon/message_factory.py +2 -1
  11. lghorizon-0.9.0.dev1/lghorizon/models/__init__.py +12 -0
  12. {lghorizon-0.9.0/lghorizon → lghorizon-0.9.0.dev1/lghorizon/models}/exceptions.py +3 -3
  13. lghorizon-0.9.0.dev1/lghorizon/models/lghorizon_auth.py +227 -0
  14. lghorizon-0.9.0.dev1/lghorizon/models/lghorizon_channel.py +53 -0
  15. lghorizon-0.9.0.dev1/lghorizon/models/lghorizon_config.py +65 -0
  16. lghorizon-0.9.0.dev1/lghorizon/models/lghorizon_customer.py +55 -0
  17. {lghorizon-0.9.0/lghorizon → lghorizon-0.9.0.dev1/lghorizon/models}/lghorizon_device.py +58 -125
  18. lghorizon-0.9.0.dev1/lghorizon/models/lghorizon_device_state.py +180 -0
  19. lghorizon-0.9.0.dev1/lghorizon/models/lghorizon_entitlements.py +21 -0
  20. lghorizon-0.9.0.dev1/lghorizon/models/lghorizon_events.py +119 -0
  21. lghorizon-0.9.0.dev1/lghorizon/models/lghorizon_message.py +113 -0
  22. lghorizon-0.9.0.dev1/lghorizon/models/lghorizon_mqtt_client.py +126 -0
  23. lghorizon-0.9.0.dev1/lghorizon/models/lghorizon_profile.py +46 -0
  24. lghorizon-0.9.0.dev1/lghorizon/models/lghorizon_recordings.py +243 -0
  25. lghorizon-0.9.0.dev1/lghorizon/models/lghorizon_sources.py +127 -0
  26. lghorizon-0.9.0.dev1/lghorizon/models/lghorizon_ui_status.py +127 -0
  27. lghorizon-0.9.0/lghorizon/lghorizon_recording_factory.py → lghorizon-0.9.0.dev1/lghorizon/recording_factory.py +3 -17
  28. lghorizon-0.9.0.dev1/lghorizon.egg-info/PKG-INFO +41 -0
  29. lghorizon-0.9.0.dev1/lghorizon.egg-info/SOURCES.txt +47 -0
  30. {lghorizon-0.9.0 → lghorizon-0.9.0.dev1}/main.py +15 -6
  31. lghorizon-0.9.0.dev1/renovate.json +7 -0
  32. lghorizon-0.9.0/PKG-INFO +0 -191
  33. lghorizon-0.9.0/README.md +0 -153
  34. lghorizon-0.9.0/lghorizon/__init__.py +0 -71
  35. lghorizon-0.9.0/lghorizon/lghorizon_models.py +0 -1512
  36. lghorizon-0.9.0/lghorizon/lghorizon_mqtt_client.py +0 -335
  37. lghorizon-0.9.0/lghorizon.egg-info/PKG-INFO +0 -191
  38. lghorizon-0.9.0/lghorizon.egg-info/SOURCES.txt +0 -33
  39. lghorizon-0.9.0/renovate.json +0 -7
  40. {lghorizon-0.9.0 → lghorizon-0.9.0.dev1}/.coverage +0 -0
  41. {lghorizon-0.9.0 → lghorizon-0.9.0.dev1}/.flake8 +0 -0
  42. {lghorizon-0.9.0 → lghorizon-0.9.0.dev1}/.gitignore +0 -0
  43. {lghorizon-0.9.0 → lghorizon-0.9.0.dev1}/.vscode/launch.json +0 -0
  44. {lghorizon-0.9.0 → lghorizon-0.9.0.dev1}/LICENSE +0 -0
  45. {lghorizon-0.9.0 → lghorizon-0.9.0.dev1}/instructions.txt +0 -0
  46. {lghorizon-0.9.0 → lghorizon-0.9.0.dev1}/lghorizon/const.py +0 -0
  47. {lghorizon-0.9.0 → lghorizon-0.9.0.dev1}/lghorizon/helpers.py +0 -0
  48. {lghorizon-0.9.0 → lghorizon-0.9.0.dev1}/lghorizon/py.typed +0 -0
  49. {lghorizon-0.9.0 → lghorizon-0.9.0.dev1}/lghorizon.egg-info/dependency_links.txt +0 -0
  50. {lghorizon-0.9.0 → lghorizon-0.9.0.dev1}/lghorizon.egg-info/not-zip-safe +0 -0
  51. {lghorizon-0.9.0 → lghorizon-0.9.0.dev1}/lghorizon.egg-info/requires.txt +0 -0
  52. {lghorizon-0.9.0 → lghorizon-0.9.0.dev1}/lghorizon.egg-info/top_level.txt +0 -0
  53. {lghorizon-0.9.0 → lghorizon-0.9.0.dev1}/lib64 +0 -0
  54. {lghorizon-0.9.0 → lghorizon-0.9.0.dev1}/pyvenv.cfg +0 -0
  55. {lghorizon-0.9.0 → lghorizon-0.9.0.dev1}/secrets_stub.json +0 -0
  56. {lghorizon-0.9.0 → lghorizon-0.9.0.dev1}/setup.cfg +0 -0
  57. {lghorizon-0.9.0 → lghorizon-0.9.0.dev1}/setup.py +0 -0
@@ -11,7 +11,7 @@ jobs:
11
11
  steps:
12
12
  - uses: actions/checkout@master
13
13
  - name: Set up Python 3.10
14
- uses: actions/setup-python@v6
14
+ uses: actions/setup-python@v5
15
15
  with:
16
16
  python-version: '3.13'
17
17
  - name: Install pypa/build
@@ -12,7 +12,7 @@ jobs:
12
12
  steps:
13
13
  - uses: actions/checkout@master
14
14
  - name: Set up Python 3.10
15
- uses: actions/setup-python@v6
15
+ uses: actions/setup-python@v5
16
16
  with:
17
17
  python-version: '3.13'
18
18
 
@@ -0,0 +1,41 @@
1
+ Metadata-Version: 2.4
2
+ Name: lghorizon
3
+ Version: 0.9.0.dev1
4
+ Summary: Python client for Liberty Global Horizon settop boxes
5
+ Home-page: https://github.com/sholofly/LGHorizon-python
6
+ Author: Rudolf Offereins
7
+ Author-email: r.offereins@gmail.com
8
+ License: MIT license
9
+ Keywords: LG,Horizon,API,Settop box
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Natural Language :: English
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Programming Language :: Python :: 3.6
17
+ Classifier: Programming Language :: Python :: 3.7
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.9
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: paho-mqtt
24
+ Requires-Dist: requests>=2.22.0
25
+ Requires-Dist: backoff>=1.9.0
26
+ Dynamic: author
27
+ Dynamic: author-email
28
+ Dynamic: classifier
29
+ Dynamic: description
30
+ Dynamic: description-content-type
31
+ Dynamic: home-page
32
+ Dynamic: keywords
33
+ Dynamic: license
34
+ Dynamic: license-file
35
+ Dynamic: requires-dist
36
+ Dynamic: requires-python
37
+ Dynamic: summary
38
+
39
+ # LG Horizon Api
40
+
41
+ Python library to control multiple LG Horizon boxes
@@ -0,0 +1,3 @@
1
+ # LG Horizon Api
2
+
3
+ Python library to control multiple LG Horizon boxes
@@ -0,0 +1,36 @@
1
+ """Python client for LG Horizon."""
2
+
3
+ from .lghorizonapi import LGHorizonApi
4
+ from .models.lghorizon_auth import (
5
+ LGHorizonAuth,
6
+ )
7
+ from .models.exceptions import (
8
+ LGHorizonApiUnauthorizedError,
9
+ LGHorizonApiConnectionError,
10
+ LGHorizonApiLockedError,
11
+ )
12
+ from .const import (
13
+ ONLINE_RUNNING,
14
+ ONLINE_STANDBY,
15
+ RECORDING_TYPE_SHOW,
16
+ RECORDING_TYPE_SEASON,
17
+ RECORDING_TYPE_SINGLE,
18
+ )
19
+
20
+ __all__ = [
21
+ "LGHorizonApi",
22
+ "LGHorizonBox",
23
+ "LGHorizonRecordingListSeasonShow",
24
+ "LGHorizonRecordingSingle",
25
+ "LGHorizonRecordingShow",
26
+ "LGHorizonRecordingEpisode",
27
+ "LGHorizonCustomer",
28
+ "LGHorizonApiUnauthorizedError",
29
+ "LGHorizonApiConnectionError",
30
+ "LGHorizonApiLockedError",
31
+ "ONLINE_RUNNING",
32
+ "ONLINE_STANDBY",
33
+ "RECORDING_TYPE_SHOW",
34
+ "RECORDING_TYPE_SEASON",
35
+ "RECORDING_TYPE_SINGLE",
36
+ ] # noqa
@@ -3,34 +3,33 @@
3
3
  import random
4
4
  import json
5
5
  import urllib.parse
6
- from datetime import datetime as dt, timezone
7
-
8
- import time
9
6
 
10
7
  from typing import cast, Dict, Optional
11
8
 
12
- from .lghorizon_models import LGHorizonDeviceState, LGHorizonRunningState
13
- from .lghorizon_models import LGHorizonStatusMessage, LGHorizonUIStatusMessage
14
- from .lghorizon_models import (
9
+ from .models.lghorizon_device_state import LGHorizonDeviceState, LGHorizonRunningState
10
+ from .models.lghorizon_message import LGHorizonStatusMessage, LGHorizonUIStatusMessage
11
+ from .models.lghorizon_sources import (
15
12
  LGHorizonSourceType,
16
13
  LGHorizonLinearSource,
17
14
  LGHorizonVODSource,
18
15
  LGHorizonReplaySource,
19
16
  LGHorizonNDVRSource,
20
17
  LGHorizonReviewBufferSource,
21
- LGHorizonRecordingSource,
22
18
  )
23
- from .lghorizon_models import LGHorizonAuth
24
- from .lghorizon_models import LGHorizonReplayEvent, LGHorizonVOD, LGHorizonVODType
19
+ from .models.lghorizon_auth import LGHorizonAuth
20
+ from .models.lghorizon_events import (
21
+ LGHorizonReplayEvent,
22
+ LGHorizonVOD,
23
+ )
25
24
 
26
- from .lghorizon_models import LGHorizonRecordingSingle
27
- from .lghorizon_models import LGHorizonChannel
28
- from .lghorizon_models import (
25
+ from .models.lghorizon_recordings import LGHorizonRecordingSingle
26
+ from .models.lghorizon_channel import LGHorizonChannel
27
+ from .models.lghorizon_ui_status import (
29
28
  LGHorizonUIStateType,
30
29
  LGHorizonAppsState,
31
30
  LGHorizonPlayerState,
32
31
  )
33
- from .lghorizon_models import LGHorizonCustomer
32
+ from .models.lghorizon_customer import LGHorizonCustomer
34
33
 
35
34
 
36
35
  class LGHorizonDeviceStateProcessor:
@@ -100,9 +99,6 @@ class LGHorizonDeviceStateProcessor:
100
99
  return
101
100
  await device_state.reset()
102
101
  device_state.source_type = player_state.source_type
103
- device_state.ui_state_type = LGHorizonUIStateType.MAINUI
104
- device_state.speed = player_state.speed
105
-
106
102
  match player_state.source_type:
107
103
  case LGHorizonSourceType.LINEAR:
108
104
  await self._process_linear_state(device_state, player_state)
@@ -120,10 +116,9 @@ class LGHorizonDeviceStateProcessor:
120
116
  device_state: LGHorizonDeviceState,
121
117
  apps_state: LGHorizonAppsState,
122
118
  ) -> None:
123
- device_state.id = apps_state.id
124
- device_state.show_title = apps_state.app_name
119
+ device_state.channel_id = apps_state.id
120
+ device_state.title = apps_state.app_name
125
121
  device_state.image = apps_state.logo_path
126
- device_state.ui_state_type = LGHorizonUIStateType.APPS
127
122
 
128
123
  async def _process_linear_state(
129
124
  self,
@@ -145,22 +140,12 @@ class LGHorizonDeviceStateProcessor:
145
140
  service_path,
146
141
  )
147
142
  replay_event = LGHorizonReplayEvent(event_json)
148
- device_state.id = replay_event.event_id
149
143
  channel = self._channels[replay_event.channel_id]
150
144
  device_state.source_type = source.source_type
151
- device_state.channel_id = channel.id
145
+ device_state.channel_id = channel.channel_number
152
146
  device_state.channel_name = channel.title
153
- device_state.episode_title = replay_event.episode_name
154
- device_state.season_number = replay_event.season_number
155
- device_state.episode_number = replay_event.episode_number
156
- device_state.show_title = replay_event.title
157
- now_in_ms = int(time.time() * 1000)
158
-
159
- device_state.last_position_update = int(time.time() * 1000)
160
- device_state.start_time = replay_event.start_time
161
- device_state.end_time = replay_event.end_time
162
- device_state.duration = replay_event.end_time - replay_event.start_time
163
- device_state.position = now_in_ms - int(replay_event.start_time * 1000)
147
+ device_state.title = replay_event.title
148
+ device_state.sub_title = replay_event.full_episode_title
164
149
 
165
150
  # Add random number to url to force refresh
166
151
  join_param = "?"
@@ -170,6 +155,7 @@ class LGHorizonDeviceStateProcessor:
170
155
  f"{channel.stream_image}{join_param}{str(random.randrange(1000000))}"
171
156
  )
172
157
  device_state.image = image_url
158
+ await device_state.reset_progress()
173
159
 
174
160
  async def _process_reviewbuffer_state(
175
161
  self,
@@ -191,20 +177,13 @@ class LGHorizonDeviceStateProcessor:
191
177
  service_path,
192
178
  )
193
179
  replay_event = LGHorizonReplayEvent(event_json)
194
- device_state.id = replay_event.event_id
195
180
  channel = self._channels[replay_event.channel_id]
196
181
  device_state.source_type = source.source_type
197
- device_state.channel_id = channel.id
182
+ device_state.channel_id = channel.channel_number
198
183
  device_state.channel_name = channel.title
199
- device_state.episode_title = replay_event.episode_name
200
- device_state.season_number = replay_event.season_number
201
- device_state.episode_number = replay_event.episode_number
202
- device_state.show_title = replay_event.title
203
- device_state.last_position_update = player_state.last_speed_change_time
204
- device_state.position = player_state.relative_position
205
- device_state.start_time = replay_event.start_time
206
- device_state.end_time = replay_event.end_time
207
- device_state.duration = replay_event.end_time - replay_event.start_time
184
+ device_state.title = replay_event.title
185
+ device_state.sub_title = replay_event.full_episode_title
186
+
208
187
  # Add random number to url to force refresh
209
188
  join_param = "?"
210
189
  if join_param in channel.stream_image:
@@ -213,6 +192,7 @@ class LGHorizonDeviceStateProcessor:
213
192
  f"{channel.stream_image}{join_param}{str(random.randrange(1000000))}"
214
193
  )
215
194
  device_state.image = image_url
195
+ await device_state.reset_progress()
216
196
 
217
197
  async def _process_replay_state(
218
198
  self,
@@ -234,27 +214,15 @@ class LGHorizonDeviceStateProcessor:
234
214
  service_path,
235
215
  )
236
216
  replay_event = LGHorizonReplayEvent(event_json)
237
- device_state.id = replay_event.event_id
238
- # Iets met buffer doen
239
- channel = self._channels[replay_event.channel_id]
240
- padding = channel.replay_pre_padding + channel.replay_post_padding
241
217
  device_state.source_type = source.source_type
242
- device_state.channel_id = channel.id
243
- device_state.episode_title = replay_event.episode_name
244
- device_state.season_number = replay_event.season_number
245
- device_state.episode_number = replay_event.episode_number
246
- device_state.show_title = replay_event.title
247
- device_state.last_position_update = int(time.time() * 1000)
248
- device_state.start_time = replay_event.start_time
249
- device_state.end_time = replay_event.end_time
250
- device_state.duration = (
251
- replay_event.end_time - replay_event.start_time + padding
252
- )
253
- device_state.position = (
254
- player_state.relative_position + channel.replay_pre_padding
255
- )
218
+ device_state.channel_id = None
219
+ device_state.title = replay_event.title
220
+ if replay_event.full_episode_title:
221
+ device_state.sub_title = replay_event.full_episode_title
222
+
256
223
  # Add random number to url to force refresh
257
224
  device_state.image = await self._get_intent_image_url(replay_event.event_id)
225
+ await device_state.reset_progress()
258
226
 
259
227
  async def _process_vod_state(
260
228
  self,
@@ -276,20 +244,11 @@ class LGHorizonDeviceStateProcessor:
276
244
  service_path,
277
245
  )
278
246
  vod = LGHorizonVOD(vod_json)
279
- device_state.id = vod.id
280
- if vod.vod_type == LGHorizonVODType.EPISODE:
281
- device_state.show_title = vod.series_title
282
- device_state.episode_title = vod.title
283
- device_state.season_number = vod.season
284
- device_state.episode_number = vod.episode
285
- else:
286
- device_state.show_title = vod.title
287
-
247
+ device_state.title = vod.title
248
+ device_state.sub_title = vod.full_episode_title
288
249
  device_state.duration = vod.duration
289
- device_state.last_position_update = int(time.time() * 1000)
290
- device_state.position = player_state.relative_position
291
-
292
250
  device_state.image = await self._get_intent_image_url(vod.id)
251
+ await device_state.reset_progress()
293
252
 
294
253
  async def _process_ndvr_state(
295
254
  self, device_state: LGHorizonDeviceState, player_state: LGHorizonPlayerState
@@ -308,36 +267,13 @@ class LGHorizonDeviceStateProcessor:
308
267
  service_path,
309
268
  )
310
269
  recording = LGHorizonRecordingSingle(recording_json)
311
- device_state.id = recording.id
270
+ device_state.title = recording.title
271
+ device_state.sub_title = recording.full_episode_title
312
272
  device_state.channel_id = recording.channel_id
313
273
  if recording.channel_id:
314
274
  channel = self._channels[recording.channel_id]
315
275
  device_state.channel_name = channel.title
316
276
 
317
- device_state.episode_title = recording.episode_title
318
- device_state.season_number = recording.season_number
319
- device_state.episode_number = recording.episode_number
320
- device_state.last_position_update = player_state.last_speed_change_time
321
- device_state.position = player_state.relative_position
322
- if recording.start_time:
323
- device_state.start_time = int(
324
- dt.fromisoformat(
325
- recording.start_time.replace("Z", "+00:00")
326
- ).timestamp()
327
- )
328
- if recording.end_time:
329
- device_state.end_time = int(
330
- dt.fromisoformat(recording.end_time.replace("Z", "+00:00")).timestamp()
331
- )
332
- if recording.start_time and recording.end_time:
333
- device_state.duration = device_state.end_time - device_state.start_time
334
- if recording.source == LGHorizonRecordingSource.SHOW:
335
- device_state.show_title = recording.title
336
- else:
337
- device_state.show_title = recording.show_title
338
-
339
- device_state.image = await self._get_intent_image_url(recording.id)
340
-
341
277
  async def _get_intent_image_url(self, intent_id: str) -> Optional[str]:
342
278
  """Get intent image url."""
343
279
  service_config = await self._auth.get_service_config()