Ivars 5 vuotta sitten
vanhempi
commit
ceb5f19f34
9 muutettua tiedostoa jossa 1503 lisäystä ja 721 poistoa
  1. 7
    7
      ContentSources.py
  2. 4
    0
      changelog.md
  3. 96
    240
      playstreamproxy.py
  4. 583
    0
      playstreamproxy0.py
  5. 146
    0
      playstreamproxy5.py
  6. 605
    462
      project.wpr
  7. 1
    0
      run.py
  8. 13
    12
      sources/tvdom.py
  9. 48
    0
      util.py

+ 7
- 7
ContentSources.py Näytä tiedosto

@@ -62,7 +62,7 @@ class ContentSources(object):
62 62
                 cfg.add_item("home",(title,"%s::home"%pl,img,desc))
63 63
                 cfg.write_streams()
64 64
 
65
-    def get_content(self,data):
65
+    def get_content(self,data, qs=None):
66 66
         source = data.split("::")[0]
67 67
         if source in self.plugins:
68 68
             content0 = self.plugins[source].get_content(data)
@@ -94,7 +94,7 @@ class ContentSources(object):
94 94
         else:
95 95
             return self.error_content
96 96
 
97
-    def get_streams(self,data):
97
+    def get_streams(self,data, qs=None):
98 98
         if stream_type(data):
99 99
             if "::" in data:
100 100
                 data = data.split("::")[1]
@@ -126,7 +126,7 @@ class ContentSources(object):
126 126
         else:
127 127
             return []
128 128
 
129
-    def get_info(self,data):
129
+    def get_info(self,data, qs=None):
130 130
         nfo = {}
131 131
         if self.is_video(data):
132 132
             source = data.split("::")[0]
@@ -147,10 +147,10 @@ class ContentSources(object):
147 147
         return nfo
148 148
 
149 149
 
150
-    def stream_type(self,data):
150
+    def stream_type(self,data, qs=None):
151 151
         return stream_type(data)
152 152
 
153
-    def is_video(self,data):
153
+    def is_video(self,data, qs=None):
154 154
         if self.stream_type(data):
155 155
             return True
156 156
         source = data.split("::")[0]
@@ -159,7 +159,7 @@ class ContentSources(object):
159 159
         else:
160 160
             return False
161 161
 
162
-    def options_read(self,source):
162
+    def options_read(self,source, qs=None):
163 163
         if source in self.plugins:
164 164
             options = self.plugins[source].options_read()
165 165
             if options:
@@ -169,7 +169,7 @@ class ContentSources(object):
169 169
         else:
170 170
             return None
171 171
 
172
-    def options_write(self,source,options):
172
+    def options_write(self,source,options, qs=None):
173 173
         if source in self.plugins:
174 174
             return self.plugins[source].options_write(options)
175 175
         else:

+ 4
- 0
changelog.md Näytä tiedosto

@@ -1,3 +1,7 @@
1
+**23.02.2019**
2
+- salabots tvdom
3
+- playstreamproxy servē strīmus priekš m3u8
4
+
1 5
 **23.12.2018**
2 6
 - salabots tvplay, filmix
3 7
 

+ 96
- 240
playstreamproxy.py Näytä tiedosto

@@ -2,13 +2,13 @@
2 2
 # -*- coding: utf-8 -*-
3 3
 """
4 4
 StreamProxy daemon (based on Livestream daemon)
5
-Ensures persistent cookies, User-Agents and others tricks to play protected HLS/DASH streams
5
+Provides API to ContetSources + stream serving to play via m3u8 playlists
6 6
 """
7 7
 import os
8 8
 import sys
9 9
 import time
10 10
 import atexit
11
-import re
11
+import re, json
12 12
 import binascii
13 13
 
14 14
 from signal import SIGTERM
@@ -19,6 +19,10 @@ from urllib import unquote, quote
19 19
 import urllib,urlparse
20 20
 #import cookielib,urllib2
21 21
 import requests
22
+from ContentSources import ContentSources
23
+from sources.SourceBase import stream_type
24
+import util
25
+from util import streamproxy_decode3, streamproxy_encode3
22 26
 
23 27
 try:
24 28
     from requests.packages.urllib3.exceptions import InsecureRequestWarning
@@ -28,23 +32,32 @@ except:
28 32
 
29 33
 HOST_NAME = ""
30 34
 PORT_NUMBER = 8880
31
-DEBUG = True
35
+DEBUG = False
32 36
 DEBUG2 = False
33 37
 
34 38
 SPLIT_CHAR = "~"
35 39
 SPLIT_CODE = "%7E"
36 40
 EQ_CODE = "%3D"
37 41
 COL_CODE = "%3A"
42
+cunicode = lambda s: s.decode("utf8") if isinstance(s, str) else s
43
+cstr = lambda s: s.encode("utf8") if isinstance(s, unicode) else s
38 44
 headers2dict = lambda  h: dict([l.strip().split(": ") for l in h.strip().splitlines()])
45
+
39 46
 headers0 = headers2dict("""
40
-icy-metadata: 1
41 47
 User-Agent: GStreamer souphttpsrc libsoup/2.52.2
48
+icy-metadata: 1
42 49
 """)
43
-sessions = {}
50
+headers0_ = headers2dict("""
51
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
52
+User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36
53
+""")
54
+
44 55
 cur_directory = os.path.dirname(os.path.realpath(__file__))
45
-sources = None
46 56
 slinks = {}
57
+sessions = {}
47 58
 
59
+cfg_file = "streams.cfg"
60
+sources = ContentSources("sources", cfg_file)
48 61
 
49 62
 class StreamHandler(BaseHTTPRequestHandler):
50 63
 
@@ -65,39 +78,47 @@ class StreamHandler(BaseHTTPRequestHandler):
65 78
 
66 79
     def do_GET(self):
67 80
         """Respond to a GET request"""
68
-        print "\n\n"+40*"#"+"\nget_url: \n%s", self.path
69
-        p = self.path.split("~")
70
-        #url = urllib.unquote(p[0][1:]) # TODO - vajag nocekot vai visi urli strādā
71
-        urlp = p[0][1:]
72
-        url = urlp.replace(COL_CODE, ":")
73
-        #headers = self.headers.dict
74
-        headers = {} # TODO izmanto saņemtos headerus, var aizvietot ar defaultajiem
75
-        #headers["host"] = urlparse.urlparse(url).hostname
76
-        if len(p)>1:
77
-            for h in p[1:]:
78
-                k = h.split("=")[0].lower()
79
-                v = urllib.unquote(h.split("=")[1])
80
-                headers[k]=v
81
+        #
82
+        print "\n\n"+40*"#"+"\nget_url: \n%s" % self.path
83
+        cmd = self.path.split("/")[1]
81 84
         if DEBUG:
82
-            print "url=%s"%url
85
+            print "cmd=%s"%cmd
83 86
             print "Original request headers + url headers:"
84 87
             print_headers(self.headers.dict)
85 88
         self.protocol_version = 'HTTP/1.1'
86
-
87 89
         try:
88
-            if "::" in url: # encoded source link
89
-                self.fetch_source(urlp, headers)
90
-            elif ".lattelecom.tv/" in url: # lattelecom.tv hack
91
-                self.fetch_ltc( url, headers)
92
-            elif "filmas.lv" in url or "viaplay" in url: #  HLS session/decode filmas.lv in url:
93
-                self.fetch_url2(url, headers)
94
-            else: # plain fetch
95
-                self.fetch_url( url, headers)
90
+            if cmd == "playstream":
91
+                self.fetch_source( self.path)
92
+            elif cmd in ["get_content", "get_streams", "get_info", "is_video", "options_read", "options_write"]:
93
+                cmd, data, headers, qs = streamproxy_decode3(self.path)
94
+                if cmd == "get_content":
95
+                    content = sources.get_content(data)
96
+                elif cmd == "get_streams":
97
+                    content = sources.get_streams(data)
98
+                elif cmd == "get_info":
99
+                    content = sources.get_info(data)
100
+                elif cmd == "is_video":
101
+                    content = sources.is_video(data)
102
+                elif cmd == "options_read":
103
+                    content = sources.options_read(data)
104
+                else:
105
+                    content = []
106
+                txt = json.dumps(content)
107
+                self.send_response(200)
108
+                self.send_header("Server", "playstreamproxy")
109
+                self.send_header("Content-type", "application/json")
110
+                self.end_headers()
111
+                self.wfile.write(txt)
112
+                self.wfile.close()
113
+            else:
114
+                self.write_error(404)
115
+
96 116
         except Exception as e:
97 117
             print "Got Exception: ", str(e)
98 118
             import traceback
99 119
             traceback.print_exc()
100 120
 
121
+
101 122
     ### Remote server request procedures ###
102 123
 
103 124
     def fetch_offline(self):
@@ -109,21 +130,24 @@ class StreamHandler(BaseHTTPRequestHandler):
109 130
         self.wfile.write(open("offline.mp4", "rb").read())
110 131
         #self.wfile.close()
111 132
 
112
-    def fetch_source(self, urlp, headers):
133
+    def fetch_source(self, urlp):
134
+        cmd, data, headers, qs = streamproxy_decode3(urlp)
113 135
         if DEBUG:
114 136
             print "\n***********************************************************"
115 137
             print "fetch_source: \n%s"%urlp
116 138
         base_data = hls_base(urlp)
117
-        data = urllib.unquote_plus(base_data)[:-1]
118
-        if DEBUG: print "base_data=", base_data
119
-        if DEBUG: print "data=", data
120
-        if not base_data in slinks :
139
+        if DEBUG:
140
+            print "base_data=", base_data
141
+            print "data=", data
142
+        if not base_data in slinks:
121 143
             streams = sources.get_streams(data)
122 144
             if not streams:
123 145
                 self.write_error(500)  # TODO
124 146
                 return
125
-            url = streams[0]["url"]
126
-            base_url = hls_base(url)
147
+            stream = streams[0]
148
+            url = stream["url"]
149
+            headers = stream["headers"] if "headers" in stream else headers0
150
+            base_url = hls_base2(url)
127 151
             if DEBUG: print "New link, base_url=",base_url
128 152
             ses = requests.Session()
129 153
             ses.trust_env = False
@@ -136,40 +160,23 @@ class StreamHandler(BaseHTTPRequestHandler):
136 160
             else:
137 161
                 url = urlp.replace(base_data, slinks[base_data]["base_url"])
138 162
                 if DEBUG: print "Existing new link", url
139
-        r = self.get_page_ses(url,ses,True,headers = headers)
163
+        headers2 = headers if headers else self.headers.dict
164
+        headers2 = del_headers(headers2, ["host"])
165
+        r = self.get_page_ses(url,ses,True,headers = headers2)
140 166
         code = r.status_code
141
-        if not code in (200,206): # TODO mēģina vēlreiz get_streams
167
+        if not code in (200,206): # TODO 206 apstrāde!
142 168
             self.write_error(code)
143 169
             return
170
+        if code == 206:
171
+            print "Code=206"
144 172
         self.send_response(code)
173
+        #headers2 = del_headers(r.headers, ["Content-Encoding"])
145 174
         self.send_headers(r.headers)
146 175
         CHUNK_SIZE = 1024 *4
147
-        for chunk in r.iter_content(chunk_size=CHUNK_SIZE):
148
-            try:
149
-                self.wfile.write(chunk)
150
-            except Exception as e:
151
-                print "Exception: ", str(e)
152
-                self.wfile.close()
153
-                return
154
-        if DEBUG: print "**File downloaded"
155
-        if "connection" in r.headers and r.headers["connection"] <> "keep-alive":
156
-            self.wfile.close()
157
-        return
158
-
159
-
160
-    def fetch_url(self, url,headers):
161
-        if DEBUG:
162
-            print "\n***********************************************************"
163
-            print "fetch_url: \n%s"%url
164
-        r = self.get_page(url,headers = headers)
165
-        code = r.status_code
166
-        if not code in (200,206):
167
-            self.write_error(code)
168
-            return
169
-        self.send_response(code)
170
-        self.send_headers(r.headers)
171
-        CHUNK_SIZE = 1024*4
172
-        for chunk in r.iter_content(chunk_size=CHUNK_SIZE):
176
+        while True:
177
+            chunk = r.raw.read(CHUNK_SIZE, decode_content=False)
178
+            if not chunk:
179
+                break
173 180
             try:
174 181
                 self.wfile.write(chunk)
175 182
             except Exception as e:
@@ -177,180 +184,11 @@ class StreamHandler(BaseHTTPRequestHandler):
177 184
                 self.wfile.close()
178 185
                 return
179 186
         if DEBUG: print "**File downloaded"
180
-        if "connection" in r.headers and r.headers["connection"] <> "keep-alive":
181
-            self.wfile.close()
182
-        return
183
-
184
-    def fetch_ltc(self, url, headers):
185
-        "lattelecom.tv hack (have to update chunklist after each 6 min"
186
-        if DEBUG:
187
-            print "\n\n***********************************************************"
188
-            print "fetch_ltc: \n%s"%url
189
-        base_url = hls_base(url)
190
-        if DEBUG: print "base_url=",base_url
191
-        if base_url not in sessions:
192
-            if DEBUG: print "New session"
193
-            sessions[base_url] = {}
194
-            sessions[base_url]["session"] = requests.Session()
195
-            sessions[base_url]["session"].trust_env = False
196
-            sessions[base_url]["session"].headers.update(headers0)
197
-            sessions[base_url]["playlist"] = ""
198
-            sessions[base_url]["chunklist"] = []
199
-
200
-        #  change ts file to valid one media_w215689190_33.ts?
201
-        tsfile = re.search("media_\w+_(\d+)\.ts", url, re.IGNORECASE)
202
-        if tsfile and sessions[base_url]["chunklist"]:
203
-            tnum = int(tsfile.group(1))
204
-            url2 = sessions[base_url]["chunklist"][tnum]
205
-            if not url2.startswith("http"):
206
-                url2 = base_url + url2
207
-            url = url2
208
-            if DEBUG: print "[playstreamproxy] url changed to ", url
209
-
210
-        ### get_page ###
211
-        ses = sessions[base_url]["session"]
212
-        #ses.headers.update(headers0)
213
-        ses.headers.update(headers)
214
-        # ses.headers["Connection"]="Keep-Alive"
215
-        r = self.get_page_ses(url,ses)
216
-        code = r.status_code #r.status_code
217
-
218
-        if not (code in (200,206)) and tsfile:
219
-            # update chunklist
220
-            r2 = self.get_page(sessions[base_url]["playlist"])
221
-            streams = re.findall(r"#EXT-X-STREAM-INF:.*?BANDWIDTH=(\d+).*?\n(.+?)$", r2.content, re.IGNORECASE | re.MULTILINE)
222
-            if streams:
223
-                sorted(streams, key=lambda item: int(item[0]), reverse=True)
224
-                chunklist = streams[0][1]
225
-                if not chunklist.startswith("http"):
226
-                    chunklist = base_url + chunklist
227
-            else:
228
-                self.write_error(r.status_code)
229
-                return
230
-            print "[playstreamproxy] trying to update chunklist", chunklist
231
-            r3 = self.get_page_ses(chunklist,ses,True)
232
-            ts_list = re.findall(r"#EXTINF:.*?\n(.+?)$", r3.content, re.IGNORECASE | re.MULTILINE)
233
-            sessions[base_url]["chunklist"]= ts_list
234
-            tnum = int(tsfile.group(1))
235
-            url2 = sessions[base_url]["chunklist"][tnum]
236
-            if not url2.startswith("http"):
237
-                url2 = base_url + url2
238
-            r = self.get_page_ses(url2,ses,True)
239
-            if not r.status_code in (200,206):
240
-                self.write_error(r.status_code)
241
-                return
242
-        elif not r.status_code in (200,206):
243
-            self.write_error(r.status_code)
244
-            return
245
-
246
-        if "playlist.m3u8" in url:
247
-            sessions[base_url]["playlist"] = url
248
-
249
-        ### Start of return formin and sending
250
-        self.send_response(200)
251
-        #headers2 = del_headers(r.headers,["Content-Encoding",'Transfer-Encoding',"Connection",'content-range',"range"])
252
-        headers2  = {"server":"playstreamproxy", "content-type":"text/html"}
253
-
254
-        if DEBUG: print "\n** Return  content"
255
-        headers2["content-type"]  = r.headers["content-type"]
256
-        if "content-length" in r.headers:
257
-            headers2["content-length"] = r.headers["content-length"]
258
-        self.send_headers(r.headers)
259
-        CHUNK_SIZE = 4 * 1024
260
-        for chunk in r.iter_content(chunk_size=CHUNK_SIZE):
261
-            try:
262
-                #print "#",
263
-                self.wfile.write(chunk)
264
-            except Exception as e:
265
-                print "Exception: ", str(e)
266
-                return
267
-        if DEBUG: print "File downloaded = "
187
+        #if "connection" in r.headers and r.headers["connection"] <> "keep-alive":
268 188
         self.wfile.close()
269
-        #time.sleep(1)
270 189
         return
271 190
 
272 191
 
273
-    def fetch_url2(self, url, headers):
274
-        if DEBUG:
275
-            print "\n***********************************************************"
276
-            print "fetch_url2: \n%s"%url
277
-        base_url = hls_base(url)
278
-        if DEBUG: print "base_url=",base_url
279
-        if base_url not in sessions:
280
-            if DEBUG: print "New session"
281
-            sessions[base_url] = {}
282
-            sessions[base_url]["session"] = requests.Session()
283
-            sessions[base_url]["session"].trust_env = False
284
-            sessions[base_url]["session"].headers.update(headers0)
285
-            sessions[base_url]["key"] = binascii.a2b_hex(headers["key"]) if "key" in headers and headers["key"] else None
286
-        ses = sessions[base_url]["session"]
287
-        ses.trust_env = False
288
-        key = sessions[base_url]["key"]
289
-        #ses.headers.clear()
290
-        ses.headers.update(headers)
291
-        r = self.get_page_ses(url, ses,stream=False)
292
-        code = r.status_code #r.status_code
293
-        if not (code in (200,206)):
294
-            self.write_error(r.status_code)
295
-            return
296
-
297
-        ### Start of return formin and sending
298
-        self.send_response(200)
299
-        #headers2 = del_headers(r.headers,["Content-Encoding",'Transfer-Encoding',"Connection",'content-range',"range"])
300
-        headers2  = {"server":"playstreamproxy", "content-type":"text/html"}
301
-
302
-        # Content-Type: application/vnd.apple.mpegurl (encrypted)
303
-        if r.headers["content-type"] == "application/vnd.apple.mpegurl" and key:
304
-            content = r.content
305
-            content = r.content.replace(base_url,"")
306
-            content = re.sub("#EXT-X-KEY:METHOD=AES-128.+\n", "", content, 0, re.IGNORECASE | re.MULTILINE)
307
-            headers2["content-type"] = "application/vnd.apple.mpegurl"
308
-            headers2["content-length"] = "%s"%len(content)
309
-            r.headers["content-length"] = "%s"%len(content)
310
-            #headers2['content-range'] = 'bytes 0-%s/%s'%(len(content)-1,len(content))
311
-            self.send_headers(headers2)
312
-            #self.send_headers(r.headers)
313
-            self.wfile.write(content)
314
-            self.wfile.close()
315
-
316
-        # Content-Type: video/MP2T (encrypted)
317
-        elif r.headers["content-type"] == "video/MP2T" and key:
318
-            print "Decode video/MP2T"
319
-            content = r.content
320
-            from Crypto.Cipher import AES
321
-            iv = content[:16]
322
-            d = AES.new(key, AES.MODE_CBC, iv)
323
-            content = d.decrypt(content[16:])
324
-            headers2["content-type"] = "video/MP2T"
325
-            headers2["content-length"] = "%s"% (len(content))
326
-            #headers2['content-range'] = 'bytes 0-%s/%s' % (len(content) - 1, len(content))
327
-            print content[0:16]
328
-            print "Finish decode"
329
-            self.send_headers(headers2)
330
-            self.wfile.write(content)
331
-            self.wfile.close()
332
-
333
-        else:
334
-            if DEBUG: print "Return regular content"
335
-            headers2["content-type"]  = r.headers["content-type"]
336
-            if "content-length" in r.headers:
337
-                headers2["content-length"] = r.headers["content-length"]
338
-            self.send_headers(r.headers)
339
-            #self.send_headers(headers2)
340
-            CHUNK_SIZE = 4 * 1024
341
-            for chunk in r.iter_content(chunk_size=CHUNK_SIZE):
342
-                try:
343
-                    #print "#",
344
-                    self.wfile.write(chunk)
345
-                except Exception as e:
346
-                    print "Exception: ", str(e)
347
-                    return
348
-            if DEBUG: print "File downloaded = "
349
-            if "connection" in r.headers and r.headers["connection"]<>"keep-alive":
350
-                self.wfile.close()
351
-            #time.sleep(1)
352
-            return
353
-
354 192
     def send_headers(self,headers):
355 193
         #if DEBUG:
356 194
             #print "**Return headers: "
@@ -403,9 +241,6 @@ class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
403 241
     """Handle requests in a separate thread."""
404 242
 
405 243
 def start(host = HOST_NAME, port = PORT_NUMBER):
406
-    import ContentSources, util
407
-    global sources
408
-    sources = ContentSources.ContentSources(os.path.join(cur_directory, "sources"))
409 244
     httpd = ThreadedHTTPServer((host, port), StreamHandler)
410 245
     print time.asctime(), "Server Starts - %s:%s" % (HOST_NAME, PORT_NUMBER)
411 246
     try:
@@ -558,9 +393,30 @@ def del_headers(headers0,tags):
558 393
     return headers
559 394
 
560 395
 def hls_base(url):
561
-    url2 = url.split("?")[0]
562
-    url2 = "/".join(url2.split("/")[0:-1])+ "/"
563
-    return url2
396
+    base = url.split("?")[0]
397
+    base = "/".join(base.split("/")[0:3])+ "/"
398
+    rest = url.replace(base, "")
399
+    return base
400
+
401
+def hls_base2(url):
402
+    base = url.split("?")[0]
403
+    base = "/".join(base.split("/")[0:-1])+ "/"
404
+    rest = url.replace(base, "")
405
+    return base
406
+
407
+print streamproxy_encode3("get_content", "ltc::home", qs={"user": "ivars777", "password": "Kaskade7"})
408
+print streamproxy_encode3("get_content", "ltc::content/live-streams/101?include=quality", qs={"user": "ivars777", "password": "Kaskade7"})
409
+urlp = streamproxy_encode3("playstream",
410
+                          "ltc::content/live-streams/101?include=quality",
411
+                          #headers=headers0,
412
+                          qs={"user": "ivars777", "password": "Kaskade7"})
413
+print urlp
414
+print streamproxy_encode3("playstream", "replay::tiesraide/ltv1")
415
+print streamproxy_encode3("playstream", "tvdom::tiesraides/ltv1/")
416
+print streamproxy_encode3("playstream", "tvplay::asset/10311641")
417
+print streamproxy_encode3("playstream", "xtv::rigatv24/video/Zw4pVPqr7OX-festivals_mainam_pasauli_sakam_ar_sevi")
418
+print streamproxy_encode3("playstream", "iplayer::episodes/b094f49s")
419
+#cmd, data, headers, qs = streamproxy_decode3(urlp)
564 420
 
565 421
 if __name__ == "__main__":
566 422
     daemon = ProxyDaemon("/var/run/playstreamproxy.pid")

+ 583
- 0
playstreamproxy0.py Näytä tiedosto

@@ -0,0 +1,583 @@
1
+#!/usr/bin/python
2
+# -*- coding: utf-8 -*-
3
+"""
4
+StreamProxy daemon (based on Livestream daemon)
5
+Ensures persistent cookies, User-Agents and others tricks to play protected HLS/DASH streams
6
+"""
7
+import os
8
+import sys
9
+import time
10
+import atexit
11
+import re
12
+import binascii
13
+
14
+from signal import SIGTERM
15
+
16
+from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
17
+from SocketServer import ThreadingMixIn
18
+from urllib import unquote, quote
19
+import urllib,urlparse
20
+#import cookielib,urllib2
21
+import requests
22
+
23
+try:
24
+    from requests.packages.urllib3.exceptions import InsecureRequestWarning
25
+    requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
26
+except:
27
+    pass
28
+
29
+HOST_NAME = ""
30
+PORT_NUMBER = 8880
31
+DEBUG = True
32
+DEBUG2 = False
33
+
34
+SPLIT_CHAR = "~"
35
+SPLIT_CODE = "%7E"
36
+EQ_CODE = "%3D"
37
+COL_CODE = "%3A"
38
+headers2dict = lambda  h: dict([l.strip().split(": ") for l in h.strip().splitlines()])
39
+headers0 = headers2dict("""
40
+icy-metadata: 1
41
+User-Agent: GStreamer souphttpsrc libsoup/2.52.2
42
+""")
43
+sessions = {}
44
+cur_directory = os.path.dirname(os.path.realpath(__file__))
45
+sources = None
46
+slinks = {}
47
+
48
+
49
+class StreamHandler(BaseHTTPRequestHandler):
50
+
51
+    def do_HEAD(self):
52
+        print "**get_head"
53
+        self.send_response(200)
54
+        self.send_header("Server", "playstreamproxy")
55
+        if ".m3u8" in self.path.lower():
56
+            ct = "application/vnd.apple.mpegurl"
57
+        elif ".ts" in self.path.lower():
58
+            ct = "video/MP2T"
59
+        elif ".mp4" in self.path.lower():
60
+            ct = "video/mp4"
61
+        else:
62
+            ct = "text/html"
63
+        self.send_header("Content-type", ct)
64
+        self.end_headers()
65
+
66
+    def do_GET(self):
67
+        """Respond to a GET request"""
68
+        print "\n\n"+40*"#"+"\nget_url: \n%s", self.path
69
+        p = self.path.split("~")
70
+        #url = urllib.unquote(p[0][1:]) # TODO - vajag nocekot vai visi urli strādā
71
+        urlp = p[0][1:]
72
+        url = urlp.replace(COL_CODE, ":")
73
+        #headers = self.headers.dict
74
+        headers = {} # TODO izmanto saņemtos headerus, var aizvietot ar defaultajiem
75
+        #headers["host"] = urlparse.urlparse(url).hostname
76
+        if len(p)>1:
77
+            for h in p[1:]:
78
+                k = h.split("=")[0].lower()
79
+                v = urllib.unquote(h.split("=")[1])
80
+                headers[k]=v
81
+        if DEBUG:
82
+            print "url=%s"%url
83
+            print "Original request headers + url headers:"
84
+            print_headers(self.headers.dict)
85
+        self.protocol_version = 'HTTP/1.1'
86
+
87
+        try:
88
+            if "::" in url: # encoded source link
89
+                self.fetch_source(urlp, headers)
90
+            elif ".lattelecom.tv/" in url: # lattelecom.tv hack
91
+                self.fetch_ltc( url, headers)
92
+            elif "filmas.lv" in url or "viaplay" in url: #  HLS session/decode filmas.lv in url:
93
+                self.fetch_url2(url, headers)
94
+            else: # plain fetch
95
+                self.fetch_url( url, headers)
96
+        except Exception as e:
97
+            print "Got Exception: ", str(e)
98
+            import traceback
99
+            traceback.print_exc()
100
+
101
+    ### Remote server request procedures ###
102
+
103
+    def fetch_offline(self):
104
+        print "** Fetch offline"
105
+        self.send_response(200)
106
+        self.send_header("Server", "playstreamproxy")
107
+        self.send_header("Content-type", "video/mp4")
108
+        self.end_headers()
109
+        self.wfile.write(open("offline.mp4", "rb").read())
110
+        #self.wfile.close()
111
+
112
+    def fetch_source(self, urlp, headers):
113
+        if DEBUG:
114
+            print "\n***********************************************************"
115
+            print "fetch_source: \n%s"%urlp
116
+        base_data = hls_base(urlp)
117
+        data = urllib.unquote_plus(base_data)[:-1]
118
+        if DEBUG: print "base_data=", base_data
119
+        if DEBUG: print "data=", data
120
+        if not base_data in slinks :
121
+            streams = sources.get_streams(data)
122
+            if not streams:
123
+                self.write_error(500)  # TODO
124
+                return
125
+            url = streams[0]["url"]
126
+            base_url = hls_base(url)
127
+            if DEBUG: print "New link, base_url=",base_url
128
+            ses = requests.Session()
129
+            ses.trust_env = False
130
+            slinks[base_data] = {"data": data, "urlp":urlp,"url": url, "base_url": base_url,"session":ses}
131
+        else:
132
+            ses = slinks[base_data]["session"]
133
+            if urlp == slinks[base_data]["urlp"]:
134
+                url = slinks[base_data]["url"]
135
+                if DEBUG: print "Existing base link", url
136
+            else:
137
+                url = urlp.replace(base_data, slinks[base_data]["base_url"])
138
+                if DEBUG: print "Existing new link", url
139
+        r = self.get_page_ses(url,ses,True,headers = headers)
140
+        code = r.status_code
141
+        if not code in (200,206): # TODO mēģina vēlreiz get_streams
142
+            self.write_error(code)
143
+            return
144
+        self.send_response(code)
145
+        self.send_headers(r.headers)
146
+        CHUNK_SIZE = 1024 *4
147
+        for chunk in r.iter_content(chunk_size=CHUNK_SIZE):
148
+            try:
149
+                self.wfile.write(chunk)
150
+            except Exception as e:
151
+                print "Exception: ", str(e)
152
+                self.wfile.close()
153
+                return
154
+        if DEBUG: print "**File downloaded"
155
+        if "connection" in r.headers and r.headers["connection"] <> "keep-alive":
156
+            self.wfile.close()
157
+        return
158
+
159
+
160
+    def fetch_url(self, url,headers):
161
+        if DEBUG:
162
+            print "\n***********************************************************"
163
+            print "fetch_url: \n%s"%url
164
+        r = self.get_page(url,headers = headers)
165
+        code = r.status_code
166
+        if not code in (200,206):
167
+            self.write_error(code)
168
+            return
169
+        self.send_response(code)
170
+        self.send_headers(r.headers)
171
+        CHUNK_SIZE = 1024*4
172
+        for chunk in r.iter_content(chunk_size=CHUNK_SIZE):
173
+            try:
174
+                self.wfile.write(chunk)
175
+            except Exception as e:
176
+                print "Exception: ", str(e)
177
+                self.wfile.close()
178
+                return
179
+        if DEBUG: print "**File downloaded"
180
+        if "connection" in r.headers and r.headers["connection"] <> "keep-alive":
181
+            self.wfile.close()
182
+        return
183
+
184
+    def fetch_ltc(self, url, headers):
185
+        "lattelecom.tv hack (have to update chunklist after each 6 min"
186
+        if DEBUG:
187
+            print "\n\n***********************************************************"
188
+            print "fetch_ltc: \n%s"%url
189
+        base_url = hls_base(url)
190
+        if DEBUG: print "base_url=",base_url
191
+        if base_url not in sessions:
192
+            if DEBUG: print "New session"
193
+            sessions[base_url] = {}
194
+            sessions[base_url]["session"] = requests.Session()
195
+            sessions[base_url]["session"].trust_env = False
196
+            sessions[base_url]["session"].headers.update(headers0)
197
+            sessions[base_url]["playlist"] = ""
198
+            sessions[base_url]["chunklist"] = []
199
+
200
+        #  change ts file to valid one media_w215689190_33.ts?
201
+        tsfile = re.search("media_\w+_(\d+)\.ts", url, re.IGNORECASE)
202
+        if tsfile and sessions[base_url]["chunklist"]:
203
+            tnum = int(tsfile.group(1))
204
+            url2 = sessions[base_url]["chunklist"][tnum]
205
+            if not url2.startswith("http"):
206
+                url2 = base_url + url2
207
+            url = url2
208
+            if DEBUG: print "[playstreamproxy] url changed to ", url
209
+
210
+        ### get_page ###
211
+        ses = sessions[base_url]["session"]
212
+        #ses.headers.update(headers0)
213
+        ses.headers.update(headers)
214
+        # ses.headers["Connection"]="Keep-Alive"
215
+        r = self.get_page_ses(url,ses)
216
+        code = r.status_code #r.status_code
217
+
218
+        if not (code in (200,206)) and tsfile:
219
+            # update chunklist
220
+            r2 = self.get_page(sessions[base_url]["playlist"])
221
+            streams = re.findall(r"#EXT-X-STREAM-INF:.*?BANDWIDTH=(\d+).*?\n(.+?)$", r2.content, re.IGNORECASE | re.MULTILINE)
222
+            if streams:
223
+                sorted(streams, key=lambda item: int(item[0]), reverse=True)
224
+                chunklist = streams[0][1]
225
+                if not chunklist.startswith("http"):
226
+                    chunklist = base_url + chunklist
227
+            else:
228
+                self.write_error(r.status_code)
229
+                return
230
+            print "[playstreamproxy] trying to update chunklist", chunklist
231
+            r3 = self.get_page_ses(chunklist,ses,True)
232
+            ts_list = re.findall(r"#EXTINF:.*?\n(.+?)$", r3.content, re.IGNORECASE | re.MULTILINE)
233
+            sessions[base_url]["chunklist"]= ts_list
234
+            tnum = int(tsfile.group(1))
235
+            url2 = sessions[base_url]["chunklist"][tnum]
236
+            if not url2.startswith("http"):
237
+                url2 = base_url + url2
238
+            r = self.get_page_ses(url2,ses,True)
239
+            if not r.status_code in (200,206):
240
+                self.write_error(r.status_code)
241
+                return
242
+        elif not r.status_code in (200,206):
243
+            self.write_error(r.status_code)
244
+            return
245
+
246
+        if "playlist.m3u8" in url:
247
+            sessions[base_url]["playlist"] = url
248
+
249
+        ### Start of return formin and sending
250
+        self.send_response(200)
251
+        #headers2 = del_headers(r.headers,["Content-Encoding",'Transfer-Encoding',"Connection",'content-range',"range"])
252
+        headers2  = {"server":"playstreamproxy", "content-type":"text/html"}
253
+
254
+        if DEBUG: print "\n** Return  content"
255
+        headers2["content-type"]  = r.headers["content-type"]
256
+        if "content-length" in r.headers:
257
+            headers2["content-length"] = r.headers["content-length"]
258
+        self.send_headers(r.headers)
259
+        CHUNK_SIZE = 4 * 1024
260
+        for chunk in r.iter_content(chunk_size=CHUNK_SIZE):
261
+            try:
262
+                #print "#",
263
+                self.wfile.write(chunk)
264
+            except Exception as e:
265
+                print "Exception: ", str(e)
266
+                return
267
+        if DEBUG: print "File downloaded = "
268
+        self.wfile.close()
269
+        #time.sleep(1)
270
+        return
271
+
272
+
273
+    def fetch_url2(self, url, headers):
274
+        if DEBUG:
275
+            print "\n***********************************************************"
276
+            print "fetch_url2: \n%s"%url
277
+        base_url = hls_base(url)
278
+        if DEBUG: print "base_url=",base_url
279
+        if base_url not in sessions:
280
+            if DEBUG: print "New session"
281
+            sessions[base_url] = {}
282
+            sessions[base_url]["session"] = requests.Session()
283
+            sessions[base_url]["session"].trust_env = False
284
+            sessions[base_url]["session"].headers.update(headers0)
285
+            sessions[base_url]["key"] = binascii.a2b_hex(headers["key"]) if "key" in headers and headers["key"] else None
286
+        ses = sessions[base_url]["session"]
287
+        ses.trust_env = False
288
+        key = sessions[base_url]["key"]
289
+        #ses.headers.clear()
290
+        ses.headers.update(headers)
291
+        r = self.get_page_ses(url, ses,stream=False)
292
+        code = r.status_code #r.status_code
293
+        if not (code in (200,206)):
294
+            self.write_error(r.status_code)
295
+            return
296
+
297
+        ### Start of return formin and sending
298
+        self.send_response(200)
299
+        #headers2 = del_headers(r.headers,["Content-Encoding",'Transfer-Encoding',"Connection",'content-range',"range"])
300
+        headers2  = {"server":"playstreamproxy", "content-type":"text/html"}
301
+
302
+        # Content-Type: application/vnd.apple.mpegurl (encrypted)
303
+        if r.headers["content-type"] == "application/vnd.apple.mpegurl" and key:
304
+            content = r.content
305
+            content = r.content.replace(base_url,"")
306
+            content = re.sub("#EXT-X-KEY:METHOD=AES-128.+\n", "", content, 0, re.IGNORECASE | re.MULTILINE)
307
+            headers2["content-type"] = "application/vnd.apple.mpegurl"
308
+            headers2["content-length"] = "%s"%len(content)
309
+            r.headers["content-length"] = "%s"%len(content)
310
+            #headers2['content-range'] = 'bytes 0-%s/%s'%(len(content)-1,len(content))
311
+            self.send_headers(headers2)
312
+            #self.send_headers(r.headers)
313
+            self.wfile.write(content)
314
+            self.wfile.close()
315
+
316
+        # Content-Type: video/MP2T (encrypted)
317
+        elif r.headers["content-type"] == "video/MP2T" and key:
318
+            print "Decode video/MP2T"
319
+            content = r.content
320
+            from Crypto.Cipher import AES
321
+            iv = content[:16]
322
+            d = AES.new(key, AES.MODE_CBC, iv)
323
+            content = d.decrypt(content[16:])
324
+            headers2["content-type"] = "video/MP2T"
325
+            headers2["content-length"] = "%s"% (len(content))
326
+            #headers2['content-range'] = 'bytes 0-%s/%s' % (len(content) - 1, len(content))
327
+            print content[0:16]
328
+            print "Finish decode"
329
+            self.send_headers(headers2)
330
+            self.wfile.write(content)
331
+            self.wfile.close()
332
+
333
+        else:
334
+            if DEBUG: print "Return regular content"
335
+            headers2["content-type"]  = r.headers["content-type"]
336
+            if "content-length" in r.headers:
337
+                headers2["content-length"] = r.headers["content-length"]
338
+            self.send_headers(r.headers)
339
+            #self.send_headers(headers2)
340
+            CHUNK_SIZE = 4 * 1024
341
+            for chunk in r.iter_content(chunk_size=CHUNK_SIZE):
342
+                try:
343
+                    #print "#",
344
+                    self.wfile.write(chunk)
345
+                except Exception as e:
346
+                    print "Exception: ", str(e)
347
+                    return
348
+            if DEBUG: print "File downloaded = "
349
+            if "connection" in r.headers and r.headers["connection"]<>"keep-alive":
350
+                self.wfile.close()
351
+            #time.sleep(1)
352
+            return
353
+
354
+    def send_headers(self,headers):
355
+        #if DEBUG:
356
+            #print "**Return headers: "
357
+            #print_headers(headers)
358
+        for h in headers:
359
+            self.send_header(h, headers[h])
360
+        self.end_headers()
361
+
362
+    def write_error(self,code):
363
+        print "***Error, code=%s" % code
364
+        self.send_response(code)
365
+        #self.send_headers(r.headers)
366
+        self.wfile.close() # TODO?
367
+        # self.fetch_offline()
368
+
369
+    def get_page_ses(self,url,ses,stream=True, headers=None):
370
+        headers= headers if headers else headers0
371
+        ses.headers.update(headers)
372
+        if DEBUG:
373
+            print "\n\n====================================================\n**get_page_ses\n%s"%url
374
+            print "**Server request headers: "
375
+            print_headers(ses.headers)
376
+        r = ses.get(url, stream=stream, verify=False)
377
+        if DEBUG:
378
+            print "**Server response:", r.status_code
379
+            print "**Server response headers: "
380
+            print_headers(r.headers)
381
+        return r
382
+
383
+    def get_page(self,url,headers=None):
384
+        if not headers:
385
+            headers = headers0
386
+        if DEBUG:
387
+            print "\n\n====================================================\n**get_page\n%s"%url
388
+            print "**Server request headers: "
389
+            print_headers(headers)
390
+        r = requests.get(url, headers=headers,stream=True)
391
+        if DEBUG:
392
+            print "**Server response:", r.status_code
393
+            print "**Server response headers: "
394
+            print_headers(r.headers)
395
+        return r
396
+
397
+    def address_string(self):
398
+        host, port = self.client_address[:2]
399
+        #return socket.getfqdn(host)
400
+        return host
401
+
402
+class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
403
+    """Handle requests in a separate thread."""
404
+
405
+def start(host = HOST_NAME, port = PORT_NUMBER):
406
+    import ContentSources, util
407
+    global sources
408
+    sources = ContentSources.ContentSources(os.path.join(cur_directory, "sources"))
409
+    httpd = ThreadedHTTPServer((host, port), StreamHandler)
410
+    print time.asctime(), "Server Starts - %s:%s" % (HOST_NAME, PORT_NUMBER)
411
+    try:
412
+        httpd.serve_forever()
413
+    except KeyboardInterrupt:
414
+        pass
415
+    httpd.server_close()
416
+    print time.asctime(), "Server Stops - %s:%s" % (HOST_NAME, PORT_NUMBER)
417
+
418
+
419
+class Daemon:
420
+    """
421
+    A generic daemon class.
422
+    Usage: subclass the Daemon class and override the run() method
423
+    """
424
+    def __init__(self, pidfile, stdin="/dev/null", stdout="/dev/null", stderr="/dev/null"):
425
+        self.stdin = stdin
426
+        self.stdout = stdout
427
+        self.stderr = stderr
428
+        self.pidfile = pidfile
429
+
430
+    def daemonize(self):
431
+        """
432
+        do the UNIX double-fork magic, see Stevens' "Advanced
433
+        Programming in the UNIX Environment" for details (ISBN 0201563177)
434
+        http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16
435
+        """
436
+        try:
437
+            pid = os.fork()
438
+            if pid > 0:
439
+                # exit first parent
440
+                sys.exit(0)
441
+        except OSError, e:
442
+            sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
443
+            sys.exit(1)
444
+
445
+        # decouple from parent environment
446
+        os.chdir("/")
447
+        os.setsid()
448
+        os.umask(0)
449
+
450
+        # do second fork
451
+        try:
452
+            pid = os.fork()
453
+            if pid > 0:
454
+                # exit from second parent
455
+                sys.exit(0)
456
+        except OSError, e:
457
+            sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
458
+            sys.exit(1)
459
+
460
+        # redirect standard file descriptors
461
+        sys.stdout.flush()
462
+        sys.stderr.flush()
463
+        si = file(self.stdin, "r")
464
+        so = file(self.stdout, "a+")
465
+        se = file(self.stderr, "a+", 0)
466
+        os.dup2(si.fileno(), sys.stdin.fileno())
467
+        os.dup2(so.fileno(), sys.stdout.fileno())
468
+        os.dup2(se.fileno(), sys.stderr.fileno())
469
+
470
+        # write pidfile
471
+        atexit.register(self.delpid)
472
+        pid = str(os.getpid())
473
+        file(self.pidfile,"w+").write("%s\n" % pid)
474
+
475
+    def delpid(self):
476
+        os.remove(self.pidfile)
477
+
478
+    def start(self):
479
+        """
480
+        Start the daemon
481
+        """
482
+        # Check for a pidfile to see if the daemon already runs
483
+        try:
484
+            pf = file(self.pidfile,"r")
485
+            pid = int(pf.read().strip())
486
+            pf.close()
487
+        except IOError:
488
+            pid = None
489
+
490
+        if pid:
491
+            message = "pidfile %s already exist. Daemon already running?\n"
492
+            sys.stderr.write(message % self.pidfile)
493
+            sys.exit(1)
494
+
495
+        # Start the daemon
496
+        self.daemonize()
497
+        self.run()
498
+
499
+    def stop(self):
500
+        """
501
+        Stop the daemon
502
+        """
503
+        # Get the pid from the pidfile
504
+        try:
505
+            pf = file(self.pidfile,"r")
506
+            pid = int(pf.read().strip())
507
+            pf.close()
508
+        except IOError:
509
+            pid = None
510
+
511
+        if not pid:
512
+            message = "pidfile %s does not exist. Daemon not running?\n"
513
+            sys.stderr.write(message % self.pidfile)
514
+            return # not an error in a restart
515
+
516
+        # Try killing the daemon process
517
+        try:
518
+            while 1:
519
+                os.kill(pid, SIGTERM)
520
+                time.sleep(0.1)
521
+        except OSError, err:
522
+            err = str(err)
523
+            if err.find("No such process") > 0:
524
+                if os.path.exists(self.pidfile):
525
+                    os.remove(self.pidfile)
526
+            else:
527
+                print str(err)
528
+                sys.exit(1)
529
+
530
+    def restart(self):
531
+        """
532
+        Restart the daemon
533
+        """
534
+        self.stop()
535
+        self.start()
536
+
537
+    def run(self):
538
+        """
539
+        You should override this method when you subclass Daemon. It will be called after the process has been
540
+        daemonized by start() or restart().
541
+        """
542
+
543
+class ProxyDaemon(Daemon):
544
+    def run(self):
545
+        start()
546
+
547
+def print_headers(headers):
548
+    for h in headers:
549
+        print "%s: %s"%(h,headers[h])
550
+
551
+def del_headers(headers0,tags):
552
+    headers = headers0.copy()
553
+    for t in tags:
554
+        if t in headers:
555
+            del headers[t]
556
+        if t.lower() in headers:
557
+            del headers[t.lower()]
558
+    return headers
559
+
560
+def hls_base(url):
561
+    url2 = url.split("?")[0]
562
+    url2 = "/".join(url2.split("/")[0:-1])+ "/"
563
+    return url2
564
+
565
+if __name__ == "__main__":
566
+    daemon = ProxyDaemon("/var/run/playstreamproxy.pid")
567
+    if len(sys.argv) == 2:
568
+        if "start" == sys.argv[1]:
569
+            daemon.start()
570
+        elif "stop" == sys.argv[1]:
571
+            daemon.stop()
572
+        elif "restart" == sys.argv[1]:
573
+            daemon.restart()
574
+        elif "manualstart" == sys.argv[1]:
575
+            start()
576
+        else:
577
+            print "Unknown command"
578
+            sys.exit(2)
579
+        sys.exit(0)
580
+    else:
581
+        print "usage: %s start|stop|restart|manualstart" % sys.argv[0]
582
+        sys.exit(2)
583
+

+ 146
- 0
playstreamproxy5.py Näytä tiedosto

@@ -0,0 +1,146 @@
1
+# coding: utf-8
2
+u"""
3
+GIS failu web serveris
4
+"""
5
+prog = "web-server"
6
+version="%(prog)s v0.1"
7
+epilog = "(c)Ivars 2017"
8
+
9
+import sys, os, os.path, argparse
10
+import re,cgi, urllib, json
11
+import datetime,time
12
+
13
+from zope.interface import implements
14
+from twisted.internet import reactor,defer, threads, task
15
+from twisted.web import server, resource, http, error, util
16
+from twisted.python import log,logfile
17
+from twisted.web import static
18
+
19
+xset = lambda x,y: x if x else y
20
+
21
+options = []
22
+
23
+class ResponseFile(static.File):
24
+
25
+    def __init__(self, path):
26
+        static.File.__init__(self, path)
27
+
28
+    def render_GET(self, request):
29
+        #request.setHeader('Content-Disposition', ['attachment ; filename="tick_db_export.csv"'])
30
+        return static.File.render_GET(self, request)
31
+
32
+def parse_arguments(argv):
33
+    "Parse command line arguments"
34
+    parser = argparse.ArgumentParser(formatter_class=argparse.RawDescriptionHelpFormatter, description =__doc__,epilog=epilog, version=version, prog=prog)
35
+    parser.add_argument("-p", "--port",  type=int, help="web server port")
36
+    parser.add_argument("--debug", action="store_true", dest="debug", default=False, help="print debug info")
37
+    return parser.parse_args(argv), parser
38
+
39
+#import config   # get parameters from ini file
40
+def main(argv):
41
+
42
+    # get global parameters from command line arguments and ini file
43
+    global options
44
+    options,parser = parse_arguments(argv[1:])
45
+
46
+    #log.startLogging(logfile.DailyLogFile.fromFullPath("logs\\gis-faili.log"))
47
+    log.startLogging(sys.stdout)
48
+
49
+    # root = MyFile(base)               # no directory browsing
50
+    #root = Root()
51
+    #root = static.File("/")
52
+    #root = ResponseFile(".")
53
+    root = urlproxy.URLProxy()
54
+
55
+    # Start web server
56
+    reactor.listenTCP(8000, server.Site(root))
57
+    reactor.run()
58
+
59
+
60
+class ResourceThread(resource.Resource):
61
+    "Resource subclass, which renders request in thread. Redefine 'process' "
62
+    def render_GET(self, request):
63
+        d = threads.deferToThread(self.process,request)
64
+        d.addCallback(self._cb,request)
65
+        d.addErrback(self._eb,request)
66
+        request.notifyFinish().addErrback(self._connection_lost, d)
67
+        return server.NOT_DONE_YET
68
+
69
+    def render_POST(self, request):
70
+        d = threads.deferToThread(self.process,request)
71
+        d.addCallback(self._cb,request)
72
+        d.addErrback(self._eb,request)
73
+        request.notifyFinish().addErrback(self._connection_lost, d)
74
+        return server.NOT_DONE_YET
75
+
76
+    def _cb(self, result, request):
77
+        "Callback function"
78
+        request.write(result)
79
+        request.finish()
80
+
81
+    def _eb(self, failure, request):
82
+        "Errorback function"
83
+        #err = failure.trap(defer.CancelledError)
84
+        if failure == defer.CancelledError:
85
+            log.msg("Connection canceled")
86
+            return None
87
+        else:
88
+            #log.err(failure)
89
+            failure.printTraceback()
90
+            body = resource.ErrorPage(http.INTERNAL_SERVER_ERROR,
91
+                             "Internal errror, see log file for details","").render(request)
92
+        request.write(body)
93
+        request.finish()
94
+
95
+    def _connection_lost(self,err,d):
96
+        d.cancel()
97
+
98
+    def process(self,request):
99
+        "Process request. Shoud be redefined"
100
+        return "No process method defined"
101
+
102
+    def get_req(self,request,exclude=[]):
103
+        "Return request parameters as Rec() object in utf8, add user. Exclude parameters listed in exclude"
104
+        req = Rec()
105
+        for k,v in request.args.iteritems():
106
+            if k in exclude:
107
+                continue
108
+            req[k] = ",".join(v)
109
+            req[k] = req[k].decode("utf8")
110
+        req.user = request.getUser()
111
+        return req
112
+
113
+
114
+######### http commands handlers ####################
115
+class Root(ResourceThread):
116
+    "Server root"
117
+    isLeaf = True
118
+
119
+    def process(self,request):
120
+        "Apstrada pieprasijumu"
121
+        req = self.get_req(request)
122
+        request.responseHeaders.setRawHeaders("Content-type",['text/html; charset=utf-8'])
123
+        html = u"Response text".encode("utf8")
124
+        return html
125
+        #self.file = static.File("./aaa.mp4")
126
+        #request.uri = "/aaa.mp4"
127
+        #return self.file.render_GET(request)
128
+
129
+
130
+import UserDict
131
+class Rec(UserDict.DictMixin):
132
+    "Dict like class allowing a.x instead of a['x']"
133
+    def __init__(self, dict=None, **kwargs):
134
+        self.__dict__ = {}
135
+        if dict is not None:
136
+            self.update(dict)
137
+        if len(kwargs):
138
+            self.update(kwargs)
139
+    def __setitem__(self, name, value): setattr(self, name, value)
140
+    def __getitem__(self, name): return getattr(self,name,None)
141
+    def __getattr__(self,key): return None
142
+    def has_key(self,key): return True if self.__dict__.has_key(key) else False
143
+    def keys(self): return list(self.__dict__)
144
+
145
+if __name__ == "__main__":
146
+    sys.exit(main(sys.argv))

+ 605
- 462
project.wpr
File diff suppressed because it is too large
Näytä tiedosto


+ 1
- 0
run.py Näytä tiedosto

@@ -394,6 +394,7 @@ class VideoInfo(tkd.Dialog):
394 394
                     print "No image found ", img_path
395 395
             elif img:
396 396
                 fcache = img.replace(":", "_").replace("/", "-").replace(":", "_")
397
+                fcache = fcache.split("?")[0]
397 398
                 fcache = os.path.join(self.tmp_path, fcache)
398 399
                 if os.path.exists(fcache):
399 400
                     im =  Image.open(fcache)

+ 13
- 12
sources/tvdom.py Näytä tiedosto

@@ -189,7 +189,7 @@ Referer: https://tvdom.tv/
189 189
         elif clist=="play_arhivs" and len(data.split("/"))==3 and not re.search("_\d+",plist[2]):
190 190
             url = "https://tvdom.tv/"+data
191 191
             r = self._http_request(url)
192
-            vid=re.search('materialIdentifier : "(\d+)', r, re.DOTALL).group(1)
192
+            vid=re.search('materialIdentifier":"(\d+)', r, re.DOTALL).group(1)
193 193
             pname = re.split("[_\.\-]", plist[-1])[0]
194 194
             data2 = data+"_"+vid
195 195
             m = re.search(r'var program_title\s*= "(.+?)"', r)
@@ -206,8 +206,8 @@ Referer: https://tvdom.tv/
206 206
             r2 = r if i2 == -1 else r[:i2]
207 207
             result = re.findall(r'<img src="([^"]+)" class="content-item__image" alt=""><a href="/([^"]+)" class="content-item__hide-info">.+?<h3 class="content-item__name">\s*<a href="#">\s*([^<]*)\s*<br>\s*([^<]*)\s*</a>\s*</h3>', r2, re.DOTALL)
208 208
             for item in result:
209
-                if pname not in item[1]:
210
-                    continue
209
+                #if pname not in item[1]:
210
+                #    continue
211 211
                 title = item[2] + " " + item[3].strip()
212 212
                 title =  h.unescape(title.decode("utf8")).encode("utf8")
213 213
                 img = "https://tvdom.tv"+item[0]
@@ -289,21 +289,22 @@ Referer: https://tvdom.tv/
289 289
 
290 290
         # Tiešraides video
291 291
         else:
292
-            m = re.search("var streamConnectionUrl = '([^']+)'", r, re.DOTALL)
292
+            #m = re.search("var streamConnectionUrl = '([^']+)'", r, re.DOTALL)
293
+            m = re.search(r"sources: (\[.+?\]),", r, re.DOTALL)
293 294
             if m:
294
-                data2 = m.group(1)
295
+                js = json.loads(m.group(1))
296
+                data2 = js[0]["src"]
295 297
             else:
296 298
                 if "no_player_body" in r:
297 299
                     raise Exception("Lai skatītos maksas kanālus, nepieciešams iegādāties abonementu")
298
-                raise Exception("No stream found")
300
+                else:
301
+                    raise Exception("No stream found")
299 302
             m = re.search('title: "([^"]+)"', r, re.DOTALL)
300 303
             title = m.group(1) if m else data2
301
-            m = re.search('<div id="panel">([^<]+)<', r, re.DOTALL)
302
-            desc = m.group(1) if m else title
303
-            m = re.search('<div id="panel">([^<]+)<', r, re.DOTALL)
304
-            desc = m.group(1) if m else title
305
-            m = re.search('var promo_image *= "([^"]+)', r, re.DOTALL)
306
-            img = m.group(1) if m else ""
304
+            m = re.search('<div class="fp-descr">([^<]+)<', r, re.DOTALL)
305
+            desc = m.group(1).strip() if m else title
306
+            m = re.search('class="content-item content-item--on-air catchUpEvent".+?<img src="(.+?)"', r, re.DOTALL)
307
+            img = "https://tvdom.tv" + m.group(1) if m else ""
307 308
             stream = util.item()
308 309
             stream["name"] = title
309 310
             stream["url"] = data2

+ 48
- 0
util.py Näytä tiedosto

@@ -234,6 +234,54 @@ def streamproxy_decode2(urlp):
234 234
             headers[h.split("=")[0]]=urllib.unquote(h.split("=")[1])
235 235
     return url,headers
236 236
 
237
+
238
+def streamproxy_encode3(cmd, data, proxy_url=None, headers=None, qs=None):
239
+    PROXY_URL = "http://localhost:8880/"
240
+    #url2 = url.replace(SPLIT_CHAR,SPLIT_CODE).replace(":",COL_CODE).replace(" ",SPACE_CODE)
241
+    data2 = urllib.quote_plus(data)
242
+    if not proxy_url:
243
+        proxy_url = PROXY_URL
244
+    urlp = proxy_url + cmd + "/" + data2 + "/"
245
+    qs2 = []
246
+    if headers:
247
+        headers2 = []
248
+        for h in headers:
249
+            headers2.append("%s=%s"%(h,urllib.quote_plus(headers[h])))
250
+        headers2 = SPLIT_CHAR.join(headers2)
251
+        qs2.append("headers="+urllib.quote_plus(headers2))
252
+    if qs:
253
+        for qv in qs:
254
+            qs2.append(qv+"="+urllib.quote_plus(qs[qv]))
255
+    if qs2:
256
+        qs2 = "&".join(qs2)
257
+    if qs2:
258
+        urlp = urlp + "?" + qs2
259
+    return urlp
260
+
261
+def streamproxy_decode3(urlp):
262
+    m = re.search("http://[^/]+",urlp)
263
+    path = urlp.replace(m.group(0),"") if m else urlp
264
+    cmd = path.split("/")[1]
265
+    data = path.split("/")[2].split("?")[0]
266
+    data = urllib.unquote_plus(data)
267
+    qs = path.split("?")[1] if "?" in path else ""
268
+    qs2 = {}
269
+    headers = {}
270
+    if qs:
271
+        qs = qs.split("&")
272
+        for q in qs:
273
+            qk = q.split("=")[0]
274
+            if qk == "headers":
275
+                hh = urllib.unquote_plus(q.split("=")[1])
276
+                hh = hh.split(SPLIT_CHAR)
277
+                for h in hh:
278
+                    headers[h.split("=")[0]] = urllib.unquote_plus(h.split("=")[1])
279
+            else:
280
+                qv = urllib.unquote_plus(q.split("=")[1]) if "=" in q else ""
281
+                qs2[qk] = qv
282
+    return cmd, data, headers, qs2
283
+
284
+
237 285
 class Captions(object):
238 286
     def __init__(self,uri):
239 287
         self.uri = uri