Ivars 5 years ago
parent
commit
2ef1b529ca
2 changed files with 574 additions and 1 deletions
  1. 1
    1
      changelog.md
  2. 573
    0
      ltcproxy.py

+ 1
- 1
changelog.md View File

1
 **08.07.2019**
1
 **08.07.2019**
2
-- izmaiņas gitā (autonoms projekts)
2
+- izmaiņas GITā (autonoms projekts)
3
 
3
 
4
 **23.02.2019**
4
 **23.02.2019**
5
 - salabots tvdom
5
 - salabots tvdom

+ 573
- 0
ltcproxy.py View File

1
+#!/bin/env python
2
+# -*- coding: utf-8 -*-
3
+"""
4
+Shortcut.lv proxy server
5
+
6
+usage: %s start|stop|restart|manualstart [options]
7
+    -p PORT         - port number
8
+    -s WSGI_SERVER  - wsgi server - wsgiref,cheroot,mtwsgi,waitress...
9
+    -d              - debug printout
10
+    -r              - remote debug mode (ptvsd)"""
11
+
12
+import os, sys, time
13
+import urllib,urlparse, urllib2, requests
14
+from urllib import unquote, quote
15
+import re, json
16
+import ConfigParser, getopt
17
+import arrow
18
+from diskcache import Cache
19
+import daemonize
20
+import bottle
21
+from bottle import Bottle, hook, response, route, request, run
22
+
23
+cunicode = lambda s: s.decode("utf8") if isinstance(s, str) else s
24
+cstr = lambda s: s.encode("utf8") if isinstance(s, unicode) else s
25
+headers2dict = lambda  h: dict([l.strip().split(": ") for l in h.strip().splitlines()])
26
+
27
+headers0 = headers2dict("""
28
+User-Agent: Shortcut.lv v2.9.1 / Dalvik/1.6.0 (Linux; U; Android 4.4.2; SM-G900FD Build/KOT49H)
29
+Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
30
+""")
31
+url0 = "https://manstv.lattelecom.tv/api/v1.7/get/content/"
32
+
33
+cur_directory = os.path.dirname(os.path.realpath(__file__))
34
+cache_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "cache")
35
+if not os.path.exists(cache_dir):
36
+    os.mkdir(cache_dir)
37
+
38
+config = ConfigParser.ConfigParser()
39
+proxy_cfg_file = os.path.join(cur_directory, "ltcproxy.cfg")
40
+
41
+DEBUG = False
42
+PORT_NUMBER = 8881
43
+REDIRECT = False
44
+CACHE = True
45
+KEY = ["1234","3786"]
46
+SERVER = "wsgiref"
47
+WORKERS = 10
48
+LTC_USER = "ivars777"
49
+LTC_PASSWORD = "kaskade7"
50
+
51
+if not os.path.exists(proxy_cfg_file):
52
+    config.add_section("ltcproxy")
53
+    config.set("ltcproxy", "debug", DEBUG)
54
+    config.set("ltcproxy", "port", PORT_NUMBER)
55
+    config.set("ltcproxy", "redirect", REDIRECT)
56
+    config.set("ltcproxy", "cache", CACHE)
57
+    config.set("ltcproxy", "key", " ".join(KEY))
58
+    config.set("ltcproxy", "wsgi", SERVER)
59
+    config.set("ltcproxy", "workers", WORKERS)
60
+    config.set("ltcproxy", "ltc_user", LTC_USER)
61
+    config.set("ltcproxy", "ltc_password", LTC_PASSWORD)
62
+    config.write(open(proxy_cfg_file, "w"))
63
+else:
64
+    config.read(proxy_cfg_file)
65
+    DEBUG = config.getboolean("ltcproxy", "debug")
66
+    PORT_NUMBER = config.getint("ltcproxy", "port")
67
+    REDIRECT = config.getboolean("ltcproxy", "redirect")
68
+    CACHE = config.getboolean("ltcproxy", "cache")
69
+    KEY = config.get("ltcproxy", "key").split(" ")
70
+    SERVER = config.get("ltcproxy", "wsgi")
71
+    WORKERS = config.getint("ltcproxy", "workers")
72
+    LTC_USER = config.get("ltcproxy", "ltc_user")
73
+    LTC_PASSWORD = config.get("ltcproxy", "ltc_password")
74
+
75
+s = Cache(cache_dir)
76
+app = Bottle()
77
+token = None
78
+
79
+########################################################################################
80
+
81
+@app.hook('before_request')
82
+def set_globals():
83
+    global s, headers0, token
84
+    key = request.path.split("/")[1]
85
+    if not key in KEY:
86
+        print "Error: Wrong key - %s"% key
87
+        raise bottle.HTTPError(500, "Wrong key")
88
+    s = Cache(cache_dir)
89
+    if "token" in s and s["token"]:
90
+        token = s["token"]
91
+    else:
92
+        token = login(LTC_USER, LTC_PASSWORD)
93
+        if token:
94
+            s.set("token", token, expire=3600*24*1) #  pēc 1d ekspirejas
95
+            print "** %s: token=%s" % (request.remote_addr,token)
96
+        else:
97
+            print "Can not login"
98
+            raise bottle.HTTPError(500, "Can not login")
99
+
100
+# @app.route('/playstream/<url:re:.*>')
101
+
102
+
103
+### Live playlist ###
104
+@app.route("/<key>/live/<ch>/")
105
+def get_live(key, ch):
106
+    global s, token, headers0
107
+    path0, rest = hls_split(request.url)
108
+    response.content_type = "application/x-mpegURL" # r.headers["content-type"] # 'application/vnd.apple.mpegurl' # application/x-mpegURL
109
+    if "c"+ch in s:
110
+        stream_url2 = s["c"+ch]
111
+        mediaid = s["m"+ch]
112
+        print "** %s: serving live playlist for %s (%s) from cache" % (request.remote_addr,path0,mediaid )
113
+    else:
114
+        stream_url2, mediaid = refresh_live_chunklist_url(ch)
115
+        print "** %s: getting ive playlist for %s (%s)" % (request.remote_addr,path0,mediaid )
116
+    stream_url2 += token
117
+
118
+    if REDIRECT:
119
+        bottle.redirect(stream_url2, 307)
120
+
121
+    for i in range(3):
122
+        r2 = requests.get(stream_url2,headers=headers0)
123
+        if r2.status_code == 200:
124
+            break
125
+        time.sleep(1)
126
+    else:
127
+        print "Error %s getting live chunklist %s"% (r2.status_code,stream_url2)
128
+        raise bottle.HTTPError(r2.status_code)
129
+    return r2.content
130
+
131
+### Live TS chunk ###
132
+@app.route("/<key>/live/<ch>/<tail>")
133
+def get_live_chunk(key, ch, tail):
134
+    global s, token, headers0
135
+    path0, rest = hls_split(request.url)
136
+    chid = re.search("resource_id=c-(\\w+)",rest).group(1)
137
+    chunkid = re.search("(\d+)\.ts", request.url).group(1)
138
+    path2 = ch + "/" +  chunkid
139
+    if CACHE and path2 in s:
140
+        print "** %s: serving live ts %s from cache" % (request.remote_addr,path2)
141
+        f = s.get(path2, read=True)
142
+        response.headers["Content-Type"] =  s[path2+"@"] #'video/MP2T'
143
+        response.headers["Content-Length"] = s[path2+"#"]
144
+        while True:
145
+            chunk = f.read(8192)
146
+            if not chunk:
147
+                break
148
+            yield chunk
149
+
150
+    else: #  no cache
151
+        if ch in s:
152
+            stream_url = s[ch]
153
+            mediaid= s["m"+ch]
154
+        else:
155
+            print "No stream_url %s in cache" % path0
156
+            raise bottle.HTTPError(500)
157
+        base0, rest0 = hls_base(stream_url)
158
+        rest2 = "media_%s_%s.ts?resource_id=c-%s&auth_token=app_" % (mediaid, chunkid, chid)
159
+        url = base0 + rest2 + token
160
+        url2 = hls_base(stream_url)[0] + rest
161
+        headers = dict(request.headers)
162
+        del headers["Host"]
163
+        # headers["Authorization"] = "Bearer " + token
164
+        print "** %s: getting live ts from %s(%s)- %s" % (request.remote_addr, path2, mediaid,url[:40])
165
+        if DEBUG:
166
+            print "=== Request headers ==="
167
+            print_headers(headers)
168
+        r = requests.get(url, stream=True, headers=headers0)
169
+        if r.status_code <> 200:
170
+            r = requests.get(url, stream=True, headers=headers0) # try once more
171
+            if r.status_code <> 200:
172
+                # Refresh chunklist
173
+                print "## %s: Refreshing chunklist/mediaid  for live channel %s" %(request.remote_addr, ch)
174
+                chunklist_url, mediaid = refresh_live_chunklist_url(ch)
175
+                rest2 = "media_%s_%s.ts?resource_id=c-%s&auth_token=app_" % (mediaid, chunkid, chid)
176
+                url = base0 + rest2 + token
177
+                url2 = chunklist_url + token
178
+                print "** %s: getting live ts from %s(%s)- %s" % (request.remote_addr, path2, mediaid,url[:40])
179
+                r = requests.get(url, stream=True, headers=headers0)
180
+                if r.status_code <> 200:
181
+                    print "Error %s opening stream \n%s" %(r.status_code,url)
182
+                    print url2
183
+                    raise bottle.HTTPError(r.status_code, "Error opening stream "+url)
184
+
185
+        content = ""
186
+        response.content_type = r.headers["content-type"] # 'application/vnd.apple.mpegurl' #
187
+        # response.headers.clear()
188
+        for k in r.headers:
189
+            response.headers[k] =  r.headers[k]
190
+        if DEBUG:
191
+            print "=== Response headers ==="
192
+            print_headers(response.headers)
193
+        for chunk in r.iter_content(chunk_size=8192):
194
+            if chunk:
195
+                content += chunk
196
+                yield chunk
197
+        if len(content) <> int(r.headers["content-length"]):
198
+            print "Content length problem"
199
+        if CACHE:
200
+            s.set(path2, content, expire=3600, read=True)
201
+            s.set(path2+"#", len(content), expire=3600, read=True)
202
+            s.set(path2+"@", r.headers["Content-Type"], expire=3600, read=True)
203
+
204
+### Archive playlist ###
205
+@app.route("/<key>/live/<ch>/<ts>/")
206
+def get_archive(key, ch, ts):
207
+    global s, token, headers0
208
+    path0, rest = hls_split(request.url)
209
+    start = int(ts) + 60 * 5
210
+    epg = get_epg(ch, start)
211
+    print "** %s: getting archive playlist for channel %s" % (request.remote_addr,path0)
212
+    if epg:
213
+        epgid = epg["id"]
214
+        epg_start = int(epg["attributes"]["unix-start"])
215
+        epg_stop = int(epg["attributes"]["unix-stop"])
216
+        epg_title = epg["attributes"]["title"]
217
+    else:
218
+        print "EPG not found"
219
+        raise bottle.HTTPError(500, "EPG not found")
220
+
221
+    stream_url = epg_get_stream_url(epgid)
222
+    if REDIRECT:
223
+        bottle.redirect(stream_url, 307)
224
+
225
+    # Getting chunklist
226
+    stream_url2, mediaid = refresh_epg_chunklist_url(stream_url)
227
+    r2 = requests.get(stream_url2)
228
+    if r2.status_code <> 200:
229
+        print "Error %s getting archive chunklist %s"% (r2.status_code,stream_url2)
230
+        raise bottle.HTTPError(r2.status_code)
231
+    result = re.findall(r"#EXTINF:([\d\.]+),\n(.+)", r2.content)
232
+    ll = 0
233
+    i = 0
234
+    for chunk_len, chunk_url in result:
235
+        ll += float(chunk_len)
236
+        if ll > (start - epg_start):
237
+            break
238
+        i += 1
239
+    result2 =result[i:]
240
+    content = re.search("(^.+?)#EXTINF", r2.content, re.DOTALL).group(1)
241
+    for chunk_len, chunk_url in result2:
242
+        content += "#EXTINF:%s,\n" % chunk_len
243
+        content += chunk_url + "\n"
244
+    content += "#EXT-X-ENDLIST"
245
+    response.content_type = r2.headers["content-type"] # 'application/vnd.apple.mpegurl' #
246
+    return content
247
+
248
+def live_get_stream_url(ch):
249
+    global s, token, headers0
250
+    if ch in s:
251
+        stream_url = s[ch]
252
+        stream_url += token
253
+    else:
254
+        # Getting live stream url
255
+        url = url0 + "live-streams/%s?include=quality&auth_token=app_%s" % (ch, token)
256
+        headers = headers0.copy()
257
+        headers["Authorization"] = "Bearer " + token
258
+        r = requests.get(url, headers=headers)
259
+        if r.status_code <> 200:
260
+            print "Error getting epg stream url "+url
261
+            raise bottle.HTTPError(r.status_code, "Error getting epg stream url "+url)
262
+        js = json.loads(r.content)
263
+        stream_url = js["data"][0]["attributes"]["stream-url"]
264
+        stream_url0 = stream_url.replace(token, "")
265
+        s.set(ch, stream_url0, expire=3600*24*7, read=False)
266
+    return str(stream_url)
267
+
268
+
269
+def epg_get_stream_url(epgid):
270
+    global s, token, headers0
271
+    if epgid in s:
272
+        stream_url = s[epgid]
273
+        stream_url += token
274
+    else:
275
+        # Getting epg stream url
276
+        url = url0 + "record-streams/%s?include=quality&auth_token=app_%s" % (epgid, token)
277
+        headers = headers0.copy()
278
+        headers["Authorization"] = "Bearer " + token
279
+        r = requests.get(url, headers=headers)
280
+        if r.status_code <> 200:
281
+            print "Error getting epg stream url "+url
282
+            raise bottle.HTTPError(r.status_code, "Error getting epg stream url "+url)
283
+        js = json.loads(r.content)
284
+        stream_url = js["data"][0]["attributes"]["stream-url"]
285
+        stream_url0 = stream_url.replace(token, "")
286
+        s.set(epgid, stream_url0, expire=3600*24*7, read=False)
287
+    return str(stream_url)
288
+
289
+def refresh_live_chunklist_url(ch):
290
+    global s, token, headers0
291
+    stream_url = live_get_stream_url(ch)
292
+    r = requests.get(stream_url)
293
+    if r.status_code <> 200:
294
+        print "Error %s getting live chunklist %s"% (r.status_code,stream_url)
295
+        raise bottle.HTTPError(r.status_code)
296
+    chid = re.search("resource_id=c\\-(\\w+)",stream_url).group(1)
297
+    rest2 = re.search("chunklist.+$", r.content).group(0).replace(token,"")
298
+    mediaid = re.search("chunklist_(.+?)\\.m3u8",rest2).group(1)
299
+    base2 = hls_base(stream_url)[0]
300
+    stream_url2 = base2 + rest2 # chunlist url
301
+    s.set("m"+ch, mediaid, expire=3600*24*7, read=False)
302
+    s.set("c"+ch, stream_url2, expire=3600*24*7, read=False)
303
+    return stream_url2,mediaid
304
+
305
+def refresh_epg_chunklist_url(stream_url):
306
+    global s, token, headers0
307
+    r = requests.get(stream_url)
308
+    if r.status_code <> 200:
309
+        print "Error %s getting archive chunklist %s"% (r.status_code,stream_url)
310
+        raise bottle.HTTPError(r.status_code)
311
+    epgid = re.search("resource_id=a-(\\d+)",stream_url).group(1)
312
+    rest2 = re.search("chunklist.+$", r.content).group(0)
313
+    mediaid = re.search("chunklist_(.+?)\\.m3u8",rest2).group(1)
314
+    s.set("m"+epgid, mediaid, expire=3600*24*7, read=False)
315
+    base2 = hls_base(stream_url)[0]
316
+    stream_url2 = base2 + rest2 # chunlist url
317
+    return stream_url2,mediaid
318
+
319
+
320
+### Archive ts chunk ###
321
+@app.route("/<key>/live/<ch>/<ts>/<tail>")
322
+def get_archive_chunk(key, ch, ts, tail):
323
+    global s, token, headers0
324
+    path0, rest = hls_split(request.url)
325
+    epgid = re.search("resource_id=a-(\\d+)",rest).group(1)
326
+    chunkid = re.search("(\\d+)\\.ts", rest).group(1)
327
+    path2 = epgid + "/" +  chunkid
328
+    if CACHE and path2 in s:
329
+        print "** %s: serving archive ts from cache %s" % (request.remote_addr,path2)
330
+        f = s.get(path2, read=True)
331
+        response.headers["Content-Type"] =  s[path2+"@"] #'video/MP2T'
332
+        response.headers["Content-Length"] = s[path2+"#"]
333
+        while True:
334
+            chunk = f.read(8192)
335
+            if not chunk:
336
+                break
337
+            yield chunk
338
+
339
+    else: #  No cache
340
+        stream_url = epg_get_stream_url(epgid)
341
+        if "m"+epgid in s:
342
+            mediaid= s["m"+epgid]
343
+        else:
344
+            chunklist_url, mediaid = refresh_epg_chunklist_url(stream_url)
345
+        base0, rest0 = hls_base(stream_url)
346
+        #media_w76603200_0.ts?resource_id=a-6559656352477&auth_token=app_
347
+        rest2 = "media_%s_%s.ts?resource_id=a-%s&auth_token=app_" % (mediaid, chunkid, epgid)
348
+        url = base0 + rest2 + token
349
+        print "** %s: getting archive ts from %s(%s) - %s" % (request.remote_addr,path2, mediaid, rest2[:rest2.index("?")])
350
+        #print url
351
+        headers = dict(request.headers)
352
+        del headers["Host"]
353
+        # headers["Authorization"] = "Bearer " + token
354
+
355
+        r = requests.get(url, stream=True, headers=headers)
356
+        if r.status_code <> 200:
357
+            r = requests.get(url, stream=True, headers=headers) # try once more
358
+            if r.status_code <> 200:
359
+                # Refresh chunklist
360
+                print "## %s: Refreshing chunklist/mediaid  for epg %s" %(request.remote_addr, epgid)
361
+                chunklist_url, mediaid = refresh_epg_chunklist_url(stream_url)
362
+                rest2 = "media_%s_%s.ts?resource_id=a-%s&auth_token=app_" % (mediaid, chunkid, epgid)
363
+                url = base0 + rest2 + token
364
+                print "** %s: getting archive ts from %s(%s) - %s" % (request.remote_addr, path2, mediaid, rest2[:rest2.index("?")])
365
+                r = requests.get(url, stream=True, headers=headers0)
366
+                if r.status_code <> 200:
367
+                    print "Error %s opening stream \n%s" %(r.status_code,url)
368
+                    raise bottle.HTTPError(r.status_code, "Error opening stream "+url)
369
+
370
+        content = ""
371
+        response.content_type = r.headers["content-type"] # 'application/vnd.apple.mpegurl' #
372
+        # response.headers.clear()
373
+        for k in r.headers:
374
+            response.headers[k] =  r.headers[k]
375
+        if DEBUG:
376
+            print_headers(response.headers)
377
+        for chunk in r.iter_content(chunk_size=8192):
378
+            if chunk:
379
+                content += chunk
380
+            yield chunk
381
+        if CACHE:
382
+            path2 = epgid + "/" + chunkid
383
+            s.set(path2, content, expire=3600, read=True)
384
+            s.set(path2+"#", len(content), expire=3600, read=True)
385
+            s.set(path2+"@", r.headers["Content-Type"], expire=3600, read=True)
386
+
387
+
388
+@app.route("/<key>/vod/<ch>/")
389
+def get_vod(key, ch):
390
+    global s, token, headers0
391
+    path0, rest = hls_split(request.url)
392
+    if path0 in s:
393
+        stream_url = s[path0] + token
394
+        print "** %s: getting vod to %s from cache (%s)" % (request.remote_addr, path0)
395
+    else:
396
+        url = url0 + "vod-streams/%s?include=language,subtitles,quality &auth_token=app_%s" % (ch, token)
397
+        headers = headers0.copy()
398
+        headers["Authorization"] = "Bearer " + token
399
+        r = requests.get(url, headers=headers)
400
+        if r.status_code <> 200:
401
+            raise bottle.HTTPError(r.status_code, "Error opening stream "+url)
402
+        js = json.loads(r.content)
403
+        stream_url = js["data"][0]["attributes"]["stream-url"]
404
+        stream_url0 = stream_url.replace(token, "")
405
+        s.set(path0, stream_url0, expire=3600*24*7, read=False)
406
+        print "** %s: changing vod to %s (%s)" % (request.remote_addr, path0)
407
+    if True: # REDIRECT:
408
+        bottle.redirect(stream_url, 307)
409
+    r = requests.get(stream_url)
410
+    if r.status_code <> 200:
411
+        raise bottle.HTTPError(r.status_code)
412
+    response.content_type = r.headers["content-type"] # 'application/vnd.apple.mpegurl' #
413
+    return r.content
414
+
415
+
416
+def get_epg(ch, start):
417
+    url = url0 + "epgs/?filter[channel]=%s&filter[utFrom]=%s&filter[utTo]=%s&include=channel&page[size]=40page[number]=1" % (ch, start, start )
418
+    r = requests.get(url)
419
+    if r.status_code <> 200:
420
+        raise bottle.HTTPError(500, "EPG not found")
421
+    js = json.loads(r.content)
422
+    if "data" in js:
423
+        for epg in js["data"]:
424
+            if int(epg["id"]) < 0:
425
+                continue
426
+            else:
427
+                break
428
+        return epg
429
+    else:
430
+        return None
431
+
432
+
433
+####################################################################
434
+# Run WSGI server
435
+def start(server,port):
436
+    print "*** Starting ltcproxy ***"
437
+    options = {}
438
+    if server == "mtwsgi":
439
+        import mtwsgi
440
+        server = mtwsgi.MTServer
441
+        options = {"thread_count": WORKERS,}
442
+
443
+    run(app=app,server=server, host='0.0.0.0',
444
+            port=port,
445
+            reloader=False,
446
+            quiet=False,
447
+            plugins=None,
448
+            debug=True,
449
+            config=None,
450
+            **options)
451
+
452
+def login(user,password):
453
+    """Login in to site, get token"""
454
+
455
+    # Dabūjam tokenu
456
+    url = "https://manstv.lattelecom.tv/api/v1.7/post/user/users/%s" % user
457
+    params = "uid=5136baee57505694&password=%s&" % (password)
458
+    headers = headers2dict("""
459
+User-Agent: Shortcut.lv v2.9.1 / Dalvik/1.6.0 (Linux; U; Android 4.4.2; SM-G900FD Build/KOT49H)
460
+Content-Type: application/x-www-form-urlencoded; charset=UTF-8
461
+Host: manstv.lattelecom.tv
462
+""" )
463
+    try:
464
+        r = urllib2.Request(url, data=params, headers=headers)
465
+        u = urllib2.urlopen(r)
466
+        content = u.read()
467
+        u.close()
468
+    except Exception as ex:
469
+        return None
470
+    if r and "token" in content:
471
+        token = re.search('"token":"(.+?)"', content).group(1)
472
+        return token
473
+    else:
474
+        return False
475
+
476
+def refresh_token(token):
477
+    """Refresh"""
478
+
479
+    url = "https://manstv.lattelecom.tv/api/v1.7/post/user/refresh-token/"
480
+    params = "uid=5136baee57505694&token=%s&" % (token)
481
+    headers = headers2dict("""
482
+User-Agent: Shortcut.lv v2.9.1 / Dalvik/1.6.0 (Linux; U; Android 4.4.2; SM-G900FD Build/KOT49H)
483
+Content-Type: application/x-www-form-urlencoded; charset=UTF-8
484
+Host: manstv.lattelecom.tv
485
+""" )
486
+    try:
487
+        r = urllib2.Request(url, data=params, headers=headers)
488
+        u = urllib2.urlopen(r)
489
+        content = u.read()
490
+        u.close()
491
+    except Exception as ex:
492
+        return None
493
+    if r and "token" in content:
494
+        token2 = re.search('"token":"(.+?)"', content).group(1)
495
+        return token2
496
+    else:
497
+        return False
498
+
499
+def print_headers(headers):
500
+    for h in headers:
501
+        print "%s: %s"%(h,headers[h])
502
+
503
+def del_headers(headers0,tags):
504
+    headers = headers0.copy()
505
+    for t in tags:
506
+        if t in headers:
507
+            del headers[t]
508
+        if t.lower() in headers:
509
+            del headers[t.lower()]
510
+    return headers
511
+
512
+def hls_split(url):
513
+    pp = urlparse.urlsplit(url)
514
+    path0 = pp.path[:pp.path.rindex("/")+1]
515
+    path0 = path0[path0.index("/", 1):]
516
+    rest = pp.path[pp.path.rindex("/")+1:] + "?" + pp.query
517
+    return path0, rest
518
+
519
+def hls_base(url):
520
+    base = url.split("?")[0]
521
+    base = "/".join(base.split("/")[0:-1])+ "/"
522
+    rest = url.replace(base, "")
523
+    return base, rest
524
+
525
+#########################################################################################
526
+if __name__ == '__main__':
527
+    # 1561839586
528
+    # get_epg("101", 1561839586)
529
+
530
+    try:
531
+        opts, args = getopt.gnu_getopt(sys.argv[1:], "p:s:dr", ["port=","server=","--debug"])
532
+    except getopt.GetoptError as err:
533
+        print str(err)
534
+        print str(__doc__)
535
+        sys.exit(2)
536
+    opts = dict(opts)
537
+
538
+    if not len(args):
539
+        print str(__doc__)
540
+        sys.exit(2)
541
+
542
+    if "-r" in opts:
543
+        print "Enabling remote debuging (ptvsd)"
544
+        import ptvsd
545
+        ptvsd.enable_attach(address = ('0.0.0.0', 5678),redirect_output=False)
546
+    if "-d" in opts:
547
+        print "Enabling debuging mode (more output)"
548
+        DEBUG = True
549
+    pid = "/var/run/ltcproxy.pid"
550
+    daemon = daemonize.Daemon(start, pid)
551
+    server =  opts["-s"] if "-s" in opts else SERVER
552
+    port = opts["-p"] if "-p" in opts else PORT_NUMBER
553
+
554
+    if "start" == args[0]:
555
+        s.clear()
556
+        daemon.start(server,port)
557
+        daemon.is_running()
558
+    elif "stop" == args[0]:
559
+        daemon.stop()
560
+    elif "restart" == args[0]:
561
+        s.clear()
562
+        daemon.restart()
563
+        daemon.is_running()
564
+    elif "manualstart" == args[0]:
565
+        s.clear()
566
+        start(server,port)
567
+    elif "status" == args[0]:
568
+        daemon.is_running()
569
+    else:
570
+        print "Unknown command"
571
+        print str(__doc__)
572
+        sys.exit(2)
573
+    sys.exit(0)