Kodi plugin to to play various online streams (mostly Latvian)

addon.py 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341
  1. # -*- coding: utf-8 -*-
  2. try:
  3. import wingdbstub
  4. #import ptvsd
  5. #ptvsd.enable_attach("debug", address = ('127.0.0.1', 5678))
  6. #ptvsd.wait_for_attach()
  7. print "[playstream] Remote debug started"
  8. except:
  9. print "[playstream] Remote debug skipped"
  10. pass
  11. import os, os.path,sys, glob, shutil, re
  12. import urllib, traceback
  13. try:
  14. import cPickle as pickle
  15. except:
  16. import pickle
  17. import pickle, threading
  18. from kodiswift import Plugin, ListItem, storage
  19. from kodiswift import xbmc, xbmcgui, xbmcplugin, xbmcvfs, xbmcaddon, CLI_MODE
  20. from resources.lib.content import ContentSources, util
  21. #from downloadqueue import DownloadQueue
  22. cur_directory = os.path.dirname(__file__)
  23. icon_folder = os.path.join(cur_directory, "resources", "picons")
  24. icong_url = xbmcaddon.Addon().getAddonInfo("path") + "/resources/picons/"
  25. sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),"resources","lib", "content", "sources"))
  26. plugin = Plugin()
  27. prefix = "plugin://%s/" % plugin.id
  28. #plugin.load_addon_settings()
  29. use_storage = plugin.get_setting("general_use_storage",bool) # TODO vajag nočekot vai nav labāk lietot pickle
  30. storage_ttl = plugin.get_setting("general_ttl",int)
  31. use_proxy = plugin.get_setting("general_proxy_use",bool)
  32. proxy_url = plugin.get_setting("general_proxy_url",str)
  33. proxy_remote_key = plugin.get_setting("general_proxy_remote_key",str)
  34. playlist = plugin.get_setting("general_playlist",str)
  35. download_dir = plugin.get_setting("general_download_dir",str)
  36. view_mode = plugin.get_setting("general_view_mode",str)
  37. streams_file = plugin.get_setting("general_streams_file",str)
  38. streams_file_remote = plugin.get_setting("general_streams_file_remote",str)
  39. use_streams_file_remote = plugin.get_setting("general_use_streams_file_remote",bool)
  40. download_overwrite = plugin.get_setting("general_download_overwrite",bool)
  41. #storage_path = os.path.join(plugin.storage_path,"sources.p")
  42. if use_storage:
  43. try:
  44. storage = plugin.get_storage("playstream","pickle",ttl=30)
  45. except Exception as e:
  46. os.remove(os.path.join(plugin.storage_path, "playstream"))
  47. try:
  48. storage = plugin.get_storage("playstream", "pickle", ttl=30)
  49. except Exception as e:
  50. print "[playstream] error opening storage", plugin.storage_path
  51. print "Got Exception: ", str(e)
  52. import traceback
  53. traceback.print_exc()
  54. plugin.notify("Error opening permament storage", "Info", 10000, xbmcgui.NOTIFICATION_INFO)
  55. storage = None
  56. os.remove(os.path.join(plugin.storage_path, "playstream"))
  57. cur_directory = os.path.dirname(__file__)
  58. sources_directory = os.path.join(cur_directory,"resources","lib", "content", "sources")
  59. cfg_list = glob.glob(os.path.join(sources_directory,"*.cfg"))
  60. for cf in cfg_list:
  61. cf2 = os.path.join(plugin.storage_path,os.path.split(cf)[1])
  62. if not os.path.exists(cf2):
  63. shutil.copyfile(cf,cf2)
  64. if use_storage and storage is not None and "sources" in storage:
  65. print "[playstream] Restore sources from storage"
  66. sources = storage["sources"]
  67. # if use_storage and os.path.exists(storage_path): #"sources" in storage:
  68. #sources = pickle.load(open(storage_path,"rb"))
  69. else:
  70. print "[playstream] Create sources objects"
  71. if use_streams_file_remote:
  72. cfg_file = streams_file_remote
  73. cfg_file2 = streams_file
  74. else:
  75. cfg_file = streams_file
  76. cfg_file2 = None
  77. try:
  78. sources = ContentSources.ContentSources(sources_directory, cfg_file, cfg_file2)
  79. except Exception as e:
  80. xbmc.executebuiltin("Notification(Error,%s" % e.message)
  81. xbmc.executebuiltin("XBMC.ActivateWindow(Home)")
  82. #plugin.notify("Using streams file '%s'" % sources.plugins["config"].streams_file)
  83. for source in sources.plugins:
  84. if not ("options" in dir(sources.plugins[source]) and sources.plugins[source].options): continue
  85. options = sources.plugins[source].options
  86. if not options: continue
  87. for option in options:
  88. key="%s_%s"%(source,option)
  89. if key in ("viaplay_device"): continue # exception list,
  90. value = plugin.get_setting(key)
  91. options[option] = value
  92. sources.plugins[source].options_write(options)
  93. @plugin.route(".+" )
  94. def main():
  95. global prefix
  96. plugin.set_content("movies")
  97. data = plugin.request.url.replace(prefix,"")
  98. data = urllib.unquote(data)
  99. sources.plugins["config"].read_streams()
  100. if not data:
  101. data = u"config::home"
  102. print "[playstream] processing data=%s"%data
  103. if sources.is_video(data):
  104. try:
  105. streams = sources.get_streams(data)
  106. except Exception,e:
  107. #xbmcgui.Dialog().ok("Error",unicode(e))
  108. plugin.notify(unicode(e),"Error",10000, xbmcgui.NOTIFICATION_ERROR)
  109. traceback.print_exc()
  110. return plugin.set_resolved_url(None)
  111. if streams:
  112. return play_video(streams)
  113. else:
  114. plugin.notify("No streams found!","Error",10000,xbmcgui.NOTIFICATION_ERROR)
  115. return plugin.set_resolved_url(None)
  116. else:
  117. if "{0}" in data:
  118. q = plugin.keyboard(default=None, heading="Search for", hidden=False)
  119. if isinstance(q,str):
  120. q = q.decode("utf8")
  121. if isinstance(data,str):
  122. data = data.decode("utf8")
  123. data = data.format(q)
  124. try:
  125. items = get_list(data)
  126. except Exception,e:
  127. plugin.notify(unicode(e),"Error",10000,xbmcgui.NOTIFICATION_ERROR)
  128. traceback.print_exc()
  129. return []
  130. if use_storage and storage is not None:
  131. print "[playstream] Save sources to storage"
  132. storage["sources"] = sources
  133. storage.sync()
  134. return plugin.finish(items, view_mode=get_view_mode(view_mode), update_listing=False, cache_to_disc=False)
  135. def get_list(data):
  136. if isinstance(data,unicode):
  137. data = data.encode("utf8")
  138. content = sources.get_content(data)
  139. print "[playstream] %s items returned"%len(content)
  140. items = []
  141. i = 1
  142. for item in content:
  143. if item[1] == "back": continue
  144. title = item[0].decode("utf8") if isinstance(item[0],str) else item[0]
  145. data2 = item[1].decode("utf8") if isinstance(item[1],str) else item[1]
  146. data2 = urllib.quote(data2, ":/")
  147. is_playable = True if sources.is_video(item[1]) else False
  148. img = item[2].decode("utf8") if isinstance(item[2],str) else item[2]
  149. desc = item[3].decode("utf8") if isinstance(item[3],str) else item[3]
  150. #print title.encode("utf8"),data2,img
  151. context_menu = [
  152. #("Add to PlayStream playlist",
  153. # u'RunScript(special://home/addons/%s/context_menu.py,"playlist","%s","%s","%s","%s")' % (
  154. # plugin.id, title, data2, playlist, proxy_url)),
  155. ("Add to PlayStream favorites",
  156. u'RunScript(special://home/addons/%s/context_menu.py,"add","%s","%s","%s","%s")'%(
  157. plugin.id, title, data2 ,img, desc)),
  158. ]
  159. if data.startswith("config::"):
  160. lst = data.split("::")[1]
  161. context_menu.extend([
  162. ("Delete from PlayStream favorites",
  163. u'RunScript(special://home/addons/%s/context_menu.py,"delete","%s","%s")' % (
  164. plugin.id, lst, i)),
  165. ("Move in PlayStream favorites",
  166. u'RunScript(special://home/addons/%s/context_menu.py,"move","%s","%s")' % (
  167. plugin.id, lst, i)),
  168. ])
  169. context_menu.extend([
  170. # "ActivateWindow(<window-id>,'plugin://<plugin-id>/<parameter-optional>',return)"
  171. ("* Download jobs list *",
  172. u'RunScript(special://home/addons/%s/queue_management.py,"%s","list")' % (plugin.id, plugin.handle)),
  173. #("* Download jobs list *", u'ActivateWindow(10025,"plugin://plugin.video.playstream/downloads::list")'), #plugin.id
  174. #("Active download jobs", u'RunPlugin(plugin://plugin.video.playstream/downloads::list)'), #plugin.id
  175. ])
  176. if is_playable:
  177. context_menu.extend([
  178. ("Download video to default folder",
  179. u'RunScript(special://home/addons/%s/context_download.py,"download","%s","%s","%s")' % (
  180. plugin.id, title, data2, download_dir)),
  181. ("Download video, ask folder",
  182. u'RunScript(special://home/addons/%s/context_download.py,"download2","%s","%s","%s")' % (
  183. plugin.id, title, data2, download_dir)),
  184. ("Download video to subfolder, ask parent folder",
  185. u'RunScript(special://home/addons/%s/context_download.py,"download3","%s","%s","%s")' % (
  186. plugin.id, title, data2, download_dir)),
  187. ])
  188. else:
  189. context_menu.extend([
  190. ("Download list to default folder",
  191. u'RunScript(special://home/addons/%s/context_download.py,"download","%s","%s","%s")' % (
  192. plugin.id, title, data2, download_dir)),
  193. ("Download list, ask folder",
  194. u'RunScript(special://home/addons/%s/context_download.py,"download2","%s","%s","%s")' % (
  195. plugin.id, title, data2, download_dir)),
  196. ])
  197. item = {
  198. "label": title,
  199. "path": prefix+data2,
  200. "thumbnail":thumb_data(img, is_playable),
  201. #"poster":thumb_data(img, is_playable) ,
  202. "icon":thumb_data(img, is_playable) ,
  203. "info":{"plot":desc},
  204. "is_playable":is_playable,
  205. "context_menu": context_menu,
  206. }
  207. if view_mode == "Poster":
  208. item["poster"] = thumb_data(img, is_playable)
  209. items.append(item)
  210. i += 1
  211. return items
  212. def play_video(streams):
  213. if len(streams)>1:
  214. slist = []
  215. for s in streams:
  216. name2 = "%s,%s" % (s["quality"],s["lang"]) if s["lang"] else s["quality"]
  217. slist.append("[%s] %s"%(name2, s["name"]))
  218. res = xbmcgui.Dialog().select("Select stream",slist) if not CLI_MODE else 0
  219. #res = xbmcgui.Dialog().contextmenu(slist) if not CLI_MODE else 0
  220. stream = streams[res]
  221. else:
  222. stream = streams[0]
  223. subfiles = []
  224. #stream = util.stream_chamge(stream)
  225. if use_proxy:
  226. # if "resolver" in stream and stream["resolver"] in ("hqq","filmas") or \
  227. # "surl" in stream and re.search(r"http*://(hqq|goo\.gl)",stream["surl"]) or \
  228. # "lattelecom.tv/mobile-vod/" in stream["url"]: # TODO
  229. 3 #re.search(r"http*://.+?lattelecom\.tv/.+?auth_token=[^=]+=", stream["url"]):
  230. # stream["url"] = util.streamproxy_encode(stream["url"],stream["headers"],proxy_url)
  231. # stream["headers"] = {}
  232. qs = {"key": proxy_remote_key} if proxy_remote_key else None
  233. stream["url"] = util.streamproxy_encode3("playstream", stream["url"], proxy_url, stream["headers"] )
  234. stream["headers"] = None
  235. if stream["headers"]:
  236. hh = []
  237. for k in stream["headers"]:
  238. h = "%s=%s"%(k,urllib.quote(stream["headers"][k]))
  239. hh.append(h)
  240. hh = "&".join(hh)
  241. stream["url"] = stream["url"] +"|"+hh
  242. print "[playstream] play_video ", stream["url"]
  243. if "subs" in stream and stream["subs"]:
  244. for sub in stream["subs"]:
  245. suburl = sub["url"]
  246. subs = util.Captions(suburl)
  247. srt = subs.get_srt()
  248. #subfile = plugin.temp_fn("subtitles.srt")
  249. subfile = os.path.join(os.path.dirname(__file__),sub["lang"]+".srt")
  250. f = open(subfile, "w")
  251. f.write(srt)
  252. f.close()
  253. subfiles.append(subfile)
  254. item = ListItem(label=stream["name"], thumbnail=thumb_data(stream["img"], True), path=stream["url"])
  255. item.set_info("video",{"plot":stream["desc"]})
  256. item.set_is_playable(True)
  257. return plugin.set_resolved_url(item,subfiles)
  258. #return plugin.play_video(item)
  259. def thumb_data(img, video=False):
  260. default = "video.png" if video else "folder.png"
  261. if img in ("default", ""):
  262. img = default
  263. if not img.startswith("http"):
  264. img = icong_url + img
  265. return img
  266. def get_view_mode(vm):
  267. modes = {
  268. "skin.estuary": {
  269. "None": None,
  270. "List": 50,
  271. "Poster": 51,
  272. "IconWall":52 ,
  273. "Shift": 53,
  274. "InfoWall": 54,
  275. "WideList": 55,
  276. "Wall": 500,
  277. "Banner": 501,
  278. "FanArt": 502
  279. },
  280. "skin.estuary.is": {
  281. "None": None,
  282. "List": 50,
  283. "Poster": 51,
  284. "IconWall":52 ,
  285. "Shift": 53,
  286. "InfoWall": 54,
  287. "WideList": 55,
  288. "Wall": 500,
  289. "Banner": 501,
  290. "FanArt": 502
  291. },
  292. "skin.estuary.isl": {
  293. "None": None,
  294. "List": 50,
  295. "Poster": 51,
  296. "IconWall":52 ,
  297. "Shift": 53,
  298. "InfoWall": 54,
  299. "WideList": 55,
  300. "Wall": 500,
  301. "Banner": 501,
  302. "FanArt": 502
  303. },
  304. }
  305. skin = xbmc.getSkinDir()
  306. if skin in modes and vm in modes[skin]:
  307. view_mode = modes[skin][vm]
  308. else:
  309. view_mode = 50
  310. return view_mode
  311. if __name__ == '__main__':
  312. if CLI_MODE:
  313. from kodiswift.cli.cli import main as start
  314. start()
  315. else:
  316. plugin.run()
  317. if use_storage and storage is not None:
  318. print "[playstream] Save sources to storage"
  319. storage["sources"] = sources
  320. storage.sync()
  321. print "Save sources to storage"
  322. #pickle.dump(sources,open(storage_path,"wb"),pickle.HIGHEST_PROTOCOL)