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

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