1+ from __future__ import annotations
12import json
23import re
34import xbmc
5+ import xbmcaddon
46import xbmcgui
57import xbmcvfs
68import xml .etree .ElementTree as ElementTree
79from urllib .parse import unquote
810from typing import Any
911
10- # noinspection PyPackages
11- from .constants import ADDON
12+
13+ # (TODO - once OzWeather 2.1.6 and all the other addons updates are released, the * here can just be ADDON again, sigh)
14+ # noinspection PyPackages,PyUnusedImports
15+ from .constants import *
1216# noinspection PyPackages
1317from .logger import Logger
1418
@@ -214,6 +218,131 @@ def is_playback_paused() -> bool:
214218 return bool (xbmc .getCondVisibility ("Player.Paused" ))
215219
216220
221+ def get_addon_version (addon_id : str ) -> str | None :
222+ """
223+ Helper function to return the currently installed version of Kodi addon by its ID.
224+
225+ :param addon_id: the ID of the addon, e.g. weather.ozweather
226+ :return: the version string (e.g. "0.0.1"), or None if the addon is not installed and enabled
227+ """
228+ try :
229+ addon = xbmcaddon .Addon (id = addon_id )
230+ version = addon .getAddonInfo ('version' )
231+ except RuntimeError as e :
232+ Logger .error (f"Error getting version for { addon_id } " )
233+ Logger .error (e )
234+ return None
235+
236+ return version
237+
238+
239+ def version_tuple (version_str : str ) -> tuple :
240+ """
241+ Helper function to return a version tuple from a version string "2.1.5" -> (2, 1 , 5)
242+ Useful for comparisons, e.g. if version_tuple(version) <= version_tuple('2.1.5')
243+
244+ :param version_str: the addon version string
245+ :return: version in tuple form (1, 2, 3)
246+ """
247+ return tuple (map (int , version_str .split ('.' )))
248+
249+
250+ def get_resume_point (library_type : str , dbid : int ) -> float | None :
251+ """
252+ Get the resume point from the Kodi library for a given Kodi DB item
253+
254+ :param library_type: one of 'episode', 'movie' or 'musicvideo'
255+ :param dbid: the Kodi DB item ID
256+ :return: the resume point, or None if there isn't one set
257+ """
258+
259+ params = _get_jsonrpc_video_lib_params (library_type )
260+ # Short circuit if there is an issue get the JSON RPC method etc.
261+ if not params :
262+ return None
263+ get_method , id_name , result_key = params
264+
265+ json_dict = {
266+ "jsonrpc" :"2.0" ,
267+ "id" :"getResumePoint" ,
268+ "method" :get_method ,
269+ "params" :{
270+ id_name :dbid ,
271+ "properties" :["resume" ],
272+ }
273+ }
274+
275+ query = json .dumps (json_dict )
276+ json_response = send_kodi_json (f'Get resume point for { library_type } with dbid: { dbid } ' , query )
277+ if not json_response :
278+ Logger .error ("Nothing returned from JSON-RPC query" )
279+ return None
280+
281+ result = json_response .get ('result' )
282+ if result :
283+ try :
284+ resume_point = result [result_key ]['resume' ]['position' ]
285+ except (KeyError , TypeError ) as e :
286+ Logger .error ("Could not get resume point" )
287+ Logger .error (e )
288+ resume_point = None
289+ else :
290+ Logger .error ("No result returned from JSON-RPC query" )
291+ resume_point = None
292+
293+ Logger .info (f"Resume point retrieved: { resume_point } " )
294+
295+ return resume_point
296+
297+
298+ def get_playcount (library_type : str , dbid : int ) -> int | None :
299+ """
300+ Get the playcount for the given Kodi DB item
301+
302+ :param library_type: one of 'episode', 'movie' or 'musicvideo'
303+ :param dbid: the Kodi DB item ID
304+ :return: the playcount if there is one, or None
305+ """
306+
307+ params = _get_jsonrpc_video_lib_params (library_type )
308+ # Short circuit if there is an issue get the JSON RPC method etc.
309+ if not params :
310+ return None
311+ get_method , id_name , result_key = params
312+
313+ json_dict = {
314+ "jsonrpc" :"2.0" ,
315+ "id" :"getPlayCount" ,
316+ "method" :get_method ,
317+ "params" :{
318+ id_name :dbid ,
319+ "properties" :["playcount" ],
320+ }
321+ }
322+
323+ query = json .dumps (json_dict )
324+ json_response = send_kodi_json (f'Get playcount for { library_type } with dbid: { dbid } ' , query )
325+ if not json_response :
326+ Logger .error ("Nothing returned from JSON-RPC query" )
327+ return None
328+
329+ result = json_response .get ('result' )
330+ if result :
331+ try :
332+ play_count = result [result_key ]['playcount' ]
333+ except (KeyError , TypeError ) as e :
334+ Logger .error ("Could not get playcount" )
335+ Logger .error (e )
336+ play_count = None
337+ else :
338+ Logger .error ("No result returned from JSON-RPC query" )
339+ play_count = None
340+
341+ Logger .info (f"Playcount retrieved: { play_count } " )
342+
343+ return play_count
344+
345+
217346def footprints (startup : bool = True ) -> None :
218347 """
219348 TODO - this has moved to Logger - update all addons to use Logger.start/.stop directly, then ultimately remove this!
@@ -225,3 +354,30 @@ def footprints(startup: bool = True) -> None:
225354 Logger .start ()
226355 else :
227356 Logger .stop ()
357+
358+
359+ def _get_jsonrpc_video_lib_params (library_type : str ) -> tuple [str , str , str ] | None :
360+ """
361+ Given a Kodi library type, return the JSON RPC library parameters needed to get details
362+
363+ :param library_type: one of 'episode', 'movie' or 'musicvideo'
364+ :return: method for getting details, the name of the id, and the key for the results returned
365+ """
366+
367+ if library_type == 'episode' :
368+ get_method = 'VideoLibrary.GetEpisodeDetails'
369+ id_name = 'episodeid'
370+ result_key = 'episodedetails'
371+ elif library_type == 'movie' :
372+ get_method = 'VideoLibrary.GetMovieDetails'
373+ id_name = 'movieid'
374+ result_key = 'moviedetails'
375+ elif library_type == 'musicvideo' :
376+ get_method = 'VideoLibrary.GetMusicVideoDetails'
377+ id_name = 'musicvideoid'
378+ result_key = 'musicvideodetails'
379+ else :
380+ Logger .error (f"Unsupported library type: { library_type } " )
381+ return None
382+
383+ return get_method , id_name , result_key
0 commit comments