import urllib
import os
import sys
import subprocess
import re
import string

FFMPEG="/Users/jack/packages/ffmpeg-svn-current/ffmpeg"

EXT_TO_TYPE = {
    "ogg" : "ogg",
    "ogv" : "ogg",
    "oga" : "ogg",
    "mp4" : "mp4",
    "m4v" : "mp4"
}

class MFPostProc(urllib.addclosehook):
    
    def __init__(self, fp):
        urllib.addclosehook.__init__(self, fp, self.closehook)
        self.cached = False
        self.local_filename = None
        self.suffix = None
        self.range_info = None
        self.ffmpeg_args = []
        self._init_range_info()
        
    def closehook(self):
        if self.cached and self.local_filename:
            os.remove(self.local_filename)
        self.cached = False
        self.local_filename = None
        
    def mf_range_info(self):
        return self.range_info
        
    def _init_range_info(self):
        self.range_info = {}
        for h in self.fp.headers:
            lower_h = h.lower()
            if h[:15] == 'content-range: ':
                cr = h[15:]
                space_pos = cr.find(' ')
                rinfo_name = cr[:space_pos].strip()
                rinfo_value = cr[space_pos:].strip()
                self.range_info[rinfo_name] = rinfo_value
        
    def ensure_content_ranges(self, ranges):
        for r in ranges:
            if not r in self.range_info:
                break
        else:
            return
        # We miss some information. We need to retrieve it.
        self._cache_content()
        
        cmd = [
            FFMPEG,
            "-an",
            "-vn",
            "-i",
            self.local_filename,
            "-"
        ]
        null = open("/dev/null", "wb")
        proc = subprocess.Popen(cmd, stdout=null, stderr=subprocess.PIPE)
        data = proc.communicate()[1]
        
        self._parse_ffmpeg_output(data)
        
    PAT_TIME=re.compile(r"\sDuration: (?P<hh>\d+):(?P<mm>\d+):(?P<ss>\d+.\d+),\sstart:\s(?P<begin>\d+.\d+),\s", re.MULTILINE) 
    PAT_WH=re.compile(r"\sStream.*:\sVideo:[^,]+,[^,]+,\s(?P<w>\d+)x(?P<h>\d+),", re.MULTILINE)
    
    def _parse_ffmpeg_output(self, data):
        match = self.PAT_TIME.search(data)
        if match:
            hh = string.atoi(match.group('hh'))
            mm = string.atoi(match.group('mm'))
            ss = string.atof(match.group('ss'))
            end_ss = ss + 60*(mm + 60*hh)
            begin_ss = string.atof(match.group('begin'))
            self._add_content_range('seconds', '%f-%f' % (begin_ss, end_ss))
        
        match = self.PAT_WH.search(data)
        if match:
            w = string.atoi(match.group('w'))
            h = string.atoi(match.group('h'))
            self._add_content_range('x_jack_xywh', '(0, 0, %d, %d)' % (w, h))
            
    def _add_content_range(self, name, value):
        print >>sys.stderr, 'FFMPEG: add content-range', name, value
        if name in self.range_info:
            print >>sys.stderr, 'FFMPEG: WARNING: Replacing', name, self.range_info[name]
        self.range_info[name] = value
        
    def _cache_content(self):
        """Retrieve self.fp to local file."""
        if self.local_filename:
            return
        import tempfile
        tp, path = urllib.splittype(self.fp.geturl())
        host, path = urllib.splithost(path or "")
        path, garbage = urllib.splitquery(path or "")
        path, garbage = urllib.splitattr(path or "")
        path, tag = urllib.splittag(path or "")
        if tp == 'file' and (host == '' or host.lower() == 'localhost'):
            # The file is alread local. Don't crete a copy.
            self.local_filename = urllib.url2pathname(path)
            return
        # Otherwise we need to cache a copy.
        self.suffix = os.path.splitext(path)[1]
        (fd, self.local_filename) = tempfile.mkstemp(suffix)
        tfp = os.fdopen(fd, 'wb')
        while 1:
            data = self.fp.read(64*1024)
            if not data: break
            tfp.write(data)
        self.fp.close()
        assert 0 # TBD
        
    def select_xywh(self, x, y, w, h):
        print >>sys.stderr, "DEBUG: xywh=(%d, %d, %d, %d)" % (x, y, w, h)
        if not 'x_jack_xywh' in self.range_info:
            print >>sys.stderr, "WARNING: Media Fragments: ffmpeg postprocess: Cannot do spatial selection, no original xywh"
            return
        xywh_info = self.range_info['x_jack_xywh']
        orig_x, orig_y, orig_w, orig_h = eval(xywh_info)
        # convert to ltrb deltas. Positive deltas means: pixels need to be removed.
        delta_l = x - orig_x
        delta_t = y - orig_y
        delta_r = (orig_x+orig_w) - (x+w)
        delta_b = (orig_y+orig_h) - (y+h)
        if delta_l > 0:
            self.ffmpeg_args += [
                "-cropleft",
                str(delta_l)
            ]
        elif delta_l < 0:
            self.ffmpeg_args += [
                "-padleft",
                str(-delta_l)
            ]
        if delta_t > 0:
            self.ffmpeg_args += [
                "-croptop",
                str(delta_t)
            ]
        elif delta_t < 0:
            self.ffmpeg_args += [
                "-padtop",
                str(-delta_t)
            ]
        if delta_r > 0:
            self.ffmpeg_args += [
                "-cropright",
                str(delta_r)
            ]
        elif delta_r < 0:
            self.ffmpeg_args += [
                "-padright",
                str(-delta_r)
            ]
        if delta_b > 0:
            self.ffmpeg_args += [
                "-cropbottom",
                str(delta_b)
            ]
        elif delta_b < 0:
            self.ffmpeg_args += [
                "-padbottom",
                str(-delta_b)
            ]
         
    def select_time(self, begin_ss, end_ss):
        if begin_ss != None:
            print >>sys.stderr, "DEBUG: begin=%f" % begin_ss
        if end_ss != None:
            print >>sys.stderr, "DEBUG: end=%f" % end_ss
        # XXX Only works if no pre-selection has been done!
        if begin_ss:
            self.ffmpeg_args += [
                "-ss",
                "%f" % begin_ss
            ]
        if end_ss:
            if not begin_ss:
                begin_ss = 0
            self.ffmpeg_args += [
                "-t",
                "%f" % (end_ss - begin_ss)
            ]
        
    def select_id(self, id):
        print >>sys.stderr, "WARNING: Media Fragments: ffmpeg postprocess: cannot select by id, not implemented"
        
    def select_track(self, track):
        print >>sys.stderr, "WARNING: Media Fragments: ffmpeg postprocess: cannot select by track, not implemented"
        
    def process(self):
        if not self.ffmpeg_args:
            return
        containertype = "ogg" # XXX EXT_TO_TYPE[self.suffix]
        cmd = [FFMPEG] + self.ffmpeg_args + [
            "-i",
            self.local_filename,
            "-f",
            containertype,
            "-vcodec",
            "copy",
            "-acodec",
            "copy",
            "-"
        ]
        print >>sys.stderr, 'DEBUG: Start ffmpeg: ', cmd
        null = open("/dev/null", "wb")
        proc = subprocess.Popen(cmd)
        # XXX Need extra headers too
        self.fp = urllib.addinfourl(proc.stdout, self.fp.headers, self.fp.url)
            
def _test():
    import sys
    fp = urllib.urlopen(sys.argv[1])
    pp = MFPostProc(fp)
    pp.ensure_content_ranges(["seconds", "x_jack_wh", "x_jack_track", "x_jack_id"])
    
if __name__ == "__main__":
    _test()
    