Exemplo n.º 1
0
    def handle_query(self, query, tsn):
        mname = False
        if 'Command' in query and len(query['Command']) >= 1:

            command = query['Command'][0]

            # If we are looking at the root container
            if (command == 'QueryContainer' and
                (not 'Container' in query or query['Container'][0] == '/')):
                self.root_container()
                return

            if 'Container' in query:
                # Dispatch to the container plugin
                basepath = query['Container'][0].split('/')[0]
                if self.do_command(query, command, basepath, tsn):
                    return

            elif command == 'QueryItem':
                path = query.get('Url', [''])[0]
                splitpath = [x for x in unquote_plus(path).split('/') if x]
                if splitpath and not '..' in splitpath:
                    if self.do_command(query, command, splitpath[0], tsn):
                        return

            elif (command == 'QueryFormats' and 'SourceFormat' in query
                  and query['SourceFormat'][0].startswith('video')):
                if config.is_ts_capable(tsn):
                    self.send_xml(VIDEO_FORMATS_TS)
                else:
                    self.send_xml(VIDEO_FORMATS)
                return

            elif command == 'QueryServer':
                self.send_xml(SERVER_INFO)
                return

            elif command in ('GetActiveTransferCount', 'GetTransferStatus'):
                plugin = GetPlugin('video')
                if hasattr(plugin, command):
                    method = getattr(plugin, command)
                    method(self, query)
                    return True

            elif command in ('FlushServer', 'ResetServer'):
                # Does nothing -- included for completeness
                self.send_response(200)
                self.send_header('Content-Length', '0')
                self.end_headers()
                self.wfile.flush()
                return

        # If we made it here it means we couldn't match the request to
        # anything.
        self.unsupported(query)
Exemplo n.º 2
0
    def handle_query(self, query, tsn):
        if 'Command' in query and len(query['Command']) >= 1:

            command = query['Command'][0]

            # If we are looking at the root container
            if (command == 'QueryContainer' and
                    (not 'Container' in query or query['Container'][0] == '/')):
                self.root_container()
                return

            if 'Container' in query:
                # Dispatch to the container plugin
                basepath = query['Container'][0].split('/')[0]
                if self.do_command(query, command, basepath, tsn):
                    return

            elif command == 'QueryItem':
                path = query.get('Url', [''])[0]
                splitpath = [x for x in unquote_plus(path).split('/') if x]
                if splitpath and not '..' in splitpath:
                    if self.do_command(query, command, splitpath[0], tsn):
                        return

            elif (command == 'QueryFormats' and 'SourceFormat' in query and
                  query['SourceFormat'][0].startswith('video')):
                if config.is_ts_capable(tsn):
                    self.send_xml(VIDEO_FORMATS_TS)
                else:
                    self.send_xml(VIDEO_FORMATS)
                return

            elif command == 'QueryServer':
                self.send_xml(SERVER_INFO)
                return

            elif command in ('GetActiveTransferCount', 'GetTransferStatus'):
                plugin = GetPlugin('video')
                if hasattr(plugin, command):
                    method = getattr(plugin, command)
                    method(self, query)
                    return True

            elif command in ('FlushServer', 'ResetServer'):
                # Does nothing -- included for completeness
                self.send_response(200)
                self.send_header('Content-Length', '0')
                self.end_headers()
                self.wfile.flush()
                return

        # If we made it here it means we couldn't match the request to
        # anything.
        self.unsupported(query)
Exemplo n.º 3
0
    def use_ts(self, tsn, file_path):
        if config.is_ts_capable(tsn):
            if file_path[-5:].lower() == '.tivo':
                try:
                    flag = file(file_path).read(8)
                except:
                    return False
                if ord(flag[7]) & 0x20:
                    return True
            elif config.has_ts_flag():
                return True

        return False
Exemplo n.º 4
0
    def use_ts(self, tsn, file_path):
        if config.is_ts_capable(tsn):
            if file_path[-5:].lower() == '.tivo':
                try:
                    flag = file(file_path).read(8)
                except:
                    return False
                if ord(flag[7]) & 0x20:
                    return True
            elif config.has_ts_flag():
                return True

        return False
Exemplo n.º 5
0
    def use_ts(self, tsn, file_path):
        if config.is_ts_capable(tsn):
            ext = os.path.splitext(file_path)[1].lower()
            if ext == '.tivo':
                try:
                    flag = file(file_path).read(8)
                except:
                    return False
                if ord(flag[7]) & 0x20:
                    return True
            else:
                opt = config.get_ts_flag()
                if ((opt == 'auto' and ext in LIKELYTS) or
                    (opt in ['true', 'yes', 'on'])):
                    return True

        return False
Exemplo n.º 6
0
    def use_ts(self, tsn, file_path):
        if config.is_ts_capable(tsn):
            ext = os.path.splitext(file_path)[1].lower()
            if ext == '.tivo':
                try:
                    flag = file(file_path).read(8)
                except:
                    return False
                if ord(flag[7]) & 0x20:
                    return True
            else:
                opt = config.get_ts_flag()
                if ((opt == 'auto' and ext in LIKELYTS)
                        or (opt in ['true', 'yes', 'on'])):
                    return True

        return False
Exemplo n.º 7
0
    def get_tivo_file(self, tivoIP, url, mak, togo_path):
        # global status
        status[url].update({'running': True, 'queued': False})

        outfile = self.get_out_file(url, tivoIP, togo_path)

        auth_handler.add_password('TiVo DVR', url, 'tivo', mak)
        try:
            ts_format = False
            if status[url]['ts_format'] and config.is_ts_capable(
                    config.tivos_by_ip(tivoIP)):
                ts_format = True
                handle = self.tivo_open(url + '&Format=video/x-tivo-mpeg-ts')
            else:
                handle = self.tivo_open(url)
        except Exception, msg:
            status[url]['running'] = False
            status[url]['error'] = str(msg)
            return
Exemplo n.º 8
0
            data = []
            tivoIP = ''
            TotalItems = 0
            ItemStart = 0
            ItemCount = 0
            title = ''

        t = Template(NPL_TEMPLATE, filter=EncodeUnicode)
        t.escape = escape
        t.quote = quote
        t.folder = folder
        t.status = status
        if tivoIP in queue:
            t.queue = queue[tivoIP]
        t.has_tivodecode = has_tivodecode
        t.togo_mpegts = config.is_ts_capable(tsn)
        t.tname = tivo_name
        t.tivoIP = tivoIP
        t.container = handler.cname
        t.data = data
        t.len = len
        t.TotalItems = getint(TotalItems)
        t.ItemStart = getint(ItemStart)
        t.ItemCount = getint(ItemCount)
        t.FirstAnchor = quote(FirstAnchor)
        t.shows_per_page = shows_per_page
        t.title = title
        handler.send_html(str(t), refresh='300')

    def get_tivo_file(self, tivoIP, url, mak, togo_path):
        # global status
Exemplo n.º 9
0
    def ToGo(handler, query):
        """
        HTTP command handler to download a set of recordings from a Tivo.

        If there is already a thread downloading recordings from that Tivo,
        the new recordings will be appended to the existing download task
        list for that Tivo, otherwise a new task list will be created and
        a thread spawned to process it.
        """
        togo_path = config.get_togo('path')
        for name, data in config.getShares():
            if togo_path == name:
                togo_path = data.get('path')
        if togo_path:
            tivoIP = query['TiVo'][0]
            tsn = config.tivos_by_ip(tivoIP)
            tivo_name = config.tivos[tsn].get('name', tivoIP)
            tivo_mak = config.get_tsn('tivo_mak', tsn)
            urls = query.get('Url', [])
            decode = 'decode' in query
            save = 'save' in query
            ts_format = 'ts_format' in query and config.is_ts_capable(tsn)
            sortable = bool(config.get_togo('sortable_names', False))
            for theurl in urls:

                status = {'url': theurl,
                          'running': False,
                          'queued': True,
                          'finished': False,
                          'showinfo': showinfo[theurl], # metadata information about the show
                          'decode': decode,         # decode the downloaded tivo file
                          'save': save,             # save the tivo file's metadata to a .txt file
                          'ts_format': ts_format,   # download using transport stream otherwise program stream
                          'sortable': sortable,     # name saved tivo file in a sortable manner
                          'error': '',
                          'rate': 0,
                          'size': 0,
                          'retry': 0,
                          'download_attempts': [],  # information about each download attempt (used for sync error log)
                          'ts_error_packets': [],   # list of TS packets w/ sync lost as tuples (packet_no, count)
                          'best_attempt_index': None, # index into download_attempts of the attempt w/ fewest errors
                          'best_file': '',
                          'best_error_count': None} # count of TS packets lost (sync byte was wrong) in 'best_file'

                with active_tivos_lock:
                    if tivoIP in active_tivos:
                        with active_tivos[tivoIP]['lock']:
                            active_tivos[tivoIP]['queue'].append(status)
                    else:
                        # we have to add authentication info again because the
                        # download netloc may be different from that used to
                        # retrieve the list of recordings (and in fact the port
                        # is different, 443 to get the NPL and 80 for downloading).
                        auth_handler.add_password('TiVo DVR', urlsplit(theurl).netloc, 'tivo', tivo_mak)
                        logger.debug('ToGo: add password for TiVo DVR netloc: %s', urlsplit(theurl).netloc)

                        active_tivos[tivoIP] = {'tivoIP': tivoIP,
                                                'lock': RLock(),
                                                'thread': None,
                                                'tivo_name': tivo_name,
                                                'mak': tivo_mak,
                                                'dest_path': togo_path,
                                                'fn_format_info': {'episode': config.get_togo('episode_fn'),
                                                                   'movie': config.get_togo('movie_fn')
                                                                  },
                                                'ts_error_mode': config.get_togo('ts_error_mode', 'ignore'),
                                                'ts_max_retries': int(config.get_togo('ts_max_retries', 0)),
                                                'queue': [status]}

                        active_tivos[tivoIP]['thread'] = TivoDownload(tivoIP, active_tivos, active_tivos_lock, tivo_open)
                        active_tivos[tivoIP]['thread'].start()

                logger.info('[%s] Queued "%s" for transfer to %s',
                            time.strftime('%d/%b/%Y %H:%M:%S'),
                            unquote(theurl), togo_path)
            urlstring = '<br>'.join([unquote(x) for x in urls])
            message = TRANS_QUEUE % (urlstring, togo_path)
        else:
            message = MISSING
        handler.redir(message, 5)
Exemplo n.º 10
0
    def NPL(handler, query):
        """
        ToGo.NPL returns an html page displaying the now playing list (NPL)
        from a particular TiVo device.
        The query may specify:
        - TiVo: the IPv4 address of the TiVo whose NPL is to be retrieved
        - ItemCount: the number of shows/folders to put on the page (default: 50, max: 50)
        - AnchorItem: the url identifying the 1st item in the retrieved list (default 1st item in folder)
        - AnchorOffset: the offset from the AnchorItem to start the retrieval from (default 0)
        - SortOrder:
        - Recurse:
        """

        def getint(thing):
            try:
                result = int(thing)
            except:                             # pylint: disable=bare-except
                result = 0
            return result


        shows_per_page = 50 # Change this to alter the number of shows returned (max is 50)
        if 'ItemCount' in query:
            shows_per_page = int(query['ItemCount'][0])

        if shows_per_page > 50:
            shows_per_page = 50

        folder = ''
        FirstAnchor = ''
        has_tivodecode = bool(config.get_bin('tivodecode'))
        has_tivolibre = bool(config.get_bin('tivolibre'))

        if 'TiVo' in query:
            tivoIP = query['TiVo'][0]
            try:
                tsn = config.tivos_by_ip(tivoIP)
                attrs = config.tivos[tsn]
                tivo_name = attrs.get('name', tivoIP)
                tivo_mak = config.get_tsn('tivo_mak', tsn)
            except config.Error as e:
                logger.error('NPL: %s', e)
                t = Template(ERROR_TEMPLATE)
                t.e = e
                t.additional_info = 'Your browser may have cached an old page'
                handler.send_html(str(t))
                return

            protocol = attrs.get('protocol', 'https')
            ip_port = '%s:%d' % (tivoIP, attrs.get('port', 443))
            path = attrs.get('path', DEFPATH)
            baseurl = '%s://%s%s' % (protocol, ip_port, path)
            theurl = baseurl
            if 'Folder' in query:
                folder = query['Folder'][0]
                theurl = urljoin(theurl, folder)
            theurl += '&ItemCount=%d' % shows_per_page
            if 'AnchorItem' in query:
                theurl += '&AnchorItem=' + quote(query['AnchorItem'][0])
            if 'AnchorOffset' in query:
                theurl += '&AnchorOffset=' + query['AnchorOffset'][0]
            if 'SortOrder' in query:
                theurl += '&SortOrder=' + query['SortOrder'][0]
            if 'Recurse' in query:
                theurl += '&Recurse=' + query['Recurse'][0]

            if (theurl not in tivo_cache or
                    (time.time() - tivo_cache[theurl]['thepage_time']) >= 60):
                # if page is not cached or old then retrieve it
                auth_handler.add_password('TiVo DVR', ip_port, 'tivo', tivo_mak)
                logger.debug('NPL: (1) add password for TiVo DVR netloc: %s', ip_port)
                try:
                    logger.debug("NPL.theurl: %s", theurl)
                    with tivo_open(theurl) as page:
                        tivo_cache[theurl] = {'thepage': minidom.parse(page),
                                              'thepage_time': time.time()}
                except IOError as e:
                    handler.redir(UNABLE % (tivoIP, html.escape(str(e))), 10)
                    return

            xmldoc = tivo_cache[theurl]['thepage']
            items = xmldoc.getElementsByTagName('Item')

            TotalItems = tag_data(xmldoc, 'TiVoContainer/Details/TotalItems')
            ItemStart = tag_data(xmldoc, 'TiVoContainer/ItemStart')
            ItemCount = tag_data(xmldoc, 'TiVoContainer/ItemCount')
            title = tag_data(xmldoc, 'TiVoContainer/Details/Title')
            if items:
                FirstAnchor = tag_data(items[0], 'Links/Content/Url')

            data = []
            for item in items:
                entry = {}
                for tag in ('CopyProtected', 'ContentType'):
                    value = tag_data(item, 'Details/' + tag)
                    if value:
                        entry[tag] = value
                if entry['ContentType'].startswith('x-tivo-container'):
                    entry['Url'] = tag_data(item, 'Links/Content/Url')
                    entry['Title'] = tag_data(item, 'Details/Title')
                    entry['TotalItems'] = tag_data(item, 'Details/TotalItems')
                    lc = tag_data(item, 'Details/LastCaptureDate')
                    if not lc:
                        lc = tag_data(item, 'Details/LastChangeDate')
                    entry['LastChangeDate'] = time.strftime('%b %d, %Y',
                                                            time.localtime(int(lc, 16)))
                else:
                    keys = {'Icon':         'Links/CustomIcon/Url',
                            'Url':          'Links/Content/Url',
                            'Details':      'Links/TiVoVideoDetails/Url',
                            'SourceSize':   'Details/SourceSize',
                            'Duration':     'Details/Duration',
                            'CaptureDate':  'Details/CaptureDate'}
                    for key in keys:
                        value = tag_data(item, keys[key])
                        if value:
                            entry[key] = value

                    if 'SourceSize' in entry:
                        rawsize = entry['SourceSize']
                        entry['SourceSize'] = metadata.human_size(rawsize)

                    if 'Duration' in entry:
                        dur = getint(entry['Duration']) // 1000
                        entry['Duration'] = ('%d:%02d:%02d' %
                                             (dur // 3600, (dur % 3600) // 60, dur % 60))

                    if 'CaptureDate' in entry:
                        entry['CaptureDate'] = time.strftime('%b %d, %Y',
                                                             time.localtime(int(entry['CaptureDate'], 16)))

                    dnld_url = entry['Url']
                    # the tivo download url seems to always be absolute, so is this necessary?
                    # I'm commenting it out -mjl 7/23/2017
                    #dnld_url = urljoin(baseurl, dnld_url)
                    if not dnld_url in showinfo:
                        showinfo[dnld_url] = ShowInfo()
                        showinfo[dnld_url].from_tivo_container_item(item)

                    entry.update(showinfo[dnld_url].get_old_basicmeta())

                data.append(entry)
        else:
            data = []
            tivoIP = ''
            TotalItems = 0
            ItemStart = 0
            ItemCount = 0
            title = ''
            tsn = ''
            tivo_name = ''

        t = Template(NPL_TEMPLATE)
        t.quote = quote
        t.folder = folder
        t.urlstatus = ToGo.get_urlstatus(tivoIP)
        t.has_tivodecode = has_tivodecode
        t.has_tivolibre = has_tivolibre
        t.togo_mpegts = config.is_ts_capable(tsn)
        t.tname = tivo_name
        t.tivoIP = tivoIP
        t.container = handler.cname
        t.data = data
        t.len = len
        t.TotalItems = getint(TotalItems)
        t.ItemStart = getint(ItemStart)
        t.ItemCount = getint(ItemCount)
        t.FirstAnchor = quote(FirstAnchor)
        t.shows_per_page = shows_per_page
        t.title = title
        handler.send_html(str(t), refresh='300')
Exemplo n.º 11
0
Arquivo: togo.py Projeto: myeh/pytivo
        else:
            data = []
            tivoIP = ''
            TotalItems = 0
            ItemStart = 0
            ItemCount = 0
            title = ''

        t = Template(NPL_TEMPLATE, filter=EncodeUnicode)
        t.quote = quote
        t.folder = folder
        t.status = status
        if tivoIP in queue:
            t.queue = queue[tivoIP]
        t.has_tivodecode = has_tivodecode
        t.togo_mpegts = config.is_ts_capable(tsn)
        t.tname = tivo_name
        t.tivoIP = tivoIP
        t.container = handler.cname
        t.data = data
        t.len = len
        t.TotalItems = getint(TotalItems)
        t.ItemStart = getint(ItemStart)
        t.ItemCount = getint(ItemCount)
        t.FirstAnchor = quote(FirstAnchor)
        t.shows_per_page = shows_per_page
        t.title = title
        handler.send_html(str(t), refresh='300')

    def get_tivo_file(self, tivoIP, url, mak, togo_path):
        # global status
Exemplo n.º 12
0
    def ToGo(self, handler, query):
        togo_path = config.get_server('togo_path')
        for name, data in config.getShares():
            if togo_path == name:
                togo_path = data.get('path')
        if togo_path:
            tivoIP = query['TiVo'][0]
            tsn = config.tivos_by_ip(tivoIP)
            tivo_mak = config.get_tsn('tivo_mak', tsn)
            urls = query.get('Url', [])
            decode = 'decode' in query
            save = 'save' in query

            if 'postprocess' in query:
                postprocess = query['postprocess'][0]
            else:
                postprocess = config.get_server('vrd_post_processing')

            if 'postprocess_profile' in query:
                postprocess_profile = query['postprocess_profile'][0]
            else:
                postprocess_profile = config.get_server('vrd_profile', '')

            postprocess_decrypt = 'postprocess_decrypt' in query
            if not postprocess_decrypt:
                try:
                    postprocess_decrypt = config.config.getboolean(
                        'Server', 'vrd_decrypt_qsf')
                except:
                    postprocess_decrypt = False

            postprocess_delete = 'postprocess_delete' in query
            if not postprocess_delete:
                try:
                    postprocess_delete = config.config.getboolean(
                        'Server', 'vrd_delete_on_success')
                except:
                    postprocess_delete = False

            ts_format = 'ts_format' in query and config.is_ts_capable(tsn)
            for theurl in urls:
                if theurl in status:
                    del status[theurl]

                status[theurl] = {
                    'running':
                    False,
                    'status':
                    '',
                    'error':
                    '',
                    'rate':
                    0,
                    'percent':
                    0,
                    'queued':
                    True,
                    'size':
                    0,
                    'postprocessing':
                    False,
                    'finished':
                    False,
                    'decode':
                    decode,
                    'save':
                    save,
                    'ts_format':
                    ts_format,
                    'postprocess':
                    postprocess,
                    'postprocess_profile':
                    postprocess_profile,
                    'postprocess_decrypt':
                    postprocess_decrypt,
                    'postprocess_delete':
                    postprocess_delete,
                    'retry':
                    0,
                    'ts_max_retries':
                    int(config.get_server('togo_ts_max_retries', 0)),
                    'ts_error_count':
                    0,
                    'best_file':
                    '',
                    'best_error_count':
                    0
                }
                if tivoIP in queue:
                    queue[tivoIP].append(theurl)
                else:
                    queue[tivoIP] = [theurl]
                    thread.start_new_thread(
                        ToGo.process_queue,
                        (self, tivoIP, tivo_mak, togo_path))
                logger.info('[%s] Queued "%s" for transfer to %s' %
                            (time.strftime('%d/%b/%Y %H:%M:%S'),
                             unquote(theurl), togo_path))
            urlstring = '<br>'.join(
                [unicode(unquote(x), 'utf-8') for x in urls])
            message = TRANS_QUEUE % (urlstring, togo_path)
        else:
            message = MISSING
        handler.redir(message, 5)
Exemplo n.º 13
0
    def get_out_file(self, url, tivoIP, togo_path):
        # Use TiVo Desktop style naming
        if url in basic_meta:
            if 'title' in basic_meta[url]:
                title = basic_meta[url]['title']

                episodeTitle = ''
                if 'episodeTitle' in basic_meta[url]:
                    episodeTitle = basic_meta[url]['episodeTitle']

                recordDate = datetime.now()
                if 'recordDate' in basic_meta[url]:
                    recordDate = datetime.fromtimestamp(
                        int(basic_meta[url]['recordDate'], 0), pytz.utc)

                callSign = ''
                if 'callsign' in basic_meta[url]:
                    callSign = basic_meta[url]['callsign']

                count = 1
                while True:
                    fileName = title

                    try:
                        sortable = config.config.getboolean(
                            'Server', 'togo_sortable_names')
                    except:
                        sortable = False

                    if sortable == True:
                        fileName += ' - '
                        fileName += recordDate.strftime('%Y-%m-%d')

                        if len(episodeTitle):
                            fileName += ' - \'\'' + episodeTitle + '\'\''

                        if len(callSign):
                            fileName += ' (' + callSign + ')'
                    else:
                        if len(episodeTitle):
                            fileName += ' - \'\'' + episodeTitle + '\'\''

                        fileName += ' (Recorded '
                        fileName += recordDate.strftime('%b %d, %Y')
                        if len(callSign):
                            fileName += ', ' + callSign
                        fileName += ')'

                    ts = status[url]['ts_format'] and config.is_ts_capable(
                        config.tivos_by_ip(tivoIP))
                    if not status[url]['decode']:
                        if ts:
                            fileName += ' (TS)'
                        else:
                            fileName += ' (PS)'

                    if count > 1:
                        fileName += ' (%d)' % count

                    if status[url]['decode']:
                        if ts:
                            fileName += '.ts'
                        else:
                            fileName += '.mpg'
                    else:
                        fileName += '.tivo'

                    for ch in BADCHAR:
                        fileName = fileName.replace(ch, BADCHAR[ch])

                    if os.path.isfile(os.path.join(togo_path, fileName)):
                        count += 1
                        continue

                    return os.path.join(togo_path, fileName)

            # If we get here then use old style naming
            parse_url = urlparse.urlparse(url)

            name = unicode(unquote(parse_url[2]),
                           'utf-8').split('/')[-1].split('.')
            try:
                id = unquote(parse_url[4]).split('id=')[1]
                name.insert(-1, ' - ' + id)
            except:
                pass
            ts = status[url]['ts_format'] and config.is_ts_capable(
                config.tivos_by_ip(tivoIP))
            if status[url]['decode']:
                if ts:
                    name[-1] = 'ts'
                else:
                    name[-1] = 'mpg'
            else:
                if ts:
                    name.insert(-1, ' (TS)')
                else:
                    name.insert(-1, ' (PS)')

            nameHold = name
            name.insert(-1, '.')

            count = 2
            newName = name
            while (os.path.isfile(os.path.join(togo_path, ''.join(newName)))):
                newName = nameHold
                newName.insert(-1, ' (%d)' % count)
                newName.insert(-1, '.')
                count += 1

            name = newName
            name = ''.join(name)
            for ch in BADCHAR:
                name = name.replace(ch, BADCHAR[ch])

            return os.path.join(togo_path, name)
Exemplo n.º 14
0
    def NPL(self, handler, query):
        global basic_meta
        shows_per_page = 50 # Change this to alter the number of shows returned
        folder = ''
        has_tivodecode = bool(config.get_bin('tivodecode'))

        if 'TiVo' in query:
            tivoIP = query['TiVo'][0]
            tsn = config.tivos_by_ip(tivoIP)

            togo_mpegts = config.is_ts_capable(tsn)
            useragent = handler.headers.getheader('User-Agent', '')

            tivo_name = config.tivo_names[tsn]
            tivo_mak = config.get_tsn('tivo_mak', tsn)
            theurl = ('https://' + tivoIP +
                      '/TiVoConnect?Command=QueryContainer&ItemCount=' +
                      str(shows_per_page) + '&Container=/NowPlaying')
            if 'Folder' in query:
                folder += query['Folder'][0]
                theurl += '/' + folder
            if 'AnchorItem' in query:
                theurl += '&AnchorItem=' + quote(query['AnchorItem'][0])
            if 'AnchorOffset' in query:
                theurl += '&AnchorOffset=' + query['AnchorOffset'][0]

            if (theurl not in tivo_cache or
                (time.time() - tivo_cache[theurl]['thepage_time']) >= 60):
                # if page is not cached or old then retreive it
                auth_handler.add_password('TiVo DVR', tivoIP, 'tivo', tivo_mak)
                try:
                    page = self.tivo_open(theurl)
                except IOError, e:
                    handler.redir(UNABLE % tivoIP, 10)
                    return
                tivo_cache[theurl] = {'thepage': minidom.parse(page),
                                      'thepage_time': time.time()}
                page.close()

            xmldoc = tivo_cache[theurl]['thepage']
            items = xmldoc.getElementsByTagName('Item')
            TotalItems = tag_data(xmldoc, 'TiVoContainer/Details/TotalItems')
            ItemStart = tag_data(xmldoc, 'TiVoContainer/ItemStart')
            ItemCount = tag_data(xmldoc, 'TiVoContainer/ItemCount')
            FirstAnchor = tag_data(items[0], 'Links/Content/Url')
            title = tag_data(xmldoc, 'TiVoContainer/Details/Title')

            data = []
            for item in items:
                entry = {}
                entry['ContentType'] = tag_data(item, 'Details/ContentType')
                for tag in ('CopyProtected', 'UniqueId'):
                    value = tag_data(item, 'Details/' + tag)
                    if value:
                        entry[tag] = value
                if entry['ContentType'] == 'x-tivo-container/folder':
                    entry['Title'] = tag_data(item, 'Details/Title')
                    entry['TotalItems'] = tag_data(item, 'Details/TotalItems')
                    lc = tag_data(item, 'Details/LastCaptureDate')
                    if not lc:
                        lc = tag_data(item, 'Details/LastChangeDate')
                    entry['LastChangeDate'] = time.strftime('%b %d, %Y',
                        time.localtime(int(lc, 16)))
                else:
                    keys = {'Icon': 'Links/CustomIcon/Url',
                            'Url': 'Links/Content/Url',
                            'SourceSize': 'Details/SourceSize',
                            'Duration': 'Details/Duration',
                            'CaptureDate': 'Details/CaptureDate'}
                    for key in keys:
                        value = tag_data(item, keys[key])
                        if value:
                            entry[key] = value

                    rawsize = entry['SourceSize']
                    entry['SourceSize'] = metadata.human_size(rawsize)

                    dur = int(entry['Duration']) / 1000
                    entry['Duration'] = ( '%d:%02d:%02d' %
                        (dur / 3600, (dur % 3600) / 60, dur % 60) )

                    entry['CaptureDate'] = time.strftime('%b %d, %Y',
                        time.localtime(int(entry['CaptureDate'], 16)))

                    url = entry['Url']
                    if url in basic_meta:
                        entry.update(basic_meta[url])
                    else:
                        basic_data = metadata.from_container(item)
                        entry.update(basic_data)
                        basic_meta[url] = basic_data

                data.append(entry)