#!/usr/bin/python
# -*- coding: utf-8 -*-

'''

MP4 media headers
-----------------

 * https://github.com/psaavedra/qtfaststart/tree/master/qtfaststart
 * http://wiki.multimedia.cx/index.php?title=QuickTime_container#Byte_Ordering

FLV media headers
-----------------

[flv header]
F L V 01 05 00 00 00 09      FLV_HEADER (9 bytes)
00 00 62 F1                  PREVIOUS_TAG_SIZE (4 bytes)
[end flv header]

[tag header]
08                           TAG_TYPE (08, 09, 12)
00 03 44                     DATA_SIZE
00 72 63                     TIMESTAMP (milliseconds)
00                           EXT TIMESTAMP
00 00 00                     STREAM_ID (ALLWAYS 0)
[end tag header]

'''

import os
import sys
import subprocess
import uuid

from json import dumps

import bottle
from bottle import route, run, view
# from bottle import hook
from bottle import abort, request, response
from bottle import HTTPResponse

from optparse import OptionParser

import time
from datetime import datetime
import calendar

import signal
import subprocess

DEV_NULL=open('/dev/null', "w")

LOG_FILE = "./server.log"
LOG_LEVEL = 20
LOG_SEVERITIES = \
    ["emerg", "alert", "crit", "err", "warning", "notice", "info", "debug"]

CACHE_LISTDIR_TIME=10
STREAM_NOLIMIT=False

# SUBPROCESS_TIMES = { <<process>>:<<epoch>>, }
SUBPROCESS_TIMES={}

SESSIONS_STATUS = {}

logfile = "/dev/stdout"
loglevel = 20

cache_listdir_value = {}
cache_listdir_timer={}


###############################################################################

def message(module, mess, severity="info", identifier=None, peer=None):
  global logger

  severity = severity.lower()

  msg_by_level = {}
  msg_by_level["emerg"] = logger.critical
  msg_by_level["alert"] = logger.critical
  msg_by_level["crit"] = logger.critical
  msg_by_level["err"] = logger.error
  msg_by_level["warning"] = logger.warning
  msg_by_level["notice"] = logger.info
  msg_by_level["info"] = logger.info
  msg_by_level["debug"] = logger.debug

  res = "[%s]" % str(module)

  if peer:
      res = res + " [%s]" % str(peer)
  else:
      try:
        if request.get_header('X-Real-IP', default=None):
          res = res + " [%s]" % str( request.get_header('X-Real-IP') )
        elif request.get_header('X-Forwarded-For', default=None):
          res = res + " [%s]" % str( request.get_header('X-Forwarded-For') )
        else:
          res = res + " [%s]" % str(request.environ["REMOTE_ADDR"])
      except:
        res = res + " [-]"

  if identifier:
      res = res + " [%s]" % str(identifier)
  else:
      try:
          res = res + " [%s]" % str(request.session_id)
      except Exception, e :
          res = res + " [-]"

  res = res + " " + str(mess)

  msg_by_level[severity](res)



def get_session(func):
    def wrapper(**kwargs):
        message("get_session", "Looking for submited session_id", "debug")

        session_id = request.get_cookie("session")
        if request.GET.get('session'):
            session_id = request.GET.get('session')

        if not session_id:
            u = uuid.uuid1()
            session_id = u.hex
            message("get_session", "New session_id: %s" % session_id,
                    "debug")
            request.new_session = True
        else:
            message("get_session", "Session_id: %s" % session_id,
                    "debug")

        session_id = str(session_id)

        try:
            response.set_cookie("session", session_id)
            request.session_id = session_id
        except Exception, e:
            message("get_session", "Exception: %s" % e, "CRIT")

        return func(**kwargs)

    return wrapper


def common_actions_vod(func):
    def wrapper(**kwargs):
        message("common_actions_vod", "Common actions", "debug")
        message("common_actions_vod", "kwargs: %s" % dumps(kwargs), "debug")

        m = "Request Method: " + request.method
        message("common_actions_vod", m, "debug")

        m = "Request URL: " + request.url
        message("common_actions_vod", m, "info")

        m = "Request parameters: " + repr(request.query_string)
        message("common_actions_vod", m, "debug")

        m = "Request Headers: " + print_headers(request)
        message("common_actions_vod", m, "info")

        mo = ""
        if kwargs.has_key("path"):
            mo = kwargs["path"]
        if request.GET.get('mo'):
            mo = request.GET.get('mo')

        mo = securing_string(mo)
        request.mo = mo
        message("common_actions_vod", "MO: %s" % mo, "debug")

        if not os.path.exists(ops.workdir + "/videos/" + str(mo)):
            m = "mo not found: %s" % ops.workdir + "/videos/" + str(mo)
            message("common_actions_vod", m, "warning")
            return abort(404, "mo. not found")

        return func(**kwargs)
    return wrapper


def common_actions_pvr(func):
    def wrapper(**kwargs):
        message("common_actions_pvr", "Common actions", "debug")
        message("common_actions_pvr", "kwargs: %s" % dumps(kwargs), "debug")

        m = "Request Method: " + request.method
        message("common_actions_pvr", m, "debug")

        m = "Request URL: " + request.url
        message("common_actions_pvr", m, "info")

        m = "Request parameters: " + repr(request.query_string)
        message("common_actions_pvr", m, "debug")

        m = "Request Headers: " + print_headers(request)
        message("common_actions_pvr", m, "info")

        # Securization of channel_id value.
        channel_id = securing_string(kwargs["channel_id"])
        request.pvr_channel_id = channel_id

        if not os.path.exists(ops.workdir + "/pvr/ts/" + str(channel_id)):
            return abort(404, "Channel id. not found")

        # Assuring access to files entries cached
        timer = int(time.time() - ops.maxchunkduration * 1.5 )
        endtimer = None

        if kwargs.has_key("date"):
            try:
                date_object = \
datetime.strptime(kwargs["date"] + "_UTC" , '%Y-%m-%d_%H:%M:%S_%Z')
                timer = int(calendar.timegm(date_object.utctimetuple()))
            except ValueError:
                m = "Incorrect date format on URL. Expected: %Y-%m-%d_%H:%M:%S"
                message("common_actions_pvr", m, "warning")

        # date example: 2011-10-14_5:29:43
        if request.GET.get('date'):
            date = request.GET.get('date')
            try:
                date_object = datetime.strptime(date, '%Y-%m-%d_%H:%M:%S')
                timer = int(calendar.timegm(date_object.utctimetuple()))
            except ValueError:
                m = "Incorrect format on date parameter. Expected: %Y-%m-%d_%H:%M:%S"
                message("common_actions_pvr", m, "warning")

        if request.GET.get('time'):
            timer = request.GET.get('time')

        request.pvr_timer = timer
        message("common_actions_pvr", "Timer: %s" % timer, "debug")

        if kwargs.has_key("endate"):
            try:
                date_object = \
datetime.strptime(kwargs["enddate"] + "_UTC" , '%Y-%m-%d_%H:%M:%S_%Z')
                endtimer = int(calendar.timegm(date_object.utctimetuple()))
            except ValueError:
                m = "Incorrect enddate format on URL. Expected: %Y-%m-%d_%H:%M:%S"
                message("common_actions_pvr", m, "warning")

        # enddate example: 2011-10-14_5:29:43
        if request.GET.get('enddate'):
            date = request.GET.get('enddate')
            try:
                date_object = datetime.strptime(date, '%Y-%m-%d_%H:%M:%S')
                endtimer = int(calendar.timegm(date_object.utctimetuple()))
            except ValueError:
                m = "Incorrect format on enddate parameter. Expected: %Y-%m-%d_%H:%M:%S"
                message("common_actions_pvr", m, "warning")

        if request.GET.get('endtime'):
            endtimer = request.GET.get('endtime')


        request.pvr_endtimer = endtimer
        message("common_actions_pvr", "Endtimer: %s" % endtimer, "debug")

        chunkoffset = 1
        try:
            if request.GET.get('chunkoffset'):
                chunkoffset = int(request.GET.get('chunkoffset'))
        except Exception, e:
            m = "Invalid value in the chunkoffset parameter"
            message("common_actions_pvr", m, "warning")
        request.pvr_chunkoffset = chunkoffset
        message("common_actions_pvr", "Chunkoffset: %s" % chunkoffset, "debug")

        return func(**kwargs)
    return wrapper


def print_headers(r):
    res = "["
    for k,v in r.headers.iteritems():
      res = res + str(k) + ":" + str(v) + ", "
    res = res + "]"
    return res


def dump_hex(data):
    res = ""
    for i in data:
        res = res + i.encode("hex") + " "
    return res


def signal_term_handler(signal, frame):
    print 'Stoppping server ... '
    m = "Signal number %s detected. Stoppping server" % str(signal)
    message("signal_term_handler", m, "INFO")
    sys.exit(0)

def signal_hup_handler(signal, frame):
    print 'Reloading server ... '
    m = "Signal number %s detected. Reloading server" % str(signal)
    message ("signal_hup_handler",  m, "INFO")


signal.signal(signal.SIGINT, signal_term_handler)
signal.signal(signal.SIGTERM, signal_term_handler)
signal.signal(signal.SIGUSR1, signal_hup_handler)
signal.signal(signal.SIGHUP, signal_hup_handler)


def initialize_session_status_dict(session_id, mo):
    peer = ""
    try:
        if request.get_header('X-Real-IP', default=None):
          peer = request.get_header('X-Real-IP')
        elif request.get_header('X-Forwarded-For', default=None):
          peer = request.get_header('X-Forwarded-For')
        else:
          peer = request.environ["REMOTE_ADDR"]
    except:
        pass

    user_agent = request.get_header('User-Agent', default="")

    session_status = {}
    session_status["session_id"] = session_id
    session_status["mo"] = mo
    session_status["actual_transfered_bytes"] = 0
    session_status["creation_time"] = time.time()
    session_status["last_time"] = time.time()
    session_status["session_type"] = ""
    session_status["streamer_type"] = ""
    session_status["peer"] = peer
    session_status["user_agent"] = user_agent
    return session_status

def get_session_status(session_id, mo):
    global SESSIONS_STATUS
    try:
        return SESSIONS_STATUS[session_id][mo]
    except:
        session_status = initialize_session_status_dict(session_id, mo)
        if not SESSIONS_STATUS.has_key(session_id):
            SESSIONS_STATUS[session_id]={}
        if not SESSIONS_STATUS[session_id].has_key(mo):
            SESSIONS_STATUS[session_id][mo]=session_status
        return session_status

def get_flv_stream(session_id, mo, offset=0, tmp_dir="./tmp/flunnt/flv/offsets/"):
  m = "Init (%s, %s, %s, %s) " % (session_id, mo, offset, tmp_dir)
  message("get_flv_stream", m, "info")

  path = ops.workdir + "/videos/" + str(mo)

  try:
    f = open( path , 'r')
    seeking_entry_point = True
    header = '''FLV\x01\x05\x00\x00\x00\x09'''
    yield header

    hits = 0
    counter_loops = 0
    while True:
      counter_loops = (counter_loops + 1 ) % 50000
      if seeking_entry_point:

          if counter_loops == 1:
              m = "Seeking entry point for FLV media type"
              message("get_flv_stream", m, "INFO")
          data = f.read(368) # 23 * 4 * 3
          data_find = data.find("\x00\x00\x00\x00")
          if data_find > 11 and (len(data) > data_find + 4 ) and data[data_find-7] in ["\x09", ]: # ["\x08","\x09","\x12", ]:

              bitflags = data[data_find+4]

              # message("get_flv_stream", bitflags.encode("hex"), "DEBUG")
              # if bitflags.encode("hex") != "17":
              # Querido yo del manhana  ... esto viene d que quiero entrar
              # en un keyframe.
              #
              # Doc: http://osflash.org/flv.
              #
              #
              #  bitflags es el primer byte del body del paquete FLV que
              #  viene a ser el siguiente byte despues de la cadena de
              #  cuatro CEROS.
              #
              # En este byte viene el tipo de frame y el tipo de codec el
              # cual es irrelevante en este caso.
              #
              # Un keyframe, que es lo que nos interesa, es 0001XXXX,
              # y sin mas , por eso la comparacion binaria de aqui abajo:
              if ( ( bytearray(bitflags)[0] & 0b11110000 ) >> 4 ) != 1:
                 continue
              # Un saludo. Nos vemos en el futuro.
              r = data[data_find-11:]

              timestamp = int(  r[8:11].encode('hex'), 16  )  / 1000.0

              if timestamp > offset:
                  hits = hits + 1
              else:
                  hits = 0
              if hits >= 3:
                  message("get_flv_stream", dump_hex(data), "DEBUG")
                  message("get_flv_stream", dump_hex(r), "DEBUG")
                  message("get_flv_stream", "timestamp: " + repr(timestamp), "DEBUG")
                  yield r
                  seeking_entry_point = False
                  message("get_flv_stream", "Offset reached", "DEBUG")

          if data_find > 11 and (len(data) > data_find + 4 ) and  data[data_find-7] in ["\x08", ]:

              r = data[data_find-11:]
              timestamp = int(  r[8:11].encode('hex'), 16  )  / 1000.0
              if timestamp > offset:
                  hits = hits + 1
              else:
                  hits = 0
              if hits >= 30:
                  message("get_flv_stream", dump_hex(data), "DEBUG")
                  message("get_flv_stream", dump_hex(r), "DEBUG")
                  message("get_flv_stream", "timestamp: " + repr(timestamp), "DEBUG")
                  yield r
                  seeking_entry_point = False
                  message("get_flv_stream", "Offset reached", "DEBUG")
      else:
              data = f.read(32768)
              yield data


      if len(data) == 0:
          m = "Reached end of file"
          message("get_flv_stream", m, "DEBUG")
          break

    f.close()
  except Exception,e:
    m = "Exception: %s " % repr(e)
    message("get_flv_stream", m, "info")
    f.close()


def get_mp4_stream(session_id, mo, offset=0, tmp_dir="./tmp/flunnt/mp4/offsets/"):
    '''
    offset: time in seconds (long)
    '''
    m = "Init (%s, %s,  %s, %s) " % (session_id, mo, offset, tmp_dir)
    message("get_mp4_stream", m, "info")

    path = ops.workdir + "/videos/" + str(mo)

    offset_file = tmp_dir + os.sep + path + "_" + str(offset)

    m = "offset_file: %s" % offset_file
    message("get_mp4_stream", m, "info")

    dir_ = os.path.dirname(offset_file)
    d_ = ""
    for d in dir_.split(os.sep):
        d_ = d_ + d + os.sep
        try:
          if d != "":
              os.mkdir(d_)
        except Exception, e:
          pass

    try:
        if not os.path.exists(offset_file + "_qtf"):
            cmd = '''/usr/bin/ffmpeg -y -ss %s -i %s -vcodec copy -acodec copy -f mp4  %s '''\
                % (str(offset), path, offset_file )
            m = "Command: " + cmd
            message("get_mp4_stream", m, "info")
            ffmpeg = subprocess.Popen(cmd.split(), shell=False, bufsize=1024,
                 stdin=subprocess.PIPE, stderr=DEV_NULL,
                 stdout=DEV_NULL, close_fds=True)
            ffmpeg.wait()

            cmd = '''/usr/local/bin/qtfaststart  %s %s'''\
                % (offset_file, offset_file + "_qtf")
            m = "Command: " + cmd
            message("get_mp4_stream", m, "info")
            cmd_split = cmd.split()
            message("get_mp4_stream", str(cmd_split), "info")

            attempt = 0
            while True:
              try:
                stdout_ = DEV_NULL
                stderr_ = DEV_NULL
                message("get_mp4_stream", "Executing qtfaststart", "debug")
                qtf = subprocess.Popen(cmd_split, shell=False, bufsize=1024,
                   stdin=subprocess.PIPE, stderr=stderr_,
                   stdout=stdout_, close_fds=True)

                qtf.wait()
                break
              except Exception, e:
                attempt = attempt +1
                message(\
                  "get_mp4_stream", "Error executing qtfaststart (attempt %s/3)" % attempt, "error")
                if attempt > 3:
                    raise e
                time.sleep(3)

            try:
                m = "Removing %s temporary file" % (offset_file)
                message("get_mp4_stream", m, "info")
                os.remove(offset_file)
            except Exception, e:
                m = "Unexpected error removing %s file" % (offset_file
                          , repr(e))
                message("get_mp4_stream", m, "info")

        else: # File was been generated by other concurrent process.
            m = "_qtf temporary file already exists"
            message("get_mp4_stream", m, "debug")

        for i in range(3):
            try:
                m = "Opening file ..."
                message("get_mp4_stream", m, "info")
                time.sleep(2)
                f = open(offset_file + "_qtf")
                m = "File opened"
                message("get_mp4_stream", m, "info")

                try:
                  m = "Removing %s temporary file" % (offset_file + "_qtf")
                  message("get_mp4_stream", m, "info")
                  os.remove(offset_file + "_qtf")
                except Exception, e:
                  m = "Unexpected error removing %s file" % (offset_file
                          + "_qtf", repr(e))
                  message("get_mp4_stream", m, "info")
                  pass
                break
            except Exception, e:
                m = "File not opened: %e" % str(e)
                message("get_mp4_stream", m, "error")
                pass

        m = "Ready to streaming"
        message("get_mp4_stream", m, "info")

        for i in f:
            yield i
    except Exception, e:
        m = "Unexpected error: %s" % str(e)
        message("get_mp4_stream", m, "error")
        try:
            m = "Forcing removing %s temporary file" % (offset_file)
            message("get_mp4_stream", m, "debug")
            os.remove(offset_file)
        except Exception, e:
            pass
        try:
            m = "Forcing removing %s temporary file" % (offset_file + "_qtf")
            message("get_mp4_stream", m, "debug")
            os.remove(offset_file + "_qtf")
        except Exception, e:
            pass
        yield ""

def get_media_info(path):
    # files = get_listdir(ops.workdir + "/cache/videos/" + str(channel_id))
    res = {}

    cmd = '''ffmpeg -i %s''' % path

    ffmpeg = subprocess.Popen(cmd.split(), shell=False, bufsize=1024,
         stdin=subprocess.PIPE, stdout=DEV_NULL,
         stderr=subprocess.PIPE, close_fds=True)

    for i in ffmpeg.stderr:
        if i.count("bitrate:") != 0 and \
        i.count("start:") != 0 :
            try:
              # Duration: 00:03:21.8, start: 0.000000, bitrate: 1147 kb/s
              res["duration"] = i.split("Duration: ")[1].split(",")[0]
              res["duration_hours"] = int(i.split("Duration: ")[1].split(",")[0].split(":")[0])
              res["duration_minutes"] = int(i.split("Duration: ")[1].split(",")[0].split(":")[1])
              res["duration_seconds"] = float(i.split("Duration: ")[1].split(",")[0].split(":")[2])
              res["total_duration_seconds"] = int( \
                    res["duration_hours"] * 60 * 60 + \
                    res["duration_minutes"] * 60 + \
                    res["duration_seconds"] )
            except Exception, e:
              m =  "Output readed for %s path: %s" % (path, i)
              message("get_media_info", m, "DEBUG")

              m =  "Error getting duration info for %s: %s" % (path, repr(e))
              message("get_media_info", m, "WARNING")

            try:
              res["bitrate"] = \
                long(i.split("bitrate: ")[1].split("kb")[0].strip()) * 1024
            except Exception, e:
              m =  "Output readed for %s path: %s" % (path, i)
              message("get_media_info", m, "DEBUG")

              m =  "Error getting bitrate info for %s: %s" % (path, repr(e))
              message("get_media_info", m, "WARNING")

        if i.count("Input #0") != 0:
            pass

    ffmpeg.wait()
    return res


def get_listdir(dirname):
  global CACHE_LISTDIR_TIME
  global cache_listdir_timer
  global cache_listdir_value

  try:
    timer = 0
    if cache_listdir_timer.has_key(dirname):
        timer = cache_listdir_timer[dirname]

    current_time = time.time()
    if current_time > timer + CACHE_LISTDIR_TIME:
        files = os.listdir(dirname)
        files.sort()

        # XXX: Deleting last inclompleted files. Assuming is been writed by the
        # recorder process
        if len(files) > 0:
            path = dirname + os.sep + files[-1]
            size = os.path.getsize(path)

            if size == 0:
              files.__delitem__(-1)

        cache_listdir_value[dirname] = files
        cache_listdir_timer[dirname] = current_time

  except Exception, e:
      m = "[get_listdir] " + str(e)
      message("get_listdir", m, "err")


  return cache_listdir_value[dirname]

def get_next_higher_filename_position(listdir, filename):
    """
    """
    global cache_listdir_value
    global cache_listdir_timer

    if len(listdir)==1:
        if listdir[0] > filename:
            return 0
        else:
            # Value out of range for listdir list
            return 1

    pivot = len(listdir) / 2

    if listdir[pivot] == filename:
        return pivot
    if listdir[pivot] < filename:
        position = get_next_higher_filename_position(listdir[pivot:],filename)
        return pivot + position
    if listdir[pivot] > filename:
        return get_next_higher_filename_position(listdir[:pivot],filename)


def securing_string(string, strings_to_avoid=["./","*","^"]):
    res = string
    for c in strings_to_avoid:
        res = res.replace(c,'')
    if res == string:
        return res
    else:
        return securing_string(res)


def pvr_hls(chunkoffset,channel_id, starttime, endtime=None):

  session_id = request.session_id
  mo = "%s" % (channel_id)

  session_status = get_session_status(session_id, mo)
  session_status["session_type"] = "pvr"
  session_status["streamer_type"] = "pvr_hls"

  is_live = False if endtime else True

  m =  "Live streaming: %s" % is_live
  message("pvr_hls", m, "debug")

  # TODO: Habria que establecer un tiempo de margen en el starttime
  # por el cual se considera que una petición es demasiado antigua en el
  # tiempo con respecto al time del primer chunk encontrado.

  m =  "Channel Id: " + str(channel_id)
  message("pvr_hls", m, "INFO")

  m = ""
  try:
      m = "Timer set to: " + str(starttime) + \
            " (" + datetime.utcfromtimestamp(float(starttime)).isoformat() + ")"
  except Exception, e:
      m = str(e)
  message("pvr_hls", m, "INFO")
  try:
      if endtime:
          m = "End Timer set to: " + str(endtime) + \
            " (" + datetime.utcfromtimestamp(float(endtime)).isoformat() + ")"
      else:
          m = "End Timer set to: None"
  except Exception, e:
      m = str(e)
  message("pvr_hls", m, "INFO")

  m =  "Chunk offset set to: " + str(chunkoffset)
  message("pvr_hls", m, "INFO")

  files = get_listdir(ops.workdir + "/pvr/ts/" + str(channel_id))
  position  = get_next_higher_filename_position(files, str(starttime))

  m = ""
  if position >= len(files):
      m = "Position of next higher filename:" + str(position)
  else:
      m = "Position of next higher filename:" + str(position) \
              + " (" + files[position] + ")"
  message("pvr_hls", m, "debug")

  if position >= len(files):

      # XXX: Waiting for current recording chunnks
      m = "Waiting for current recording chunks (" \
                + str(ops.maxchunkduration) + " seconds)"
      message("pvr_hls", m, "warning")
      time.sleep(int(ops.maxchunkduration))

      files = get_listdir(ops.workdir + "/pvr/ts/" + str(channel_id))
      position  = get_next_higher_filename_position(files, str(starttime))

      if position >= len(files):
          m = "Returning 404 - No chunks found: position >= len(files)"
          message("pvr_hls", m, "info")

          # Petición aun no satisfacible
          return abort(404, "No chunks found")

  # Chunks found since time and chunkoffset
  chunks = []
  ### chunks_needed = int(ops.chunkstoserve)
  chunks_needed = 3
  # Pointer to the current chunk number to be processed by the while loop
  current_chunk_number = int(chunkoffset)
  # feched is activated when we found a chunk equal to the current_chunk 
  feched = False
  # sequence is used to send the first chunk index on the M3U8 response
  sequence = current_chunk_number

  timestamp=None
  chunk_number=None
  duration=None
  # We use previous_chunk_number to found chunks discontinuities before
  # fetching the chunks with current_chunk_number 
  previous_chunk_number=None
  while True:
      if chunks_needed <= 0 and is_live:
          break

      if position >= len(files):
          break

      try:
          timestamp,duration,chunk_number = \
            files[position].split(".ts")[0].split("_")
          # print position
          # print current_chunk_number
          # print files[position]
          timestamp = int(timestamp)
      except:
          position == position + 1
          continue

      if feched and endtime and (float(endtime) < float(timestamp)):
          m = "End time reached"
          message("pvr_hls", m, "INFO")
          break

      if is_live and int(chunk_number) < current_chunk_number:
          if not feched:
              if previous_chunk_number and \
                 int(previous_chunk_number) > int(chunk_number):
                     # Discontinuity found before fetch the first element.
                     m = "Discontinuity found before fetch the first chunk"
                     message("pvr_hls", m, "INFO")
                     break
              previous_chunk_number = chunk_number
              position = position + 1
              continue
          else:
              break

      if not is_live or int(chunk_number) == current_chunk_number:
          feched = True
          chunks.append(files[position])
          current_chunk_number = current_chunk_number + 1
          position = position + 1
          chunks_needed = chunks_needed - 1
          continue

      if int(chunk_number) > current_chunk_number:
          if current_chunk_number == 1:
              current_chunk_number = int(chunk_number)
              sequence = current_chunk_number
          else:
              m = "Discontinuity found"
              message("pvr_hls", m, "INFO")
              break

  m = "Chunks found"
  message("pvr_hls", m, "debug")

  # Each media file URI in a Playlist has a unique sequence number.  The
  # sequence number of a URI is equal to the sequence number of the URI
  # that preceded it plus one.  The EXT-X-MEDIA-SEQUENCE tag indicates
  # the sequence number of the first URI that appears in a Playlist
  # file.
  # Its format is:
  #    #EXT-X-MEDIA-SEQUENCE:<number>

  try:
    m3u8_header = "#EXTM3U\n\n"
    m3u8_header = m3u8_header + "#EXT-X-VERSION:3\n\n"
    m3u8_header = m3u8_header + "#EXT-X-MEDIA-SEQUENCE:%s\n\n" % sequence
    # duration = 2
    m3u8_header = m3u8_header + "#EXT-X-TARGETDURATION:%s\n\n" \
      % (int(duration))

    m3u8_body = ""

    # XXX: Hack demo de OTT de R
    try:
      chunks = chunks[:-1]
    except:
      pass
    # End XXX
    for c in chunks:
      timestamp,duration,chunk = c.split(".ts")[0].split("_")
      m3u8_body = m3u8_body + "#EXTINF:%s,\n" % duration
      m3u8_body = m3u8_body + ops.chunkserverpath + "/" + channel_id + "/" + c + "\n\n"
      session_status["actual_transfered_bytes"] += os.stat( \
          "%s/pvr/ts/%s/%s" % (ops.workdir,channel_id,c)).st_size
      session_status["last_time"] = time.time()

    m3u8_footer = ""
    # EXT-X-ENDLIST cause the end of the stream
    if endtime:
      m3u8_footer = "#EXT-X-ENDLIST\n"

    m3u8 = m3u8_header + m3u8_body + m3u8_footer
  except Exception, e:
      m = str(e)
      message("pvr_hls", m, "debug")


  response.content_type = 'application/x-mpegURL'
  return m3u8


def get_reencapsule_process(input_, format="flv"):
    global SUBPROCESSES

    cmd= "ffmpeg  -y -i %s  -vcodec copy -acodec copy -f %s -" % (input_,format)
    p = subprocess.Popen(cmd.split(), shell=False, bufsize=102400,
                       stdin=subprocess.PIPE,
                       stdout=subprocess.PIPE,
                       stderr=subprocess.PIPE, close_fds=True)
    SUBPROCESS_TIMES[p]=time.time()

    return p

def reencapsule(process):
  # res = ""
  # try:
  #   for d in data:
  #     process.stdin.write(d)
  # except Exception, e:
  #     print "Uy: " + str(e)
  # print process.stderr
  # rr = ""

  # try:
  #   for r in process.stderr:
  #     print "B"
  #     print r

  # except Exception, e:
  #     print "Uy: " + str(e)
  # print "sss"    

  try:
    for r in process.stdout:
      # print r
      yield r
      SUBPROCESS_TIMES[process]=time.time()

  except Exception, e:
      m =  str(e)
      message("reencapsule", m, "error")
      yield ""



def reencapsulation_http(source, format="flv"):

  if format=="flv":
    response.content_type = 'video/x-flv'

  process = get_reencapsule_process(source, format)

  return reencapsule(process)


def pvr_http(channel_id, timer, stream_limited=True):

  session_id = request.session_id
  mo = "%s_%s" % (channel_id, timer)

  session_status = get_session_status(session_id, mo)
  session_status["session_type"] = "pvr"
  session_status["streamer_type"] = "pvr_http"

  response.content_type = 'video/mpeg2'

  # TODO: Habria que establecer un tiempo de margen en el timer
  # por el cual se considera que una petición es demasiado antigua en el
  # tiempo con respecto al time del primer chunk encontrado.

  m =  "Channel Id: " + str(channel_id)
  message("pvr_http", m, "INFO")
  m = "Timer set to: " + str(timer) + \
            " (" + datetime.utcfromtimestamp(float(timer)).isoformat() + ")"
  message("pvr_http", m, "INFO")

  files = get_listdir(ops.workdir + "/pvr/ts/" + str(channel_id))
  position  = get_next_higher_filename_position(files, str(timer))

  m = ""
  if position >= len(files):
      m = "Position of next higher filename:" + str(position)
  else:
      m = "Position of next higher filename:" + str(position) \
          + " (" + files[position] + ")"
  message("pvr_http", m, "INFO")

  streaming = True
  if position >= len(files):

      # XXX: Waiting for current recording chunks
      m = "Waiting for current recording chunks (" \
                + str(ops.maxchunkduration) + " seconds)"
      message("pvr_http", m, "warning")
      time.sleep(int(ops.maxchunkduration))

      files = get_listdir(ops.workdir + "/pvr/ts/" + str(channel_id))
      position  = get_next_higher_filename_position(files, str(timer))

      if position >= len(files):
          m = "position >= len(files): Returning 404 - No chunks found"
          message("pvr_http", m, "INFO")

          # Petición aun no satisfacible
          yield abort(404, "No chunks found")
          streaming = False

  timestamp=None
  chunk_number=None
  duration=None
  previous_chunk_number=None

  delay = 0
  waitings = 0
  while streaming:

      c_time_00 = time.time()
      waitings = 0

      if position >= len(files):
          m = "Refreshing file list"
          message("pvr_http", m, "debug")

          timestamp = int(timestamp) + 1

          files = get_listdir(ops.workdir + "/pvr/ts/" + str(channel_id))
          # XXX: Avoiding repetition of the previous chunk
          # print timestamp
          position  = get_next_higher_filename_position(files, str(timestamp))
          if position >= len(files):
              if duration:
                try:
                    duration = int(duration)
                except Exception:
                    duration = ops.maxchunkduration

                # XXX: Waiting for current recording chunks
                m = "Waiting for current recording chunks (" \
                    + str(duration) + " seconds) (" + str(c_time_00) + ")"
                message("pvr_http", m, "warning")
                time.sleep(int(duration))
                waitings = waitings + 1

                files = get_listdir(ops.workdir + "/pvr/ts/" + str(channel_id))
                position  = get_next_higher_filename_position(files, str(timestamp))
                if position >= len(files):
                   m = "Breaking stream: position >= len(files)"
                   message("pvr_http", m, "notice")
                   break

      # m = "Position:" + str(position) + " last:" \
      #     + str(len(files)-1) + " = " + str(len(files) - 1 - int(position))
      # message("pvr_http", m, "INFO")

      try:
          timestamp,duration,chunk_number = \
            files[position].split(".ts")[0].split("_")
          m = "Working with: " + files[position] \
            + " (Position: " + str(position) + ")"
          message("pvr_http", m, "debug")

      except Exception, e:
          position = position + 1
          continue

      if previous_chunk_number:
          # print previous_chunk_number
          # print chunk_number
          if not ((int (previous_chunk_number) + 1) == int(chunk_number)):
              m = "previous_chunk_number + 1 != chunk_number: Breaking stream"
              message("pvr_http", m, "warning")
              break

      previous_chunk_number = int(chunk_number)

      speed = 1500
      try:
          if stream_limited:
            path = ops.workdir + "/pvr/ts/" + str(channel_id) + '/' \
                 + files[position]
            size = os.path.getsize(path)
            speed = size / (long(duration) - delay)
            m = "Speed: " + str(speed) + " Byte/s (" + str(size) + " /" \
              + " (" + str(duration) + " - " + str(delay) + "))"
            message("pvr_http", m, "debug")

            if speed <= 0:
              m = "Speed <= 0 detected"
              message("pvr_http", m, "warning")

              time.sleep(int(duration))
              waitings = waitings + 1

              path = ops.workdir + "/pvr/ts/" + str(channel_id) + '/' \
                 + files[position]
              size = os.path.getsize(path)
              speed = size /  int(duration)

            # XXX: Hack to avoid pauses
            speed = speed * 1.1
            m = "Speed modified: " + str(speed) + " Byte/s (" + str(size) + "/" \
              + str(duration) + ")"
            message("pvr_http", m, "debug")

          f = open( path , 'r')
      except Exception, e:
          m = "Opening ... Error:" + str(e)
          message("pvr_http", m, "err")

      c_time_10 = time.time()

      delay_10 = c_time_10 - c_time_00 - (waitings * int(duration))
      if delay_10 < 0:
          delay_10 = 0

      t_c = 0
      while True:
          data = f.read(int(speed / 16.0))
          if not data:
              break
          yield data
          session_status["actual_transfered_bytes"] += sys.getsizeof(data)
          session_status["last_time"] = time.time()

          if stream_limited:
            t_c = t_c + 0.0625
            time.sleep(0.0625)

      c_time_20 = time.time()

      delay_20 = c_time_20 - c_time_10 - t_c

      f.close()

      position = position + 1

      c_time_30 = time.time()
      delay_30 = c_time_30 - c_time_20
      delay = delay_10 + delay_20 + delay_30

      m = "Delay: " + str(delay_10) + " + " \
          + str(delay_20) + " + " + str(delay_30) + " = " + str(delay)
      message("pvr_http", m, "debug")




def vod_http_partial_content(session_id, mo, offset_min=0, offset_max=None, stream_limited=True):
  '''
  session_id=session identifier
  mo=identifier of media filename
  offset_min = bytes
  offset_max = bytes
  stream_limited = boolean
  '''
  try:
    try:
      offset = int(offset)
    except Exception:
      offset=0

    m =  "VoD media Id: %s (offset_min: %s) (offset_max: %s)" % (mo, offset_min, offset_max)
    message("vod_http_partial_content", m, "INFO")

    path = ops.workdir + "/videos/" + str(mo)

    session_status = get_session_status(session_id, mo)
    session_status["session_type"] = "vod"
    session_status["streamer_type"] = "vod_http_partial_content"

    # Recuperar el fichero
    try:
      length = os.path.getsize(ops.workdir + "/videos/" + str(mo))

      if length <= offset_min:
          # TODO: debe de dar un 400 no se que
          pass
      if length <= offset_max: # length must be bigger than offset_max
          # TODO: debe de dar un 400 no se que
          pass

      if offset_max and offset_min > offset_max:
          # TODO: debe de dar un 400 no se que
          pass

      f = open( path , 'r')
      response.content_type = 'video/mp4'
      f.seek(offset_min)

      response.status = 206
      response.set_header('Accept-Ranges', 'bytes')
      response.set_header('Content-Length', length)
      response.set_header('Content-Type', "video/mp4")
      if not offset_max:
          response.set_header('Content-Length', length - offset_min)
          response.set_header('Content-Range', \
                  "bytes " + str(offset_min) + "-" + str(length - 1) + "/" + str(length))
      else:
          response.set_header('Content-Length', offset_max - offset_min + 1)
          response.set_header('Content-Range', \
                  "bytes " + str(offset_min) + "-" + str(offset_max) + "/" + str(length))


      if not offset_max:
          while True:
              data = f.read(32768)
              session_status["actual_transfered_bytes"] += sys.getsizeof(data)
              session_status["last_time"] = time.time()
              yield data
              if len(data) == 0:
                  m = "Reached end of file"
                  message("vod_http_partial_content", m, "DEBUG")
                  break
      else:
          bytes_to_read = 32768
          total_bytes_to_read = offset_max - offset_min + 1  # total bytes to read
          while True:
              if total_bytes_to_read < bytes_to_read:
                  bytes_to_read = total_bytes_to_read
              data = f.read(bytes_to_read)
              yield data
              session_status["actual_transfered_bytes"] += sys.getsizeof(data)
              session_status["last_time"] = time.time()
              total_bytes_to_read = total_bytes_to_read - bytes_to_read
              if len(data) == 0:
                  m = "Reached end of the partial content"
                  message("vod_http_partial_content", m, "DEBUG")
                  break

      message("vod_http_partial_content", "End \n", "DEBUG")
      f.close()

    except Exception, e:
      message("vod_http_partial_content", "Internal error: %s" % repr(e),"error")
      yield abort(500, "Internal error: %s" % repr(e))

  except Exception, e:
     m = "Unexpected error: " + repr(e)
     message("vod_http_partial_content", m, "error")
     yield abort(500, m)


def vod_http(session_id, mo, offset=0, stream_limited=True):
 '''
  session_id=session identifier
  offset=seconds (float)
  mo=identifier of media filename
  stream_limited = boolean
 '''
 try:

  try:
      offset = float(offset)
  except Exception:
      offset=0

  m =  "VoD media Id: %s (offset: %s)" % (mo, offset)
  message("vod_http", m, "INFO")

  path = ops.workdir + "/videos/" + str(mo)

  # TODO: Analizar el formato
  # response.content_type = 'video/mpeg2'
  # response.content_type = 'video/x-flv''
  # speed = get_media_info(path)["bitrate"] / 8
  # speed = size / (long(duration))
  speed = 2000000
  m = "Speed: " + str(speed) + " Byte/s"
  message("vod_http", m, "debug")

  # Controlar el offset
  # bytes_offset = offset * speed
  # m = "Bytes offset set to: %s" \
  #     % (str(bytes_offset))
  # message("vod_http", m, "INFO")

  # XXX: speed overhead in order to avoid stalled state dring the streaming
  # speed = speed + 5000000

  session_status = get_session_status(session_id, mo)
  session_status["session_type"] = "vod"
  session_status["streamer_type"] = "vod_http"

  # Recuperar el fichero

  try:
      f = open( path , 'r')
      response.content_type = 'video/mp4'
      media_type = None
      if f.read(3) == "FLV":
        media_type = "FLV"
        response.content_type = 'video/x-flv'
      f.seek(0)
      if f.read(96).find("mp4") != -1:
        media_type = "MP4"
        response.content_type = 'video/mp4'
      if offset != 0:
          if media_type == "FLV":
              for ii in get_flv_stream(session_id, mo, offset, "/tmp/flunnt/flv/offsets/"):
                  yield ii
                  session_status["actual_transfered_bytes"] += sys.getsizeof(ii)
                  session_status["last_time"] = time.time()
          if media_type == "MP4":
              for ii in get_mp4_stream(session_id, mo, offset, "/tmp/flunnt/mp4/offsets/"):
                  yield ii
                  session_status["actual_transfered_bytes"] += sys.getsizeof(ii)
                  session_status["last_time"] = time.time()
      else:
          f.seek(0)
          data = f.read(16)
          yield data
          session_status["actual_transfered_bytes"] += sys.getsizeof(data)

          m = "Header: " + dump_hex(data)
          message("vod_http", m, "DEBUG")
          while True:
              data = f.read(int(speed / 16.0))
              yield data
              session_status["actual_transfered_bytes"] += sys.getsizeof(data)
              session_status["last_time"] = time.time()
              time.sleep(1.0 / 16)
              if len(data) == 0:
                  m = "Reached end of file"
                  message("vod_http", m, "DEBUG")
                  break
      f.close()

  except Exception, e:
      message("vod_http", "Oops: " + repr(e), "debug")
      yield abort(404, "No file found")
      streaming = False

  counter_loops = 0

 except Exception, e:
     m = "Unexpected error: " + repr(e)
     message("vod_http", m, "error")
     yield abort(500, m)









################################################################################

parser = OptionParser()

parser.add_option("-w", "--workdir", dest="workdir", default=".",
        help="Work directory (default: .)")

parser.add_option("-C", "--chunkserverpath", dest="chunkserverpath",
        help="Chunk server path (default: http://localhost/ts/)",
        default="http://localhost/ts/")

parser.add_option("-P", "--prefixpath", dest="prefixpath",
        help="Path of indexer (default: '')",
        default="")

parser.add_option("-i", "--ip", dest="ip",
        help="Listen IP (default: 127.0.0.1)",
        default="127.0.0.1")

parser.add_option("-p", "--port", dest="port",
        help="Listen port (default: 9002)",
        default=9002, type="int")

parser.add_option("-T", "--cachetimeout", dest="cachetimeout",
        help="Cache timeout (default: 10). Must be minor than \
maxchunkduration value",
        default=10, type="int")

parser.add_option("-M", "--maxchunkduration", dest="maxchunkduration",
        help="Chunk duration (default: 30)",
        default=30, type="int")

parser.add_option("-F", "--fastcgi",
        action="store_true", dest="fcgi", default=False,
        help="Run as FastCGI server")

parser.add_option("--loglevel",
        dest="loglevel", help="Log level (default: %s)" % LOG_LEVEL,
        default=LOG_LEVEL, type="int")

parser.add_option("-L", "--logfile",
        dest="logfile", default=LOG_FILE,
        help="Log file (default: %s)" % LOG_FILE)

parser.add_option("-c", "--chunkstoserve", dest="chunkstoserve",
        help="Chunks to serve (default: 4)",
        default=4, type="int")

(ops, args) = parser.parse_args()


################################################################################

PREFIX_PATH = ops.prefixpath
CACHE_LISTDIR_TIME = ops.cachetimeout

# logging ######################################################################
import logging
hdlr = logging.FileHandler(ops.logfile)
hdlr.setFormatter(logging.Formatter('%(levelname)s %(asctime)s %(message)s'))
logger = logging.getLogger('fluunt')
logger.addHandler(hdlr)
logger.setLevel(ops.loglevel)


# setting up ###################################################################

logger.info("Default encoding: %s" % sys.getdefaultencoding())

m = "Starting server"
message("main", m, "INFO")

m = "Verbosity level set to: %s" % ops.loglevel
message("main", m, "INFO")


################################################################################


@route(PREFIX_PATH +  '/crossdomain.xml')
@get_session
def crossdomain_xml():
    res = '''
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<allow-access-from domain="*" />
<site-control permitted-cross-domain-policies="all"/>
</cross-domain-policy>
'''
    return res


@route(PREFIX_PATH +  '/reencapsulation/:format_')
@get_session
def reencapsulation_http_url(format_):
    source = ""
    if request.GET.get('source'):
        source = request.GET.get('source')
    m = "Source: " + source
    message("reencapsulation_http_url", m, "debug")
    return reencapsulation_http(source, format_)


@route(PREFIX_PATH +  '/pvr/channels')
def pvr_channels():
  """
  """
  response.content_type = 'application/json'
  files = get_listdir(ops.workdir + "/pvr/ts/" )
  res = {}
  res["channels"] = files
  return res


@route(PREFIX_PATH +  '/pvr/:channel_id/segments')
def pvr_segments(channel_id):
  """
  For example:
    channel_id = divinity_udp_239.192.201.5_1234
    or
    channel_id = divinitybyhttp
  """

  # Securization of channel_id value.
  channel_id = securing_string(channel_id)

  if not os.path.exists(ops.workdir + "/pvr/ts/" + str(channel_id)):
      return abort(404, "Channel id. not found")

  files = get_listdir(ops.workdir + "/pvr/ts/" + str(channel_id))

  res = {}
  extra = {}
  extra["num_of_chunks"]= len(files)
  segments = []

  if len(files) == 0:
    res["extra"] = extra
    res["segments"] = segments
    return res


  # 1328100159_5_126.ts
  started_segment = False
  segment_start=None
  segment_num_of_chunks=0
  previous_chunk_number = -1
  previous_timestamp = None
  for f in files:
      try:
          timestamp,chunkduration,chunknumber = \
              f.split(".ts")[0].split("_")
          timestamp=long(timestamp)
          chunknumber=int(chunknumber)
          chunkduration=int(chunkduration)
      except Exception:
          continue

      if not started_segment:

          segment_start = timestamp
          segment_chunk_duration = chunkduration
          segment_num_of_chunks = 1

          started_segment = True
      else:
          # Segment end has been reached
          if chunknumber != previous_chunknumber + 1:
              segment = {}
              segment["start_timestamp"] = segment_start
              segment["start_isoformat"] = \
                datetime.utcfromtimestamp(segment["start_timestamp"]).isoformat()
              segment["end_timestamp"] = previous_timestamp \
                + segment_chunk_duration
              segment["end_isoformat"] = \
                datetime.utcfromtimestamp(segment["end_timestamp"]).isoformat()
              segment["chunk_duration"] = segment_chunk_duration
              segment["num_of_chunks"] = segment_num_of_chunks
              segments.append(segment)

              # Resetting for new segment
              segment_start = timestamp
              segment_chunk_duration = chunkduration
              segment_num_of_chunks = 1
              started_segment = True

          else:
              segment_num_of_chunks = segment_num_of_chunks + 1

      previous_chunknumber = chunknumber
      previous_timestamp = timestamp

  # Closing the last segment
  segment = {}
  segment["start_timestamp"] = segment_start
  segment["start_isoformat"] = \
    datetime.utcfromtimestamp(segment["start_timestamp"]).isoformat()
  segment["end_timestamp"] = timestamp + segment_chunk_duration
  segment["end_isoformat"] = \
    datetime.utcfromtimestamp(segment["end_timestamp"]).isoformat()
  segment["chunk_duration"] = segment_chunk_duration
  segment["num_of_chunks"] = segment_num_of_chunks
  segments.append(segment)

  res["extra"] = extra
  res["segments"] = segments
  return res

@route(PREFIX_PATH +  '/pvr/:channel_id/playlist.m3u8')
@route(PREFIX_PATH +  '/pvr/:channel_id/:date/playlist.m3u8')
@route(PREFIX_PATH +  '/pvr/:channel_id/:date/:enddate/playlist.m3u8')
@get_session
def pvr_hls_playlist(channel_id, date=None, enddate=None):
  """
  For example:
    channel_id = divinity_udp_239.192.201.5_1234
    or
    channel_id = divinitybyhttp
    date and enddate: %Y-%m-%d_%H:%M:%S
    time and endtime: 1400665691


    Returns something like:

    EXTM3U
    #EXT-X-VERSION:3
    #EXT-X-STREAM-INF:PROGRAM-ID=1
    hls.m3u8?session=9daa2446836f4a7f92af6108f0be64b0

  """
  m3u8 = ""
  try:
    params = request.query_string.strip()
    new_params = ""
    if params != "":
      for p in params.split("&"):
        if not p.count("session=") > 0:
            new_params = new_params + p + "&"
    new_params = new_params + "session=" + request.session_id

    m3u8 = "#EXTM3U\n"
    m3u8 = m3u8 + "#EXT-X-VERSION:3\n"
    m3u8 = m3u8 + "#EXT-X-STREAM-INF:PROGRAM-ID=1\n"
    m3u8 = m3u8 + "hls.m3u8?" + new_params + "\n"
  except Exception, e:
      m = str(e)
      message("pvr_hls_playlist", m, "error")

  response.content_type = 'application/x-mpegURL'
  return m3u8

@route(PREFIX_PATH +  '/pvr/:channel_id/hls.m3u8')
@route(PREFIX_PATH +  '/pvr/:channel_id/:date/hls.m3u8')
@route(PREFIX_PATH +  '/pvr/:channel_id/:date/:enddate/hls.m3u8')
@get_session
@common_actions_pvr
def pvr_hls_url_start_end(channel_id, date=None, enddate=None):
  """
  For example:
    channel_id = divinity_udp_239.192.201.5_1234
    or
    channel_id = divinitybyhttp
    date and enddate: %Y-%m-%d_%H:%M:%S
    time and endtime: 1400665691
  """
  return pvr_hls(request.pvr_chunkoffset,request.pvr_channel_id,
          request.pvr_timer, request.pvr_endtimer)


@route(PREFIX_PATH +  '/pvr/:channel_id/media.ts')
@get_session
@common_actions_pvr
def pvr_http_parameters(channel_id):
  """
  For example:
    channel_id = divinity_udp_239.192.201.5_1234
    or
    channel_id = divinitybyhttp

    parameters:
       time=timestamp
       date=%Y-%m-%d_%H:%M:%S
  """
  return pvr_http(request.pvr_channel_id, request.pvr_timer)


@route(PREFIX_PATH +  '/pvr/:channel_id/:date')
@get_session
@common_actions_pvr
def pvr_http_url(channel_id, date):
  """
  For example:
    channel_id = divinity_udp_239.192.201.5_1234
    or
    channel_id = divinitybyhttp
  """
  return pvr_http(request.pvr_channel_id, request.pvr_timer)


@route(PREFIX_PATH + '/play')
@route(PREFIX_PATH + '<path:path>')
@get_session
@common_actions_vod
def vod_http_parameters(path=None):
  """
  Parameters:
      mo=rp_domingos_riazor_03-01-2013.flv
      session=1357288024956.892   # session identifier
      usage=normal
      tstart=249000  # offset in milliseconds

  """
  try:

    session_id = request.session_id
    mo = request.mo

    media_path = ops.workdir + "/videos/" + str(mo)
    response.set_header('Accept-Ranges', 'bytes')
    length = os.path.getsize(media_path)
    response.set_header('Content-Length', length)
    http_range_header =  \
      request.get_header("Range", default=None)
    # Range: bytes=0-999
    # Range: bytes=1000-

    try:
       offset = float(long(request.GET.get('tstart')) / 1000.0)
    except Exception:
       offset = 0.0

    res = None
    if offset == 0.0:
        if not http_range_header:
            res = vod_http(session_id, mo, offset)
        else:
            try:
                # pass
                response.set_header("Content-Duration", get_media_info(media_path)["total_duration_seconds"])
            except Exception, e:
                m = "Content-Duration header can not be generated: %s" % e
                message("vod_http_parameters", m, "warning")

            try:
                offset_min = int(http_range_header.split("=")[1].split("-")[0])
            except Exception:
                offset_min = 0
            try:
                offset_max = int(http_range_header.split("=")[1].split("-")[1])
            except Exception:
                offset_max = None

            res = vod_http_partial_content(session_id, mo, offset_min, offset_max)
            # res = HTTPResponse(body=res_body, status=200)
            m = "Partial content (206) Response Headers: \
               " + print_headers(response)
            message("vod_http_parameters", m, "debug")
    else:
        # Si hay parametro tstart valido entonces obviamos el
        # posicionamiento por cabeceras Range
        # XXX: XXX XXX
        # res = vod_http(session_id, mo, 0, session_id)
        res = vod_http(session_id, mo, offset)

  except Exception, e:
     m = "Unexpected error: " + repr(e)
     message("vod_http_parameters", m, "error")
     res =  abort(500, m)

  m = "Response Headers: " + print_headers(response)
  message("vod_http_parameters", m, "info")

  return res


@route(PREFIX_PATH +  '/server/status')
def server_status():
    """
    """
    global SESSIONS_STATUS

    summary = {}
    summary["active_sessions"] = 0
    summary["active_streams"] = 0
    summary["active_streams_transfered_bytes"] = 0
    response.content_type = 'application/json'

    for k1 in SESSIONS_STATUS.keys():
        try:
            for k2 in SESSIONS_STATUS[k1].keys():
                try:
                    session_status = SESSIONS_STATUS[k1][k2]
                    if session_status["last_time"] + 30 < time.time():
                        m = "Deleting stream"
                        message("server_status", m, "info")
                        SESSIONS_STATUS[k1].pop(k2)
                    else:
                        summary["active_streams"] += 1
                        if session_status.has_key("actual_transfered_bytes"):
                            summary["active_streams_transfered_bytes"] += \
                                session_status["actual_transfered_bytes"]
                except:
                    m = "Unexpected error counting MO's into a session"
                    message("server_status", m, "err")

            if len(SESSIONS_STATUS[k1]) == 0:
                SESSIONS_STATUS.pop(k1)
                m = "Deleting session"
                message("server_status", m, "info")

            else:
                summary["active_sessions"] += 1
        except:
            m = "Unexpected error counting number of sessions"
            message("server_status", m, "err")

    res = {}
    res["summary"] = summary
    res["sessions"] = SESSIONS_STATUS

    return res


@route(PREFIX_PATH +  '/server/streams/count')
def server_status():
    """
    """
    global SESSIONS_STATUS

    mo_startswith = None
    if request.GET.get('mo_startswith'):
        mo_startswith = request.GET.get('mo_startswith')

    mo_find = None
    if request.GET.get('mo_find'):
        mo_find = request.GET.get('mo_find')

    counter = 0

    for k1 in SESSIONS_STATUS.keys():
        try:
            for k2 in SESSIONS_STATUS[k1].keys():
                try:
                    session_status = SESSIONS_STATUS[k1][k2]
                    if session_status["last_time"] + 30 < time.time():
                        SESSIONS_STATUS[k1].pop(k2)
                    else:
                        if mo_find and 0 > session_status["mo"].find(mo_find):
                            continue
                        if mo_startswith and not session_status["mo"].startswith(mo_startswith):
                            continue
                        counter += 1
                except:
                    m = "Unexpected error counting MO's into a session"
                    message("server_status", m, "err")

            if len(SESSIONS_STATUS[k1]) == 0:
                SESSIONS_STATUS.pop(k1)
        except:
            m = "Unexpected error counting number of sessions"
            message("server_status", m, "err")

    res = {}
    res["count"] = counter

    response.content_type = 'application/json'
    return res





###############################################################################


if __name__ == '__main__':
    if ops.fcgi:
        bottle.run(server=bottle.FlupFCGIServer, port=ops.port, host=ops.ip)
    else:
        # bottle.run(host=ops.ip, port=ops.port)
        # bottle.run(server=bottle.CherryPyServer, port=ops.port, host=ops.ip, 200)
        bottle.run(server=bottle.PasteServer, port=ops.port, host=ops.ip,
                quiet=True,
                use_threadpool=True, threadpool_workers=150)


