Browse Source

pielikti add

Ivars 6 years ago
parent
commit
fc05c87500
87 changed files with 4710 additions and 0 deletions
  1. 340
    0
      addon.py
  2. 23
    0
      addon.xml
  3. 104
    0
      changelog.md
  4. 489
    0
      context_download.py
  5. 95
    0
      context_menu.py
  6. 71
    0
      deploy.bat
  7. 59
    0
      download_service.py
  8. 64
    0
      downloadqueue.py
  9. 2
    0
      get_version.py
  10. 124
    0
      kmake.bat
  11. 2
    0
      link.bat
  12. 2727
    0
      project.wpr
  13. 204
    0
      queue_management.py
  14. 36
    0
      readme.md
  15. BIN
      release/plugin.video.playstream-0.1.12.zip
  16. BIN
      release/plugin.video.playstream-0.1.15.zip
  17. BIN
      release/plugin.video.playstream-0.1.17.zip
  18. BIN
      release/plugin.video.playstream-0.1.18.zip
  19. BIN
      release/plugin.video.playstream-0.1.2.zip
  20. BIN
      release/plugin.video.playstream-0.1.20.zip
  21. BIN
      release/plugin.video.playstream-0.1.21.zip
  22. BIN
      release/plugin.video.playstream-0.1.22.zip
  23. BIN
      release/plugin.video.playstream-0.1.23.zip
  24. BIN
      release/plugin.video.playstream-0.1.24.zip
  25. BIN
      release/plugin.video.playstream-0.1.26.zip
  26. BIN
      release/plugin.video.playstream-0.1.27.zip
  27. BIN
      release/plugin.video.playstream-0.1.28.zip
  28. BIN
      release/plugin.video.playstream-0.1.29.zip
  29. BIN
      release/plugin.video.playstream-0.1.3.zip
  30. BIN
      release/plugin.video.playstream-0.1.30.zip
  31. BIN
      release/plugin.video.playstream-0.1.31.zip
  32. BIN
      release/plugin.video.playstream-0.1.32.zip
  33. BIN
      release/plugin.video.playstream-0.1.33.zip
  34. BIN
      release/plugin.video.playstream-0.1.34.zip
  35. BIN
      release/plugin.video.playstream-0.1.35.zip
  36. BIN
      release/plugin.video.playstream-0.1.36.zip
  37. BIN
      release/plugin.video.playstream-0.1.37.zip
  38. BIN
      release/plugin.video.playstream-0.1.38.zip
  39. BIN
      release/plugin.video.playstream-0.1.4.zip
  40. BIN
      release/plugin.video.playstream-0.1.40.zip
  41. BIN
      release/plugin.video.playstream-0.1.41.zip
  42. BIN
      release/plugin.video.playstream-0.1.42.zip
  43. BIN
      release/plugin.video.playstream-0.1.43.zip
  44. BIN
      release/plugin.video.playstream-0.1.44.zip
  45. BIN
      release/plugin.video.playstream-0.1.45.zip
  46. BIN
      release/plugin.video.playstream-0.1.46.zip
  47. BIN
      release/plugin.video.playstream-0.1.48.zip
  48. BIN
      release/plugin.video.playstream-0.1.49.zip
  49. BIN
      release/plugin.video.playstream-0.1.5.zip
  50. BIN
      release/plugin.video.playstream-0.1.50.zip
  51. BIN
      release/plugin.video.playstream-0.1.51.zip
  52. BIN
      release/plugin.video.playstream-0.1.52.zip
  53. BIN
      release/plugin.video.playstream-0.1.53.zip
  54. BIN
      release/plugin.video.playstream-0.1.54.zip
  55. BIN
      release/plugin.video.playstream-0.1.55.zip
  56. BIN
      release/plugin.video.playstream-0.1.56.zip
  57. BIN
      release/plugin.video.playstream-0.1.57.zip
  58. BIN
      release/plugin.video.playstream-0.1.58.zip
  59. BIN
      release/plugin.video.playstream-0.1.59.zip
  60. BIN
      release/plugin.video.playstream-0.1.6.zip
  61. BIN
      release/plugin.video.playstream-0.1.60.zip
  62. BIN
      release/plugin.video.playstream-0.1.61.zip
  63. BIN
      release/plugin.video.playstream-0.1.62.zip
  64. BIN
      release/plugin.video.playstream-0.1.63.zip
  65. BIN
      release/plugin.video.playstream-0.1.64.zip
  66. BIN
      release/plugin.video.playstream-0.1.65.zip
  67. BIN
      release/plugin.video.playstream-0.1.66.zip
  68. BIN
      release/plugin.video.playstream-0.1.67.zip
  69. BIN
      release/plugin.video.playstream-0.1.68.zip
  70. BIN
      release/plugin.video.playstream-0.1.7.zip
  71. BIN
      release/plugin.video.playstream-0.1.70.zip
  72. BIN
      release/plugin.video.playstream-0.1.71.zip
  73. BIN
      release/plugin.video.playstream-0.1.72.zip
  74. BIN
      release/plugin.video.playstream-0.1.73.zip
  75. BIN
      release/plugin.video.playstream-0.1.74.zip
  76. BIN
      release/plugin.video.playstream-0.1.75.zip
  77. BIN
      release/plugin.video.playstream-0.1.76.zip
  78. BIN
      release/plugin.video.playstream-0.1.77.zip
  79. BIN
      release/plugin.video.playstream-0.1.78.zip
  80. BIN
      release/plugin.video.playstream-0.1.79.zip
  81. BIN
      release/plugin.video.playstream-0.1.8.zip
  82. BIN
      release/plugin.video.playstream-0.1.80.zip
  83. BIN
      release/plugin.video.playstream-0.1.9.zip
  84. 10
    0
      run.py
  85. 38
    0
      service.py
  86. 2
    0
      unlink.bat
  87. 320
    0
      wingdbstub.py

+ 340
- 0
addon.py View File

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

+ 23
- 0
addon.xml View File

@@ -0,0 +1,23 @@
1
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2
+<addon version="0.1.83" id="plugin.video.playstream" name="PlayStream" provider-name="ivars777"  >
3
+  <requires>
4
+    <import addon="xbmc.python" version="2.1.0"/>
5
+    <import addon="script.module.requests" />
6
+    <import addon="script.module.simplejson" />
7
+    <import addon="script.module.pycryptodome" optional="true"/>
8
+  </requires>
9
+  <extension point="xbmc.python.pluginsource" library="addon.py">
10
+    <provides>video</provides>
11
+  </extension>
12
+  <extension point="xbmc.service" library="service.py" />
13
+  <extension point="xbmc.addon.metadata">
14
+    <platform>all</platform>
15
+    <language></language>
16
+    <summary>Play online streams from various sites (mostly Latvian)</summary>
17
+    <description>Play online streams from various sites:
18
+    - replay.lv
19
+    - skaties.lv
20
+    - shortcut.lv
21
+    </description>
22
+  </extension>
23
+</addon>

+ 104
- 0
changelog.md View File

@@ -0,0 +1,104 @@
1
+**0.1.77** (28.06.2018):
2
+- [change] salabots, lai strādā ar Kodi 18 (alpha2)
3
+
4
+**0.1.76** (28.06.2018):
5
+- [feature] ltv.lsm.lv tiešraides strīmi pievienoti replay (futbols)
6
+- [change] uzlabots title ltc, filmix
7
+
8
+**0.1.74** (15.06.2018):
9
+- vēlreiz salabots replay un filmas.lv
10
+
11
+**0.1.72** (15.06.2018):
12
+- salabots replay (stradā futbola čempionāta širfrētie strimi)
13
+- reanimētas filmas.lv (downloads nestrādās)
14
+
15
+**0.1.70** (15.06.2018):
16
+- salabots replay
17
+- uzlabota lejuplāde (ne lidz galam)
18
+
19
+**0.1.63** (18.05.2018):
20
+- salabots filmix domeins (tagad filmix.co)
21
+
22
+**0.1.62** (27.04.2018)
23
+- [change] Salabotas shortcut.lv tiešraides un arhīvs
24
+
25
+**0.1.61** (10.04.2018)
26
+- [change] Salaboti shortcut.lv videonomas saraksti
27
+
28
+**0.1.60** (15.03.2018)
29
+- [bugfix] Slabota shortcut tiešairaide
30
+
31
+**0.1.59** (11.03.2018)
32
+- [bugfix] Slabots filmix
33
+
34
+**0.1.58** (11.03.2018)
35
+- [feature] PlayStream favoritiem sataisits Delete un Move
36
+- [change] uzlabota ltc informācija
37
+- [feature] iespēja opcijās norādīt streams.cfg faila atrašanos/nosaukumu gan lokālā failu sistēmā gan remote (ftp://user:password@host/path/too/streams.cfg), gan http (rakstīšana netiek atbalstīta)
38
+- [change] filmix rādā visus tulkojumus/strīmus (agrak tikai pirmo)
39
+
40
+**0.1.55** (18.02.2018)
41
+- [bugfix] salabots ltc (arhīvs, epg u.c.)
42
+- [bugfix] salabots filmix search
43
+- [change] replay, ltc tiešraidēs nosaukumā rāda epg
44
+
45
+**0.1.54** (17.01.2018)
46
+- [bugfix] salabots submodule
47
+
48
+**0.1.52** (17.01.2018)
49
+- [feature] PlayStream autostart opcija
50
+- [feature] View Mode opcija
51
+
52
+**0.1.50** (12.12.2017)
53
+- [change] Labojumi atbilstoši PlayStream 0.7o
54
+- [feature] Testa variants video lejupielādei
55
+
56
+**0.1.46** (14.10.2017)
57
+- [change] Labojumi atbilstoši PlayStream 0.7k
58
+
59
+**0.1.45** (05.10.2017)
60
+- [change] Salabots ltc, replay, cnemalive u.c.
61
+
62
+**0.1.44** (03.09.2017)
63
+- [change] Sakārtotas pikonas, u.c.
64
+
65
+**0.1.43** (03.09.2017)
66
+- [bugfix] Salaboti video avoti atbilstoši PlayStream 0.7e (lattelekom u.c.)
67
+- [change] pārstrukturets kods (video satura avoti atseviškā git modulī)
68
+- [change] opcija nelietot proxy problemātiskajiem strimiem (lattlecom.tv, viaplay u.c.), izskatās, ka Web serveris stradā par lēnu
69
+
70
+**0.1.41** (03.09.2017)
71
+- [bugfix] Salabots filmix (pamainīts direct url avots uz html5 + dekodēšana kods)
72
+
73
+**0.1.40** (27.08.2017
74
+- [change] Salabots TVDom atbilstoši jaunajam API (papildus parametri - reģions, valoda)
75
+- [bugfix] Salabots Euronews
76
+- [change] Nestrādājošie avoti noslēpti menu, kad tiks salaboti parādīsies atkal
77
+- [change] Sataisīts izmainītais Filmix.me
78
+
79
+**0.1.38** (11.04.2016):
80
+- [feature] options to set local proxy port (by default 8880), proxy url in order to use remote proxy
81
+
82
+**0.1.35** (10.04.2016):
83
+- [feature] option to save plugin status state between calls (necessary to lattelecom.tv archive)
84
+
85
+**0.1.34** (09.04.2016):
86
+- [feature] proxy hack to play full lattelecom.tv videos (more than 5-6 minutes), turn on proxy in settings and restart kodi
87
+
88
+**0.1.33** (01.04.2016):
89
+- [feature] LMT straume (without TV streams)
90
+
91
+**0.1.32** (28.03.2016):
92
+- [feature] sources configod files stored in user_data area (remains after update)
93
+- [feature] option to use persistent storage to store sources objects (including login status) between calls (exprerimental, not very useful currently)
94
+- [feature] proxy server to play encoded hls streams as well as hqq and some other resolved streams (exprerimental, not working correctly)
95
+- [bugfix] viaplay login
96
+
97
+**0.1.24** (23.03.2016):
98
+- [feature] subtitles, context menu etc added
99
+
100
+**0.1.5** (14.02.2016):
101
+- [feature] addon settings added
102
+
103
+**0.1.2** (12.02.2016):
104
+- initial exprerimental version

+ 489
- 0
context_download.py View File

@@ -0,0 +1,489 @@
1
+# -*- coding: utf-8 -*-
2
+try:
3
+    import wingdbstub
4
+except:
5
+    pass
6
+
7
+import sys, os, urllib2, urllib, re, requests, datetime, time, json
8
+#CLI_MODE = True
9
+from kodiswift import xbmc, xbmcgui, CLI_MODE
10
+from kodiswift import Plugin, storage
11
+from resources.lib.content import util, ContentSources, file
12
+from downloadqueue import DownloadQueue
13
+import traceback
14
+
15
+
16
+#from resources.lib.content import Downloader
17
+#from twisted.web import client
18
+#from twisted.internet import reactor, defer
19
+
20
+plugin = Plugin(addon_id="plugin.video.playstream")
21
+#plugin.load_addon_settings()
22
+use_storage = plugin.get_setting("general_use_storage",bool) # TODO vajag nočekot vai nav labāk lietot pickle
23
+overwrite = plugin.get_setting("general_download_overwrite",bool)
24
+sleep_time = 5  # TODO jāliek iekš parametriem
25
+
26
+cunicode = lambda s: s.decode("utf8") if isinstance(s, str) else s
27
+cstr = lambda s: s.encode("utf8") if isinstance(s, unicode) else s
28
+
29
+#print "argv=",sys.argv
30
+cmd = sys.argv[1]
31
+title = sys.argv[2]
32
+data = sys.argv[3]
33
+download_dir = sys.argv[4]
34
+#overwrite = sys.argv[5] if len(sys.argv)>5 else False
35
+
36
+queue_dir = os.path.join(xbmc.translatePath("special://temp"), "download_queue") if not CLI_MODE else "download_queue"
37
+download_queue = DownloadQueue(queue_dir)
38
+
39
+job_id = datetime.datetime.now().strftime("%Y%m%d%H%M")
40
+
41
+if not os.path.exists(queue_dir):
42
+    os.mkdir(queue_dir)
43
+
44
+
45
+if cmd in ["download2", "download3"] and not CLI_MODE:
46
+    download_dir2 = xbmcgui.Dialog().browse(0, "Select download folder", "files")
47
+    if download_dir2:
48
+        download_dir = download_dir2
49
+    else:
50
+        sys.exit()
51
+
52
+#remote_dir = True if re.search("^\w+:", download_dir) else False
53
+#print "encoding=",fs_encoding
54
+
55
+cur_directory = os.path.dirname(__file__)
56
+sources_directory = os.path.join(cur_directory,"resources","lib", "content", "sources")
57
+sources = ContentSources.ContentSources(sources_directory)
58
+
59
+
60
+def main():
61
+    if not sources.is_video(data):  # Folderis
62
+        n = 0
63
+        dname = file.make_fname(title)
64
+        download_dir2 = file.join(download_dir, dname)
65
+        for current2 in sources.get_content(data):
66
+            if sources.is_video(current2[1]):
67
+                n += 1
68
+                download_video(current2[0], current2[1], download_dir2, overwrite, num=n)
69
+        #if n>0:
70
+        #    notify("%s videos download started to %s"%(n,download_dir2))
71
+        #else:
72
+        #    notify("No videos to download")
73
+    else:
74
+        if cmd == "download3":
75
+            dname = file.make_fname(title)
76
+            download_dir2 = file.join(download_dir, dname)
77
+        else:
78
+            download_dir2 = download_dir
79
+        download_video(title, data,download_dir2, overwrite)
80
+        #self.msg("Video download started to %s"%download_dir)
81
+
82
+def download_video(title, data, download_dir, overwrite, cb_notify=None, num=None):
83
+
84
+    streams = sources.get_streams(data)
85
+    if not streams:
86
+        notify("No streams to download")
87
+        return
88
+
89
+    if len(streams)>1 and not num:
90
+        slist = []
91
+        for s in streams:
92
+            slist.append("%s [%s,%s]"%(s["name"],s["quality"],s["lang"]))
93
+        res = xbmcgui.Dialog().select("Select stream",slist) if not CLI_MODE else 0
94
+        #res = xbmcgui.Dialog().contextmenu(slist) if not CLI_MODE else 0
95
+        stream = streams[res]
96
+    else:
97
+        stream = streams[0]
98
+
99
+    download_stream(stream, download_dir, overwrite, notify, num=num)
100
+
101
+    #d = Downloader.download_video(stream["url"], os.path.join(download_dir, output), stream["headers"])
102
+    #reactor.run()
103
+
104
+
105
+    #xbmcgui.Dialog().ok("Info","Start download")
106
+
107
+    #mode = "a" if os.path.exists("context_menu.log") else "w"
108
+    #with open("context_menu.log", mode) as f:
109
+    #    f.write("%s %s %s %s", sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4])
110
+
111
+def notify(text, title="Info", time=10000):
112
+    if isinstance(text, unicode):
113
+        text = text.encode("utf8")
114
+    #xbmc.executebuiltin('Notification(Hello World,This is a simple example of notifications,5000,/script.hellow.world.png)')
115
+    if CLI_MODE:
116
+        print "Info: ", text
117
+    else:
118
+        xbmc.executebuiltin('Notification(%s, %s, %d, %s)'%("Info", text, time, xbmcgui.NOTIFICATION_INFO))
119
+
120
+####################################################################################################
121
+# Platform independent part, should be put in separate module
122
+####################################################################################################
123
+
124
+def download_stream(stream, download_dir, overwrite=True, cb_notify=None, num=False):
125
+    #output =  stream["name"].replace("\\"," ").replace(":"," ").replace("|"," ")
126
+    url = stream["url"]
127
+    title = stream["name"].strip()
128
+    output = file.make_fname(title)
129
+
130
+    headers = stream["headers"] if "headers" in stream and stream["headers"] else {"user-agent":"Enigma2"}
131
+    try:
132
+        h = get_header(url,headers=headers)
133
+        mtype = h.get("content-type")
134
+        ext,stream_type = get_ext(mtype)
135
+    except Exception as e:
136
+        ext,stream_type = (".ts","hls")
137
+    #stream_type = ContentSources.stream_type(url) #self.sources.stream_type(stream["url"])
138
+    if not stream_type: #
139
+        print "Not supported stream type found to download - %s"%(url)
140
+        raise Exception("Not supported stream type found to download - %s"%(url))
141
+
142
+    fname = output+ext
143
+    outputfile = file.join(download_dir, fname)
144
+    exists = file.exists(outputfile)
145
+    if exists and num and num > 1:
146
+        output = output + "_%02i" % num
147
+        fname = output+ext
148
+        outputfile = file.join(download_dir, fname)
149
+        exists = file.exists(outputfile)
150
+
151
+    if exists and not overwrite:
152
+        if cb_notify: cb_notify("Download skipped - %s" % output)
153
+        print "Download skipped - %s" % output
154
+        return
155
+
156
+    print "download_dir=", download_dir
157
+    if not file.isdir(download_dir):
158
+        try:
159
+            file.mkdir(file.encode(download_dir))
160
+        except Exception as e:
161
+            traceback.print_exc()
162
+            print 'Error creating download directory "%s"!\nPlease specify in the settings existing directory\n%s'%(download_dir,str(e))
163
+            raise Exception('Error creating download directory "%s"!\nPlease specify in the settings existing directory\n%s'%(download_dir,str(e)))
164
+
165
+    output_path = file.join(download_dir, output)
166
+
167
+    if "nfo" in stream and stream["nfo"]:
168
+        nfofile = file.join(download_dir, output+".nfo")
169
+        #print "nfofile=", nfofile.encode("utf8")
170
+        nfo_txt = util.nfo2xml(stream["nfo"])
171
+        f = file.open(file.encode(nfofile),"w")
172
+        f.write(nfo_txt)
173
+        f.close()
174
+
175
+    subfiles = []
176
+    if "subs" in stream and stream["subs"]:
177
+        for sub in stream["subs"]:
178
+            suburl = sub["url"]
179
+            slang = "." + sub["lang"] if sub["lang"] else ""
180
+            download_sub(suburl, output+slang, download_dir)
181
+
182
+    if "img" in stream and stream["img"]:
183
+        download_image(stream["img"], output, download_dir)
184
+
185
+    ### Start video file download ####
186
+    job = {
187
+        "job_id": job_id,
188
+        "status": "downloading",
189
+        "url":url,
190
+        "output": output,
191
+        "file": outputfile,
192
+        "download_dir": download_dir,
193
+        "headers": headers,
194
+        "totalbytes": -1,
195
+        "currentbytes": 0,
196
+        "overwrite": overwrite,
197
+    }
198
+    download_queue.job_put(job_id, job)
199
+
200
+    if cb_notify: cb_notify("%s put in download queue" % output)
201
+    print "%s put in download queue" % output
202
+
203
+
204
+    if stream_type == "hls":
205
+        download_hls(url, outputfile, headers=headers, cb_notify=cb_notify)
206
+    else:
207
+        download_file(url, outputfile, headers=headers, cb_notify=cb_notify)
208
+
209
+
210
+def download_hls(url, outputfile, headers=None, overwrite=True, limit=None, cb_notify=None):
211
+    UA = "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:51.0) Gecko/20100101 Firefox/51.0"
212
+    if not headers:
213
+        headers = {"User-Agent" : UA}
214
+    key = headers["key"] if "key" in headers else ""
215
+#    if not "User-Agent" in headers:
216
+#        headers["User-Agent"] = UA
217
+    #outputfile = file.join(download_dir,output)
218
+
219
+    if cb_notify: cb_notify("Download started - %s" % outputfile)
220
+    print "Download started - %s" % outputfile, url
221
+    #print url
222
+
223
+
224
+    try:
225
+        r = requests.get(url,headers=headers)
226
+    except Exception as e:
227
+        raise Exception("Cannot open manifsest file - %s"%url)
228
+    if not r.content.startswith("#EXTM3U"):
229
+        raise Exception("Not valid manifest file - %s" % url)
230
+    streams = re.findall(r"#EXT-X-STREAM-INF:.*?BANDWIDTH=(\d+).*?\n(.+?)$", r.content, re.IGNORECASE | re.MULTILINE)
231
+    i = 0
232
+    while streams:
233
+        if i > 4: break
234
+        sorted(streams, key=lambda item: int(item[0]), reverse=True)
235
+        base_url = "/".join(url.split("?")[0].split("/")[:-1])+"/"
236
+        url = streams[0][1]
237
+        if not url.startswith("http"):
238
+            url = base_url + url
239
+        #print url
240
+        try:
241
+            r = requests.get(url, headers=headers)
242
+        except Exception as e:
243
+            raise Exception("Cannot open manifsest file - %s"%url)
244
+        i += 1
245
+        streams = re.findall(r"#EXT-X-STREAM-INF:.*?BANDWIDTH=(\d+).*?\n(.+?)$", r.content, re.IGNORECASE | re.MULTILINE)
246
+    m = re.search('#EXT-X-KEY:METHOD=(.+?),URI="([^"]+)"', r.content, re.IGNORECASE)
247
+
248
+    if m:
249
+        url_key = m.group(2)
250
+        r2 = requests.get(url_key, headers=headers)
251
+        key = r2.content
252
+    else:
253
+        key = None
254
+
255
+    ts_list = re.findall(r"#EXTINF:([\d\.]+),.*?\n(.+?)$", r.content, re.IGNORECASE | re.MULTILINE)
256
+    base_url = "/".join(url.split("/")[:-1])+"/"
257
+
258
+    if not len(ts_list):
259
+        raise Exception("Cannot read fragment list in  manifsest file - %s"%url)
260
+
261
+    ts_num = 0
262
+    type = "vod" if "#EXT-X-ENDLIST" in r.content else "live"
263
+    currentbytes = 0.0
264
+    totalbytes = -1
265
+    currenttime = 0.0
266
+    totaltime = sum(map(float,zip(*ts_list)[0]))
267
+    #ts_file = open(outputfile, "wb")
268
+
269
+    tsfile = file.open(file.encode(outputfile),"wb")
270
+    for ts in ts_list:
271
+        url2 = ts[1]
272
+        #print "Downloading ", url2
273
+        #fname = os.path.join(download_dir,url2.split("/")[-1])
274
+        if not url2.startswith("http"):
275
+            url2 = base_url + url2
276
+
277
+        r = requests.get(url2, headers=headers, verify=False)
278
+        content = r.content
279
+        if key:
280
+            #from Crypto.Cipher import AES
281
+            import pyaes
282
+            iv = content[:16]
283
+            #key2 = binascii.a2b_hex(key)
284
+            #d = AES.new(key, AES.MODE_CBC, iv)
285
+            d = pyaes.AESModeOfOperationCBC(key, iv = iv)
286
+            content2 = ""
287
+            for i in range(16, len(content), 16):
288
+                content2 += d.decrypt(content[i:i+16])
289
+            content = content2
290
+            #content = d.decrypt(content[16:])
291
+            #d = pyaes.Decrypter(pyaes.AESModeOfOperationCBC(key, iv))
292
+            #content = d.feed(content[16:])
293
+        tsfile.write(content)
294
+
295
+        content_length = len(content)
296
+        currentbytes += content_length
297
+        currenttime += float(ts_list[ts_num][0])
298
+        totalbytes = currentbytes * totaltime / currenttime
299
+
300
+        # Checking queue
301
+        while True:
302
+            job = download_queue.job_get(job_id)
303
+            if not job:
304
+                print "Download canceled"
305
+                notify("Download canceled - %s" % output)
306
+                tsfile.close()
307
+                os.remove(outputfile)
308
+                return
309
+            if job["status"] in ("waiting", "paused"):
310
+                time.sleep(sleep_time)
311
+                continue
312
+            break
313
+        job["currentbytes"] = currentbytes
314
+        job["totalbytes"] = totalbytes
315
+        download_queue.job_put(job_id, job)
316
+
317
+        ts_num += 1
318
+        #print "Fragment %s downloaded (%s)"%(self.ts_num,len(content))
319
+        progress = float(currentbytes)/float(totalbytes)*100
320
+        msg = "%.1f%% (%iMB/%iMB)"%(progress,currentbytes / 1024 / 1024,totalbytes / 1024 / 1024)
321
+        #print msg
322
+        #notify(msg)
323
+
324
+        if type == "vod":
325
+            if ts_num >= len(ts_list) or (limit and currenttime>limit):
326
+                break
327
+        else:  # live stream # TODO
328
+            if limit and currenttime>limit: # TODO
329
+                break
330
+
331
+    download_queue.job_remove(job_id)
332
+    print "Finished"
333
+    notify("Download finished - %s" % outputfile)
334
+    tsfile.close()
335
+
336
+
337
+def download_file(url, outputfile, headers=None, overwrite=True, limit=None, cb_notify=None):
338
+    global job
339
+    UA = "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:51.0) Gecko/20100101 Firefox/51.0"
340
+    if not headers:
341
+        headers = {"User-Agent" : UA}
342
+    key = headers["key"] if "key" in headers else ""
343
+#    if not "User-Agent" in headers:
344
+#        headers["User-Agent"] = UA
345
+    #fname = file.join(download_dir,output)
346
+
347
+    if cb_notify: cb_notify("Download started - %s" % outputfile)
348
+    print "Download started - %s" % outputfile
349
+    #print url
350
+    try:
351
+        r = requests.get(url,headers=headers, stream=True)
352
+    except Exception as e:
353
+        raise Exception("Cannot open url - %s"%url)
354
+    currentbytes = 0.0
355
+    totalbytes = int(r.headers["content-length"])
356
+
357
+
358
+    fd = file.open(file.encode(outputfile), 'wb')
359
+    for chunk in r.iter_content(chunk_size=1024*1024):
360
+        fd.write(chunk)
361
+        currentbytes += len(chunk)
362
+        progress = float(currentbytes)/float(totalbytes)*100
363
+        msg = "%.1f%% (%iMB/%iMB)"%(progress,currentbytes / 1024 / 1024,totalbytes / 1024 / 1024)
364
+        #print msg
365
+
366
+        # Checking queue
367
+        while True:
368
+            job = download_queue.job_get(job_id)
369
+            if not job:
370
+                print "Download canceled"
371
+                notify("Download canceled - %s" % outputfile)
372
+                fd.close()
373
+                os.remove(fname)
374
+                return
375
+            if job["status"] in ("waiting", "paused"):
376
+                time.sleep(sleep_time)
377
+                continue
378
+            break
379
+        job["currentbytes"] = currentbytes
380
+        job["totalbytes"] = totalbytes
381
+        download_queue.job_put(job_id, job)
382
+
383
+        #notify(msg)
384
+
385
+    fd.close()
386
+    job.remove()
387
+    print "Finished"
388
+    notify("Download finished - %s" % outputfile)
389
+
390
+def download_sub(suburl, output, download_dir):
391
+    try:
392
+        subs = urllib2.urlopen(suburl).read()
393
+    except:
394
+        subs = None
395
+    if subs:
396
+        if ".xml" in suburl:
397
+            subs = util.ttaf2srt(subs)
398
+            subext = ".srt"
399
+        elif ".vtt" in suburl:
400
+            subs = util.vtt2srt(subs)
401
+            subext = ".srt"
402
+        elif ".srt" in suburl:
403
+            subext = ".srt"
404
+        else:
405
+            subext = ".srt"
406
+        if subext:
407
+            subfile = file.join(download_dir, output+subext)
408
+            f = file.open(file.encode(subfile),"w")
409
+            f.write(subs)
410
+            f.close()
411
+    else:
412
+        print "\n Error downloading subtitle %s"%suburl
413
+        raise Exception("Error downloading subtitle %s"%suburl)
414
+
415
+
416
+def download_image(imgurl, output, download_dir):
417
+    ext = imgurl.split("?")[0].split(".")[-1]
418
+    ext = "." + ext
419
+    ext = ".jpg"
420
+    imgfile =  file.join(download_dir, output+ext)
421
+    try:
422
+        img = urllib2.urlopen(imgurl).read()
423
+    except Exception(e):
424
+        img = None
425
+    if img:
426
+        f = file.open(file.encode(imgfile),"wb")
427
+        f.write(img)
428
+        f.close()
429
+    else:
430
+        print "\n Error downloading image %s"%imgurl
431
+        raise Exception("Error downloading image %s"%imgurl)
432
+
433
+
434
+
435
+def get_header(url,headers=None):
436
+    r = requests.head(url,headers=headers)
437
+    return r.headers
438
+
439
+def get_ext(mtype):
440
+    stype = "http"
441
+    if mtype in ("vnd.apple.mpegURL","application/x-mpegURL",'application/x-mpegurl',"application/vnd.apple.mpegurl"):
442
+        return ".ts","hls"
443
+    elif mtype in ("application/dash+xml"):
444
+        return ".ts","dash" # TODO dash stream type  could be different !
445
+    elif mtype in ("video/mp4"):
446
+        return ".mp4","http"
447
+    elif mtype in ("video/MP2T","video/mp2t"):
448
+        return ".ts","http"
449
+    elif mtype in ("video/x-flv"):
450
+        return ".flv","http"
451
+    elif mtype in ("video/quicktime"):
452
+        return ".mov","http"
453
+    elif mtype in ("video/x-msvideo"):
454
+        return ".avi","http"
455
+    elif mtype in ("video/x-ms-wmv"):
456
+        return ".wmv","http"
457
+    elif mtype in ("video/x-matroska"):
458
+        return ".mkv","http"
459
+    else:
460
+        return ".mp4","http"
461
+
462
+
463
+class Job(object):
464
+    def __init__(self, job_id, queue_dir, job={}):
465
+        self.job_id = job_id
466
+        self.queue_dir = queue_dir
467
+        self.job_file = os.path.join(queue_dir, job_id)
468
+        self.job = job
469
+
470
+    def read(self):
471
+        try:
472
+            with open(self.job_file, "r") as f:
473
+                s = f.read()
474
+            self.job = json.loads(s)
475
+        except:
476
+            self.job = {}
477
+        return self.job
478
+
479
+    def write(self):
480
+        s = json.dumps(self.job)
481
+        with open(self.job_file, "w") as f:
482
+            f.write(s)
483
+    def remove(self):
484
+        if os.path.exists(self.job_file):
485
+            os.remove(self.job_file)
486
+
487
+
488
+if __name__ == '__main__':
489
+    main()

+ 95
- 0
context_menu.py View File

@@ -0,0 +1,95 @@
1
+import sys, os, os.path
2
+from kodiswift import xbmc, xbmcgui, CLI_MODE
3
+from kodiswift import Plugin, storage
4
+from resources.lib.content import util
5
+try:
6
+    import wingdbstub
7
+except:
8
+    pass
9
+
10
+
11
+plugin = Plugin(addon_id="plugin.video.playstream")
12
+#plugin.load_addon_settings()
13
+#playlist = plugin.get_setting("general_playlist",str)
14
+#proxy_url = plugin.get_setting("general_proxy_url",str)
15
+streams_file = plugin.get_setting("general_streams_file",str)
16
+streams_file_remote = plugin.get_setting("general_streams_file_remote",str)
17
+use_streams_file_remote = plugin.get_setting("general_use_streams_file_remote",bool)
18
+download_overwrite = plugin.get_setting("general_download_overwrite",bool)
19
+
20
+cmd = sys.argv[1]
21
+#title = sys.argv[2]
22
+#data = sys.argv[3]
23
+#img = sys.argv[4]
24
+#desc = sys.argv[5]
25
+
26
+if cmd in ("add", "delete", "move"):
27
+    from resources.lib.content.sources.config import Source
28
+    fname = streams_file_remote if use_streams_file_remote else streams_file
29
+    try:
30
+        cfg = Source(cfg_file=fname)
31
+    except Exception as e:
32
+        plugin.notify("Cannot open content config file %s" % fname)
33
+    lists = cfg.get_lists()
34
+    titles = [cfg.get_title(name) for name in lists]
35
+
36
+if cmd == "add":
37
+    if not CLI_MODE:
38
+        ret = xbmcgui.Dialog().select("Select menu",titles)
39
+    else:
40
+        ret = 3
41
+    if ret > 0:
42
+        cfg.add_item(lists[ret],sys.argv[2:])
43
+        cfg.write_streams()
44
+
45
+elif cmd == "delete":
46
+    cfg.del_item(sys.argv[2], int(sys.argv[3]))
47
+    cfg.write_streams()
48
+    xbmc.executebuiltin("Container.Refresh")
49
+
50
+elif cmd == "move":
51
+    name = sys.argv[2]
52
+    pos = int(sys.argv[3])
53
+    item = cfg.get_list_items(name)[pos]
54
+    cfg.del_item(name, pos)
55
+    ret = xbmcgui.Dialog().select("Select menu",titles)
56
+    if ret > 0:
57
+        name2 = lists[ret]
58
+        items2 = cfg.get_list_items(name)
59
+        items2 = [it[0] for it in items2]
60
+        ret = xbmcgui.Dialog().select("Select position before item will be insert",items2)
61
+        if ret > 0:
62
+            cfg.add_item(name2, item, ret)
63
+            cfg.write_streams()
64
+            xbmc.executebuiltin("Container.Refresh")
65
+
66
+elif cmd == "playlist":
67
+    title = sys.argv[2]
68
+    data = sys.argv[3]
69
+    playlist = sys.argv[4]
70
+    proxy_url = sys.argv[5]
71
+    if not os.path.exists(playlist):
72
+        pl = open(playlist,"wb")
73
+        pl.write('#EXTM3U\n')
74
+    else:
75
+        pl = open(playlist,"a")
76
+    urlp = util.streamproxy_encode2(data,{},proxy_url)
77
+    if isinstance(urlp,unicode):
78
+        urlp = urlp.encode("utf8")
79
+    if isinstance(title,unicode):
80
+        title = title.encode("utf8")
81
+    pl.write("#EXTINF:0,%s\n%s\n"%(title,urlp))
82
+    pl.close()
83
+    #plugin.notify("Item '%s' added to %s"%(title,playlist), "Info", 10000, xbmcgui.NOTIFICATION_INFO)
84
+
85
+elif cmd == "download":
86
+    xbmcgui.Dialog().ok("Info","Not yet implemented!")
87
+
88
+else:
89
+    xbmcgui.Dialog().ok("Error","Wrong command")
90
+
91
+
92
+
93
+#mode = "a" if os.path.exists("context_menu.log") else "w"
94
+#with open("context_menu.log", mode) as f:
95
+#    f.write("%s %s %s %s", sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4])

+ 71
- 0
deploy.bat View File

@@ -0,0 +1,71 @@
1
+@echo off
2
+if (abox)==(%1%) (
3
+	set TARGET=a:\0\Android\data\org.xbmc.kodi\files\.kodi\addons\plugin.video.playstream\
4
+) else (
5
+    set TARGET=C:\Users\User\AppData\Roaming\Kodi\addons\plugin.video.playstream\
6
+)
7
+
8
+for %%f in (
9
+readme.md
10
+changelog.md
11
+addon.xml
12
+addon.py
13
+context_menu.py
14
+context_download.py
15
+downloadqueue.py
16
+service.py
17
+icon.png
18
+kodiswift\*.py
19
+resources\__init__.py
20
+resources\settings.xml
21
+resources\icon.png
22
+resources\language\English\*
23
+resources\lib\__init__.py
24
+resources\lib\content\__init__.py
25
+resources\lib\content\ContentSources.py
26
+resources\lib\content\playstreamproxy.py
27
+resources\lib\content\util.py
28
+resources\lib\content\file.py
29
+resources\lib\content\run.py
30
+resources\lib\content\resolver.py
31
+resources\lib\content\demjson.py
32
+resources\lib\content\ordereddict.py
33
+resources\lib\content\sources\__init__.py
34
+resources\lib\content\sources\SourceBase.py
35
+resources\lib\content\sources\cinemalive.py
36
+resources\lib\content\sources\config.py
37
+resources\lib\content\sources\euronews.py
38
+resources\lib\content\sources\filmix.py
39
+resources\lib\content\sources\filmon.py
40
+resources\lib\content\sources\iplayer.py
41
+resources\lib\content\sources\movieplace.py
42
+resources\lib\content\sources\ltc.py
43
+resources\lib\content\sources\mtgplay.py
44
+resources\lib\content\sources\play24.py
45
+resources\lib\content\sources\replay.py
46
+resources\lib\content\sources\serialguru.py
47
+resources\lib\content\sources\tvdom.py
48
+resources\lib\content\sources\ustvnow.py
49
+resources\lib\content\sources\viaplay.py
50
+resources\lib\content\sources\lmt.py
51
+resources\lib\content\sources\filmas.py
52
+resources\lib\content\sources\YouTubeVideoUrl.py
53
+resources\lib\content\sources\jsinterp.py
54
+resources\lib\content\sources\swfinterp.py
55
+resources\lib\content\sources\streams.cfg
56
+resources\lib\content\resolvers\__init__.py
57
+resources\lib\content\resolvers\aadecode.py
58
+resources\lib\content\resolvers\hqqresolver.py
59
+resources\lib\content\resolvers\openload3.py
60
+resources\lib\content\resolvers\hdgo.py
61
+resources\lib\content\resolvers\kapnob.py
62
+resources\lib\content\resolvers\kodik.py
63
+resources\lib\content\resolvers\youtuberesolver.py
64
+resources\picons\*
65
+) do echo f | xcopy /y   %%f %TARGET%%%f
66
+
67
+xcopy /y /q resources\lib\content\picons\* %TARGET%resources\picons\
68
+
69
+rem xcopy /y /d addon_data\settings.xml "C:\Users\user\AppData\Roaming\Kodi\userdata\addon_data\plugin.video.playstream\settings.xml"
70
+
71
+pause

+ 59
- 0
download_service.py View File

@@ -0,0 +1,59 @@
1
+# -*- coding: utf-8 -*-
2
+try:
3
+    import wingdbstub
4
+except:
5
+    pass
6
+import os,os.path,sys, datetime, traceback
7
+from kodiswift import Plugin, ListItem, storage
8
+from kodiswift import xbmc, xbmcgui, xbmcplugin, xbmcvfs, CLI_MODE
9
+
10
+sys.path.append(os.path.join(os.path.dirname(__file__), "resources", "lib"))
11
+#from resources.lib import ContentSources, util
12
+#sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),"resources","lib","sources"))
13
+from twisted.web import client
14
+from twisted.internet import reactor, defer, ssl, task
15
+
16
+import content.Downloader as Downloader
17
+import sys,os, os.path, re
18
+import urlparse, requests
19
+
20
+print "twisted imported"
21
+#str(time.time())
22
+
23
+plugin = Plugin()
24
+plugin.load_addon_settings()
25
+download_dir = plugin.get_setting("general_download_dir",str)
26
+queue_dir = os.path.join(plugin.storage_path,"downloads)")
27
+if not os.path.exists(queue_dir):
28
+    os.mkdir(queue_dir)
29
+
30
+class DownloadQueue(object):
31
+    def __init__(self):
32
+        self.flist=[]
33
+        self.q=[]
34
+
35
+    def check_queue(self):
36
+        print "check_queue"
37
+        flist = os.listdir(queue_dir)
38
+        print "Checking queue"
39
+
40
+    def start_download(self):
41
+        pass
42
+
43
+    def download_ok(self):
44
+        pass
45
+
46
+    def download_err(self):
47
+        pass
48
+
49
+def notify(text, title="Info", time=10000):
50
+    if isinstance(text, unicode):
51
+        text = text.encode("utf8")
52
+    #xbmc.executebuiltin('Notification(Hello World,This is a simple example of notifications,5000,/script.hellow.world.png)')
53
+    xbmc.executebuiltin('Notification(%s, %s, %d, %s)'%("Info", text, time, xbmcgui.NOTIFICATION_INFO))
54
+
55
+
56
+queue = DownloadQueue()
57
+loop = task.LoopingCall(queue.check_queue)
58
+loop.start(2)
59
+reactor.run()

+ 64
- 0
downloadqueue.py View File

@@ -0,0 +1,64 @@
1
+# -*- coding: utf-8 -*-
2
+try:
3
+    import wingdbstub
4
+except:
5
+    pass
6
+
7
+import sys, os, urllib2, urllib, re, requests, datetime, json
8
+from resources.lib.content import file
9
+#CLI_MODE = True
10
+# from kodiswift import xbmc, xbmcgui, CLI_MODE
11
+# from kodiswift import Plugin, storage
12
+
13
+
14
+class DownloadQueue(object):
15
+    def __init__(self, queue_dir):
16
+        self.queue_dir = queue_dir
17
+        if not os.path.isdir(queue_dir):
18
+            os.mkdir(queue_dir)
19
+
20
+    def job_get(self, job_id):
21
+        try:
22
+            job = json.loads(open(os.path.join(self.queue_dir, job_id), "r").read())
23
+        except:
24
+            job = None
25
+        return job
26
+
27
+    def job_put(self, job_id, job):
28
+        try:
29
+            s = json.dumps(job)
30
+            open(os.path.join(self.queue_dir, job_id), "w").write(s)
31
+        except:
32
+            raise Exception("Can not store job")
33
+
34
+    def job_remove(self, job_id):
35
+        try:
36
+            os.remove(os.path.join(self.queue_dir, job_id))
37
+        except:
38
+            pass
39
+
40
+
41
+    def job_ids(self, filer=""):
42
+        lst = os.listdir(self.queue_dir)
43
+        lst.sort()
44
+        return lst
45
+
46
+    def jobs(self, filter=""):
47
+        jobs = []
48
+        for job_id in self.job_ids():
49
+            jobs.append(self.job_get(job_id))
50
+        return jobs
51
+
52
+    def jobs_count(self):
53
+        count = {}
54
+        for job in jobs():
55
+            stat = job["status"]
56
+            if stat in count:
57
+                count[stat] += 1
58
+            else:
59
+                count[stat] = 1
60
+        return stat
61
+
62
+    def clear(self):
63
+        for job_id in self.job_ids():
64
+            os.remove(file.join(self.queue_dir, job_id))

+ 2
- 0
get_version.py View File

@@ -0,0 +1,2 @@
1
+import re,sys
2
+print re.search('<addon version="([^"]+)',open(sys.argv[1]).read()).group(1)

+ 124
- 0
kmake.bat View File

@@ -0,0 +1,124 @@
1
+@echo off
2
+
3
+:--- Pull content submodule ---
4
+pushd resources\lib\content
5
+:git commit -a -m a
6
+:git checkout .
7
+:git pull
8
+popd
9
+
10
+:=== Parameters ===
11
+python get_version.py addon.xml >version.txt
12
+set /p ver=<version.txt
13
+echo %ver%
14
+rem pause
15
+
16
+set prog=PlayStream
17
+set pack_name=plugin.video.playstream
18
+set desc=Play online streams from various sources, mostly Latvian
19
+
20
+set ipk_dir=ipkg\
21
+set release_dir=release\
22
+set repo_dir=..\repo\
23
+set feed_dir=q:\web\repo\
24
+
25
+set AR=\MinGW\bin\ar.exe
26
+set TAR=\MinGW\msys\1.0\bin\tar.exe
27
+rem set ZIP=\Program Files (x86)\Gow\bin\zip.exe
28
+
29
+:=== data files ===
30
+if exist "%pack_name%" rm -r -f "%pack_name%"
31
+mkdir "%pack_name%""
32
+if not exist %release_dir% mkdir %release_dir%
33
+if not exist %repo_dir% mkdir %repo_dir%
34
+if not exist  %repo_dir%%pack_name% mkdir  %repo_dir%%pack_name%
35
+
36
+
37
+for %%f in (
38
+readme.md
39
+changelog.md
40
+addon.xml
41
+addon.py
42
+context_menu.py
43
+context_download.py
44
+downloadqueue.py
45
+service.py
46
+icon.png
47
+kodiswift\*.py
48
+resources\__init__.py
49
+resources\settings.xml
50
+resources\icon.png
51
+resources\language\English\*
52
+resources\lib\__init__.py
53
+resources\lib\content\__init__.py
54
+resources\lib\content\ContentSources.py
55
+resources\lib\content\playstreamproxy.py
56
+resources\lib\content\Downloader.py
57
+resources\lib\content\resolver.py
58
+resources\lib\content\util.py
59
+resources\lib\content\run.py
60
+resources\lib\content\file.py
61
+resources\lib\content\demjson.py
62
+resources\lib\content\ordereddict.py
63
+resources\lib\content\sources\__init__.py
64
+resources\lib\content\sources\SourceBase.py
65
+resources\lib\content\sources\cinemalive.py
66
+resources\lib\content\sources\config.py
67
+resources\lib\content\sources\euronews.py
68
+resources\lib\content\sources\filmix.py
69
+resources\lib\content\sources\filmon.py
70
+resources\lib\content\sources\iplayer.py
71
+resources\lib\content\sources\movieplace.py
72
+resources\lib\content\sources\ltc.py
73
+resources\lib\content\sources\mtgplay.py
74
+resources\lib\content\sources\play24.py
75
+resources\lib\content\sources\replay.py
76
+resources\lib\content\sources\lmt.py
77
+resources\lib\content\sources\serialguru.py
78
+resources\lib\content\sources\tvdom.py
79
+resources\lib\content\sources\ustvnow.py
80
+resources\lib\content\sources\viaplay.py
81
+resources\lib\content\sources\filmas.py
82
+resources\lib\content\sources\YouTubeVideoUrl.py
83
+resources\lib\content\sources\jsinterp.py
84
+resources\lib\content\sources\swfinterp.py
85
+resources\lib\content\sources\streams.cfg
86
+resources\lib\content\resolvers\__init__.py
87
+resources\lib\content\resolvers\aadecode.py
88
+resources\lib\content\resolvers\hqqresolver.py
89
+resources\lib\content\resolvers\openload3.py
90
+resources\lib\content\resolvers\hdgo.py
91
+resources\lib\content\resolvers\kapnob.py
92
+resources\lib\content\resolvers\kodik.py
93
+resources\lib\content\resolvers\cloudsany.py
94
+resources\lib\content\resolvers\youtuberesolver.py
95
+) do echo f| xcopy %%f %pack_name%\%%f
96
+
97
+xcopy /y /q resources\lib\content\picons\* %pack_name%\resources\picons\
98
+
99
+pause
100
+
101
+if exist  %release_dir%%pack_name%-%ver%.zip rm %release_dir%%pack_name%-%ver%.zip
102
+rem zip  -r %release_dir%%pack_name%-%ver%.zip %pack_name%
103
+"C:\Program Files\WinRAR\winrar.exe" a -afzip -r %release_dir%%pack_name%-%ver%.zip %pack_name%
104
+copy  addon.xml  %repo_dir%%pack_name%\addon.xml /Y
105
+copy  %release_dir%%pack_name%-%ver%.zip  %repo_dir%%pack_name%\%pack_name%-%ver%.zip /Y
106
+python -c "import hashlib; print hashlib.md5(open(r'%repo_dir%%pack_name%\%pack_name%-%ver%.zip','r').read()).hexdigest()" >%repo_dir%%pack_name%\%pack_name%-%ver%.zip.md5
107
+
108
+git add %release_dir%%pack_name%-%ver%.zip
109
+rm -r -f "%pack_name%"
110
+
111
+if not ()==(%1%) (
112
+    git commit -m %ver%
113
+    git tag -d "%ver%"
114
+    git tag %ver%
115
+    git push
116
+
117
+    pushd  %repo_dir%..
118
+    call update_repo.bat
119
+    rem xcopy /s /y /u repo %feed_dir%
120
+    robocopy repo %feed_dir%  addons.* /copy:dat
121
+    robocopy repo\%pack_name% q:\web\repo\%pack_name% /copy:DAT /xo
122
+    popd
123
+)
124
+pause

+ 2
- 0
link.bat View File

@@ -0,0 +1,2 @@
1
+set plugin=plugin.video.playstream
2
+mklink /j \users\user\AppData\Roaming\Kodi\addons\%plugin% %cd%

+ 2727
- 0
project.wpr
File diff suppressed because it is too large
View File


+ 204
- 0
queue_management.py View File

@@ -0,0 +1,204 @@
1
+# -*- coding: utf-8 -*-
2
+try:
3
+    import wingdbstub
4
+except:
5
+    pass
6
+
7
+import sys, os, urllib2, urllib, re, requests, datetime, time, json
8
+#CLI_MODE = True
9
+from kodiswift import xbmc, xbmcgui, xbmcplugin, CLI_MODE
10
+from kodiswift import Plugin, storage
11
+import pyxbmct.addonwindow as pyxbmct
12
+from downloadqueue import DownloadQueue
13
+import traceback, threading
14
+from resources.lib.content import file
15
+
16
+
17
+plugin = Plugin(addon_id="plugin.video.playstream")
18
+prefix = "plugin://plugin.video.playstream/"
19
+#plugin.load_addon_settings()
20
+timer = None
21
+timer_file = os.path.join(xbmc.translatePath("special://temp"), "timer_active") if not CLI_MODE else "timer_active"
22
+if os.path.exists(timer_file): os.remove(timer_file)
23
+sleep_time = 5  # TODO jāliek iekš parametriem
24
+
25
+#print "argv=",sys.argv
26
+handle = int(sys.argv[1])
27
+handle = 7777
28
+cmd = sys.argv[2]
29
+job_id = sys.argv[3] if len(sys.argv) > 3 else None
30
+
31
+queue_dir = os.path.join(xbmc.translatePath("special://temp"), "download_queue") if not CLI_MODE else "download_queue"
32
+download_queue = DownloadQueue(queue_dir)
33
+
34
+if not os.path.exists(queue_dir):
35
+    os.mkdir(queue_dir)
36
+
37
+
38
+def main():
39
+
40
+    if cmd == "list":
41
+        ret = downloads_list()
42
+    elif cmd == "view":
43
+        ret = downloads_view_job(job_id)
44
+    else:
45
+        ret = downloads_cmd(cmd, job_id)
46
+
47
+def refresh_container():
48
+    global timer
49
+    if os.path.exists(timer_file):
50
+        xbmc.executebuiltin('Container.Refresh')
51
+    else:
52
+        timer.stop()
53
+
54
+def downloads_list():
55
+
56
+    #plugin.finish(items, view_mode=get_view_mode("WideList"), update_listing=True, cache_to_disc=False)
57
+    #xbmcplugin.endOfDirectory(handle, succeeded=True, updateListing=True, cacheToDisc=False)
58
+
59
+    items = ["Item {0}".format(i) for i in xrange(1, 11)]
60
+    dialog = MultiChoiceDialog("Select items", [])
61
+    dialog.doModal()
62
+    xbmcgui.Dialog().notification("Finished", "Selected: {0}".format(str(dialog.selected)))
63
+    del dialog #You need to delete your instance when it is no longer needed
64
+
65
+
66
+    timer = threading.Timer(4.0, refresh_container)
67
+    #timer.start()
68
+    #open(timer_file, "w").write("started")
69
+
70
+def get_jobs():
71
+    global timer
72
+    jobs = download_queue.jobs()
73
+    items = []
74
+    i = 1
75
+    for job in jobs:
76
+        progress = float(job["currentbytes"])/float(job["totalbytes"])*100 if job["currentbytes"] and  job["totalbytes"] else ""
77
+        msg = "%.1f%% (%iMB/%iMB)"%(progress, job["currentbytes"] / 1024 / 1024, job["totalbytes"] / 1024 / 1024)
78
+        title = "%s - %s: %s" % (job["status"], msg, job["output"])
79
+        data2 = prefix + "downloads::view/%s" % job["job_id"]
80
+        is_playable = False
81
+        img = None
82
+        desc = "%s\n%s\n%s\n%s\n%s" % (job["output"], msg, job["job_id"], job["file"], job["url"])
83
+        #print title.encode("utf8"),data2,img
84
+        context_menu = [
85
+            ("Cancel download job", u'RunPlugin(plugin://plugin.video.playstream/downloads::cancel/%s'%(job["job_id"])),
86
+            ("Pause download job", u'RunPlugin(plugin://plugin.video.playstream/downloads::pause/%s'%(job["job_id"])),
87
+            ("Continue paused download job", u'RunPlugin(plugin://plugin.video.playstream/downloads::continue/%s'%(job["job_id"])),
88
+            ("Cancel all jobs", u'RunPlugin(plugin://plugin.video.playstream/downloads::cancel_all'),
89
+        ]
90
+
91
+        item2 = xbmcgui.ListItem(title, "", "", "", data2)
92
+        item2.setInfo("Video", {"title":title, "plot": desc,})
93
+        item2.addContextMenuItems(context_menu)
94
+        #item.setProperty("is_folder", "true")
95
+        items.append(item2)
96
+        #xbmcplugin.addDirectoryItem(handle=handle, url=data2, listitem=item2, isFolder=False)
97
+        i += 1
98
+    return items
99
+
100
+
101
+def downloads_view_job(job_id):
102
+    pass
103
+
104
+def downloads_cmd(cmd, params):
105
+    pass
106
+
107
+def get_view_mode(vm):
108
+    modes = {
109
+        "skin.estuary": {
110
+            "None": None,
111
+            "List": 50,
112
+            "Poster": 51,
113
+            "IconWall":52 ,
114
+            "Shift": 53,
115
+            "InfoWall": 54,
116
+            "WideList": 55,
117
+            "Wall": 500,
118
+            "Banner": 501,
119
+            "FanArt": 502
120
+        },
121
+        "skin.estuary.is": {
122
+            "None": None,
123
+            "List": 50,
124
+            "Poster": 51,
125
+            "IconWall":52 ,
126
+            "Shift": 53,
127
+            "InfoWall": 54,
128
+            "WideList": 55,
129
+            "Wall": 500,
130
+            "Banner": 501,
131
+            "FanArt": 502
132
+        },
133
+    }
134
+    skin = xbmc.getSkinDir()
135
+    if skin in modes and vm in modes[skin]:
136
+        view_mode = modes[skin][vm]
137
+    else:
138
+        view_mode = 50
139
+    return view_mode
140
+
141
+class MultiChoiceDialog(pyxbmct.AddonDialogWindow):
142
+    def __init__(self, title="", items=None):
143
+        super(MultiChoiceDialog, self).__init__(title)
144
+        self.setGeometry(1200, 760, 10, 10)
145
+        self.selected = []
146
+        self.set_controls()
147
+        self.connect_controls()
148
+        self.update_list()
149
+        self.set_navigation()
150
+
151
+    def onAction(self, action):
152
+        action_id = action.getId()
153
+        if action_id in (101, 117):
154
+            print "Context menu"
155
+
156
+    def update_list(self):
157
+        items = get_jobs()
158
+        self.listing.reset()
159
+        self.listing.addItems(items)
160
+
161
+    def set_controls(self):
162
+        self.listing = pyxbmct.List(_imageWidth=15)
163
+        self.placeControl(self.listing, 0, 0, rowspan=10, columnspan=9)
164
+        self.ok_button = pyxbmct.Button("OK")
165
+        self.placeControl(self.ok_button, 1, 9)
166
+        self.cancel_button = pyxbmct.Button("Cancel")
167
+        self.placeControl(self.cancel_button, 2, 9)
168
+
169
+    def connect_controls(self):
170
+        self.connect(self.listing, self.item_selected)
171
+        self.connect(self.ok_button, self.ok)
172
+        self.connect(self.cancel_button, self.close)
173
+        self.connect(117, self.context_menu)
174
+
175
+    def set_navigation(self):
176
+        self.listing.controlRight(self.ok_button)
177
+        self.listing.controlLeft(self.ok_button)
178
+        self.ok_button.setNavigation(self.listing, self.listing, self.cancel_button, self.cancel_button)
179
+        self.cancel_button.setNavigation(self.listing, self.listing, self.ok_button, self.ok_button)
180
+        if self.listing.size():
181
+            self.setFocus(self.listing)
182
+        else:
183
+            self.setFocus(self.cancel_button)
184
+
185
+    def context_menu(self):
186
+        list_item = self.listing.getSelectedItem()
187
+        print "aaaa"
188
+
189
+
190
+    def item_selected(self):
191
+        list_item = self.listing.getSelectedItem()
192
+        pass
193
+
194
+
195
+    def ok(self):
196
+        self.selected = self.listing.getSelectedItem()
197
+        super(MultiChoiceDialog, self).close()
198
+
199
+    def close(self):
200
+        self.selected = None
201
+        super(MultiChoiceDialog, self).close()
202
+
203
+if __name__ == '__main__':
204
+    main()

+ 36
- 0
readme.md View File

@@ -0,0 +1,36 @@
1
+PlayStream
2
+==========
3
+
4
+Kodi plugin to to play various online streams (mostly Latvian).
5
+Stream sources are in  subfolder "resources/lib/sources", and can be added/customized
6
+Currently implemented:
7
+- Main menu and manual stream definition in sources/streams.cfg file
8
+- **replay.lsm.lv** - Latvian TV live streams and archive (Latvian)
9
+- **skaties.lv** - skaties.lv streams (Latvian LNT, TV3, TV3+, TV6, TV2)
10
+- **lattelecom.tv(shortcut.lv)** - live, archive and video (Latvian, English, Russian), account required
11
+- **play24.lv** - RigaTV24 live and archive (Latvian)
12
+- **viaplay.lv** - video streams (partly working, only non-DRM - Latvian, Russian)
13
+- **LMT straume** - LMT video streams (Latvian)
14
+- **cinemalive.tv** - movies and series in Latvian (partly working)
15
+- **movieplace.lv** - movies and series in Latvian (partly working)
16
+- **dom.tv** - live and archive of Latvian and Russion TV channels, registration required
17
+- **BBC iPlayer** - BBC live and archive, UK IP address (VPN) required
18
+- **Euronews** - Euronews live and archive
19
+- **filmon.tv** - media portal content
20
+- **ustvnow.tv** - USA live TV (only SD), registration required
21
+- **filmix.net** - movies and series in Russian
22
+- **serialguru.ru** - movies and series in Russian (partly working)
23
+
24
+
25
+---
26
+Copyright (c) 2016 ivars777 (ivars777@gmail.com)
27
+Distributed under the GNU GPL v3. For full terms see http://www.gnu.org/licenses/gpl-3.0.en.html
28
+Used fragments of code from
29
+- enigma2-plugin-tv3play by Taapat (https://github.com/Taapat/enigma2-plugin-tv3play)
30
+- Kodi plugin script.module.stream.resolver (https://github.com/kodi-czsk/script.module.stream.resolver)
31
+- enigma2-plugin-youtube by Taapat (https://github.com/Taapat/enigma2-plugin-youtube), originally from https://github.com/rg3/youtube-dl/blob/master/youtube_dl
32
+
33
+
34
+
35
+
36
+

BIN
release/plugin.video.playstream-0.1.12.zip View File


BIN
release/plugin.video.playstream-0.1.15.zip View File


BIN
release/plugin.video.playstream-0.1.17.zip View File


BIN
release/plugin.video.playstream-0.1.18.zip View File


BIN
release/plugin.video.playstream-0.1.2.zip View File


BIN
release/plugin.video.playstream-0.1.20.zip View File


BIN
release/plugin.video.playstream-0.1.21.zip View File


BIN
release/plugin.video.playstream-0.1.22.zip View File


BIN
release/plugin.video.playstream-0.1.23.zip View File


BIN
release/plugin.video.playstream-0.1.24.zip View File


BIN
release/plugin.video.playstream-0.1.26.zip View File


BIN
release/plugin.video.playstream-0.1.27.zip View File


BIN
release/plugin.video.playstream-0.1.28.zip View File


BIN
release/plugin.video.playstream-0.1.29.zip View File


BIN
release/plugin.video.playstream-0.1.3.zip View File


BIN
release/plugin.video.playstream-0.1.30.zip View File


BIN
release/plugin.video.playstream-0.1.31.zip View File


BIN
release/plugin.video.playstream-0.1.32.zip View File


BIN
release/plugin.video.playstream-0.1.33.zip View File


BIN
release/plugin.video.playstream-0.1.34.zip View File


BIN
release/plugin.video.playstream-0.1.35.zip View File


BIN
release/plugin.video.playstream-0.1.36.zip View File


BIN
release/plugin.video.playstream-0.1.37.zip View File


BIN
release/plugin.video.playstream-0.1.38.zip View File


BIN
release/plugin.video.playstream-0.1.4.zip View File


BIN
release/plugin.video.playstream-0.1.40.zip View File


BIN
release/plugin.video.playstream-0.1.41.zip View File


BIN
release/plugin.video.playstream-0.1.42.zip View File


BIN
release/plugin.video.playstream-0.1.43.zip View File


BIN
release/plugin.video.playstream-0.1.44.zip View File


BIN
release/plugin.video.playstream-0.1.45.zip View File


BIN
release/plugin.video.playstream-0.1.46.zip View File


BIN
release/plugin.video.playstream-0.1.48.zip View File


BIN
release/plugin.video.playstream-0.1.49.zip View File


BIN
release/plugin.video.playstream-0.1.5.zip View File


BIN
release/plugin.video.playstream-0.1.50.zip View File


BIN
release/plugin.video.playstream-0.1.51.zip View File


BIN
release/plugin.video.playstream-0.1.52.zip View File


BIN
release/plugin.video.playstream-0.1.53.zip View File


BIN
release/plugin.video.playstream-0.1.54.zip View File


BIN
release/plugin.video.playstream-0.1.55.zip View File


BIN
release/plugin.video.playstream-0.1.56.zip View File


BIN
release/plugin.video.playstream-0.1.57.zip View File


BIN
release/plugin.video.playstream-0.1.58.zip View File


BIN
release/plugin.video.playstream-0.1.59.zip View File


BIN
release/plugin.video.playstream-0.1.6.zip View File


BIN
release/plugin.video.playstream-0.1.60.zip View File


BIN
release/plugin.video.playstream-0.1.61.zip View File


BIN
release/plugin.video.playstream-0.1.62.zip View File


BIN
release/plugin.video.playstream-0.1.63.zip View File


BIN
release/plugin.video.playstream-0.1.64.zip View File


BIN
release/plugin.video.playstream-0.1.65.zip View File


BIN
release/plugin.video.playstream-0.1.66.zip View File


BIN
release/plugin.video.playstream-0.1.67.zip View File


BIN
release/plugin.video.playstream-0.1.68.zip View File


BIN
release/plugin.video.playstream-0.1.7.zip View File


BIN
release/plugin.video.playstream-0.1.70.zip View File


BIN
release/plugin.video.playstream-0.1.71.zip View File


BIN
release/plugin.video.playstream-0.1.72.zip View File


BIN
release/plugin.video.playstream-0.1.73.zip View File


BIN
release/plugin.video.playstream-0.1.74.zip View File


BIN
release/plugin.video.playstream-0.1.75.zip View File


BIN
release/plugin.video.playstream-0.1.76.zip View File


BIN
release/plugin.video.playstream-0.1.77.zip View File


BIN
release/plugin.video.playstream-0.1.78.zip View File


BIN
release/plugin.video.playstream-0.1.79.zip View File


BIN
release/plugin.video.playstream-0.1.8.zip View File


BIN
release/plugin.video.playstream-0.1.80.zip View File


BIN
release/plugin.video.playstream-0.1.9.zip View File


+ 10
- 0
run.py View File

@@ -0,0 +1,10 @@
1
+#!C:\Python27\python2.7.exe
2
+# EASY-INSTALL-ENTRY-SCRIPT: 'xbmcswift2==0.3.0','console_scripts','xbmcswift2'
3
+__requires__ = 'xbmcswift2==0.3.0'
4
+import sys
5
+from pkg_resources import load_entry_point
6
+#import xbmcswift2
7
+
8
+if __name__ == '__main__':
9
+    sys.exit(load_entry_point('xbmcswift2==0.3.0', 'console_scripts', 'xbmcswift2')())
10
+    #xbmcswift2.Plugin.run()

+ 38
- 0
service.py View File

@@ -0,0 +1,38 @@
1
+# -*- coding: utf-8 -*-
2
+try:
3
+    import wingdbstub
4
+except:
5
+    pass
6
+import os,os.path,sys, urllib, traceback
7
+from kodiswift import Plugin, ListItem, storage
8
+from kodiswift import xbmc, xbmcgui, xbmcplugin, xbmcvfs, CLI_MODE
9
+#from resources.lib import ContentSources, util
10
+#sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),"resources","lib","sources"))
11
+from  resources.lib.content import playstreamproxy
12
+from downloadqueue import DownloadQueue
13
+
14
+#monitor = xbmc.Monitor()
15
+
16
+plugin = Plugin()
17
+plugin.load_addon_settings()
18
+autostart = plugin.get_setting("general_autostart", bool)
19
+
20
+queue_dir = os.path.join(xbmc.translatePath("special://temp"), "download_queue") if not CLI_MODE else "download_queue"
21
+download_queue = DownloadQueue(queue_dir)
22
+download_queue.clear()  # TODO restarte nepabeigtos uzdevumus varbut?
23
+
24
+if autostart:
25
+    xbmc.executebuiltin("RunAddon(plugin.video.playstream)")
26
+# view_mode = plugin.get_setting("view_mode", str)
27
+
28
+port = plugin.get_setting("general_port",int)
29
+start_proxy = plugin.get_setting("general_proxy",bool)
30
+host = "localhost"
31
+if start_proxy:
32
+    plugin.notify("Starting playstreamproxy","Info",10000, xbmcgui.NOTIFICATION_INFO)
33
+    try:
34
+        playstreamproxy.start(host, port)
35
+    except Exception as e:
36
+        xbmcgui.Dialog().ok("Error starting playstreamproxyserver",unicode(e))
37
+        #plugin.notify(unicode(e), "Error", 10000, xbmcgui.NOTIFICATION_ERROR)
38
+        traceback.print_exc()

+ 2
- 0
unlink.bat View File

@@ -0,0 +1,2 @@
1
+set plugin_name=plugin.video.playstream
2
+rmdir \users\user\AppData\Roaming\Kodi\addons\%plugin%

+ 320
- 0
wingdbstub.py View File

@@ -0,0 +1,320 @@
1
+#########################################################################
2
+""" wingdbstub.py    -- Debug stub for debuggifying Python programs
3
+
4
+Copyright (c) 1999-2001, Archaeopteryx Software, Inc.  All rights reserved.
5
+
6
+Written by Stephan R.A. Deibel and John P. Ehresman
7
+
8
+Usage:
9
+-----
10
+
11
+This is the file that Wing DB users copy into their python project 
12
+directory if they want to be able to debug programs that are launched
13
+outside of the IDE (e.g., CGI scripts, in response to a browser page
14
+load).
15
+
16
+To use this, edit the configuration values below to match your 
17
+Wing installation and requirements of your project.
18
+
19
+Then, add the following line to your code:
20
+
21
+  import wingdbstub
22
+
23
+Debugging will start immediately after this import statements.
24
+
25
+Next make sure that your IDE is running and that it's configured to accept
26
+connections from the host the debug program will be running on.
27
+
28
+Now, invoking your python file should run the code within the debugger.
29
+Note, however, that Wing will not stop in the code unless a breakpoint
30
+is set.
31
+
32
+If the debug process is started before the IDE, or is not listening
33
+at the time this module is imported then the program will run with
34
+debugging until an attach request is seen.  Attaching only works 
35
+if the .wingdebugpw file is present; see the manual for details.
36
+
37
+On win32, you either need to edit WINGHOME in this script or
38
+pass in an environment variable called WINGHOME that points to
39
+the Wing installation directory.
40
+
41
+"""
42
+#########################################################################
43
+
44
+import sys
45
+import os
46
+import imp
47
+
48
+
49
+#------------------------------------------------------------------------
50
+# Default configuration values:  Note that the named environment 
51
+# variables, if set, will override these settings.
52
+
53
+# Set this to 1 to disable all debugging; 0 to enable debugging
54
+# (WINGDB_DISABLED environment variable)
55
+kWingDebugDisabled = 0
56
+
57
+# Host:port of the IDE within which to debug: As configured in the IDE
58
+# with the Server Port preference
59
+# (WINGDB_HOSTPORT environment variable)
60
+kWingHostPort = 'localhost:50005'
61
+
62
+# Port on which to listen for connection requests, so that the
63
+# IDE can (re)attach to the debug process after it has started.
64
+# Set this to '-1' to disable listening for connection requests.
65
+# This is only used when the debug process is not attached to
66
+# an IDE or the IDE has dropped its connection. The configured
67
+# port can optionally be added to the IDE's Common Attach Hosts
68
+# preference. Note that a random port is used instead if this 
69
+# port is already in use!
70
+# (WINGDB_ATTACHPORT environment variable)
71
+kAttachPort = '50015'
72
+
73
+# Set this to a filename to log verbose information about the debugger
74
+# internals to a file.  If the file does not exist, it will be created
75
+# as long as its enclosing directory exists and is writeable.  Use 
76
+# "<stderr>" or "<stdout>".  Note that "<stderr>" may cause problems 
77
+# on win32 if the debug process is not running in a console.
78
+# (WINGDB_LOGFILE environment variable)
79
+kLogFile = None
80
+
81
+# Set to get a tremendous amount of logging from the debugger internals
82
+# (WINGDB_LOGVERYVERBOSE)
83
+kLogVeryVerbose = 0
84
+
85
+# Set this to 1 when debugging embedded scripts in an environment that
86
+# creates and reuses a Python instance across multiple script invocations:  
87
+# It turns off automatic detection of program quit so that the debug session
88
+# can span multiple script invocations.  When this is turned on, you may
89
+# need to call ProgramQuit() on the debugger object to shut down the
90
+# debugger cleanly when your application exits or discards the Python
91
+# instance.  If multiple Python instances are created in the same run,
92
+# only the first one will be able to debug unless it terminates debug
93
+# and the environment variable WINGDB_ACTIVE is unset before importing
94
+# this module in the second or later Python instance.  See the Wing
95
+# IDE manual for details.
96
+kEmbedded = 0
97
+
98
+# Path to search for the debug password file and the name of the file
99
+# to use.  The password file contains the encryption type and connect 
100
+# password for all connections to the IDE and must match the wingdebugpw
101
+# file in the profile dir used by the IDE.  Any entry of '$<winguserprofile>' 
102
+# is replaced by the wing user profile directory for the user that the 
103
+# current process is running as
104
+# (WINGDB_PWFILEPATH environment variable)
105
+kPWFilePath = [os.path.dirname(__file__), '$<winguserprofile>']
106
+kPWFileName = 'wingdebugpw'
107
+
108
+# Whether to exit if the debugger fails to run or to connect with an IDE
109
+# for whatever reason
110
+kExitOnFailure = 0
111
+
112
+#------------------------------------------------------------------------
113
+# Find Wing debugger installation location
114
+
115
+# Edit this to point to your Wing installation or set to None to use env WINGHOME
116
+# On OS X this must be set to name of the Wing application bundle
117
+# (for example, /Applications/WingIDE.app)
118
+WINGHOME = None
119
+
120
+if sys.hexversion >= 0x03000000:
121
+  def has_key(o, key):
122
+    return key in o
123
+else:
124
+  def has_key(o, key):
125
+    return o.has_key(key)
126
+    
127
+# Check environment:  Must have WINGHOME defined if still == None
128
+if WINGHOME == None:
129
+  if has_key(os.environ, 'WINGHOME'):
130
+    WINGHOME=os.environ['WINGHOME']
131
+  else:
132
+    sys.stdout.write("*******************************************************************\n")
133
+    sys.stdout.write("Error: Could not find Wing installation!  You must set WINGHOME or edit\n")
134
+    sys.stdout.write("wingdbstub.py where indicated to point it to the location where\n")
135
+    sys.stdout.write("Wing is installed.\n")
136
+    sys.exit(1)
137
+
138
+kPWFilePath.append(WINGHOME)
139
+
140
+# The user settings dir where per-user settings & patches are located.  Will be
141
+# set from environment if left as None
142
+kUserSettingsDir = None
143
+if kUserSettingsDir is None:
144
+  kUserSettingsDir = os.environ.get('WINGDB_USERSETTINGS')
145
+  
146
+def _FindActualWingHome(winghome):
147
+  """ Find the actual directory to use for winghome.  Needed on OS X
148
+  where the .app directory is the preferred dir to use for WINGHOME and
149
+  .app/Contents/MacOS is accepted for backward compatibility. """
150
+  
151
+  if sys.platform != 'darwin':
152
+    return winghome
153
+  
154
+  app_dir = None
155
+  if os.path.isdir(winghome):
156
+    if winghome.endswith('/'):
157
+      wo_slash = winghome[:-1]
158
+    else:
159
+      wo_slash = winghome
160
+      
161
+    if wo_slash.endswith('.app'):
162
+      app_dir = wo_slash
163
+    elif wo_slash.endswith('.app/Contents/MacOS'):
164
+      app_dir = wo_slash[:-len('/Contents/MacOS')]
165
+    
166
+  if app_dir and os.path.isdir(os.path.join(app_dir, 'Contents', 'Resources')):
167
+    return os.path.join(app_dir, 'Contents', 'Resources')
168
+  
169
+  return winghome
170
+  
171
+def _ImportWingdb(winghome, user_settings=None):
172
+  """ Find & import wingdb module. """
173
+  
174
+  try:
175
+    exec_dict = {}
176
+    execfile(os.path.join(winghome, 'bin', '_patchsupport.py'), exec_dict)
177
+    find_matching = exec_dict['FindMatching']
178
+    dir_list = find_matching('bin', winghome, user_settings)
179
+  except Exception:
180
+    dir_list = []
181
+  dir_list.extend([os.path.join(winghome, 'bin'), os.path.join(winghome, 'src')])
182
+  for path in dir_list:
183
+    try:
184
+      f, p, d = imp.find_module('wingdb', [path])
185
+      try:
186
+        return imp.load_module('wingdb', f, p, d)
187
+      finally:
188
+        if f is not None:
189
+          f.close()
190
+      break
191
+    except ImportError:
192
+      pass
193
+
194
+#------------------------------------------------------------------------
195
+# Set debugger if it hasn't been set -- this is to handle module reloading
196
+# In the reload case, the debugger variable will be set to something
197
+try:
198
+  debugger
199
+except NameError:
200
+  debugger = None
201
+  
202
+# Unset WINGDB_ACTIVE env if it was inherited from another process
203
+# XXX Would be better to be able to call getpid() on dbgtracer but can't access it yet
204
+if 'WINGDB_ACTIVE' in os.environ and os.environ['WINGDB_ACTIVE'] != str(os.getpid()):
205
+  del os.environ['WINGDB_ACTIVE']
206
+
207
+# Start debugging if not disabled and this module has never been imported
208
+# before
209
+if (not kWingDebugDisabled and debugger is None
210
+    and not has_key(os.environ, 'WINGDB_DISABLED') and 
211
+    not has_key(os.environ, 'WINGDB_ACTIVE')):
212
+
213
+  exit_on_fail = 0
214
+  
215
+  try:
216
+    # Obtain exit if fails value
217
+    exit_on_fail = os.environ.get('WINGDB_EXITONFAILURE', kExitOnFailure)
218
+    
219
+    # Obtain configuration for log file to use, if any
220
+    logfile = os.environ.get('WINGDB_LOGFILE', kLogFile)
221
+    if logfile == '-' or logfile == None or len(logfile.strip()) == 0:
222
+      logfile = None
223
+
224
+    very_verbose_log = os.environ.get('WINGDB_LOGVERYVERBOSE', kLogVeryVerbose)
225
+    if type(very_verbose_log) == type('') and very_verbose_log.strip() == '':
226
+      very_verbose_log = 0
227
+      
228
+    # Determine remote host/port where the IDE is running
229
+    hostport = os.environ.get('WINGDB_HOSTPORT', kWingHostPort)
230
+    colonpos = hostport.find(':')
231
+    host = hostport[:colonpos]
232
+    port = int(hostport[colonpos+1:])
233
+  
234
+    # Determine port to listen on locally for attach requests
235
+    attachport = int(os.environ.get('WINGDB_ATTACHPORT', kAttachPort))
236
+  
237
+    # Check if running embedded script
238
+    embedded = int(os.environ.get('WINGDB_EMBEDDED', kEmbedded))
239
+  
240
+    # Obtain debug password file search path
241
+    if has_key(os.environ, 'WINGDB_PWFILEPATH'):
242
+      pwfile_path = os.environ['WINGDB_PWFILEPATH'].split(os.pathsep)
243
+    else:
244
+      pwfile_path = kPWFilePath
245
+    
246
+    # Obtain debug password file name
247
+    if has_key(os.environ, 'WINGDB_PWFILENAME'):
248
+      pwfile_name = os.environ['WINGDB_PWFILENAME']
249
+    else:
250
+      pwfile_name = kPWFileName
251
+    
252
+    # Load wingdb.py
253
+    actual_winghome = _FindActualWingHome(WINGHOME)
254
+    wingdb = _ImportWingdb(actual_winghome, kUserSettingsDir)
255
+    if wingdb == None:
256
+      sys.stdout.write("*******************************************************************\n")
257
+      sys.stdout.write("Error: Cannot find wingdb.py in $(WINGHOME)/bin or $(WINGHOME)/src\n")
258
+      sys.stdout.write("Error: Please check the WINGHOME definition in wingdbstub.py\n")
259
+      sys.exit(2)
260
+    
261
+    # Find the netserver module and create an error stream
262
+    netserver = wingdb.FindNetServerModule(actual_winghome, kUserSettingsDir)
263
+    err = wingdb.CreateErrStream(netserver, logfile, very_verbose_log)
264
+    
265
+    # Start debugging
266
+    debugger = netserver.CNetworkServer(host, port, attachport, err, 
267
+                                        pwfile_path=pwfile_path,
268
+                                        pwfile_name=pwfile_name,
269
+                                        autoquit=not embedded)
270
+    debugger.StartDebug(stophere=0)
271
+    os.environ['WINGDB_ACTIVE'] = str(os.getpid())
272
+    if debugger.ChannelClosed():
273
+      raise ValueError('Not connected')
274
+    
275
+  except:
276
+    if exit_on_fail:
277
+      raise
278
+    else:
279
+      pass
280
+
281
+def Ensure(require_connection=1, require_debugger=1):
282
+  """ Ensure the debugger is started and attempt to connect to the IDE if
283
+  not already connected.  Will raise a ValueError if:
284
+  
285
+  * the require_connection arg is true and the debugger is unable to connect
286
+  * the require_debugger arg is true and the debugger cannot be loaded
287
+  
288
+  If SuspendDebug() has been called through the low-level API, calling
289
+  Ensure() resets the suspend count to zero and additional calls to
290
+  ResumeDebug() will be ignored until SuspendDebug() is called again.
291
+  
292
+  Note that a change to the host & port to connect to will only
293
+  be use if a new connection is made.
294
+  
295
+  """
296
+  
297
+  if debugger is None:
298
+    if require_debugger:
299
+      raise ValueError("No debugger")
300
+    return
301
+
302
+  hostport = os.environ.get('WINGDB_HOSTPORT', kWingHostPort)
303
+  colonpos = hostport.find(':')
304
+  host = hostport[:colonpos]
305
+  port = int(hostport[colonpos+1:])
306
+  
307
+  resumed = debugger.ResumeDebug()
308
+  while resumed > 0:
309
+    resumed = debugger.ResumeDebug()
310
+  
311
+  debugger.SetClientAddress((host, port))  
312
+  
313
+  if not debugger.DebugActive():
314
+    debugger.StartDebug()
315
+  elif debugger.ChannelClosed():
316
+    debugger.ConnectToClient()
317
+    
318
+  if require_connection and debugger.ChannelClosed():
319
+    raise ValueError('Not connected')
320
+