Skip to content

Commit 5f13509

Browse files
authored
Merge pull request #4700 from Gujal00/matrix
[plugin.video.imdb.trailers] 2.1.22
2 parents 0378880 + 2e9f4f1 commit 5f13509

File tree

3 files changed

+221
-68
lines changed

3 files changed

+221
-68
lines changed

plugin.video.imdb.trailers/addon.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2-
<addon id="plugin.video.imdb.trailers" name="IMDb Trailers" version="2.1.21" provider-name="gujal">
2+
<addon id="plugin.video.imdb.trailers" name="IMDb Trailers" version="2.1.22" provider-name="gujal">
33
<requires>
44
<import addon="xbmc.python" version="3.0.0"/>
55
<import addon="script.module.beautifulsoup4" version="4.0.0"/>

plugin.video.imdb.trailers/changelog.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
plugin.video.imdb.trailers:
22
---------------------------
3+
v2.1.22 (20250809)
4+
[fix] In Cinemas listing
5+
[new] Play specific season trailer for tvshows
6+
plugin://plugin.video.imdb.trailers/?action=play_id&imdb={imdb_id}&season={season_number}
7+
38
v2.1.21 (20250513)
49
[fix] listing when character names not available
510
[fix] add Trailer to tile to avoid scrobbling by trakt module

plugin.video.imdb.trailers/resources/lib/imdb_trailers.py

Lines changed: 215 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import re
2121
import sys
2222
import datetime
23-
import json
2423
import threading
2524
from kodi_six import xbmc, xbmcgui, xbmcplugin, xbmcaddon, xbmcvfs
2625
from bs4 import BeautifulSoup, SoupStrainer
@@ -57,7 +56,7 @@
5756
if not xbmcvfs.exists(_addonpath):
5857
xbmcvfs.mkdir(_addonpath)
5958

60-
SHOWING_URL = 'https://www.imdb.com/showtimes/_ajax/location/'
59+
SHOWING_URL = 'https://www.imdb.com/showtimes/'
6160
COMING_URL = 'https://www.imdb.com/calendar/?type=MOVIE'
6261
DETAILS_PAGE = "https://www.imdb.com/video/{0}/"
6362
quality = int(_settings("video_quality")[:-1])
@@ -451,10 +450,10 @@ def process_imdbid(self, imdbID):
451450
def get_contents1(self, key):
452451
if key == 'showing':
453452
page_data = client.request(SHOWING_URL, headers=self.headers)
454-
tlink = SoupStrainer('div', {'class': 'lister-list'})
453+
tlink = SoupStrainer('ul', {'class': re.compile('^ipc-metadata-list')})
455454
mdiv = BeautifulSoup(page_data, "html.parser", parse_only=tlink)
456-
videos = mdiv.find_all('div', {'class': 'lister-item'})
457-
imdbIDs = [x.find('div', {'class': 'lister-item-image'}).get('data-tconst') for x in videos]
455+
videos = mdiv.find_all('li', {'class': 'ipc-metadata-list-summary-item'})
456+
imdbIDs = [x.find('a').get('href').split('/')[-2] for x in videos]
458457
else:
459458
page_data = client.request(COMING_URL, headers=self.headers)
460459
imdbIDs = re.findall(r'<a class="ipc-metadata-list-summary-item__t".+?href="/title/([^/]+)', page_data, re.DOTALL)
@@ -485,22 +484,25 @@ def list_contents1(self):
485484
self.log('list_contents1({0})'.format(key))
486485

487486
items = cache.get(self.get_contents1, cache_duration, key)
488-
for litem in items:
489-
listitem = self.make_listitem(litem.get('labels'), litem.get('cast2'))
490-
listitem.setArt(litem.get('art'))
491-
listitem.setProperty('IsPlayable', 'true')
492-
url = sys.argv[0] + '?' + urllib_parse.urlencode({'action': 'play',
493-
'videoid': litem.get('videoId')})
494-
xbmcplugin.addDirectoryItem(int(sys.argv[1]), url, listitem, False)
487+
if items:
488+
for litem in items:
489+
listitem = self.make_listitem(litem.get('labels'), litem.get('cast2'))
490+
listitem.setArt(litem.get('art'))
491+
listitem.setProperty('IsPlayable', 'true')
492+
url = sys.argv[0] + '?' + urllib_parse.urlencode({
493+
'action': 'play',
494+
'videoid': litem.get('videoId')
495+
})
496+
xbmcplugin.addDirectoryItem(int(sys.argv[1]), url, listitem, False)
495497

496-
# Sort methods and content type...
497-
xbmcplugin.setContent(int(sys.argv[1]), 'movies')
498-
xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_UNSORTED)
499-
xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_TITLE)
500-
if force_mode:
501-
xbmc.executebuiltin('Container.SetViewMode({})'.format(view_mode))
502-
# End of directory...
503-
xbmcplugin.endOfDirectory(int(sys.argv[1]), cacheToDisc=True)
498+
# Sort methods and content type...
499+
xbmcplugin.setContent(int(sys.argv[1]), 'movies')
500+
xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_UNSORTED)
501+
xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORT_METHOD_VIDEO_TITLE)
502+
if force_mode:
503+
xbmc.executebuiltin('Container.SetViewMode({})'.format(view_mode))
504+
# End of directory...
505+
xbmcplugin.endOfDirectory(int(sys.argv[1]), cacheToDisc=True)
504506

505507
def get_contents2(self, key):
506508
videos = self.fetchdata(key)
@@ -720,24 +722,42 @@ def list_contents2(self):
720722
def fetch_video_url(self, video_id):
721723
if DEBUG:
722724
self.log('fetch_video_url("{0}")'.format(video_id))
723-
vidurl = DETAILS_PAGE.format(video_id)
724-
pagedata = client.request(vidurl, headers=self.headers)
725-
r = re.search(r'application/json">([^<]+)', pagedata)
726-
if r:
727-
details = json.loads(r.group(1)).get('props', {}).get('pageProps', {}).get('videoPlaybackData', {}).get('video')
728-
if details:
729-
details = {i.get('displayName').get('value'): i.get('url') for i in details.get('playbackURLs') if i.get('videoMimeType') == 'MP4'}
730-
vids = [(x[:-1], details[x]) for x in details.keys() if 'p' in x]
731-
vids.sort(key=lambda x: int(x[0]), reverse=True)
725+
query = '''query VideoPlayback(
726+
$viconst: ID!
727+
) {
728+
video(id: $viconst) {
729+
...SharedVideoAllPlaybackUrls
730+
}
731+
}
732+
'''
733+
fragment = '''fragment SharedVideoAllPlaybackUrls on Video {
734+
playbackURLs {
735+
displayName {
736+
value
737+
}
738+
videoMimeType
739+
url
740+
}
741+
}
742+
'''
743+
pdata = {
744+
'operationName': "VideoPlayback",
745+
'query': self.gqlmin(query + fragment),
746+
'variables': {"viconst": video_id}
747+
}
748+
data = client.request(self.api_url, headers=self.headers, post=pdata)
749+
vids = data.get('data').get('video').get('playbackURLs')
750+
vids = {i.get('displayName').get('value'): i.get('url') for i in vids
751+
if i.get('videoMimeType') == 'MP4'}
752+
vids = [(x[:-1], y) for x, y in vids.items() if 'p' in x]
753+
vids.sort(key=lambda x: int(x[0]), reverse=True)
754+
if DEBUG:
755+
self.log('Found %s videos' % len(vids))
756+
for qual, vid in vids:
757+
if int(qual) <= quality:
732758
if DEBUG:
733-
self.log('Found %s videos' % len(vids))
734-
for qual, vid in vids:
735-
if int(qual) <= quality:
736-
if DEBUG:
737-
self.log('videoURL: %s' % vid)
738-
return vid
739-
740-
return None
759+
self.log('videoURL: %s' % vid)
760+
return vid
741761

742762
def play(self):
743763
if DEBUG:
@@ -758,38 +778,55 @@ def play(self):
758778

759779
def play_id(self):
760780
imdb_id = self.parameters('imdb')
781+
season = self.parameters('season')
761782
if DEBUG:
762783
self.log('play_id("{0}")'.format(imdb_id))
763-
764-
video = self.fetchdata_id(imdb_id)
765-
if 'errors' in video.keys():
766-
msg = 'Invalid IMDb ID'
767-
xbmcgui.Dialog().notification(_plugin, msg, _icon, 3000, False)
768-
return
769-
770-
video = video.get('data').get('title')
771-
if video.get('latestTrailer'):
772-
videoid = video.get('latestTrailer').get('id')
773-
title = video.get('titleText').get('text')
774-
try:
775-
year = video.get('releaseDate').get('year')
776-
except AttributeError:
777-
year = ''
778-
plot = video.get('plot')
779-
if plot:
780-
plot = plot.get('plotText').get('plainText')
781-
thumbnail = video.get('latestTrailer').get('thumbnail').get('url')
782-
poster = video.get('primaryImage').get('url')
783-
listitem = self.make_plistitem(title + ' (Trailer)', plot, year)
784-
listitem.setArt({'thumb': poster,
785-
'icon': poster,
786-
'poster': poster,
787-
'fanart': thumbnail})
788-
listitem.setPath(cache.get(self.fetch_video_url, cache_duration, videoid))
789-
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem=listitem)
784+
if season:
785+
self.log('play_id_season("{0}")'.format(season))
786+
787+
if season:
788+
video = self.fetchdata_id_season(imdb_id, season)
789+
if video:
790+
videoid = video.get('id')
791+
title = video.get('name')
792+
listitem = self.make_plistitem(title)
793+
listitem.setPath(cache.get(self.fetch_video_url, cache_duration, videoid))
794+
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem=listitem)
795+
else:
796+
msg = 'No Trailers available'
797+
xbmcgui.Dialog().notification(_plugin, msg, _icon, 3000, False)
790798
else:
791-
msg = 'No Trailers available'
792-
xbmcgui.Dialog().notification(_plugin, msg, _icon, 3000, False)
799+
video = self.fetchdata_id(imdb_id)
800+
if 'errors' in video.keys():
801+
msg = 'Invalid IMDb ID'
802+
xbmcgui.Dialog().notification(_plugin, msg, _icon, 3000, False)
803+
return
804+
805+
video = video.get('data').get('title')
806+
if video.get('latestTrailer'):
807+
videoid = video.get('latestTrailer').get('id')
808+
title = video.get('titleText').get('text')
809+
try:
810+
year = video.get('releaseDate').get('year')
811+
except AttributeError:
812+
year = ''
813+
plot = video.get('plot')
814+
if plot:
815+
plot = plot.get('plotText').get('plainText')
816+
thumbnail = video.get('latestTrailer').get('thumbnail').get('url')
817+
poster = video.get('primaryImage').get('url')
818+
listitem = self.make_plistitem(title + ' (Trailer)', plot, year)
819+
listitem.setArt({
820+
'thumb': poster,
821+
'icon': poster,
822+
'poster': poster,
823+
'fanart': thumbnail
824+
})
825+
listitem.setPath(cache.get(self.fetch_video_url, cache_duration, videoid))
826+
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem=listitem)
827+
else:
828+
msg = 'No Trailers available'
829+
xbmcgui.Dialog().notification(_plugin, msg, _icon, 3000, False)
793830

794831
def parameters(self, arg):
795832
_parameters = urllib_parse.parse_qs(urllib_parse.urlparse(sys.argv[2]).query)
@@ -798,7 +835,7 @@ def parameters(self, arg):
798835
val = val[0]
799836
return val
800837

801-
def make_plistitem(self, title, plot, year=0):
838+
def make_plistitem(self, title, plot='', year=0):
802839
li = xbmcgui.ListItem(title)
803840
if _kodiver > 19.8:
804841
vtag = li.getVideoInfoTag()
@@ -942,6 +979,117 @@ def fetchdata_id(self, imdb_id):
942979
data = client.request(self.api_url, headers=self.headers, post=pdata)
943980
return data
944981

982+
def fetchdata_id_season(self, imdb_id, season):
983+
video = {}
984+
query = [
985+
'''
986+
query TitleVideoGallerySubPage(
987+
$const: ID!
988+
$first: Int!
989+
$filter: VideosQueryFilter
990+
$sort: VideoSort
991+
) {
992+
title(id: $const) {
993+
videoStrip(first: $first, filter: $filter, sort: $sort) {
994+
...VideoGalleryItems
995+
}
996+
}
997+
}
998+
''',
999+
'''
1000+
query TitleVideoGalleryPagination(
1001+
$const: ID!
1002+
$first: Int!
1003+
$after: ID !
1004+
$filter: VideosQueryFilter
1005+
$sort: VideoSort
1006+
) {
1007+
title(id: $const) {
1008+
videoStrip(first: $first, after: $after, filter: $filter, sort: $sort) {
1009+
...VideoGalleryItems
1010+
}
1011+
}
1012+
}
1013+
'''
1014+
]
1015+
1016+
fragment = '''
1017+
fragment VideoGalleryItems on VideoConnection {
1018+
pageInfo {
1019+
endCursor
1020+
hasNextPage
1021+
}
1022+
edges {
1023+
position
1024+
node {
1025+
id
1026+
contentType {
1027+
displayName {
1028+
value
1029+
}
1030+
id
1031+
}
1032+
name {
1033+
value
1034+
}
1035+
}
1036+
}
1037+
}
1038+
'''
1039+
1040+
opname = ['TitleVideoGallerySubPage', 'TitleVideoGalleryPagination']
1041+
page_size = 100
1042+
vpar = {
1043+
"const": imdb_id,
1044+
"first": page_size,
1045+
"filter": {
1046+
"maturityLevel": "INCLUDE_MATURE",
1047+
"types": ["TRAILER"]
1048+
},
1049+
"sort": {
1050+
"by": "DATE",
1051+
"order": "DESC"
1052+
}
1053+
}
1054+
1055+
pdata = {
1056+
'operationName': opname[0],
1057+
'query': self.gqlmin(query[0] + fragment),
1058+
'variables': vpar
1059+
}
1060+
1061+
data = client.request(self.api_url, headers=self.headers, post=pdata)
1062+
try:
1063+
data = data.get('data').get('title').get('videoStrip')
1064+
trailers = [{'id': x.get('node').get('id'), 'name': x.get('node').get('name').get('value')} for x in data.get('edges')]
1065+
next_page = data.get('pageInfo').get('hasNextPage')
1066+
while next_page:
1067+
vpar.update({"after": data.get('pageInfo').get('endCursor')})
1068+
pdata = {
1069+
'operationName': opname[1],
1070+
'query': self.gqlmin(query[1] + fragment),
1071+
'variables': vpar
1072+
}
1073+
data = client.request(self.api_url, headers=self.headers, post=pdata)
1074+
data = data.get('data').get('title').get('videoStrip')
1075+
trailers += [{'id': x.get('node').get('id'), 'name': x.get('node').get('name').get('value')} for x in data.get('edges')]
1076+
next_page = data.get('pageInfo').get('hasNextPage')
1077+
if trailers:
1078+
rel_trailers = [x for x in trailers if f'season {season}' in x.get('name').lower()]
1079+
if rel_trailers:
1080+
if len(rel_trailers) > 1:
1081+
rel_trailers = [
1082+
x for x in rel_trailers
1083+
if all(y not in x.get('name').lower() for y in ['teaser', 'preview', 'episode'])
1084+
]
1085+
video = rel_trailers[0]
1086+
else:
1087+
video = trailers[0]
1088+
except:
1089+
pass
1090+
1091+
return video
1092+
9451093
def fetchdata(self, key):
9461094
vpar = {'limit': 100}
9471095
if key == 'trending':

0 commit comments

Comments
 (0)