Ivars 6 年前
父节点
当前提交
1bba20563f
共有 100 个文件被更改,包括 0 次插入8429 次删除
  1. 0
    3
      .gitmodules
  2. 0
    279
      addon.py
  3. 0
    22
      addon.xml
  4. 0
    74
      changelog.md
  5. 0
    269
      context_download.py
  6. 0
    94
      context_menu.py
  7. 0
    69
      deploy.bat
  8. 0
    44
      download_service.py
  9. 0
    2
      get_version.py
  10. 二进制
      icon.png
  11. 0
    117
      kmake.bat
  12. 0
    83
      kodiswift/__init__.py
  13. 0
    37
      kodiswift/actions.py
  14. 0
    18
      kodiswift/cli/__init__.py
  15. 0
    216
      kodiswift/cli/app.py
  16. 0
    78
      kodiswift/cli/cli.py
  17. 0
    101
      kodiswift/cli/console.py
  18. 0
    192
      kodiswift/cli/create.py
  19. 0
    19
      kodiswift/cli/data/addon.py
  20. 0
    17
      kodiswift/cli/data/addon.xml
  21. 0
    1
      kodiswift/cli/data/resources/__init__.py
  22. 0
    26
      kodiswift/cli/data/resources/language/English/strings.po
  23. 0
    1
      kodiswift/cli/data/resources/lib/__init__.py
  24. 0
    147
      kodiswift/common.py
  25. 0
    76
      kodiswift/constants.py
  26. 0
    339
      kodiswift/listitem.py
  27. 0
    104
      kodiswift/logger.py
  28. 0
    1
      kodiswift/mockxbmc/__init__.py
  29. 0
    1835
      kodiswift/mockxbmc/polib.py
  30. 0
    37
      kodiswift/mockxbmc/utils.py
  31. 0
    97
      kodiswift/mockxbmc/xbmc.py
  32. 0
    70
      kodiswift/mockxbmc/xbmcaddon.py
  33. 0
    67
      kodiswift/mockxbmc/xbmcgui.py
  34. 0
    88
      kodiswift/mockxbmc/xbmcplugin.py
  35. 0
    25
      kodiswift/mockxbmc/xbmcvfs.py
  36. 0
    155
      kodiswift/module.py
  37. 0
    357
      kodiswift/plugin.py
  38. 0
    46
      kodiswift/request.py
  39. 0
    163
      kodiswift/storage.py
  40. 0
    212
      kodiswift/urls.py
  41. 0
    572
      kodiswift/xbmcmixin.py
  42. 0
    11
      make_links.bat
  43. 0
    2229
      project.wpr
  44. 0
    36
      readme.md
  45. 二进制
      release/plugin.video.playstream-0.1.12.zip
  46. 二进制
      release/plugin.video.playstream-0.1.15.zip
  47. 二进制
      release/plugin.video.playstream-0.1.17.zip
  48. 二进制
      release/plugin.video.playstream-0.1.18.zip
  49. 二进制
      release/plugin.video.playstream-0.1.2.zip
  50. 二进制
      release/plugin.video.playstream-0.1.20.zip
  51. 二进制
      release/plugin.video.playstream-0.1.21.zip
  52. 二进制
      release/plugin.video.playstream-0.1.22.zip
  53. 二进制
      release/plugin.video.playstream-0.1.23.zip
  54. 二进制
      release/plugin.video.playstream-0.1.24.zip
  55. 二进制
      release/plugin.video.playstream-0.1.26.zip
  56. 二进制
      release/plugin.video.playstream-0.1.27.zip
  57. 二进制
      release/plugin.video.playstream-0.1.28.zip
  58. 二进制
      release/plugin.video.playstream-0.1.29.zip
  59. 二进制
      release/plugin.video.playstream-0.1.3.zip
  60. 二进制
      release/plugin.video.playstream-0.1.30.zip
  61. 二进制
      release/plugin.video.playstream-0.1.31.zip
  62. 二进制
      release/plugin.video.playstream-0.1.32.zip
  63. 二进制
      release/plugin.video.playstream-0.1.33.zip
  64. 二进制
      release/plugin.video.playstream-0.1.34.zip
  65. 二进制
      release/plugin.video.playstream-0.1.35.zip
  66. 二进制
      release/plugin.video.playstream-0.1.36.zip
  67. 二进制
      release/plugin.video.playstream-0.1.37.zip
  68. 二进制
      release/plugin.video.playstream-0.1.38.zip
  69. 二进制
      release/plugin.video.playstream-0.1.4.zip
  70. 二进制
      release/plugin.video.playstream-0.1.40.zip
  71. 二进制
      release/plugin.video.playstream-0.1.41.zip
  72. 二进制
      release/plugin.video.playstream-0.1.42.zip
  73. 二进制
      release/plugin.video.playstream-0.1.43.zip
  74. 二进制
      release/plugin.video.playstream-0.1.44.zip
  75. 二进制
      release/plugin.video.playstream-0.1.45.zip
  76. 二进制
      release/plugin.video.playstream-0.1.46.zip
  77. 二进制
      release/plugin.video.playstream-0.1.48.zip
  78. 二进制
      release/plugin.video.playstream-0.1.49.zip
  79. 二进制
      release/plugin.video.playstream-0.1.5.zip
  80. 二进制
      release/plugin.video.playstream-0.1.50.zip
  81. 二进制
      release/plugin.video.playstream-0.1.51.zip
  82. 二进制
      release/plugin.video.playstream-0.1.53.zip
  83. 二进制
      release/plugin.video.playstream-0.1.54.zip
  84. 二进制
      release/plugin.video.playstream-0.1.55.zip
  85. 二进制
      release/plugin.video.playstream-0.1.56.zip
  86. 二进制
      release/plugin.video.playstream-0.1.57.zip
  87. 二进制
      release/plugin.video.playstream-0.1.58.zip
  88. 二进制
      release/plugin.video.playstream-0.1.59.zip
  89. 二进制
      release/plugin.video.playstream-0.1.6.zip
  90. 二进制
      release/plugin.video.playstream-0.1.60.zip
  91. 二进制
      release/plugin.video.playstream-0.1.61.zip
  92. 二进制
      release/plugin.video.playstream-0.1.62.zip
  93. 二进制
      release/plugin.video.playstream-0.1.63.zip
  94. 二进制
      release/plugin.video.playstream-0.1.64.zip
  95. 二进制
      release/plugin.video.playstream-0.1.65.zip
  96. 二进制
      release/plugin.video.playstream-0.1.66.zip
  97. 二进制
      release/plugin.video.playstream-0.1.67.zip
  98. 二进制
      release/plugin.video.playstream-0.1.68.zip
  99. 二进制
      release/plugin.video.playstream-0.1.7.zip
  100. 0
    0
      release/plugin.video.playstream-0.1.70.zip

+ 0
- 3
.gitmodules 查看文件

@@ -1,3 +0,0 @@
1
-[submodule "resources/lib/content"]
2
-	path = resources/lib/content
3
-	url = http://git.blue.lv/home/content.git

+ 0
- 279
addon.py 查看文件

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

+ 0
- 22
addon.xml 查看文件

@@ -1,22 +0,0 @@
1
-<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2
-<addon version="0.1.59" 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
-  </requires>
8
-  <extension point="xbmc.python.pluginsource" library="addon.py">
9
-    <provides>video</provides>
10
-  </extension>
11
-  <extension point="xbmc.service" library="service.py" />
12
-  <extension point="xbmc.addon.metadata">
13
-    <platform>all</platform>
14
-    <language></language>
15
-    <summary>Play online streams from various sites (mostly Latvian)</summary>
16
-    <description>Play online streams from various sites:
17
-    - replay.lv
18
-    - skaties.lv
19
-    - shortcut.lv
20
-    </description>
21
-  </extension>
22
-</addon>

+ 0
- 74
changelog.md 查看文件

@@ -1,74 +0,0 @@
1
-**0.1.59** (11.03.2018)
2
-- [bugfix] Slabots filmix
3
-
4
-**0.1.58** (11.03.2018)
5
-- [feature] PlayStream favoritiem sataisits Delete un Move
6
-- [change] uzlabota ltc informācija
7
-- [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)
8
-- [change] filmix rādā visus tulkojumus/strīmus (agrak tikai pirmo)
9
-
10
-**0.1.55** (18.02.2018)
11
-- [bugfix] salabots ltc (arhīvs, epg u.c.)
12
-- [bugfix] salabots filmix search
13
-- [change] replay, ltc tiešraidēs nosaukumā rāda epg
14
-
15
-**0.1.54** (17.01.2018)
16
-- [bugfix] salabots submodule
17
-
18
-**0.1.52** (17.01.2018)
19
-- [feature] PlayStream autostart opcija
20
-- [feature] View Mode opcija
21
-
22
-**0.1.50** (12.12.2017)
23
-- [change] Labojumi atbilstoši PlayStream 0.7o
24
-- [feature] Testa variants video lejupielādei
25
-
26
-**0.1.46** (14.10.2017)
27
-- [change] Labojumi atbilstoši PlayStream 0.7k
28
-
29
-**0.1.45** (05.10.2017)
30
-- [change] Salabots ltc, replay, cnemalive u.c.
31
-
32
-**0.1.44** (03.09.2017)
33
-- [change] Sakārtotas pikonas, u.c.
34
-
35
-**0.1.43** (03.09.2017)
36
-- [bugfix] Salaboti video avoti atbilstoši PlayStream 0.7e (lattelekom u.c.)
37
-- [change] pārstrukturets kods (video satura avoti atseviškā git modulī)
38
-- [change] opcija nelietot proxy problemātiskajiem strimiem (lattlecom.tv, viaplay u.c.), izskatās, ka Web serveris stradā par lēnu
39
-
40
-**0.1.41** (03.09.2017)
41
-- [bugfix] Salabots filmix (pamainīts direct url avots uz html5 + dekodēšana kods)
42
-
43
-**0.1.40** (27.08.2017
44
-- [change] Salabots TVDom atbilstoši jaunajam API (papildus parametri - reģions, valoda)
45
-- [bugfix] Salabots Euronews
46
-- [change] Nestrādājošie avoti noslēpti menu, kad tiks salaboti parādīsies atkal
47
-- [change] Sataisīts izmainītais Filmix.me
48
-
49
-**0.1.38** (11.04.2016):
50
-- [feature] options to set local proxy port (by default 8880), proxy url in order to use remote proxy
51
-
52
-**0.1.35** (10.04.2016):
53
-- [feature] option to save plugin status state between calls (necessary to lattelecom.tv archive)
54
-
55
-**0.1.34** (09.04.2016):
56
-- [feature] proxy hack to play full lattelecom.tv videos (more than 5-6 minutes), turn on proxy in settings and restart kodi
57
-
58
-**0.1.33** (01.04.2016):
59
-- [feature] LMT straume (without TV streams)
60
-
61
-**0.1.32** (28.03.2016):
62
-- [feature] sources configod files stored in user_data area (remains after update)
63
-- [feature] option to use persistent storage to store sources objects (including login status) between calls (exprerimental, not very useful currently)
64
-- [feature] proxy server to play encoded hls streams as well as hqq and some other resolved streams (exprerimental, not working correctly)
65
-- [bugfix] viaplay login
66
-
67
-**0.1.24** (23.03.2016):
68
-- [feature] subtitles, context menu etc added
69
-
70
-**0.1.5** (14.02.2016):
71
-- [feature] addon settings added
72
-
73
-**0.1.2** (12.02.2016):
74
-- initial exprerimental version

+ 0
- 269
context_download.py 查看文件

@@ -1,269 +0,0 @@
1
-import sys, os, urllib2, re, requests
2
-#CLI_MODE = True
3
-from kodiswift import xbmc, xbmcgui, CLI_MODE
4
-from kodiswift import Plugin, storage
5
-from resources.lib.content import util, ContentSources
6
-#from resources.lib.content import Downloader
7
-#from twisted.web import client
8
-#from twisted.internet import reactor, defer
9
-
10
-#plugin = Plugin()
11
-#plugin.load_addon_settings()
12
-#playlist = plugin.get_setting("general_playlist",str)
13
-#proxy_url = plugin.get_setting("general_proxy_url",str)
14
-
15
-cunicode = lambda s: s.decode("utf8") if isinstance(s, str) else s
16
-cstr = lambda s: s.encode("utf8") if isinstance(s, unicode) else s
17
-
18
-cmd = sys.argv[1]
19
-title = sys.argv[2]
20
-data = sys.argv[3]
21
-download_dir = sys.argv[4]
22
-
23
-cur_directory = os.path.dirname(__file__)
24
-sources_directory = os.path.join(cur_directory,"resources","lib", "content", "sources")
25
-sources = ContentSources.ContentSources(sources_directory)
26
-
27
-def main():
28
-    if not sources.is_video(data):
29
-        print "It is not video link"
30
-        notify("It is not video link")
31
-        sys.exit(1)
32
-
33
-    streams = sources.get_streams(data)
34
-
35
-    if not CLI_MODE:
36
-        ret = 0
37
-        #ret = xbmcgui.Dialog().select("Select stream",streams) # TODO
38
-    else:
39
-        ret = 0
40
-    stream = streams[ret]
41
-    #output =  stream["name"].replace("\\"," ").replace(":"," ").replace("|"," ")
42
-    output = re.sub("[\\/\n\r\t,:\?\|'~\.]","_",title)
43
-    if isinstance(output, str):
44
-        output = output.decode("utf8")
45
-
46
-    for sub in stream["subs"]:
47
-        suburl = sub["url"]
48
-        slang = "_" + sub["lang"] if sub["lang"] else ""
49
-        download_sub(suburl, output+slang)
50
-
51
-    if "nfo" in stream and stream["nfo"]:
52
-        nfofile = os.path.join(download_dir, output+".nfo")
53
-        with open(nfofile,"w") as f:
54
-            nfo_txt = util.nfo2xml(stream["nfo"])
55
-            f.write(nfo_txt)
56
-
57
-    download_video(stream["url"], os.path.join(download_dir, output), stream["headers"])
58
-
59
-    #d = Downloader.download_video(stream["url"], os.path.join(download_dir, output), stream["headers"])
60
-    #reactor.run()
61
-
62
-
63
-    #xbmcgui.Dialog().ok("Info","Start download")
64
-
65
-    #mode = "a" if os.path.exists("context_menu.log") else "w"
66
-    #with open("context_menu.log", mode) as f:
67
-    #    f.write("%s %s %s %s", sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4])
68
-
69
-
70
-def download_video(url,output,headers=None):
71
-    #output = stream["name"].replace("\\"," ").replace(":"," ").replace("|"," ")
72
-    if not headers:
73
-        headers = {"user-agent":"Enigma2"}
74
-    try:
75
-        h = get_header(url,headers=headers)
76
-        mtype = h.get("content-type")
77
-        ext,stream_type = get_ext(mtype)
78
-    except Exception as e:
79
-        ext,stream_type = (".ts","hls")
80
-    output = output+ext
81
-    if stream_type == "hls":
82
-        download_hls(url, output, headers=headers)
83
-    else:
84
-        download_file(url, output, headers=headers)
85
-
86
-
87
-def download_hls(url, title, download_dir="", headers=None, overwrite=True, limit=None):
88
-    UA = "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:51.0) Gecko/20100101 Firefox/51.0"
89
-    if not headers:
90
-        headers = {"User-Agent" : UA}
91
-    key = headers["key"] if "key" in headers else ""
92
-#    if not "User-Agent" in headers:
93
-#        headers["User-Agent"] = UA
94
-    tsname = os.path.join(download_dir,title)
95
-
96
-    notify("Download started - %s" % title)
97
-    print "Start download"
98
-    print url
99
-    try:
100
-        r = requests.get(url,headers=headers)
101
-    except Exception as e:
102
-        raise Exception("Cannot open manifsest file - %s"%url)
103
-    if not r.content.startswith("#EXTM3U"):
104
-        raise Exception("Not valid manifest file - %s" % url)
105
-    streams = re.findall(r"#EXT-X-STREAM-INF:.*?BANDWIDTH=(\d+).*?\n(.+?)$", r.content, re.IGNORECASE | re.MULTILINE)
106
-    i = 0
107
-    while streams:
108
-        if i > 4: break
109
-        sorted(streams, key=lambda item: int(item[0]), reverse=True)
110
-        base_url = "/".join(url.split("?")[0].split("/")[:-1])+"/"
111
-        url = streams[0][1]
112
-        if not url.startswith("http"):
113
-            url = base_url + url
114
-        print url
115
-        try:
116
-            r = requests.get(url, headers=headers)
117
-        except Exception as e:
118
-            raise Exception("Cannot open manifsest file - %s"%url)
119
-        i += 1
120
-        streams = re.findall(r"#EXT-X-STREAM-INF:.*?BANDWIDTH=(\d+).*?\n(.+?)$", r.content, re.IGNORECASE | re.MULTILINE)
121
-    ts_list = re.findall(r"#EXTINF:([\d\.]+),.*?\n(.+?)$", r.content, re.IGNORECASE | re.MULTILINE)
122
-    base_url = "/".join(url.split("/")[:-1])+"/"
123
-
124
-    if not len(ts_list):
125
-        raise Exception("Cannot read fragment list in  manifsest file - %s"%url)
126
-
127
-    ts_num = 0
128
-    type = "vod" if "#EXT-X-ENDLIST" in r.content else "live"
129
-    currentbytes = 0.0
130
-    totalbytes = -1
131
-    currenttime = 0.0
132
-    totaltime = sum(map(float,zip(*ts_list)[0]))
133
-    #ts_file = open(outputfile, "wb")
134
-
135
-    if isinstance(tsname, str):
136
-        tsname = tsname.decode("utf8")
137
-    tsfile = open(tsname,"wb")
138
-    for ts in ts_list:
139
-        url2 = ts[1]
140
-        #print "Downloading ", url2
141
-        #fname = os.path.join(download_dir,url2.split("/")[-1])
142
-        if not url2.startswith("http"):
143
-            url2 = base_url + url2
144
-
145
-        r = requests.get(url2, headers=headers, verify=False)
146
-        content = r.content
147
-        if key:
148
-            from Crypto.Cipher import AES
149
-            key2 = binascii.a2b_hex(key)
150
-            iv = content[:16]
151
-            d = AES.new(key2, AES.MODE_CBC, iv)
152
-            content = d.decrypt(content[16:])
153
-        #with open(fname,"wb") as f:
154
-            #f.write(content)
155
-        tsfile.write(content)
156
-
157
-        content_length = len(content)
158
-        currentbytes += content_length
159
-        currenttime += float(ts_list[ts_num][0])
160
-        totalbytes = currentbytes * totaltime / currenttime
161
-        ts_num += 1
162
-        #print "Fragment %s downloaded (%s)"%(self.ts_num,len(content))
163
-        progress = float(currentbytes)/float(totalbytes)*100
164
-        print "%.1f%% (%i/%i)"%(progress,currentbytes,totalbytes)
165
-
166
-        if type == "vod":
167
-            if ts_num >= len(ts_list) or (limit and currenttime>limit):
168
-                break
169
-        else:
170
-            if limit and currenttime>limit: # TODO
171
-                break
172
-
173
-    print "Finished"
174
-    notify("Download finished - %s" % title)
175
-
176
-    tsfile.close()
177
-
178
-
179
-def download_file(url, title, download_dir="", headers=None, overwrite=True, limit=None):
180
-    UA = "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:51.0) Gecko/20100101 Firefox/51.0"
181
-    if not headers:
182
-        headers = {"User-Agent" : UA}
183
-    key = headers["key"] if "key" in headers else ""
184
-#    if not "User-Agent" in headers:
185
-#        headers["User-Agent"] = UA
186
-    fname = os.path.join(download_dir,title)
187
-    if isinstance(fname, str):
188
-        fname = fname.decode("utf8")
189
-
190
-    notify("Download started - %s" % title)
191
-    print "Start download"
192
-    print url
193
-    try:
194
-        r = requests.get(url,headers=headers, stream=True)
195
-    except Exception as e:
196
-        raise Exception("Cannot open url - %s"%url)
197
-    currentbytes = 0.0
198
-    totalbytes = int(r.headers["content-length"])
199
-
200
-    with open(fname, 'wb') as fd:
201
-        for chunk in r.iter_content(chunk_size=1024*1024):
202
-            fd.write(chunk)
203
-            currentbytes += len(chunk)
204
-            progress = float(currentbytes)/float(totalbytes)*100
205
-            print "%.1f%% (%i/%i)"%(progress,currentbytes,totalbytes)
206
-
207
-    print "Finished"
208
-    notify("Download finished - %s" % title)
209
-
210
-def download_sub(suburl, output):
211
-    try:
212
-        subs = urllib2.urlopen(suburl).read()
213
-    except:
214
-        subs = None
215
-    if subs:
216
-        if ".xml" in suburl:
217
-            subs = util.ttaf2srt(subs)
218
-            subext = ".srt"
219
-        elif ".vtt" in suburl:
220
-            subext = ".vtt"
221
-        elif ".srt" in suburl:
222
-            subext = ".srt"
223
-        else:
224
-            subext = ""
225
-        if subext:
226
-            subfile = cunicode(os.path.join(download_dir, output+subext))
227
-            with open(subfile,"w") as f:
228
-                f.write(subs)
229
-    else:
230
-        print "\n Error downloading subtitle %s"%suburl
231
-
232
-
233
-
234
-def get_header(url,headers=None):
235
-    r = requests.head(url,headers=headers)
236
-    return r.headers
237
-
238
-def get_ext(mtype):
239
-    stype = "http"
240
-    if mtype in ("vnd.apple.mpegURL","application/x-mpegURL",'application/x-mpegurl',"application/vnd.apple.mpegurl"):
241
-        return ".ts","hls"
242
-    elif mtype in ("application/dash+xml"):
243
-        return ".ts","dash" # TODO dash stream type  could be different !
244
-    elif mtype in ("video/mp4"):
245
-        return ".mp4","http"
246
-    elif mtype in ("video/MP2T","video/mp2t"):
247
-        return ".ts","http"
248
-    elif mtype in ("video/x-flv"):
249
-        return ".flv","http"
250
-    elif mtype in ("video/quicktime"):
251
-        return ".mov","http"
252
-    elif mtype in ("video/x-msvideo"):
253
-        return ".avi","http"
254
-    elif mtype in ("video/x-ms-wmv"):
255
-        return ".wmv","http"
256
-    elif mtype in ("video/x-matroska"):
257
-        return ".mkv","http"
258
-    else:
259
-        return ".mp4","http"
260
-
261
-
262
-def notify(text, title="Info", time=10000):
263
-    if isinstance(text, unicode):
264
-        text = text.encode("utf8")
265
-    #xbmc.executebuiltin('Notification(Hello World,This is a simple example of notifications,5000,/script.hellow.world.png)')
266
-    xbmc.executebuiltin('Notification(%s, %s, %d, %s)'%("Info", text, time, xbmcgui.NOTIFICATION_INFO))
267
-
268
-if __name__ == '__main__':
269
-    main()

+ 0
- 94
context_menu.py 查看文件

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

+ 0
- 69
deploy.bat 查看文件

@@ -1,69 +0,0 @@
1
-@echo off
2
-if (abox)==(%1%) (
3
-	set TARGET=b:\sdcard\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
-service.py
16
-icon.png
17
-kodiswift\*.py
18
-resources\__init__.py
19
-resources\settings.xml
20
-resources\icon.png
21
-resources\language\English\*
22
-resources\lib\__init__.py
23
-resources\lib\content\__init__.py
24
-resources\lib\content\ContentSources.py
25
-resources\lib\content\playstreamproxy.py
26
-resources\lib\content\util.py
27
-resources\lib\content\run.py
28
-resources\lib\content\resolver.py
29
-resources\lib\content\demjson.py
30
-resources\lib\content\ordereddict.py
31
-resources\lib\content\sources\__init__.py
32
-resources\lib\content\sources\SourceBase.py
33
-resources\lib\content\sources\cinemalive.py
34
-resources\lib\content\sources\config.py
35
-resources\lib\content\sources\euronews.py
36
-resources\lib\content\sources\filmix.py
37
-resources\lib\content\sources\filmon.py
38
-resources\lib\content\sources\iplayer.py
39
-resources\lib\content\sources\movieplace.py
40
-resources\lib\content\sources\ltc.py
41
-resources\lib\content\sources\mtgplay.py
42
-resources\lib\content\sources\play24.py
43
-resources\lib\content\sources\replay.py
44
-resources\lib\content\sources\serialguru.py
45
-resources\lib\content\sources\tvdom.py
46
-resources\lib\content\sources\ustvnow.py
47
-resources\lib\content\sources\viaplay.py
48
-resources\lib\content\sources\lmt.py
49
-resources\lib\content\sources\filmas.py
50
-resources\lib\content\sources\YouTubeVideoUrl.py
51
-resources\lib\content\sources\jsinterp.py
52
-resources\lib\content\sources\swfinterp.py
53
-resources\lib\content\sources\streams.cfg
54
-resources\lib\content\resolvers\__init__.py
55
-resources\lib\content\resolvers\aadecode.py
56
-resources\lib\content\resolvers\hqqresolver.py
57
-resources\lib\content\resolvers\openload3.py
58
-resources\lib\content\resolvers\hdgo.py
59
-resources\lib\content\resolvers\kapnob.py
60
-resources\lib\content\resolvers\kodik.py
61
-resources\lib\content\resolvers\youtuberesolver.py
62
-resources\picons\*
63
-) do echo f | xcopy /y   %%f %TARGET%%%f
64
-
65
-xcopy /y /q resources\lib\content\picons\* %TARGET%resources\picons\
66
-
67
-rem xcopy /y /d addon_data\settings.xml "C:\Users\user\AppData\Roaming\Kodi\userdata\addon_data\plugin.video.playstream\settings.xml"
68
-
69
-pause

+ 0
- 44
download_service.py 查看文件

@@ -1,44 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-import os,os.path,sys, datetime, traceback
3
-from kodiswift import Plugin, ListItem, storage
4
-from kodiswift import xbmc, xbmcgui, xbmcplugin, xbmcvfs, CLI_MODE
5
-#from resources.lib import ContentSources, util
6
-#sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),"resources","lib","sources"))
7
-import resources.lib.Downloader as Downloader
8
-import sys,os, os.path, re
9
-import urlparse, requests
10
-from twisted.web import client
11
-from twisted.internet import reactor, defer, ssl, task
12
-
13
-#str(time.time())
14
-
15
-plugin = Plugin()
16
-plugin.load_addon_settings()
17
-download_dir = plugin.get_setting("general_download_dir",str)
18
-queue_dir = os.path.join(plugin.storage_path,"downloads)")
19
-if not os.path.exists(queue_dir):
20
-    os.mkdir(queue_dir)
21
-
22
-class DownloadQueue(object):
23
-    def __init__(self):
24
-        self.flist=[]
25
-        self.q=[]
26
-
27
-    def check_queue(self):
28
-        flist = os.listdir(queue_dir)
29
-        print "Checking queue"
30
-
31
-    def start_download(self):
32
-        pass
33
-
34
-    def download_ok(self):
35
-        pass
36
-
37
-    def download_err(self):
38
-        pass
39
-
40
-
41
-queue = DownloadQueue()
42
-loop = task.LoopingCall(queue.check_queue)
43
-loop.start(2)
44
-reactor.run()

+ 0
- 2
get_version.py 查看文件

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

二进制
icon.png 查看文件


+ 0
- 117
kmake.bat 查看文件

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

+ 0
- 83
kodiswift/__init__.py 查看文件

@@ -1,83 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-"""
3
-kodiswift
4
-----------
5
-
6
-A micro framework to enable rapid development of Kodi plugins.
7
-
8
-:copyright: (c) 2012 by Jonathan Beluch
9
-:license: GPLv3, see LICENSE for more details.
10
-"""
11
-from __future__ import absolute_import
12
-
13
-from types import ModuleType
14
-
15
-try:
16
-    import xbmc
17
-    import xbmcgui
18
-    import xbmcplugin
19
-    import xbmcaddon
20
-    import xbmcvfs
21
-
22
-    CLI_MODE = False
23
-except ImportError:
24
-    CLI_MODE = True
25
-    import sys
26
-    from kodiswift.logger import log
27
-
28
-    # Mock the Kodi modules
29
-    from kodiswift.mockxbmc import xbmc, xbmcgui, xbmcplugin, xbmcaddon, xbmcvfs
30
-
31
-    class _Module(ModuleType):
32
-        """A wrapper class for a module used to override __getattr__.
33
-        This class will behave normally for any existing module attributes.
34
-        For any attributes which do not exist in the wrapped module, a mock
35
-        function will be returned. This function will also return itself
36
-        enabling multiple mock function calls.
37
-        """
38
-
39
-        def __init__(self, wrapped=None):
40
-            self.wrapped = wrapped
41
-            if wrapped:
42
-                self.__dict__.update(wrapped.__dict__)
43
-
44
-        def __getattr__(self, name):
45
-            """Returns any existing attr for the wrapped module or returns a
46
-            mock function for anything else. Never raises an AttributeError.
47
-            """
48
-            try:
49
-                return getattr(self.wrapped, name)
50
-            except AttributeError:
51
-                # noinspection PyUnusedLocal
52
-                # pylint disable=unused-argument
53
-                def func(*args, **kwargs):
54
-                    """A mock function which returns itself, enabling chainable
55
-                    function calls.
56
-                    """
57
-                    log.warning('The %s method has not been implemented on '
58
-                                'the CLI. Your code might not work properly '
59
-                                'when calling it.', name)
60
-                    return self
61
-
62
-                return func
63
-
64
-    xbmc = _Module(xbmc)
65
-    xbmcgui = _Module(xbmcgui)
66
-    xbmcplugin = _Module(xbmcplugin)
67
-    xbmcaddon = _Module(xbmcaddon)
68
-    xbmcvfs = _Module(xbmcvfs)
69
-    for m in (xbmc, xbmcgui, xbmcplugin, xbmcaddon, xbmcvfs):
70
-        name = reversed(m.__name__.rsplit('.', 1)).next()
71
-        sys.modules[name] = m
72
-
73
-from kodiswift.storage import TimedStorage
74
-from kodiswift.request import Request
75
-from kodiswift.common import (kodi_url, clean_dict, pickle_dict, unpickle_args,
76
-                              unpickle_dict, download_page)
77
-from kodiswift.constants import SortMethod
78
-from kodiswift.listitem import ListItem
79
-from kodiswift.logger import setup_log
80
-from kodiswift.module import Module
81
-from kodiswift.urls import AmbiguousUrlException, NotFoundException, UrlRule
82
-from kodiswift.xbmcmixin import XBMCMixin
83
-from kodiswift.plugin import Plugin

+ 0
- 37
kodiswift/actions.py 查看文件

@@ -1,37 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-"""
3
-kodiswift.actions
4
-------------------
5
-
6
-This module contains wrapper functions for Kodi built-in functions.
7
-
8
-:copyright: (c) 2012 by Jonathan Beluch
9
-:license: GPLv3, see LICENSE for more details.
10
-"""
11
-
12
-__all__ = ['background', 'update_view']
13
-
14
-
15
-def background(url):
16
-    """This action will run an addon in the background for the provided URL.
17
-
18
-    See 'RunPlugin()' at
19
-    http://kodi.wiki/view/List_of_built-in_functions
20
-
21
-    Args:
22
-        url (str): Full path must be specified.
23
-            Does not work for folder plugins.
24
-
25
-    Returns:
26
-        str: String of the builtin command
27
-    """
28
-    return 'RunPlugin(%s)' % url
29
-
30
-
31
-def update_view(url):
32
-    """This action will update the current container view with provided url.
33
-
34
-    See 'Container.Update()' at
35
-    http://kodi.wiki/view/List_of_built-in_functions
36
-    """
37
-    return 'Container.Update(%s)' % url

+ 0
- 18
kodiswift/cli/__init__.py 查看文件

@@ -1,18 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-"""
3
-    kodiswift.cli
4
-    ----------------
5
-
6
-    This package contains modules that are used only when running kodiswift in
7
-    CLI mode. Nothing from this package should be called from addon code.
8
-
9
-    :copyright: (c) 2012 by Jonathan Beluch
10
-    :license: GPLv3, see LICENSE for more details.
11
-"""
12
-
13
-
14
-def Option(*args, **kwargs):
15
-    """Returns a tuple of args, kwargs passed to the function. Useful for
16
-    recording arguments for future function calls.
17
-    """
18
-    return args, kwargs

+ 0
- 216
kodiswift/cli/app.py 查看文件

@@ -1,216 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-"""
3
-kodiswift.cli.app
4
-----------------
5
-
6
-This package contains the code which runs plugins from the command line.
7
-
8
-:copyright: (c) 2012 by Jonathan Beluch
9
-:license: GPLv3, see LICENSE for more details.
10
-"""
11
-from __future__ import absolute_import
12
-
13
-import logging
14
-import os
15
-import sys
16
-from xml.etree import ElementTree as Et
17
-
18
-from kodiswift import Plugin, ListItem, logger
19
-from kodiswift.cli import Option
20
-from kodiswift.cli.console import (display_listitems, continue_or_quit,
21
-                                   get_user_choice)
22
-from kodiswift.common import Modes
23
-
24
-__all__ = ['get_addon_module_name', 'crawl', 'RunCommand', 'PluginManager',
25
-           'setup_options', 'patch_sysargv', 'patch_plugin', 'once',
26
-           'interactive']
27
-
28
-
29
-class RunCommand(object):
30
-    """A CLI command to run a plugin."""
31
-
32
-    command = 'run'
33
-    usage = '%prog run [once|interactive|crawl] [url]'
34
-    option_list = (
35
-        Option('-q', '--quiet', action='store_true',
36
-               help='set logging level to quiet'),
37
-        Option('-v', '--verbose', action='store_true',
38
-               help='set logging level to verbose'),
39
-    )
40
-
41
-    @staticmethod
42
-    def run(opts, args):
43
-        """The run method for the 'run' command. Executes a plugin from the
44
-        command line.
45
-        """
46
-        setup_options(opts)
47
-
48
-        mode = Modes.ONCE
49
-        if len(args) > 0 and hasattr(Modes, args[0].upper()):
50
-            _mode = args.pop(0).upper()
51
-            mode = getattr(Modes, _mode)
52
-
53
-        url = None
54
-        if len(args) > 0:
55
-            # A url was specified
56
-            url = args.pop(0)
57
-
58
-        plugin_mgr = PluginManager.load_plugin_from_addon_xml(mode, url)
59
-        plugin_mgr.run()
60
-
61
-
62
-def setup_options(opts):
63
-    """Takes any actions necessary based on command line options"""
64
-    if opts.quiet:
65
-        logger.log.setLevel(logging.WARNING)
66
-        logger.GLOBAL_LOG_LEVEL = logging.WARNING
67
-
68
-    if opts.verbose:
69
-        logger.log.setLevel(logging.DEBUG)
70
-        logger.GLOBAL_LOG_LEVEL = logging.DEBUG
71
-
72
-
73
-def get_addon_module_name(addon_xml_filename):
74
-    """Attempts to extract a module name for the given addon's addon.xml file.
75
-    Looks for the 'xbmc.python.pluginsource' extension node and returns the
76
-    addon's filename without the .py suffix.
77
-    """
78
-    try:
79
-        xml = Et.parse(addon_xml_filename).getroot()
80
-    except IOError:
81
-        sys.exit('Cannot find an addon.xml file in the current working '
82
-                 'directory. Please run this command from the root directory '
83
-                 'of an addon.')
84
-
85
-    try:
86
-        plugin_source = (ext for ext in xml.findall('extension') if
87
-                         ext.get('point') == 'xbmc.python.pluginsource').next()
88
-    except StopIteration:
89
-        sys.exit('ERROR, no pluginsource in addonxml')
90
-
91
-    return plugin_source.get('library').split('.')[0]
92
-
93
-
94
-class PluginManager(object):
95
-    """A class to handle running a plugin in CLI mode. Handles setup state
96
-    before calling plugin.run().
97
-    """
98
-
99
-    @classmethod
100
-    def load_plugin_from_addon_xml(cls, mode, url):
101
-        """Attempts to import a plugin's source code and find an instance of
102
-        :class:`~kodiswift.Plugin`. Returns an instance of PluginManager if
103
-        successful.
104
-        """
105
-        cwd = os.getcwd()
106
-        sys.path.insert(0, cwd)
107
-        module_name = get_addon_module_name(os.path.join(cwd, 'addon.xml'))
108
-        addon = __import__(module_name)
109
-
110
-        # Find the first instance of kodiswift.Plugin
111
-        try:
112
-            plugin = (attr_value for attr_value in vars(addon).values()
113
-                      if isinstance(attr_value, Plugin)).next()
114
-        except StopIteration:
115
-            sys.exit('Could not find a Plugin instance in %s.py' % module_name)
116
-
117
-        return cls(plugin, mode, url)
118
-
119
-    def __init__(self, plugin, mode, url):
120
-        self.plugin = plugin
121
-        self.mode = mode
122
-        self.url = url
123
-
124
-    def run(self):
125
-        """This method runs the the plugin in the appropriate mode parsed from
126
-        the command line options.
127
-        """
128
-        handle = 0
129
-        handlers = {
130
-            Modes.ONCE: once,
131
-            Modes.CRAWL: crawl,
132
-            Modes.INTERACTIVE: interactive,
133
-        }
134
-        handler = handlers[self.mode]
135
-        patch_sysargv(self.url or 'plugin://%s/' % self.plugin.id, handle)
136
-        return handler(self.plugin)
137
-
138
-
139
-def patch_sysargv(*args):
140
-    """Patches sys.argv with the provided args"""
141
-    sys.argv = args[:]
142
-
143
-
144
-def patch_plugin(plugin, path, handle=None):
145
-    """Patches a few attributes of a plugin instance to enable a new call to
146
-    plugin.run()
147
-    """
148
-    if handle is None:
149
-        handle = plugin.request.handle
150
-    patch_sysargv(path, handle)
151
-    plugin._end_of_directory = False
152
-
153
-
154
-def once(plugin, parent_stack=None):
155
-    """A run mode for the CLI that runs the plugin once and exits."""
156
-    plugin.clear_added_items()
157
-    items = plugin.run()
158
-
159
-    # if update_listing=True, we need to remove the last url from the parent
160
-    # stack
161
-    if parent_stack and plugin._update_listing:
162
-        del parent_stack[-1]
163
-
164
-    # if we have parent items, include the most recent in the display
165
-    if parent_stack:
166
-        items.insert(0, parent_stack[-1])
167
-
168
-    display_listitems(items, plugin.request.url)
169
-    return items
170
-
171
-
172
-def interactive(plugin):
173
-    """A run mode for the CLI that runs the plugin in a loop based on user
174
-    input.
175
-    """
176
-    items = [item for item in once(plugin) if not item.get_played()]
177
-    parent_stack = []  # Keep track of parents so we can have a '..' option
178
-
179
-    selected_item = get_user_choice(items)
180
-    while selected_item is not None:
181
-        if parent_stack and selected_item == parent_stack[-1]:
182
-            # User selected the parent item, remove from list
183
-            parent_stack.pop()
184
-        else:
185
-            # User selected non parent item, add current url to parent stack
186
-            parent_stack.append(ListItem.from_dict(label='..',
187
-                                                   path=plugin.request.url))
188
-        patch_plugin(plugin, selected_item.get_path())
189
-
190
-        items = [item for item in once(plugin, parent_stack=parent_stack)
191
-                 if not item.get_played()]
192
-        selected_item = get_user_choice(items)
193
-
194
-
195
-def crawl(plugin):
196
-    """Performs a breadth-first crawl of all possible routes from the
197
-    starting path. Will only visit a URL once, even if it is referenced
198
-    multiple times in a plugin. Requires user interaction in between each
199
-    fetch.
200
-    """
201
-    # TODO: use OrderedSet?
202
-    paths_visited = set()
203
-    paths_to_visit = set(item.get_path() for item in once(plugin))
204
-
205
-    while paths_to_visit and continue_or_quit():
206
-        path = paths_to_visit.pop()
207
-        paths_visited.add(path)
208
-
209
-        # Run the new listitem
210
-        patch_plugin(plugin, path)
211
-        new_paths = set(item.get_path() for item in once(plugin))
212
-
213
-        # Filter new items by checking against urls_visited and
214
-        # urls_tovisit
215
-        paths_to_visit.update(path for path in new_paths
216
-                              if path not in paths_visited)

+ 0
- 78
kodiswift/cli/cli.py 查看文件

@@ -1,78 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-"""
3
-kodiswift.cli.cli
4
-------------------
5
-
6
-The main entry point for the kodiswift console script. CLI commands can be
7
-registered in this module.
8
-
9
-:copyright: (c) 2012 by Jonathan Beluch
10
-:license: GPLv3, see LICENSE for more details.
11
-"""
12
-from __future__ import absolute_import
13
-
14
-import sys
15
-from optparse import OptionParser
16
-
17
-from kodiswift.cli.app import RunCommand
18
-from kodiswift.cli.create import CreateCommand
19
-
20
-# TODO: Make an ABC for Command
21
-COMMANDS = {
22
-    RunCommand.command: RunCommand,
23
-    CreateCommand.command: CreateCommand,
24
-}
25
-
26
-
27
-# TODO: Make this usage dynamic based on COMMANDS dict
28
-USAGE = """%prog <command>
29
-
30
-Commands:
31
-    create
32
-        Create a new plugin project.
33
-
34
-    run
35
-        Run an kodiswift plugin from the command line.
36
-
37
-Help:
38
-    To see options for a command, run `kodiswift <command> -h`
39
-"""
40
-
41
-
42
-def main():
43
-    """The entry point for the console script kodiswift.
44
-
45
-    The 'xbcmswift2' script is command bassed, so the second argument is always
46
-    the command to execute. Each command has its own parser options and usages.
47
-    If no command is provided or the -h flag is used without any other
48
-    commands, the general help message is shown.
49
-    """
50
-    parser = OptionParser()
51
-    if len(sys.argv) == 1:
52
-        parser.set_usage(USAGE)
53
-        parser.error('At least one command is required.')
54
-
55
-    # spy sys.argv[1] in order to use correct opts/args
56
-    command = sys.argv[1]
57
-
58
-    if command == '-h':
59
-        parser.set_usage(USAGE)
60
-        opts, args = parser.parse_args()
61
-
62
-    if command not in COMMANDS.keys():
63
-        parser.error('Invalid command')
64
-
65
-    # We have a proper command, set the usage and options list according to the
66
-    # specific command
67
-    manager = COMMANDS[command]
68
-    if hasattr(manager, 'option_list'):
69
-        for args, kwargs in manager.option_list:
70
-            parser.add_option(*args, **kwargs)
71
-    if hasattr(manager, 'usage'):
72
-        parser.set_usage(manager.usage)
73
-
74
-    opts, args = parser.parse_args()
75
-
76
-    # Since we are calling a specific comamnd's manager, we no longer need the
77
-    # actual command in sys.argv so we slice from position 1
78
-    manager.run(opts, args[1:])

+ 0
- 101
kodiswift/cli/console.py 查看文件

@@ -1,101 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-"""
3
-    kodiswift.cli.console
4
-    ----------------------
5
-
6
-    This module contains code to handle CLI interaction.
7
-
8
-    :copyright: (c) 2012 by Jonathan Beluch
9
-    :license: GPLv3, see LICENSE for more details.
10
-"""
11
-from __future__ import print_function, absolute_import
12
-
13
-
14
-def get_max_len(items):
15
-    """Returns the max of the lengths for the provided items"""
16
-    try:
17
-        return max(len(item) for item in items)
18
-    except ValueError:
19
-        return 0
20
-
21
-
22
-def display_listitems(items, url):
23
-    """Displays a list of items along with the index to enable a user
24
-    to select an item.
25
-
26
-    Args:
27
-        items (list[kodiswift.ListItem]):
28
-        url (str):
29
-    """
30
-    if len(items) == 2 and items[0].label == '..' and items[1].played:
31
-        display_video(items)
32
-    else:
33
-        label_width = get_max_len(item.label for item in items)
34
-        num_width = len(str(len(items)))
35
-        output = []
36
-        for i, item in enumerate(items):
37
-            output.append('[%s] %s (%s)' % (
38
-                str(i).rjust(num_width),
39
-                item.label.ljust(label_width),
40
-                item.path))
41
-
42
-        line_width = get_max_len(output)
43
-        output.append('-' * line_width)
44
-
45
-        header = [
46
-            '',
47
-            '=' * line_width,
48
-            'Current URL: %s' % url,
49
-            '-' * line_width,
50
-            '%s %s Path' % ('#'.center(num_width + 2),
51
-                            'Label'.ljust(label_width)),
52
-            '-' * line_width,
53
-        ]
54
-        print('\n'.join(header + output))
55
-
56
-
57
-def display_video(items):
58
-    """Prints a message for a playing video and displays the parent
59
-    listitem.
60
-    """
61
-    parent_item, played_item = items
62
-
63
-    title_line = 'Playing Media %s (%s)' % (played_item.label, played_item.path.encode("utf8"))
64
-    parent_line = '[0] %s (%s)' % (parent_item.label, parent_item.path)
65
-    line_width = get_max_len([title_line, parent_line])
66
-
67
-    output = [
68
-        '-' * line_width,
69
-        title_line,
70
-        '-' * line_width,
71
-        parent_line.encode("utf8") if isinstance(parent_line, unicode) else parent_line ,
72
-    ]
73
-    print('\n'.join(output))
74
-
75
-
76
-def get_user_choice(items):
77
-    """Returns the selected item from provided items or None if 'q' was
78
-    entered for quit.
79
-    """
80
-    choice = raw_input('Choose an item or "q" to quit: ')
81
-    while choice != 'q':
82
-        try:
83
-            item = items[int(choice)]
84
-            print()  # Blank line for readability between interactive views
85
-            return item
86
-        except ValueError:
87
-            # Passed something that couldn't be converted with int()
88
-            choice = raw_input('You entered a non-integer. Choice must be an'
89
-                               ' integer or "q": ')
90
-        except IndexError:
91
-            # Passed an integer that was out of range of the list of urls
92
-            choice = raw_input('You entered an invalid integer. Choice must '
93
-                               'be from above url list or "q": ')
94
-    return None
95
-
96
-
97
-def continue_or_quit():
98
-    """Prints an exit message and returns False if the user wants to
99
-    quit.
100
-    """
101
-    return raw_input('Enter to continue or "q" to quit') != 'q'

+ 0
- 192
kodiswift/cli/create.py 查看文件

@@ -1,192 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-"""
3
-kodiswift.cli.create
4
----------------------
5
-
6
-This module contains the code to initialize a new Kodi addon project.
7
-
8
-:copyright: (c) 2012 by Jonathan Beluch
9
-:license: GPLv3, see LICENSE for more details.
10
-"""
11
-from __future__ import print_function, absolute_import
12
-
13
-import os
14
-import readline
15
-import string
16
-from getpass import getpass
17
-from optparse import OptionParser
18
-from os import getcwd
19
-from shutil import copytree, ignore_patterns
20
-from xml.sax import saxutils
21
-
22
-
23
-class CreateCommand(object):
24
-    """A CLI command to initialize a new Kodi addon project."""
25
-    command = 'create'
26
-    usage = '%prog create'
27
-
28
-    # noinspection PyUnusedLocal
29
-    @staticmethod
30
-    def run(opts, args):
31
-        """Required run function for the 'create' CLI command."""
32
-        create_new_project()
33
-
34
-
35
-# Path to skeleton file templates dir
36
-SKEL = os.path.join(os.path.dirname(__file__), 'data')
37
-
38
-
39
-def error_msg(msg):
40
-    """A decorator that sets the error_message attribute of the decorated
41
-    function to the provided value.
42
-    """
43
-    def decorator(func):
44
-        """Sets the error_message attribute on the provided function"""
45
-        func.error_message = msg
46
-        return func
47
-    return decorator
48
-
49
-
50
-def parse_cli():
51
-    """Currently only one positional arg, create."""
52
-    parser = OptionParser()
53
-    return parser.parse_args()
54
-
55
-
56
-@error_msg('** Value must be non-blank.')
57
-def validate_nonblank(value):
58
-    """A callable that retunrs the value passed"""
59
-    return value
60
-
61
-
62
-@error_msg('** Value must contain only letters or underscores.')
63
-def validate_pluginid(value):
64
-    """Returns True if the provided value is a valid plugin id"""
65
-    valid = string.ascii_letters + string.digits + '.' + '_'
66
-    return all(c in valid for c in value)
67
-
68
-
69
-@error_msg('** The provided path must be an existing folder.')
70
-def validate_isfolder(value):
71
-    """Returns true if the provided path is an existing directory"""
72
-    return os.path.isdir(value)
73
-
74
-
75
-def get_valid_value(prompt, validator, default=None):
76
-    """Displays the provided prompt and gets input from the user. This behavior
77
-    loops indefinitely until the provided validator returns True for the user
78
-    input. If a default value is provided, it will be used only if the user
79
-    hits Enter and does not provide a value.
80
-
81
-    If the validator callable has an error_message attribute, it will be
82
-    displayed for an invalid value, otherwise a generic message is used.
83
-    """
84
-    ans = get_value(prompt, default)
85
-    while not validator(ans):
86
-        try:
87
-            print(validator.error_message)
88
-        except AttributeError:
89
-            print('Invalid value.')
90
-        ans = get_value(prompt, default)
91
-
92
-    return ans
93
-
94
-
95
-def get_value(prompt, default=None, hidden=False):
96
-    """Displays the provided prompt and returns the input from the user. If the
97
-    user hits Enter and there is a default value provided, the default is
98
-    returned.
99
-    """
100
-    _prompt = '%s : ' % prompt
101
-    if default:
102
-        _prompt = '%s [%s]: ' % (prompt, default)
103
-
104
-    if hidden:
105
-        ans = getpass(_prompt)
106
-    else:
107
-        ans = raw_input(_prompt)
108
-
109
-    # If user hit Enter and there is a default value
110
-    if not ans and default:
111
-        ans = default
112
-    return ans
113
-
114
-
115
-def update_file(filename, items):
116
-    """Edits the given file in place, replacing any instances of {key} with the
117
-    appropriate value from the provided items dict. If the given filename ends
118
-    with ".xml" values will be quoted and escaped for XML.
119
-    """
120
-    # TODO: Implement something in the templates to denote whether the value
121
-    # being replaced is an XML attribute or a value. Perhaps move to dyanmic
122
-    # XML tree building rather than string replacement.
123
-    should_escape = filename.endswith('addon.xml')
124
-
125
-    with open(filename, 'r') as inp:
126
-        text = inp.read()
127
-
128
-    for key, val in items.items():
129
-        if should_escape:
130
-            val = saxutils.quoteattr(val)
131
-        text = text.replace('{%s}' % key, val)
132
-    output = text
133
-
134
-    with open(filename, 'w') as out:
135
-        out.write(output)
136
-
137
-
138
-def create_new_project():
139
-    """Creates a new Kodi Plugin directory based on user input"""
140
-    readline.parse_and_bind('tab: complete')
141
-
142
-    print("""
143
-    kodiswift - A micro-framework for creating Kodi plugins.
144
-    xbmc@jonathanbeluch.com
145
-    --
146
-""")
147
-    print('I\'m going to ask you a few questions to get this project started.')
148
-
149
-    opts = {}
150
-
151
-    # Plugin Name
152
-    opts['plugin_name'] = get_valid_value(
153
-        'What is your plugin name?',
154
-        validate_nonblank
155
-    )
156
-
157
-    # Plugin ID
158
-    opts['plugin_id'] = get_valid_value(
159
-        'Enter your plugin id.',
160
-        validate_pluginid,
161
-        'plugin.video.%s' % (opts['plugin_name'].lower().replace(' ', ''))
162
-    )
163
-
164
-    # Parent Directory
165
-    opts['parent_dir'] = get_valid_value(
166
-        'Enter parent folder (where to create project)',
167
-        validate_isfolder,
168
-        getcwd()
169
-    )
170
-
171
-    # Parent Directory
172
-    opts['plugin_dir'] = os.path.join(opts['parent_dir'], opts['plugin_id'])
173
-    assert not os.path.isdir(opts['plugin_dir']), \
174
-        'A folder named %s already exists in %s.' % (opts['plugin_id'],
175
-                                                     opts['parent_dir'])
176
-
177
-    # Provider
178
-    opts['provider_name'] = get_valid_value(
179
-        'Enter provider name',
180
-        validate_nonblank,
181
-    )
182
-
183
-    # Create the project folder by copying over skel
184
-    copytree(SKEL, opts['plugin_dir'], ignore=ignore_patterns('*.pyc'))
185
-
186
-    # Walk through all the new files and fill in with out options
187
-    for root, _, files in os.walk(opts['plugin_dir']):
188
-        for filename in files:
189
-            update_file(os.path.join(root, filename), opts)
190
-
191
-    print('Projects successfully created in %s.' % opts['plugin_dir'])
192
-    print('Done.')

+ 0
- 19
kodiswift/cli/data/addon.py 查看文件

@@ -1,19 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-from kodiswift import Plugin
3
-
4
-
5
-plugin = Plugin()
6
-
7
-
8
-@plugin.route('/')
9
-def index():
10
-    item = {
11
-        'label': 'Hello Kodi!',
12
-        'path': 'http://example.com/video.mp4',
13
-        'is_playable': True
14
-    }
15
-    return [item]
16
-
17
-
18
-if __name__ == '__main__':
19
-    plugin.run()

+ 0
- 17
kodiswift/cli/data/addon.xml 查看文件

@@ -1,17 +0,0 @@
1
-<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2
-<addon id={plugin_id} name={plugin_name} version="0.0.1" provider-name={provider_name}>
3
-  <requires>
4
-    <import addon="xbmc.python" version="2.24.0" />
5
-    <import addon="script.module.kodiswift" version="0.0.1" />
6
-  </requires>
7
-  <extension point="xbmc.python.pluginsource" library="addon.py">
8
-    <provides>video</provides>
9
-  </extension>
10
-  <extension point="xbmc.addon.metadata">
11
-    <platform>all</platform>
12
-    <language>en</language>
13
-    <summary lang="en">Summary for {plugin_name}</summary>
14
-    <description lang="en">Description for {plugin_name}</description>
15
-    <disclaimer lang="en">Disclaimer for {plugin_name}</disclaimer>
16
-  </extension>
17
-</addon>

+ 0
- 1
kodiswift/cli/data/resources/__init__.py 查看文件

@@ -1 +0,0 @@
1
-# -*- coding: utf-8 -*-

+ 0
- 26
kodiswift/cli/data/resources/language/English/strings.po 查看文件

@@ -1,26 +0,0 @@
1
-# Kodi Media Center language file
2
-# Addon Name: {plugin_name}
3
-# Addon id: {plugin_id}
4
-# Addon Provider: {provider_name}
5
-msgid ""
6
-msgstr ""
7
-"Project-Id-Version: Kodi Addons\n"
8
-"Report-Msgid-Bugs-To: \n"
9
-"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
10
-"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
11
-"Last-Translator: \n"
12
-"Language-Team: \n"
13
-"MIME-Version: 1.0\n"
14
-"Content-Type: text/plain; charset=UTF-8\n"
15
-"Content-Transfer-Encoding: 8bit\n"
16
-"Language: en\n"
17
-"Plural-Forms: nplurals=2; plural=(n != 1);\n"
18
-
19
-# strings 30000 thru 30999 reserved for plugins and plugin settings
20
-# strings 31000 thru 31999 reserved for skins
21
-# strings 32000 thru 32999 reserved for scripts
22
-# strings 33000 thru 33999 reserved for common strings used in add-ons
23
-
24
-msgctxt "#33000"
25
-msgid "Hello Kodi"
26
-msgstr ""

+ 0
- 1
kodiswift/cli/data/resources/lib/__init__.py 查看文件

@@ -1 +0,0 @@
1
-# -*- coding: utf-8 -*-

+ 0
- 147
kodiswift/common.py 查看文件

@@ -1,147 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-"""
3
-kodiswift.common
4
------------------
5
-
6
-This module contains some common helpful functions.
7
-
8
-:copyright: (c) 2012 by Jonathan Beluch
9
-:license: GPLv3, see LICENSE for more details.
10
-"""
11
-from __future__ import absolute_import
12
-
13
-import urllib
14
-
15
-try:
16
-    import cPickle as pickle
17
-except ImportError:
18
-    import pickle
19
-
20
-__all__ = ['clean_dict', 'kodi_url', 'unpickle_args', 'pickle_dict',
21
-           'unpickle_dict', 'download_page', 'Modes']
22
-
23
-
24
-class Modes(object):
25
-    ONCE = 'ONCE'
26
-    CRAWL = 'CRAWL'
27
-    INTERACTIVE = 'INTERACTIVE'
28
-
29
-
30
-def kodi_url(url, **options):
31
-    """Appends key/val pairs to the end of a URL. Useful for passing arbitrary
32
-    HTTP headers to Kodi to be used when fetching a media resource, e.g.
33
-    cookies.
34
-
35
-    Args:
36
-        url (str):
37
-        **options (dict):
38
-
39
-    Returns:
40
-        str:
41
-    """
42
-    options = urllib.urlencode(options)
43
-    if options:
44
-        return url + '|' + options
45
-    return url
46
-
47
-
48
-def clean_dict(data):
49
-    """Remove keys with a value of None
50
-
51
-    Args:
52
-        data (dict):
53
-
54
-    Returns:
55
-        dict:
56
-    """
57
-    return dict((k, v) for k, v in data.items() if v is not None)
58
-
59
-
60
-def pickle_dict(items):
61
-    """Convert `items` values into pickled values.
62
-
63
-    Args:
64
-        items (dict): A dictionary
65
-
66
-    Returns:
67
-        dict: Values which aren't instances of basestring are pickled. Also,
68
-            a new key '_pickled' contains a comma separated list of keys
69
-            corresponding to the pickled values.
70
-    """
71
-    ret = {}
72
-    pickled_keys = []
73
-    for k, v in items.items():
74
-        if isinstance(v, basestring):
75
-            ret[k] = v
76
-        else:
77
-            pickled_keys.append(k)
78
-            ret[k] = pickle.dumps(v)
79
-    if pickled_keys:
80
-        ret['_pickled'] = ','.join(pickled_keys)
81
-    return ret
82
-
83
-
84
-def unpickle_args(items):
85
-    """Takes a dict and un-pickles values whose keys are found in a '_pickled'
86
-    key.
87
-
88
-    >>> unpickle_args({'_pickled': ['foo'], 'foo': ['I3%0A.']})
89
-    {'foo': 3}
90
-
91
-    Args:
92
-        items (dict): A pickled dictionary.
93
-
94
-    Returns:
95
-        dict: Dict with values un-pickled.
96
-    """
97
-    # Technically there can be more than one _pickled value. At this point
98
-    # we'll just use the first one
99
-    pickled = items.pop('_pickled', None)
100
-    if pickled is None:
101
-        return items
102
-
103
-    pickled_keys = pickled[0].split(',')
104
-    ret = {}
105
-    for k, v in items.items():
106
-        if k in pickled_keys:
107
-            ret[k] = [pickle.loads(val) for val in v]
108
-        else:
109
-            ret[k] = v
110
-    return ret
111
-
112
-
113
-def unpickle_dict(items):
114
-    """un-pickles a dictionary that was pickled with `pickle_dict`.
115
-
116
-    Args:
117
-        items (dict): A pickled dictionary.
118
-
119
-    Returns:
120
-        dict: An un-pickled dictionary.
121
-    """
122
-    pickled_keys = items.pop('_pickled', '').split(',')
123
-    ret = {}
124
-    for k, v in items.items():
125
-        if k in pickled_keys:
126
-            ret[k] = pickle.loads(v)
127
-        else:
128
-            ret[k] = v
129
-    return ret
130
-
131
-
132
-def download_page(url, data=None):
133
-    """Returns the response for the given url. The optional data argument is
134
-    passed directly to urlopen.
135
-
136
-    Args:
137
-        url (str): The URL to read.
138
-        data (Optional[any]): If given, a POST request will be made with
139
-            :param:`data` as the POST body.
140
-
141
-    Returns:
142
-        str: The results of requesting the URL.
143
-    """
144
-    conn = urllib.urlopen(url, data)
145
-    resp = conn.read()
146
-    conn.close()
147
-    return resp

+ 0
- 76
kodiswift/constants.py 查看文件

@@ -1,76 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-"""
3
-kodiswift.constants
4
---------------------
5
-
6
-This module contains some helpful constants which ease interaction
7
-with Kodi.
8
-
9
-:copyright: (c) 2012 by Jonathan Beluch
10
-:license: GPLv3, see LICENSE for more details.
11
-"""
12
-from __future__ import absolute_import
13
-
14
-from kodiswift import xbmcplugin
15
-
16
-__all__ = ['SortMethod']
17
-
18
-
19
-class SortMethod(object):
20
-    """Static class to hold all of the available sort methods. The prefix
21
-    of 'SORT_METHOD_' is stripped.
22
-
23
-    e.g. SORT_METHOD_TITLE becomes SortMethod.TITLE
24
-    """
25
-    ALBUM = xbmcplugin.SORT_METHOD_ALBUM
26
-    ALBUM_IGNORE_THE = xbmcplugin.SORT_METHOD_ALBUM_IGNORE_THE
27
-    ARTIST = xbmcplugin.SORT_METHOD_ARTIST
28
-    ARTIST_IGNORE_THE = xbmcplugin.SORT_METHOD_ARTIST_IGNORE_THE
29
-    BITRATE = xbmcplugin.SORT_METHOD_BITRATE
30
-    CHANNEL = xbmcplugin.SORT_METHOD_CHANNEL
31
-    COUNTRY = xbmcplugin.SORT_METHOD_COUNTRY
32
-    DATE = xbmcplugin.SORT_METHOD_DATE
33
-    DATEADDED = xbmcplugin.SORT_METHOD_DATEADDED
34
-    DATE_TAKEN = xbmcplugin.SORT_METHOD_DATE_TAKEN
35
-    DRIVE_TYPE = xbmcplugin.SORT_METHOD_DRIVE_TYPE
36
-    DURATION = xbmcplugin.SORT_METHOD_DURATION
37
-    EPISODE = xbmcplugin.SORT_METHOD_EPISODE
38
-    FILE = xbmcplugin.SORT_METHOD_FILE
39
-    FULLPATH = xbmcplugin.SORT_METHOD_FULLPATH
40
-    GENRE = xbmcplugin.SORT_METHOD_GENRE
41
-    LABEL = xbmcplugin.SORT_METHOD_LABEL
42
-    LABEL_IGNORE_FOLDERS = xbmcplugin.SORT_METHOD_LABEL_IGNORE_FOLDERS
43
-    LABEL_IGNORE_THE = xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE
44
-    LASTPLAYED = xbmcplugin.SORT_METHOD_LASTPLAYED
45
-    LISTENERS = xbmcplugin.SORT_METHOD_LISTENERS
46
-    MPAA_RATING = xbmcplugin.SORT_METHOD_MPAA_RATING
47
-    NONE = xbmcplugin.SORT_METHOD_NONE
48
-    PLAYCOUNT = xbmcplugin.SORT_METHOD_PLAYCOUNT
49
-    PLAYLIST_ORDER = xbmcplugin.SORT_METHOD_PLAYLIST_ORDER
50
-    PRODUCTIONCODE = xbmcplugin.SORT_METHOD_PRODUCTIONCODE
51
-    PROGRAM_COUNT = xbmcplugin.SORT_METHOD_PROGRAM_COUNT
52
-    SIZE = xbmcplugin.SORT_METHOD_SIZE
53
-    SONG_RATING = xbmcplugin.SORT_METHOD_SONG_RATING
54
-    STUDIO = xbmcplugin.SORT_METHOD_STUDIO
55
-    STUDIO_IGNORE_THE = xbmcplugin.SORT_METHOD_STUDIO_IGNORE_THE
56
-    TITLE = xbmcplugin.SORT_METHOD_TITLE
57
-    TITLE_IGNORE_THE = xbmcplugin.SORT_METHOD_TITLE_IGNORE_THE
58
-    TRACKNUM = xbmcplugin.SORT_METHOD_TRACKNUM
59
-    UNSORTED = xbmcplugin.SORT_METHOD_UNSORTED
60
-    VIDEO_RATING = xbmcplugin.SORT_METHOD_VIDEO_RATING
61
-    VIDEO_RUNTIME = xbmcplugin.SORT_METHOD_VIDEO_RUNTIME
62
-    VIDEO_SORT_TITLE = xbmcplugin.SORT_METHOD_VIDEO_SORT_TITLE
63
-    VIDEO_SORT_TITLE_IGNORE_THE = xbmcplugin.SORT_METHOD_VIDEO_SORT_TITLE_IGNORE_THE
64
-    VIDEO_TITLE = xbmcplugin.SORT_METHOD_VIDEO_TITLE
65
-    VIDEO_USER_RATING = xbmcplugin.SORT_METHOD_VIDEO_USER_RATING
66
-    VIDEO_YEAR = xbmcplugin.SORT_METHOD_VIDEO_YEAR
67
-
68
-    @classmethod
69
-    def from_string(cls, sort_method):
70
-        """Returns the sort method specified. sort_method is case insensitive.
71
-        Will raise an AttributeError if the provided sort_method does not
72
-        exist.
73
-
74
-        >>> SortMethod.from_string('title')
75
-        """
76
-        return getattr(cls, sort_method.upper())

+ 0
- 339
kodiswift/listitem.py 查看文件

@@ -1,339 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-"""
3
-kodiswift.listitem
4
-------------------
5
-
6
-This module contains the ListItem class, which acts as a wrapper
7
-for xbmcgui.ListItem.
8
-
9
-:copyright: (c) 2012 by Jonathan Beluch
10
-:license: GPLv3, see LICENSE for more details.
11
-"""
12
-from __future__ import absolute_import
13
-
14
-import warnings
15
-
16
-from kodiswift import xbmcgui
17
-
18
-__all__ = ['ListItem']
19
-
20
-
21
-class ListItem(object):
22
-    """A wrapper for the xbmcgui.ListItem class. The class keeps track
23
-    of any set properties that xbmcgui doesn't expose getters for.
24
-    """
25
-
26
-    def __init__(self, label=None, label2=None, icon=None, thumbnail=None,
27
-                 path=None):
28
-        """Defaults are an emtpy string since xbmcgui.ListItem will not
29
-        accept None.
30
-        """
31
-        self._listitem = xbmcgui.ListItem(label=label, label2=label2, path=path)
32
-
33
-        # The docs have the thumbnail property set as thumb
34
-        # http://mirrors.kodi.tv/docs/python-docs/16.x-jarvis/xbmcgui.html#ListItem-setArt
35
-        self._art = {'icon': icon, 'thumb': thumbnail}
36
-        self._icon = icon
37
-        self._path = path
38
-        self._thumbnail = thumbnail
39
-        self._context_menu_items = []
40
-        self._played = False
41
-        self._playable = False
42
-        self.is_folder = True
43
-
44
-    def get_context_menu_items(self):
45
-        """Returns the list of currently set context_menu items."""
46
-        return self._context_menu_items
47
-
48
-    def add_context_menu_items(self, items, replace_items=False):
49
-        """Adds context menu items. If replace_items is True all
50
-        previous context menu items will be removed.
51
-        """
52
-        for label, action in items:
53
-            assert isinstance(label, basestring)
54
-            assert isinstance(action, basestring)
55
-        if replace_items:
56
-            self._context_menu_items = []
57
-        self._context_menu_items.extend(items)
58
-        self._listitem.addContextMenuItems(items, replace_items)
59
-
60
-    @property
61
-    def label(self):
62
-        """
63
-        Returns:
64
-            str:
65
-        """
66
-        return self._listitem.getLabel()
67
-
68
-    @label.setter
69
-    def label(self, value):
70
-        """
71
-        Args:
72
-            value (str):
73
-        """
74
-        self._listitem.setLabel(value)
75
-
76
-    def get_label(self):
77
-        warnings.warn('get_label is deprecated, use label property',
78
-                      DeprecationWarning)
79
-        return self.label
80
-
81
-    def set_label(self, value):
82
-        warnings.warn('set_label is deprecated, use label property',
83
-                      DeprecationWarning)
84
-        return self._listitem.setLabel(value)
85
-
86
-    @property
87
-    def label2(self):
88
-        return self._listitem.getLabel2()
89
-
90
-    @label2.setter
91
-    def label2(self, value):
92
-        self._listitem.setLabel2(value)
93
-
94
-    def get_label2(self):
95
-        warnings.warn('get_label2 is deprecated, use label2 property',
96
-                      DeprecationWarning)
97
-        return self.label2
98
-
99
-    def set_label2(self, value):
100
-        warnings.warn('set_label2 is deprecated, use label2 property',
101
-                      DeprecationWarning)
102
-        return self._listitem.setLabel2(value)
103
-
104
-    @property
105
-    def selected(self):
106
-        return self._listitem.isSelected()
107
-
108
-    @selected.setter
109
-    def selected(self, value):
110
-        self._listitem.select(value)
111
-
112
-    def is_selected(self):
113
-        warnings.warn('is_selected is deprecated, use selected property',
114
-                      DeprecationWarning)
115
-        return self._listitem.isSelected()
116
-
117
-    def select(self, selected_status=True):
118
-        warnings.warn('select is deprecated, use selected property',
119
-                      DeprecationWarning)
120
-        return self._listitem.select(selected_status)
121
-
122
-    @property
123
-    def icon(self):
124
-        return self._art.get('icon')
125
-
126
-    @icon.setter
127
-    def icon(self, value):
128
-        self._art['icon'] = value
129
-        self._listitem.setArt(self._art)
130
-
131
-    def get_icon(self):
132
-        warnings.warn('get_icon is deprecated, use icon property',
133
-                      DeprecationWarning)
134
-        return self.icon
135
-
136
-    def set_icon(self, icon):
137
-        warnings.warn('set_icon is deprecated, use icon property',
138
-                      DeprecationWarning)
139
-        self.icon = icon
140
-        return self.icon
141
-
142
-    @property
143
-    def thumbnail(self):
144
-        return self._art.get('thumb')
145
-
146
-    @thumbnail.setter
147
-    def thumbnail(self, value):
148
-        self._art['thumb'] = value
149
-        self._listitem.setArt(self._art)
150
-
151
-    def get_thumbnail(self):
152
-        warnings.warn('get_thumbnail is deprecated, use thumbnail property',
153
-                      DeprecationWarning)
154
-        return self.thumbnail
155
-
156
-    def set_thumbnail(self, thumbnail):
157
-        warnings.warn('set_thumbnail is deprecated, use thumbnail property',
158
-                      DeprecationWarning)
159
-        self.thumbnail = thumbnail
160
-        return self.thumbnail
161
-
162
-    @property
163
-    def poster(self):
164
-        return self._art.get('poster')
165
-
166
-    @poster.setter
167
-    def poster(self, value):
168
-        self._art['poster'] = value
169
-        self._listitem.setArt(self._art)
170
-
171
-    @property
172
-    def path(self):
173
-        return self._path
174
-
175
-    @path.setter
176
-    def path(self, value):
177
-        self._path = value
178
-        self._listitem.setPath(value)
179
-
180
-    def get_path(self):
181
-        warnings.warn('get_path is deprecated, use path property',
182
-                      DeprecationWarning)
183
-        return self._path
184
-
185
-    def set_path(self, path):
186
-        warnings.warn('set_path is deprecated, use path property',
187
-                      DeprecationWarning)
188
-        self._path = path
189
-        return self._listitem.setPath(path)
190
-
191
-    @property
192
-    def playable(self):
193
-        return self._playable
194
-
195
-    @playable.setter
196
-    def playable(self, value):
197
-        self._playable = value
198
-        is_playable = 'true' if self._playable else 'false'
199
-        self.set_property('isPlayable', is_playable)
200
-
201
-    def get_is_playable(self):
202
-        warnings.warn('get_is_playable is deprecated, use playable property',
203
-                      DeprecationWarning)
204
-        return self._playable
205
-
206
-    def set_is_playable(self, is_playable):
207
-        warnings.warn('set_is_playable is deprecated, use playable property',
208
-                      DeprecationWarning)
209
-        self._playable = is_playable
210
-        value = 'false'
211
-        if is_playable:
212
-            value = 'true'
213
-        self.set_property('isPlayable', value)
214
-
215
-    @property
216
-    def played(self):
217
-        return self._played
218
-
219
-    @played.setter
220
-    def played(self, value):
221
-        self._played = value
222
-
223
-    def set_played(self, was_played):
224
-        """Sets the played status of the listitem.
225
-
226
-        Used to differentiate between a resolved video versus a playable item.
227
-        Has no effect on Kodi, it is strictly used for kodiswift.
228
-        """
229
-        warnings.warn('set_played is deprecated, use played property',
230
-                      DeprecationWarning)
231
-        self._played = was_played
232
-
233
-    def get_played(self):
234
-        warnings.warn('get_played is deprecated, use played property',
235
-                      DeprecationWarning)
236
-        return self._played
237
-
238
-    @property
239
-    def art(self):
240
-        return self._art
241
-
242
-    @art.setter
243
-    def art(self, value):
244
-        self._art = value
245
-        self._listitem.setArt(value)
246
-
247
-    def set_art(self, value):
248
-        self._art = value
249
-        self._listitem.setArt(value)
250
-
251
-    def set_info(self, info_type, info_labels):
252
-        """Sets the listitem's info"""
253
-        return self._listitem.setInfo(info_type, info_labels)
254
-
255
-    def get_property(self, key):
256
-        """Returns the property associated with the given key"""
257
-        return self._listitem.getProperty(key)
258
-
259
-    def set_property(self, key, value):
260
-        """Sets a property for the given key and value"""
261
-        return self._listitem.setProperty(key, value)
262
-
263
-    def add_stream_info(self, stream_type, stream_values):
264
-        """Adds stream details"""
265
-        return self._listitem.addStreamInfo(stream_type, stream_values)
266
-
267
-    def as_tuple(self):
268
-        """Returns a tuple of list item properties:
269
-            (path, the wrapped xbmcgui.ListItem, is_folder)
270
-        """
271
-        return self.path, self._listitem, self.is_folder
272
-
273
-    def as_xbmc_listitem(self):
274
-        """Returns the wrapped xbmcgui.ListItem"""
275
-        return self._listitem
276
-
277
-    @classmethod
278
-    def from_dict(cls, label=None, label2=None, icon=None, thumbnail=None,
279
-                  path=None, selected=None, info=None, properties=None,
280
-                  context_menu=None, replace_context_menu=False,
281
-                  is_playable=None, info_type='video', stream_info=None,
282
-                  **kwargs):
283
-        """A ListItem constructor for setting a lot of properties not
284
-        available in the regular __init__ method. Useful to collect all
285
-        the properties in a dict and then use the **dct to call this
286
-        method.
287
-        """
288
-        # TODO(Sinap): Should this just use **kwargs? or should art be a dict?
289
-        listitem = cls(label, label2, path=path)
290
-        listitem.art = {
291
-            'icon': icon,
292
-            'thumb': thumbnail,
293
-            'poster': kwargs.get('poster'),
294
-            'banner': kwargs.get('banner'),
295
-            'fanart': kwargs.get('fanart'),
296
-            'landscape': kwargs.get('landscape'),
297
-        }
298
-
299
-        if selected is not None:
300
-            listitem.selected = selected
301
-
302
-        if info:
303
-            listitem.set_info(info_type, info)
304
-
305
-        if is_playable:
306
-            listitem.playable = True
307
-            listitem.is_folder = False # III
308
-
309
-        if properties:
310
-            # Need to support existing tuples, but prefer to have a dict for
311
-            # properties.
312
-            if hasattr(properties, 'items'):
313
-                properties = properties.items()
314
-            for key, val in properties:
315
-                listitem.set_property(key, val)
316
-
317
-        if stream_info:
318
-            for stream_type, stream_values in stream_info.items():
319
-                listitem.add_stream_info(stream_type, stream_values)
320
-
321
-        if context_menu:
322
-            listitem.add_context_menu_items(context_menu, replace_context_menu)
323
-
324
-        return listitem
325
-
326
-    def __eq__(self, other):
327
-        if not isinstance(other, ListItem):
328
-            raise NotImplementedError
329
-        self_props = (self.label, self.label2, self.art, self.path,
330
-                      self.playable, self.selected, self.played,)
331
-        other_props = (other.label, other.label2, other.art, other.path,
332
-                       other.playable, other.selected, other.played,)
333
-        return self_props == other_props
334
-
335
-    def __str__(self):
336
-        return ('%s (%s)' % (self.label, self.path)).encode('utf-8')
337
-
338
-    def __repr__(self):
339
-        return ("<ListItem '%s'>" % self.label).encode('utf-8')

+ 0
- 104
kodiswift/logger.py 查看文件

@@ -1,104 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-"""
3
-kodiswift.log
4
---------------
5
-
6
-This module contains the kodiswift logger as well as a convenience
7
-method for creating new loggers.
8
-
9
-:copyright: (c) 2012 by Jonathan Beluch
10
-:license: GPLv3, see LICENSE for more details.
11
-"""
12
-from __future__ import absolute_import
13
-
14
-import logging
15
-
16
-from kodiswift import CLI_MODE
17
-
18
-__all__ = ['setup_log', 'GLOBAL_LOG_LEVEL', 'log']
19
-
20
-# TODO: Add logging to a file as well when on CLI with lowest threshold
21
-#       possible
22
-# fh = logging.FileHandler('log_filename.txt')
23
-# fh.setLevel(logging.DEBUG)
24
-# fh.setFormatter(formatter)
25
-# log.addHandler(fh)
26
-# TODO: Allow a global flag to set logging level when dealing with Kodi
27
-# TODO: Add -q and -v flags to CLI to quiet or enable more verbose logging
28
-
29
-
30
-class XBMCFilter(object):
31
-    """A logging filter that streams to STDOUT or to the xbmc log if
32
-    running inside Kodi.
33
-    """
34
-    python_to_xbmc = {
35
-        'DEBUG': 'LOGDEBUG',
36
-        'INFO': 'LOGNOTICE',
37
-        'WARNING': 'LOGWARNING',
38
-        'ERROR': 'LOGERROR',
39
-        'CRITICAL': 'LOGSEVERE',
40
-    }
41
-
42
-    xbmc_levels = {
43
-        'LOGDEBUG': 0,
44
-        'LOGINFO': 1,
45
-        'LOGNOTICE': 2,
46
-        'LOGWARNING': 3,
47
-        'LOGERROR': 4,
48
-        'LOGSEVERE': 5,
49
-        'LOGFATAL': 6,
50
-        'LOGNONE': 7,
51
-    }
52
-
53
-    def __init__(self, prefix):
54
-        self.prefix = prefix
55
-
56
-    def filter(self, record):
57
-        """Returns True for all records if running in the CLI, else returns
58
-        True.
59
-
60
-        When running inside Kodi it calls the xbmc.log() method and prevents
61
-        the message from being double printed to STDOUT.
62
-        """
63
-
64
-        # When running in Kodi, any logged statements will be double printed
65
-        # since we are calling xbmc.log() explicitly. Therefore we return False
66
-        # so every log message is filtered out and not printed again.
67
-        if CLI_MODE:
68
-            return True
69
-        else:
70
-            # Must not be imported until here because of import order issues
71
-            # when running in CLI
72
-            from kodiswift import xbmc
73
-            xbmc_level = XBMCFilter.xbmc_levels.get(
74
-                XBMCFilter.python_to_xbmc.get(record.levelname))
75
-            xbmc.log('%s%s' % (self.prefix, record.getMessage()), xbmc_level)
76
-            return False
77
-
78
-
79
-if CLI_MODE:
80
-    GLOBAL_LOG_LEVEL = logging.INFO
81
-else:
82
-    GLOBAL_LOG_LEVEL = logging.DEBUG
83
-
84
-
85
-def setup_log(name):
86
-    """Returns a logging instance for the provided name. The returned
87
-    object is an instance of logging.Logger. Logged messages will be
88
-    printed to stderr when running in the CLI, or forwarded to Kodi's
89
-    log when running in Kodi mode.
90
-    """
91
-    _log = logging.getLogger(name)
92
-    _log.setLevel(GLOBAL_LOG_LEVEL)
93
-    handler = logging.StreamHandler()
94
-    formatter = logging.Formatter(
95
-        '%(asctime)s - %(levelname)s - [%(name)s] %(message)s')
96
-    handler.setFormatter(formatter)
97
-    _log.addHandler(handler)
98
-    _log.addFilter(XBMCFilter('[%s] ' % name))
99
-    return _log
100
-
101
-
102
-# The kodiswift log
103
-# Plugin writers should use plugin.log instead.
104
-log = setup_log('kodiswift')

+ 0
- 1
kodiswift/mockxbmc/__init__.py 查看文件

@@ -1 +0,0 @@
1
-# -*- coding: utf-8 -*-

+ 0
- 1835
kodiswift/mockxbmc/polib.py
文件差异内容过多而无法显示
查看文件


+ 0
- 37
kodiswift/mockxbmc/utils.py 查看文件

@@ -1,37 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-from __future__ import absolute_import
3
-
4
-import os
5
-from xml.dom.minidom import parse
6
-
7
-import kodiswift.mockxbmc.polib as polib
8
-
9
-
10
-def load_addon_strings(addon, filename):
11
-    """This is not an official Kodi method, it is here to facilitate
12
-    mocking up the other methods when running outside of Kodi."""
13
-    def get_strings(fn):
14
-        if os.path.exists(filename):
15
-            po = polib.pofile(fn)
16
-            return {entry.msgctxt[1:]: entry.msgid for entry in po}
17
-        else:
18
-            fn = os.path.splitext(fn)[0] + '.xml'
19
-            xml = parse(fn)
20
-            strings = {tag.getAttribute('id'): tag.firstChild.data
21
-                       for tag in xml.getElementsByTagName('string')}
22
-            return strings
23
-    addon._strings = get_strings(filename)
24
-
25
-
26
-def get_addon_id(addon_xml):
27
-    """Parses an addon id from the given addon.xml filename."""
28
-    xml = parse(addon_xml)
29
-    addon_node = xml.getElementsByTagName('addon')[0]
30
-    return addon_node.getAttribute('id')
31
-
32
-
33
-def get_addon_name(addon_xml):
34
-    """Parses an addon name from the given addon.xml filename."""
35
-    xml = parse(addon_xml)
36
-    addon_node = xml.getElementsByTagName('addon')[0]
37
-    return addon_node.getAttribute('name')

+ 0
- 97
kodiswift/mockxbmc/xbmc.py 查看文件

@@ -1,97 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-from __future__ import print_function, absolute_import
3
-
4
-import errno
5
-import os
6
-import tempfile
7
-
8
-import kodiswift
9
-from kodiswift.cli.create import get_value
10
-
11
-TEMP_DIR = os.path.join(tempfile.gettempdir(), 'kodiswift_debug')
12
-kodiswift.log.info('Using temp directory %s', TEMP_DIR)
13
-
14
-
15
-def _create_dir(path):
16
-    """Creates necessary directories for the given path or does nothing
17
-    if the directories already exist.
18
-    """
19
-    try:
20
-        os.makedirs(path)
21
-    except OSError as exc:
22
-        if exc.errno == errno.EEXIST:
23
-            pass
24
-        else:
25
-            raise
26
-
27
-
28
-def log(msg, level=0):
29
-    levels = [
30
-        'LOGDEBUG',
31
-        'LOGINFO',
32
-        'LOGNOTICE',
33
-        'LOGWARNING',
34
-        'LOGERROR',
35
-        'LOGSEVERE',
36
-        'LOGFATAL',
37
-        'LOGNONE',
38
-    ]
39
-    print('%s - %s' % (levels[level], msg))
40
-
41
-
42
-# noinspection PyPep8Naming
43
-def translatePath(path):
44
-    """Creates folders in the OS's temp directory. Doesn't touch any
45
-    possible Kodi installation on the machine. Attempting to do as
46
-    little work as possible to enable this function to work seamlessly.
47
-    """
48
-    valid_dirs = ['xbmc', 'home', 'temp', 'masterprofile', 'profile',
49
-                  'subtitles', 'userdata', 'database', 'thumbnails',
50
-                  'recordings', 'screenshots', 'musicplaylists',
51
-                  'videoplaylists', 'cdrips', 'skin', ]
52
-
53
-    # TODO: Remove asserts
54
-    assert path.startswith('special://'), 'Not a valid special:// path.'
55
-    parts = path.split('/')[2:]
56
-    assert len(parts) > 1, 'Need at least a single root directory'
57
-    assert parts[0] in valid_dirs, '%s is not a valid root dir.' % parts[0]
58
-
59
-    # We don't want to swallow any potential IOErrors here, so only makedir for
60
-    # the root dir, the user is responsible for making any further child dirs
61
-    _create_dir(os.path.join(TEMP_DIR, parts[0]))
62
-
63
-    return os.path.join(TEMP_DIR, *parts)
64
-
65
-
66
-# noinspection PyPep8Naming
67
-class Keyboard(object):
68
-    def __init__(self, default='', heading='', hidden=False):
69
-        self._heading = heading
70
-        self._default = default
71
-        self._hidden = hidden
72
-        self._confirmed = False
73
-        self._input = None
74
-
75
-    def setDefault(self, default):
76
-        self._default = default
77
-
78
-    def setHeading(self, heading):
79
-        self._heading = heading
80
-
81
-    def setHiddenInput(self, hidden):
82
-        self._hidden = hidden
83
-
84
-    def doModal(self):
85
-        self._confirmed = False
86
-        try:
87
-            self._input = get_value(
88
-                self._heading, self._default, hidden=self._hidden)
89
-            self._confirmed = True
90
-        except(KeyboardInterrupt, EOFError):
91
-            pass
92
-
93
-    def isConfirmed(self):
94
-        return self._confirmed
95
-
96
-    def getText(self):
97
-        return self._input

+ 0
- 70
kodiswift/mockxbmc/xbmcaddon.py 查看文件

@@ -1,70 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-from __future__ import absolute_import
3
-
4
-import os
5
-
6
-from kodiswift.logger import log
7
-from kodiswift.mockxbmc import utils
8
-
9
-__all__ = ['Addon']
10
-
11
-
12
-def _get_env_setting(name):
13
-    return os.getenv('KODISWIFT_%s' % name.upper())
14
-
15
-
16
-# noinspection PyPep8Naming
17
-class Addon(object):
18
-    def __init__(self, id=None):
19
-        # In CLI mode, kodiswift must be run from the root of the addon
20
-        # directory, so we can rely on getcwd() being correct.
21
-        addon_xml = os.path.join(os.getcwd(), 'addon.xml')
22
-        _id = None
23
-        if os.path.exists(addon_xml):
24
-            _id = utils.get_addon_id(addon_xml)
25
-        self._info = {
26
-            'id': id or _id,
27
-            'name': utils.get_addon_name(addon_xml),
28
-            'profile': 'special://profile/addon_data/%s/' % _id,
29
-            'path': 'special://home/addons/%s' % _id
30
-        }
31
-        self._strings = {}
32
-        self._settings = {}
33
-        strings_fn = os.path.join(
34
-            os.getcwd(), 'resources', 'language', 'English', 'strings.po')
35
-        utils.load_addon_strings(self, strings_fn)
36
-
37
-    def getAddonInfo(self, prop):
38
-        properties = ['author', 'changelog', 'description', 'disclaimer',
39
-                      'fanart', 'icon', 'id', 'name', 'path', 'profile',
40
-                      'stars', 'summary', 'type', 'version']
41
-        if prop not in properties:
42
-            raise ValueError('%s is not a valid property.' % prop)
43
-        return self._info.get(prop, 'Unavailable')
44
-
45
-    def getLocalizedString(self, str_id):
46
-        key = str(str_id)
47
-        if key not in self._strings:
48
-            raise KeyError('id not found in English/strings.po or '
49
-                           'strings.xml.')
50
-        return self._strings[key]
51
-
52
-    def getSetting(self, key):
53
-        log.warning('xbmcaddon.Plugin.getSetting() has not been implemented '
54
-                    'in CLI mode.')
55
-        try:
56
-            value = self._settings[key]
57
-        except KeyError:
58
-            # see if we have an env var
59
-            value = _get_env_setting(key)
60
-            if _get_env_setting(key) is None:
61
-                value = raw_input(
62
-                    '* Please enter a temporary value for %s: ' % key)
63
-            self._settings[key] = value
64
-        return value
65
-
66
-    def setSetting(self, key, value):
67
-        self._settings[key] = value
68
-
69
-    def openSettings(self):
70
-        pass

+ 0
- 67
kodiswift/mockxbmc/xbmcgui.py 查看文件

@@ -1,67 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-
3
-
4
-# noinspection PyPep8Naming,PyUnusedLocal
5
-class ListItem(object):
6
-    def __init__(self, label=None, label2=None, iconImage=None,
7
-                 thumbnailImage=None, path=None):
8
-        self.label = label
9
-        self.label2 = label2
10
-        self.iconImage = iconImage
11
-        self.thumbnailImage = thumbnailImage
12
-        self.path = path
13
-        self.properties = {}
14
-        self.stream_info = {}
15
-        self.art = {}
16
-        self.selected = False
17
-        self.context_menu_items = None
18
-        self.infolabels = {}
19
-
20
-    def addContextMenuItems(self, items, replaceItems=False):
21
-        self.context_menu_items = items
22
-
23
-    def getLabel(self):
24
-        return self.label
25
-
26
-    def getLabel2(self):
27
-        return self.label2
28
-
29
-    def getProperty(self, key):
30
-        return self.properties[key.lower()]
31
-
32
-    def isSelected(self):
33
-        return self.selected
34
-
35
-    def select(self, selected):
36
-        self.selected = selected
37
-
38
-    def setIconImage(self, icon):
39
-        self.iconImage = icon
40
-
41
-    def setInfo(self, info_type, infoLabels):
42
-        #assert info_type in ['video', 'music', 'pictures']
43
-        self.infolabels.update(infoLabels)
44
-
45
-    def setLabel(self, label):
46
-        self.label = label
47
-
48
-    def setLabel2(self, label2):
49
-        self.label2 = label2
50
-
51
-    def setPath(self, path):
52
-        self.path = path
53
-
54
-    def setProperty(self, key, value):
55
-        self.properties[key.lower()] = value
56
-
57
-    def addStreamInfo(self, stream_type, stream_values):
58
-        self.stream_info.update({stream_type: stream_values})
59
-
60
-    def setThumbnailImage(self, thumb):
61
-        self.thumbnailImage = thumb
62
-
63
-    def setArt(self, values):
64
-        self.art = values
65
-
66
-class WindowXMLDialog:
67
-    pass

+ 0
- 88
kodiswift/mockxbmc/xbmcplugin.py 查看文件

@@ -1,88 +0,0 @@
1
-# coding: utf-8
2
-"""
3
-Functions for Kodi plugins
4
-"""
5
-import sys
6
-
7
-SORT_METHOD_ALBUM = 13
8
-SORT_METHOD_ALBUM_IGNORE_THE = 14
9
-SORT_METHOD_ARTIST = 11
10
-SORT_METHOD_ARTIST_IGNORE_THE = 12
11
-SORT_METHOD_BITRATE = 40
12
-SORT_METHOD_CHANNEL = 38
13
-SORT_METHOD_COUNTRY = 16
14
-SORT_METHOD_DATE = 3
15
-SORT_METHOD_DATEADDED = 19
16
-SORT_METHOD_DATE_TAKEN = 41
17
-SORT_METHOD_DRIVE_TYPE = 6
18
-SORT_METHOD_DURATION = 8
19
-SORT_METHOD_EPISODE = 22
20
-SORT_METHOD_FILE = 5
21
-SORT_METHOD_FULLPATH = 32
22
-SORT_METHOD_GENRE = 15
23
-SORT_METHOD_LABEL = 1
24
-SORT_METHOD_LABEL_IGNORE_FOLDERS = 33
25
-SORT_METHOD_LABEL_IGNORE_THE = 2
26
-SORT_METHOD_LASTPLAYED = 34
27
-SORT_METHOD_LISTENERS = 36
28
-SORT_METHOD_MPAA_RATING = 28
29
-SORT_METHOD_NONE = 0
30
-SORT_METHOD_PLAYCOUNT = 35
31
-SORT_METHOD_PLAYLIST_ORDER = 21
32
-SORT_METHOD_PRODUCTIONCODE = 26
33
-SORT_METHOD_PROGRAM_COUNT = 20
34
-SORT_METHOD_SIZE = 4
35
-SORT_METHOD_SONG_RATING = 27
36
-SORT_METHOD_STUDIO = 30
37
-SORT_METHOD_STUDIO_IGNORE_THE = 31
38
-SORT_METHOD_TITLE = 9
39
-SORT_METHOD_TITLE_IGNORE_THE = 10
40
-SORT_METHOD_TRACKNUM = 7
41
-SORT_METHOD_UNSORTED = 37
42
-SORT_METHOD_VIDEO_RATING = 18
43
-SORT_METHOD_VIDEO_RUNTIME = 29
44
-SORT_METHOD_VIDEO_SORT_TITLE = 24
45
-SORT_METHOD_VIDEO_SORT_TITLE_IGNORE_THE = 25
46
-SORT_METHOD_VIDEO_TITLE = 23
47
-SORT_METHOD_VIDEO_YEAR = 17
48
-
49
-def player(url, title = "", suburl= "",headers={}):
50
-    from subprocess import call
51
-    if not url:
52
-        return
53
-    cmd1 = [r"c:\Program Files\VideoLAN\VLC\vlc.exe",url,
54
-           "--meta-title",title.decode("utf8").encode(sys.getfilesystemencoding()),
55
-           "--http-user-agent","Enigma2"
56
-    ]
57
-    # gst-launch-1.0 -v souphttpsrc ssl-strict=false proxy=127.0.0.1:8888 extra-headers="Origin:adadadasd"  location="http://bitdash-a.akamaihd.net/content/sintel/sintel.mpd" ! decodebin! autovideosink
58
-    cmd2 = [
59
-        r"C:\gstreamer\1.0\x86_64\bin\gst-launch-1.0","-v",
60
-        "playbin", 'uri="%s"'%url,
61
-        #"souphttpsrc", "ssl-strict=false",
62
-        #"proxy=127.0.0.1:8888",
63
-        #'location="%s"'%url,
64
-        #'!decodebin!autovideosink'
65
-    ]
66
-    cmd3 = ["ffplay.exe",url]
67
-    cmd = cmd1 if url.startswith("https") else cmd2
68
-    ret = call(cmd3)
69
-    #if ret:
70
-        #a = raw_input("*** Error, continue")
71
-    return
72
-
73
-def setResolvedUrl(handle, succeeded, listitem):
74
-    """Callback function to tell XBMC that the file plugin has been resolved to a url
75
-
76
-    :param handle: integer - handle the plugin was started with.
77
-    :param succeeded: bool - True=script completed successfully/False=Script did not.
78
-    :param listitem: ListItem - item the file plugin resolved to for playback.
79
-
80
-    Example::
81
-
82
-        xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
83
-
84
-    """
85
-    url = listitem.path
86
-    title = listitem.label
87
-    player(url,title)
88
-    pass

+ 0
- 25
kodiswift/mockxbmc/xbmcvfs.py 查看文件

@@ -1,25 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-
3
-import os
4
-
5
-
6
-def exists(target):
7
-    return os.path.exists(target)
8
-
9
-
10
-def rename(origin, target):
11
-    return os.rename(origin, target)
12
-
13
-
14
-def delete(target):
15
-    if os.path.isfile(target) and not os.path.isdir(target):
16
-        return os.unlink(target)
17
-    return False
18
-
19
-
20
-def mkdir(target):
21
-    os.mkdir(target)
22
-
23
-
24
-def listdir(target):
25
-    return os.listdir(target)

+ 0
- 155
kodiswift/module.py 查看文件

@@ -1,155 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-"""
3
-kodiswift.module
4
------------------
5
-
6
-This module contains the Module Class.
7
-
8
-:copyright: (c) 2012 by Jonathan Beluch
9
-:license: GPLv3, see LICENSE for more details.
10
-"""
11
-from __future__ import absolute_import
12
-
13
-from kodiswift import setup_log
14
-from kodiswift.xbmcmixin import XBMCMixin
15
-
16
-__all__ = ['Module']
17
-
18
-
19
-class Module(XBMCMixin):
20
-    """Modules are basically mini plugins except they don't have any
21
-    functionality until they are registered with a Plugin.
22
-    """
23
-
24
-    def __init__(self, namespace):
25
-        # Get rid of package prefixes
26
-        self._namespace = namespace.split('.')[-1]
27
-        self._view_functions = {}
28
-        self._routes = []
29
-        self._register_funcs = []
30
-        self._plugin = None
31
-        self._url_prefix = None
32
-        self._log = setup_log(namespace)
33
-
34
-    @property
35
-    def plugin(self):
36
-        """Returns the plugin this module is registered to, or
37
-
38
-        Returns:
39
-            kodiswift.Plugin:
40
-
41
-        Raises:
42
-            RuntimeError: If not registered
43
-        """
44
-        if self._plugin is None:
45
-            raise RuntimeError('Module must be registered in order to call'
46
-                               'this method.')
47
-        return self._plugin
48
-
49
-    @plugin.setter
50
-    def plugin(self, value):
51
-        self._plugin = value
52
-
53
-    @property
54
-    def cache_path(self):
55
-        """Returns the module's cache_path."""
56
-        return self.plugin.storage_path
57
-
58
-    @property
59
-    def addon(self):
60
-        """Returns the module's addon"""
61
-        return self.plugin.addon
62
-
63
-    @property
64
-    def added_items(self):
65
-        """Returns this module's added_items"""
66
-        return self.plugin.added_items
67
-
68
-    @property
69
-    def handle(self):
70
-        """Returns this module's handle"""
71
-        return self.plugin.handle
72
-
73
-    @property
74
-    def request(self):
75
-        """Returns the current request"""
76
-        return self.plugin.request
77
-
78
-    @property
79
-    def log(self):
80
-        """Returns the registered plugin's log."""
81
-        return self._log
82
-
83
-    @property
84
-    def url_prefix(self):
85
-        """Sets or gets the url prefix of the module.
86
-
87
-        Raises an Exception if this module is not registered with a
88
-        Plugin.
89
-
90
-        Returns:
91
-            str:
92
-
93
-        Raises:
94
-            RuntimeError:
95
-        """
96
-        if self._url_prefix is None:
97
-            raise RuntimeError(
98
-                'Module must be registered in order to call this method.')
99
-        return self._url_prefix
100
-
101
-    @url_prefix.setter
102
-    def url_prefix(self, value):
103
-        self._url_prefix = value
104
-
105
-    @property
106
-    def register_funcs(self):
107
-        return self._register_funcs
108
-
109
-    def route(self, url_rule, name=None, options=None):
110
-        """A decorator to add a route to a view. name is used to
111
-        differentiate when there are multiple routes for a given view."""
112
-        def decorator(func):
113
-            """Adds a url rule for the provided function"""
114
-            view_name = name or func.__name__
115
-            self.add_url_rule(url_rule, func, name=view_name, options=options)
116
-            return func
117
-        return decorator
118
-
119
-    def url_for(self, endpoint, explicit=False, **items):
120
-        """Returns a valid Kodi plugin URL for the given endpoint name.
121
-        endpoint can be the literal name of a function, or it can
122
-        correspond to the name keyword arguments passed to the route
123
-        decorator.
124
-
125
-        Currently, view names must be unique across all plugins and
126
-        modules. There are not namespace prefixes for modules.
127
-        """
128
-        # TODO: Enable items to be passed with keywords of other var names
129
-        #       such as endpoing and explicit
130
-        # TODO: Figure out how to handle the case where a module wants to
131
-        #       call a parent plugin view.
132
-        if not explicit and not endpoint.startswith(self._namespace):
133
-            endpoint = '%s.%s' % (self._namespace, endpoint)
134
-        return self._plugin.url_for(endpoint, **items)
135
-
136
-    def add_url_rule(self, url_rule, view_func, name, options=None):
137
-        """This method adds a URL rule for routing purposes. The
138
-        provided name can be different from the view function name if
139
-        desired. The provided name is what is used in url_for to build
140
-        a URL.
141
-
142
-        The route decorator provides the same functionality.
143
-        """
144
-        name = '%s.%s' % (self._namespace, name)
145
-
146
-        def register_rule(plugin, url_prefix):
147
-            """Registers a url rule for the provided plugin and
148
-            url_prefix.
149
-            """
150
-            full_url_rule = url_prefix + url_rule
151
-            plugin.add_url_rule(full_url_rule, view_func, name, options)
152
-
153
-        # Delay actual registration of the url rule until this module is
154
-        # registered with a plugin
155
-        self._register_funcs.append(register_rule)

+ 0
- 357
kodiswift/plugin.py 查看文件

@@ -1,357 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-"""
3
-kodiswift.plugin
4
------------------
5
-
6
-This module contains the Plugin class. This class handles all of the url
7
-routing and interaction with Kodi for a plugin.
8
-
9
-:copyright: (c) 2012 by Jonathan Beluch
10
-:license: GPLv3, see LICENSE for more details.
11
-"""
12
-from __future__ import absolute_import
13
-
14
-import collections
15
-import inspect
16
-import os
17
-import sys
18
-
19
-import kodiswift
20
-from kodiswift import xbmc, xbmcaddon, Request
21
-from kodiswift.logger import log, setup_log
22
-from kodiswift.urls import UrlRule, NotFoundException, AmbiguousUrlException
23
-from kodiswift.xbmcmixin import XBMCMixin
24
-
25
-__all__ = ['Plugin']
26
-
27
-
28
-class Plugin(XBMCMixin):
29
-    """The Plugin objects encapsulates all the properties and methods necessary
30
-    for running an Kodi plugin. The plugin instance is a central place for
31
-    registering view functions and keeping track of plugin state.
32
-
33
-    Usually the plugin instance is created in the main plugin.py file for the
34
-    plugin. Typical creation looks like this::
35
-
36
-        >>> from kodiswift import Plugin
37
-        >>> plugin = Plugin('Hello Kodi')
38
-    """
39
-
40
-    def __init__(self, name=None, addon_id=None, plugin_file=None,
41
-                 info_type=None):
42
-        """
43
-        Args:
44
-            name (Optional[str]): The name of the plugin, e.g. 'Hello Kodi'.
45
-            addon_id (Optional[str): The Kodi addon ID for the plugin,
46
-                e.g. 'plugin.video.hellokodi'. This parameter is now optional
47
-                and is really only useful for testing purposes. If it is not
48
-                provided, the correct value will be parsed from the
49
-                addon.xml file.
50
-            plugin_file (Optional[str]): If provided, it should be the path
51
-                to the plugin.py file in the root of the addon directory.
52
-                This only has an effect when kodiswift is running on the
53
-                command line. Will default to the current working directory
54
-                since kodiswift requires execution in the root addon directory
55
-                anyway. The parameter still exists to ease testing.
56
-            info_type (Optional[str):
57
-        """
58
-        self._name = name
59
-        self._routes = []
60
-        self._view_functions = {}
61
-        self._addon = xbmcaddon.Addon(addon_id) if addon_id else xbmcaddon.Addon()
62
-
63
-        self._addon_id = addon_id or self._addon.getAddonInfo('id')
64
-        self._name = name or self._addon.getAddonInfo('name')
65
-
66
-        self._info_type = info_type
67
-        if not self._info_type:
68
-            types = {
69
-                'video': 'video',
70
-                'audio': 'music',
71
-                'image': 'pictures',
72
-            }
73
-            self._info_type = types.get(self._addon_id.split('.')[1], 'video')
74
-
75
-        # Keeps track of the added list items
76
-        self._current_items = []
77
-
78
-        # Gets initialized when self.run() is called
79
-        self._request = None
80
-
81
-        # A flag to keep track of a call to xbmcplugin.endOfDirectory()
82
-        self._end_of_directory = False
83
-
84
-        # Keep track of the update_listing flag passed to
85
-        # xbmcplugin.endOfDirectory()
86
-        self._update_listing = False
87
-
88
-        # The plugin's named logger
89
-        self._log = setup_log(self._addon_id)
90
-
91
-        # The path to the storage directory for the addon
92
-        self._storage_path = xbmc.translatePath(
93
-            'special://profile/addon_data/%s/.storage/' % self._addon_id)
94
-        if not os.path.isdir(self._storage_path):
95
-            os.makedirs(self._storage_path)
96
-
97
-        # If we are running in CLI, we need to load the strings.xml manually
98
-        # Since kodiswift currently relies on execution from an addon's root
99
-        # directly, we can rely on cwd for now...
100
-        if kodiswift.CLI_MODE:
101
-            from kodiswift.mockxbmc import utils
102
-            if plugin_file:
103
-                plugin_dir = os.path.dirname(plugin_file)
104
-            else:
105
-                plugin_dir = os.getcwd()
106
-            strings_fn = os.path.join(
107
-                plugin_dir, 'resources', 'language', 'English', 'strings.po')
108
-            utils.load_addon_strings(self._addon, strings_fn)
109
-            self.load_addon_settings()
110
-
111
-    @property
112
-    def info_type(self):
113
-        return self._info_type
114
-
115
-    @property
116
-    def log(self):
117
-        """The log instance for the plugin.
118
-
119
-        Returns an instance of the stdlib's ``logging.Logger``.
120
-        This log will print to STDOUT when running in CLI mode and will
121
-        forward messages to Kodi's log when running in Kodi.
122
-
123
-        Examples:
124
-            ``plugin.log.debug('Debug message')``
125
-            ``plugin.log.warning('Warning message')``
126
-            ``plugin.log.error('Error message')``
127
-
128
-        Returns:
129
-            logging.Logger:
130
-        """
131
-        return self._log
132
-
133
-    @property
134
-    def id(self):
135
-        """The id for the addon instance.
136
-        """
137
-        return self._addon_id
138
-
139
-    @property
140
-    def storage_path(self):
141
-        """A full path to the storage folder for this plugin's addon data.
142
-        """
143
-        return self._storage_path
144
-
145
-    @property
146
-    def addon(self):
147
-        """This addon's wrapped instance of xbmcaddon.Plugin.
148
-        """
149
-        return self._addon
150
-
151
-    @property
152
-    def added_items(self):
153
-        """The list of currently added items.
154
-
155
-        Even after repeated calls to :meth:`~kodiswift.Plugin.add_items`, this
156
-        property will contain the complete list of added items.
157
-        """
158
-        return self._current_items
159
-
160
-    @property
161
-    def handle(self):
162
-        """The current plugin's handle. Equal to ``plugin.request.handle``.
163
-        """
164
-        return self.request.handle
165
-
166
-    @property
167
-    def request(self):
168
-        """The current :class:`~kodiswift.Request`.
169
-
170
-        Raises:
171
-            Exception: if the request hasn't been initialized yet via
172
-                :meth:`~kodiswift.Plugin.run()`.
173
-
174
-        Returns:
175
-            kodiswift.Request:
176
-        """
177
-        if self._request is None:
178
-            raise Exception('It seems the current request has not been '
179
-                            'initialized yet. Please ensure that '
180
-                            '`plugin.run()` has been called before attempting '
181
-                            'to access the current request.')
182
-        return self._request
183
-
184
-    @property
185
-    def name(self):
186
-        """The addon's name.
187
-
188
-        Returns:
189
-            str:
190
-        """
191
-        return self._name
192
-
193
-    def clear_added_items(self):
194
-        self._current_items = []
195
-
196
-    def register_module(self, module, url_prefix):
197
-        """Registers a module with a plugin. Requires a url_prefix that will
198
-        then enable calls to url_for.
199
-
200
-        Args:
201
-            module (kodiswift.Module):
202
-            url_prefix (str): A url prefix to use for all module urls,
203
-                e.g. '/mymodule'
204
-        """
205
-        module.plugin = self
206
-        module.url_prefix = url_prefix
207
-        for func in module.register_funcs:
208
-            func(self, url_prefix)
209
-
210
-    def cached_route(self, url_rule, name=None, options=None, ttl=None):
211
-        """A decorator to add a route to a view and also apply caching. The
212
-        url_rule, name and options arguments are the same arguments for the
213
-        route function. The TTL argument if given will passed along to the
214
-        caching decorator.
215
-        """
216
-        route_decorator = self.route(url_rule, name=name, options=options)
217
-        if ttl:
218
-            cache_decorator = self.cached(ttl)
219
-        else:
220
-            cache_decorator = self.cached()
221
-
222
-        def new_decorator(func):
223
-            return route_decorator(cache_decorator(func))
224
-
225
-        return new_decorator
226
-
227
-    def route(self, url_rule=None, name=None, root=False, options=None):
228
-        """A decorator to add a route to a view. name is used to
229
-        differentiate when there are multiple routes for a given view."""
230
-
231
-        def decorator(f):
232
-            view_name = name or f.__name__
233
-            if root:
234
-                url = '/'
235
-            elif not url_rule:
236
-                url = '/' + view_name + '/'
237
-                args = inspect.getargspec(f)[0]
238
-                if args:
239
-                    url += '/'.join('%s/<%s>' % (p, p) for p in args)
240
-            else:
241
-                url = url_rule
242
-            self.add_url_rule(url, f, name=view_name, options=options)
243
-            return f
244
-
245
-        return decorator
246
-
247
-    def add_url_rule(self, url_rule, view_func, name, options=None):
248
-        """This method adds a URL rule for routing purposes. The
249
-        provided name can be different from the view function name if
250
-        desired. The provided name is what is used in url_for to build
251
-        a URL.
252
-
253
-        The route decorator provides the same functionality.
254
-        """
255
-        rule = UrlRule(url_rule, view_func, name, options)
256
-        if name in self._view_functions.keys():
257
-            # TODO: Raise exception for ambiguous views during registration
258
-            log.warning('Cannot add url rule "%s" with name "%s". There is '
259
-                        'already a view with that name', url_rule, name)
260
-            self._view_functions[name] = None
261
-        else:
262
-            log.debug('Adding url rule "%s" named "%s" pointing to function '
263
-                      '"%s"', url_rule, name, view_func.__name__)
264
-            self._view_functions[name] = rule
265
-        self._routes.append(rule)
266
-
267
-    def url_for(self, endpoint, **items):
268
-        """Returns a valid Kodi plugin URL for the given endpoint name.
269
-        endpoint can be the literal name of a function, or it can
270
-        correspond to the name keyword arguments passed to the route
271
-        decorator.
272
-
273
-        Raises AmbiguousUrlException if there is more than one possible
274
-        view for the given endpoint name.
275
-        """
276
-        try:
277
-            rule = self._view_functions[endpoint]
278
-        except KeyError:
279
-            try:
280
-                rule = (rule for rule in self._view_functions.values()
281
-                        if rule.view_func == endpoint).next()
282
-            except StopIteration:
283
-                raise NotFoundException(
284
-                    '%s does not match any known patterns.' % endpoint)
285
-
286
-        # rule can be None since values of None are allowed in the
287
-        # _view_functions dict. This signifies more than one view function is
288
-        # tied to the same name.
289
-        if not rule:
290
-            # TODO: Make this a regular exception
291
-            raise AmbiguousUrlException
292
-
293
-        path_qs = rule.make_path_qs(items)
294
-        return 'plugin://%s%s' % (self._addon_id, path_qs)
295
-
296
-    def redirect(self, url):
297
-        """Used when you need to redirect to another view, and you only
298
-        have the final plugin:// url."""
299
-        # TODO: Should we be overriding self.request with the new request?
300
-        new_request = self._parse_request(url=url, handle=self.request.handle)
301
-        log.debug('Redirecting %s to %s', self.request.path, new_request.path)
302
-        return self._dispatch(new_request.path)
303
-
304
-    def run(self):
305
-        """The main entry point for a plugin."""
306
-        self._request = self._parse_request()
307
-        log.debug('Handling incoming request for %s', self.request.path)
308
-        items = self._dispatch(self.request.path)
309
-
310
-        # Close any open storages which will persist them to disk
311
-        if hasattr(self, '_unsynced_storage'):
312
-            for storage in self._unsynced_storage.values():
313
-                log.debug('Saving a %s storage to disk at "%s"',
314
-                          storage.file_format, storage.file_path)
315
-                storage.close()
316
-
317
-        return items
318
-
319
-    def _dispatch(self, path):
320
-        for rule in self._routes:
321
-            try:
322
-                view_func, items = rule.match(path)
323
-            except NotFoundException:
324
-                continue
325
-            log.info('Request for "%s" matches rule for function "%s"',
326
-                     path, view_func.__name__)
327
-            resp = view_func(**items)
328
-
329
-            # Only call self.finish() for UI container listing calls to plugin
330
-            # (handle will be >= 0). Do not call self.finish() when called via
331
-            # RunPlugin() (handle will be -1).
332
-            if not self._end_of_directory and self.handle >= 0:
333
-                if isinstance(resp, dict):
334
-                    resp['items'] = self.finish(**resp)
335
-                elif isinstance(resp, collections.Iterable):
336
-                    resp = self.finish(items=resp)
337
-            return resp
338
-
339
-        raise NotFoundException('No matching view found for %s' % path)
340
-
341
-    @staticmethod
342
-    def _parse_request(url=None, handle=None):
343
-        """Handles setup of the plugin state, including request
344
-        arguments, handle, mode.
345
-
346
-        This method never needs to be called directly. For testing, see
347
-        plugin.test()
348
-        """
349
-        # To accommodate self.redirect, we need to be able to parse a full
350
-        # url as well
351
-        if url is None:
352
-            url = sys.argv[0]
353
-            if len(sys.argv) == 3:
354
-                url += sys.argv[2]
355
-        if handle is None:
356
-            handle = sys.argv[1]
357
-        return Request(url, handle)

+ 0
- 46
kodiswift/request.py 查看文件

@@ -1,46 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-"""
3
-kodiswift.request
4
-------------------
5
-
6
-This module contains the Request class. This class represents an incoming
7
-request from Kodi.
8
-
9
-:copyright: (c) 2012 by Jonathan Beluch
10
-:license: GPLv3, see LICENSE for more details.
11
-"""
12
-from __future__ import absolute_import
13
-
14
-import urlparse
15
-
16
-from kodiswift.common import unpickle_args
17
-
18
-__all__ = ['Request']
19
-
20
-
21
-class Request(object):
22
-
23
-    def __init__(self, url, handle):
24
-        """The request objects contains all the arguments passed to the plugin via
25
-        the command line.
26
-
27
-        Args:
28
-            url (str): The complete plugin URL being requested. Since Kodi
29
-                typically passes the URL query string in a separate argument
30
-                from the base URL, they must be joined into a single string
31
-                before being provided.
32
-            handle (Union[int, str]): The handle associated with the current
33
-                request.
34
-        """
35
-        self.url = url
36
-
37
-        #: The current request's handle, an integer.
38
-        self.handle = int(handle)
39
-
40
-        # urlparse doesn't like the 'plugin' scheme, so pass a protocol
41
-        # relative url, e.g. //plugin.video.helloxbmc/path
42
-        self.scheme, remainder = url.split(':', 1)
43
-        parts = urlparse.urlparse(remainder)
44
-        self.netloc, self.path, self.query_string = (
45
-            parts[1], parts[2], parts[4])
46
-        self.args = unpickle_args(urlparse.parse_qs(self.query_string))

+ 0
- 163
kodiswift/storage.py 查看文件

@@ -1,163 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-"""
3
-kodiswift.storage
4
------------------
5
-
6
-This module contains persistent storage classes.
7
-
8
-:copyright: (c) 2012 by Jonathan Beluch
9
-:license: GPLv3, see LICENSE for more details.
10
-"""
11
-from __future__ import absolute_import
12
-
13
-import collections
14
-import json
15
-import os
16
-import time
17
-import shutil
18
-from datetime import datetime
19
-
20
-try:
21
-    import cPickle as pickle
22
-except ImportError:
23
-    import pickle
24
-
25
-__all__ = ['Formats', 'PersistentStorage', 'TimedStorage', 'UnknownFormat']
26
-
27
-
28
-class UnknownFormat(Exception):
29
-    pass
30
-
31
-
32
-class Formats(object):
33
-    PICKLE = 'pickle'
34
-    JSON = 'json'
35
-
36
-
37
-class PersistentStorage(collections.MutableMapping):
38
-    def __init__(self, file_path, file_format=Formats.PICKLE):
39
-        """
40
-        Args:
41
-            file_path (str):
42
-            file_format (Optional[kodiswift.Formats]):
43
-        """
44
-        super(PersistentStorage, self).__init__()
45
-        self.file_path = file_path
46
-        self.file_format = file_format
47
-        self._store = {}
48
-        self._loaded = False
49
-
50
-    def __getitem__(self, key):
51
-        return self._store[key]
52
-
53
-    def __setitem__(self, key, value):
54
-        self._store[key] = value
55
-
56
-    def __delitem__(self, key):
57
-        del self._store[key]
58
-
59
-    def __iter__(self):
60
-        return iter(self._store)
61
-
62
-    def __len__(self):
63
-        return len(self._store)
64
-
65
-    def __enter__(self):
66
-        self.load()
67
-        self.sync()
68
-        return self
69
-
70
-    def __exit__(self, exc_type, exc_val, exc_tb):
71
-        self.sync()
72
-
73
-    def __repr__(self):
74
-        return '%s(%r)' % (self.__class__.__name__, self._store)
75
-
76
-    def items(self):
77
-        return self._store.items()
78
-
79
-    def load(self):
80
-        """Load the file from disk.
81
-
82
-        Returns:
83
-            bool: True if successfully loaded, False if the file
84
-                doesn't exist.
85
-
86
-        Raises:
87
-            UnknownFormat: When the file exists but couldn't be loaded.
88
-        """
89
-
90
-        if not self._loaded and os.path.exists(self.file_path):
91
-            with open(self.file_path, 'rb') as f:
92
-                for loader in (pickle.load, json.load):
93
-                    try:
94
-                        f.seek(0)
95
-                        self._store = loader(f)
96
-                        self._loaded = True
97
-                        break
98
-                    except pickle.UnpicklingError:
99
-                        pass
100
-            # If the file exists and wasn't able to be loaded, raise an error.
101
-            if not self._loaded:
102
-                raise UnknownFormat('Failed to load file')
103
-        return self._loaded
104
-
105
-    def close(self):
106
-        self.sync()
107
-
108
-    def sync(self):
109
-        temp_file = self.file_path + '.tmp'
110
-        try:
111
-            with open(temp_file, 'wb') as f:
112
-                if self.file_format == Formats.PICKLE:
113
-                    pickle.dump(self._store, f, 2)
114
-                elif self.file_format == Formats.JSON:
115
-                    json.dump(self._store, f, separators=(',', ':'))
116
-                else:
117
-                    raise NotImplementedError(
118
-                        'Unknown file format ' + repr(self.file_format))
119
-        except Exception:
120
-            if os.path.exists(temp_file):
121
-                os.remove(temp_file)
122
-            raise
123
-        shutil.move(temp_file, self.file_path)
124
-
125
-
126
-class TimedStorage(PersistentStorage):
127
-    """A dict with the ability to persist to disk and TTL for items."""
128
-
129
-    def __init__(self, file_path, ttl=None, **kwargs):
130
-        """
131
-        Args:
132
-            file_path (str):
133
-            ttl (Optional[int]):
134
-        """
135
-        super(TimedStorage, self).__init__(file_path, **kwargs)
136
-        self.ttl = ttl
137
-
138
-    def __setitem__(self, key, value):
139
-        self._store[key] = (value, time.time())
140
-
141
-    def __getitem__(self, item):
142
-        val, timestamp = self._store[item]
143
-        ttl_diff = datetime.utcnow() - datetime.utcfromtimestamp(timestamp)
144
-        if self.ttl and ttl_diff > self.ttl:
145
-            del self._store[item]
146
-            raise KeyError
147
-        return val
148
-
149
-    def __repr__(self):
150
-        return '%s(%r)' % (self.__class__.__name__,
151
-                           dict((k, v[0]) for k, v in self._store.items()))
152
-
153
-    def items(self):
154
-        items = []
155
-        for k in self._store.keys():
156
-            try:
157
-                items.append((k, self[k]))
158
-            except KeyError:
159
-                pass
160
-        return items
161
-
162
-    def sync(self):
163
-        super(TimedStorage, self).sync()

+ 0
- 212
kodiswift/urls.py 查看文件

@@ -1,212 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-"""
3
-kodiswift.urls
4
----------------
5
-
6
-This module contains URLRule class for dealing with url patterns.
7
-
8
-:copyright: (c) 2012 by Jonathan Beluch
9
-:license: GPLv3, see LICENSE for more details.
10
-"""
11
-from __future__ import absolute_import
12
-
13
-import re
14
-from urllib import urlencode, unquote_plus, quote_plus
15
-
16
-from kodiswift.common import pickle_dict, unpickle_dict
17
-
18
-__all__ = ['UrlRule', 'AmbiguousUrlException', 'NotFoundException']
19
-
20
-
21
-class AmbiguousUrlException(Exception):
22
-    pass
23
-
24
-
25
-class NotFoundException(Exception):
26
-    pass
27
-
28
-
29
-class UrlRule(object):
30
-    """A class the encapsulates a URL
31
-    """
32
-
33
-    def __init__(self, url_rule, view_func, name, options):
34
-        """Stores the various properties related to a routing URL rule.
35
-
36
-        It also provides a few methods to create URLs from the rule or to
37
-        match a given URL against a rule.
38
-
39
-        Args:
40
-            url_rule: The relative url pattern for the rule.
41
-                It may include <var_name> to denote where dynamic variables
42
-                should be matched.
43
-            view_func: The function that should be bound to this rule.
44
-                This should be an actual function object.
45
-            name: The name of the url rule. This is used in the reverse
46
-                process of creating urls for a given rule.
47
-            options: A dict containing any default values for the url rule.
48
-
49
-        Warnings:
50
-            view_func: The function signature should match any variable
51
-                names in the provided url_rule.
52
-        """
53
-        self._name = name
54
-        self._url_rule = url_rule
55
-        self._view_func = view_func
56
-        self._options = options or {}
57
-        self._keywords = re.findall(r'<(.+?)>', url_rule)
58
-
59
-        # change <> to {} for use with str.format()
60
-        self._url_format = self._url_rule.replace('<', '{').replace('>', '}')
61
-
62
-        # Make a regex pattern for matching incoming URLs
63
-        rule = self._url_rule
64
-        if rule != '/':
65
-            # Except for a path of '/', the trailing slash is optional.
66
-            rule = self._url_rule.rstrip('/') + '/?'
67
-        p = rule.replace('<', '(?P<').replace('>', '>[^/]+?)')
68
-
69
-        try:
70
-            self._regex = re.compile('^' + p + '$')
71
-        except re.error:
72
-            raise ValueError('There was a problem creating this URL rule. '
73
-                             'Ensure you do not have any unpaired angle '
74
-                             'brackets: "<" or ">"')
75
-
76
-    def __eq__(self, other):
77
-        if isinstance(other, UrlRule):
78
-            return (
79
-                (self._name, self._url_rule, self._view_func, self._options) ==
80
-                (other._name, other._url_rule, other._view_func, other._options)
81
-            )
82
-        else:
83
-            raise NotImplementedError
84
-
85
-    def __ne__(self, other):
86
-        return not self == other
87
-
88
-    def match(self, path):
89
-        """Attempts to match a url to the given path.
90
-
91
-        If successful, a tuple is returned. The first item is the matched
92
-        function and the second item is a dictionary containing items to be
93
-        passed to the function parsed from the provided path.
94
-
95
-        Args:
96
-            path (str): The URL path.
97
-
98
-        Raises:
99
-            NotFoundException: If the provided path does not match this
100
-                url rule.
101
-        """
102
-        m = self._regex.search(path)
103
-        if not m:
104
-            raise NotFoundException
105
-
106
-        # urlunencode the values
107
-        items = dict((key, unquote_plus(val))
108
-                     for key, val in m.groupdict().items())
109
-
110
-        # unpickle any items if present
111
-        items = unpickle_dict(items)
112
-
113
-        # We need to update our dictionary with default values provided in
114
-        # options if the keys don't already exist.
115
-        [items.setdefault(key, val) for key, val in self._options.items()]
116
-        return self._view_func, items
117
-
118
-    def _make_path(self, items):
119
-        """Returns a relative path for the given dictionary of items.
120
-
121
-        Uses this url rule's url pattern and replaces instances of <var_name>
122
-        with the appropriate value from the items dict.
123
-        """
124
-        for key, val in items.items():
125
-            if not isinstance(val, basestring):
126
-                raise TypeError('Value "%s" for key "%s" must be an instance'
127
-                                ' of basestring' % (val, key))
128
-            items[key] = quote_plus(val)
129
-
130
-        try:
131
-            path = self._url_format.format(**items)
132
-        except AttributeError:
133
-            # Old version of python
134
-            path = self._url_format
135
-            for key, val in items.items():
136
-                path = path.replace('{%s}' % key, val)
137
-        return path
138
-
139
-    def _make_qs(self, items):
140
-        """Returns a query string for the given dictionary of items. All keys
141
-        and values in the provided items will be urlencoded. If necessary, any
142
-        python objects will be pickled before being urlencoded.
143
-        """
144
-        return urlencode(pickle_dict(items))
145
-
146
-    def make_path_qs(self, items):
147
-        """Returns a relative path complete with query string for the given
148
-        dictionary of items.
149
-
150
-        Any items with keys matching this rule's url pattern will be inserted
151
-        into the path. Any remaining items will be appended as query string
152
-        parameters.
153
-
154
-        All items will be urlencoded. Any items which are not instances of
155
-        basestring, or int/long will be pickled before being urlencoded.
156
-
157
-        .. warning:: The pickling of items only works for key/value pairs which
158
-                     will be in the query string. This behavior should only be
159
-                     used for the simplest of python objects. It causes the
160
-                     URL to get very lengthy (and unreadable) and Kodi has a
161
-                     hard limit on URL length. See the caching section if you
162
-                     need to persist a large amount of data between requests.
163
-        """
164
-        # Convert any ints and longs to strings
165
-        for key, val in items.items():
166
-            if isinstance(val, (int, long)):
167
-                items[key] = str(val)
168
-
169
-        # First use our defaults passed when registering the rule
170
-        url_items = dict((key, val) for key, val in self._options.items()
171
-                         if key in self._keywords)
172
-
173
-        # Now update with any items explicitly passed to url_for
174
-        url_items.update((key, val) for key, val in items.items()
175
-                         if key in self._keywords)
176
-
177
-        # Create the path
178
-        path = self._make_path(url_items)
179
-
180
-        # Extra arguments get tacked on to the query string
181
-        qs_items = dict((key, val) for key, val in items.items()
182
-                        if key not in self._keywords)
183
-        qs = self._make_qs(qs_items)
184
-
185
-        if qs:
186
-            return '?'.join([path, qs])
187
-        return path
188
-
189
-    @property
190
-    def regex(self):
191
-        """The regex for matching paths against this url rule."""
192
-        return self._regex
193
-
194
-    @property
195
-    def view_func(self):
196
-        """The bound function"""
197
-        return self._view_func
198
-
199
-    @property
200
-    def url_format(self):
201
-        """The url pattern"""
202
-        return self._url_format
203
-
204
-    @property
205
-    def name(self):
206
-        """The name of this url rule."""
207
-        return self._name
208
-
209
-    @property
210
-    def keywords(self):
211
-        """The list of path keywords for this url rule."""
212
-        return self._keywords

+ 0
- 572
kodiswift/xbmcmixin.py 查看文件

@@ -1,572 +0,0 @@
1
-# -*- coding: utf-8 -*-
2
-from __future__ import absolute_import
3
-
4
-import os
5
-import warnings
6
-from datetime import timedelta
7
-from functools import wraps
8
-
9
-import kodiswift
10
-from kodiswift import xbmc, xbmcplugin, xbmcgui, CLI_MODE
11
-from kodiswift.constants import SortMethod
12
-from kodiswift.logger import log
13
-from kodiswift.storage import TimedStorage, UnknownFormat
14
-
15
-__all__ = ['XBMCMixin']
16
-
17
-# TODO(Sinap): Need to either break the single mixin into multiple or just use
18
-#              a parent class.
19
-
20
-
21
-# noinspection PyUnresolvedReferences,PyAttributeOutsideInit
22
-class XBMCMixin(object):
23
-    """A mixin to add Kodi helper methods. In order to use this mixin,
24
-    the child class must implement the following methods and
25
-    properties:
26
-
27
-        # Also, the child class is responsible for ensuring that this path
28
-        # exists.
29
-        self.storage_path
30
-
31
-        self.added_items
32
-
33
-        self.request
34
-
35
-        self.addon
36
-
37
-        _end_of_directory = False
38
-
39
-        _update_listing
40
-
41
-        self.handle
42
-
43
-    # optional
44
-    self.info_type: should be in ['video', 'music', 'pictures']
45
-    _memoized_storage = None
46
-    _unsynced_storage = None
47
-    # TODO: Ensure above is implemented
48
-    """
49
-
50
-    _function_cache_name = '.functions'
51
-
52
-    def cached(self, ttl=60 * 24):
53
-        """A decorator that will cache the output of the wrapped function.
54
-
55
-        The key used for the cache is the function name as well as the
56
-        `*args` and `**kwargs` passed to the function.
57
-
58
-        Args:
59
-            ttl: Time to live in minutes.
60
-
61
-        Notes:
62
-            ttl: For route caching, you should use
63
-                :meth:`kodiswift.Plugin.cached_route`.
64
-        """
65
-        def decorating_function(function):
66
-            storage = self.get_storage(
67
-                self._function_cache_name, file_format='pickle', ttl=ttl)
68
-            kwd_mark = 'f35c2d973e1bbbc61ca60fc6d7ae4eb3'
69
-
70
-            @wraps(function)
71
-            def wrapper(*args, **kwargs):
72
-                key = (function.__name__, kwd_mark) + args
73
-                if kwargs:
74
-                    key += (kwd_mark,) + tuple(sorted(kwargs.items()))
75
-
76
-                try:
77
-                    result = storage[key]
78
-                    log.debug('Storage hit for function "%s" with args "%s" '
79
-                              'and kwargs "%s"', function.__name__, args,
80
-                              kwargs)
81
-                except KeyError:
82
-                    log.debug('Storage miss for function "%s" with args "%s" '
83
-                              'and kwargs "%s"', function.__name__, args,
84
-                              kwargs)
85
-                    result = function(*args, **kwargs)
86
-                    storage[key] = result
87
-                    storage.sync()
88
-                return result
89
-
90
-            return wrapper
91
-
92
-        return decorating_function
93
-
94
-    def clear_function_cache(self):
95
-        """Clears the storage that caches results when using
96
-        :meth:`kodiswift.Plugin.cached_route` or
97
-        :meth:`kodiswift.Plugin.cached`.
98
-        """
99
-        self.get_storage(self._function_cache_name).clear()
100
-
101
-    def list_storage(self):
102
-        """Returns a list of existing stores.
103
-
104
-        The returned names can then be used to call get_storage().
105
-        """
106
-        # Filter out any storage used by kodiswift so caller doesn't corrupt
107
-        # them.
108
-        return [name for name in os.listdir(self.storage_path)
109
-                if not name.startswith('.')]
110
-
111
-    def get_storage(self, name='main', file_format='pickle', ttl=None):
112
-        """Returns a storage for the given name.
113
-
114
-        The returned storage is a fully functioning python dictionary and is
115
-        designed to be used that way. It is usually not necessary for the
116
-        caller to load or save the storage manually. If the storage does
117
-        not already exist, it will be created.
118
-
119
-        See Also:
120
-            :class:`kodiswift.TimedStorage` for more details.
121
-
122
-        Args:
123
-            name (str): The name  of the storage to retrieve.
124
-            file_format (str): Choices are 'pickle', 'csv', and 'json'.
125
-                Pickle is recommended as it supports python objects.
126
-
127
-                Notes: If a storage already exists for the given name, the
128
-                    file_format parameter is ignored. The format will be
129
-                    determined by the existing storage file.
130
-
131
-            ttl (int): The time to live for storage items specified in minutes
132
-                or None for no expiration. Since storage items aren't expired
133
-                until a storage is loaded form disk, it is possible to call
134
-                get_storage() with a different TTL than when the storage was
135
-                created. The currently specified TTL is always honored.
136
-
137
-        Returns:
138
-            kodiswift.storage.TimedStorage:
139
-        """
140
-        if not hasattr(self, '_unsynced_storage'):
141
-            self._unsynced_storage = {}
142
-        filename = os.path.join(self.storage_path, name)
143
-        try:
144
-            storage = self._unsynced_storage[filename]
145
-            log.debug('Loaded storage "%s" from memory', name)
146
-        except KeyError:
147
-            if ttl:
148
-                ttl = timedelta(minutes=ttl)
149
-            try:
150
-                storage = TimedStorage(filename, ttl, file_format=file_format)
151
-                storage.load()
152
-            except UnknownFormat:
153
-                # Thrown when the storage file is corrupted and can't be read.
154
-                # Prompt user to delete storage.
155
-                choices = ['Clear storage', 'Cancel']
156
-                ret = xbmcgui.Dialog().select(
157
-                    'A storage file is corrupted. It'
158
-                    ' is recommended to clear it.', choices)
159
-                if ret == 0:
160
-                    os.remove(filename)
161
-                    storage = TimedStorage(filename, ttl,
162
-                                           file_format=file_format)
163
-                else:
164
-                    raise Exception('Corrupted storage file at %s' % filename)
165
-
166
-            self._unsynced_storage[filename] = storage
167
-            log.debug('Loaded storage "%s" from disk', name)
168
-        return storage
169
-
170
-    def get_string(self, string_id):
171
-        """Returns the localized string from strings.po or strings.xml for the
172
-        given string_id.
173
-        """
174
-        string_id = int(string_id)
175
-        if not hasattr(self, '_strings'):
176
-            self._strings = {}
177
-        if string_id not in self._strings:
178
-            self._strings[string_id] = self.addon.getLocalizedString(string_id)
179
-        return self._strings[string_id]
180
-
181
-    def set_content(self, content):
182
-        """Sets the content type for the plugin."""
183
-        contents = ['files', 'songs', 'artists', 'albums', 'movies', 'tvshows',
184
-                    'episodes', 'musicvideos']
185
-        if content not in contents:
186
-            self.log.warning('Content type "%s" is not valid', content)
187
-        xbmcplugin.setContent(self.handle, content)
188
-
189
-    def get_setting(self, key, converter=None, choices=None):
190
-        """Returns the settings value for the provided key.
191
-
192
-        If converter is str, unicode, bool or int the settings value will be
193
-        returned converted to the provided type. If choices is an instance of
194
-        list or tuple its item at position of the settings value be returned.
195
-
196
-        Args:
197
-            key (str): The ID of the setting defined in settings.xml.
198
-            converter (Optional[str, unicode, bool, int]): How to convert the
199
-                setting value.
200
-                TODO(Sinap): Maybe this should just be a callable object?
201
-            choices (Optional[list,tuple]):
202
-
203
-        Notes:
204
-            converter: It is suggested to always use unicode for
205
-                text-settings because else xbmc returns utf-8 encoded strings.
206
-
207
-        Examples:
208
-            * ``plugin.get_setting('per_page', int)``
209
-            * ``plugin.get_setting('password', unicode)``
210
-            * ``plugin.get_setting('force_viewmode', bool)``
211
-            * ``plugin.get_setting('content', choices=('videos', 'movies'))``
212
-        """
213
-        # TODO: allow pickling of settings items?
214
-        # TODO: STUB THIS OUT ON CLI
215
-        value = self.addon.getSetting(key)
216
-        if converter is str:
217
-            return value
218
-        elif converter is unicode:
219
-            return value.decode('utf-8')
220
-        elif converter is bool:
221
-            return not(value.lower()=="false" or value=="0")
222
-        elif converter is int:
223
-            return int(value)
224
-        elif isinstance(choices, (list, tuple)):
225
-            return choices[int(value)]
226
-        elif converter is None:
227
-            log.warning('No converter provided, unicode should be used, '
228
-                        'but returning str value')
229
-            return value
230
-        else:
231
-            raise TypeError('Acceptable converters are str, unicode, bool and '
232
-                            'int. Acceptable choices are instances of list '
233
-                            ' or tuple.')
234
-
235
-    def set_setting(self, key, val):
236
-        # TODO: STUB THIS OUT ON CLI - setSetting takes id=x, value=x throws an error otherwise
237
-        return self.addon.setSetting(id=key, value=val)
238
-
239
-    def load_addon_settings(self):
240
-        """ Mock loading addon settings from user_data/settings.xml file.
241
-        If settings.xml does no exists, create using default values"""
242
-        if not CLI_MODE:
243
-            return
244
-        from xml.dom.minidom import parse
245
-        xml = parse(os.path.join("resources","language","English","strings.xml"))
246
-        self.addon._strings = dict((tag.getAttribute('id'), tag.firstChild.data) for tag in xml.getElementsByTagName('string'))
247
-        settings_file = os.path.join("addon_data","settings.xml")
248
-        if not os.path.exists(settings_file):
249
-            xml = parse(os.path.join("resources","settings.xml"))
250
-            self.addon._settings = dict((tag.getAttribute('id'), tag.attributes["default"].value)for tag in xml.getElementsByTagName('setting') if "id" in tag.attributes.keys())
251
-            if not os.path.exists("addon_data"):
252
-                os.mkdir("addon_data")
253
-            with open(settings_file,"w") as f:
254
-                f.write("<settings>\n")
255
-                for s in self.addon._settings:
256
-                    f.write('    <setting id="%s" value="%s" />\n'%(s,self.addon._settings[s]))
257
-                f.write("</settings>\n")
258
-        else:
259
-            xml = parse(settings_file)
260
-            self.addon._settings = dict((tag.getAttribute('id'), tag.attributes["value"].value) for tag in xml.getElementsByTagName('setting'))
261
-
262
-    def open_settings(self):
263
-        """Opens the settings dialog within Kodi"""
264
-        self.addon.openSettings()
265
-
266
-    @staticmethod
267
-    def add_to_playlist(items, playlist='video'):
268
-        """Adds the provided list of items to the specified playlist.
269
-        Available playlists include *video* and *music*.
270
-        """
271
-        playlists = {'music': 0, 'video': 1}
272
-        if playlist not in playlists:
273
-            raise ValueError('Playlist "%s" is invalid.' % playlist)
274
-
275
-        selected_playlist = xbmc.PlayList(playlists[playlist])
276
-        _items = []
277
-        for item in items:
278
-            if not hasattr(item, 'as_xbmc_listitem'):
279
-                if 'info_type' in item:
280
-                    log.warning('info_type key has no affect for playlist '
281
-                                'items as the info_type is inferred from the '
282
-                                'playlist type.')
283
-                # info_type has to be same as the playlist type
284
-                item['info_type'] = playlist
285
-                item = kodiswift.ListItem.from_dict(**item)
286
-            _items.append(item)
287
-            selected_playlist.add(item.get_path(), item.as_xbmc_listitem())
288
-        return _items
289
-
290
-    @staticmethod
291
-    def get_view_mode_id(view_mode):
292
-        warnings.warn('get_view_mode_id is deprecated.', DeprecationWarning)
293
-        return None
294
-
295
-    @staticmethod
296
-    def set_view_mode(view_mode_id):
297
-        """Calls Kodi's Container.SetViewMode. Requires an integer
298
-        view_mode_id"""
299
-        xbmc.executebuiltin('Container.SetViewMode(%d)' % view_mode_id)
300
-
301
-    def keyboard(self, default=None, heading=None, hidden=False):
302
-        """Displays the keyboard input window to the user. If the user does not
303
-        cancel the modal, the value entered by the user will be returned.
304
-
305
-        :param default: The placeholder text used to prepopulate the input
306
-                        field.
307
-        :param heading: The heading for the window. Defaults to the current
308
-                        addon's name. If you require a blank heading, pass an
309
-                        empty string.
310
-        :param hidden: Whether or not the input field should be masked with
311
-                       stars, e.g. a password field.
312
-        """
313
-        if heading is None:
314
-            heading = self.addon.getAddonInfo('name')
315
-        if default is None:
316
-            default = ''
317
-        keyboard = xbmc.Keyboard(default, heading, hidden)
318
-        keyboard.doModal()
319
-        if keyboard.isConfirmed():
320
-            return keyboard.getText()
321
-
322
-    def notify(self, msg='', title=None, delay=5000, image=''):
323
-        """Displays a temporary notification message to the user. If
324
-        title is not provided, the plugin name will be used. To have a
325
-        blank title, pass '' for the title argument. The delay argument
326
-        is in milliseconds.
327
-        """
328
-        if not image:
329
-            image = xbmcgui.NOTIFICATION_INFO
330
-        if not msg:
331
-            log.warning('Empty message for notification dialog')
332
-        if title is None:
333
-            title = self.addon.getAddonInfo('name')
334
-        if isinstance(msg,unicode):
335
-            msg = msg.encode("utf8")
336
-        if isinstance(title,unicode):
337
-            title = title.encode("utf8")
338
-        xbmcgui.Dialog().notification(title, msg, image, delay)
339
-        #xbmc.executebuiltin('Notification("%s", "%s", "%s", "%s")' % (msg, title, delay, image))
340
-
341
-    def set_resolved_url(self, item=None, subtitles=None):
342
-        """Takes a url or a listitem to be played. Used in conjunction with a
343
-        playable list item with a path that calls back into your addon.
344
-
345
-        :param item: A playable list item or url. Pass None to alert Kodi of a
346
-                     failure to resolve the item.
347
-
348
-                     .. warning:: When using set_resolved_url you should ensure
349
-                                  the initial playable item (which calls back
350
-                                  into your addon) doesn't have a trailing
351
-                                  slash in the URL. Otherwise it won't work
352
-                                  reliably with Kodi's PlayMedia().
353
-        :param subtitles: A URL to a remote subtitles file or a local filename
354
-                          for a subtitles file to be played along with the
355
-                          item.
356
-        """
357
-        if self._end_of_directory:
358
-            raise Exception('Current Kodi handle has been removed. Either '
359
-                            'set_resolved_url(), end_of_directory(), or '
360
-                            'finish() has already been called.')
361
-        self._end_of_directory = True
362
-
363
-        succeeded = True
364
-        if item is None:
365
-            # None item indicates the resolve url failed.
366
-            item = {}
367
-            succeeded = False
368
-
369
-        if isinstance(item, basestring):
370
-            # caller is passing a url instead of an item dict
371
-            item = {'path': item}
372
-
373
-        item = self._listitemify(item)
374
-        item.set_played(True)
375
-        xbmcplugin.setResolvedUrl(self.handle, succeeded,
376
-                                  item.as_xbmc_listitem())
377
-
378
-        # call to _add_subtitles must be after setResolvedUrl
379
-        if subtitles:
380
-            if isinstance(subtitles,list):
381
-                for subtitle in subtitles:
382
-                    self._add_subtitles(subtitle)
383
-            else:
384
-                self._add_subtitles(subtitles)
385
-
386
-        return [item]
387
-
388
-    def play_video(self, item, player=None):
389
-        if isinstance(item, dict):
390
-            item['info_type'] = 'video'
391
-
392
-        item = self._listitemify(item)
393
-        item.set_played(True)
394
-        if player:
395
-            _player = xbmc.Player(player)
396
-        else:
397
-            _player = xbmc.Player()
398
-        _player.play(item.get_path(), item.as_xbmc_listitem())
399
-        return [item]
400
-
401
-    def add_items(self, items):
402
-        """Adds ListItems to the Kodi interface.
403
-
404
-        Each item in the provided list should either be instances of
405
-        kodiswift.ListItem, or regular dictionaries that will be passed
406
-        to kodiswift.ListItem.from_dict.
407
-
408
-        Args:
409
-            items: An iterable of items where each item is either a
410
-                dictionary with keys/values suitable for passing to
411
-                :meth:`kodiswift.ListItem.from_dict` or an instance of
412
-                :class:`kodiswift.ListItem`.
413
-
414
-        Returns:
415
-            kodiswift.ListItem: The list of ListItems.
416
-        """
417
-        _items = [self._listitemify(item) for item in items]
418
-        tuples = [item.as_tuple() for item in _items if hasattr(item, 'as_tuple')]
419
-        xbmcplugin.addDirectoryItems(self.handle, tuples, len(tuples))
420
-
421
-        # We need to keep track internally of added items so we can return them
422
-        # all at the end for testing purposes
423
-        self.added_items.extend(_items)
424
-
425
-        # Possibly need an if statement if only for debug mode
426
-        return _items
427
-
428
-    def add_sort_method(self, sort_method, label2_mask=None):
429
-        """A wrapper for `xbmcplugin.addSortMethod()
430
-        <http://mirrors.xbmc.org/docs/python-docs/xbmcplugin.html#-addSortMethod>`_.
431
-        You can use ``dir(kodiswift.SortMethod)`` to list all available sort
432
-        methods.
433
-
434
-        Args:
435
-            sort_method: A valid sort method. You can provided the constant
436
-                from xbmcplugin, an attribute of SortMethod, or a string name.
437
-                For instance, the following method calls are all equivalent:
438
-                 * ``plugin.add_sort_method(xbmcplugin.SORT_METHOD_TITLE)``
439
-                 * ``plugin.add_sort_method(SortMethod.TITLE)``
440
-                 * ``plugin.add_sort_method('title')``
441
-            label2_mask: A mask pattern for label2. See the `Kodi
442
-                documentation <http://mirrors.xbmc.org/docs/python-docs/xbmcplugin.html#-addSortMethod>`_
443
-                for more information.
444
-        """
445
-        try:
446
-            # Assume it's a string and we need to get the actual int value
447
-            sort_method = SortMethod.from_string(sort_method)
448
-        except AttributeError:
449
-            # sort_method was already an int (or a bad value)
450
-            pass
451
-
452
-        if label2_mask:
453
-            xbmcplugin.addSortMethod(self.handle, sort_method, label2_mask)
454
-        else:
455
-            xbmcplugin.addSortMethod(self.handle, sort_method)
456
-
457
-    def end_of_directory(self, succeeded=True, update_listing=False,
458
-                         cache_to_disc=True):
459
-        """Wrapper for xbmcplugin.endOfDirectory. Records state in
460
-        self._end_of_directory.
461
-
462
-        Typically it is not necessary to call this method directly, as
463
-        calling :meth:`~kodiswift.Plugin.finish` will call this method.
464
-        """
465
-        self._update_listing = update_listing
466
-        if not self._end_of_directory:
467
-            self._end_of_directory = True
468
-            # Finalize the directory items
469
-            return xbmcplugin.endOfDirectory(self.handle, succeeded,
470
-                                             update_listing, cache_to_disc)
471
-        else:
472
-            raise Exception('Already called endOfDirectory.')
473
-
474
-    def finish(self, items=None, sort_methods=None, succeeded=True,
475
-               update_listing=False, cache_to_disc=True, view_mode=None):
476
-        """Adds the provided items to the Kodi interface.
477
-
478
-        Args:
479
-            items (List[Dict[str, str]]]): an iterable of items where each
480
-                item is either a dictionary with keys/values suitable for
481
-                passing to :meth:`kodiswift.ListItem.from_dict` or an
482
-                instance of :class:`kodiswift.ListItem`.
483
-
484
-            sort_methods (Union[List[str], str]): A list of valid Kodi
485
-                sort_methods. Each item in the list can either be a sort
486
-                method or a tuple of `sort_method, label2_mask`.
487
-                See :meth:`add_sort_method` for more detail concerning
488
-                valid sort_methods.
489
-
490
-            succeeded (bool):
491
-            update_listing (bool):
492
-            cache_to_disc (bool): Whether to tell Kodi to cache this folder
493
-                to disk.
494
-            view_mode (Union[str, int]): Can either be an integer
495
-                (or parsable integer string) corresponding to a view_mode or
496
-                the name of a type of view. Currently the only view type
497
-                supported is 'thumbnail'.
498
-
499
-        Returns:
500
-            List[kodiswift.listitem.ListItem]: A list of all ListItems added
501
-                to the Kodi interface.
502
-        """
503
-        # If we have any items, add them. Items are optional here.
504
-        if items:
505
-            self.add_items(items)
506
-        if sort_methods:
507
-            for sort_method in sort_methods:
508
-                if isinstance(sort_method, (list, tuple)):
509
-                    self.add_sort_method(*sort_method)
510
-                else:
511
-                    self.add_sort_method(sort_method)
512
-
513
-        # Attempt to set a view_mode if given
514
-        if view_mode is not None:
515
-            # First check if we were given an integer or parsable integer
516
-            try:
517
-                view_mode_id = int(view_mode)
518
-            except ValueError:
519
-                view_mode_id = None
520
-            if view_mode_id is not None:
521
-                self.set_view_mode(view_mode_id)
522
-
523
-        # Finalize the directory items
524
-        self.end_of_directory(succeeded, update_listing, cache_to_disc)
525
-
526
-        # Return the cached list of all the list items that were added
527
-        return self.added_items
528
-
529
-    def _listitemify(self, item):
530
-        """Creates an kodiswift.ListItem if the provided value for item is a
531
-        dict. If item is already a valid kodiswift.ListItem, the item is
532
-        returned unmodified.
533
-        """
534
-        info_type = self.info_type if hasattr(self, 'info_type') else 'video'
535
-
536
-        # Create ListItems for anything that is not already an instance of
537
-        # ListItem
538
-        if not hasattr(item, 'as_tuple') and hasattr(item, 'keys'):
539
-            if 'info_type' not in item:
540
-                item['info_type'] = info_type
541
-            item = kodiswift.ListItem.from_dict(**item)
542
-        return item
543
-
544
-    @staticmethod
545
-    def _add_subtitles(subtitles):
546
-        """Adds subtitles to playing video.
547
-
548
-        Warnings:
549
-            You must start playing a video before calling this method or it
550
-            will raise and Exception after 30 seconds.
551
-
552
-        Args:
553
-            subtitles (str): A URL to a remote subtitles file or a local
554
-                filename for a subtitles file.
555
-        """
556
-        # This method is named with an underscore to suggest that callers pass
557
-        # the subtitles argument to set_resolved_url instead of calling this
558
-        # method directly. This is to ensure a video is played before calling
559
-        # this method.
560
-        player = xbmc.Player()
561
-        monitor = xbmc.Monitor()
562
-        while not monitor.abortRequested():
563
-            if monitor.waitForAbort(30):
564
-                # Abort requested, so exit.
565
-                break
566
-            elif player.isPlaying():
567
-                # No abort requested after 30 seconds and a video is playing
568
-                # so add the subtitles and exit.
569
-                player.setSubtitles(subtitles)
570
-                break
571
-            else:
572
-                raise Exception('No video playing. Aborted after 30 seconds.')

+ 0
- 11
make_links.bat 查看文件

@@ -1,11 +0,0 @@
1
-set dir1=\Data\Programming\Kodi\plugin.video.playstream\
2
-set dir2=\Data\Programming\enigma2\PlayStream2\
3
-
4
-mklink \Data\Programming\Kodi\plugin.video.playstream\resources\lib\ContentSources.py \Data\Programming\enigma2\PlayStream2\ContentSources.py
5
-mklink \Data\Programming\Kodi\plugin.video.playstream\resources\lib\Downloader.py \Data\Programming\enigma2\PlayStream2\Downloader.py
6
-mklink \Data\Programming\Kodi\plugin.video.playstream\resources\lib\util.py \Data\Programming\enigma2\PlayStream2\util.py
7
-mklink \Data\Programming\Kodi\plugin.video.playstream\resources\lib\resolver.py \Data\Programming\enigma2\PlayStream2\resolver.py
8
-mklink \Data\Programming\Kodi\plugin.video.playstream\resources\lib\playstreamproxy.py \Data\Programming\enigma2\PlayStream2\playstreamproxy.py
9
-mklink \Data\Programming\Kodi\plugin.video.playstream\resources\lib\demjson.py \Data\Programming\enigma2\PlayStream2\demjson.py
10
-mklink /j \Data\Programming\Kodi\plugin.video.playstream\resources\lib\sources \Data\Programming\enigma2\PlayStream2\sources
11
-mklink /j \Data\Programming\Kodi\plugin.video.playstream\resources\lib\resolvers \Data\Programming\enigma2\PlayStream2\resolvers

+ 0
- 2229
project.wpr
文件差异内容过多而无法显示
查看文件


+ 0
- 36
readme.md 查看文件

@@ -1,36 +0,0 @@
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
-

二进制
release/plugin.video.playstream-0.1.12.zip 查看文件


二进制
release/plugin.video.playstream-0.1.15.zip 查看文件


二进制
release/plugin.video.playstream-0.1.17.zip 查看文件


二进制
release/plugin.video.playstream-0.1.18.zip 查看文件


二进制
release/plugin.video.playstream-0.1.2.zip 查看文件


二进制
release/plugin.video.playstream-0.1.20.zip 查看文件


二进制
release/plugin.video.playstream-0.1.21.zip 查看文件


二进制
release/plugin.video.playstream-0.1.22.zip 查看文件


二进制
release/plugin.video.playstream-0.1.23.zip 查看文件


二进制
release/plugin.video.playstream-0.1.24.zip 查看文件


二进制
release/plugin.video.playstream-0.1.26.zip 查看文件


二进制
release/plugin.video.playstream-0.1.27.zip 查看文件


二进制
release/plugin.video.playstream-0.1.28.zip 查看文件


二进制
release/plugin.video.playstream-0.1.29.zip 查看文件


二进制
release/plugin.video.playstream-0.1.3.zip 查看文件


二进制
release/plugin.video.playstream-0.1.30.zip 查看文件


二进制
release/plugin.video.playstream-0.1.31.zip 查看文件


二进制
release/plugin.video.playstream-0.1.32.zip 查看文件


二进制
release/plugin.video.playstream-0.1.33.zip 查看文件


二进制
release/plugin.video.playstream-0.1.34.zip 查看文件


二进制
release/plugin.video.playstream-0.1.35.zip 查看文件


二进制
release/plugin.video.playstream-0.1.36.zip 查看文件


二进制
release/plugin.video.playstream-0.1.37.zip 查看文件


二进制
release/plugin.video.playstream-0.1.38.zip 查看文件


二进制
release/plugin.video.playstream-0.1.4.zip 查看文件


二进制
release/plugin.video.playstream-0.1.40.zip 查看文件


二进制
release/plugin.video.playstream-0.1.41.zip 查看文件


二进制
release/plugin.video.playstream-0.1.42.zip 查看文件


二进制
release/plugin.video.playstream-0.1.43.zip 查看文件


二进制
release/plugin.video.playstream-0.1.44.zip 查看文件


二进制
release/plugin.video.playstream-0.1.45.zip 查看文件


二进制
release/plugin.video.playstream-0.1.46.zip 查看文件


二进制
release/plugin.video.playstream-0.1.48.zip 查看文件


二进制
release/plugin.video.playstream-0.1.49.zip 查看文件


二进制
release/plugin.video.playstream-0.1.5.zip 查看文件


二进制
release/plugin.video.playstream-0.1.50.zip 查看文件


二进制
release/plugin.video.playstream-0.1.51.zip 查看文件


二进制
release/plugin.video.playstream-0.1.53.zip 查看文件


二进制
release/plugin.video.playstream-0.1.54.zip 查看文件


二进制
release/plugin.video.playstream-0.1.55.zip 查看文件


二进制
release/plugin.video.playstream-0.1.56.zip 查看文件


二进制
release/plugin.video.playstream-0.1.57.zip 查看文件


二进制
release/plugin.video.playstream-0.1.58.zip 查看文件


二进制
release/plugin.video.playstream-0.1.59.zip 查看文件


二进制
release/plugin.video.playstream-0.1.6.zip 查看文件


二进制
release/plugin.video.playstream-0.1.60.zip 查看文件


二进制
release/plugin.video.playstream-0.1.61.zip 查看文件


二进制
release/plugin.video.playstream-0.1.62.zip 查看文件


二进制
release/plugin.video.playstream-0.1.63.zip 查看文件


二进制
release/plugin.video.playstream-0.1.64.zip 查看文件


二进制
release/plugin.video.playstream-0.1.65.zip 查看文件


二进制
release/plugin.video.playstream-0.1.66.zip 查看文件


二进制
release/plugin.video.playstream-0.1.67.zip 查看文件


二进制
release/plugin.video.playstream-0.1.68.zip 查看文件


二进制
release/plugin.video.playstream-0.1.7.zip 查看文件


+ 0
- 0
release/plugin.video.playstream-0.1.70.zip 查看文件


部分文件因为文件数量过多而无法显示