From e12c77fc1431729fc754e45aa3deda3cffb110ba Mon Sep 17 00:00:00 2001 From: Jason Hood Date: Thu, 3 Oct 2024 10:52:16 +1000 Subject: [PATCH 1/3] [imdb] Update Fix a writer not having 'attributes'. Update the hash used to retrieve the storyline. Recognise a review might not contain a rating. --- imdb/src/plugin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/imdb/src/plugin.py b/imdb/src/plugin.py index fc3436b54..319c1f57f 100644 --- a/imdb/src/plugin.py +++ b/imdb/src/plugin.py @@ -398,7 +398,7 @@ def downloadTitle(self, title, titleId): params = { "operationName": 'TMD_Storyline', "variables": '{"titleId":"%s"}' % titleId, - "extensions": '{"persistedQuery":{"sha256Hash":"87f41463a48af95ebba3129889d17181402622bfd30c8dc9216d99ac984f0091","version":1}}' + "extensions": '{"persistedQuery":{"sha256Hash":"78f137c28457417c10cf92a79976e54a65f8707bfc4fd1ad035da881ee5eaac6","version":1}}' } self.haveTMD = self.haveHTML = False tmd = getPage("https://caching.graphql.imdb.com/", params=params, headers={"content-type": "application/json"}, cookies=self.cookie) @@ -413,7 +413,7 @@ def gotReviews(self, response): self.reviewsHTML = self.reviewsHTML.decode("utf8") reviewsmask = re.compile( - '(?P\d+).*?' + '(?:(?P\d+).*?)?' 'class="title" > (?P.*?)\n.*?' '\n>(?P<author>.*?)</a></span><span class="review-date">(?P<date>.*?)</span>.*?' '(?:spoiler-warning">(?P<spoiler>.*?)</.*?)?' @@ -485,7 +485,7 @@ def showExtras(self, synopsis=False, reviews=False): pos = 0 reviews = [] for review in self.reviews: - reviews.append(review['rating'] + "/10 | " + review['date']) + reviews.append((review['rating'] and review['rating'] + "/10 | " or "") + review['date']) reviews.append(review['title'] + " [" + review['author'] + "]") reviews.append("") if review['spoiler']: @@ -925,7 +925,7 @@ def makedate(date): 'creator': ", ".join(get(name, ('name', 'nameText', 'text')) for name in get(main, ('creators', 'credits'))), 'episodes': get(main, ('episodes', 'totalEpisodes', 'total')), 'seasons': len(get(main, ('episodes', 'seasons'))), - 'writer': ", ".join(get(name, ('name', 'nameText', 'text')) + (name['attributes'] and " (" + name['attributes'][0]['text'] + ")" or "") for name in get(main, ('writers', 'credits'))), + 'writer': ", ".join(get(name, ('name', 'nameText', 'text')) + (name.get('attributes') and " (" + name['attributes'][0]['text'] + ")" or "") for name in get(main, ('writers', 'credits'))), 'country': ', '.join(get(country, 'text') for country in get(main, ('countriesOfOrigin', 'countries'))), 'premiere': main['releaseDate'] and "%s (%s)" % (makedate(main['releaseDate']), get(main, ('releaseDate', 'country', 'text'))), # there's also main['releaseYear']['year'] From a1cfce577c7c8b12a09ec1f30de1d88fa899eede Mon Sep 17 00:00:00 2001 From: Jason Hood <jadoxa@yahoo.com.au> Date: Fri, 4 Oct 2024 22:48:28 +1000 Subject: [PATCH 2/3] [imdb] Another approach for TMD Even with the updated hash getting the TMD data still sometimes failed. It even failed on the website, which showed me an alternative way to get the data. --- imdb/src/plugin.py | 109 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 2 deletions(-) diff --git a/imdb/src/plugin.py b/imdb/src/plugin.py index 319c1f57f..164e0c394 100644 --- a/imdb/src/plugin.py +++ b/imdb/src/plugin.py @@ -55,10 +55,10 @@ config.plugins.imdb.showepisodeinfo = ConfigYesNo(default=False) -def getPage(url, params=None, headers=None, cookies=None): +def getPage(url, params=None, data=None, headers=None, cookies=None): headers = headers or {} headers["user-agent"] = "Mozilla/5.0 Gecko/20100101 Firefox/100.0" - return deferToThread(requests.get, url, params=params, headers=headers, cookies=cookies, timeout=30.05) + return deferToThread(requests.post if data else requests.get, url, params=params, data=data, headers=headers, cookies=cookies, timeout=30.05) def savePage(response, filename): @@ -386,6 +386,110 @@ def gotTMD(self, response): self.json = response.content if six.PY3: self.json = self.json.decode("utf8") + if self.json.startswith('{"errors'): + if not self.tmdTitleId: + print("[IMDb] error getting TMD", self.json) + else: + print("[IMDb] getting TMD via POST") + query = ( + '{"query":"' + 'query TMD_Storyline($titleId: ID!) {\\n' + ' title(id: $titleId) {\\n' + ' id\\n' + ' ...TMD_Storyline_PlotSection\\n' + ' ...TMD_Storyline_Taglines\\n' + ' ...TMD_Storyline_Genres\\n' + ' ...TMD_Storyline_Certificate\\n' + ' ...TMD_Storyline_ParentsGuide\\n' + ' }\\n' + '}\\n' + '\\n' + 'fragment TMD_Storyline_PlotSection on Title {\\n' + ' summaries: plots(first: 1, filter: {type: SUMMARY}) {\\n' + ' edges {\\n' + ' node {\\n' + ' ...PlotData\\n' + ' author\\n' + ' }\\n' + ' }\\n' + ' }\\n' + ' outlines: plots(first: 1, filter: {type: OUTLINE}) {\\n' + ' edges {\\n' + ' node {\\n' + ' ...PlotData\\n' + ' }\\n' + ' }\\n' + ' }\\n' + ' synopses: plots(first: 1, filter: {type: SYNOPSIS}) {\\n' + ' edges {\\n' + ' node {\\n' + ' ...PlotData\\n' + ' }\\n' + ' }\\n' + ' }\\n' + ' storylineKeywords: keywords(first: 5) {\\n' + ' edges {\\n' + ' node {\\n' + ' legacyId\\n' + ' text\\n' + ' }\\n' + ' }\\n' + ' total\\n' + ' }\\n' + '}\\n' + '\\n' + 'fragment PlotData on Plot {\\n' + ' plotText {\\n' + ' plaidHtml\\n' + ' }\\n' + '}\\n' + '\\n' + 'fragment TMD_Storyline_Taglines on Title {\\n' + ' taglines(first: 1) {\\n' + ' edges {\\n' + ' node {\\n' + ' text\\n' + ' }\\n' + ' }\\n' + ' total\\n' + ' }\\n' + '}\\n' + '\\n' + 'fragment TMD_Storyline_Genres on Title {\\n' + ' genres {\\n' + ' genres {\\n' + ' id\\n' + ' text\\n' + ' }\\n' + ' }\\n' + '}\\n' + '\\n' + 'fragment TMD_Storyline_Certificate onTitle {\\n' + ' certificate {\\n' + ' rating\\n' + ' ratingReason\\n' + ' ratingsBody {\\n' + ' id\\n' + ' }\\n' + ' }\\n' + '}\\n' + '\\n' + 'fragment TMD_Storyline_ParentsGuide on Title {\\n' + ' parentsGuide {\\n' + ' guideItems(first: 0) {\\n' + ' total\\n' + ' }\\n' + ' }\\n' + '}",' + '"operationName":"TMD_Storyline",' + '"variables":{"titleId":"%s"},' + '"extensions":{"persistedQuery":{"version":1,' + '"sha256Hash":"78f137c28457417c10cf92a79976e54a65f8707bfc4fd1ad035da881ee5eaac6"}}}' + ) % self.tmdTitleId + self.tmdTitleId = None + tmd = getPage("https://caching.graphql.imdb.com/", data=query, headers={"content-type": "application/json"}, cookies=self.cookie) + tmd.addBoth(self.gotTMD) + return if self.haveHTML: self.IMDBparse() else: @@ -401,6 +505,7 @@ def downloadTitle(self, title, titleId): "extensions": '{"persistedQuery":{"sha256Hash":"78f137c28457417c10cf92a79976e54a65f8707bfc4fd1ad035da881ee5eaac6","version":1}}' } self.haveTMD = self.haveHTML = False + self.tmdTitleId = titleId tmd = getPage("https://caching.graphql.imdb.com/", params=params, headers={"content-type": "application/json"}, cookies=self.cookie) tmd.addBoth(self.gotTMD) download = getPage(fetchurl, cookies=self.cookie) From 85baf8e44ccd37666843c825c7fa0584698a3f5d Mon Sep 17 00:00:00 2001 From: Jason Hood <jadoxa@yahoo.com.au> Date: Fri, 4 Oct 2024 22:54:18 +1000 Subject: [PATCH 3/3] [imdb] Show videos in menu Add the videos provided by IMDb to the context menu. If subtitles are available they will be shown as additional indented language entries. E.g. Trailer (1:23) English Selecting the first will play without subtitles, selecting the second will play with English subtitles. --- imdb/src/plugin.py | 53 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/imdb/src/plugin.py b/imdb/src/plugin.py index 164e0c394..32025b48b 100644 --- a/imdb/src/plugin.py +++ b/imdb/src/plugin.py @@ -4,11 +4,12 @@ from . import _ from Plugins.Plugin import PluginDescriptor -from enigma import ePicLoad, eServiceCenter +from enigma import ePicLoad, eServiceCenter, eServiceReference from Screens.Screen import Screen from Screens.Setup import Setup from Screens.HelpMenu import HelpableScreen from Screens.ChoiceBox import ChoiceBox +from Screens.InfoBar import MoviePlayer from Screens.VirtualKeyBoard import VirtualKeyBoard from Components.ActionMap import HelpableActionMap from Components.Pixmap import Pixmap @@ -649,6 +650,9 @@ def contextMenuPressed(self): (_("Search Trailer"), self.searchYttrailer), )) + for video in self.videos: + list.append((video[0], self.playVideo, video[1], video[2])) + self.session.openWithCallback( self.menuCallback, ChoiceBox, @@ -657,7 +661,13 @@ def contextMenuPressed(self): ) def menuCallback(self, ret=None): - ret and ret[1]() + if ret: + ret[1]() if len(ret) == 2 else ret[1](ret[2], ret[3]) + + def playVideo(self, name, url): + ref = eServiceReference(4097, 0, url) + ref.setName(name) + self.session.open(IMDbPlayer, ref) def saveHtmlDetails(self): try: @@ -778,6 +788,7 @@ def getIMDB(self, search=False): self.json = self.generalinfos = None self.castTxt = self.extraTxt = self.synopsisTxt = self.reviewsTxt = "" self.extra = self.synopsis = "" + self.videos = [] self.reviews = [] self.spoilers = False safeRemove("/tmp/poster.jpg", "/tmp/poster-big.jpg") @@ -1289,6 +1300,29 @@ def connections(node): self.synopsisTxt = html2text(get(tmd, ('synopses', 'edges', 'node', 'plotText', 'plaidHtml'))) self.synopsis = text2label(self.synopsisTxt) + for video in get(fold, ('primaryVideos', 'edges')): + video = video['node'] + typ = get(video, ('contentType', 'displayName', 'value')) + desc = get(video, ('description', 'value')) + name = get(video, ('name', 'value')) + # If the name is the same as the title, use the description if + # it appears to be a name, otherwise just use the content type. + if name == self.eventName: + name = desc if desc and len(desc) < 70 and desc != name else typ + runtime = video['runtime']['value'] + # Assume the first video is the best. + url = get(video, ('playbackURLs', 'url')) + if self.eventName.lower() in name.lower(): + title = name + else: + title = "%s - %s" % (self.eventName, name) + self.videos.append(("%s (%d:%02d)" % (name, runtime // 60, runtime % 60), title, url)) + for subt in get(video, 'timedTextTracks'): + self.videos.append((" " + (get(subt, ('displayName', 'value')) + or get(subt, ('displayName', 'language')) + or get(subt, 'language')), + title, url + "&suburi=" + get(subt, 'url'))) + self.callbackData = Detailstext Detailstext = text2label(Detailstext) self["detailslabel"].setText(Detailstext) @@ -1353,6 +1387,21 @@ def createSummary(self): return IMDbLCDScreen +class IMDbPlayer(MoviePlayer): + def __init__(self, session, service): + MoviePlayer.__init__(self, session, service) + self.skinName = "MoviePlayer" + + def leavePlayer(self): + self.close() + + def doEofInternal(self, playing): + self.close() + + def showMovies(self): + pass + + class IMDbLCDScreen(Screen): skin = """ <screen position="0,0" size="132,64" title="IMDB Plugin">