Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolution filter fix #20

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
__pycache__
venv/
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think neither of these are required? The plugin doesn't require a separate venv (it's running in Kodi anyway), and there is no need to build a ZIP (see below as well).

Copy link
Contributor Author

@miawgogo miawgogo Jan 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is so i could install the kodi stubs to get some code completion from VS Code relating to the Kodi functions.

I prefer to use a venv to do this rather than installing it locally/globally to avoid polluting my users python libaries with development libaries

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to keep them locally, and ignored. You can add it to .git/info/exclude. Git also reads that file as a .gitignore but it will be local only / not part of the repository.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from VS Code

That explains all the (re)formatting of further unchanged code. PyCharm doesn't do that (automatically), which I prefer.

plugin.video.stash.zip
13 changes: 13 additions & 0 deletions resources/language/resource.language.en_gb/strings.po
Original file line number Diff line number Diff line change
Expand Up @@ -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 ""
21 changes: 17 additions & 4 deletions resources/lib/criterion_parser.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import json
from resources.lib.utils.resolutions import Resolution_map

""" elif "resolution" == criterion:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented code, so can be removed

val = criterion["resolution"]["modifier"]["value"]
criterion["resolution"]["modifier"]["value"] = Resolution_map[val] """


def parse(criterions):
Expand All @@ -8,9 +13,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

Expand All @@ -21,12 +32,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']
Expand Down
34 changes: 23 additions & 11 deletions resources/lib/listing/listing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -77,30 +80,39 @@ 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',
'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['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})
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
Expand Down
23 changes: 15 additions & 8 deletions resources/lib/listing/scene_marker_listing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand All @@ -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))

Expand Down
49 changes: 47 additions & 2 deletions resources/lib/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,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:])


Expand Down Expand Up @@ -80,16 +101,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):
Expand Down
36 changes: 24 additions & 12 deletions resources/lib/stash_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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:
Expand All @@ -51,13 +53,16 @@ def find_scenes(self, scene_filter=None, sort_field='title', sort_dir='asc'):
id
title
details
rating
rating100
date
created_at
play_count
resume_time
last_played_at
paths {
screenshot
}
file {
files {
duration
video_codec
audio_codec
Expand All @@ -66,6 +71,7 @@ def find_scenes(self, scene_filter=None, sort_field='title', sort_dir='asc'):
}
studio {
name
image_path
}
performers {
name
Expand Down Expand Up @@ -101,14 +107,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
Expand All @@ -133,13 +145,13 @@ def find_scene(self, id):
id
title
details
rating
rating100
date
created_at
paths {
screenshot
}
file {
files {
duration
video_codec
audio_codec
Expand Down Expand Up @@ -197,7 +209,7 @@ def find_performers(self, **kwargs):
'modifier': 'GREATER_THAN',
'value': 0,
}
}
}
}

result = self.__call_graphql(query, variables)
Expand Down Expand Up @@ -262,7 +274,7 @@ def find_studios(self):
'modifier': 'GREATER_THAN',
'value': 0,
}
}
}
}

result = self.__call_graphql(query, variables)
Expand All @@ -283,13 +295,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
Expand Down
Loading