From b6bdd79396e531b629c29415735b118acd570e9e Mon Sep 17 00:00:00 2001 From: miawgogo Date: Thu, 11 Jan 2024 19:46:17 +0000 Subject: [PATCH 1/6] streaming - added a option to allow the user to set a maximum resilution to make use of stash's transcoding for low power devices like some google TVs. Due to the encoding delays on consumer hardware inputstream support has also been added to use the DASH streams from stash --- .gitignore | 1 + build.sh | 10 ++++ plugin.video.stash.zip | Bin 0 -> 23254 bytes .../resource.language.en_gb/strings.po | 13 +++++ resources/lib/listing/listing.py | 23 +++++--- resources/lib/listing/scene_marker_listing.py | 23 +++++--- resources/lib/plugin.py | 50 ++++++++++++++++- resources/lib/stash_interface.py | 32 +++++++---- resources/lib/utils/logger.py | 52 ++++++++++++++++++ resources/settings.xml | 6 +- 10 files changed, 178 insertions(+), 32 deletions(-) create mode 100755 build.sh create mode 100644 plugin.video.stash.zip create mode 100644 resources/lib/utils/logger.py diff --git a/.gitignore b/.gitignore index bee8a64..0d27464 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ __pycache__ +venv/ diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..8ac5c17 --- /dev/null +++ b/build.sh @@ -0,0 +1,10 @@ +#! /bin/bash +( + mkdir -p /tmp/plugin.video.stash + rsync -a --exclude 'resources/stash_logo.svg' addon.xml addon.py resources /tmp/plugin.video.stash + cd /tmp/ + rm -r /tmp/plugin.video.stash/venv + zip -r plugin.video.stash.zip plugin.video.stash/ + rm -r /tmp/plugin.video.stash +) +mv /tmp/plugin.video.stash.zip . diff --git a/plugin.video.stash.zip b/plugin.video.stash.zip new file mode 100644 index 0000000000000000000000000000000000000000..1d097ac363fcb44e813299ba5e3ce748df7a77ec GIT binary patch literal 23254 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 literal 0 HcmV?d00001 diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index 4e3cc78..060c49f 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -64,3 +64,16 @@ msgstr "" msgctxt "#30012" msgid "Scene Tags" msgstr "" + + +msgctxt "#30013" +msgid "Server Settings" +msgstr "" + +msgctxt "#30014" +msgid "Playback Settings" +msgstr "" + +msgctxt "#30015" +msgid "Max Resolution" +msgstr "" \ No newline at end of file diff --git a/resources/lib/listing/listing.py b/resources/lib/listing/listing.py index 27cc192..d147cfe 100644 --- a/resources/lib/listing/listing.py +++ b/resources/lib/listing/listing.py @@ -22,7 +22,8 @@ def __init__(self, client: StashInterface, type: str, label: str, **kwargs): def list_items(self, params: dict): title = params['title'] if 'title' in params else self._label - criterion = json.loads(params['criterion']) if 'criterion' in params else {} + criterion = json.loads( + params['criterion']) if 'criterion' in params else {} sort_field = params['sort_field'] if 'sort_field' in params else None sort_dir = params['sort_dir'] if 'sort_dir' in params else 'asc' @@ -36,7 +37,8 @@ def list_items(self, params: dict): xbmcplugin.endOfDirectory(self.handle) def get_root_item(self, override_title: str = "") -> (xbmcgui.ListItem, str): - item = xbmcgui.ListItem(label=override_title if override_title != "" else self._label) + item = xbmcgui.ListItem( + label=override_title if override_title != "" else self._label) url = utils.get_url(list=self._type) return item, url @@ -48,7 +50,8 @@ def get_filters(self) -> [(xbmcgui.ListItem, str)]: items = [] default_filter = self._client.find_default_filter(self._filter_type) if default_filter is not None: - items.append(self._create_item_from_filter(default_filter, local.get_localized(30007))) + items.append(self._create_item_from_filter( + default_filter, local.get_localized(30007))) else: item = xbmcgui.ListItem(label=local.get_localized(30007)) url = utils.get_url(list=self._type) @@ -77,8 +80,9 @@ def _create_item(self, scene: dict, **kwargs): title = kwargs['title'] if 'title' in kwargs else scene['title'] screenshot = kwargs['screenshot'] if 'screenshot' in kwargs else scene['paths']['screenshot'] # * 2 because rating is 1 to 5 and Kodi uses 1 to 10 - rating = scene['rating'] * 2 if 'rating' in scene and scene['rating'] is not None else 0 - duration = int(scene['file']['duration']) + rating = scene['rating'] * \ + 2 if 'rating' in scene and scene['rating'] is not None else 0 + duration = int(scene['files'][0]['duration']) item = xbmcgui.ListItem(label=title) item.setInfo('video', {'title': title, 'mediatype': 'video', @@ -92,12 +96,13 @@ def _create_item(self, scene: dict, **kwargs): 'dateadded': scene['created_at'] }) - item.addStreamInfo('video', {'codec': scene['file']['video_codec'], - 'width': scene['file']['width'], - 'height': scene['file']['height'], + item.addStreamInfo('video', {'codec': scene['files'][0]['video_codec'], + 'width': scene['files'][0]['width'], + 'height': scene['files'][0]['height'], 'duration': duration}) - item.addStreamInfo('audio', {'codec': scene['file']['audio_codec']}) + item.addStreamInfo( + 'audio', {'codec': scene['files'][0]['audio_codec']}) screenshot = self._client.add_api_key(screenshot) item.setArt({'thumb': screenshot, 'fanart': screenshot}) diff --git a/resources/lib/listing/scene_marker_listing.py b/resources/lib/listing/scene_marker_listing.py index 8bea8a6..fde1d29 100644 --- a/resources/lib/listing/scene_marker_listing.py +++ b/resources/lib/listing/scene_marker_listing.py @@ -8,13 +8,15 @@ class SceneMarkerListing(Listing): def __init__(self, client: StashInterface): - Listing.__init__(self, client, 'scene_markers', local.get_localized(30010), filter_type='SCENE_MARKERS') + Listing.__init__(self, client, 'scene_markers', local.get_localized( + 30010), filter_type='SCENE_MARKERS') def get_navigation(self) -> [NavigationItem]: return [ PerformerItem(self._client, 'scene_markers'), TagItem(self._client, 'scene_markers'), - TagItem(self._client, 'scene_markers', type='scene_tags', label=local.get_localized(30012)), + TagItem(self._client, 'scene_markers', type='scene_tags', + label=local.get_localized(30012)), ] def get_navigation_item(self, params: dict) -> Optional[NavigationItem]: @@ -30,19 +32,24 @@ def _create_items(self, criterion: dict, sort_field: str, sort_dir: int, params: if 'scene' in params: scene = self._client.find_scene(params['scene']) - self._set_title('{} {}'.format(local.get_localized(30011), scene['title'])) + self._set_title('{} {}'.format( + local.get_localized(30011), scene['title'])) markers = scene['scene_markers'] else: - (_, markers) = self._client.find_scene_markers(criterion, sort_field, sort_dir) + (_, markers) = self._client.find_scene_markers( + criterion, sort_field, sort_dir) items = [] for marker in markers: - title = '{} - {}'.format(marker['title'], marker['primary_tag']['name']) - item = self._create_item(marker['scene'], title=title, screenshot=marker['screenshot']) + title = '{} - {}'.format(marker['title'], + marker['primary_tag']['name']) + item = self._create_item( + marker['scene'], title=title, screenshot=marker['screenshot']) url = self._create_play_url(marker['scene']['id']) - duration = marker['scene']['file']['duration'] - item.setProperty('StartPercent', str(round(marker['seconds'] / duration * 100, 2))) + duration = marker['scene']['files'][0]['duration'] + item.setProperty('StartPercent', str( + round(marker['seconds'] / duration * 100, 2))) items.append((item, url)) diff --git a/resources/lib/plugin.py b/resources/lib/plugin.py index 4c5f19d..1bfa04f 100644 --- a/resources/lib/plugin.py +++ b/resources/lib/plugin.py @@ -6,6 +6,7 @@ import xbmcgui import xbmcplugin from resources.lib import utils +from resources.lib.utils import logger from resources.lib.listing import create_listing, Listing, SceneListing from resources.lib.navigation import NavigationItem from resources.lib.stash_interface import StashInterface @@ -18,12 +19,33 @@ api_key: str = '' client: Optional[StashInterface] = None +res_map = { + "Original": "", + "4k": "FOUR_K", + "1440p": "QUAD_HD", + "FHD": "FULL_HD", + "720p": "STANDARD_HD", + "SD": "STANDARD", + "240p": "LOW", +} + +res_height_map = { + "4k": 1920, + "1440p": 1440, + "FHD": 1080, + "720p": 720, + "SD": 480, + "240p": 240, +} + def run(): global api_key global client + global playback_res api_key = _ADDON.getSetting('api_key') client = StashInterface(_ADDON.getSetting('base_url'), api_key) + playback_res = res_map.get(_ADDON.getSetting('res'), "") router(sys.argv[2][1:]) @@ -80,16 +102,40 @@ def browse_for(params: dict): navigation.list_items() +def get_mp4_stream(sceneStreams: dict) -> str: + for stream in sceneStreams: + if stream["label"] == "DASH": + return stream + + def play(params: dict): scene = client.find_scene(params['play']) - item = xbmcgui.ListItem(path=scene['paths']['stream']) + item = xbmcgui.ListItem() + if playback_res != "" and scene["files"][0]["height"] > res_height_map[_ADDON.getSetting('res')]: + stream = get_mp4_stream(scene["sceneStreams"]) + url = stream["url"].replace("ORIGINAL", playback_res) + item.setMimeType('application/xml+dash') + item.setContentLookup(False) + + item.setProperty('inputstream', 'inputstream.adaptive') + item.setProperty('inputstream.adaptive.manifest_type', 'mpd') + item.setProperty('inputstream.adaptive.stream_headers', + f'apikey={api_key}') + item.setProperty('inputstream.adaptive.manifest_headers', + f'apikey={api_key}') + item.setPath(url) + else: + streamurl = scene['paths']['stream'] + item.setPath(streamurl) + xbmcplugin.setResolvedUrl(_HANDLE, True, listitem=item) def increment_o(params: dict): if 'scene' in params: o_count = client.scene_increment_o(params['scene']) - xbmc.executebuiltin('Notification(Stash, {} {})'.format(utils.local.get_localized(30009), o_count)) + xbmc.executebuiltin('Notification(Stash, {} {})'.format( + utils.local.get_localized(30009), o_count)) def router(param_string: str): diff --git a/resources/lib/stash_interface.py b/resources/lib/stash_interface.py index 12e855e..a153076 100644 --- a/resources/lib/stash_interface.py +++ b/resources/lib/stash_interface.py @@ -19,7 +19,8 @@ def __init__(self, url, api_key): def add_api_key(self, url: str): if self._api_key: - url = "{}{}apikey={}".format(url, '&' if '?' in url else '?', urllib.parse.quote(self._api_key)) + url = "{}{}apikey={}".format( + url, '&' if '?' in url else '?', urllib.parse.quote(self._api_key)) return url @@ -34,7 +35,8 @@ def __call_graphql(self, query, variables=None): result = response.json() if result.get("errors", None): for error in result["errors"]: - raise Exception("GraphQL error: {}".format(error['message'])) + raise Exception( + "GraphQL error: {}".format(error['message'])) if result.get("data", None): return result.get("data") else: @@ -51,13 +53,13 @@ def find_scenes(self, scene_filter=None, sort_field='title', sort_dir='asc'): id title details - rating + rating100 date created_at paths { screenshot } - file { + files { duration video_codec audio_codec @@ -101,14 +103,20 @@ def find_scene(self, id): id title details - rating + rating100 date created_at + sceneStreams { + url + mime_type + label + __typename + } paths { stream screenshot } - file { + files { duration video_codec audio_codec @@ -133,13 +141,13 @@ def find_scene(self, id): id title details - rating + rating100 date created_at paths { screenshot } - file { + files { duration video_codec audio_codec @@ -197,7 +205,7 @@ def find_performers(self, **kwargs): 'modifier': 'GREATER_THAN', 'value': 0, } - } + } } result = self.__call_graphql(query, variables) @@ -262,7 +270,7 @@ def find_studios(self): 'modifier': 'GREATER_THAN', 'value': 0, } - } + } } result = self.__call_graphql(query, variables) @@ -283,13 +291,13 @@ def find_scene_markers(self, markers_filter=None, sort_field='title', sort_dir=0 id title details - rating + rating100 date created_at paths { screenshot } - file { + files { duration video_codec audio_codec diff --git a/resources/lib/utils/logger.py b/resources/lib/utils/logger.py new file mode 100644 index 0000000..1f2388c --- /dev/null +++ b/resources/lib/utils/logger.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +""" + + Copyright (C) 2014-2016 bromix (plugin.video.youtube) + Copyright (C) 2016-2018 plugin.video.youtube + + SPDX-License-Identifier: GPL-2.0-only + See LICENSES/GPL-2.0-only for more information. + + I took this code from the youtube plugin to avoid rewriting it +""" + +import xbmc +import xbmcaddon + +DEBUG = xbmc.LOGDEBUG +INFO = xbmc.LOGINFO +NOTICE = INFO +WARNING = xbmc.LOGWARNING +ERROR = xbmc.LOGERROR +FATAL = xbmc.LOGFATAL +SEVERE = FATAL +NONE = xbmc.LOGNONE + +_ADDON_ID = 'plugin.video.stash' + + +def log(text, log_level=DEBUG, addon_id=_ADDON_ID): + if not addon_id: + addon_id = xbmcaddon.Addon().getAddonInfo('id') + log_line = '[%s] %s' % (addon_id, text) + xbmc.log(msg=log_line, level=log_level) + + +def log_debug(text, addon_id=_ADDON_ID): + log(text, DEBUG, addon_id) + + +def log_info(text, addon_id=_ADDON_ID): + log(text, INFO, addon_id) + + +def log_notice(text, addon_id=_ADDON_ID): + log(text, NOTICE, addon_id) + + +def log_warning(text, addon_id=_ADDON_ID): + log(text, WARNING, addon_id) + + +def log_error(text, addon_id=_ADDON_ID): + log(text, ERROR, addon_id) diff --git a/resources/settings.xml b/resources/settings.xml index 2945e67..8160a99 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -1,7 +1,11 @@ - + + + + + From 7524bf08acc7134e7cfc6bdfd5e7e6776614e89d Mon Sep 17 00:00:00 2001 From: miawgogo Date: Thu, 11 Jan 2024 19:52:19 +0000 Subject: [PATCH 2/6] streaming - made original the default --- resources/settings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/settings.xml b/resources/settings.xml index 8160a99..8a1286b 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -5,7 +5,7 @@ - + From b352964aadfc7607a6d9825f1aab63a0d55d9b6c Mon Sep 17 00:00:00 2001 From: miawgogo Date: Thu, 11 Jan 2024 19:55:59 +0000 Subject: [PATCH 3/6] chore - cleaning up --- .gitignore | 1 + plugin.video.stash.zip | Bin 23254 -> 0 bytes resources/settings.xml | 2 +- 3 files changed, 2 insertions(+), 1 deletion(-) delete mode 100644 plugin.video.stash.zip diff --git a/.gitignore b/.gitignore index 0d27464..28424e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ __pycache__ venv/ +plugin.video.stash.zip diff --git a/plugin.video.stash.zip b/plugin.video.stash.zip deleted file mode 100644 index 1d097ac363fcb44e813299ba5e3ce748df7a77ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23254 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 diff --git a/resources/settings.xml b/resources/settings.xml index 8a1286b..7d42584 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -5,7 +5,7 @@ - + From 0303eba0a1849c58a5345babcb9b2d7e3a0ae43a Mon Sep 17 00:00:00 2001 From: miawgogo Date: Mon, 15 Jan 2024 00:11:03 +0000 Subject: [PATCH 4/6] some fixes and some additions --- resources/lib/criterion_parser.py | 22 ++++++++++++++++++---- resources/lib/listing/listing.py | 11 +++++++++-- resources/lib/stash_interface.py | 4 ++++ resources/lib/utils/resolutions.py | 18 ++++++++++++++++++ 4 files changed, 49 insertions(+), 6 deletions(-) create mode 100644 resources/lib/utils/resolutions.py diff --git a/resources/lib/criterion_parser.py b/resources/lib/criterion_parser.py index d2a9992..a0dc38f 100644 --- a/resources/lib/criterion_parser.py +++ b/resources/lib/criterion_parser.py @@ -1,4 +1,10 @@ import json +from resources.lib.utils.resolutions import Resolution_map +from resources.lib.utils import logger + +""" elif "resolution" == criterion: + val = criterion["resolution"]["modifier"]["value"] + criterion["resolution"]["modifier"]["value"] = Resolution_map[val] """ def parse(criterions): @@ -8,9 +14,15 @@ def parse(criterions): if criterion in ('sceneIsMissing', 'imageIsMissing', 'performerIsMissing', 'galleryIsMissing', 'tagIsMissing', 'studioIsMissing', 'studioIsMissing'): filter['is_missing'] = criterion['value'] else: - is_timestamp_field = criterion in ('created_at', 'updated_at', 'scene_created_at', 'scene_updated_at') - value_transformer = (lambda v: v.replace(' ', 'T') if isinstance(v, str) else v) if is_timestamp_field else lambda v: v - filter[criterion] = parse_criterion(criterions[criterion], value_transformer) + if "resolution" == criterion: + val = criterions[criterion]["value"] + criterions[criterion]["value"] = Resolution_map[val] + is_timestamp_field = criterion in ( + 'created_at', 'updated_at', 'scene_created_at', 'scene_updated_at') + value_transformer = (lambda v: v.replace(' ', 'T') if isinstance( + v, str) else v) if is_timestamp_field else lambda v: v + filter[criterion] = parse_criterion( + criterions[criterion], value_transformer) return filter @@ -21,12 +33,14 @@ def parse_criterion(criterion, value_transformer): filter['modifier'] = criterion['modifier'] value = criterion.get('value', '') + if isinstance(value, dict) and not value.keys() - ['items', 'excluded', 'depth']: if value.get('items') is not None: filter['value'] = list(map(lambda v: v['id'], value['items'])) if value.get('excluded') is not None: - filter['excludes'] = list(map(lambda v: v['id'], value['excluded'])) + filter['excludes'] = list( + map(lambda v: v['id'], value['excluded'])) if value.get('depth') is not None: filter['depth'] = value['depth'] diff --git a/resources/lib/listing/listing.py b/resources/lib/listing/listing.py index d147cfe..1df52a6 100644 --- a/resources/lib/listing/listing.py +++ b/resources/lib/listing/listing.py @@ -89,11 +89,13 @@ def _create_item(self, scene: dict, **kwargs): 'plot': scene['details'], 'cast': list(map(lambda p: p['name'], scene['performers'])), 'duration': duration, + 'playcount': scene.get("play_count", 0), 'studio': scene['studio']['name'] if scene['studio'] is not None else None, 'userrating': rating, 'premiered': scene['date'], 'tag': list(map(lambda t: t['name'], scene['tags'])), - 'dateadded': scene['created_at'] + 'dateadded': scene['created_at'], + 'lastplayed': scene["last_played_at"] }) item.addStreamInfo('video', {'codec': scene['files'][0]['video_codec'], @@ -105,7 +107,12 @@ def _create_item(self, scene: dict, **kwargs): 'audio', {'codec': scene['files'][0]['audio_codec']}) screenshot = self._client.add_api_key(screenshot) - item.setArt({'thumb': screenshot, 'fanart': screenshot}) + art_dict = {'thumb': screenshot, 'fanart': screenshot} + if scene['studio']: + art_dict["banner"] = scene["studio"]["image_path"] + item.setArt(art_dict) + item.setProperty( + 'ResumeTime', str(0.0 if not scene["resume_time"] else float(scene["resume_time"]))) item.setProperty('IsPlayable', 'true') return item diff --git a/resources/lib/stash_interface.py b/resources/lib/stash_interface.py index a153076..b1ef41d 100644 --- a/resources/lib/stash_interface.py +++ b/resources/lib/stash_interface.py @@ -56,6 +56,9 @@ def find_scenes(self, scene_filter=None, sort_field='title', sort_dir='asc'): rating100 date created_at + play_count + resume_time + last_played_at paths { screenshot } @@ -68,6 +71,7 @@ def find_scenes(self, scene_filter=None, sort_field='title', sort_dir='asc'): } studio { name + image_path } performers { name diff --git a/resources/lib/utils/resolutions.py b/resources/lib/utils/resolutions.py new file mode 100644 index 0000000..44da687 --- /dev/null +++ b/resources/lib/utils/resolutions.py @@ -0,0 +1,18 @@ + +# TODO: Replace strings with Enum +Resolution_map = { + "144p": "VERY_LOW", + "240p": "LOW", + "360p": "R360P", + "480p": "STANDARD", + "540p": "WEB_HD", + "720p": "STANDARD_HD", + "1080p": "FULL_HD", + "1440p": "QUAD_HD", + "4k": "FOUR_K", + "5k": "FIVE_K", + "6k": "SIX_K", + "7k": "SEVEN_K", + "8k": "EIGHT_K", + "Huge": "HUGE", +} From c15b97847915ebf6b7a03f8ca6c976cebfb71575 Mon Sep 17 00:00:00 2001 From: miawgogo Date: Mon, 15 Jan 2024 17:51:29 +0000 Subject: [PATCH 5/6] changes in response to review --- build.sh | 10 ------ resources/lib/criterion_parser.py | 1 - resources/lib/plugin.py | 1 - resources/lib/utils/logger.py | 52 ------------------------------- 4 files changed, 64 deletions(-) delete mode 100755 build.sh delete mode 100644 resources/lib/utils/logger.py diff --git a/build.sh b/build.sh deleted file mode 100755 index 8ac5c17..0000000 --- a/build.sh +++ /dev/null @@ -1,10 +0,0 @@ -#! /bin/bash -( - mkdir -p /tmp/plugin.video.stash - rsync -a --exclude 'resources/stash_logo.svg' addon.xml addon.py resources /tmp/plugin.video.stash - cd /tmp/ - rm -r /tmp/plugin.video.stash/venv - zip -r plugin.video.stash.zip plugin.video.stash/ - rm -r /tmp/plugin.video.stash -) -mv /tmp/plugin.video.stash.zip . diff --git a/resources/lib/criterion_parser.py b/resources/lib/criterion_parser.py index a0dc38f..8080ff7 100644 --- a/resources/lib/criterion_parser.py +++ b/resources/lib/criterion_parser.py @@ -1,6 +1,5 @@ import json from resources.lib.utils.resolutions import Resolution_map -from resources.lib.utils import logger """ elif "resolution" == criterion: val = criterion["resolution"]["modifier"]["value"] diff --git a/resources/lib/plugin.py b/resources/lib/plugin.py index 1bfa04f..c08fa49 100644 --- a/resources/lib/plugin.py +++ b/resources/lib/plugin.py @@ -6,7 +6,6 @@ import xbmcgui import xbmcplugin from resources.lib import utils -from resources.lib.utils import logger from resources.lib.listing import create_listing, Listing, SceneListing from resources.lib.navigation import NavigationItem from resources.lib.stash_interface import StashInterface diff --git a/resources/lib/utils/logger.py b/resources/lib/utils/logger.py deleted file mode 100644 index 1f2388c..0000000 --- a/resources/lib/utils/logger.py +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 -*- -""" - - Copyright (C) 2014-2016 bromix (plugin.video.youtube) - Copyright (C) 2016-2018 plugin.video.youtube - - SPDX-License-Identifier: GPL-2.0-only - See LICENSES/GPL-2.0-only for more information. - - I took this code from the youtube plugin to avoid rewriting it -""" - -import xbmc -import xbmcaddon - -DEBUG = xbmc.LOGDEBUG -INFO = xbmc.LOGINFO -NOTICE = INFO -WARNING = xbmc.LOGWARNING -ERROR = xbmc.LOGERROR -FATAL = xbmc.LOGFATAL -SEVERE = FATAL -NONE = xbmc.LOGNONE - -_ADDON_ID = 'plugin.video.stash' - - -def log(text, log_level=DEBUG, addon_id=_ADDON_ID): - if not addon_id: - addon_id = xbmcaddon.Addon().getAddonInfo('id') - log_line = '[%s] %s' % (addon_id, text) - xbmc.log(msg=log_line, level=log_level) - - -def log_debug(text, addon_id=_ADDON_ID): - log(text, DEBUG, addon_id) - - -def log_info(text, addon_id=_ADDON_ID): - log(text, INFO, addon_id) - - -def log_notice(text, addon_id=_ADDON_ID): - log(text, NOTICE, addon_id) - - -def log_warning(text, addon_id=_ADDON_ID): - log(text, WARNING, addon_id) - - -def log_error(text, addon_id=_ADDON_ID): - log(text, ERROR, addon_id) From 83ff01cf89c8f2a347bae118103b9ae1cae7e5e9 Mon Sep 17 00:00:00 2001 From: miawgogo Date: Mon, 15 Jan 2024 17:53:45 +0000 Subject: [PATCH 6/6] Correction to settings string --- resources/settings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/settings.xml b/resources/settings.xml index 7d42584..acb9a93 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -5,7 +5,7 @@ - +