Ivars 7 years ago
parent
commit
cdb8e624f5

+ 18
- 0
download_service.py View File

1
+# -*- coding: utf-8 -*-
2
+import os,os.path,sys, urllib, 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 playstreamproxy
8
+#monitor = xbmc.Monitor()
9
+
10
+
11
+plugin = Plugin()
12
+plugin.load_addon_settings()
13
+port = plugin.get_setting("general_port",int)
14
+start_proxy = plugin.get_setting("general_proxy",bool)
15
+host = "localhost"
16
+if start_proxy:
17
+    plugin.notify("Starting playstreamproxy","Info",10000, xbmcgui.NOTIFICATION_INFO)
18
+    playstreamproxy.start(host, port)

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


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


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


+ 272
- 0
resources/lib/Downloader.py View File

1
+#from boxbranding import getMachineBrand, getMachineName
2
+import sys,os, os.path, re
3
+import urlparse, requests
4
+from twisted.web import client
5
+from twisted.internet import reactor, defer, ssl
6
+
7
+USER_AGENT = "Enigma2 HbbTV/1.1.1 (+PVR+RTSP+DL;OpenATV;;;)"
8
+
9
+#####################################################################################################
10
+class HTTPProgressDownloader(client.HTTPDownloader):
11
+    def __init__(self, url, outfile, headers=None):
12
+        agent = USER_AGENT
13
+        if headers and "user-agent" in headers:
14
+            agent = headers["user-agent"]
15
+        if headers and "User-Agent" in headers:
16
+            agent = headers["User-Agent"]
17
+        client.HTTPDownloader.__init__(self, url, outfile, headers=headers, agent=agent)
18
+        self.status = None
19
+        self.progress_callback = None
20
+        self.deferred = defer.Deferred()
21
+
22
+    def noPage(self, reason):
23
+        if self.status == "304":
24
+            print reason.getErrorMessage()
25
+            client.HTTPDownloader.page(self, "")
26
+        else:
27
+            client.HTTPDownloader.noPage(self, reason)
28
+
29
+    def gotHeaders(self, headers):
30
+        if self.status == "200":
31
+            if headers.has_key("content-length"):
32
+                self.totalbytes = int(headers["content-length"][0])
33
+            else:
34
+                self.totalbytes = 0
35
+            self.currentbytes = 0.0
36
+        return client.HTTPDownloader.gotHeaders(self, headers)
37
+
38
+    def pagePart(self, packet):
39
+        if self.status == "200":
40
+            self.currentbytes += len(packet)
41
+        if self.totalbytes and self.progress_callback:
42
+            self.progress_callback(self.currentbytes, self.totalbytes)
43
+        return client.HTTPDownloader.pagePart(self, packet)
44
+
45
+    def pageEnd(self):
46
+        return client.HTTPDownloader.pageEnd(self)
47
+
48
+class DownloadWithProgress:
49
+    def __init__(self, url, outputfile, headers=None, limit=0, contextFactory=None, *args, **kwargs):
50
+        self.limit = limit
51
+        uri = urlparse.urlparse(url)
52
+        scheme = uri.scheme
53
+        host = uri.hostname
54
+        port = uri.port if uri.port else 80
55
+        path = uri.path
56
+        if not headers:
57
+            headers = {"user-agent":USER_AGENT}
58
+        self.factory = HTTPProgressDownloader(url, outputfile, headers, *args, **kwargs)
59
+        if scheme == "https":
60
+            self.connection = reactor.connectSSL(host, port, self.factory, ssl.ClientContextFactory())
61
+        else:
62
+            self.connection = reactor.connectTCP(host, port, self.factory)
63
+
64
+    def start(self):
65
+        return self.factory.deferred
66
+
67
+    def stop(self):
68
+        if self.connection:
69
+            print "[stop]"
70
+            self.connection.disconnect()
71
+
72
+    def addProgress(self, progress_callback):
73
+        print "[addProgress]"
74
+        self.factory.progress_callback = progress_callback
75
+
76
+#####################################################################################################
77
+class DownloadWithProgressFragmented:
78
+    def __init__(self, url, outputfile, headers = None, limit = 0, contextFactory=None, *args, **kwargs):
79
+        self.url = url
80
+        self.outputfile = outputfile
81
+        self.base_url = "/".join(url.split("/")[:-1])+"/"
82
+        self.headers = headers if headers else  {"user-agent":"Enigma2"}
83
+        self.limit = limit
84
+        self.agent = kwargs["agent"] if "agent" in kwargs else None
85
+        self.cookie = kwargs["cookie"] if "cookie" in kwargs else None
86
+        self.deferred = defer.Deferred()
87
+        #self.deferred.addCallback(self.start_download)
88
+
89
+    def start_download(self):
90
+        print "Start download"
91
+        try:
92
+            r = requests.get(self.url,headers=self.headers)
93
+        except Exception as e:
94
+            #self.deferred.errback("Cannot open manifsest file - %s"%url)
95
+            self.deferred.errback(e)
96
+        if not r.content.startswith("#EXTM3U"):
97
+            self.deferred.errback(Exception("Not valid manifest file - %s"%self.url))
98
+        streams = re.findall(r"#EXT-X-STREAM-INF:.*?BANDWIDTH=(\d+).*?\n(.+?)$", r.content, re.IGNORECASE | re.MULTILINE)
99
+        if streams:
100
+            sorted(streams, key=lambda item: int(item[0]), reverse=True)
101
+            url = streams[0][1]
102
+            if not url.startswith("http"):
103
+                url = self.base_url + url
104
+            try:
105
+                r = requests.get(url, headers=self.headers)
106
+            except Exception as e:
107
+                self.deferred.errback(Exception("Cannot open manifsest file - %s"%url))
108
+        self.ts_list = re.findall(r"#EXTINF:([\d\.]+),.*?\n(.+?)$", r.content, re.IGNORECASE | re.MULTILINE)
109
+        if not len(self.ts_list):
110
+            self.deferred.errback(Exception("Cannot read fragment list in  manifsest file - %s"%url))
111
+        self.ts_num = 0
112
+        self.type = "vod" if "#EXT-X-ENDLIST" in r.content else "live"
113
+        self.currentbytes = 0.0
114
+        self.totalbytes = -1
115
+        self.currenttime = 0.0
116
+        self.totaltime = sum(map(float,zip(*self.ts_list)[0]))
117
+        try:
118
+            self.ts_file = open(self.outputfile, "wb")
119
+        except Exception as e:
120
+            self.deferred.errback(Exception("Cannot open output file - %s" % self.outputfile))
121
+        self.download_fragment()
122
+
123
+    def download_fragment(self):
124
+        if self.ts_num>=len(self.ts_list):
125
+            pass
126
+            print "Call later"
127
+            reactor.callLater(10,self.update_manifest)
128
+            reactor.callLater(10, self.download_fragment)
129
+        else:
130
+            print "Start fragment download"
131
+            url = self.ts_list[self.ts_num][1]
132
+            if not "://" in url:
133
+                url = self.base_url+url
134
+            self.d = client.getPage(url,headers = self.headers)
135
+            self.d.addCallbacks(self.download_ok,self.download_err)
136
+
137
+
138
+    def download_ok(self,content):
139
+        content_length = len(content)
140
+        self.currentbytes += content_length
141
+        self.currenttime += float(self.ts_list[self.ts_num][0])
142
+        self.totalbytes = self.currentbytes * self.totaltime / self.currenttime
143
+        self.ts_num += 1
144
+        #print "Fragment %s downloaded (%s)"%(self.ts_num,len(content))
145
+        self.ts_file.write(content)
146
+        self.progress_callback(self.currentbytes, self.totalbytes)
147
+        if self.type == "vod":
148
+            if self.ts_num >= len(self.ts_list) or (self.limit and self.currenttime>self.limit):
149
+                self.ts_file.close()
150
+                self.download_finished()
151
+            else:
152
+                self.download_fragment()
153
+        else:
154
+            if self.limit and self.currenttime>self.limit: # TODO
155
+                self.ts_file.close()
156
+                self.download_finished()
157
+            else:
158
+                self.download_fragment()
159
+
160
+    def update_manifest(self):
161
+        self.d2 = client.getPage(self.url, headers=self.headers)
162
+        self.d2.addCallbacks(self.update_manifest_ok, self.update_manifest_err)
163
+
164
+    def update_manifest_ok(self,content):
165
+        print "Update manifest"
166
+        ts_list = re.findall(r"#EXTINF:([\d\.]+),\n(.+?)$", content, re.IGNORECASE | re.MULTILINE)
167
+        last_ts = self.ts_list[-1]
168
+        found = False
169
+        for ts in ts_list:
170
+            if ts == last_ts:
171
+                found = True
172
+            elif found:
173
+                print "Append %s"%ts[1]
174
+                self.ts_list.append(ts)
175
+        #reactor.callLater(5,self.download_fragment)
176
+
177
+    def update_manifest_err(self,content):
178
+        return
179
+
180
+    def download_err(self,content):
181
+        self.deferred.errback("Error while downloading %s"%self.ts_list[self.ts_num][1])
182
+
183
+    def download_finished(self):
184
+        self.totalbytes = self.currentbytes
185
+        self.deferred.callback("Done")
186
+
187
+    def start(self):
188
+        reactor.callLater(1,self.start_download)
189
+        return self.deferred
190
+
191
+    def stop(self):
192
+        self.deferred.errback() # TODO
193
+
194
+    def addProgress(self, progress_callback):
195
+        print "[addProgress]"
196
+        self.progress_callback = progress_callback
197
+
198
+#####################################################################################################
199
+def get_header(url,headers=None):
200
+    headers = {"user-agent":USER_AGENT}
201
+    r = requests.head(url,headers=headers)
202
+    return r.headers
203
+
204
+def get_ext(mtype):
205
+    stype = "http"
206
+    if mtype in ("vnd.apple.mpegURL","application/x-mpegURL",'application/x-mpegurl',"application/vnd.apple.mpegurl"):
207
+        return ".ts","hls"
208
+    elif mtype in ("application/dash+xml"):
209
+        return ".ts","dash" # TODO dash stream type  could be different !
210
+    elif mtype in ("video/mp4"):
211
+        return ".mp4","http"
212
+    elif mtype in ("video/MP2T","video/mp2t"):
213
+        return ".ts","http"
214
+    elif mtype in ("video/x-flv"):
215
+        return ".flv","http"
216
+    elif mtype in ("video/quicktime"):
217
+        return ".mov","http"
218
+    elif mtype in ("video/x-msvideo"):
219
+        return ".avi","http"
220
+    elif mtype in ("video/x-ms-wmv"):
221
+        return ".wmv","http"
222
+    elif mtype in ("video/x-matroska"):
223
+        return ".mkv","http"
224
+    else:
225
+        return ".mp4","http"
226
+
227
+
228
+
229
+##############################################
230
+def print_progress(currentbytes, totalbytes):
231
+    progress = float(currentbytes)/float(totalbytes)*100
232
+    print "%s (%i/%i)"%(progress,currentbytes,totalbytes)
233
+
234
+def download_ok(*args):
235
+    print "Download OK"
236
+    reactor.stop()
237
+
238
+def download_err(e):
239
+    print "Download Error %s"%e.getBriefTraceback()
240
+    pass
241
+def stop():
242
+    reactor.stop()
243
+###############################################
244
+
245
+if __name__ == "__main__":
246
+    if len(sys.argv)>2:
247
+        url= sys.argv[1]
248
+        output = sys.argv[1]
249
+    else:
250
+        url = "http://walterebert.com/playground/video/hls/ts/480x270.m3u8"
251
+        url = "http://techslides.com/demos/sample-videos/small.mp4"
252
+        #url = "http://wx17.poiuytrew.pw/s/c507282042b1bf25e0b72c34a68426f3/hd_30/Jackie.2016.D.iTunes.BDRip.1080p_720.mp4"
253
+        url = "http://player.tvnet.lv/live/amlst:11/chunklist_w361981294_b528000.m3u8"
254
+        #url = "http://vod-hls-uk-live.akamaized.net/usp/auth/vod/piff_abr_full_hd/a3e90e-b08ktytr/vf_b08ktytr_f9d55583-afc7-49bb-9bf4-d8f1ac99f56f.ism.hlsv2.ism/vf_b08ktytr_f9d55583-afc7-49bb-9bf4-d8f1ac99f56f.ism.hlsv2-audio=128000-video=5070000.m3u8"
255
+        #url = "https://58174450afee9.streamlock.net/vod/mp4:_definst_/f/e/8e49fc32.mp4/playlist.m3u8?safwerwfasendtime=1490877870&safwerwfasstarttime=1490859339&safwerwfashash=hS2FfVZysQVazBQ6RJn1IhUevBkKxIF09Ly3BjfT43U="
256
+    try:
257
+        h = get_header(url,headers={"user-agent":"Enigma2"})
258
+        mtype = h.get("content-type")
259
+        ext,stream_type = get_ext(mtype)
260
+    except:
261
+        ext,stream_type = (".ts","hls")
262
+    output = urlparse.urlparse(url)[2].split('/')[-1] + ext
263
+    output = os.path.join("downloads", output)
264
+    if stream_type == "hls":
265
+        d = DownloadWithProgressFragmented(url,output,headers={"user-agent":"Enigma2"})
266
+    else:
267
+        d = DownloadWithProgress(url,output,headers={"user-agent":"Enigma2"})
268
+    d.addProgress(print_progress)
269
+    d.start().addCallback(download_ok).addErrback(download_err)
270
+    reactor.run()
271
+
272
+

+ 189
- 0
resources/lib/sources/lmt.py View File

1
+#!/usr/bin/env python
2
+# coding=utf8
3
+#
4
+# This file is part of PlayStream - enigma2 plugin to play video streams from various sources
5
+# Copyright (c) 2016 ivars777 (ivars777@gmail.com)
6
+# Distributed under the GNU GPL v3. For full terms see http://www.gnu.org/licenses/gpl-3.0.en.html
7
+#
8
+try:
9
+    import json
10
+except:
11
+    import simplejson as json
12
+import requests
13
+import datetime, re, sys,os
14
+import ConfigParser
15
+from collections import OrderedDict
16
+from SourceBase import SourceBase
17
+import resolver
18
+try:
19
+    import util
20
+except:
21
+    sys.path.insert(0,'..')
22
+    import util
23
+
24
+headers2dict = lambda  h: dict([l.strip().split(": ") for l in h.strip().splitlines()])
25
+import HTMLParser
26
+h = HTMLParser.HTMLParser()
27
+
28
+class Source(SourceBase):
29
+
30
+    def __init__(self, country="",cfg_path=None):
31
+        self.name = "lmt"
32
+        self.title = "LMT straume"
33
+        self.img = "http://www.lob.lv/images/logo/lmt_straume_vert_rgb.png"
34
+        self.desc = "LMT straume - dažādi video latviesu valodā"
35
+        self.headers = headers2dict("""
36
+User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0
37
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
38
+Accept-Language: en-US,en;q=0.5
39
+""")
40
+        self.headers2 = headers2dict("""
41
+User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0
42
+X-Requested-With: XMLHttpRequest
43
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
44
+""")
45
+        self.url = "http://straume.lmt.lv/lv/"
46
+
47
+
48
+    ######### Entry point ########
49
+    def get_content(self, data):
50
+        print "[lmt] get_content:", data
51
+        source, data, path, plist, clist, params, qs = self.parse_data(data)
52
+        content = []
53
+        content.append(("..return", "back","","Return back"))
54
+
55
+        if clist=="home":
56
+            content.extend([
57
+                ("Meklēt", "lmt::meklet?q={0}","","Meklēt"),
58
+                ("Straumes", "lmt::straumes","","Tiešraides un aktuāli video"),
59
+                #("TV", "lmt::tv","","TV tiešraides (tikai LMT tīklā)"),
60
+                ("Jaunākie", "lmt::video/jaunakie?videoPage=1", "", "Visu žanru jaunākie video"),
61
+                ("Sports", "lmt::video/sports?videoPage=1", "", "Sports"),
62
+                ("Kultūra un māksla", "lmt::video/kultura?videoPage=1", "", "Kultūra un māksla"),
63
+                ("Konferences", "lmt::video/konferences?videoPage=1", "", "Konferences"),
64
+                ("Raidījumi", "lmt::video/raidijumi?videoPage=1", "", "Raidījumi"),
65
+                ("Notikumi", "lmt::video/notikumi?videoPage=1", "", "Notikumi"),
66
+                ("Filmas un seriāli", "lmt::video/filmas?videoPage=1", "", "Filmas un seriāli"),
67
+                ("Dažādi video", "lmt::video/dazadi?videoPage=1", "", "Dažādi video"),
68
+            ])
69
+            return content
70
+
71
+        elif clist in ("meklet","video", "straumes","video-saraksts"):
72
+            r=self.call(data)
73
+            result = re.findall('<a class="video-picture" (.+?)</li>', r, re.IGNORECASE | re.MULTILINE)
74
+            for r2 in result:
75
+                m = re.search('<a class="video-title" href="/lv/([^"]+)">([^<]+)<', r2)
76
+                title = m.group(2)
77
+                data2 = m.group(1)
78
+                m = re.search("([^ ]+) 2x", r2)
79
+                if m:
80
+                    img = m.group(1)
81
+                else:
82
+                    m = re.search('<img src="([^"]+)', r2)
83
+                    img = m.group(1) if m else ""
84
+                m = re.search('<span class="playlist-overlay">([^<]+)</span>', r2)
85
+                overlay = m.group(1) if m else ""
86
+                m = re.search('<span class="badge badge-[^>]+>([^<]+)(<[^>]+>([^<]+))*</span>', r2, re.IGNORECASE)
87
+                badge = ""
88
+                if m:
89
+                    badge = m.group(1)
90
+                    if m.group(3):
91
+                        badge = badge + m.group(3)
92
+                categories = re.findall('<span class="category-title">([^<]+)</span>', r2)
93
+                categories = "".join(categories)
94
+                if overlay:
95
+                    title = "%s [%s]"%(title,overlay)
96
+                if badge:
97
+                    title = "%s [%s]"%(title,badge)
98
+                desc = title
99
+                if categories:
100
+                    desc = desc + "\n"+ categories
101
+                content.append((title,self.name+"::"+data2,img,desc))
102
+            m = re.search("videoPage=(\d+)",data)
103
+            if m:
104
+                page = int(m.group(1))+1
105
+                data2 = re.sub(r"videoPage=\d+", r"videoPage=%s"%page, data)
106
+                content.append(("Next page",self.name+"::"+data2,self.img,"Next page"))
107
+            #print content
108
+            return content
109
+
110
+
111
+        ### kaut kas neparedzets ###
112
+        else:
113
+            return content
114
+
115
+    def is_video(self,data):
116
+        source,data,path,plist,clist,params,qs = self.parse_data(data)
117
+        if not clist in ("meklet","video", "straumes","video-saraksts","home"):
118
+            return True
119
+
120
+    def call(self, data,params=None,headers=None,lang=""):
121
+        if not headers: headers = self.headers
122
+        url = self.url+data
123
+        r = requests.get(url,headers = headers)
124
+        return r.content
125
+        #result = self._http_request(url,params,headers=headers)
126
+        return result
127
+
128
+    def get_streams(self,data):
129
+        print "[lmt] get_streams:", data
130
+        if not self.is_video(data):
131
+            return []
132
+        source,data,path,plist,clist,params,qs = self.parse_data(data)
133
+        r = self.call(path)
134
+        title = re.search("<h1>(.+?)</h1", r, re.IGNORECASE).group(1)
135
+        m = re.search('<a class="category-title".+?[^>]+>([^<]+)<', r, re.IGNORECASE | re.DOTALL)
136
+        categories = m.group(1) if m else ""
137
+        m = re.search('<span class="category-title">([^<]+)</span>.+?</p>', r, re.IGNORECASE | re.DOTALL)
138
+        if m:
139
+            categories = categories + m.group(1)
140
+        if categories:
141
+            tite = "%s [%s]"%(title,categories)
142
+        img = re.search('<meta property="twitter:image" content="([^"]+)">', r, re.IGNORECASE | re.DOTALL).group(1)
143
+        desc = title + "\n" + re.search('<meta property="og:description" content="([^"]+)">', r, re.IGNORECASE | re.DOTALL).group(1)
144
+        m = re.search('file: "([^"]+)"', r, re.IGNORECASE)
145
+        if m:
146
+            data2 = m.group(1)
147
+            stream = util.item()
148
+            stream["name"] = title
149
+            stream["url"] = data2
150
+            stream["img"] = img
151
+            stream["desc"] = desc
152
+            stream["resolver"] = "lmt"
153
+            return [stream]
154
+        else:
155
+            m = re.search('src="(http://www.youtube.com/[^"]+)"',r)
156
+            if m:
157
+                url = m.group(1)
158
+            else:
159
+                raise Exception("No stream found")
160
+            streams = resolver.resolve(url)
161
+            for s in streams:
162
+                s["name"] = title
163
+                s["desc"] = desc
164
+                s["img"] = img
165
+                streams.append(s)
166
+            return streams
167
+
168
+
169
+if __name__ == "__main__":
170
+    country= "lv"
171
+    c = Source(country)
172
+    if len(sys.argv)>1:
173
+        data= sys.argv[1]
174
+    else:
175
+        data = "home"
176
+    content = c.get_content(data)
177
+    for item in content:
178
+        print item
179
+    #cat = api.get_categories(country)
180
+    #chan = api.get_channels("lv")
181
+    #prog = api.get_programs(channel=6400)
182
+    #prog = api.get_programs(category=55)
183
+    #seas = api.get_seasons(program=6453)
184
+    #str = api.get_streams(660243)
185
+    #res = api.get_videos(802)
186
+    #formats = api.getAllFormats()
187
+    #det = api.detailed("1516")
188
+    #vid = api.getVideos("13170")
189
+    pass