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

addon.py 13KB

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