1717import sys
1818from urllib .parse import urlencode , parse_qsl
1919import json
20+ import datetime
2021
2122import xbmc
2223import xbmcgui
2627
2728import requests
2829import requests_cache
30+ import StorageServer
2931#import web_pdb;
3032
3133# Get the plugin url in plugin:// notation.
4042#TMDB url for poster and fanart
4143TMDB_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
5148def 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
145134def 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
298346def 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
588639if __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