Skip to content

Commit 39b2f86

Browse files
committed
New matching implementation between lists and local media, new sorting options
1 parent 2e59247 commit 39b2f86

2 files changed

Lines changed: 133 additions & 68 deletions

File tree

addon.xml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<addon id="plugin.video.jlom" version="0.1.7" name="Just Lists Of Movies" provider-name="lbnt">
2+
<addon id="plugin.video.jlom" version="0.1.8" name="Just Lists Of Movies" provider-name="lbnt">
33
<requires>
44
<import addon="xbmc.python" version="3.0.1"/>
55
<import addon="script.module.requests"/>
66
<import addon="script.module.requests-cache"/>
7+
<import addon="script.common.plugin.cache"/>
78
<import addon="script.globalsearch"/>
89
</requires>
910
<extension point="xbmc.python.pluginsource" library="main.py">
@@ -18,6 +19,6 @@
1819
<icon>resources/images/icon.png</icon>
1920
<screenshot>resources/images/screenshot.jpg</screenshot>
2021
</assets>
21-
<news>Lists are now hosted on github and cached</news>
22+
<news>0.1.8 New matching implementation between lists and local media, new sorting options</news>
2223
</extension>
2324
</addon>

main.py

Lines changed: 130 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import sys
1818
from urllib.parse import urlencode, parse_qsl
1919
import json
20+
import datetime
2021

2122
import xbmc
2223
import xbmcgui
@@ -26,6 +27,7 @@
2627

2728
import requests
2829
import requests_cache
30+
import StorageServer
2931
#import web_pdb;
3032

3133
# Get the plugin url in plugin:// notation.
@@ -40,13 +42,8 @@
4042
#TMDB url for poster and fanart
4143
TMDB_IMAGE_BASE_URL = "https://image.tmdb.org/t/p/w500"
4244

43-
ADDON_USER_DATA_FOLDER = translatePath(Addon().getAddonInfo('profile'))
44-
CACHE_FILE = translatePath(os.path.join(ADDON_USER_DATA_FOLDER, 'requests_cache'))
45-
46-
#cache responses from github to avoid too many requests
47-
mkdir(ADDON_USER_DATA_FOLDER)
48-
requests_cache.install_cache( CACHE_FILE, backend='sqlite', expire_after=3600) # Default expiration: 1 hour
49-
45+
#global tmdb to local dbid index
46+
tmdb_index = {}
5047

5148
def get_url(**kwargs):
5249
"""
@@ -100,47 +97,39 @@ def list_folders(folder_list):
10097
# Finish creating a virtual folder.
10198
xbmcplugin.endOfDirectory(HANDLE)
10299

103-
104-
def get_media(title, year):
100+
def build_tmdbid_to_dbid_index():
105101
"""
106-
Try to find the movie in the local database
102+
Get a mapping of TMDB IDs to local database IDs for all movies in the library.
103+
Returns a dictionary where keys are TMDB IDs and values are local database IDs.
107104
"""
108105

109-
#year are often a problem when searching in the library
110-
#let's be less strict
111-
year_minus_1 = str(int(year)-1)
112-
year_plus_1 = str(int(year)+1)
113-
year_minus_2 = str(int(year)-2)
114-
year_plus_2 = str(int(year)+2)
115-
116106
#Construct the JSON-RPC query
117107
json_query = {
118108
"jsonrpc": "2.0",
119109
"method": "VideoLibrary.GetMovies",
120110
"params": {
121-
"filter": {
122-
"and":
123-
[
124-
{"field": "originaltitle", "operator": "is", "value": title},
125-
{"field": "year", "operator": "is", "value": [ year, year_minus_1, year_plus_1, year_minus_2, year_plus_2 ]} ]
126-
},
127-
"properties": ["title","imdbnumber"]
111+
"properties": ["uniqueid"]
128112
},
129113
"id": "libMovies"
130114
}
131115

116+
# Notify user about index building
117+
xbmcgui.Dialog().notification('jlom', 'Building movies index...', xbmcgui.NOTIFICATION_INFO)
132118

133119
# Execute the JSON-RPC query
134120
response = xbmc.executeJSONRPC(json.dumps(json_query))
135121

136122
# Parse the response
137123
result = json.loads(response)
138124

139-
# Check if any movies were found
140-
if result["result"]["limits"]["total"] >= 1:
141-
return result["result"]["movies"][0]["movieid"]
142-
else:
143-
return None
125+
"""Build a dict {tmdb: movieid}."""
126+
index = {}
127+
for movie in result["result"]["movies"]:
128+
tmdb = movie["uniqueid"].get("tmdb")
129+
if tmdb is not None:
130+
index[tmdb] = movie["movieid"]
131+
132+
return index
144133

145134
def play_media(dbid):
146135

@@ -165,26 +154,50 @@ def get_movie_details(id):
165154
json_query = {
166155
"jsonrpc": "2.0",
167156
"method": "VideoLibrary.GetMovieDetails",
168-
"params": {"movieid": id,
169-
"properties": ["director",
170-
"art",
171-
"fanart",
172-
"file",
173-
"genre",
174-
"imdbnumber",
175-
"lastplayed",
176-
"originaltitle",
177-
"playcount",
178-
"plot",
179-
"plotoutline",
180-
"premiered",
181-
"rating", "runtime", #"resume",
182-
"setid", "sorttitle", "streamdetails",
183-
"thumbnail",
184-
"title",
185-
"userrating",
186-
"votes"]},
187-
"id": "1"}
157+
"params": {
158+
"movieid": id,
159+
"properties": [
160+
"title",
161+
"genre",
162+
"year",
163+
"rating",
164+
"director",
165+
"trailer",
166+
"tagline",
167+
"plot",
168+
"plotoutline",
169+
"originaltitle",
170+
"lastplayed",
171+
"playcount",
172+
"writer",
173+
"studio",
174+
"mpaa",
175+
"cast",
176+
"country",
177+
"imdbnumber",
178+
"runtime",
179+
"set",
180+
"showlink",
181+
"streamdetails",
182+
"top250",
183+
"votes",
184+
"fanart",
185+
"thumbnail",
186+
"file",
187+
"sorttitle",
188+
"resume",
189+
"setid",
190+
"dateadded",
191+
"tag",
192+
"art",
193+
"userrating",
194+
"ratings",
195+
"premiered",
196+
"uniqueid"
197+
]
198+
},
199+
"id": "1"
200+
}
188201

189202
# Execute the JSON-RPC query
190203
response = xbmc.executeJSONRPC(json.dumps(json_query))
@@ -213,12 +226,16 @@ def list_movies(movie_list):
213226
xbmcplugin.setContent(HANDLE, 'movies')
214227

215228
if ordered_by == "":
216-
xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_NONE)
229+
xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_NONE) #default
217230
xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_TITLE)
231+
xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_VIDEO_YEAR)
218232
elif ordered_by == "rank":
219-
xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_UNSORTED)
220-
elif ordered_by == "year":
233+
xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_UNSORTED) #default
221234
xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_VIDEO_YEAR)
235+
xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_TITLE)
236+
elif ordered_by == "year":
237+
xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_VIDEO_YEAR)#default
238+
xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_TITLE)
222239

223240
# Iterate through movies.
224241
for index, movie in enumerate(movies):
@@ -240,15 +257,13 @@ def list_movies(movie_list):
240257
info_tag = list_item.getVideoInfoTag()
241258
info_tag.setMediaType('movie')
242259
info_tag.setTitle(movie['title'])
260+
info_tag.setOriginalTitle(movie['original_title'])
261+
info_tag.setYear(int(movie['release_date'][0:4]) if movie['release_date'] != "" else None)
243262
#info_tag.setGenres([genre_info['genre']])
244263
info_tag.setPlot(movie['overview'])
245-
if movie['release_date'] != '':
246-
info_tag.setYear(int(movie['release_date'][0:4]))
247-
#get movie id in library
248-
local_id = get_media(movie['original_title'],movie['release_date'][0:4])
249-
else:
250-
#without year, abort
251-
local_id = None
264+
265+
#try to find the movie in the local database using the tmdb index
266+
local_id = tmdb_index.get(str(movie['id']))
252267

253268
#if found, make it playable
254269
if local_id != None :
@@ -258,17 +273,50 @@ def list_movies(movie_list):
258273
#set info from db
259274
info_tag.setDbId(int(local_id))
260275
info_tag.setPath(f'videodb://movies/titles/{local_id}')
276+
261277
info_tag.setTitle(movie_details["result"]["moviedetails"]["title"])
262278
info_tag.setGenres(movie_details["result"]["moviedetails"]["genre"])
279+
info_tag.setYear(movie_details["result"]["moviedetails"]["year"])
280+
info_tag.setRating(movie_details["result"]["moviedetails"]["rating"])
281+
info_tag.setDirectors(movie_details["result"]["moviedetails"]["director"])
282+
info_tag.setTrailer(movie_details["result"]["moviedetails"]["trailer"])
283+
info_tag.setTagLine(movie_details["result"]["moviedetails"]["tagline"])
263284
info_tag.setPlot(movie_details["result"]["moviedetails"]["plot"])
285+
info_tag.setPlotOutline(movie_details["result"]["moviedetails"]["plotoutline"])
286+
info_tag.setOriginalTitle(movie_details["result"]["moviedetails"]["originaltitle"])
287+
info_tag.setLastPlayed(movie_details["result"]["moviedetails"]["lastplayed"])
288+
info_tag.setPlaycount(movie_details["result"]["moviedetails"]["playcount"])
289+
info_tag.setWriters(movie_details["result"]["moviedetails"]["writer"])
290+
info_tag.setStudios(movie_details["result"]["moviedetails"]["studio"])
291+
info_tag.setMpaa(movie_details["result"]["moviedetails"]["mpaa"])
292+
actors = []
293+
for actor in movie_details["result"]["moviedetails"]["cast"]:
294+
actors.append(xbmc.Actor(actor.get("name"), actor.get("role"), actor.get("order"), actor.get("thumbnail")))
295+
info_tag.setCast(actors)
296+
info_tag.setCountries(movie_details["result"]["moviedetails"]["country"])
297+
info_tag.setIMDBNumber(movie_details["result"]["moviedetails"]["imdbnumber"])
264298
info_tag.setDuration(movie_details["result"]["moviedetails"]["runtime"])
299+
info_tag.setSet(movie_details["result"]["moviedetails"]["set"])
300+
info_tag.setShowLinks(movie_details["result"]["moviedetails"]["showlink"])
301+
#stream details
302+
info_tag.setTop250(movie_details["result"]["moviedetails"]["top250"])
303+
info_tag.setVotes(int(movie_details["result"]["moviedetails"]["votes"]))
304+
#fanart and thumbnail
265305
info_tag.setFilenameAndPath(movie_details["result"]["moviedetails"]["file"])
306+
info_tag.setSortTitle(movie_details["result"]["moviedetails"]["sorttitle"])
307+
info_tag.setResumePoint(movie_details["result"]["moviedetails"]["resume"]["position"], movie_details["result"]["moviedetails"]["resume"]["total"])
308+
info_tag.setSetId(movie_details["result"]["moviedetails"]["setid"])
309+
info_tag.setDateAdded(movie_details["result"]["moviedetails"]["dateadded"])
310+
info_tag.setTags(movie_details["result"]["moviedetails"]["tag"])
311+
#art
312+
info_tag.setUserRating(movie_details["result"]["moviedetails"]["userrating"])
313+
#info_tag.setRatings(...) # todo: implement
266314
info_tag.setPremiered(movie_details["result"]["moviedetails"]["premiered"])
267-
info_tag.setPlaycount(movie_details["result"]["moviedetails"]["playcount"])
268-
315+
info_tag.setUniqueIDs(movie_details["result"]["moviedetails"]["uniqueid"])
316+
269317
#difference between available and not available item
270318
list_item.setProperty('IsPlayable', 'true')
271-
#list_item.setInfo("video", {"overlay": xbmcgui.ICON_OVERLAY_HD}) #does not work anyway
319+
#list_item.setInfo(type="video", infoLabels={"overlay": xbmcgui.ICON_OVERLAY_WATCHED}) #does not work anyway
272320

273321

274322
if ordered_by == "rank":
@@ -296,6 +344,9 @@ def list_movies(movie_list):
296344
xbmcplugin.endOfDirectory(HANDLE)
297345

298346
def get_list(list_type, list_id):
347+
"""
348+
Get a list from the configured URL
349+
"""
299350

300351
list_url = Addon().getSettingString('general_url')
301352
list_url = list_url if list_url.endswith('/') else list_url + '/'
@@ -570,7 +621,7 @@ def router(paramstring):
570621
# the movie was not found in the library and we propose other actions
571622
other_actions = ['Search in library']
572623
if Addon().getSettingBool('radarr_enable') == True :
573-
other_actions.append('Add to radarr')
624+
other_actions.append('Add to Radarr')
574625

575626
choice = xbmcgui.Dialog().contextmenu(other_actions)
576627
if choice == 0:
@@ -586,10 +637,23 @@ def router(paramstring):
586637
raise ValueError(f'Invalid paramstring: {paramstring}!')
587638

588639
if __name__ == '__main__':
589-
# Call the router function and pass the plugin call parameters to it.
590-
# We use string slicing to trim the leading '?' from the plugin call paramstring
591640
#logging.basicConfig(level=logging.DEBUG)
592-
593641
#web_pdb.set_trace()
594642

643+
#initialize requests cache
644+
ADDON_USER_DATA_FOLDER = translatePath(Addon().getAddonInfo('profile'))
645+
CACHE_FILE = translatePath(os.path.join(ADDON_USER_DATA_FOLDER, 'requests_cache'))
646+
647+
mkdir(ADDON_USER_DATA_FOLDER) #make sure the folder exists
648+
requests_cache.install_cache( CACHE_FILE, backend='sqlite', expire_after=3600) # Default expiration: 1 hour
649+
650+
651+
# instantiate the cache for the tmdb index
652+
cache = StorageServer.StorageServer("jlom", 1)
653+
654+
# get data from cache or build it if not present
655+
tmdb_index = cache.cacheFunction(build_tmdbid_to_dbid_index)
656+
657+
# Call the router function and pass the plugin call parameters to it.
658+
# We use string slicing to trim the leading '?' from the plugin call paramstring
595659
router(sys.argv[2][1:])

0 commit comments

Comments
 (0)