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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  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. playlist = plugin.get_setting("general_playlist",str)
  34. download_dir = plugin.get_setting("general_download_dir",str)
  35. view_mode = plugin.get_setting("general_view_mode",str)
  36. streams_file = plugin.get_setting("general_streams_file",str)
  37. streams_file_remote = plugin.get_setting("general_streams_file_remote",str)
  38. use_streams_file_remote = plugin.get_setting("general_use_streams_file_remote",bool)
  39. download_overwrite = plugin.get_setting("general_download_overwrite",bool)
  40. #storage_path = os.path.join(plugin.storage_path,"sources.p")
  41. if use_storage:
  42. try:
  43. storage = plugin.get_storage("playstream","pickle",ttl=30)
  44. except Exception as e:
  45. os.remove(os.path.join(plugin.storage_path, "playstream"))
  46. try:
  47. storage = plugin.get_storage("playstream", "pickle", ttl=30)
  48. except Exception as e:
  49. print "[playstream] error opening storage", plugin.storage_path
  50. print "Got Exception: ", str(e)
  51. import traceback
  52. traceback.print_exc()
  53. plugin.notify("Error opening permament storage", "Info", 10000, xbmcgui.NOTIFICATION_INFO)
  54. storage = None
  55. os.remove(os.path.join(plugin.storage_path, "playstream"))
  56. cur_directory = os.path.dirname(__file__)
  57. sources_directory = os.path.join(cur_directory,"resources","lib", "content", "sources")
  58. cfg_list = glob.glob(os.path.join(sources_directory,"*.cfg"))
  59. for cf in cfg_list:
  60. cf2 = os.path.join(plugin.storage_path,os.path.split(cf)[1])
  61. if not os.path.exists(cf2):
  62. shutil.copyfile(cf,cf2)
  63. if use_storage and storage is not None and "sources" in storage:
  64. print "[playstream] Restore sources from storage"
  65. sources = storage["sources"]
  66. # if use_storage and os.path.exists(storage_path): #"sources" in storage:
  67. #sources = pickle.load(open(storage_path,"rb"))
  68. else:
  69. print "[playstream] Create sources objects"
  70. if use_streams_file_remote:
  71. try:
  72. sources = ContentSources.ContentSources(sources_directory, streams_file_remote)
  73. except Exception as e:
  74. try:
  75. sources = ContentSources.ContentSources(sources_directory, streams_file)
  76. plugin.notify("Remote streams file is not available, fallback to local")
  77. except Exception as e:
  78. plugin.notify(e.message)
  79. else:
  80. try:
  81. sources = ContentSources.ContentSources(sources_directory, streams_file)
  82. except Exception as e:
  83. plugin.notify(e.message)
  84. for source in sources.plugins:
  85. if not ("options" in dir(sources.plugins[source]) and sources.plugins[source].options): continue
  86. options = sources.plugins[source].options
  87. if not options: continue
  88. for option in options:
  89. key="%s_%s"%(source,option)
  90. if key in ("viaplay_device"): continue # exception list,
  91. value = plugin.get_setting(key)
  92. options[option] = value
  93. sources.plugins[source].options_write(options)
  94. @plugin.route(".+" )
  95. def main():
  96. global prefix
  97. plugin.set_content("movies")
  98. data = plugin.request.url.replace(prefix,"")
  99. data = urllib.unquote(data)
  100. sources.plugins["config"].read_streams()
  101. if not data:
  102. data = u"config::home"
  103. print "[playstream] processing data=%s"%data
  104. if sources.is_video(data):
  105. try:
  106. streams = sources.get_streams(data)
  107. except Exception,e:
  108. #xbmcgui.Dialog().ok("Error",unicode(e))
  109. plugin.notify(unicode(e),"Error",10000, xbmcgui.NOTIFICATION_ERROR)
  110. traceback.print_exc()
  111. return plugin.set_resolved_url(None)
  112. if streams:
  113. return play_video(streams)
  114. else:
  115. plugin.notify("No streams found!","Error",10000,xbmcgui.NOTIFICATION_ERROR)
  116. return plugin.set_resolved_url(None)
  117. else:
  118. if "{0}" in data:
  119. q = plugin.keyboard(default=None, heading="Search for", hidden=False)
  120. if isinstance(q,str):
  121. q = q.decode("utf8")
  122. if isinstance(data,str):
  123. data = data.decode("utf8")
  124. data = data.format(q)
  125. try:
  126. items = get_list(data)
  127. except Exception,e:
  128. plugin.notify(unicode(e),"Error",10000,xbmcgui.NOTIFICATION_ERROR)
  129. traceback.print_exc()
  130. return []
  131. if use_storage and storage is not None:
  132. print "[playstream] Save sources to storage"
  133. storage["sources"] = sources
  134. storage.sync()
  135. return plugin.finish(items, view_mode=get_view_mode(view_mode), update_listing=False, cache_to_disc=False)
  136. def get_list(data):
  137. if isinstance(data,unicode):
  138. data = data.encode("utf8")
  139. content = sources.get_content(data)
  140. print "[playstream] %s items returned"%len(content)
  141. items = []
  142. i = 1
  143. for item in content:
  144. if item[1] == "back": continue
  145. title = item[0].decode("utf8") if isinstance(item[0],str) else item[0]
  146. data2 = item[1].decode("utf8") if isinstance(item[1],str) else item[1]
  147. data2 = urllib.quote(data2, ":/")
  148. is_playable = True if sources.is_video(item[1]) else False
  149. img = item[2].decode("utf8") if isinstance(item[2],str) else item[2]
  150. desc = item[3].decode("utf8") if isinstance(item[3],str) else item[3]
  151. #print title.encode("utf8"),data2,img
  152. context_menu = [
  153. #("Add to PlayStream playlist",
  154. # u'RunScript(special://home/addons/%s/context_menu.py,"playlist","%s","%s","%s","%s")' % (
  155. # plugin.id, title, data2, playlist, proxy_url)),
  156. ("Add to PlayStream favorites",
  157. u'RunScript(special://home/addons/%s/context_menu.py,"add","%s","%s","%s","%s")'%(
  158. plugin.id, title, data2 ,img, desc)),
  159. ]
  160. if data.startswith("config::"):
  161. lst = data.split("::")[1]
  162. context_menu.extend([
  163. ("Delete from PlayStream favorites",
  164. u'RunScript(special://home/addons/%s/context_menu.py,"delete","%s","%s")' % (
  165. plugin.id, lst, i)),
  166. ("Move in PlayStream favorites",
  167. u'RunScript(special://home/addons/%s/context_menu.py,"move","%s","%s")' % (
  168. plugin.id, lst, i)),
  169. ])
  170. context_menu.extend([
  171. # "ActivateWindow(<window-id>,'plugin://<plugin-id>/<parameter-optional>',return)"
  172. ("* Download jobs list *",
  173. u'RunScript(special://home/addons/%s/queue_management.py,"%s","list")' % (plugin.id, plugin.handle)),
  174. #("* Download jobs list *", u'ActivateWindow(10025,"plugin://plugin.video.playstream/downloads::list")'), #plugin.id
  175. #("Active download jobs", u'RunPlugin(plugin://plugin.video.playstream/downloads::list)'), #plugin.id
  176. ])
  177. if is_playable:
  178. context_menu.extend([
  179. ("Download video to default folder",
  180. u'RunScript(special://home/addons/%s/context_download.py,"download","%s","%s","%s")' % (
  181. plugin.id, title, data2, download_dir)),
  182. ("Download video, ask folder",
  183. u'RunScript(special://home/addons/%s/context_download.py,"download2","%s","%s","%s")' % (
  184. plugin.id, title, data2, download_dir)),
  185. ("Download video to subfolder, ask parent folder",
  186. u'RunScript(special://home/addons/%s/context_download.py,"download3","%s","%s","%s")' % (
  187. plugin.id, title, data2, download_dir)),
  188. ])
  189. else:
  190. context_menu.extend([
  191. ("Download list to default folder",
  192. u'RunScript(special://home/addons/%s/context_download.py,"download","%s","%s","%s")' % (
  193. plugin.id, title, data2, download_dir)),
  194. ("Download list, ask folder",
  195. u'RunScript(special://home/addons/%s/context_download.py,"download2","%s","%s","%s")' % (
  196. plugin.id, title, data2, download_dir)),
  197. ])
  198. item = {
  199. "label": title,
  200. "path": prefix+data2,
  201. "thumbnail":thumb_data(img, is_playable),
  202. #"poster":thumb_data(img, is_playable) ,
  203. "icon":thumb_data(img, is_playable) ,
  204. "info":{"plot":desc},
  205. "is_playable":is_playable,
  206. "context_menu": context_menu,
  207. }
  208. if view_mode == "Poster":
  209. item["poster"] = thumb_data(img, is_playable)
  210. items.append(item)
  211. i += 1
  212. return items
  213. def play_video(streams):
  214. if len(streams)>1:
  215. slist = []
  216. for s in streams:
  217. name2 = "%s,%s" % (s["quality"],s["lang"]) if s["lang"] else s["quality"]
  218. slist.append("[%s] %s"%(name2, s["name"]))
  219. res = xbmcgui.Dialog().select("Select stream",slist) 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)