"""
Simple HTTP Live Streaming client.

References:
    http://tools.ietf.org/html/draft-pantos-http-live-streaming-08

This program is free software. It comes without any warranty, to
the extent permitted by applicable law. You can redistribute it
and/or modify it under the terms of the Do What The Fuck You Want
To Public License, Version 2, as published by Sam Hocevar. See
http://sam.zoy.org/wtfpl/COPYING for more details.

Last updated: July 22, 2012

Original Code From:
    http://nneonneo.blogspot.gr/2010/08/http-live-streaming-client.html

Depends on python-crypto (for secure stream)
Modified for OpenPli enigma2 usage by athoik
"""

import urlparse, urllib2, os , re
import sys, threading, time, Queue
import operator
from twisted.web.client import downloadPage, getPage, error

SUPPORTED_VERSION = 3
STREAM_PFILE      = '/tmp/shahid.avi'


class hlsclient(threading.Thread):
 
    def __init__(self):
        self._stop = False
        self.thread = None
        self._downLoading = False
        threading.Thread.__init__(self)

    def setUrl(self, url):
        self.url = url
        self._stop = False
        self.thread = None
        self._downLoading = False

    def isDownloading(self):
        return self._downLoading

    def run(self):
        self.play()

    def download_chunks(self, downloadUrl, chunk_size=4096):
        conn=urllib2.urlopen(downloadUrl)
        while 1:
            data=conn.read(chunk_size)
            if not data: return
            yield data

    def download_file(self, downloadUrl):
        print downloadUrl
        return ''.join(self.download_chunks(downloadUrl))

    def validate_m3u(self, conn):
        ''' make sure file is an m3u, and returns the encoding to use. '''
        mime = conn.headers.get('Content-Type', '').split(';')[0].lower()
        if mime == 'application/vnd.apple.mpegurl':
            enc = 'utf8'
        elif mime == 'audio/mpegurl':
            enc = 'iso-8859-1'
        elif conn.url.endswith('.m3u8'):
            enc = 'utf8'
        elif conn.url.endswith('.m3u'):
            enc = 'iso-8859-1'
        else:
            raise Exception('[hlsclient::validate_m3u] Stream MIME type or file extension not recognized')
        if conn.readline().rstrip('\r\n') != '#EXTM3U':
            raise Exception('[hlsclient::validate_m3u] Stream is not in M3U format')
        return enc

    def gen_m3u(self, url, skip_comments=True):
        request = urllib2.Request(url, headers={"Accept":"*/*","Accept-Encoding":"gzip, deflate, sdch","Accept-Language":"de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4","Connection":"keep-alive","Host":"195.154.250.112","Referer":"http://p2pcast.tech/stream.php?id=510035&osr=2&p2p=0&stretching=uniform","User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.75 Safari/537.36","X-Requested-With":"ShockwaveFlash/20.0.0.306"})
        conn = urllib2.urlopen(request)
        print conn
        #conn = getPage(url, timeout=8, method='GET', headers={"Accept":"*/*","Accept-Encoding":"gzip, deflate, sdch","Accept-Language":"de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4","Connection":"keep-alive","Host":"195.154.242.49","Referer":"http://p2pcast.tech/stream.php?id=510035&osr=2&p2p=0&stretching=uniform","User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.75 Safari/537.36","X-Requested-With":"ShockwaveFlash/20.0.0.306"})
        #enc = self.validate_m3u(conn)
        enc='utf8'
        for line in conn:
            line = line.rstrip('\r\n').decode(enc)
            if not line:
                # blank line
                continue
            elif line.startswith('#EXTM3U'):
                continue
            elif line.startswith('#EXT'):
                # tag
                yield line
            elif line.startswith('#'):
                # comment
                if skip_comments:
                    continue
                else:
                    yield line
            else:
                # media file
                yield line
 
    def parse_m3u_tag(self, line):
        if ':' not in line:
            return line, []
        tag, attribstr = line.split(':', 1)
        attribs = []
        last = 0
        quote = False
        for i,c in enumerate(attribstr+','):
            if c == '"':
                quote = not quote
            if quote:
                continue
            if c == ',':
                attribs.append(attribstr[last:i])
                last = i+1
        return tag, attribs

    def parse_kv(self, attribs, known_keys=None):
        d = {}
        for item in attribs:
            k, v = item.split('=', 1)
            k=k.strip()
            v=v.strip().strip('"')
            if known_keys is not None and k not in known_keys:
                raise ValueError('[hlsclient::parse_kv] unknown attribute %s' % k)
            d[k] = v
        return d

    def handle_basic_m3u(self, hlsUrl):
#http://l3md.shahid.net/media/l3/2fda1d3fd7ab453cad983544e8ed70e4/3be7afa11f5a4037bb0b1163d378c444/4df816395413420488af72856246b027/kormebra_s01_e27.mpegts/playlist-f01309a9eaa9e824e1b65430ddecf7f592cdfd76.m3u8
#line = re.sub('foo','bar', line.rstrip())
        base_key_url = 	re.sub('playlist-.*?.m3u8','',hlsUrl)
        seq = 1
        enc = None
        nextlen = 5
        duration = 5
        for line in self.gen_m3u(hlsUrl):
            if line.startswith('#EXT'):
                tag, attribs = self.parse_m3u_tag(line)
                if tag == '#EXTINF':
                    duration = float(attribs[0])
                elif tag == '#EXT-X-TARGETDURATION':
                    assert len(attribs) == 1, '[hlsclient::handle_basic_m3u] too many attribs in EXT-X-TARGETDURATION'
                    targetduration = int(attribs[0])
                    pass
                elif tag == '#EXT-X-MEDIA-SEQUENCE':
                    assert len(attribs) == 1, '[hlsclient::handle_basic_m3u] too many attribs in EXT-X-MEDIA-SEQUENCE'
                    seq = int(attribs[0])
                elif tag == '#EXT-X-KEY':
                    attribs = self.parse_kv(attribs, ('METHOD', 'URI', 'IV'))
                    assert 'METHOD' in attribs, '[hlsclient::handle_basic_m3u] expected METHOD in EXT-X-KEY'
                    if attribs['METHOD'] == 'NONE':
                        assert 'URI' not in attribs, '[hlsclient::handle_basic_m3u] EXT-X-KEY: METHOD=NONE, but URI found'
                        assert 'IV' not in attribs, '[hlsclient::handle_basic_m3u] EXT-X-KEY: METHOD=NONE, but IV found'
                        enc = None
                    elif attribs['METHOD'] == 'AES-128':
                        from Crypto.Cipher import AES
                        assert 'URI' in attribs, '[hlsclient::handle_basic_m3u] EXT-X-KEY: METHOD=AES-128, but no URI found'
                        if 'https://' in attribs['URI']:
                            key = self.download_file(attribs['URI'].strip('"')) #key = self.download_file(base_key_url+attribs['URI'].strip('"'))
                            print(attribs['URI'].strip('"')) 
                        else:
                            key = self.download_file(base_key_url+attribs['URI'].strip('"'))
                            print(base_key_url+attribs['URI'].strip('"'))
                        assert len(key) == 16, '[hlsclient::handle_basic_m3u] EXT-X-KEY: downloaded key file has bad length'
                        if 'IV' in attribs:
                            assert attribs['IV'].lower().startswith('0x'), '[hlsclient::handle_basic_m3u] EXT-X-KEY: IV attribute has bad format'
                            iv = attribs['IV'][2:].zfill(32).decode('hex')
                            assert len(iv) == 16, '[hlsclient::handle_basic_m3u] EXT-X-KEY: IV attribute has bad length'
                        else:
                            iv = '\0'*8 + struct.pack('>Q', seq)
                        enc = AES.new(key, AES.MODE_CBC, iv)
                        print 'huch we print: %s' %enc
                    else:
                        assert False, '[hlsclient::handle_basic_m3u] EXT-X-KEY: METHOD=%s unknown' % attribs['METHOD']
                elif tag == '#EXT-X-PROGRAM-DATE-TIME':
                    assert len(attribs) == 1, '[hlsclient::handle_basic_m3u] 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('[hlsclient::handle_basic_m3u] dont know how to handle EXT-X-STREAM-INF in basic playlist')
                elif tag == '#EXT-X-DISCONTINUITY':
                    assert not attribs
                    print '[hlsclient::handle_basic_m3u] discontinuity in stream'
                elif tag == '#EXT-X-VERSION':
                    assert len(attribs) == 1
                    if int(attribs[0]) > SUPPORTED_VERSION:
                        print '[hlsclient::handle_basic_m3u] file version %s exceeds supported version %d; some things might be broken' % (attribs[0], SUPPORTED_VERSION)
                else:
                    raise ValueError('[hlsclient::handle_basic_m3u] tag %s not known' % tag)
            else:
                yield (seq, enc, duration, targetduration, line)
                seq += 1

    def player_pipe(self, queue, videopipe):
        while not self._stop:
            block = queue.get(block=True)
            if block is None: return
            videopipe.write(block)
            #videopipe.flush()
            if not self._downLoading:
                print 'Connected...'
                self._downLoading = True

    def play(self):
        #check if pipe exists
        if os.access(STREAM_PFILE, os.W_OK):
            os.remove(STREAM_PFILE)

        os.mkfifo(STREAM_PFILE)
        videopipe = open(STREAM_PFILE, "wb")
        variants = []
        variant = None
        for line in self.gen_m3u(self.url):
            if line.startswith('#EXT'):
                tag, attribs = self.parse_m3u_tag(line)
                if tag == '#EXT-X-STREAM-INF':
                    variant = attribs
            elif variant:
                variants.append((line, variant))
                variant = None
        if len(variants) == 1:
            self.url = urlparse.urljoin(self.url, variants[0][0])
        elif len(variants) >= 2:
            print '[hlsclient::play] More than one variant of the stream was provided.'
            autoChoice = {}
            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':
                        #Limit bandwidth?
                        #if int(value) < 1000000:
                        #    autoChoice[i] = int(value)
                        autoChoice[i] = int(value)
                        print 'bitrate %.2f kbps' % (int(value)/1024.0),
                    elif key == 'PROGRAM-ID':
                        print 'program %s' % value,
                    elif key == 'CODECS':
                        print 'codec %s' % value,
                    elif key == 'RESOLUTION':
                        print 'resolution %s' % value,
                    else:
                        raise ValueError('[hlsclient::play] unknown STREAM-INF attribute %s' % key)
                print
            choice = max(autoChoice.iteritems(), key=operator.itemgetter(1))[0]
            print '[hlsclient::play] Autoselecting %s' % choice
            #Use the first choice for testing
            #choice = 0
            self.url = urlparse.urljoin(self.url, variants[choice][0])

        queue = Queue.Queue(1024) # 1024 blocks of 4K each ~ 4MB buffer
        self.thread = threading.Thread(target=self.player_pipe, args=(queue, videopipe))
        self.thread.start()
        last_seq = -1
        targetduration = 5
        changed = 0
        try:
            while self.thread.isAlive():
                if self._stop:
                    print '[hlsclient::play] Stopping Download Thread'
                    self.hread._Thread__stop()
                medialist = list(self.handle_basic_m3u(self.url))
                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[-3:]
                for media in medialist:
                    if media is None:
                        queue.put(None, block=True)
                        return
                    seq, enc, duration, targetduration, media_url = media
                    if seq > last_seq:
                        for chunk in self.download_chunks(urlparse.urljoin(self.url, media_url)):
                            if enc: chunk = enc.decrypt(chunk)
                            queue.put(chunk, block=True)
                        last_seq = seq
                        changed = 1
                self._sleeping = True
                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)
                self._sleeping = False
                changed -= 1
        except Exception as ex:
            print '[hlsclient::play] Exception %s; Stopping threads' % ex 
            self._stop = True
            self_downLoading = False
            self.thread._Thread__stop()
            self._Thread__stop()

    def stop(self):
        self._stop = True
        self._downLoading = False
        if self.thread:
            self.thread._Thread__stop()
        print '[hlsclient::stop] Stopping Main hlsclient thread'
        self._Thread__stop()



if __name__ == '__main__':
    print sys.argv
    if len(sys.argv) <> 3:
        print "usage", sys.argv[0], "<stream URL> L%s" % len(sys.argv)
        sys.exit(1)
    else:
        try:
            h = hlsclient()
            h.setUrl(sys.argv[1])
            print (sys.argv[2])
            if (sys.argv[2]) == '1':
                h.start()
                while 1:
                    time.sleep(10)
            else:
                h.stop()
        except:
            if h:
                h.stop()

