From 52cc6cbfc212e6747cafee71eae2db55c669ddce Mon Sep 17 00:00:00 2001 From: Darklyter Date: Mon, 27 Sep 2021 19:59:41 -0400 Subject: [PATCH] Initial commit of Plex metadata agent to pull Stash data (#15) Full bundle is included, put into "3rd party' directory to segment from Stash based scripts --- .../Contents/Code/__init__.py | 192 ++++++++++++++++++ .../Contents/DefaultPrefs.json | 50 +++++ .../StashPlexAgent.bundle/Contents/Info.plist | 28 +++ 3rd party/StashPlexAgent.bundle/README.md | 18 ++ 4 files changed, 288 insertions(+) create mode 100644 3rd party/StashPlexAgent.bundle/Contents/Code/__init__.py create mode 100644 3rd party/StashPlexAgent.bundle/Contents/DefaultPrefs.json create mode 100644 3rd party/StashPlexAgent.bundle/Contents/Info.plist create mode 100644 3rd party/StashPlexAgent.bundle/README.md diff --git a/3rd party/StashPlexAgent.bundle/Contents/Code/__init__.py b/3rd party/StashPlexAgent.bundle/Contents/Code/__init__.py new file mode 100644 index 0000000..ca0125e --- /dev/null +++ b/3rd party/StashPlexAgent.bundle/Contents/Code/__init__.py @@ -0,0 +1,192 @@ +import os +import re +import time +import string +import thread +import threading +import copy +import json +import dateutil.parser as dateparser +from urllib2 import HTTPError +from urllib2 import quote +from datetime import datetime +from lxml import etree + +# preferences +preference = Prefs +DEBUG = preference['debug'] +if DEBUG: + Log('Agent debug logging is enabled!') +else: + Log('Agent debug logging is disabled!') + +def ValidatePrefs(): + pass + + +def Start(): + Log("Stash metadata agent started") + HTTP.Headers['Accept'] = 'application/json' + HTTP.CacheTime = 0.1 + ValidatePrefs() + + +def HttpReq(url, authenticate=True, retry=True): + Log("Requesting: %s" % url) + api_string = '' + if Prefs['APIKey']: + api_string = '&apikey=%s' % Prefs['APIKey'] + + if Prefs['UseHTTPS']: + connectstring = 'https://%s:%s/graphql?query=%s%s' + else: + connectstring = 'http://%s:%s/graphql?query=%s%s' + try: + connecttoken = connectstring % (Prefs['Hostname'], Prefs['Port'], url, api_string) + Log(connecttoken) + return JSON.ObjectFromString( + HTTP.Request(connecttoken).content) + except Exception, e: + if not retry: + raise e + return HttpReq(url, authenticate, False) + +class StashPlexAgent(Agent.Movies): + name = 'Stash Plex Agent' + languages = [Locale.Language.English] + primary_provider = True + accepts_from = [ + 'com.plexapp.agents.xbmcnfo' + ] + + def search(self, results, media, lang): + DEBUG = Prefs['debug'] + file_query = r"""query{findScenes(scene_filter:{path:{value:"\"\"",modifier:INCLUDES}}){scenes{id,title,date,studio{id,name}}}}""" + mediaFile = media.items[0].parts[0].file + filename = String.Unquote(mediaFile).encode('utf8', 'ignore') + filename = os.path.splitext(os.path.basename(filename))[0] + if filename: + filename = str(quote(filename.encode('UTF-8'))) + query = file_query.replace("", filename) + request = HttpReq(query) + if DEBUG: + Log(request) + movie_data = request['data']['findScenes']['scenes'] + score = 100 if len(movie_data) == 1 else 85 + for scene in movie_data: + if scene['date']: + title = scene['title'] + ' - ' + scene['date'] + else: + title = scene['title'] + Log("Title Found: " + title + " Score: " + str(score) + " ID:" + scene['id']) + results.Append(MetadataSearchResult(id = str(scene['id']), name = title, score = int(score), lang = lang)) + + + def update(self, metadata, media, lang, force=False): + DEBUG = Prefs['debug'] + Log("update(%s)" % metadata.id) + mid = metadata.id + id_query = "query{findScene(id:%s){path,id,title,details,url,date,rating,paths{screenshot,stream}studio{id,name,image_path,parent_studio{id,name,details}}tags{id,name}performers{name,image_path}movies{movie{name}}}}" + data = HttpReq(id_query % mid) + data = data['data']['findScene'] + metadata.collections.clear() + + if data['date']: + try: + Log("Trying to parse:" + data["date"]) + date=dateparser().parse(data["date"]) + except Exception as ex: + Log(ex) + date=None + pass + # Set the date and year if found. + if date is not None: + metadata.originally_available_at = date + metadata.year = date.year + + # Get the title + if data['title']: + metadata.title = data["title"] + + # Get the Studio + if not data["studio"] is None: + metadata.studio = data["studio"]["name"] + + # Get the rating + if not data["rating"] is None: + metadata.rating = float(data["rating"]) * 2 + if Prefs["CreateRatingTags"]: + if int(data["rating"]) > 0: + rating = str(int(data["rating"])) + ratingstring = "Rating: " + rating + " Stars" + try: + metadata.collections.add(ratingstring) + except: + pass + + # Set the summary + if data['details']: + summary=data["details"].replace("\n"," ").replace("\r", "").replace("\t","") + metadata.summary = summary + + # Set series and add to collections + if Prefs["CreateCollectionTags"]: + if not data["studio"] is None: + site="Site: " + data["studio"]["name"] + try: + metadata.collections.add(site) + except: + pass + if not data["studio"]["parent_studio"] is None: + site="Studio: " + data["studio"]["parent_studio"]["name"] + else: + site="Studio: " + data["studio"]["name"] + try: + metadata.collections.add(site) + except: + pass + + # Add the genres + metadata.genres.clear() + if Prefs["IgnoreTags"]: + ignore_tags = Prefs["IgnoreTags"].split(",") + ignore_tags = list(map(lambda x: x.strip(), ignore_tags)) + try: + if data["tags"]: + genres = data["tags"] + for genre in genres: + if not genre["id"] in ignore_tags and "ambiguous" not in genre["name"].lower(): + metadata.genres.add(genre["name"]) + except: + pass + + # Add the performers + metadata.roles.clear() + # Create and populate role with actor's name + try: + if data["performers"]: + api_string = "" + if Prefs['APIKey']: + api_string = '&apikey=%s' % Prefs['APIKey'] + models=data["performers"] + for model in models: + if DEBUG: + Log("Pulling Model: " + model["name"] + " With Image: " + model["image_path"]) + role = metadata.roles.new() + role.name=model["name"] + role.photo=model["image_path"] + api_string + except: + pass + + # Add posters and fan art. + if not data["paths"]["screenshot"] is None: + api_string = "" + if Prefs['APIKey']: + api_string = '&apikey=%s' % Prefs['APIKey'] + try: + thumb = HTTP.Request(data["paths"]["screenshot"] + api_string) + metadata.posters[data["paths"]["screenshot"] + api_string] = Proxy.Preview(thumb) + metadata.art[data["paths"]["screenshot"] + api_string] = Proxy.Preview(thumb) + except: + pass + diff --git a/3rd party/StashPlexAgent.bundle/Contents/DefaultPrefs.json b/3rd party/StashPlexAgent.bundle/Contents/DefaultPrefs.json new file mode 100644 index 0000000..8443137 --- /dev/null +++ b/3rd party/StashPlexAgent.bundle/Contents/DefaultPrefs.json @@ -0,0 +1,50 @@ +[ + { + "id": "Hostname", + "label": "The host for Stash", + "type": "text", + "default": "127.0.0.1" + }, + { + "id": "Port", + "label": "The port for Stash", + "type": "text", + "default": "9999" + }, + { + "id": "UseHTTPS", + "label": "Use HTTPS instead of HTTP to connect", + "type": "bool", + "default": false + }, + { + "id": "APIKey", + "label": "The API Key for Stash if Authentication is enabled", + "type": "text", + "default": "" + }, + { + "id": "IgnoreTags", + "label": "Stash Tag ID numbers to ignore (comma separated, 0 to disable)", + "type": "text", + "default": "1,2" + }, + { + "id": "CreateCollectionTags", + "label": "Auto create Plex Collection tags for Site and Studio", + "type": "bool", + "default": true + }, + { + "id": "CreateRatingTags", + "label": "Auto create Plex Collection tags for Stash star rating", + "type": "bool", + "default": false + }, + { + "id": "debug", + "label": "Use debug logging", + "type": "bool", + "default": false + }, +] diff --git a/3rd party/StashPlexAgent.bundle/Contents/Info.plist b/3rd party/StashPlexAgent.bundle/Contents/Info.plist new file mode 100644 index 0000000..7f1514f --- /dev/null +++ b/3rd party/StashPlexAgent.bundle/Contents/Info.plist @@ -0,0 +1,28 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + StashPlexAgent + CFBundleIdentifier + com.plexapp.agents.stashplexagent + CFBundleInfoDictionaryVersion + 6.0 + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + PlexFrameworkVersion + 2 + PlexPluginClass + Agent + PlexPluginMode + AlwaysOn + PlexPluginCodePolicy + Elevated + + diff --git a/3rd party/StashPlexAgent.bundle/README.md b/3rd party/StashPlexAgent.bundle/README.md new file mode 100644 index 0000000..61e490d --- /dev/null +++ b/3rd party/StashPlexAgent.bundle/README.md @@ -0,0 +1,18 @@ +# StashPlexAgent.bundle +A very simplistic Plex agent to pull metadata from Stash. + +Scenes are matched based on filename (without path or extension) against the Stash "Path", so files must be scanned into Stash with their current filename. + +Preferences are set under the plugin, or in the library definition (if you set it as the primary agent for the library). I'm using "Video Files Scanner" with it. + +By default it will create Plex "Site: " and "Studio: " collection tags, but this can be disabled in preferences. (If the studio doesn't have a parent then the primary studio will be used instead and be in both tags) + +Also Stash "Tags" are placed into Plex "Genres" + +You can also set tag numbers to ignore on import, I've left mine in as an example. You probably want to change these unless your "temporary" tags miraculously line up with mine. (Also initially you might need to try saving a couple of times. Plex seems to not want to initally keep changes in this field for some reason) + +For installing just download the bundle and put it into your "\PlexMediaServer\Plex Media Server\Plug-ins" folder. (The entire bundle as a directory... "\StashPlexAgent.bundle") + +I guarantee there will be problems. When they pop up feel free to get with me (@Darklyter) on either the TPDB or Stash Discord channels. + +Also this agent only handles scenes currently. I haven't played with movies in Stash yet, but can take a look if there is interest. Currently the Plex ADE agent handles that for me. \ No newline at end of file