def getDecrypter(key, iv): global USEDec if USEDec == 1: enc = AES.new(key, AES.MODE_CBC, iv) elif USEDec == 3: ivb = array.array('B', iv) keyb = array.array('B', key) enc = python_aes.new(keyb, 2, ivb) else: enc = androidsslPy._load_crypto_libcrypto() enc = enc(key, iv) return enc
def getDecrypter(key,iv): global USEDec if USEDec==1: enc =AES.new(key, AES.MODE_CBC, iv) elif USEDec==3: ivb=array.array('B',iv) keyb= array.array('B',key) enc=python_aes.new(keyb, 2, ivb) else: enc =androidsslPy._load_crypto_libcrypto() enc = enc(key, iv) return enc
def handle_basic_m3u(url): global iv global key global USEDec seq = 1 enc = None nextlen = 5 duration = 5 targetduration=5 for line in gen_m3u(url): if line.startswith('#EXT'): tag, attribs = parse_m3u_tag(line) if tag == '#EXTINF': duration = float(attribs[0]) elif tag == '#EXT-X-TARGETDURATION': assert len(attribs) == 1, "too many attribs in EXT-X-TARGETDURATION" targetduration = int(attribs[0]) pass elif tag == '#EXT-X-MEDIA-SEQUENCE': assert len(attribs) == 1, "too many attribs in EXT-X-MEDIA-SEQUENCE" seq = int(attribs[0]) elif tag == '#EXT-X-KEY': attribs = parse_kv(attribs, ('METHOD', 'URI', 'IV')) assert 'METHOD' in attribs, 'expected METHOD in EXT-X-KEY' if attribs['METHOD'] == 'NONE': assert 'URI' not in attribs, 'EXT-X-KEY: METHOD=NONE, but URI found' assert 'IV' not in attribs, 'EXT-X-KEY: METHOD=NONE, but IV found' enc = None elif attribs['METHOD'] == 'AES-128': assert 'URI' in attribs, 'EXT-X-KEY: METHOD=AES-128, but no URI found' #from Crypto.Cipher import AES key = download_file(attribs['URI'].strip('"')) assert len(key) == 16, 'EXT-X-KEY: downloaded key file has bad length' if 'IV' in attribs: assert attribs['IV'].lower().startswith('0x'), 'EXT-X-KEY: IV attribute has bad format' iv = attribs['IV'][2:].zfill(32).decode('hex') assert len(iv) == 16, 'EXT-X-KEY: IV attribute has bad length' else: iv = '\0'*8 + struct.pack('>Q', seq) if not USEDec==3: enc = AES.new(key, AES.MODE_CBC, iv) else: ivb=array.array('B',iv) keyb= array.array('B',key) enc=python_aes.new(keyb, 2, ivb) #enc = AES_CBC(key) #print key #print iv #enc=AESDecrypter.new(key, 2, iv) else: assert False, 'EXT-X-KEY: METHOD=%s unknown'%attribs['METHOD'] elif tag == '#EXT-X-PROGRAM-DATE-TIME': assert len(attribs) == 1, "too many attribs in EXT-X-PROGRAM-DATE-TIME" # TODO parse attribs[0] as ISO8601 date/time pass elif tag == '#EXT-X-ALLOW-CACHE': # XXX deliberately ignore pass elif tag == '#EXT-X-ENDLIST': assert not attribs yield None return elif tag == '#EXT-X-STREAM-INF': raise ValueError("don't know how to handle EXT-X-STREAM-INF in basic playlist") elif tag == '#EXT-X-DISCONTINUITY': assert not attribs print "[warn] discontinuity in stream" elif tag == '#EXT-X-VERSION': assert len(attribs) == 1 if int(attribs[0]) > SUPPORTED_VERSION: print "[warn] file version %s exceeds supported version %d; some things might be broken"%(attribs[0], SUPPORTED_VERSION) #else: # raise ValueError("tag %s not known"%tag) else: yield (seq, enc, duration, targetduration, line) seq += 1
def downloadInternal(url, file, maxbitrate=0, stopEvent=None, testing=False): global key global iv global USEDec global callbackDRM if stopEvent and stopEvent.isSet(): return dumpfile = None #dumpfile=open('c:\\temp\\myfile.mp4',"wb") variants = [] variant = None #url check if requires redirect redirurl = url try: print 'going gor ', url res = getUrl(url, returnres=True) print 'here ', res if res.history: print 'history' redirurl = res.url res.close() if testing: return True except: traceback.print_exc() print 'redirurl', redirurl for line in gen_m3u(url): if line.startswith('#EXT'): tag, attribs = parse_m3u_tag(line) if tag == '#EXT-X-STREAM-INF': variant = attribs elif variant: variants.append((line, variant)) variant = None print 'variants', variants if len(variants) == 0: url = redirurl if len(variants) == 1: url = urlparse.urljoin(redirurl, variants[0][0]) elif len(variants) >= 2: print "More than one variant of the stream was provided." choice = -1 lastbitrate = 0 print 'maxbitrate', maxbitrate for i, (vurl, vattrs) in enumerate(variants): print i, vurl, for attr in vattrs: key, value = attr.split('=') key = key.strip() value = value.strip().strip('"') if key == 'BANDWIDTH': print 'bitrate %.2f kbps' % (int(value) / 1024.0) if int(value) <= int(maxbitrate) and int( value) > lastbitrate: choice = i lastbitrate = int(value) elif key == 'PROGRAM-ID': print 'program %s' % value, elif key == 'CODECS': print 'codec %s' % value, elif key == 'RESOLUTION': print 'resolution %s' % value, else: print "unknown STREAM-INF attribute %s" % key #raise ValueError("unknown STREAM-INF attribute %s"%key) print if choice == -1: choice = 0 #choice = int(raw_input("Selection? ")) print 'choose %d' % choice url = urlparse.urljoin(redirurl, variants[choice][0]) #queue = Queue.Queue(1024) # 1024 blocks of 4K each ~ 4MB buffer control = ['go'] #thread = threading.Thread(target=player_pipe, args=(queue, control,file)) #thread.start() last_seq = -1 targetduration = 5 changed = 0 glsession = None #if ':7777' in url: # try: # glsession=re.compile(':7777\/.*?m3u8.*?session=(.*?)&').findall(url)[0] # except: # pass try: while 1 == 1: #thread.isAlive(): if stopEvent and stopEvent.isSet(): return medialist = list(handle_basic_m3u(url)) if testing: if len(medialist) == 0: raise Exception('empty m3u8') return True playedSomething = False if medialist == None: return False if None in medialist: # choose to start playback at the start, since this is a VOD stream pass else: # choose to start playback three files from the end, since this is a live stream medialist = medialist[0:] #print 'medialist',medialist addsomewait = False lastKeyUrl = "" lastkey = None for media in medialist: if stopEvent and stopEvent.isSet(): return if media is None: #queue.put(None, block=True) return seq, encobj, duration, targetduration, media_url, vod = media addsomewait = True if seq > last_seq: #print 'downloading.............',url enc = None if encobj: codeurl, iv = encobj if codeurl <> lastKeyUrl: if codeurl.startswith('http'): key = download_file(codeurl) elif codeurl.startswith('LSDRMCallBack$'): key = callbackDRM.DRMCallback( codeurl.split('LSDRMCallBack$')[1], url) else: key = codeurl codeurl = lastKeyUrl else: key = lastkey lastkey = key if not USEDec == 3: enc = AES.new(key, AES.MODE_CBC, iv) else: ivb = array.array('B', iv) keyb = array.array('B', key) enc = python_aes.new(keyb, 2, ivb) #enc=AESDecrypter.new(key, 2, iv) if glsession: media_url = media_url.replace( glsession, glsession[:-10] + ''.join( random.choice(string.ascii_uppercase + string.digits) for _ in range(10))) try: for chunk in download_chunks(urlparse.urljoin( url, media_url), enc=encobj): if stopEvent and stopEvent.isSet(): return #print '1. chunk available %d'%len(chunk) if enc: if not USEDec == 3: chunk = enc.decrypt(chunk) else: chunkb = array.array('B', chunk) chunk = enc.decrypt(chunkb) chunk = "".join(map(chr, chunk)) #if enc: chunk = enc.decrypt(chunk,key,'CBC') #print '2. chunk done %d'%len(chunk) if dumpfile: dumpfile.write(chunk) #queue.put(chunk, block=True) send_back(chunk, file) #print '3. chunk available %d'%len(chunk) last_seq = seq changed = 1 playedSomething = True except: pass '''if changed == 1: # initial minimum reload delay time.sleep(duration) elif changed == 0: # first attempt time.sleep(targetduration*0.5) elif changed == -1: # second attempt time.sleep(targetduration*1.5) else: # third attempt and beyond time.sleep(targetduration*3.0) changed -= 1 ''' return if not playedSomething: xbmc.sleep(2000 + (3000 if addsomewait else 0)) except: control[0] = 'stop' raise
def downloadInternal(url,file,maxbitrate=0,stopEvent=None): global key global iv global USEDec if stopEvent and stopEvent.isSet(): return dumpfile = None #dumpfile=open('c:\\temp\\myfile.mp4',"wb") variants = [] variant = None #url check if requires redirect redirurl=url try: print 'going gor ',url res=getUrl(url,returnres=True ) print 'here ', res if res.history: print 'history' redirurl=res.url res.close() except: traceback.print_exc() print 'redirurl',redirurl for line in gen_m3u(url): if line.startswith('#EXT'): tag, attribs = parse_m3u_tag(line) if tag == '#EXT-X-STREAM-INF': variant = attribs elif variant: variants.append((line, variant)) variant = None print 'variants',variants if len(variants)==0: url=redirurl if len(variants) == 1: url = urlparse.urljoin(redirurl, variants[0][0]) elif len(variants) >= 2: print "More than one variant of the stream was provided." choice=-1 lastbitrate=0 print 'maxbitrate',maxbitrate for i, (vurl, vattrs) in enumerate(variants): print i, vurl, for attr in vattrs: key, value = attr.split('=') key = key.strip() value = value.strip().strip('"') if key == 'BANDWIDTH': print 'bitrate %.2f kbps'%(int(value)/1024.0) if int(value)<=int(maxbitrate) and int(value)>lastbitrate: choice=i lastbitrate=int(value) elif key == 'PROGRAM-ID': print 'program %s'%value, elif key == 'CODECS': print 'codec %s'%value, elif key == 'RESOLUTION': print 'resolution %s'%value, else: print "unknown STREAM-INF attribute %s"%key #raise ValueError("unknown STREAM-INF attribute %s"%key) print if choice==-1: choice=0 #choice = int(raw_input("Selection? ")) print 'choose %d'%choice url = urlparse.urljoin(redirurl, variants[choice][0]) #queue = Queue.Queue(1024) # 1024 blocks of 4K each ~ 4MB buffer control = ['go'] #thread = threading.Thread(target=player_pipe, args=(queue, control,file)) #thread.start() last_seq = -1 targetduration = 5 changed = 0 glsession=None if ':7777' in url: try: glsession=re.compile(':7777\/.*?m3u8.*?session=(.*?)&').findall(url)[0] except: pass try: while 1==1:#thread.isAlive(): if stopEvent and stopEvent.isSet(): return medialist = list(handle_basic_m3u(url)) playedSomething=False if medialist==None: return if None in medialist: # choose to start playback at the start, since this is a VOD stream pass else: # choose to start playback three files from the end, since this is a live stream medialist = medialist[-6:] #print 'medialist',medialist addsomewait=False for media in medialist: if stopEvent and stopEvent.isSet(): return if media is None: #queue.put(None, block=True) return seq, encobj, duration, targetduration, media_url = media addsomewait=True if seq > last_seq: #print 'downloading.............',url enc=None if encobj: codeurl,iv=encobj key = download_file(codeurl) if not USEDec==3: enc = AES.new(key, AES.MODE_CBC, iv) else: ivb=array.array('B',iv) keyb= array.array('B',key) enc=python_aes.new(keyb, 2, ivb) #enc=AESDecrypter.new(key, 2, iv) if glsession: media_url=media_url.replace(glsession,glsession[:-10]+''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(10))) try: for chunk in download_chunks(urlparse.urljoin(url, media_url),enc=encobj): if stopEvent and stopEvent.isSet(): return #print '1. chunk available %d'%len(chunk) if enc: if not USEDec==3: chunk = enc.decrypt(chunk) else: chunkb=array.array('B',chunk) chunk = enc.decrypt(chunkb) chunk="".join(map(chr, chunk)) #if enc: chunk = enc.decrypt(chunk,key,'CBC') #print '2. chunk done %d'%len(chunk) if dumpfile: dumpfile.write(chunk) #queue.put(chunk, block=True) send_back(chunk,file) #print '3. chunk available %d'%len(chunk) last_seq = seq changed = 1 playedSomething=True except: pass '''if changed == 1: # initial minimum reload delay time.sleep(duration) elif changed == 0: # first attempt time.sleep(targetduration*0.5) elif changed == -1: # second attempt time.sleep(targetduration*1.5) else: # third attempt and beyond time.sleep(targetduration*3.0) changed -= 1 ''' if not playedSomething: xbmc.sleep(2000+ (3000 if addsomewait else 0)) except: control[0] = 'stop' raise
def handle_basic_m3u(url): global iv global key global USEDec seq = 1 enc = None nextlen = 5 duration = 5 targetduration = 5 for line in gen_m3u(url): if line.startswith("#EXT"): tag, attribs = parse_m3u_tag(line) if tag == "#EXTINF": duration = float(attribs[0]) elif tag == "#EXT-X-TARGETDURATION": assert len(attribs) == 1, "too many attribs in EXT-X-TARGETDURATION" targetduration = int(attribs[0]) pass elif tag == "#EXT-X-MEDIA-SEQUENCE": assert len(attribs) == 1, "too many attribs in EXT-X-MEDIA-SEQUENCE" seq = int(attribs[0]) elif tag == "#EXT-X-KEY": attribs = parse_kv(attribs, ("METHOD", "URI", "IV")) assert "METHOD" in attribs, "expected METHOD in EXT-X-KEY" if attribs["METHOD"] == "NONE": assert "URI" not in attribs, "EXT-X-KEY: METHOD=NONE, but URI found" assert "IV" not in attribs, "EXT-X-KEY: METHOD=NONE, but IV found" enc = None elif attribs["METHOD"] == "AES-128": assert "URI" in attribs, "EXT-X-KEY: METHOD=AES-128, but no URI found" # from Crypto.Cipher import AES key = download_file(attribs["URI"].strip('"')) assert len(key) == 16, "EXT-X-KEY: downloaded key file has bad length" if "IV" in attribs: assert attribs["IV"].lower().startswith("0x"), "EXT-X-KEY: IV attribute has bad format" iv = attribs["IV"][2:].zfill(32).decode("hex") assert len(iv) == 16, "EXT-X-KEY: IV attribute has bad length" else: iv = "\0" * 8 + struct.pack(">Q", seq) if not USEDec == 3: enc = AES.new(key, AES.MODE_CBC, iv) else: ivb = array.array("B", iv) keyb = array.array("B", key) enc = python_aes.new(keyb, 2, ivb) # enc = AES_CBC(key) # print key # print iv # enc=AESDecrypter.new(key, 2, iv) else: assert False, "EXT-X-KEY: METHOD=%s unknown" % attribs["METHOD"] elif tag == "#EXT-X-PROGRAM-DATE-TIME": assert len(attribs) == 1, "too many attribs in EXT-X-PROGRAM-DATE-TIME" # TODO parse attribs[0] as ISO8601 date/time pass elif tag == "#EXT-X-ALLOW-CACHE": # XXX deliberately ignore pass elif tag == "#EXT-X-ENDLIST": assert not attribs yield None return elif tag == "#EXT-X-STREAM-INF": raise ValueError("don't know how to handle EXT-X-STREAM-INF in basic playlist") elif tag == "#EXT-X-DISCONTINUITY": assert not attribs print "[warn] discontinuity in stream" elif tag == "#EXT-X-VERSION": assert len(attribs) == 1 if int(attribs[0]) > SUPPORTED_VERSION: print "[warn] file version %s exceeds supported version %d; some things might be broken" % ( attribs[0], SUPPORTED_VERSION, ) # else: # raise ValueError("tag %s not known"%tag) else: yield (seq, enc, duration, targetduration, line) seq += 1
def downloadInternal(url,file,maxbitrate=0,stopEvent=None , callbackpath="",callbackparam="", testing=False): global key global iv global USEDec global cookieJar global clientHeader global nsplayer global callbackDRM if stopEvent and stopEvent.isSet(): return False dumpfile = None #dumpfile=open('c:\\temp\\myfile.mp4',"wb") variants = [] variant = None veryfirst=True #url check if requires redirect redirurl=url utltext='' try: print 'going for url ',url res=getUrl(url,returnres=True ) print 'here ', res if res.history: print 'history is',res.history redirurl=res.url url=redirurl utltext=res.text res.close() if testing: return True except: traceback.print_exc() print 'redirurl',redirurl if 'EXT-X-STREAM-INF' in utltext: try: for line in gen_m3u(redirurl): if line.startswith('#EXT'): tag, attribs = parse_m3u_tag(line) if tag == '#EXT-X-STREAM-INF': variant = attribs elif variant: variants.append((line, variant)) variant = None print 'variants',variants if len(variants)==0: url=redirurl if len(variants) == 1: url = urlparse.urljoin(redirurl, variants[0][0]) elif len(variants) >= 2: print "More than one variant of the stream was provided." choice=-1 lastbitrate=0 print 'maxbitrate',maxbitrate for i, (vurl, vattrs) in enumerate(variants): print i, vurl, for attr in vattrs: key, value = attr.split('=') key = key.strip() value = value.strip().strip('"') if key == 'BANDWIDTH': print 'bitrate %.2f kbps'%(int(value)/1024.0) if int(value)<=int(maxbitrate) and int(value)>lastbitrate: choice=i lastbitrate=int(value) elif key == 'PROGRAM-ID': print 'program %s'%value, elif key == 'CODECS': print 'codec %s'%value, elif key == 'RESOLUTION': print 'resolution %s'%value, else: print "unknown STREAM-INF attribute %s"%key #raise ValueError("unknown STREAM-INF attribute %s"%key) print if choice==-1: choice=0 #choice = int(raw_input("Selection? ")) print 'choose %d'%choice url = urlparse.urljoin(redirurl, variants[choice][0]) except: raise print 'final url',url last_seq = -1 targetduration = 5 changed = 0 fails=0 maxfails=5 nsplayer=False print 'inside HLS RETRY' try: #file.write(b'FLV\x01') #file.write(b'\x01') #file.write(b'\x00\x00\x00\x09') # FLV File body #file.write(b'\x00\x00\x00\x09') while 1==1:#thread.isAlive(): reconnect=False vod=False if fails>maxfails: #stopEvent.set() break if stopEvent and stopEvent.isSet(): return False try: medialist = list(handle_basic_m3u(url)) if len(medialist)==0: raise Exception('empty m3u8') print medialist if testing: return True except Exception as inst: print 'here in exp',inst print fails fails+=1 if testing and fails>6: return False if testing==False and '403' in repr(inst).lower() and callbackpath and len(callbackpath)>0: print 'callback' import importlib, os foldername=os.path.sep.join(callbackpath.split(os.path.sep)[:-1]) urlnew='' if foldername not in sys.path: sys.path.append(foldername) try: callbackfilename= callbackpath.split(os.path.sep)[-1].split('.')[0] callbackmodule = importlib.import_module(callbackfilename) urlnew,cjnew=callbackmodule.f4mcallback(callbackparam, 1, inst, cookieJar , url, clientHeader) except: traceback.print_exc() if urlnew and len(urlnew)>0 and urlnew.startswith('http'): print 'got new url',url url=urlnew cookieJar= cjnew continue else: return if '403' in repr(inst).lower() or '401' in repr(inst).lower(): if fails in [1,4,5,10,15,19]: nsplayer=True else: nsplayer=False print 'nsplayer',nsplayer xbmc.sleep(1000) continue nsplayer=False playedSomething=False if medialist==None: return ## choose to start playback three files from the end, since this is a live stream #medialist = medialist[-6:] #print 'medialist',medialist addsomewait=False lastKeyUrl="" lastkey=None playedduration=0 st=time.time() for media in medialist: if stopEvent and stopEvent.isSet(): return False if media is None: #send_back('G'+chr(254)+chr(255)+('\0'*1), file) #queue.put(None, block=True) if stopEvent: print 'set events' stopEvent.set() return False seq, encobj, duration, targetduration, media_url,vod = media if seq > last_seq: #print 'downloading.............',url enc=None if encobj: codeurl,iv=encobj if codeurl<>lastKeyUrl: if codeurl.startswith('http'): key = download_file(codeurl) elif codeurl.startswith('LSDRMCallBack$'): key=callbackDRM.DRMCallback(codeurl.split('LSDRMCallBack$')[1],url) else: key = codeurl codeurl=lastKeyUrl else: key=lastkey lastkey=key if not USEDec==3: enc = AES.new(key, AES.MODE_CBC, iv) else: ivb=array.array('B',iv) keyb= array.array('B',key) enc=python_aes.new(keyb, 2, ivb) #enc=AESDecrypter.new(key, 2, iv) try: data=None try: print 'downloading', urlparse.urljoin(url, media_url) #for chunk in download_chunks(urlparse.urljoin(url, media_url)): for chunk in download_chunks(media_url): if stopEvent and stopEvent.isSet(): return False print 'sending chunk', len(chunk) if enc: if not USEDec==3: chunk = enc.decrypt(chunk) else: chunkb=array.array('B',chunk) chunk = enc.decrypt(chunkb) chunk="".join(map(chr, chunk)) send_back(chunk,file) data="send" playedduration+=duration addsomewait=True except Exception as inst: print 'xxxx',repr(inst) if 'forcibly closed' in repr(inst): print 'returning' return False if stopEvent and stopEvent.isSet(): return False if data and len(data)>0:# chunk in download_chunks(urlparse.urljoin(url, media_url),enc=encobj): #if not veryfirst: # if dumpfile: dumpfile.write(chunk) # #queue.put(chunk, block=True) # send_back(data,file) # #print '3. chunk available %d'%len(chunk) veryfirst=False last_seq = seq changed = 1 playedSomething=True fails=0 maxfails=20 else: reconnect=True fails+=1 break except: pass if vod: return True if playedSomething == 1: # initial minimum reload delay timetowait=int(targetduration - (time.time()-st))# if (timetowait)>0: print 'sleeping because targetduration',timetowait for t in range(0,timetowait): xbmc.sleep(1000) print 'sleeep for 1sec',t if stopEvent and stopEvent.isSet(): return False '''elif changed == 0: # first attempt time.sleep(targetduration*0.5) elif changed == -1: # second attempt time.sleep(targetduration*1.5) else: # third attempt and beyond time.sleep(targetduration*3.0) changed -= 1 ''' if not playedSomething: xbmc.sleep(3000+ (3000 if addsomewait else 0)) except: raise