Browse Source

Darba versija

Ivars 7 years ago
parent
commit
22e552ab48

+ 10
- 4
ContentSources.py View File

@@ -30,6 +30,7 @@ class ContentSources(object):
30 30
             fname = os.path.split(fname)[1]
31 31
             if fname == "__init__": continue
32 32
             if ext == '.py':
33
+                print "Importing %s"%fname
33 34
                 mod = __import__(fname)
34 35
                 reload(mod)
35 36
                 if "Source" in dir(mod):
@@ -180,7 +181,7 @@ if __name__ == "__main__":
180 181
             print s #.encode(sys.stdout.encoding,"replace")
181 182
 
182 183
         while True:
183
-            a = raw_input("Enter numeber, q for exit: ")
184
+            a = raw_input("Enter number, (-) for download, q for exit: ")
184 185
             if a in ("q","Q","x","X"):
185 186
                 exit_loop = True
186 187
                 print "Exiting"
@@ -191,12 +192,14 @@ if __name__ == "__main__":
191 192
             except:
192 193
                 print "Not number!"
193 194
         if exit_loop: break
195
+        download = False
196
+        if n<0:
197
+            n = abs(n)
198
+            download = True
194 199
         cur2 = content[n]
195
-
196 200
         data0 = cur2[1].split("::")[1] if "::" in cur2[1] else cur2[1]
197 201
         if not data0:
198 202
             pass
199
-
200 203
         elif cur2[1] == "back":
201 204
             cur = history.pop()
202 205
         elif sources.is_video(cur2[1]):
@@ -213,7 +216,10 @@ if __name__ == "__main__":
213 216
                     traceback.print_exc()
214 217
                     streams = []
215 218
             if streams:
216
-                util.play_video(streams)
219
+                if not download:
220
+                    util.play_video(streams)
221
+                else:
222
+                    Downloader.download_video(streams)
217 223
             else:
218 224
                 print "**No stream to play - %s "%(
219 225
                     cur2[1])

+ 288
- 63
Downloader.py View File

@@ -1,68 +1,293 @@
1
-from boxbranding import getMachineBrand, getMachineName
2
-
1
+#from boxbranding import getMachineBrand, getMachineName
2
+import sys,os, os.path, re
3
+import urlparse, requests
3 4
 from twisted.web import client
4 5
 from twisted.internet import reactor, defer, ssl
5 6
 
7
+USER_AGENT = "Enigma2 HbbTV/1.1.1 (+PVR+RTSP+DL;OpenATV;;;)"
6 8
 
9
+#####################################################################################################
7 10
 class HTTPProgressDownloader(client.HTTPDownloader):
8
-	def __init__(self, url, outfile, headers=None):
9
-		client.HTTPDownloader.__init__(self, url, outfile, headers=headers, agent="Enigma2 HbbTV/1.1.1 (+PVR+RTSP+DL;OpenATV;;;)")
10
-		self.status = None
11
-		self.progress_callback = None
12
-		self.deferred = defer.Deferred()
13
-
14
-	def noPage(self, reason):
15
-		if self.status == "304":
16
-			print reason.getErrorMessage()
17
-			client.HTTPDownloader.page(self, "")
18
-		else:
19
-			client.HTTPDownloader.noPage(self, reason)
20
-
21
-	def gotHeaders(self, headers):
22
-		if self.status == "200":
23
-			if headers.has_key("content-length"):
24
-				self.totalbytes = int(headers["content-length"][0])
25
-			else:
26
-				self.totalbytes = 0
27
-			self.currentbytes = 0.0
28
-		return client.HTTPDownloader.gotHeaders(self, headers)
29
-
30
-	def pagePart(self, packet):
31
-		if self.status == "200":
32
-			self.currentbytes += len(packet)
33
-		if self.totalbytes and self.progress_callback:
34
-			self.progress_callback(self.currentbytes, self.totalbytes)
35
-		return client.HTTPDownloader.pagePart(self, packet)
36
-
37
-	def pageEnd(self):
38
-		return client.HTTPDownloader.pageEnd(self)
39
-
40
-class downloadWithProgress:
41
-	def __init__(self, url, outputfile, contextFactory=None, *args, **kwargs):
42
-		if hasattr(client, '_parse'):
43
-			scheme, host, port, path = client._parse(url)
44
-		else:
45
-			from twisted.web.client import _URI
46
-			uri = _URI.fromBytes(url)
47
-			scheme = uri.scheme
48
-			host = uri.host
49
-			port = uri.port
50
-			path = uri.path
51
-
52
-		self.factory = HTTPProgressDownloader(url, outputfile, *args, **kwargs)
53
-		if scheme == "https":
54
-			self.connection = reactor.connectSSL(host, port, self.factory, ssl.ClientContextFactory())
55
-		else:
56
-			self.connection = reactor.connectTCP(host, port, self.factory)
57
-
58
-	def start(self):
59
-		return self.factory.deferred
60
-
61
-	def stop(self):
62
-		if self.connection:
63
-			print "[stop]"
64
-			self.connection.disconnect()
65
-
66
-	def addProgress(self, progress_callback):
67
-		print "[addProgress]"
68
-		self.factory.progress_callback = progress_callback
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
+        self.ts_file = open(self.outputfile, "wb")
118
+        self.download_fragment()
119
+
120
+    def download_fragment(self):
121
+        if self.ts_num>=len(self.ts_list):
122
+            pass
123
+            print "Call later"
124
+            reactor.callLater(10,self.update_manifest)
125
+            reactor.callLater(10, self.download_fragment)
126
+        else:
127
+            print "Start fragment download"
128
+            url = self.ts_list[self.ts_num][1]
129
+            if not "://" in url:
130
+                url = self.base_url+url
131
+            self.d = client.getPage(url,headers = self.headers)
132
+            self.d.addCallbacks(self.download_ok,self.download_err)
133
+
134
+
135
+    def download_ok(self,content):
136
+        content_length = len(content)
137
+        self.currentbytes += content_length
138
+        self.currenttime += float(self.ts_list[self.ts_num][0])
139
+        self.totalbytes = self.currentbytes * self.totaltime / self.currenttime
140
+        self.ts_num += 1
141
+        #print "Fragment %s downloaded (%s)"%(self.ts_num,len(content))
142
+        self.ts_file.write(content)
143
+        self.progress_callback(self.currentbytes, self.totalbytes)
144
+        if self.type == "vod":
145
+            if self.ts_num >= len(self.ts_list) or (self.limit and self.currenttime>self.limit):
146
+                self.ts_file.close()
147
+                self.download_finished()
148
+            else:
149
+                self.download_fragment()
150
+        else:
151
+            if self.limit and self.currenttime>self.limit: # TODO
152
+                self.ts_file.close()
153
+                self.download_finished()
154
+            else:
155
+                self.download_fragment()
156
+
157
+    def update_manifest(self):
158
+        self.d2 = client.getPage(self.url, headers=self.headers)
159
+        self.d2.addCallbacks(self.update_manifest_ok, self.update_manifest_err)
160
+
161
+    def update_manifest_ok(self,content):
162
+        print "Update manifest"
163
+        ts_list = re.findall(r"#EXTINF:([\d\.]+),\n(.+?)$", content, re.IGNORECASE | re.MULTILINE)
164
+        last_ts = self.ts_list[-1]
165
+        found = False
166
+        for ts in ts_list:
167
+            if ts == last_ts:
168
+                found = True
169
+            elif found:
170
+                print "Append %s"%ts[1]
171
+                self.ts_list.append(ts)
172
+        #reactor.callLater(5,self.download_fragment)
173
+
174
+    def update_manifest_err(self,content):
175
+        return
176
+
177
+    def download_err(self,content):
178
+        self.deferred.errback("Error while downloading %s"%self.ts_list[self.ts_num][1])
179
+
180
+    def download_finished(self):
181
+        self.totalbytes = self.currentbytes
182
+        self.deferred.callback("Done")
183
+
184
+    def start(self):
185
+        reactor.callLater(1,self.start_download)
186
+        return self.deferred
187
+
188
+    def stop(self):
189
+        self.deferred.errback() # TODO
190
+
191
+    def addProgress(self, progress_callback):
192
+        print "[addProgress]"
193
+        self.progress_callback = progress_callback
194
+
195
+#####################################################################################################
196
+def get_header(url,headers=None):
197
+    headers = {"user-agent":USER_AGENT}
198
+    r = requests.head(url,headers=headers)
199
+    return r.headers
200
+
201
+def get_ext(mtype):
202
+    stype = "http"
203
+    if mtype in ("vnd.apple.mpegURL","application/x-mpegURL",'application/x-mpegurl',"application/vnd.apple.mpegurl"):
204
+        return ".ts","hls"
205
+    elif mtype in ("application/dash+xml"):
206
+        return ".ts","dash" # TODO dash stream type  could be different !
207
+    elif mtype in ("video/mp4"):
208
+        return ".mp4","http"
209
+    elif mtype in ("video/MP2T","video/mp2t"):
210
+        return ".ts","http"
211
+    elif mtype in ("video/x-flv"):
212
+        return ".flv","http"
213
+    elif mtype in ("video/quicktime"):
214
+        return ".mov","http"
215
+    elif mtype in ("video/x-msvideo"):
216
+        return ".avi","http"
217
+    elif mtype in ("video/x-ms-wmv"):
218
+        return ".wmv","http"
219
+    elif mtype in ("video/x-matroska"):
220
+        return ".mkv","http"
221
+    else:
222
+        return ".mp4","http"
223
+
224
+
225
+
226
+##############################################
227
+def print_progress(currentbytes, totalbytes):
228
+    progress = float(currentbytes)/float(totalbytes)*100
229
+    print "%s (%i/%i)"%(progress,currentbytes,totalbytes)
230
+
231
+def download_ok(*args):
232
+    print "Download OK"
233
+    reactor.stop()
234
+
235
+def download_err(e):
236
+    print "Download Error %s"%e.getBriefTraceback()
237
+    pass
238
+def stop():
239
+    reactor.stop()
240
+###############################################
241
+
242
+def download_vide(stream):
243
+    stream = stream[0]
244
+    url = stream["url"]
245
+    headers = stream["headers"]
246
+    output = stream["name"].replace("\\"," ").replace(":"," ").replace("|"," ")
247
+    try:
248
+        h = get_header(url,headers={"user-agent":"Enigma2"})
249
+        mtype = h.get("content-type")
250
+        ext,stream_type = get_ext(mtype)
251
+    except:
252
+        ext,stream_type = (".ts","hls")
253
+    #output = urlparse.urlparse(url)[2].split('/')[-1] + ext
254
+    output = output+ext
255
+    output = os.path.join("downloads", output)
256
+    if stream_type == "hls":
257
+        d = DownloadWithProgressFragmented(url,output,headers={"user-agent":"Enigma2"})
258
+    else:
259
+        d = DownloadWithProgress(url,output,headers={"user-agent":"Enigma2"})
260
+    d.addProgress(print_progress)
261
+    d.start().addCallback(download_ok).addErrback(download_err)
262
+    reactor.run()
263
+
264
+
265
+if __name__ == "__main__":
266
+    if len(sys.argv)>2:
267
+        url= sys.argv[1]
268
+        output = sys.argv[1]
269
+    else:
270
+        url = "http://walterebert.com/playground/video/hls/ts/480x270.m3u8"
271
+        url = "https://r3---sn-bavc5ajvh-gpme.googlevideo.com/videoplayback?key=yt6&mime=video%2Fmp4&sparams=clen%2Cdur%2Cei%2Cgir%2Cid%2Cinitcwndbps%2Cip%2Cipbits%2Citag%2Clmt%2Cmime%2Cmm%2Cmn%2Cms%2Cmv%2Cpl%2Cratebypass%2Crequiressl%2Csource%2Cupn%2Cexpire&expire=1490986184&lmt=1490940183963773&dur=1302.639&itag=18&ratebypass=yes&mm=31&requiressl=yes&ipbits=0&upn=azFGj8gY02w&ip=85.254.87.15&pl=23&ei=aFDeWLzqDcn-dLC_gdAM&signature=083F353AC09CD98A70AD7D9438DD3C91C781166B.715456B9C35F040BDC4728CA76A0D1779B684A90&source=youtube&mv=m&mt=1490964451&ms=au&mn=sn-bavc5ajvh-gpme&gir=yes&clen=73596250&id=o-AGH9y-hWn1MtW1VzCyI_8XYYEWODsTDBZbfagQH3BrfQ&initcwndbps=4493750"
272
+        #url = "http://techslides.com/demos/sample-videos/small.mp4"
273
+        #url = "http://wx17.poiuytrew.pw/s/c507282042b1bf25e0b72c34a68426f3/hd_30/Jackie.2016.D.iTunes.BDRip.1080p_720.mp4"
274
+        #url = "http://player.tvnet.lv/live/amlst:11/chunklist_w361981294_b528000.m3u8"
275
+        #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"
276
+        #url = "https://58174450afee9.streamlock.net/vod/mp4:_definst_/f/e/8e49fc32.mp4/playlist.m3u8?safwerwfasendtime=1490877870&safwerwfasstarttime=1490859339&safwerwfashash=hS2FfVZysQVazBQ6RJn1IhUevBkKxIF09Ly3BjfT43U="
277
+    try:
278
+        h = get_header(url,headers={"user-agent":"Enigma2"})
279
+        mtype = h.get("content-type")
280
+        ext,stream_type = get_ext(mtype)
281
+    except:
282
+        ext,stream_type = (".ts","hls")
283
+    output = urlparse.urlparse(url)[2].split('/')[-1] + ext
284
+    output = os.path.join("downloads", output)
285
+    if stream_type == "hls":
286
+        d = DownloadWithProgressFragmented(url,output,headers={"user-agent":"Enigma2"})
287
+    else:
288
+        d = DownloadWithProgress(url,output,headers={"user-agent":"Enigma2"})
289
+    d.addProgress(print_progress)
290
+    d.start().addCallback(download_ok).addErrback(download_err)
291
+    reactor.run()
292
+
293
+

+ 47
- 20
PlayStream.py View File

@@ -6,14 +6,14 @@
6 6
 # Used fragments of code from enigma2-plugin-tv3play by Taapat (https://github.com/Taapat/enigma2-plugin-tv3play)
7 7
 #
8 8
 
9
-__version__ = "0.6l"
9
+__version__ = "0.6r"
10 10
 __id__ = "playstream"
11 11
 __title__ = "PlayStream"
12 12
 __author__ = "ivars777@gmail.com"
13 13
 __desc__ = "Play online video streams from various sources"
14 14
 
15 15
 import os,time,sys,os,os.path
16
-import datetime,re
16
+import datetime,re, traceback
17 17
 import urllib2
18 18
 
19 19
 from Plugins.Plugin import PluginDescriptor
@@ -53,7 +53,8 @@ from twisted.web.client import downloadPage,defer,reactor
53 53
 from plugin_locale import _
54 54
 import ContentSources
55 55
 import util
56
-from VideoDownload import downloadJob, HLSDownloadJob,VideoDownloadList
56
+from VideoDownload import DownloadJob, HLSDownloadJob,VideoDownloadList
57
+from Downloader import get_header, get_ext
57 58
 #import enigma2_api
58 59
 
59 60
 e2 = None
@@ -135,7 +136,7 @@ class MainScreen(Screen):
135 136
         #print self.content
136 137
         self["list"].setList(self.content)
137 138
         self["cur"].setText(self.content[0][3])
138
-        self.setTitle2(self.cur_menu[0])
139
+        self.setTitle2(self.cur_menu[0] + " (" + self.cur_menu[1].split("::")[0]+")")
139 140
         self.show_picture(self.content[0][2])
140 141
         reactor.callLater(1,self.check_update)
141 142
 
@@ -253,7 +254,7 @@ class MainScreen(Screen):
253 254
             return
254 255
 
255 256
         elif self.sources.is_video(current[1]):
256
-            if self.sources.stream_type(current[1]):
257
+            if ContentSources.stream_type(current[1]):
257 258
                 stream = util.item()
258 259
                 stream["url"] = current[1]
259 260
                 stream["name"] = current[0]
@@ -263,6 +264,7 @@ class MainScreen(Screen):
263 264
                     streams = self.sources.get_streams(current[1])
264 265
                 except Exception,e:
265 266
                     print str(e)
267
+                    traceback.print_exc()
266 268
                     self.session. open(MessageBox, "Error - %s"%str(e) , MessageBox.TYPE_INFO)
267 269
                     return
268 270
             if streams:
@@ -274,6 +276,7 @@ class MainScreen(Screen):
274 276
                 try:
275 277
                     self.session.open(PSPlayer, streams)
276 278
                 except Exception as e:
279
+                    traceback.print_exc()
277 280
                     self.msg("Error launching player - " + str(e))
278 281
                     return
279 282
             else:
@@ -288,7 +291,7 @@ class MainScreen(Screen):
288 291
                 index = zip(*new_content)[1].index(cur_menu_old[1])
289 292
             except:
290 293
                 index = 0
291
-            self.setTitle2(self.cur_menu[0])
294
+            self.setTitle2(self.cur_menu[0]+ " (" + self.cur_menu[1].split("::")[0]+")")
292 295
             self.show_content(new_content,index)
293 296
 
294 297
         else:
@@ -313,6 +316,7 @@ class MainScreen(Screen):
313 316
                 nfo = self.sources.get_info(self.current[1])
314 317
             except Exception, e:
315 318
                 print str(e)
319
+                traceback.print_exc()
316 320
                 self.session.open(MessageBox, "Error - %s" % str(e), MessageBox.TYPE_INFO)
317 321
                 return
318 322
         else:
@@ -333,9 +337,10 @@ class MainScreen(Screen):
333 337
                 new_content = self.sources.get_content(self.cur_menu[1])
334 338
             except Exception,e:
335 339
                 self.cur_menu = self.history.pop()
340
+                traceback.print_exc()
336 341
                 self.session. open(MessageBox, "Error - %s"%str(e) , MessageBox.TYPE_INFO)
337 342
                 return
338
-            self.setTitle2(self.cur_menu[0])
343
+            self.setTitle2(self.cur_menu[0] + " (" + self.cur_menu[1].split("::")[0]+")")
339 344
             self.show_content(new_content)
340 345
 
341 346
     def back(self):
@@ -483,7 +488,7 @@ class MainScreen(Screen):
483 488
         self.session.open(VideoDownloadList)
484 489
 
485 490
     def download_video(self,current, download_dir=""):
486
-        if self.sources.stream_type(current[1]):
491
+        if ContentSources.stream_type(current[1]):
487 492
             stream = util.item()
488 493
             stream["url"] = current[1]
489 494
             stream["name"] = current[0]
@@ -493,6 +498,7 @@ class MainScreen(Screen):
493 498
                 streams = self.sources.get_streams(current[1])
494 499
             except Exception,e:
495 500
                 print "Error - %s"%str(e)
501
+                traceback.print_exc()
496 502
                 self.session. open(MessageBox, "Error - %s"%str(e) , MessageBox.TYPE_INFO)
497 503
                 return
498 504
         if not streams:
@@ -514,26 +520,25 @@ class MainScreen(Screen):
514 520
         print "download stream",stream
515 521
         #self.msg("Start downloading..")
516 522
         self.stream = stream
517
-        stream_type = self.stream["type"] #self.sources.stream_type(stream["url"])
523
+        url = stream["url"]
524
+        headers = stream["headers"] if "headers" in stream else {}
525
+        stream_type = ContentSources.stream_type(url) #self.sources.stream_type(stream["url"])
518 526
         if not stream_type: #
519 527
             print "Not supported stream type found to download - %s"%(self.current[1])
520 528
             self.msg("Not supported stream type found to download - %s"%(self.current[1]))
521 529
             return
522 530
 
523 531
         title = self.stream["name"].strip()
524
-        url = self.stream["url"]
525
-        stream_type = self.stream["type"] #self.sources.stream_type(stream["url"])
526 532
         downloadDir = config.plugins.playstream.download_dir.value if not download_dir else download_dir
527 533
         if not os.path.exists(downloadDir):
528 534
             try:
529 535
                 os.mkdir(downloadDir)
530 536
             except Exception as e:
537
+                traceback.print_exc()
531 538
                 print 'Error creating download directory "%s"!\nPlease specify in the settings existing directory\n%s'%(downloadDir,str(e))
532 539
                 self.msg('Error creating download directory "%s"!\nPlease specify in the settings existing directory\n%s'%(downloadDir,str(e)))
533 540
                 return
534 541
         fname0 = util.make_fname(title)
535
-        fname = fname0 +".mp4"
536
-        outputfile = os.path.join(downloadDir, fname)
537 542
         #print "Trying to download - ", current
538 543
         if self.stream["img"]:
539 544
             try:
@@ -566,17 +571,39 @@ class MainScreen(Screen):
566 571
             nfofile = subfile = os.path.join(downloadDir,fname0+".nfo")
567 572
             with open(nfofile,"w") as f:
568 573
                 nfo_txt = util.nfo2xml(self.stream["nfo"])
569
-                f.write()
574
+                f.write(nfo_txt)
575
+        try:
576
+            h = get_header(url,headers)
577
+        except:
578
+            h = None
579
+        if not h:
580
+            self.session.open(MessageBox, "Can non get headers - %s" % url)
581
+            return False
582
+
583
+        mtype = h.get("content-type")
584
+        if not mtype:
585
+            mtype = "videp/mp4" # TODO default mtype if not content-type in header?
586
+
587
+        fext,stream_type = get_ext(mtype)
588
+        fname = fname0 + fext
589
+        outputfile = os.path.join(downloadDir, fname)
570 590
         overwrite = config.plugins.playstream.overwrite_download.value
571 591
         if not overwrite and os.path.exists(outputfile):
572 592
             self.msg( _('Sorry, this file already exists:\n%s') % outputfile)
573 593
             return False
574 594
             #os.remove(outputfile)
575 595
 
576
-        if stream_type in ("http","https"):
596
+        if stream_type in ("http","https", "hls"):
577 597
             print "\n**Download %s - %s"%(title,url)
578
-            #reload(downloadJob)
579
-            job_manager.AddJob(downloadJob(url, outputfile, title[:20], self.video_download_stop))
598
+            #reload(DownloadJob)
599
+            try:
600
+                job_manager.AddJob(DownloadJob(url, outputfile, title[:20], self.video_download_stop, headers, stream_type))
601
+            except Exception as e:
602
+                print str(e)
603
+                traceback.print_exc()
604
+                self.session.open(MessageBox, "Error - %s" % str(e), MessageBox.TYPE_INFO)
605
+                return  False
606
+
580 607
             self.active_downloads += 1
581 608
             #self.msg(_('Video download started!'))
582 609
             return True
@@ -595,7 +622,7 @@ class MainScreen(Screen):
595 622
             self.msg("RSTP stream download not yet implemented!")
596 623
             return False
597 624
         else:
598
-            self.msg("Unkown stream type!")
625
+            self.msg("Unkown stream type - %s!"%stream_type)
599 626
             return False
600 627
 
601 628
 
@@ -637,8 +664,8 @@ class MainScreen(Screen):
637 664
             self.session.open(OptionsScreen,self)
638 665
 
639 666
     def video_download_stop(self,title):
640
-        #self.activeDownloads -= 1
641
-        #self.msg("Download '%s'finished!"%title)
667
+        self.active_downloads -= 1
668
+        self.msg2(title)
642 669
         print "video_download_stop ", title
643 670
 
644 671
     def msg2(self,msg,timeout=10,mtype = None):

+ 231
- 216
VideoDownload.py View File

@@ -1,4 +1,5 @@
1 1
 import os
2
+from os import path
2 3
 
3 4
 from enigma import eTimer, getDesktop
4 5
 from Components.ActionMap import ActionMap
@@ -7,229 +8,243 @@ from Components.Sources.List import List
7 8
 from Components.Sources.StaticText import StaticText
8 9
 from Components.Task import Task, Job, job_manager
9 10
 from Screens.Screen import Screen
10
-from Tools.Downloader import downloadWithProgress
11 11
 from Tools.Directories import fileExists
12
-from os import path
12
+
13
+from Downloader import DownloadWithProgress, DownloadWithProgressFragmented,get_header
14
+
15
+
16
+def timer_callback(timer,callback):
17
+    if "callback" in dir(timer):
18
+        timer.callback.append(callback)
19
+    else:
20
+        timer_conn = timer.timeout.connect(callback)
21
+
22
+###########################################################################################
23
+class DownloadJob(Job):
24
+    def __init__(self, url, outputfile, title, downloadStop,headers=None,stream_type="http"):
25
+        Job.__init__(self, title)
26
+        DownloadTask(self, url, outputfile, title, downloadStop,headers,stream_type)
27
+
28
+
29
+class DownloadTask(Task):
30
+    def __init__(self, job, url, outputfile, title, downloadStop,headers={},stream_type="http"):
31
+        Task.__init__(self, job, _('Downloading'))
32
+        self.job = job
33
+        self.title = title
34
+        self.url = url
35
+        self.outputfile = outputfile
36
+        self.headers = headers
37
+        self.stream_type = stream_type
38
+        self.downloadStop = downloadStop
39
+        self.job.currentbytes = 0
40
+        self.job.totalbytes = -1
41
+
42
+    def run(self, callback):
43
+        self.callback = callback
44
+        if self.stream_type == "hls":
45
+            self.download = DownloadWithProgressFragmented(self.url, self.outputfile,self.headers)
46
+        else:
47
+            self.download = DownloadWithProgress(self.url, self.outputfile, self.headers)
48
+        self.download.addProgress(self.downloadProgress)
49
+        self.download.start().addCallback(self.downloadFinished).addErrback(self.downloadFailed)
50
+
51
+    def downloadProgress(self, currentbytes, totalbytes):
52
+        self.job.currentbytes = currentbytes
53
+        self.job.totalbytes = totalbytes
54
+        progress = self.job.currentbytes/float(self.job.totalbytes) * 100
55
+        self.setProgress(progress)
56
+
57
+    def downloadFinished(self, result):
58
+        Task.processFinished(self, 0)
59
+        self.setProgress(self.end)
60
+        self.downloadStop("Success - " + self.title)
61
+
62
+    def downloadFailed(self, failure_instance=None, error_message=''):
63
+        print '[PlayStream] Video download failed'
64
+        if error_message == '' and failure_instance is not None:
65
+            error_message = failure_instance.getErrorMessage()
66
+            print '[PlayStream]', str(error_message)
67
+        Task.processFinished(self, 1)
68
+        self.downloadStop("Failed - "+ self.title)
13 69
 
14 70
 class HLSDownloadJob(Job):
15
-	def __init__(self, url, destfile, title,downloadStop):
16
-		Job.__init__(self, title)
17
-		AddHLSProcessTask(self, url, destfile, title, downloadStop)
71
+    def __init__(self, url, destfile, title,downloadStop,headers=None):
72
+        Job.__init__(self, title)
73
+        AddHLSProcessTask(self, url, destfile, title, downloadStop,headers)
18 74
 
75
+
76
+######################################################################################################
19 77
 class AddHLSProcessTask(Task):
20
-	def __init__(self, job, url, destfile, title,downloadStop):
21
-		Task.__init__(self, job, title)
22
-		self.job = job
23
-		self.title = title
24
-		cmdline = '/usr/bin/gst-launch-1.0 "%s" !  hlsdemux ! filesink location="%s"'%(url,destfile)	
25
-		self.setCmdline(cmdline)
26
-		self.url = url
27
-		self.destfile = destfile
28
-		self.downloadStop = downloadStop
29
-		self.job.currentbytes = 0
30
-		self.job.totalbytes = 0
31
-		#self.setProgress(100)
32
-		self.ProgressTimer = eTimer()
33
-		self.ProgressTimer.callback.append(self.ProgressUpdate)
34
-
35
-	def ProgressUpdate(self):
36
-		if not fileExists(self.destfile, 'r'):
37
-			return
38
-		self.job.currentbytes = path.getsize(self.destfile)
39
-		progress = self.job.currentbytes/float(self.job.totalbytes) * 100 if self.job.totalbytes else 0
40
-		self.setProgress(progress)
41
-		#self.setProgress(int((path.getsize(self.destfile)/float(self.totalbytes))*100))
42
-		self.ProgressTimer.start(5000, True)
43
-
44
-	def prepare(self):
45
-		self.job.totalbytes = -1 # TODO getsize(self.url)
46
-		self.ProgressTimer.start(5000, True)
47
-
48
-	def afterRun(self):
49
-		#self.setProgress(100)
50
-		self.ProgressTimer.stop()
51
-		self.downloadStop(self.title)
52
-
53
-class downloadJob(Job):
54
-	def __init__(self, url, outputfile, title, downloadStop):
55
-		Job.__init__(self, title)
56
-		downloadTask(self, url, outputfile, title, downloadStop)
57
-
58
-
59
-class downloadTask(Task):
60
-	def __init__(self, job, url, outputfile, title, downloadStop):
61
-		Task.__init__(self, job, _('Downloading'))
62
-		self.job = job
63
-		self.title = title
64
-		self.url = url
65
-		self.outputfile = outputfile
66
-		self.downloadStop = downloadStop
67
-		self.job.currentbytes = 0
68
-		self.job.totalbytes = -1		
69
-
70
-	def run(self, callback):
71
-		self.callback = callback
72
-		self.download = downloadWithProgress(self.url, self.outputfile)
73
-		self.download.addProgress(self.downloadProgress)
74
-		self.download.start().addCallback(self.downloadFinished)\
75
-			.addErrback(self.downloadFailed)
76
-
77
-	def downloadProgress(self, currentbytes, totalbytes):
78
-		self.job.currentbytes = currentbytes
79
-		self.job.totalbytes = totalbytes
80
-		progress = self.job.currentbytes/float(self.job.totalbytes) * 100
81
-		self.setProgress(progress)
82
-
83
-	def downloadFinished(self, result):
84
-		Task.processFinished(self, 0)
85
-		self.setProgress(self.end)
86
-		self.downloadStop(self.title)
87
-
88
-	def downloadFailed(self, failure_instance=None, error_message=''):
89
-		print '[PlayStream] Video download failed'
90
-		if error_message == '' and failure_instance is not None:
91
-			error_message = failure_instance.getErrorMessage()
92
-			print '[PlayStream]', str(error_message)
93
-		Task.processFinished(self, 1)
94
-		self.downloadStop("")
78
+    def __init__(self, job, url, destfile, title,downloadStop,headers=None):
79
+        Task.__init__(self, job, title)
80
+        self.job = job
81
+        self.title = title
82
+        cmdline = '/usr/bin/gst-launch-1.0 "%s" !  hlsdemux ! filesink location="%s"'%(url,destfile)
83
+        self.setCmdline(cmdline)
84
+        self.url = url
85
+        self.destfile = destfile
86
+        self.downloadStop = downloadStop
87
+        self.job.currentbytes = 0
88
+        self.job.totalbytes = 0
89
+        #self.setProgress(100)
90
+        self.ProgressTimer = eTimer()
91
+        timer_callback(self.ProgressTimer, self.ProgressUpdate)
92
+
93
+    def ProgressUpdate(self):
94
+        if not fileExists(self.destfile, 'r'):
95
+            return
96
+        self.job.currentbytes = path.getsize(self.destfile)
97
+        progress = self.job.currentbytes/float(self.job.totalbytes) * 100 if self.job.totalbytes else 0
98
+        self.setProgress(progress)
99
+        #self.setProgress(int((path.getsize(self.destfile)/float(self.totalbytes))*100))
100
+        self.ProgressTimer.start(5000, True)
101
+
102
+    def prepare(self):
103
+        self.job.totalbytes = -1 # TODO getsize(self.url)
104
+        self.ProgressTimer.start(5000, True)
105
+
106
+    def afterRun(self):
107
+        #self.setProgress(100)
108
+        self.ProgressTimer.stop()
109
+        self.downloadStop(self.title)
95 110
 
96 111
 
97 112
 class VideoDownloadList(Screen):
98
-	screenWidth = getDesktop(0).size().width()
99
-	if screenWidth and screenWidth == 1920:
100
-		skin = """<screen position="center,center" size="945,555">
101
-				<widget source="list" render="Listbox" position="center,45" size="900,405" \
102
-					scrollbarMode="showOnDemand" >
103
-					<convert type="TemplatedMultiContent" >
104
-						{"template": [
105
-							MultiContentEntryText(pos=(15,1), size=(465,33), \
106
-								font=0, flags=RT_HALIGN_LEFT, text=1), # Title
107
-							MultiContentEntryText(pos=(345,1), size=(225,33), \
108
-								font=0, flags=RT_HALIGN_RIGHT, text=2), # State
109
-							MultiContentEntryProgress(pos=(585,6), size=(150,33), \
110
-								percent=-3), # Progress
111
-							MultiContentEntryText(pos=(750,1), size=(120,33), \
112
-								font=0, flags=RT_HALIGN_LEFT, text=4), # Percentage
113
-							],
114
-						"fonts": [gFont("Regular",30)],
115
-						"itemHeight": 45}
116
-					</convert>
117
-				</widget>
118
-				<ePixmap position="center,484" size="210,60" pixmap="skin_default/buttons/red.png" \
119
-					transparent="1" alphatest="on" />
120
-				<widget source="key_red" render="Label" position="center,485" zPosition="2" \
121
-					size="210,60" valign="center" halign="center" font="Regular;33" transparent="1" />
122
-				</screen>"""
123
-	else:
124
-		skin = """<screen position="center,center" size="630,370">
125
-				<widget source="list" render="Listbox" position="center,30" size="600,270" \
126
-					scrollbarMode="showOnDemand" >
127
-					<convert type="TemplatedMultiContent" >
128
-						{"template": [
129
-							MultiContentEntryText(pos=(10,1), size=(210,22), \
130
-								font=0, flags=RT_HALIGN_LEFT, text=1), # Title
131
-							MultiContentEntryText(pos=(230,1), size=(150,22), \
132
-								font=0, flags=RT_HALIGN_RIGHT, text=2), # State
133
-							MultiContentEntryProgress(pos=(390,4), size=(100,22), \
134
-								percent=-3), # Progress
135
-							MultiContentEntryText(pos=(500,1), size=(80,22), \
136
-								font=0, flags=RT_HALIGN_LEFT, text=4), # Percentage
137
-							],
138
-						"fonts": [gFont("Regular",20)],
139
-						"itemHeight": 30}
140
-					</convert>
141
-				</widget>
142
-				<ePixmap position="center,323" size="140,40" pixmap="skin_default/buttons/red.png" \
143
-					transparent="1" alphatest="on" />
144
-				<widget source="key_red" render="Label" position="center,328" zPosition="2" \
145
-					size="140,30" valign="center" halign="center" font="Regular;22" transparent="1" />
146
-				</screen>"""
147
-
148
-	def __init__(self, session):
149
-		Screen.__init__(self, session)
150
-		self['key_red'] = StaticText(_('Exit'))
151
-		self['list'] = List([])
152
-		self['actions'] = ActionMap(['SetupActions', 'ColorActions'],
153
-			{
154
-				'cancel': self.close,
155
-				'ok': self.ok,
156
-				'red': self.close,
157
-		                'blue': self.abort
158
-			}, -2)
159
-		self.onLayoutFinish.append(self.layoutFinished)
160
-		self.onClose.append(self.cleanVariables)
161
-		self.progressTimer = eTimer()
162
-		self.progressTimer.callback.append(self.updateDownloadList)
163
-
164
-	def layoutFinished(self):
165
-		self.setTitle(_('Active video downloads'))
166
-		self.updateDownloadList()
167
-
168
-	def cleanVariables(self):
169
-		del self.progressTimer
170
-
171
-	def updateDownloadList(self):
172
-		self.progressTimer.stop()
173
-		downloadList = []
174
-		for job in job_manager.getPendingJobs():
175
-			progress = job.progress / float(job.end) * 100
176
-			currentbytes = job.currentbytes if "currentbytes" in dir(job) else 0
177
-			downloadList.append((job, job.name, job.getStatustext(),
178
-				int(progress), "%.1fM"%(currentbytes/1024.0/1024.0) ))
179
-		self['list'].updateList(downloadList)
180
-		if downloadList:
181
-			self.progressTimer.startLongTimer(2)
182
-
183
-	def ok(self):
184
-		current = self['list'].getCurrent()
185
-		if current:
186
-			from Screens.TaskView import JobView
187
-			self.session.open(JobView, current[0])
188
-			
189
-	def abort(self):
190
-		current = self['list'].getCurrent()
191
-		job = current[0]
192
-		if job.status == job.NOT_STARTED:
193
-			job_manager.active_jobs.remove(job)
194
-			self.close(False)
195
-		elif job.status == job.IN_PROGRESS:
196
-			job.cancel()
197
-		else:
198
-			self.close(False)
199
-	
113
+    screenWidth = getDesktop(0).size().width()
114
+    if screenWidth and screenWidth == 1920:
115
+        skin = """<screen position="center,center" size="945,555">
116
+                <widget source="list" render="Listbox" position="center,45" size="900,405" \
117
+                    scrollbarMode="showOnDemand" >
118
+                    <convert type="TemplatedMultiContent" >
119
+                        {"template": [
120
+                            MultiContentEntryText(pos=(15,1), size=(465,33), \
121
+                                font=0, flags=RT_HALIGN_LEFT, text=1), # Title
122
+                            MultiContentEntryText(pos=(345,1), size=(225,33), \
123
+                                font=0, flags=RT_HALIGN_RIGHT, text=2), # State
124
+                            MultiContentEntryProgress(pos=(585,6), size=(150,33), \
125
+                                percent=-3), # Progress
126
+                            MultiContentEntryText(pos=(750,1), size=(120,33), \
127
+                                font=0, flags=RT_HALIGN_LEFT, text=4), # Percentage
128
+                            ],
129
+                        "fonts": [gFont("Regular",30)],
130
+                        "itemHeight": 45}
131
+                    </convert>
132
+                </widget>
133
+                <ePixmap position="center,484" size="210,60" pixmap="skin_default/buttons/red.png" \
134
+                    transparent="1" alphatest="on" />
135
+                <widget source="key_red" render="Label" position="center,485" zPosition="2" \
136
+                    size="210,60" valign="center" halign="center" font="Regular;33" transparent="1" />
137
+                </screen>"""
138
+    else:
139
+        skin = """<screen position="center,center" size="630,370">
140
+                <widget source="list" render="Listbox" position="center,30" size="600,270" \
141
+                    scrollbarMode="showOnDemand" >
142
+                    <convert type="TemplatedMultiContent" >
143
+                        {"template": [
144
+                            MultiContentEntryText(pos=(10,1), size=(210,22), \
145
+                                font=0, flags=RT_HALIGN_LEFT, text=1), # Title
146
+                            MultiContentEntryText(pos=(230,1), size=(150,22), \
147
+                                font=0, flags=RT_HALIGN_RIGHT, text=2), # State
148
+                            MultiContentEntryProgress(pos=(390,4), size=(100,22), \
149
+                                percent=-3), # Progress
150
+                            MultiContentEntryText(pos=(500,1), size=(80,22), \
151
+                                font=0, flags=RT_HALIGN_LEFT, text=4), # Percentage
152
+                            ],
153
+                        "fonts": [gFont("Regular",20)],
154
+                        "itemHeight": 30}
155
+                    </convert>
156
+                </widget>
157
+                <ePixmap position="center,323" size="140,40" pixmap="skin_default/buttons/red.png" \
158
+                    transparent="1" alphatest="on" />
159
+                <widget source="key_red" render="Label" position="center,328" zPosition="2" \
160
+                    size="140,30" valign="center" halign="center" font="Regular;22" transparent="1" />
161
+                </screen>"""
162
+
163
+    def __init__(self, session):
164
+        Screen.__init__(self, session)
165
+        self['key_red'] = StaticText(_('Exit'))
166
+        self['list'] = List([])
167
+        self['actions'] = ActionMap(['SetupActions', 'ColorActions'],
168
+            {
169
+                'cancel': self.close,
170
+                'ok': self.ok,
171
+                'red': self.close,
172
+                        'blue': self.abort
173
+            }, -2)
174
+        self.onLayoutFinish.append(self.layoutFinished)
175
+        self.onClose.append(self.cleanVariables)
176
+        self.progressTimer = eTimer()
177
+        timer_callback(self.progressTimer,self.updateDownloadList)
178
+
179
+    def layoutFinished(self):
180
+        self.setTitle(_('Active video downloads'))
181
+        self.updateDownloadList()
182
+
183
+    def cleanVariables(self):
184
+        del self.progressTimer
185
+
186
+    def updateDownloadList(self):
187
+        self.progressTimer.stop()
188
+        downloadList = []
189
+        for job in job_manager.getPendingJobs():
190
+            progress = job.progress / float(job.end) * 100
191
+            currentbytes = job.currentbytes if "currentbytes" in dir(job) else 0
192
+            downloadList.append((job, job.name, job.getStatustext(),
193
+                int(progress), "%.1fM"%(currentbytes/1024.0/1024.0) ))
194
+        self['list'].updateList(downloadList)
195
+        if downloadList:
196
+            self.progressTimer.startLongTimer(2)
197
+
198
+    def ok(self):
199
+        current = self['list'].getCurrent()
200
+        if current:
201
+            from Screens.TaskView import JobView
202
+            self.session.open(JobView, current[0])
203
+
204
+    def abort(self):
205
+        current = self['list'].getCurrent()
206
+        job = current[0]
207
+        if job.status == job.NOT_STARTED:
208
+            job_manager.active_jobs.remove(job)
209
+            self.close(False)
210
+        elif job.status == job.IN_PROGRESS:
211
+            job.cancel()
212
+        else:
213
+            self.close(False)
214
+
200 215
 
201 216
 class VideoDirBrowser(Screen):
202
-	def __init__(self, session, downloadDir):
203
-		Screen.__init__(self, session)
204
-		self.skinName = ['VideoDirBrowser', 'FileBrowser']
205
-		self['key_red'] = StaticText(_('Cancel'))
206
-		self['key_green'] = StaticText(_('Use'))
207
-		if not os.path.exists(downloadDir):
208
-			downloadDir = '/'
209
-		self.filelist = FileList(downloadDir, showFiles = False)
210
-		self['filelist'] = self.filelist
211
-		self['FilelistActions'] = ActionMap(['SetupActions', 'ColorActions'],
212
-			{
213
-				'cancel': self.cancel,
214
-				'red': self.cancel,
215
-				'ok': self.ok,
216
-				'green': self.use
217
-			}, -2)
218
-		self.onLayoutFinish.append(self.layoutFinished)
219
-
220
-	def layoutFinished(self):
221
-		self.setTitle(_('Please select the download directory'))
222
-
223
-	def ok(self):
224
-		if self.filelist.canDescent():
225
-			self.filelist.descent()
226
-
227
-	def use(self):
228
-		currentDir = self['filelist'].getCurrentDirectory()
229
-		dirName = self['filelist'].getFilename()
230
-		if currentDir is None or \
231
-			(self.filelist.canDescent() and dirName and len(dirName) > len(currentDir)):
232
-			self.close(dirName)
233
-
234
-	def cancel(self):
235
-		self.close(False)
217
+    def __init__(self, session, downloadDir):
218
+        Screen.__init__(self, session)
219
+        self.skinName = ['VideoDirBrowser', 'FileBrowser']
220
+        self['key_red'] = StaticText(_('Cancel'))
221
+        self['key_green'] = StaticText(_('Use'))
222
+        if not os.path.exists(downloadDir):
223
+            downloadDir = '/'
224
+        self.filelist = FileList(downloadDir, showFiles = False)
225
+        self['filelist'] = self.filelist
226
+        self['FilelistActions'] = ActionMap(['SetupActions', 'ColorActions'],
227
+            {
228
+                'cancel': self.cancel,
229
+                'red': self.cancel,
230
+                'ok': self.ok,
231
+                'green': self.use
232
+            }, -2)
233
+        self.onLayoutFinish.append(self.layoutFinished)
234
+
235
+    def layoutFinished(self):
236
+        self.setTitle(_('Please select the download directory'))
237
+
238
+    def ok(self):
239
+        if self.filelist.canDescent():
240
+            self.filelist.descent()
241
+
242
+    def use(self):
243
+        currentDir = self['filelist'].getCurrentDirectory()
244
+        dirName = self['filelist'].getFilename()
245
+        if currentDir is None or \
246
+            (self.filelist.canDescent() and dirName and len(dirName) > len(currentDir)):
247
+            self.close(dirName)
248
+
249
+    def cancel(self):
250
+        self.close(False)

+ 14
- 0
changelog.md View File

@@ -1,3 +1,17 @@
1
+**0.6r** (01.04.2017)
2
+- [feature] LMT Straume video (bez TV, kas strādā tikai LMT tīklā)
3
+
4
+**0.6p** (31.03.2017)
5
+- [bugfix] filmix sērijas
6
+- [bugfix] daļa failu download kļūdu novērstas (bet līdz galam vēl nestrādā)
7
+
8
+**0.6n** (29.03.2017)
9
+- [bugfix][feature] failu download pārtaisīts nezimantojot gstreamer
10
+
11
+**0.6m** (28.03.2017)
12
+- [bugfix] viaplay login un playback
13
+- [feature] tekošais source nosaukums loga titlē
14
+
1 15
 **0.6j** (22.03.2017)
2 16
 - salabota InsecureRequestWaening importa kļūda
3 17
 

+ 1
- 0
deploy.bat View File

@@ -21,6 +21,7 @@ PlayStream.py
21 21
 PlayStream.png
22 22
 ContentSources.py
23 23
 VideoDownload.py
24
+Downloader.py
24 25
 enigma2_api.py
25 26
 resolver.py
26 27
 util.py

+ 15
- 10
imake.bat View File

@@ -1,15 +1,12 @@
1 1
 @echo off
2 2
 :=== Parameters ===
3 3
 
4
-if ()==(%1%) (
5
-    python get_version.py PlayStream.py >version.txt
6
-    cat version.txt
7
-    pause
8
-    set /p ver=<version.txt
9
-    echo Version: %ver%
10
-) else (
11
-    set ver=%1
12
-)
4
+python get_version.py PlayStream.py >version.txt
5
+cat version.txt
6
+pause
7
+set /p ver=<version.txt
8
+echo Version: %ver%
9
+
13 10
 pause
14 11
 set prog=PlayStream
15 12
 set pack_name=enigma2-plugin-extensions-playstream
@@ -20,7 +17,7 @@ set ext_dir2=/usr/lib/enigma2/python/Plugins/Extensions/
20 17
 set script_dir=usr\script\
21 18
 set ipk_dir=ipkg\
22 19
 set release_dir=release\
23
-set feed_dir=w:\feed\
20
+set feed_dir=q:\web\feed\
24 21
 
25 22
 set AR=\MinGW\bin\ar.exe
26 23
 set TAR=\MinGW\msys\1.0\bin\tar.exe
@@ -40,6 +37,7 @@ skin.xml
40 37
 %prog%.png
41 38
 ContentSources.py
42 39
 VideoDownload.py
40
+Downloader.py
43 41
 enigma2_api.py
44 42
 resolver.py
45 43
 util.py
@@ -61,6 +59,7 @@ sources\ltc.py
61 59
 sources\mtgplay.py
62 60
 sources\play24.py
63 61
 sources\replay.py
62
+sources\lmt.py
64 63
 sources\serialguru.py
65 64
 sources\tvdom.py
66 65
 sources\ustvnow.py
@@ -160,7 +159,13 @@ if exist %release_dir%%pack_name%_%ver%.ipk del %release_dir%%pack_name%_%ver%.i
160 159
 %AR% -r %release_dir%%pack_name%_%ver% ipkg\debian-binary ipkg\data.tar.gz ipkg\control.tar.gz
161 160
 @echo on
162 161
 mv %release_dir%%pack_name%_%ver% %release_dir%%pack_name%_%ver%.ipk
162
+
163 163
 git add %release_dir%%pack_name%_%ver%.ipk
164
+if not ()==(%1%) (
165
+git commit -m %ver%
166
+git tag %ver%
167
+git push
168
+)
164 169
 
165 170
 copy %release_dir%%pack_name%_%ver%.ipk %feed_dir%%pack_name%_%ver%.ipk
166 171
 pushd  %feed_dir%

+ 274
- 83
playstreamproxy.py View File

@@ -26,140 +26,313 @@ except:
26 26
 
27 27
 HOST_NAME = ""
28 28
 PORT_NUMBER = 88
29
-DEBUG = False
29
+DEBUG = True
30
+DEBUG2 = False
30 31
 
32
+SPLIT_CHAR = "~"
33
+SPLIT_CODE = "%7E"
34
+EQ_CODE = "%3D"
35
+COL_CODE = "%3A"
31 36
 headers2dict = lambda  h: dict([l.strip().split(": ") for l in h.strip().splitlines()])
37
+headers0 = headers2dict("""
38
+icy-metadata: 1
39
+Cache-Control: max-age=0
40
+Accept-Encoding: gzip, deflate
41
+User-Agent: GStreamer souphttpsrc libsoup/2.52.2
42
+Connection: Keep-Alive
43
+""")
32 44
 sessions = {}
33 45
 
34 46
 class StreamHandler(BaseHTTPRequestHandler):
35 47
 
36 48
     def do_HEAD(self):
49
+        print "**head"
37 50
         self.send_response(200)
38
-        self.send_header("Server", "StreamProxy")
39
-        self.send_header("Content-type", "text/html")
51
+        self.send_header("Server", "playstreamproxy")
52
+        if ".m3u8" in self.path.lower():
53
+            ct = "application/vnd.apple.mpegurl"
54
+        elif ".ts" in self.path.lower():
55
+            ct = "video/MP2T"
56
+        elif ".mp4" in ".ts" in self.path.lower():
57
+            ct = "video/mp4"
58
+        else:
59
+            ct = "text/html"
60
+        self.send_header("Content-type", ct)
40 61
         self.end_headers()
41 62
 
42 63
     def do_GET(self):
43
-        """Respond to a GET request."""
44
-        SPLIT_CHAR = "~"
45
-        SPLIT_CODE = "%7E"
46
-        EQ_CODE = "%3D"
47
-        COL_CODE = "%3A"
48
-        self.log_message("get_url: \n%s", self.path)
64
+        """Respond to a GET request"""
65
+        self.log_message("\n\n"+40*"#"+"\nget_url: \n%s", self.path)
49 66
         p = self.path.split("~")
50
-        url = urllib.unquote(p[0][1:])
67
+        #url = urllib.unquote(p[0][1:])
68
+        url = p[0][1:]
51 69
         url = url.replace(COL_CODE, ":")
52
-        headers = headers2dict("""
53
-        User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 8_0_2 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12A366 Safari/600.1.4
54
-        """)
70
+        headers = self.headers.dict
71
+        headers = {} # TODO
72
+        headers["host"] = urlparse.urlparse(url).hostname
55 73
         if len(p)>1:
56 74
             for h in p[1:]:
57
-                headers[h.split("=")[0]]=urllib.unquote(h.split("=")[1])
58
-        #self.fetch_offline(self.wfile)
75
+                k = h.split("=")[0].lower()
76
+                v = urllib.unquote(h.split("=")[1])
77
+                headers[k]=v
78
+        if DEBUG:
79
+            print "url=%s"%url
80
+            print "Original request headers + url headers:"
81
+            print_headers(headers)
82
+
83
+        self.protocol_version = 'HTTP/1.1'
84
+
85
+        # TODO fetch selection
59 86
         try:
60
-            self.fetch_url2(self.wfile, url, headers)
87
+            if ".lattelecom.tv/" in url: # lattelecom.tv hack
88
+                self.fetch_ltc(self.wfile, url, headers)
89
+            elif "filmas.lv" in url or "viaplay" in url: #  HLS session/decode filmas.lv in url:
90
+                self.fetch_url2(self.wfile, url, headers)
91
+            else: # plain fetch
92
+                self.fetch_url(self.wfile, url, headers)
61 93
         except Exception as e:
62 94
             print "Got Exception: ", str(e)
95
+            import traceback
96
+            traceback.print_exc()
97
+
98
+    ### Remote server request procedures ###
63 99
 
64 100
     def fetch_offline(self,wfile):
101
+        print "** Fetch offline"
65 102
         self.send_response(200)
66
-        self.send_header("Server", "StreamProxy")
103
+        self.send_header("Server", "playstreamproxy")
67 104
         self.send_header("Content-type", "video/mp4")
68 105
         self.end_headers()
69 106
         self.wfile.write(open("offline.mp4", "rb").read())
70
-        self.wfile.close()
107
+        #self.wfile.close()
108
+
109
+    def fetch_url(self,wfile,url,headers):
110
+        if DEBUG:
111
+            print "\n***********************************************************"
112
+            print "fetch_url: \n%s"%url
113
+            print "**Server request headers: "
114
+            print_headers(headers)
115
+        #if ".lattelecom.tv/" in url and EQ_CODE in url:
116
+        #    url = url.replace(EQ_CODE,"=")
117
+        r = requests.get(url,headers = headers)
118
+        code = r.status_code
119
+        if DEBUG:
120
+            print "** Server/proxy response, code = %s"%code
121
+            print_headers(r.headers)
122
+        if not code in (200,206):
123
+            print "***Error, code=%s",code
124
+            self.send_response(code)
125
+            self.send_headers(r.headers)
126
+            wfile.close()
127
+            return
128
+        self.send_response(code)
129
+        self.send_headers(r.headers)
130
+        CHUNK_SIZE = 1024*4
131
+        for chunk in r.iter_content(CHUNK_SIZE):
132
+            try:
133
+                wfile.write(chunk)
134
+            except Exception as e:
135
+                print "Exception: ", str(e)
136
+                wfile.close()
137
+                return
138
+        if DEBUG: print "**File downloaded"
139
+        wfile.close()
140
+        # time.sleep(1)
141
+        return
142
+
143
+    def fetch_ltc(self, wfile, url, headers):
144
+        if DEBUG:
145
+            print "\n***********************************************************"
146
+            print "fetch_url2: \n%s"%url
147
+        #self.log_message("fetch_filmas: \n%s", url)
148
+        #self.log_message("headers: %s", headers)
149
+        base_url = hls_base(url)
150
+        if DEBUG: print "base_url=",base_url
151
+        if base_url not in sessions:
152
+            if DEBUG: print "New session"
153
+            sessions[base_url] = {}
154
+            sessions[base_url]["session"] = requests.Session()
155
+            #sessions[base_url]["session"].headers = {}
156
+            sessions[base_url]["key"] = binascii.a2b_hex(headers["key"]) if "key" in headers and headers["key"] else None
157
+        ses = sessions[base_url]["session"]
158
+        key = sessions[base_url]["key"]
159
+        ses.headers.clear()
160
+        ses.headers.update(headers0)
161
+        ses.headers.update(headers)
162
+        ses.headers["Connection"]="Keep-Alive"
163
+        if DEBUG:
164
+            print "**Server request headers: "
165
+            print_headers(ses.headers)
166
+        for t in range(3):
167
+            r = ses.get(url, stream=True, verify=False)
168
+            code = r.status_code #r.status_code
169
+            if DEBUG:
170
+                print "\n\n=====================================\n**Server response:", code #r.status_code
171
+                print "**Server response headers: "
172
+                print_headers(r.headers)
173
+            if code in (200,2016): break
174
+        if not (code in (200,206)):
175
+            print "***Error, code=%s"%code
176
+            self.send_response(code)
177
+            self.send_headers(r.headers)
178
+            wfile.close()
179
+            #self.fetch_offline(wfile)
180
+            return
181
+
182
+        ### Start of return formin and sending
183
+        self.send_response(200)
184
+        #headers2 = del_headers(r.headers,["Content-Encoding",'Transfer-Encoding',"Connection",'content-range',"range"])
185
+        headers2  = {"server":"playstreamproxy", "content-type":"text/html"}
186
+
187
+        # Content-Type: application/vnd.apple.mpegurl (encrypted)
188
+        if r.headers["content-type"] == "application/vnd.apple.mpegurl":
189
+            content = r.content
190
+            content = r.content.replace(base_url,"")
191
+            content = re.sub("#EXT-X-KEY:METHOD=AES-128.+\n", "", content, 0, re.IGNORECASE | re.MULTILINE)
192
+            headers2["content-type"] = "application/vnd.apple.mpegurl"
193
+            headers2["content-length"] = "%s"%len(content)
194
+            r.headers["content-length"] = "%s"%len(content)
195
+            #headers2['content-range'] = 'bytes 0-%s/%s'%(len(content)-1,len(content))
196
+            #self.send_headers(headers2)
197
+            self.send_headers(r.headers)
198
+            wfile.write(content)
199
+            wfile.close()
200
+
201
+        # Content-Type: video/MP2T (encrypted)
202
+        elif r.headers["content-type"] == "video/MP2T" and key:
203
+            print "Decode video/MP2T"
204
+            content = r.content
205
+            from Crypto.Cipher import AES
206
+            iv = content[:16]
207
+            d = AES.new(key, AES.MODE_CBC, iv)
208
+            content = d.decrypt(content[16:])
209
+            headers2["content-type"] = "video/MP2T"
210
+            headers2["content-length"] = "%s"% (len(content))
211
+            #headers2['content-range'] = 'bytes 0-%s/%s' % (len(content) - 1, len(content))
212
+            print content[0:16]
213
+            print "Finish decode"
214
+            self.send_headers(headers2)
215
+            wfile.write(content)
216
+            wfile.close()
217
+
218
+        else:
219
+            print "Return regular content"
220
+            headers2["content-type"]  = r.headers["content-type"]
221
+            if "content-length" in r.headers:
222
+                headers2["content-length"] = r.headers["content-length"]
223
+            self.send_headers(r.headers)
224
+            CHUNK_SIZE = 4 * 1024
225
+            for chunk in r.iter_content(CHUNK_SIZE):
226
+                try:
227
+                    #print "#",
228
+                    wfile.write(chunk)
229
+                except Exception as e:
230
+                    print "Exception: ", str(e)
231
+                    return
232
+            if DEBUG: print "File downloaded = "
233
+            wfile.close()
234
+            #time.sleep(1)
235
+            return
71 236
 
72 237
 
73 238
     def fetch_url2(self, wfile, url, headers):
74
-        if DEBUG: print "\n***********************************************************"
75
-        self.log_message("fetch_url: \n%s", url)
239
+        if DEBUG:
240
+            print "\n***********************************************************"
241
+            print "fetch_url2: \n%s"%url
242
+        #self.log_message("fetch_filmas: \n%s", url)
76 243
         #self.log_message("headers: %s", headers)
77
-
78
-        base_url = "/".join(url.split("/")[0:-1])
244
+        base_url = hls_base(url)
245
+        if DEBUG: print "base_url=",base_url
79 246
         if base_url not in sessions:
80
-            sessions[base_url]={}
247
+            if DEBUG: print "New session"
248
+            sessions[base_url] = {}
81 249
             sessions[base_url]["session"] = requests.Session()
250
+            #sessions[base_url]["session"].headers = {}
82 251
             sessions[base_url]["key"] = binascii.a2b_hex(headers["key"]) if "key" in headers and headers["key"] else None
83
-            #cj = cookielib.CookieJar()
84
-            #sessions[base_url] = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
85
-        else:
86
-            if "key" in headers and headers["key"]: sessions[base_url]["key"] = binascii.a2b_hex(headers["key"])
87 252
         ses = sessions[base_url]["session"]
88 253
         key = sessions[base_url]["key"]
89
-
90
-        if DEBUG: print "**Request headers: "
254
+        ses.headers.clear()
255
+        ses.headers.update(headers0)
91 256
         ses.headers.update(headers)
92
-        #ses.addheaders=[]
93
-        for h in ses.headers:
94
-            #ses.addheaders.append((h,headers[h]))
95
-            if DEBUG: print h,"=",ses.headers[h]
96
-        r = ses.get(url, stream=True,verify=False)
97
-        #r = ses.open(url)
98
-        code = r.status_code #r.status_code
99
-        if DEBUG: print "**Response:", code #r.status_code
100
-        if DEBUG: print "**Response headers: "
101
-        for h in r.headers:
102
-            if DEBUG: print h,"=",r.headers[h]
103
-        self.send_response(code)
104
-
105
-        print code
106
-        if code <> 200:
107
-            self.fetch_offline(wfile)
257
+        ses.headers["Connection"]="Keep-Alive"
258
+        if DEBUG:
259
+            print "**Server request headers: "
260
+            print_headers(ses.headers)
261
+        for t in range(3):
262
+            r = ses.get(url, stream=True, verify=False)
263
+            code = r.status_code #r.status_code
264
+            if DEBUG:
265
+                print "\n\n=====================================\n**Server response:", code #r.status_code
266
+                print "**Server response headers: "
267
+                print_headers(r.headers)
268
+            if code in (200,2016): break
269
+        if not (code in (200,206)):
270
+            print "***Error, code=%s"%code
271
+            self.send_response(code)
272
+            self.send_headers(r.headers)
273
+            wfile.close()
274
+            #self.fetch_offline(wfile)
108 275
             return
109 276
 
110
-        if DEBUG: print "**Return headers:"
111
-        headers2 = {}
112
-        for h in r.headers:
113
-            if h.lower() in ("user-agent","server","transfer-encoding","content-encoding","connection"):
114
-                if DEBUG: print h," - skipped"
115
-                continue
116
-            else:
117
-                headers2[h] = r.headers[h]
118
-                if DEBUG:print h,"=",r.headers[h]
277
+        ### Start of return formin and sending
278
+        self.send_response(200)
279
+        #headers2 = del_headers(r.headers,["Content-Encoding",'Transfer-Encoding',"Connection",'content-range',"range"])
280
+        headers2  = {"server":"playstreamproxy", "content-type":"text/html"}
119 281
 
120
-        # Content-Type: application/vnd.apple.mpegurl
121
-        if r.headers["Content-Type"] == "application/vnd.apple.mpegurl" and key:
282
+        # Content-Type: application/vnd.apple.mpegurl (encrypted)
283
+        if r.headers["content-type"] == "application/vnd.apple.mpegurl":
122 284
             content = r.content
123
-            content = r.content.replace(base_url+"/","")
285
+            content = r.content.replace(base_url,"")
124 286
             content = re.sub("#EXT-X-KEY:METHOD=AES-128.+\n", "", content, 0, re.IGNORECASE | re.MULTILINE)
125
-            headers2["Content-Length"] = "%s"%len(content)
126
-            self.send_headers(headers2)
287
+            headers2["content-type"] = "application/vnd.apple.mpegurl"
288
+            headers2["content-length"] = "%s"%len(content)
289
+            r.headers["content-length"] = "%s"%len(content)
290
+            #headers2['content-range'] = 'bytes 0-%s/%s'%(len(content)-1,len(content))
291
+            #self.send_headers(headers2)
292
+            self.send_headers(r.headers)
127 293
             wfile.write(content)
294
+            wfile.close()
128 295
 
129
-        # Content-Type: video/MP2T
130
-        elif r.headers["Content-Type"] == "video/MP2T" and key:
296
+        # Content-Type: video/MP2T (encrypted)
297
+        elif r.headers["content-type"] == "video/MP2T" and key:
298
+            print "Decode video/MP2T"
131 299
             content = r.content
132 300
             from Crypto.Cipher import AES
133 301
             iv = content[:16]
134 302
             d = AES.new(key, AES.MODE_CBC, iv)
135 303
             content = d.decrypt(content[16:])
136
-            headers2["Content-Length"] = "%s"%len(content)
304
+            headers2["content-type"] = "video/MP2T"
305
+            headers2["content-length"] = "%s"% (len(content))
306
+            #headers2['content-range'] = 'bytes 0-%s/%s' % (len(content) - 1, len(content))
307
+            print content[0:16]
308
+            print "Finish decode"
137 309
             self.send_headers(headers2)
138 310
             wfile.write(content)
311
+            wfile.close()
139 312
 
140 313
         else:
141
-            self.send_headers(headers2)
314
+            print "Return regular content"
315
+            headers2["content-type"]  = r.headers["content-type"]
316
+            if "content-length" in r.headers:
317
+                headers2["content-length"] = r.headers["content-length"]
318
+            self.send_headers(r.headers)
142 319
             CHUNK_SIZE = 4 * 1024
143
-            if code == 200:
144
-                #while True:
145
-                    #chunk = r.read(CHUNK_SIZE)
146
-                    #if not chunk:
147
-                        #break
148
-                    #wfile.write(chunk)
149
-                #pass
150
-                #wfile.close()
151
-                for chunk in r.iter_content(1024):
152
-                    try:
153
-                        #print "#",
154
-                        wfile.write(chunk)
155
-                    except Exception as e:
156
-                        print "Exception: ", str(e)
157
-                        return
158
-                if DEBUG: print "  = file downloaded = "
159
-                time.sleep(1)
160
-        self.wfile.close()
320
+            for chunk in r.iter_content(CHUNK_SIZE):
321
+                try:
322
+                    #print "#",
323
+                    wfile.write(chunk)
324
+                except Exception as e:
325
+                    print "Exception: ", str(e)
326
+                    return
327
+            if DEBUG: print "File downloaded = "
328
+            wfile.close()
329
+            #time.sleep(1)
330
+            return
161 331
 
162 332
     def send_headers(self,headers):
333
+        #if DEBUG:
334
+            #print "**Return headers: "
335
+            #print_headers(headers)
163 336
         for h in headers:
164 337
             self.send_header(h, headers[h])
165 338
         self.end_headers()
@@ -168,8 +341,8 @@ class StreamHandler(BaseHTTPRequestHandler):
168 341
 class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
169 342
     """Handle requests in a separate thread."""
170 343
 
171
-def start():
172
-    httpd = ThreadedHTTPServer((HOST_NAME, PORT_NUMBER), StreamHandler)
344
+def start(host = HOST_NAME, port = PORT_NUMBER):
345
+    httpd = ThreadedHTTPServer((host, port), StreamHandler)
173 346
     print time.asctime(), "Server Starts - %s:%s" % (HOST_NAME, PORT_NUMBER)
174 347
     try:
175 348
         httpd.serve_forever()
@@ -307,6 +480,24 @@ class ProxyDaemon(Daemon):
307 480
     def run(self):
308 481
         start()
309 482
 
483
+def print_headers(headers):
484
+    for h in headers:
485
+        print "%s: %s"%(h,headers[h])
486
+
487
+def del_headers(headers0,tags):
488
+    headers = headers0.copy()
489
+    for t in tags:
490
+        if t in headers:
491
+            del headers[t]
492
+        if t.lower() in headers:
493
+            del headers[t.lower()]
494
+    return headers
495
+
496
+def hls_base(url):
497
+    url2 = url.split("?")[0]
498
+    url2 = "/".join(url2.split("/")[0:-1])+ "/"
499
+    return url2
500
+
310 501
 if __name__ == "__main__":
311 502
     daemon = ProxyDaemon("/var/run/playstreamproxy.pid")
312 503
     if len(sys.argv) == 2:

+ 16
- 15
readme.md View File

@@ -4,21 +4,22 @@ PlayStream
4 4
 Enigma2 plugin to to play various online streams (mostly Latvian).  
5 5
 Stream sources are in  subfolder "sources", and can be added/customized
6 6
 Currently implemented:
7
-- Main menu and manual stream definition in streams.cfg file
8
-- **replay.lsm.lv** media portal content (Latvian TV)
9
-- **skaties.lv** media portal content (MTG media portal Latvian version), other countries MTG contents are available through customizing main menu (country=xx) 
10
-- **lattelecom.tv(shortcut.lv)** media portal content
11
-- **play24.lv** media portal content
12
-- **viaplay.lv**
13
-- **cinemalive.tv**
14
-- **kinofilmnet.lv**
15
-- **dom.tv**
16
-- **BBC iPlayer**
17
-- **Euronews**
18
-- **filmon.tv** media portal content
19
-- **ustvnow.tv**
20
-- **serialguru.ru**
21
-- **filmix.net**
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)
22 23
 
23 24
 ---
24 25
 Copyright (c) 2016 ivars777 (ivars777@gmail.com)  

+ 1
- 0
resolver.py View File

@@ -123,6 +123,7 @@ if __name__ == "__main__":
123 123
     url = "https://goo.gl/yMTzqf"
124 124
     url = "http://cdn.kapnob.ru/video/5e67c8b1ad018ffa/iframe"
125 125
     url = "http://kodik.cc/video/10830/4269a802d1a9d9bdc53fe38488d53a52/720p"
126
+    url = "https://www.youtube.com/embed/RUyQ_JJ6A84?rel=0&fs=1&wmode=transparent"
126 127
     streams = resolve(url)
127 128
     if not streams:
128 129
         print "No streams found"

BIN
resolvers/hdgo.pyc View File


BIN
resolvers/hqqresolver.pyc View File


BIN
resolvers/kapnob.pyc View File


BIN
resolvers/kodik.pyc View File


BIN
resolvers/openload3.pyc View File


+ 7
- 6
sources/SourceBase.py View File

@@ -142,15 +142,16 @@ User-Agent: Mozilla/5.0 (Linux; U; Android 4.4.4; Nexus 5 Build/KTU84P) AppleWeb
142 142
         return source,data,path,plist,clist,params,qs
143 143
 
144 144
 def stream_type(data):
145
-    if "::" in data:
146
-        data = data.split("::")[1]
147 145
     data = data.lower()
148 146
     m = re.search(r"^(\w+)://", data)
149 147
     prefix = m.group(1) if m else ""
150
-    if prefix in ("http","https") and ".m3u8" in data:
151
-        return "hls"
152
-    elif prefix == "http":
153
-        return "http"
148
+    if prefix in ("http","https"):
149
+        if ".m3u8" in data:
150
+            return "hls"
151
+        elif ".mpd" in data:
152
+            return "dash"
153
+        else:
154
+            return "http"
154 155
     else:
155 156
         return prefix
156 157
 

BIN
sources/SourceBase.pyc View File


+ 1
- 1
sources/cinemalive.py View File

@@ -27,7 +27,7 @@ h = HTMLParser.HTMLParser()
27 27
     
28 28
 class Source(SourceBase):
29 29
     
30
-    def __init__(self,country=""):
30
+    def __init__(self,country="",cfg_path=None):
31 31
         self.name = "cinemalive"
32 32
         self.title = "cinemalive.tv"
33 33
         self.img = "picons/cinemalive.png" #"https://cinemalive.tv/assets/img/logo.png"

BIN
sources/cinemalive.pyc View File


+ 10
- 4
sources/config.py View File

@@ -12,11 +12,12 @@ from SourceBase import SourceBase
12 12
 os.path.dirname(os.path.abspath(__file__))
13 13
 class Source(SourceBase):
14 14
 
15
-    def __init__(self,country="lv"):
15
+    def __init__(self,country="lv",cfg_path=None):
16 16
         self.name = "config"
17 17
         self.country=country
18 18
         cur_directory = os.path.dirname(os.path.abspath(__file__))
19
-        self.streams_file = os.path.join(cur_directory,"streams.cfg")
19
+        if not cfg_path: cfg_path = cur_directory
20
+        self.streams_file = os.path.join(cfg_path,"streams.cfg")
20 21
         self.lists = collections.OrderedDict()
21 22
         self.titles = {}
22 23
         self.read_streams()
@@ -40,19 +41,24 @@ class Source(SourceBase):
40 41
                 self.lists[name] = []
41 42
             else:
42 43
                 if line[0] in ("#"): continue
43
-                items = tuple(line.strip().split("|"))
44
+                items = line.strip().split("|")
44 45
                 if not items[0]: continue
45 46
                 if len(items)==1:
46 47
                     self.titles[name] = items[0]
47 48
                 else:
49
+                    if len(items) == 4:
50
+                        items[3] = items[3].replace("\\n","\n")
48 51
                     self.lists[name].append(items)
49 52
 
50 53
     def write_streams(self):
51 54
         f = open(self.streams_file,"w")
52 55
         for l in self.lists.keys():
53 56
             f.write("[%s]\n"%l)
57
+            t = self.get_title(l)
58
+            if t<>l:
59
+                f.write("%s\n"%t)
54 60
             for item in self.lists[l]:
55
-                f.write("%s|%s|%s|%s\n"%(item[0],item[1],item[2],item[3]))
61
+                f.write("%s|%s|%s|%s\n"%(item[0].replace("\n",""),item[1],item[2],item[3].replace("\n","\\n")))
56 62
             f.write("\n")
57 63
         f.close()
58 64
 

BIN
sources/config.pyc View File


+ 3
- 2
sources/euronews.py View File

@@ -22,7 +22,7 @@ h = HTMLParser.HTMLParser()
22 22
 
23 23
 class Source(SourceBase):
24 24
 
25
-    def __init__(self,language="en"):
25
+    def __init__(self,language="en",cfg_path=None):
26 26
         self.name = "euronews"
27 27
         self.title = "Euronews"
28 28
         self.img = "http://pbs.twimg.com/profile_images/732665354242150400/tZsCnjuh_400x400.jpg"
@@ -34,7 +34,8 @@ Connection: keep-alive
34 34
         """)
35 35
         #self.language=language
36 36
         cur_directory = os.path.dirname(os.path.abspath(__file__))
37
-        self.config_file = os.path.join(cur_directory,self.name+".cfg")
37
+        if not cfg_path: cfg_path = cur_directory
38
+        self.config_file = os.path.join(cfg_path,self.name+".cfg")
38 39
         self.options = OrderedDict([("language","en")])
39 40
         self.options_read()
40 41
         self.vid={"1": "News", "2": "European Affairs", "3": "Lifestyle", "4": "Knowledge"}

BIN
sources/euronews.pyc View File


+ 10
- 5
sources/filmix.py View File

@@ -27,7 +27,7 @@ headers2dict = lambda  h: dict([l.strip().split(": ") for l in h.strip().splitli
27 27
 
28 28
 class Source(SourceBase):
29 29
 
30
-    def __init__(self,country=""):
30
+    def __init__(self,country="",cfg_path=None):
31 31
         self.name = "filmix"
32 32
         self.title = "filmix.me"
33 33
         self.img = "http://cs5324.vk.me/g33668783/a_903fcc63.jpg"
@@ -104,7 +104,9 @@ Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
104 104
             m = re.search('<p itemprop="description"[^>]+>([^<]+)<', r, re.DOTALL)
105 105
             desc = desc0 =  util.unescape(m.group(1).strip()) if m else ""
106 106
             vid = plist[-1]
107
-            js = self.get_movie_info(vid)
107
+            m = re.search(r"meta_key = \['(\w+)', '(\w+)', '(\w+)'\]", r, re.IGNORECASE)
108
+            key = m.group(3) if m else ""
109
+            js = self.get_movie_info(vid,key)
108 110
             translations = js["message"]["translations"]["flash"]
109 111
             for pl  in translations:
110 112
                 if translations[pl].startswith("http"):
@@ -228,7 +230,9 @@ Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
228 230
         video_link = m.group(1)
229 231
         series = True if video_link == '{video-link}' else False
230 232
         vid = plist[1]
231
-        js = self.get_movie_info(vid)
233
+        m = re.search(r"meta_key = \['(\w+)', '(\w+)', '(\w+)'\]", r, re.IGNORECASE)
234
+        key = m.group(3) if m else ""
235
+        js = self.get_movie_info(vid,key)
232 236
         translations = js["message"]["translations"]["flash"]
233 237
         for pl in translations:
234 238
             if translations[pl].startswith("http"):
@@ -282,7 +286,7 @@ Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
282 286
         result = self._http_request(url,params,headers=headers)
283 287
         return result
284 288
 
285
-    def get_movie_info(self,vid):
289
+    def get_movie_info(self,vid,key=""):
286 290
         headers = headers2dict("""
287 291
     User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0
288 292
     Accept: application/json, text/javascript, */*; q=0.01
@@ -290,8 +294,9 @@ Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
290 294
     Content-Type: application/x-www-form-urlencoded; charset=UTF-8
291 295
     X-Requested-With: XMLHttpRequest
292 296
     Referer: https://filmix.me/play/%s
297
+    Cookie: ad_win12=1;
293 298
     """%vid )
294
-        post_data = {"post_id":vid}
299
+        post_data = {"post_id":vid,"key=":key}
295 300
         r = util.post("https://filmix.me/api/movies/player_data", data=post_data, headers = headers)
296 301
         if not r:
297 302
             raise Exception("Can not get movie info")

BIN
sources/filmix.pyc View File


+ 1
- 1
sources/filmon.py View File

@@ -29,7 +29,7 @@ h = HTMLParser.HTMLParser()
29 29
 
30 30
 class Source(SourceBase):
31 31
 
32
-    def __init__(self,country="lv"):
32
+    def __init__(self,country="lv",cfg_path=None):
33 33
         self.name = "filmon"
34 34
         self.title = "FilmOn"
35 35
         self.img = "http://behindthegloves.com/wp-content/uploads/2016/01/FilmOn-logo1.jpg"

BIN
sources/filmon.pyc View File


+ 3
- 2
sources/iplayer.py View File

@@ -24,7 +24,7 @@ h = HTMLParser.HTMLParser()
24 24
 
25 25
 class Source(SourceBase):
26 26
 
27
-    def __init__(self):
27
+    def __init__(self,cfg_path=None):
28 28
         self.name = "iplayer"
29 29
         self.title = "BBC iPlayer"
30 30
         self.img = "http://www.userlogos.org/files/logos/inductiveload/BBC_iPlayer_logo.png"
@@ -58,7 +58,8 @@ Connection: Keep-Alive
58 58
             "s4cpbs":"http://www.lyngsat-logo.com/hires/ss/s4c_uk.png"
59 59
         }
60 60
         cur_directory = os.path.dirname(os.path.abspath(__file__))
61
-        self.config_file = os.path.join(cur_directory,self.name+".cfg")
61
+        if not cfg_path: cfg_path = cur_directory
62
+        self.config_file = os.path.join(cfg_path,self.name+".cfg")
62 63
         self.options = OrderedDict([("user","lietotajs"),("password","parole")])
63 64
         self.options_read()
64 65
 

BIN
sources/iplayer.pyc View File


+ 100
- 109
sources/lmt.py View File

@@ -9,7 +9,7 @@ try:
9 9
     import json
10 10
 except:
11 11
     import simplejson as json
12
-import urllib2, urllib
12
+import requests
13 13
 import datetime, re, sys,os
14 14
 import ConfigParser
15 15
 from collections import OrderedDict
@@ -20,6 +20,7 @@ try:
20 20
 except:
21 21
     sys.path.insert(0,'..')
22 22
     import util
23
+from YouTubeVideoUrl import YouTubeVideoUrl
23 24
 
24 25
 headers2dict = lambda  h: dict([l.strip().split(": ") for l in h.strip().splitlines()])
25 26
 import HTMLParser
@@ -44,7 +45,6 @@ Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
44 45
 """)
45 46
         self.url = "http://straume.lmt.lv/lv/"
46 47
 
47
-
48 48
     ######### Entry point ########
49 49
     def get_content(self, data):
50 50
         print "[lmt] get_content:", data
@@ -56,81 +56,58 @@ Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
56 56
             content.extend([
57 57
                 ("Meklēt", "lmt::meklet?q={0}","","Meklēt"),
58 58
                 ("Straumes", "lmt::straumes","","Tiešraides un aktuāli video"),
59
-                ("TV", "lmt::tv","","TV tiešraides (tikai LMT tīklā)"),
59
+                #("TV", "lmt::tv","","TV tiešraides (tikai LMT tīklā)"),
60 60
                 ("Jaunākie", "lmt::video/jaunakie?videoPage=1", "", "Visu žanru jaunākie video"),
61 61
                 ("Sports", "lmt::video/sports?videoPage=1", "", "Sports"),
62 62
                 ("Kultūra un māksla", "lmt::video/kultura?videoPage=1", "", "Kultūra un māksla"),
63 63
                 ("Konferences", "lmt::video/konferences?videoPage=1", "", "Konferences"),
64 64
                 ("Raidījumi", "lmt::video/raidijumi?videoPage=1", "", "Raidījumi"),
65 65
                 ("Notikumi", "lmt::video/notikumi?videoPage=1", "", "Notikumi"),
66
-                ("Filmas un seriāli", "lmt::filmas?videoPage=1", "", "Filmas un seriāli"),
66
+                ("Filmas un seriāli", "lmt::video/filmas?videoPage=1", "", "Filmas un seriāli"),
67 67
                 ("Dažādi video", "lmt::video/dazadi?videoPage=1", "", "Dažādi video"),
68
+                ("Viedtelevīzija", "lmt::video/viedtelevizija?videoPage=1", "", "Viedtelevīzija"),
68 69
             ])
69 70
             return content
70 71
 
71
-        elif clist in ("meklet","video", "filmas"):
72
+        elif clist in ("meklet","video", "straumes","video-saraksts"):
72 73
             r=self.call(data)
73
-            result = re.findall('<a href="([^"]+)"> (.+?) </a></div>.+?> (.+?)</div>', r, re.DOTALL)
74
-            for item in result:
75
-                title = item[1].replace("<b>","").replace("</b>","")
76
-                data2 = item[0].replace("http://movieplace.lv/","")
77
-                img = self.img
78
-                desc = item[2].replace("<b>","").replace("</b>","")
79
-                content.append((title,self.name+"::"+data2,img,desc))
80
-            if '<span>&raquo;</span>' in r:
81
-                m = re.search("p=(\d+)",data)
74
+            result = re.findall('<a class="video-picture" (.+?)</li>', r, re.IGNORECASE | re.MULTILINE)
75
+            for r2 in result:
76
+                m = re.search('<a class="video-title" href="/lv/([^"]+)">([^<]+)<', r2)
77
+                title = m.group(2)
78
+                data2 = m.group(1)
79
+                m = re.search("([^ ]+) 2x", r2)
82 80
                 if m:
83
-                    page = int(m.group(1))+1
84
-                    data2 = re.sub(r"p=\d+", r"p=%s"%page, data)
85
-                    content.append(("Next page",self.name+"::"+data2,self.img,"Next page"))
86
-            return content
87
-
88
-        # Filmu saraksti ##
89
-        elif clist in ["load","dir"] and len(plist)<=3:
90
-            if clist == "jaunakas":
91
-                r = self.call("")
92
-            else:
93
-                r = self.call(data)
94
-            #r = r.decode("cp1251").encode("utf8")
95
-            if clist == "load":
96
-                result = re.findall(r' <a href="/([^"]+)" alt="([^"]+)"><img src="/([^"]+)" title="([^"]+)">.+?<div class="years">([^<]+)</div>\s+<div class="country">([^<]+)</div>', r, re.DOTALL)
97
-            else:
98
-                result = re.findall(r' <a href="/([^"]+)" alt="([^"]+)"><img src="/([^"]+)" title="[^"]+">.+?<span>([^<]+)</span>\s*<div class="country">([^<]+)</div>', r, re.IGNORECASE | re.DOTALL)
99
-            for item in result:
100
-                title = item[1]+" [%s]"%item[4] if clist=="load" else item[1]+" / %s [%s]"%(item[3],item[4])
101
-                img = "http://movieplace.lv/"+item[2]
102
-                data2 = item[0]
103
-                desc = "%s\n%s"%(title,item[5]) if clist=="load" else title
81
+                    img = m.group(1)
82
+                else:
83
+                    m = re.search('<img src="([^"]+)', r2)
84
+                    img = m.group(1) if m else ""
85
+                m = re.search('<span class="playlist-overlay">([^<]+)</span>', r2)
86
+                overlay = m.group(1) if m else ""
87
+                m = re.search('<span class="badge badge-[^>]+>([^<]+)(<[^>]+>([^<]+))*</span>', r2, re.IGNORECASE)
88
+                badge = ""
89
+                if m:
90
+                    badge = m.group(1)
91
+                    if m.group(3):
92
+                        badge = badge + m.group(3)
93
+                categories = re.findall('<span class="category-title">([^<]+)</span>', r2)
94
+                categories = "".join(categories)
95
+                if overlay:
96
+                    title = "%s [%s]"%(title,overlay)
97
+                if badge:
98
+                    title = "%s [%s]"%(title,badge)
99
+                desc = title
100
+                if categories:
101
+                    desc = desc + "\n"+ categories
104 102
                 content.append((title,self.name+"::"+data2,img,desc))
105
-            m = re.search('<[ab] class="swchItemA*1"( href="/([^"]+)" onclick="[^"]+")*><span>([^<]+)</span></[ab]> </span>', r, re.DOTALL)
103
+            m = re.search("videoPage=(\d+)",data)
106 104
             if m:
107
-                if m.group(1):
108
-                    page = int(re.search("\d+$",data).group())
109
-                    page = page+1
110
-                    data2 = re.sub("\d$","%s"%page,data)
111
-                    content.append(("Next page",self.name+"::"+data2,self.img,"Next page"))
105
+                page = int(m.group(1))+1
106
+                data2 = re.sub(r"videoPage=\d+", r"videoPage=%s"%page, data)
107
+                content.append(("Next page",self.name+"::"+data2,self.img,"Next page"))
108
+            #print content
112 109
             return content
113 110
 
114
-        ### Seriāls ###
115
-        elif clist=="dir" and len(plist)==4:
116
-            r = self.call(path)
117
-            title0 = re.search('<h2 class="title" itemprop="name">(.+?)</h2>', r, re.DOTALL).group(1)
118
-            m = re.search(r'<span>VALODA:</span> <b><em itemprop="alternativeHeadline"><a href="[^"]*" class="entAllCats">([^<]+)</a></em></b></div>\s+?<div><span>SEZONA:</span> <b>([^<]+)</b></div>', r, re.IGNORECASE | re.DOTALL)
119
-            if m:
120
-                title0 = "%s / Season %s [%s]"%(title0,m.group(2),m.group(1))
121
-            desc0 = title0
122
-            img0 = "http://movieplace.lv" + re.search('<img src="(.+?)".+?itemprop="image">', r, re.DOTALL).group(1)
123
-            #TODO
124
-            result = re.findall(r'<summary>([^<]+)</summary><iframe src="https://openload\.co/embed/[^/]+/"', r, re.DOTALL)
125
-            i = 1
126
-            for item in result:
127
-                title = title0+" - " + item
128
-                data2 = data+"?e=%s"%i
129
-                img = img0
130
-                desc = desc0
131
-                content.append((title,self.name+"::"+data2,img,desc))
132
-                i += 1
133
-            return content
134 111
 
135 112
         ### kaut kas neparedzets ###
136 113
         else:
@@ -138,68 +115,82 @@ Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
138 115
 
139 116
     def is_video(self,data):
140 117
         source,data,path,plist,clist,params,qs = self.parse_data(data)
141
-        if clist=="dir" and len(plist) == 4 and "e"in qs: # sērija
142
-            return True
143
-        elif clist=="load" and len(plist) == 4:
118
+        if not clist in ("meklet","video", "straumes","video-saraksts","home"):
144 119
             return True
145
-        else:
146
-            return False
147 120
 
148 121
     def call(self, data,params=None,headers=None,lang=""):
149 122
         if not headers: headers = self.headers
150 123
         url = self.url+data
151
-        result = self._http_request(url,params,headers=headers)
124
+        r = requests.get(url,headers = headers)
125
+        return r.content
126
+        #result = self._http_request(url,params,headers=headers)
152 127
         return result
153 128
 
154 129
     def get_streams(self,data):
155
-        print "[movieplace] get_streams:", data
130
+        print "[lmt] get_streams:", data
156 131
         if not self.is_video(data):
157 132
             return []
158 133
         source,data,path,plist,clist,params,qs = self.parse_data(data)
159 134
         r = self.call(path)
160
-        if clist=="load":
161
-            m = re.search('<h2 class="title" itemprop="name">([^<]+)</h2>', r, re.DOTALL)
162
-            title = re.search('<itemprop="name">(.+?)</itemprop="name">', r, re.DOTALL).group(1)
163
-            m = re.search(r'<div role="tabpanel" class="tab-pane fade in active" id="heading-tab4">\s*(.+?)\s*</div>', r, re.DOTALL)
164
-            desc = m.group(1) if m else title
165
-            m = re.search('<meta property="og:image" content="([^"]+)" />', r, re.DOTALL)
166
-            img = m.group(1) if m else ""
167
-            rr = []
168
-            for m in re.finditer("(RU|ENG|LAT|LAT SUB)<BR( /)*>.*?>?<BR( /)*>.*?<iframe", r, re.IGNORECASE | re.DOTALL):
169
-                if len(rr)>0:
170
-                    rr[-1]["end"] = m.start()
171
-                rr.append({"lang":m.group(1),"start":m.start(),"end":len(r)})
172
-            streams = []
173
-            for m in re.finditer(r'src="(https*://(goo\.gl|songs2dl|kodik|cdn\.kapnob|hqq|openload|sv1.servkino|vidwatch|online\.kinozz).+?)"', r, re.IGNORECASE | re.DOTALL):
174
-                url = m.group(1)
175
-                lang = "?"
176
-                for rrr in rr:
177
-                    if m.start()>rrr["start"] and m.start()<rrr["end"]:
178
-                        lang = rrr["lang"]
179
-                        break
180
-                for s in resolver.resolve(url):
181
-                    s["name"] = title
182
-                    s["desc"] = desc
183
-                    s["img"] = img
184
-                    s["type"] = self.stream_type(s["url"])
185
-                    s["lang"] = lang
186
-                    streams.append(s)
187
-            return streams
135
+        title = re.search("<h1>(.+?)</h1", r, re.IGNORECASE).group(1)
136
+        m = re.search('<a class="category-title".+?[^>]+>([^<]+)<', r, re.IGNORECASE | re.DOTALL)
137
+        categories = m.group(1) if m else ""
138
+        m = re.search('<span class="category-title">([^<]+)</span>.+?</p>', r, re.IGNORECASE | re.DOTALL)
139
+        if m:
140
+            categories = categories + m.group(1)
141
+        if categories:
142
+            tite = "%s [%s]"%(title,categories)
143
+        img = re.search('<meta property="twitter:image" content="([^"]+)">', r, re.IGNORECASE | re.DOTALL).group(1)
144
+        desc = title + "\n" + re.search('<meta property="og:description" content="([^"]+)">', r, re.IGNORECASE | re.DOTALL).group(1)
145
+        m = re.search('file: "([^"]+)"', r, re.IGNORECASE)
146
+        if m:
147
+            data2 = m.group(1)
148
+            stream = util.item()
149
+            stream["name"] = title
150
+            stream["url"] = data2
151
+            stream["img"] = img
152
+            stream["desc"] = desc
153
+            stream["resolver"] = "lmt"
154
+            return [stream]
155
+        elif re.search('src="http*://www.youtube.com/embed/(\w+).*"',r):
156
+            m = re.search('src="http*://www.youtube.com/embed/(\w+).*"',r)
157
+            video_id = m.group(1)
158
+            #http://www.youtube.com/embed/RUyQ_JJ6A84?rel=0&fs=1&wmode=transparent
159
+            data2 = YouTubeVideoUrl().extract(video_id)
160
+            s = util.item()
161
+            s["name"] = title
162
+            s["url"] = data2
163
+            s["desc"] = desc
164
+            s["img"] = img
165
+            s["resolver"] = "lmt"
166
+            return [s]
167
+        elif 'src="http://cdn.tiesraides.lv/lmtstraume.lv/' in r:
168
+            m = re.search('src="(http://cdn\.tiesraides\.lv/[^"]+)"',r)
169
+            url = m.group(1)
170
+            # src="http://cdn.tiesraides.lv/lmtstraume.lv/live-record2-ip/40?c=614127284dcd58d8a84afcf498a3ac7a&v=1405"
171
+            r = self._http_request(url)
172
+            #http://edge-telia2.tiesraides.lv/live-record2/lmtstraume.lv.40_1/manifest.f4m
173
+            m = re.search("'(http://.+?\.m3u8)'",r)
174
+            data2 = m.group(1) if m else ""
175
+            s = util.item()
176
+            s["name"] = title
177
+            s["url"] = data2
178
+            s["desc"] = desc
179
+            s["img"] = img
180
+            s["resolver"] = "lmt"
181
+            return [s]
182
+        else:
183
+            raise Exception("No stream found")
188 184
 
189 185
 
190
-        elif clist=="dir" and "e" in qs: # serialā sērija
191
-            #TODO
192
-            result = re.findall(r'<summary>([^<]+)</summary><iframe src="([^"]+)"', r, re.DOTALL)
193
-            i = int(qs["s"])-1
194
-            url0 = result[i][1]
195
-            title = title + " - " + result[i][0]
196
-        else:
197
-            #iframe src="https://openload.co/embed/wlw6Vl9zwL0/"
198
-            result = re.findall(r'<iframe src="([^"]+)"', r, re.DOTALL)
199
-            if not result:
200
-                return []
201
-            url0 = result[0]
202
-        return streams
186
+                # streams = resolver.resolve(url)
187
+            # for s in streams:
188
+            #     s["name"] = title
189
+            #     s["desc"] = desc
190
+            #     s["img"] = img
191
+            #     streams.append(s)
192
+            # return streams
193
+
203 194
 
204 195
 if __name__ == "__main__":
205 196
     country= "lv"

+ 35
- 16
sources/ltc.py View File

@@ -6,7 +6,7 @@
6 6
 # Distributed under the GNU GPL v3. For full terms see http://www.gnu.org/licenses/gpl-3.0.en.html
7 7
 #
8 8
 import sys, os, os.path, re, sys
9
-import urllib,urllib2
9
+import urllib,urllib2,urlparse
10 10
 from xml.sax.saxutils import unescape,escape
11 11
 from urllib import quote, unquote
12 12
 import datetime
@@ -24,7 +24,7 @@ h = HTMLParser.HTMLParser()
24 24
 
25 25
 class Source(SourceBase):
26 26
 
27
-    def __init__(self):
27
+    def __init__(self,cfg_path=None):
28 28
         self.name = "ltc"
29 29
         self.title = "Shortcut.lv (lattelecom.tv)"
30 30
         self.img = "picons/shortcut.png"
@@ -65,7 +65,8 @@ Accept-Language: en-US,en;q=0.8
65 65
         self.today2 = self.today.strftime("%d.%m.%Y")
66 66
 
67 67
         cur_directory = os.path.dirname(os.path.abspath(__file__))
68
-        self.config_file = os.path.join(cur_directory,self.name+".cfg")
68
+        if not cfg_path: cfg_path = cur_directory
69
+        self.config_file = os.path.join(cfg_path,self.name+".cfg")
69 70
         self.options = OrderedDict([("user","lietotajs"),("password","parole")])
70 71
         self.options_read()
71 72
 
@@ -822,9 +823,19 @@ Host: manstv.lattelecom.tv
822 823
         captions = sorted(captions,key=lambda item: item["order"],reverse=True)
823 824
         for s in streams_xml:
824 825
             for server in servers:
826
+                stream = util.item()
825 827
                 server2 = self.load_balancer(server)
826 828
                 url = "http://%s/mobile-vod/%s/playlist.m3u8?resource_id=%s&auth_token=%s"%(server2,s[0],resource_id,token)
827
-                stream = util.item()
829
+                # TODO Engima2 gstreamer vajag lai padod playlist nevis chunklist
830
+                # r3 = self._http_request(url)
831
+                # sss = re.findall(r"#EXT-X-STREAM-INF:.*?BANDWIDTH=(\d+).*?\n(.+?)$", r3, re.IGNORECASE | re.MULTILINE)
832
+                # if sss:
833
+                #     url2 = sss[0][1]
834
+                #     stream["headers"] = {"Referer":url}
835
+                #     if url2.startswith("http"):
836
+                #         url = url2
837
+                #     else:
838
+                #         url = util.hls_base(url)+url2
828 839
                 stream["url"]=url
829 840
                 stream["lang"]=s[1]
830 841
                 stream["quality"]=s[2]
@@ -1019,25 +1030,33 @@ if __name__ == "__main__":
1019 1030
 
1020 1031
     if len(sys.argv)>1 and  not "ltc::" in sys.argv[1]:
1021 1032
 
1022
-        vid = sys.argv[1]
1023
-        print "login - %s"%c.login("ivars777","xxx")
1024
-        vid = "1069"
1025
-        vid = "1462566072086"
1026
-        channelid="101"
1027
-        vid = "1350462656767"
1033
+        vid = vid2 = sys.argv[1]
1034
+        password = sys.argv[2]
1035
+        print "login - %s"%c.login("ivars777",password)
1036
+        #vid = "1069"
1037
+        #vid = "1462566072086"
1038
+        #channelid="101"
1039
+        #vid = "1350462656767"
1028 1040
         #data = c.get_stream_url(vid,"vod")
1029 1041
         #call([r"c:\Program Files\VideoLAN\VLC\vlc.exe",data["stream"]])
1030
-        pass
1042
+        #pass
1031 1043
 
1032
-        print "login2 - %s"%c.login2("ivars777","xxx")
1044
+        print "login2 - %s"%c.login2("ivars777",password)
1033 1045
         #vid2 = "animation/ultimate_avengers_ii"
1034 1046
         #vid2 = "animation/ice_age"
1035
-        vid2 = "tiesraide/ltv1"
1047
+        #vid2 = "tiesraide/ltv1"
1036 1048
         #vid2 = "arhivs/1456521417815"
1037
-        data = c.get_stream_url2(vid2)
1038
-        print data
1049
+        streams = c.get_stream_url2(vid2)
1050
+        stream = streams[-1]
1051
+        print
1039 1052
         #for s in data:
1040
-        call([r"c:\Program Files\VideoLAN\VLC\vlc.exe",data["stream"]])
1053
+        #call([r"c:\Program Files\VideoLAN\VLC\vlc.exe",stream["url"]])
1054
+        cmd = ["ffplay.exe",
1055
+               "-headers","Referer:%s"%(quote(stream["headers"]["Referer"])),
1056
+               "-headers","Connection:Keep-Alive",
1057
+               stream["url"]]
1058
+        print " ".join(cmd)
1059
+        call(cmd)
1041 1060
         pass
1042 1061
 
1043 1062
 

BIN
sources/ltc.pyc View File


+ 1
- 1
sources/movieplace.py View File

@@ -28,7 +28,7 @@ h = HTMLParser.HTMLParser()
28 28
 
29 29
 class Source(SourceBase):
30 30
 
31
-    def __init__(self, country=""):
31
+    def __init__(self, country="",cfg_path=None):
32 32
         self.name = "movieplace"
33 33
         self.title = "MoviePlace.lv"
34 34
         self.img = "http://movieplace.lv/images/logo.png"

BIN
sources/movieplace.pyc View File


+ 1
- 1
sources/mtgplay.py View File

@@ -36,7 +36,7 @@ REGIONS = [
36 36
 
37 37
 class Source(SourceBase):
38 38
     
39
-    def __init__(self,country="lv"):
39
+    def __init__(self,country="lv",cfg_path=None):
40 40
         self.name = "mtgplay"
41 41
         self.title = "Skaties.lv (TV3)"
42 42
         self.img = "http://skaties.lv/touch-icon-192x192.png"

BIN
sources/mtgplay.pyc View File


+ 1
- 1
sources/play24.py View File

@@ -24,7 +24,7 @@ h = HTMLParser.HTMLParser()
24 24
     
25 25
 class Source(SourceBase):
26 26
     
27
-    def __init__(self,country="lv"):
27
+    def __init__(self,country="lv",cfg_path=None):
28 28
         self.name = "play24"
29 29
         self.title = "Play24.lv"
30 30
         self.img = "http://play24.lv/images/play24-logo-black.png"

BIN
sources/play24.pyc View File


+ 1
- 1
sources/replay.py View File

@@ -26,7 +26,7 @@ from YouTubeVideoUrl import YouTubeVideoUrl
26 26
 
27 27
 class Source(SourceBase):
28 28
 
29
-    def __init__(self,country="lv"):
29
+    def __init__(self,country="lv",cfg_path=None):
30 30
         self.name = "replay"
31 31
         self.title = "Replay.lv (LTV)"
32 32
         self.img = "http://replay.lsm.lv/apple-touch-icon.png"

BIN
sources/replay.pyc View File


+ 1
- 1
sources/serialguru.py View File

@@ -21,7 +21,7 @@ h = HTMLParser.HTMLParser()
21 21
 
22 22
 class Source(SourceBase):
23 23
 
24
-    def __init__(self,country=""):
24
+    def __init__(self,country="",cfg_path=None):
25 25
         self.name = "serialguru"
26 26
         self.title = "SerialGURU.ru"
27 27
         self.img = "http://serialguru.ru/images/xlogo_new.png.pagespeed.ic.0sre2_2OJN.png"

BIN
sources/serialguru.pyc View File


+ 2
- 0
sources/streams.cfg View File

@@ -10,6 +10,7 @@ Skaties.lv (TV3)|mtgplay::home|http://skaties.lv/touch-icon-192x192.png|MTG kan
10 10
 Shortcut (lattelecom.tv)|ltc::home|https://kursors.lv/wp-content/uploads/2016/07/Shortcut-logo.png|lattelecom TV, arhīves un video
11 11
 Play24.lv (Riga24TV)|play24::home|http://play24.lv/images/play24-logo-black.png|play24.lv (Riga24TV)tiešraide un arhīvs
12 12
 viaplay.lv|viaplay::home|https://yt3.ggpht.com/-noVdjbNR-V8/AAAAAAAAAAI/AAAAAAAAAAA/yZ9XNP5urLY/s900-c-k-no-mo-rj-c0xffffff/photo.jpg|Viaplay.lv - filmas latviešu, krievu u.c. valodās
13
+LMT straume|lmt::home|http://www.lob.lv/images/logo/lmt_straume_vert_rgb.png|LMT straume - dažādi video latviesu valodā
13 14
 TVDom.tv|tvdom::home|https://tvdom.tv/front/assets/images/logo.png|PBK tiešraides un arhīvs
14 15
 BBC iPlayer|iplayer::home|http://www.userlogos.org/files/logos/inductiveload/BBC_iPlayer_logo.png|BBC live streams and arhive
15 16
 Euronews|euronews::home|http://pbs.twimg.com/profile_images/732665354242150400/tZsCnjuh_400x400.jpg|Euronews live streams and archive
@@ -22,6 +23,7 @@ FilmOn|filmon::home|http://behindthegloves.com/wp-content/uploads/2016/01/FilmOn
22 23
 MTGPlay|config::mtg|https://www.mtg.com/wp-content/uploads/2015/11/MTG-Logo-Medium-Red-PNG.png|Other countries MTG media portals content
23 24
 Filmas.lv|filmas::home|https://www.filmas.lv/wp-content/uploads/2013/06/LVfilmas-logo-jauns21.png|Filmas.lv - Latvijas filmas
24 25
 
26
+
25 27
 [my_tv]
26 28
 My Tv
27 29
 ..return|back|default|Atgriezties atpakaļ

+ 3
- 2
sources/tvdom.py View File

@@ -25,7 +25,7 @@ h = HTMLParser.HTMLParser()
25 25
 
26 26
 class Source(SourceBase):
27 27
 
28
-    def __init__(self,country="lv"):
28
+    def __init__(self,country="lv",cfg_path=None):
29 29
         self.name = "tvdom"
30 30
         self.title = "TVDom.tv"
31 31
         self.img = "https://tvdom.tv/front/assets/images/logo.png"
@@ -37,7 +37,8 @@ class Source(SourceBase):
37 37
         self.token = None
38 38
 
39 39
         cur_directory = os.path.dirname(os.path.abspath(__file__))
40
-        self.config_file = os.path.join(cur_directory,self.name+".cfg")
40
+        if not cfg_path: cfg_path = cur_directory
41
+        self.config_file = os.path.join(cfg_path,self.name+".cfg")
41 42
         self.options = OrderedDict([("user","lietotajs"),("password","parole")])
42 43
         self.options_read()
43 44
 

BIN
sources/tvdom.pyc View File


+ 3
- 2
sources/ustvnow.py View File

@@ -29,7 +29,7 @@ h = HTMLParser.HTMLParser()
29 29
 
30 30
 class Source(SourceBase):
31 31
 
32
-    def __init__(self,country="lv"):
32
+    def __init__(self,country="lv",cfg_path=None):
33 33
         self.name = "ustvnow"
34 34
         self.title = "USTVNow"
35 35
         self.img = "http://watch.ustvnow.com/assets/ustvnow/img/ustvnow_og_image.png"
@@ -39,7 +39,8 @@ class Source(SourceBase):
39 39
         self.country=country
40 40
         self.token = ""
41 41
         cur_directory = os.path.dirname(os.path.abspath(__file__))
42
-        self.config_file = os.path.join(cur_directory,self.name+".cfg")
42
+        if not cfg_path: cfg_path = cur_directory
43
+        self.config_file = os.path.join(cfg_path,self.name+".cfg")
43 44
         self.options = OrderedDict([("user","lietotajs"),("password","parole")])
44 45
         self.options_read()
45 46
 

BIN
sources/ustvnow.pyc View File


+ 1
- 1
sources/viaplay.cfg View File

@@ -1,5 +1,5 @@
1 1
 [viaplay]
2 2
 user = ivars777@gmail.com
3 3
 password = kaskade7
4
-device = 
4
+device = 14e06be92a07f9936749b560394669b7f32d3540-499b21d2-e6ec-4973-b57c-6db7cf43bdd0=c27f3554-13f4-40ca-a11a-6d97f832613a
5 5
 

+ 11
- 4
sources/viaplay.py View File

@@ -32,7 +32,7 @@ headers2dict = lambda  h: dict([l.strip().split(": ") for l in h.strip().splitli
32 32
 
33 33
 class Source(SourceBase):
34 34
 
35
-    def __init__(self,language="en"):
35
+    def __init__(self,language="en",cfg_path=None):
36 36
         self.name = "viaplay"
37 37
         self.title = "viaplay.lv"
38 38
         self.img = "https://yt3.ggpht.com/-noVdjbNR-V8/AAAAAAAAAAI/AAAAAAAAAAA/yZ9XNP5urLY/s900-c-k-no-mo-rj-c0xffffff/photo.jpg"
@@ -46,7 +46,8 @@ Upgrade-Insecure-Requests: 1
46 46
         """)
47 47
         #self.language=language
48 48
         cur_directory = os.path.dirname(os.path.abspath(__file__))
49
-        self.config_file = os.path.join(cur_directory,self.name+".cfg")
49
+        if not cfg_path: cfg_path = cur_directory
50
+        self.config_file = os.path.join(cfg_path,self.name+".cfg")
50 51
         self.options = OrderedDict([("user","change_user"),("password","change_password"),("device","")])
51 52
         self.options_read()
52 53
         self.device = self.options["device"]
@@ -91,8 +92,10 @@ Connection: keep-alive
91 92
 Upgrade-Insecure-Requests: 1
92 93
 Content-Type: application/x-www-form-urlencoded
93 94
 """)
94
-        url = "https://viaplay.lv/tdi/login/nav/formular?csrfToken=%s"%self.csrfToken
95
+        url = "https://viaplay.lv/tdi/login/nav/form?csrfToken=%s"%self.csrfToken
96
+        #      https://viaplay.lv/tdi/login/nav/form?_infuse=1&_ts=1490554674901&csrfToken=
95 97
         params = "nav_redirectUri=https%3A%2F%2Fviaplay.lv%2F&nav_email={}&nav_password={}".format(urllib.quote(user),urllib.quote(password))
98
+        #         nav_redirectUri=https%3A%2F%2Fviaplay.lv%2F&nav_email=ivars777%40gmail.com&nav_password=kaskade7&nav_remember=true
96 99
         headers["Cookie"] = "ott_cookies_confirmed=1; PLAY_SESSION=%s;"%self.play_session
97 100
         if self.device:
98 101
             headers["Cookie"] += "ott_dids=%s"%self.device
@@ -101,6 +104,9 @@ Content-Type: application/x-www-form-urlencoded
101 104
         if not "Set-Cookie" in r.headers:
102 105
             self.play_session = None
103 106
             return False
107
+        if not "ott_web_sac" in r.cookies:
108
+            self.play_session = None
109
+            return False
104 110
         self.ott = r.cookies["ott_web_sac"]
105 111
 
106 112
         ### Dabu iekārtas ID ###
@@ -325,7 +331,7 @@ Referer: https://viaplay.lv/
325 331
         streams = []
326 332
         url = "https://viaplay.lv/"+data
327 333
         r = self._http_request(url)
328
-        if clist=="series":
334
+        if clist in ("series","livestream"): # TODO nerāda overtime u.c.
329 335
             m = re.search(r'<h1 class="is-bottom-sticked is-size-h2">(.+?)</h1>.*?<h2 class="is-size-h4">.*?<p class="is-size-h6 is-strong is-bottom-sticked">(.+?)<div class="toggler-content">\s+<p>(.+?)</p>', r, re.DOTALL)
330 336
             if not m:
331 337
                 raise Exception("Problem getting video information")
@@ -374,6 +380,7 @@ Referer: https://viaplay.lv/
374 380
             stype = "DASH" if "dash" in s["type"] else "HLS"
375 381
             if "drm" in s: ###
376 382
                 # TODO, encrypted stream
383
+                raise Exception("Can not play DRM protected stream!\nOnly local and Russian content available without DRM")
377 384
                 continue
378 385
             url = s["src"]
379 386
             #urlp = util.streamproxy_encode(s["src"])

BIN
sources/viaplay.pyc View File


+ 13
- 3
util.py View File

@@ -173,8 +173,8 @@ def player(url, title = "", suburl= "",headers={}):
173 173
         #'!decodebin!autovideosink'
174 174
     ]
175 175
     cmd3 = ["ffplay.exe",url]
176
-    cmd = cmd1 if url.startswith("https") else cmd2
177
-    ret = call(cmd3)
176
+    cmd = cmd3 if url.startswith("https") else cmd2
177
+    ret = call(cmd)
178 178
     #if ret:
179 179
         #a = raw_input("*** Error, continue")
180 180
     return
@@ -205,6 +205,12 @@ def make_fname(title):
205 205
     fname0 = re.sub("['""]","",fname0)
206 206
     return fname0
207 207
 
208
+
209
+def hls_base(url):
210
+    url2 = url.split("?")[0]
211
+    url2 = "/".join(url2.split("/")[0:-1])+ "/"
212
+    return url2
213
+
208 214
 def stream_change(stream):
209 215
     #return stream # TODO
210 216
     if "resolver" in stream and stream["resolver"] in ("viaplay","hqq","filmas") or \
@@ -678,10 +684,14 @@ if __name__ == "__main__":
678 684
     #url = "http://localhost:88/https://walterebert.com/playground/video/hls/ts/480x270.m3u8?token=xxxx~User-Agent=Enigma2~Cookie=xxxxx"
679 685
     url = "http://hyt4d6.vkcache.com/secip/0/UMQ3q2gNjTlOPnEVm3iTiA/ODAuMjMyLjI0MC42/1479610800/hls-vod-s3/flv/api/files/videos/2015/09/11/144197748923a22.mp4.m3u8http://hyt4d6.vkcache.com/secip/0/Y-ZA1qRm8toplc0dN_L6_w/ODAuMjMyLjI0MC42/1479654000/hls-vod-s3/flv/api/files/videos/2015/09/11/144197748923a22.mp4.m3u8"
680 686
     headers = {"User-Agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 9_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/47.0.2526.70 Mobile/13C71 Safari/601.1.46"}
687
+    url = "http://str1e.lattelecom.tv/mobile-vod/mp4:sf_fantastic_beasts_and_where_to_find_them_en_hd.mp4/playlist.m3u8?resource_id=fantastic_beasts_and_where_to_find_them&auth_token=6NAvMFDG+rYTAc4hb5JeL2bmsaRR7bAE23M6KDmhKYOGyXoo0gDpJUE9scYy+nQmfbgk03cWMe9MuXWSH1GqwolEk2jOQ/8Mrg7tOdbwrA8zM7nmkfCZPqQkwajZN4mfSJQVKHqXqJ8="
688
+    headers={}
689
+    print url
681 690
     urlp = streamproxy_encode(url,headers)
682 691
     print urlp
692
+    url2,headers2 = streamproxy_decode(urlp)
693
+    #print url2 - 2
683 694
     player(urlp)
684
-
685 695
     pass
686 696
 
687 697