Skip to content

Commit

Permalink
IMDb update (#686)
Browse files Browse the repository at this point in the history
* [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] 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] 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.
  • Loading branch information
adoxa authored Oct 4, 2024
1 parent aee8da1 commit e7c7fd1
Showing 1 changed file with 162 additions and 8 deletions.
170 changes: 162 additions & 8 deletions imdb/src/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -55,10 +56,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):
Expand Down Expand Up @@ -386,6 +387,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:
Expand All @@ -398,9 +503,10 @@ 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
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)
Expand All @@ -413,7 +519,7 @@ def gotReviews(self, response):
self.reviewsHTML = self.reviewsHTML.decode("utf8")

reviewsmask = re.compile(
'<span>(?P<rating>\d+)</span>.*?'
'(?:<span>(?P<rating>\d+)</span>.*?)?'
'class="title" > (?P<title>.*?)\n.*?'
'\n>(?P<author>.*?)</a></span><span class="review-date">(?P<date>.*?)</span>.*?'
'(?:spoiler-warning">(?P<spoiler>.*?)</.*?)?'
Expand Down Expand Up @@ -485,7 +591,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']:
Expand Down Expand Up @@ -544,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,
Expand All @@ -552,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:
Expand Down Expand Up @@ -673,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")
Expand Down Expand Up @@ -925,7 +1041,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']
Expand Down Expand Up @@ -1184,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)
Expand Down Expand Up @@ -1248,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">
Expand Down

0 comments on commit e7c7fd1

Please sign in to comment.