text: """
  Make a VoD file look like infinite live DASH content. The timing is synchronized with wall clock.
  
  The rewrites which are done are
  
  MPD:
      @MPD
        remove mediaPresentationDuration
        set type dynamic
        set publishTime
        set timeShiftBufferDepth
        set availabilityStartTime
        set minimumUpdatePeriod
        set maxSegmentDuration
        set/add availabilityEndTIme
      @SegmentTemplate
        set startNumber
  
  initialization segments:
     No change
  
  Media segments
     Mapped from live number to VoD number
     tfdt and sidx updated to match live time (if KEEP_SIDX = true)
     sequenceNumber updated to be continuous (and identical to the sequenceNumber asked for)
  
  The numbering and timing is based on the epoch time, and is generally
  
  [time_in_epoch clipped to multiple of duration]/duration
  
  Thus segNr corresponds to the interval [segNr*duration , (segNr+1)*duration]
  
  For infinite content, the default is startNumber = 0, availabilityStartTime = 1970-01-01T00:00:00
  """
  
  # The copyright in this software is being made available under the BSD License,
  # included below. This software may be subject to other third party and contributor
  # rights, including patent rights, and no such rights are granted under this license.
  #
  # Copyright (c) 2015, Dash Industry Forum.
  # All rights reserved.
  #
  # Redistribution and use in source and binary forms, with or without modification,
  # are permitted provided that the following conditions are met:
  #  * Redistributions of source code must retain the above copyright notice, this
  #  list of conditions and the following disclaimer.
  #  * Redistributions in binary form must reproduce the above copyright notice,
  #  this list of conditions and the following disclaimer in the documentation and/or
  #  other materials provided with the distribution.
  #  * Neither the name of Dash Industry Forum nor the names of its
  #  contributors may be used to endorse or promote products derived from this software
  #  without specific prior written permission.
  #
  #  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY
  #  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  #  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  #  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
  #  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  #  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  #  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  #  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  #  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  #  POSSIBILITY OF SUCH DAMAGE.
  
  from os.path import splitext, join
  from math import ceil
  from re import findall
  from .initsegmentfilter import InitLiveFilter
  from .mediasegmentfilter import MediaSegmentFilter
  from . import segmentmuxer
  from . import mpdprocessor
  from .timeformatconversions import make_timestamp, seconds_to_iso_duration
  from .configprocessor import ConfigProcessor
  from xml.etree import ElementTree as ET
  
  SECS_IN_DAY = 24 * 3600
  DEFAULT_MINIMUM_UPDATE_PERIOD = "P100Y"
  DEFAULT_PUBLISH_ADVANCE_IN_S = 7200
  EXTRA_TIME_AFTER_END_IN_S = 60
  
  UTC_HEAD_PATH = "dash/time.txt"
  
  PUBLISH_TIME = False
  
  
  def handle_request(host_name, url_parts, args, vod_conf_dir, content_dir, now=None, req=None, is_https=0):
      "Handle Apache request."
      dash_provider = DashProvider(host_name, url_parts, args, vod_conf_dir, content_dir, now, req, is_https)
      return dash_provider.handle_request()
  
  
  class DashProxyError(Exception):
      "Error in DashProxy."
  
  
  class DashSegmentNotAvailableError(DashProxyError):
      "Segment not available."
  
  
  def generate_period_data(mpd_data, now, cfg):
      """Generate an array of period data depending on current time (now) and tsbd. 0 gives one period with start=1000h.
  
      mpd_data is changed (minimumUpdatePeriod)."""
      # pylint: disable=too-many-locals
  
      nr_periods_per_hour = min(mpd_data['periodsPerHour'], 60)
      if nr_periods_per_hour not in (-1, 0, 1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60):
          raise Exception("Bad nr of periods per hour %d" % nr_periods_per_hour)
  
      seg_dur = mpd_data['segDuration']
      period_data = []
      ad_frequency = -1
      if mpd_data['insertAd'] > 0:
          ad_frequency = nr_periods_per_hour / mpd_data['xlinkPeriodsPerHour']
  
      if nr_periods_per_hour == -1:  # Just one period starting at at time start relative AST
          start = 0
          start_number = mpd_data['startNumber'] + start / seg_dur
          data = {'id': "p0", 'start': 'PT%dS' % start, 'startNumber': str(start_number),
                  'duration': seg_dur, 'presentationTimeOffset': "%d" % mpd_data['presentationTimeOffset'],
                  'start_s' : start}
          period_data.append(data)
      elif nr_periods_per_hour == 0:  # nrPeriodsPerHour == 0, make one old period but starting 1000h after AST
          start = 3600 * 1000
          data = {'id': "p0", 'start': 'PT%dS' % start, 'startNumber': "%d" % (start / seg_dur),
                  'duration': seg_dur, 'presentationTimeOffset': "%d" % start, 'start_s' : start}
          period_data.append(data)
      else:  # nr_periods_per_hour > 0
          period_duration = 3600 // nr_periods_per_hour
          half_period_duration = period_duration // 2
          minimum_update_period_s = (half_period_duration - 5)
          if cfg.seg_timeline or cfg.seg_timeline_nr:
              minimum_update_period_s = cfg.seg_duration
          minimum_update_period = "PT%dS" % minimum_update_period_s
          mpd_data['minimumUpdatePeriod'] = minimum_update_period
          this_period_nr = now // period_duration
          last_period_nr = (now + half_period_duration) // period_duration
          this_period_start = this_period_nr * period_duration
          first_period_nr = (now - mpd_data['timeShiftBufferDepthInS'] - seg_dur) // period_duration
          counter = 0
          for period_nr in range(first_period_nr, last_period_nr+1):
              start_time = period_nr * period_duration
              data = {'id' : "p%d" % period_nr, 'start' : 'PT%dS' % start_time,
                      'startNumber' : "%d" % (start_time/seg_dur), 'duration' : seg_dur,
                      'presentationTimeOffset' : period_nr*period_duration,
                      'start_s' : start_time}
              if mpd_data['etpPeriodsPerHour'] > 0:
                  # Check whether the early terminated feature is enabled or not.
                  # If yes, then proceed.
                  nr_etp_periods_per_hour = min(mpd_data['etpPeriodsPerHour'], 60)
                  # Limit the maximum value to 60, same as done for the period.
                  fraction_nr_periods_to_nr_etp = float(nr_periods_per_hour)/nr_etp_periods_per_hour
                  if fraction_nr_periods_to_nr_etp != int(fraction_nr_periods_to_nr_etp):
                      raise Exception("(Number of periods per hour/ Number of etp periods per hour) "
                                      "should be an integer.")
                  etp_duration = mpd_data['etpDuration']
                  if etp_duration == -1:
                      etp_duration = period_duration / 2
                      # Default value
                      # If no etpDuration is specified, then we take a default values, i.e, half of period duration.
                  if etp_duration > period_duration:
                      raise Exception("Duration of the early terminated period should be less that the duration of a "
                                      "regular period")
                  if period_nr % fraction_nr_periods_to_nr_etp == 0:
                      data['etpDuration'] = etp_duration
                      data['period_duration_s'] = etp_duration
  
              if mpd_data['mpdCallback'] > 0:
                  # Check whether the mpdCallback feature is enabled or not.
                  # If yes, then proceed.
                  nr_callback_periods_per_hour = min(mpd_data['mpdCallback'], 60)
                  # Limit the maximum value to 60, same as done for the period.
                  fraction_nr_periods_to_nr_callback = float(nr_periods_per_hour)/nr_callback_periods_per_hour
                  if fraction_nr_periods_to_nr_callback != int(fraction_nr_periods_to_nr_callback):
                      raise Exception("(Number of periods per hour/ Number of callback periods per hour) "
                                      "should be an integer.")
                  if period_nr % fraction_nr_periods_to_nr_callback == 0:
                      data['mpdCallback'] = 1
                      # If this period meets the condition, we raise a flag.
                      # We use the flag later to decide to put a mpdCallback element or not.
  
              period_data.append(data)
              if ad_frequency > 0 and ((period_nr % ad_frequency) == 0) and counter > 0:
                  period_data[counter - 1]['periodDuration'] = 'PT%dS' % period_duration
                  period_data[counter - 1]['period_duration_s'] = period_duration
              counter = counter + 1
              # print period_data
          for i, pdata in enumerate(period_data):
              if i != len(period_data) - 1: # not last periodDuration
                  if not pdata.has_key('period_duration_s'):
                      pdata['period_duration_s'] = period_duration
      return period_data
  
  
  def generate_response_with_xlink(response, cfg, filename, nr_periods_per_hour, nr_xlink_periods_per_hour, insert_ad):
      "Convert the normally created response into a response which has xlinks"
      # This functions has two functionality : 1.For MPD and 2.For PERIOD.
      # 1. When the normally created .mpd file is fed to this function, it removes periods and inserts xlink for the
      # corresponding periods at that place.
      # 2. When the xlink period is accessed, it extracts the corresponding period from .mpd file this function generates,
      # adds some information and returns the appropriate .xml document to the java client.
      # pylint: disable=too-many-locals, too-many-statements
      if cfg == ".mpd":
          root = ET.fromstring(response)
          period_id_all = []
          for child in root.findall('{urn:mpeg:dash:schema:mpd:2011}Period'):
              period_id_all.append(child.attrib['id'])
          # period_id_all = findall('Period id="([^"]*)"', response)
          # Find all period ids in the response file.
          one_xlinks_for_how_many_periods = nr_periods_per_hour / nr_xlink_periods_per_hour
          period_id_xlinks = [x for x in period_id_all if int(x[1:]) % one_xlinks_for_how_many_periods == 0]
          # Periods that will be replaced with links.
          base_url = findall('<BaseURL>([^"]*)</BaseURL>', response)
          counter = 0
          for period_id in period_id_all:
              # Start replacing only if this condition is met.
              start_pos_period = response.find(period_id) - 12
              # Find the position in the string file of the period that has be replaced
              end_pos_period = response.find("</Period>", start_pos_period) + 9
              # End position of the corresponding period.
              if period_id in period_id_xlinks:
                  counter += 1
                  original_period = response[start_pos_period:end_pos_period]
                  if insert_ad == 4:
                      xlink_period = '<Period xlink:href="http://vm1.dashif.org/dynamicxlink/invalid' \
                                     'url.php" xlink:actuate="onLoad" xmlns:xlink="http://www.w3.org/1999/xlink">' \
                                     '</Period>'
                  elif insert_ad == 3:
                      xlink_period = '<Period xlink:href="http://vm1.dashif.org/dynamicxlink/ad.php' \
                                     '?id=6_ad_twoperiods_withremote" xlink:actuate="onLoad" xmlns:xlink="http://www.' \
                                     'w3.org/1999/xlink"></Period>'
                  elif insert_ad == 2:
                      xlink_period = '<Period xlink:href="http://vm1.dashif.org/dynamicxlink/ad.php' \
                                     '?id=6_ad_twoperiods" xlink:actuate="onLoad" xmlns:xlink="http://www.w3.org/1999/' \
                                     'xlink"></Period>'
                  elif insert_ad == 1 or insert_ad == 5:
                      xlink_period = '<Period xlink:href="http://vm1.dashif.org/dynamicxlink/ad.php?' \
                                     'id=6_ad" xlink:actuate="onLoad" xmlns:xlink="http://www.w3.org/1999/xlink">' \
                                     '</Period>'
                  else:
                      xlink_period = '<Period xlink:href="%s%s+%s.period" xlink:actuate="onLoad" ' \
                                     'xmlns:xlink="http://www.w3.org/1999/xlink"></Period>' % (
                                         base_url[0], filename, period_id)
                  if insert_ad > 0 and counter == 1:  # This condition ensures that the first period in the mpd is not
                      # replaced when the ads are enabled.
                      response = insert_asset_identifier(response,
                                                         start_pos_period)  # Insert asset identifier,
                                                                            # if the the period is not replaced.
                      continue
                  if insert_ad == 5:  # Add additional content for the default content
                      start_pos_period_contents = original_period.find('>') + 1
                      xlink_period = xlink_period[:-9] + '\n<!--Default content that will be played if the xlink is not' \
                                                         ' able to load.-->' + original_period[start_pos_period_contents:]
                  response = response.replace(original_period, xlink_period)
              else:
                  if insert_ad > 0:
                      response = insert_asset_identifier(response, start_pos_period)
      else:
          # This will be done when ".period" file is being requested
          # Start manipulating the original period so that it looks like .period in the static xlink file.
          filename = filename.split('+')[1]
          # Second part of string has the period id.
          start_pos_xmlns = response.find("xmlns=")
          end_pos_xmlns = response.find('"', start_pos_xmlns + 7) + 1
          start_pos_period = response.find(filename[:-7]) - 12
          # Find the position in the string file of the period that has be replaced.
          end_pos_period = response.find("</Period>", start_pos_period) + 9
          # End position of the corresponding period.
          original_period = response[start_pos_period:end_pos_period]
          original_period = original_period.replace('">', '" ' + response[start_pos_xmlns:end_pos_xmlns] + '>', 1)
          # xml_intro = '<?xml version="1.0" encoding="utf-8"?>\n'
          # response = xml_intro + original_period
          response = original_period
      return response
  
  
  def insert_asset_identifier(response, start_pos_period):
      ad_pos = response.find(">", start_pos_period) + 1
      response = response[:ad_pos] + "\n<AssetIdentifier schemeIdUri=\"urn:org:dashif:asset-id:2013\" value=\"md:cid:" \
                                     "EIDR:10.5240%2f0EFB-02CD-126E-8092-1E49-W\"></AssetIdentifier>" + response[ad_pos:]
      return response
  
  
  class DashProvider(object):
      "Provide DASH manifest and segments."
  
      # pylint: disable=too-many-instance-attributes,too-many-arguments
  
      def __init__(self, host_name, url_parts, url_args, vod_conf_dir, content_dir, now=None, req=None, is_https=0):
          protocol = is_https and "https" or "http"
          self.base_url = "%s://%s/%s/" % (protocol, host_name, url_parts[0])  # The start. Adding other parts later.
          self.utc_head_url = "%s://%s/%s" % (protocol, host_name, UTC_HEAD_PATH)
          self.url_parts = url_parts[1:]
          self.url_args = url_args
          self.vod_conf_dir = vod_conf_dir
          self.content_dir = content_dir
          self.now_float = now  # float
          self.now = int(now)
          self.req = req
          self.new_tfdt_value = None
  
      def handle_request(self):
          "Handle the HTTP request."
          return self.parse_url()
  
      def error_response(self, msg):
          "Return a mod_python error response."
          if self.req:
              self.req.log_error("dash_proxy: [%s] %s" % ("/".join(self.url_parts[-3:]), msg))
          return {'ok': False, 'pl': msg + "\n"}
  
      # pylint:disable = too-many-locals, too-many-branches
      def parse_url(self):
          "Parse the absolute URL that is received in mod_python."
          cfg_processor = ConfigProcessor(self.vod_conf_dir, self.base_url)
          cfg_processor.process_url(self.url_parts, self.now)
          cfg = cfg_processor.getconfig()
          if cfg.ext == ".mpd" or cfg.ext == ".period":
              if cfg.ext == ".period":
                  mpd_filename = "%s/%s/%s" % (self.content_dir, cfg.content_name, cfg.filename.split('+')[0])
                  # Get the first part of the string only, which is the .manifest file name.
              else:
                  mpd_filename = "%s/%s/%s" % (self.content_dir, cfg.content_name, cfg.filename)
              mpd_input_data = cfg_processor.get_mpd_data()
              nr_xlink_periods_per_hour = min(mpd_input_data['xlinkPeriodsPerHour'], 60)
              nr_periods_per_hour = min(mpd_input_data['periodsPerHour'], 60)
              # See exception description for explanation.
              if nr_xlink_periods_per_hour > 0:
                  if nr_periods_per_hour == -1:
                      raise Exception("Xlinks can only be created for a multiperiod service.")
                  one_xlinks_for_how_many_periods = float(nr_periods_per_hour) / nr_xlink_periods_per_hour
                  if one_xlinks_for_how_many_periods != int(one_xlinks_for_how_many_periods):
                      raise Exception("(Number of periods per hour/ Number of xlinks per hour should be an integer.")
              if mpd_input_data['insertAd'] > 0 and nr_xlink_periods_per_hour < 0:
                  raise Exception("Insert ad option can only be used in conjuction with the xlink option. To use the "
                                  "insert ad option, also set use xlink_m in your url.")
              response = self.generate_dynamic_mpd(cfg, mpd_filename, mpd_input_data, self.now)
              if nr_xlink_periods_per_hour > 0:
                  response = generate_response_with_xlink(response, cfg.ext, cfg.filename, nr_periods_per_hour,
                                                          nr_xlink_periods_per_hour, mpd_input_data['insertAd'])
          elif cfg.ext == ".mp4":  # Init segment
              if self.now < cfg.availability_start_time_in_s - cfg.init_seg_avail_offset:
                  diff = (cfg.availability_start_time_in_s - cfg.init_seg_avail_offset) - self.now_float
                  response = self.error_response("Request for %s was %.1fs too early" % (cfg.filename, diff))
              else:
                  response = self.process_init_segment(cfg)
          elif cfg.ext in (".m4s", ".jpg"):  # Media segment or thumbnail
              if cfg.availability_time_offset_in_s == -1:
                  first_segment_ast = cfg.availability_start_time_in_s
              else:
                  first_segment_ast = cfg.availability_start_time_in_s + cfg.seg_duration - \
                                      cfg.availability_time_offset_in_s
  
              if self.now_float < first_segment_ast:
                  diff = first_segment_ast - self.now_float
                  response = self.error_response("Request %s before first seg AST. %.1fs too early" %
                                                 (cfg.filename, diff))
              elif cfg.availability_end_time is not None and \
                              self.now > cfg.availability_end_time + EXTRA_TIME_AFTER_END_IN_S:
                  diff = self.now_float - (cfg.availability_end_time + EXTRA_TIME_AFTER_END_IN_S)
                  response = self.error_response("Request for %s after AET. %.1fs too late" % (cfg.filename, diff))
              elif cfg.ext == ".m4s":
                  response = self.process_media_segment(cfg, self.now_float)
                  if len(cfg.multi_url) == 1:  # There is one specific baseURL with losses specified
                      a_var, b_var = cfg.multi_url[0].split("_")
                      dur1 = int(a_var[1:])
                      dur2 = int(b_var[1:])
                      total_dur = dur1 + dur2
                      num_loop = int(ceil(60.0 / (float(total_dur))))
                      now_mod_60 = self.now % 60
                      if a_var[0] == 'u' and b_var[0] == 'd':  # parse server up or down information
                          for i in range(num_loop):
                              if i * total_dur + dur1 < now_mod_60 <= (i + 1) * total_dur:
                                  response = self.error_response("BaseURL server down at %d" % (self.now))
                                  break
                      elif a_var[0] == 'd' and b_var[0] == 'u':
                          for i in range(num_loop):
                              if i * (total_dur) < now_mod_60 <= i * (total_dur) + dur1:
                                  response = self.error_response("BaseURL server down at %d" % (self.now))
                                  break
              else:  # cfg.ext == ".jpg"
                  response = self.process_thumbnail(cfg, self.now_float)
          else:
              response = "Unknown file extension: %s" % cfg.ext
          return response
  
      # pylint: disable=no-self-use
      def generate_dynamic_mpd(self, cfg, mpd_filename, in_data, now):
          "Generate the dynamic MPD."
          mpd_data = in_data.copy()
          if cfg.minimum_update_period_in_s is not None:
              mpd_data['minimumUpdatePeriod'] = seconds_to_iso_duration(cfg.minimum_update_period_in_s)
          else:
              mpd_data['minimumUpdatePeriod'] = DEFAULT_MINIMUM_UPDATE_PERIOD
          if cfg.media_presentation_duration is not None:
              mpd_data['mediaPresentationDuration'] = seconds_to_iso_duration(cfg.media_presentation_duration)
          mpd_data['timeShiftBufferDepth'] = seconds_to_iso_duration(cfg.timeshift_buffer_depth_in_s)
          mpd_data['timeShiftBufferDepthInS'] = cfg.timeshift_buffer_depth_in_s
          mpd_data['startNumber'] = cfg.adjusted_start_number
          mpd_data['publishTime'] = '%s' % make_timestamp(in_data['publishTime'])
          mpd_data['availabilityStartTime'] = '%s' % make_timestamp(in_data['availability_start_time_in_s'])
          mpd_data['duration'] = '%d' % in_data['segDuration']
          mpd_data['maxSegmentDuration'] = 'PT%dS' % in_data['segDuration']
          timescale = 1
          pto = 0
          mpd_data['presentationTimeOffset'] = cfg.adjusted_pto(pto, timescale)
          mpd_data['availabilityTimeOffset'] = '%f' % in_data['availability_time_offset_in_s']
          if mpd_data['suggested_presentation_delay_in_s'] is not None:
              spd = in_data['suggested_presentation_delay_in_s']
              mpd_data['suggestedPresentationDelay'] = \
                  seconds_to_iso_duration(spd)
          if in_data.has_key('availabilityEndTime'):
              mpd_data['availabilityEndTime'] = make_timestamp(in_data['availabilityEndTime'])
          if cfg.stop_time is not None and (now > cfg.stop_time):
              mpd_data['type'] = "static"
          mpd_proc_cfg = {'scte35Present': (cfg.scte35_per_minute > 0),
                          'continuous': in_data['continuous'],
                          'segtimeline': in_data['segtimeline'],
                          'segtimeline_nr': in_data['segtimeline_nr'],
                          'utc_timing_methods': cfg.utc_timing_methods,
                          'utc_head_url': self.utc_head_url,
                          'now': now}
          full_url = self.base_url + '/'.join(self.url_parts)
          mpmod = mpdprocessor.MpdProcessor(mpd_filename, mpd_proc_cfg, cfg,
                                            full_url)
          period_data = generate_period_data(mpd_data, now, cfg)
          mpmod.process(mpd_data, period_data)
          return mpmod.get_full_xml()
  
      def process_init_segment(self, cfg):
          "Read non-multiplexed or create muxed init segments."
  
          nr_reps = len(cfg.reps)
          if nr_reps == 1:  # Not muxed
              init_file = "%s/%s/%s/%s" % (self.content_dir, cfg.content_name, cfg.rel_path, cfg.filename)
              ilf = InitLiveFilter(init_file)
              data = ilf.filter()
          elif nr_reps == 2:  # Something that can be muxed
              com_path = "/".join(cfg.rel_path.split("/")[:-1])
              init1 = "%s/%s/%s/%s/%s" % (self.content_dir, cfg.content_name, com_path, cfg.reps[0]['id'], cfg.filename)
              init2 = "%s/%s/%s/%s/%s" % (self.content_dir, cfg.content_name, com_path, cfg.reps[1]['id'], cfg.filename)
              muxed_inits = segmentmuxer.MultiplexInits(init1, init2)
              data = muxed_inits.construct_muxed()
          else:
              data = self.error_response("Bad nr of representations: %d" % nr_reps)
          return data
  
      def process_media_segment(self, cfg, now_float):
          """Process media segment. Return error response if timing is not OK.
  
          Assumes that segment_ast = (seg_nr+1-startNumber)*seg_dur + ast."""
  
          # pylint: disable=too-many-locals
  
          def get_timescale(cfg):
              "Get timescale for the current representation."
              timescale = None
              curr_rep_id = cfg.rel_path
              for rep in cfg.reps:
                  if rep['id'] == curr_rep_id:
                      timescale = rep['timescale']
                      break
              return timescale
  
          seg_dur = cfg.seg_duration
          seg_name = cfg.filename
          seg_base, seg_ext = splitext(seg_name)
          timescale = get_timescale(cfg)
          if seg_base[0] == 't':
              # TODO. Make a more accurate test here that the timestamp is a correct one
              seg_nr = int(round(float(seg_base[1:]) / seg_dur / timescale))
          else:
              seg_nr = int(seg_base)
          seg_start_nr = cfg.start_nr == -1 and 1 or cfg.adjusted_start_number
          if seg_nr < seg_start_nr:
              return self.error_response("Request for segment %d before first %d" % (seg_nr, seg_start_nr))
          stop_number = cfg.stop_number
          if stop_number and seg_nr >= stop_number:
              return self.error_response("Beyond last segment %d" % stop_number)
          if len(cfg.last_segment_numbers) > 0:
              very_last_segment = cfg.last_segment_numbers[-1]
              if seg_nr > very_last_segment:
                  return self.error_response("Request for segment %d beyond last (%d)" % (seg_nr, very_last_segment))
          lmsg = seg_nr in cfg.last_segment_numbers
          # print cfg.last_segment_numbers
          timescale = 1
          media_time_at_ast = cfg.adjusted_pto(0, timescale)
          seg_time = (seg_nr - seg_start_nr) * seg_dur + media_time_at_ast
          seg_ast = (seg_time + seg_dur - media_time_at_ast) + \
                    cfg.availability_start_time_in_s
  
          if cfg.availability_time_offset_in_s != -1:  # - 1 is infinity
              if now_float < seg_ast - cfg.availability_time_offset_in_s:
                  return self.error_response("Request for %s was %.1fs too early" % (seg_name, seg_ast - now_float))
              # If stop_number is not None, the manifest will become static
              if ((now_float > seg_ast + seg_dur +
                      cfg.timeshift_buffer_depth_in_s) and not stop_number):
                  diff = now_float - (seg_ast + seg_dur + cfg.timeshift_buffer_depth_in_s)
                  return self.error_response("Request for %s was %.1fs too late" % (seg_name, diff))
  
          loop_duration = cfg.seg_duration * cfg.vod_nr_segments_in_loop
          nr_loops_done, time_in_loop = divmod(seg_time, loop_duration)
          offset_at_loop_start = nr_loops_done * loop_duration
          seg_nr_in_loop = time_in_loop // seg_dur
          vod_nr = seg_nr_in_loop + cfg.vod_first_segment_in_loop
          assert 0 <= vod_nr - cfg.vod_first_segment_in_loop < cfg.vod_nr_segments_in_loop
          rel_path = cfg.rel_path
          nr_reps = len(cfg.reps)
          if nr_reps == 1:  # Not muxed
              seg_content = self.filter_media_segment(cfg, cfg.reps[0], rel_path, vod_nr, seg_nr, seg_ext,
                                                      offset_at_loop_start, lmsg)
          else:
              rel_path_parts = rel_path.split("/")
              common_path_parts = rel_path_parts[:-1]
              rel_path1 = "/".join(common_path_parts + [cfg.reps[0]['id']])
              rel_path2 = "/".join(common_path_parts + [cfg.reps[1]['id']])
              seg1 = self.filter_media_segment(cfg, cfg.reps[0], rel_path1, vod_nr, seg_nr, seg_ext,
                                               offset_at_loop_start, lmsg)
              seg2 = self.filter_media_segment(cfg, cfg.reps[1], rel_path2, vod_nr, seg_nr, seg_ext,
                                               offset_at_loop_start, lmsg)
              muxed = segmentmuxer.MultiplexMediaSegments(data1=seg1, data2=seg2)
              seg_content = muxed.mux_on_sample_level()
          return seg_content
  
      # pylint: disable=too-many-arguments
      def filter_media_segment(self, cfg, rep, rel_path, vod_nr, seg_nr, seg_ext, offset_at_loop_start, lmsg):
          "Filter an actual media segment by using time-scale from init segment."
          media_seg_file = join(self.content_dir, cfg.content_name, rel_path, "%d%s" % (vod_nr, seg_ext))
          timescale = rep['timescale']
          scte35_per_minute = (rep['content_type'] == 'video') and cfg.scte35_per_minute or 0
          is_ttml = rep['content_type'] == 'subtitles'
          seg_filter = MediaSegmentFilter(media_seg_file, seg_nr, cfg.seg_duration, offset_at_loop_start, lmsg, timescale,
                                          scte35_per_minute, rel_path,
                                          is_ttml,
                                          insert_sidx=cfg.insert_sidx)
          seg_content = seg_filter.filter()
          self.new_tfdt_value = seg_filter.get_tfdt_value()
          return seg_content
  
      def process_thumbnail(self, cfg, now_float):
          """Process thumbnail. Return error response if timing is not OK.
  
          Assumes that segment_ast = (seg_nr+1-startNumber)*seg_dur."""
  
          # pylint: disable=too-many-locals
  
          def get_timescale(cfg):
              "Get timescale for the current representation."
              timescale = None
              curr_rep_id = cfg.rel_path
              for rep in cfg.reps:
                  if rep['id'] == curr_rep_id:
                      timescale = rep['timescale']
                      break
              return timescale
  
          seg_dur = cfg.seg_duration
          seg_name = cfg.filename
          seg_base, seg_ext = splitext(seg_name)
          timescale = get_timescale(cfg)
          if seg_base[0] == 't':
              # TODO. Make a more accurate test here that the timestamp is a correct one
              seg_nr = int(round(float(seg_base[1:]) / seg_dur / timescale))
          else:
              seg_nr = int(seg_base)
          seg_start_nr = cfg.start_nr == -1 and 1 or cfg.start_nr
          if seg_nr < seg_start_nr:
              return self.error_response("Request for segment %d before first %d" % (seg_nr, seg_start_nr))
          if len(cfg.last_segment_numbers) > 0:
              very_last_segment = cfg.last_segment_numbers[-1]
              if seg_nr > very_last_segment:
                  return self.error_response("Request for segment %d beyond last (%d)" % (seg_nr, very_last_segment))
          lmsg = seg_nr in cfg.last_segment_numbers
          # print cfg.last_segment_numbers
          seg_time = (seg_nr - seg_start_nr) * seg_dur + cfg.availability_start_time_in_s
          seg_ast = seg_time + seg_dur
  
          if cfg.availability_time_offset_in_s != -1:  # -1 is infinity
              if now_float < seg_ast - cfg.availability_time_offset_in_s:
                  return self.error_response("Request for %s was %.1fs too early" % (seg_name, seg_ast - now_float))
              if ((now_float > seg_ast + seg_dur +
                      cfg.timeshift_buffer_depth_in_s) and not stop_number):
                  diff = now_float - (seg_ast + seg_dur + cfg.timeshift_buffer_depth_in_s)
                  return self.error_response("Request for %s was %.1fs too late" % (seg_name, diff))
  
          time_since_ast = seg_time - cfg.availability_start_time_in_s
          loop_duration = cfg.seg_duration * cfg.vod_nr_segments_in_loop
          nr_loops_done, time_in_loop = divmod(time_since_ast, loop_duration)
          seg_nr_in_loop = time_in_loop // seg_dur
          vod_nr = seg_nr_in_loop + cfg.vod_first_segment_in_loop
          assert 0 <= vod_nr - cfg.vod_first_segment_in_loop < cfg.vod_nr_segments_in_loop
          rel_path = cfg.rel_path
          thumb_path = join(self.content_dir, cfg.content_name, rel_path,
                          "%d%s" % (vod_nr, seg_ext))
          with open(thumb_path, 'rb') as ifh:
              seg_content = ifh.read()
          return seg_content