1+ import os
2+ import datetime
13import json
4+
25import sys
36import requests
47from urllib .parse import parse_qs
5- from enum import Enum
8+ from concurrent .futures import ThreadPoolExecutor
9+ import itertools
610
7- # http://mirrors.kodi.tv/docs/python-docs/
8- # http://www.crummy.com/software/BeautifulSoup/bs4/doc/
9- from urllib .parse import urlencode , quote_plus
10- from ast import literal_eval
1111import xbmc
12+ import xbmcvfs
1213import xbmcgui
1314import xbmcplugin
1415
1516from utils import *
17+ from interface import *
1618
1719DEFAULT_MANIFESTATION = 0
1820RADIOFRANCE_PAGE = "https://www.radiofrance.fr"
1921
22+ CACHE_FILE = os .path .join (xbmcvfs .translatePath ('special://temp/' ), 'my_cache.json' )
23+ CACHE_TIMEOUT = datetime .timedelta (seconds = 300 )
24+
25+ # Function to save cache to a file
26+ def save_cache (data ):
27+ with open (CACHE_FILE , 'w' ) as f :
28+ xbmc .log ("Caching to :" + CACHE_FILE , xbmc .LOGINFO )
29+ json .dump (data , f )
30+
31+ # Function to load cache from a file
32+ def load_cache ():
33+ if os .path .exists (CACHE_FILE ):
34+ with open (CACHE_FILE , 'r' ) as f :
35+ xbmc .log ("Loading cach from :" + CACHE_FILE , xbmc .LOGINFO )
36+ try :
37+ data = json .load (f )
38+ except :
39+ return {}
40+ return data
41+ return {}
2042
2143def build_lists (data , args , url ):
22- xbmc .log (str (args ), xbmc .LOGINFO )
23-
24- def add_search ():
25- new_args = {k : v [0 ] for (k , v ) in list (args .items ())}
26- new_args ["mode" ] = "search"
27- li = xbmcgui .ListItem (label = localize (30100 ))
28- li .setIsFolder (True )
29- new_url = build_url (new_args )
30- highlight_list .append ((new_url , li , True ))
31-
32- def add_podcasts ():
33- new_args = {k : v [0 ] for (k , v ) in list (args .items ())}
34- new_args ["mode" ] = "podcasts"
35- li = xbmcgui .ListItem (label = localize (30104 ))
36- li .setIsFolder (True )
37- new_url = build_url (new_args )
38- highlight_list .append ((new_url , li , True ))
39-
40- def add_pages (item ):
41- new_args = {k : v [0 ] for (k , v ) in list (args .items ())}
42- (num , last ) = item .pages
43- if 1 < num :
44- new_args ["page" ] = num - 1
45- li = xbmcgui .ListItem (label = localize (30101 ))
46- li .setIsFolder (True )
47- new_url = build_url (new_args )
48- highlight_list .append ((new_url , li , True ))
49- if num < last :
50- new_args ["page" ] = num + 1
51- li = xbmcgui .ListItem (label = localize (30102 ))
52- li .setIsFolder (True )
53- new_url = build_url (new_args )
54- highlight_list .append ((new_url , li , True ))
55-
56- def add (item , index ):
57- new_args = {}
58- # Create kodi element
59- if item .is_folder ():
60- if item .path is not None :
61- li = xbmcgui .ListItem (label = item .title )
62- li .setArt ({"thumb" : item .image , "icon" : item .icon })
63- li .setIsFolder (True )
64- new_args = {"title" : item .title }
65- new_args ["url" ] = item .path
66- new_args ["mode" ] = "url"
67- builded_url = build_url (new_args )
68- highlight_list .append ((builded_url , li , True ))
69-
70- xbmc .log (
71- str (new_args ),
72- xbmc .LOGINFO ,
73- )
74- if 1 == len (item .subs ):
75- add (create_item (item .subs [0 ]), index )
76- elif 1 < len (item .subs ):
77- li = xbmcgui .ListItem (label = "⭐ " + item .title if item .title is not None else "" )
78- li .setArt ({"thumb" : item .image , "icon" : item .icon })
79- li .setIsFolder (True )
80- new_args = {"title" : "⭐ " + item .title if item .title is not None else "" }
81- new_args ["url" ] = url
82- new_args ["index" ] = index
83- new_args ["mode" ] = "index"
84- builded_url = build_url (new_args )
85- highlight_list .append ((builded_url , li , True ))
86-
87- xbmc .log (
88- str (new_args ),
89- xbmc .LOGINFO ,
90- )
91-
92- else :
93- # Playable element
94- li = xbmcgui .ListItem (label = item .title )
95- li .setArt ({"thumb" : item .image , "icon" : item .icon })
96- new_args = {"title" : item .title }
97- li .setIsFolder (False )
98- tag = li .getMusicInfoTag (offscreen = True )
99- tag .setMediaType ("audio" )
100- tag .setTitle (item .title )
101- tag .setURL (item .path )
102- tag .setGenres ([item .genre if item .model == Model ['Brand' ] else "podcast" ])
103- tag .setArtist (item .artists )
104- tag .setDuration (item .duration if item .duration is not None else 0 )
105- tag .setReleaseDate (item .release )
106- li .setProperty ("IsPlayable" , "true" )
107- if item .path is not None :
108- new_args ["url" ] = item .path
109- new_args ["mode" ] = (
110- "brand" if item .model == Model ["Brand" ] else "stream"
111- )
112-
113- builded_url = build_url (new_args )
114- song_list .append ((builded_url , li , False ))
115-
116- xbmc .log (
117- str (new_args ),
118- xbmc .LOGINFO ,
119- )
120-
121- highlight_list = []
122- song_list = []
44+ gui_elements_list = []
12345
12446 mode = args .get ("mode" , [None ])[0 ]
12547 if mode is None :
126- add_search ( )
127- add_podcasts ( )
48+ Search ( args ). add ( gui_elements_list )
49+ Podcasts ( args ). add ( gui_elements_list )
12850
12951 item = create_item_from_page (data )
52+ context = data .get ('context' , {})
53+
13054 if mode == "index" :
13155 element_index = int (args .get ("index" , [None ])[0 ])
132- items_list = create_item (item .subs [element_index ]). elements
56+ items_list = create_item (0 , item .subs [element_index ], context ). subs
13357 else :
13458 items_list = item .subs
13559
136- add_pages (item )
137- index = 0
138- for data in items_list :
139- sub_item = create_item ( data )
140- xbmc . log ( str ( sub_item ), xbmc . LOGINFO )
141- add ( sub_item , index )
142- index += 1
60+ Pages (item , args ). add ( gui_elements_list )
61+
62+ context = data . get ( 'context' , {})
63+ with ThreadPoolExecutor () as executor :
64+ elements_lists = list ( executor . map ( lambda idx , data : add_with_index ( idx , data , args , context ), range ( len ( items_list )), items_list ) )
65+
66+ gui_elements_list . extend ( itertools . chain . from_iterable ( elements_lists ))
14367
14468 xbmcplugin .setContent (addon_handle , "episodes" )
145- xbmcplugin .addDirectoryItems (addon_handle , highlight_list , len (highlight_list ))
146- xbmcplugin .addDirectoryItems (addon_handle , song_list , len (song_list ))
69+ xbmcplugin .addDirectoryItems (addon_handle , gui_elements_list , len (gui_elements_list ))
14770 xbmcplugin .endOfDirectory (addon_handle )
14871
72+ def add_with_index (index , data , args , context ):
73+ item = create_item (index , data , context )
74+ if not isinstance (item , Item ):
75+ _ , data , exception = item
76+ xbmc .log (f"Error: { exception } on { data } " , xbmc .LOGERROR )
77+ return []
14978
150- def brand (args ):
79+ xbmc .log (str (item ), xbmc .LOGINFO )
80+ elements_list = []
15181 url = args .get ("url" , ["" ])[0 ]
15282
153- xbmc .log ("[Play Brand]: " + url , xbmc .LOGINFO )
83+ if len (item .subs ) <= 2 :
84+ sub_list = list (map (lambda idx , data : create_item (idx , data , context ), range (len (item .subs )), item .subs ))
85+
86+ for sub_item in sub_list :
87+ if sub_item .is_folder ():
88+ elements_list .append (Folder (sub_item , args ).construct ())
89+ else :
90+ elements_list .append (Playable (sub_item , args ).construct ())
91+ elif len (item .subs ) > 1 :
92+ elements_list .append (Indexed (item , url , index , args ).construct ())
93+
94+ if item .is_folder () and item .path is not None :
95+ elements_list .append (Folder (item , args ).construct ())
96+ elif not item .is_folder ():
97+ elements_list .append (Playable (item , args ).construct ())
98+
99+ return elements_list
100+
101+ def brand (args ):
102+ url = args .get ("url" , ["" ])[0 ]
103+ xbmc .log (f"[Play Brand]: { url } " , xbmc .LOGINFO )
154104 play (url )
155105
156106def play (url ):
157107 play_item = xbmcgui .ListItem (path = url )
158108 xbmcplugin .setResolvedUrl (addon_handle , True , listitem = play_item )
159109
160-
161110def search (args ):
162111 def GUIEditExportName (name ):
163112 kb = xbmc .Keyboard ("Odyssées" , localize (30103 ))
@@ -167,64 +116,63 @@ def GUIEditExportName(name):
167116 query = kb .getText ()
168117 return query
169118
170- new_args = {k : v [0 ] for ( k , v ) in list ( args .items () )}
119+ new_args = {k : v [0 ] for k , v in args .items ()}
171120 new_args ["mode" ] = "page"
172121 value = GUIEditExportName ("Odyssées" )
173122 if value is None :
174123 return
175124
176- new_args ["url" ] = RADIOFRANCE_PAGE + " /recherche"
177- new_args = {k : [v ] for ( k , v ) in list ( new_args .items () )}
125+ new_args ["url" ] = f" { RADIOFRANCE_PAGE } /recherche"
126+ new_args = {k : [v ] for k , v in new_args .items ()}
178127 build_url (new_args )
179- get_and_build_lists (new_args , url_args = "?term=" + value + "&" )
180-
128+ get_and_build_lists (new_args , url_args = f"?term={ value } &" )
181129
182130def get_and_build_lists (args , url_args = "?" ):
183- xbmc . log (
184- "" . join ([ "Get and build: " + str ( args ) + "(url args: " + url_args + ")" ]),
185- xbmc . LOGINFO ,
186- )
131+
132+ cache = load_cache ()
133+
134+ xbmc . log ( f"Get and build: { args } (url args: { url_args } )" , xbmc . LOGINFO )
187135 url = args .get ("url" , [RADIOFRANCE_PAGE ])[0 ]
188- page = requests .get (url + "/__data.json" + url_args ).text
189- content = expand_json (page )
190136
191- build_lists (content , args , url )
137+ now = datetime .datetime .now ()
138+ if url + url_args in cache and now - datetime .datetime .fromisoformat (cache [url + url_args ]['datetime' ]) < CACHE_TIMEOUT :
139+ xbmc .log (f"Using cached data for url: { url + url_args } " , xbmc .LOGINFO )
140+ data = cache [url + url_args ]['data' ]
141+ else :
142+ page = requests .get (f"{ url } /__data.json{ url_args } " ).text
143+ data = expand_json (page )
144+ cache [url + url_args ] = {'datetime' : datetime .datetime .now ().isoformat (), 'data' : data }
145+ save_cache (cache )
192146
147+ build_lists (data , args , url )
193148
194149def main ():
195150 args = parse_qs (sys .argv [2 ][1 :])
196- mode = args .get ("mode" , None )
151+ mode = args .get ("mode" , [ None ])[ 0 ]
197152
198- xbmc .log (
199- "" .join (
200- ["mode: " , str ("" if mode is None else mode [0 ]), ", args: " , str (args )]
201- ),
202- xbmc .LOGINFO ,
203- )
153+ xbmc .log (f"Mode: { mode } , Args: { args } " , xbmc .LOGINFO )
204154
205- # initial launch of add-on
155+ # Initial launch of add-on
206156 url = ""
207157 url_args = "?"
208- url_args += "recent=false&"
209- if "page" in args and 1 < int (args .get ("page" , ["1" ])[0 ]):
210- url_args += "p=" + str ( args .get (" page" , ["1" ])[0 ])
211- if mode is not None and mode [ 0 ] == "stream" :
212- play (args ( "url" ) )
213- elif mode is not None and mode [ 0 ] == "search" :
158+ # url_args += "recent=false&"
159+ if "page" in args and int (args .get ("page" , ["1" ])[0 ]) > 1 :
160+ url_args += f"&p= { args .get (' page' , ['1' ])[0 ]} "
161+ if mode == "stream" :
162+ play (args [ "url" ][ 0 ] )
163+ elif mode == "search" :
214164 search (args )
215- elif mode is not None and mode [ 0 ] == "brand" :
165+ elif mode == "brand" :
216166 brand (args )
217167 else :
218- if mode is not None and mode [ 0 ] == "podcasts" :
168+ if mode == "podcasts" :
219169 args ["url" ][0 ] += "/podcasts"
220- elif mode is None :
170+ elif not mode :
221171 url = RADIOFRANCE_PAGE
222- args ["url" ] = []
223- args ["url" ].append (url )
172+ args ["url" ] = [url ]
224173 # New page
225174 get_and_build_lists (args , url_args )
226175
227-
228176if __name__ == "__main__" :
229177 addon_handle = int (sys .argv [1 ])
230178 main ()
0 commit comments