Beispiel #1
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
         for theurl in urls:
             status[theurl] = {'running': False, 'error': '', 'rate': '',
                               'queued': True, 'size': 0, 'finished': False,
                               'decode': decode, 'save': save}
             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([unquote(x) for x in urls])
         message = TRANS_QUEUE % (urlstring, togo_path)
     else:
         message = MISSING
     handler.redir(message, 5)
Beispiel #2
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
Beispiel #3
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
         ts_format = 'ts_format' in query
         for theurl in urls:
             status[theurl] = {
                 'running': False,
                 'error': '',
                 'rate': '',
                 'queued': True,
                 'size': 0,
                 'finished': False,
                 'decode': decode,
                 'save': save,
                 'ts_format': ts_format
             }
             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)
Beispiel #4
0
    def NPL(self, handler, query):

        def getint(thing):
            try:
                result = int(thing)
            except:
                result = 0
            return result

        global basic_meta
        shows_per_page = 50 # Change this to alter the number of shows returned
        folder = ''
        FirstAnchor = ''
        has_tivodecode = bool(config.get_bin('tivodecode'))
        useragent = handler.headers.getheader('User-Agent', '')

        if 'TiVo' in query:
            tivoIP = query['TiVo'][0]
            tsn = config.tivos_by_ip(tivoIP)
            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')
            title = tag_data(xmldoc, 'TiVoContainer/Details/Title')
            if items:
                FirstAnchor = tag_data(items[0], 'Links/Content/Url')

            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 = getint(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)
Beispiel #5
0
        auth_handler.add_password('TiVo DVR', url, 'tivo', mak)
        try:
            handle = self.tivo_open(url)
        except urllib2.HTTPError, e:
            status[url]['running'] = False
            status[url]['error'] = e.code
            logger.error(e.code)
            return
        except urllib2.URLError, e:
            status[url]['running'] = False
            status[url]['error'] = e.reason
            logger.error(e.reason)
            return

        tivo_name = config.tivo_names[config.tivos_by_ip(tivoIP)]

        logger.info('[%s] Start getting "%s" from %s' %
                    (time.strftime('%d/%b/%Y %H:%M:%S'), outfile, tivo_name))

        if status[url]['decode']:
            tivodecode_path = config.get_bin('tivodecode')
            tcmd = [tivodecode_path, '-m', mak, '-o', outfile, '-']
            tivodecode = subprocess.Popen(tcmd, stdin=subprocess.PIPE,
                                          bufsize=(512 * 1024))
            f = tivodecode.stdin
        else:
            f = open(outfile, 'wb')
        length = 0
        start_time = time.time()
        last_interval = start_time
Beispiel #6
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)
Beispiel #7
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')
Beispiel #8
0
    def GetShowsList(handler, query):
        """
        HTTP command handler to return the list of shows on a particular TiVo
        """
        json_config = {}

        if 'TiVo' in query:
            tivoIP = query['TiVo'][0]
            tsn = config.tivos_by_ip(tivoIP)
            attrs = config.tivos[tsn]
            tivo_name = attrs.get('name', tivoIP)
            tivo_mak = config.get_tsn('tivo_mak', tsn)

            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)

            # Get the total item count first
            theurl = baseurl + '&Recurse=Yes&ItemCount=0'
            auth_handler.add_password('TiVo DVR', ip_port, 'tivo', tivo_mak)
            logger.debug('GetShowsList: (1) add password for TiVo DVR netloc: %s', ip_port)
            try:
                page = tivo_open(theurl)
            except IOError:
                handler.send_error(404)
                return

            xmldoc = minidom.parse(page)
            page.close()

            LastChangeDate = tag_data(xmldoc, 'TiVoContainer/Details/LastChangeDate')

            # Check date of cache
            if tsn in json_cache and json_cache[tsn]['lastChangeDate'] == LastChangeDate:
                logger.debug("Retrieving shows from cache")
                handler.send_json(json_cache[tsn]['data'])
                return

            # loop through grabbing 50 items at a time (50 is max TiVo will return)
            TotalItems = int(tag_data(xmldoc, 'TiVoContainer/Details/TotalItems'))
            if TotalItems <= 0:
                logger.debug("Total items 0")
                handler.send_json(json_config)
                return

            GotItems = 0
            GeneratedID = 0
            while GotItems < TotalItems:
                logger.debug("Retrieving shows %s-%s of %s from %s",
                             GotItems, GotItems + 50, TotalItems, tivo_name)
                theurl = baseurl + '&Recurse=Yes&ItemCount=50'
                theurl += '&AnchorOffset=%d' % GotItems
                auth_handler.add_password('TiVo DVR', ip_port, 'tivo', tivo_mak)
                logger.debug('GetShowsList: (2) add password for TiVo DVR netloc: %s', ip_port)
                try:
                    page = tivo_open(theurl)
                except IOError:
                    handler.send_error(404)
                    return

                try:
                    xmldoc = minidom.parse(page)
                    items = xmldoc.getElementsByTagName('Item')
                except Exception as e:          # pylint: disable=broad-except
                    logger.error('XML parser error: %s: %s', e.__class__.__name__, e)
                    break
                finally:
                    page.close()

                if len(items) <= 0:
                    logger.debug("items collection empty")
                    break

                for item in items:
                    dnld_url = tag_data(item, 'Links/Content/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().from_tivo_container_item(item)
                    item_showinfo = showinfo[dnld_url]
                    ep_info = item_showinfo.get_tivo_desktop_info()

                    if not ep_info['seriesID']:
                        ep_info['seriesID'] = 'TS%08d' % GeneratedID
                        GeneratedID += 1

                    if not ep_info['episodeID']:
                        ep_info['episodeID'] = 'EP%08d' % GeneratedID
                        GeneratedID += 1

                    if not ep_info['seriesID'] in json_config:
                        json_config[ep_info['seriesID']] = {}

                    # Check for duplicate episode IDs and replace with generated ID
                    while ep_info['episodeID'] in json_config[ep_info['seriesID']]:
                        ep_info['episodeID'] = 'EP%08d' % GeneratedID
                        GeneratedID += 1

                    json_config[ep_info['seriesID']][ep_info['episodeID']] = ep_info

                itemCount = tag_data(xmldoc, 'TiVoContainer/ItemCount')
                try:
                    logger.debug("Retrieved " + itemCount + " from " + tivo_name)
                    GotItems += int(itemCount)
                except ValueError:
                    GotItems += len(items)


            # Cache data for reuse
            json_cache[tsn] = {}
            json_cache[tsn]['data'] = json.dumps(json_config)
            json_cache[tsn]['lastChangeDate'] = LastChangeDate

            handler.send_json(json_cache[tsn]['data'])
        else:
            handler.send_json(json.dumps(json_config))
Beispiel #9
0
    def NPL(self, handler, query):
        def getint(thing):
            try:
                result = int(thing)
            except:
                result = 0
            return result

        global basic_meta
        global details_urls
        shows_per_page = 50  # Change this to alter the number of shows returned
        folder = ''
        FirstAnchor = ''
        has_tivodecode = bool(config.get_bin('tivodecode'))

        if 'TiVo' in query:
            tivoIP = query['TiVo'][0]
            tsn = config.tivos_by_ip(tivoIP)
            attrs = config.tivos[tsn]
            tivo_name = attrs.get('name', tivoIP)
            tivo_mak = config.get_tsn('tivo_mak', tsn)

            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 = urlparse.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 (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', ip_port, 'tivo',
                                          tivo_mak)
                try:
                    page = self.tivo_open(theurl)
                except IOError, e:
                    handler.redir(UNABLE % (tivoIP, cgi.escape(str(e))), 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')
            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)))

                    url = urlparse.urljoin(baseurl, entry['Url'])
                    entry['Url'] = 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
                        if 'Details' in entry:
                            details_urls[url] = entry['Details']

                data.append(entry)
Beispiel #10
0
            metafile = open(outfile + '.txt', 'w')
            metadata.dump(metafile, meta)
            metafile.close()

        auth_handler.add_password('TiVo DVR', url, 'tivo', mak)
        try:
            if status[url]['ts_format']:
                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

        tivo_name = config.tivos[config.tivos_by_ip(tivoIP)].get(
            'name', tivoIP)

        logger.info('[%s] Start getting "%s" from %s' %
                    (time.strftime('%d/%b/%Y %H:%M:%S'), outfile, tivo_name))

        if status[url]['decode']:
            fname = outfile
            if mswindows:
                fname = fname.encode('cp1252')
            tivodecode_path = config.get_bin('tivodecode')
            tcmd = [tivodecode_path, '-m', mak, '-o', fname, '-']
            tivodecode = subprocess.Popen(tcmd,
                                          stdin=subprocess.PIPE,
                                          bufsize=(512 * 1024))
            f = tivodecode.stdin
Beispiel #11
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)
Beispiel #12
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)
Beispiel #13
0
    def GetShowsList(self, handler, query):
        json_config = {}
        if 'TiVo' in query:
            tivoIP = query['TiVo'][0]
            tsn = config.tivos_by_ip(tivoIP)
            attrs = config.tivos[tsn]
            tivo_name = attrs.get('name', tivoIP)
            tivo_mak = config.get_tsn('tivo_mak', tsn)

            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)

            # Get the total item count first
            theurl = baseurl + '&Recurse=Yes&ItemCount=0'
            auth_handler.add_password('TiVo DVR', ip_port, 'tivo', tivo_mak)
            try:
                page = self.tivo_open(theurl)
            except IOError, e:
                handler.send_error(404)
                return

            xmldoc = minidom.parse(page)
            page.close()

            LastChangeDate = unicode(
                tag_data(xmldoc, 'TiVoContainer/Details/LastChangeDate'))

            # Check date of cache
            if (tsn in json_cache
                    and json_cache[tsn]['lastChangeDate'] == LastChangeDate):
                logger.debug("Retrieving shows from cache")
                handler.send_json(json_cache[tsn]['data'])
                return

            global basic_meta
            global details_urls

            # loop through grabbing 50 items at a time (50 is max TiVo will return)
            TotalItems = int(
                unicode(tag_data(xmldoc, 'TiVoContainer/Details/TotalItems')))
            if TotalItems <= 0:
                logger.debug("Total items 0")
                handler.send_json(json_config)
                return

            GotItems = 0
            GeneratedID = 0
            while (GotItems < TotalItems):
                logger.debug("Retrieving shows " + str(GotItems) + "-" +
                             str(GotItems + 50) + " of " + str(TotalItems) +
                             " from " + tivo_name)
                theurl = baseurl + '&Recurse=Yes&ItemCount=50'
                theurl += '&AnchorOffset=%d' % GotItems
                auth_handler.add_password('TiVo DVR', ip_port, 'tivo',
                                          tivo_mak)
                try:
                    page = self.tivo_open(theurl)
                except IOError, e:
                    handler.send_error(404)
                    return

                try:
                    xmldoc = minidom.parse(page)
                    items = xmldoc.getElementsByTagName('Item')
                    page.close()
                except:
                    logger.debug("XML parser error")
                    break

                if len(items) <= 0:
                    logger.debug("items collection empty")
                    break

                for item in items:
                    SeriesID = tag_data(item, 'Details/SeriesId')
                    if (not SeriesID):
                        SeriesID = 'PS%08d' % GeneratedID
                        GeneratedID += 1

                    if (not SeriesID in json_config):
                        json_config[SeriesID] = {}

                    EpisodeID = tag_data(item, 'Details/ProgramId')
                    if (not EpisodeID):
                        EpisodeID = 'PE%08d' % GeneratedID
                        GeneratedID += 1

                    # Check for duplicate episode IDs and replace with generated ID
                    while EpisodeID in json_config[SeriesID]:
                        EpisodeID = 'PE%08d' % GeneratedID
                        GeneratedID += 1

                    json_config[SeriesID][EpisodeID] = {}

                    json_config[SeriesID][EpisodeID]['title'] = tag_data(
                        item, 'Details/Title')
                    json_config[SeriesID][EpisodeID]['url'] = tag_data(
                        item, 'Links/Content/Url')
                    json_config[SeriesID][EpisodeID]['detailsUrl'] = tag_data(
                        item, 'Links/TiVoVideoDetails/Url')
                    json_config[SeriesID][EpisodeID][
                        'episodeTitle'] = tag_data(item,
                                                   'Details/EpisodeTitle')
                    json_config[SeriesID][EpisodeID]['description'] = tag_data(
                        item, 'Details/Description')
                    json_config[SeriesID][EpisodeID]['recordDate'] = tag_data(
                        item, 'Details/CaptureDate')
                    json_config[SeriesID][EpisodeID]['duration'] = tag_data(
                        item, 'Details/Duration')
                    json_config[SeriesID][EpisodeID]['sourceSize'] = tag_data(
                        item, 'Details/SourceSize')
                    json_config[SeriesID][EpisodeID]['channel'] = tag_data(
                        item, 'Details/SourceChannel')
                    json_config[SeriesID][EpisodeID]['stationID'] = tag_data(
                        item, 'Details/SourceStation')
                    json_config[SeriesID][EpisodeID]['episodeID'] = EpisodeID
                    json_config[SeriesID][EpisodeID]['seriesID'] = SeriesID

                    if tag_data(item, 'Details/InProgress') == 'Yes':
                        json_config[SeriesID][EpisodeID]['inProgress'] = True
                    else:
                        json_config[SeriesID][EpisodeID]['inProgress'] = False

                    if tag_data(item, 'Details/CopyProtected') == 'Yes':
                        json_config[SeriesID][EpisodeID]['isProtected'] = True
                    else:
                        json_config[SeriesID][EpisodeID]['isProtected'] = False

                    if tag_data(item, 'Links/CustomIcon/Url'
                                ) == 'urn:tivo:image:suggestion-recording':
                        json_config[SeriesID][EpisodeID]['isSuggestion'] = True
                    else:
                        json_config[SeriesID][EpisodeID][
                            'isSuggestion'] = False

                    if tag_data(item, 'Details/CopyProtected') == 'Yes':
                        json_config[SeriesID][EpisodeID]['icon'] = 'protected'
                    elif tag_data(item, 'Links/CustomIcon/Url'
                                  ) == 'urn:tivo:image:expires-soon-recording':
                        json_config[SeriesID][EpisodeID]['icon'] = 'expiring'
                    elif tag_data(item, 'Links/CustomIcon/Url'
                                  ) == 'urn:tivo:image:expired-recording':
                        json_config[SeriesID][EpisodeID]['icon'] = 'expired'
                    elif tag_data(
                            item, 'Links/CustomIcon/Url'
                    ) == 'urn:tivo:image:save-until-i-delete-recording':
                        json_config[SeriesID][EpisodeID]['icon'] = 'kuid'
                    elif tag_data(item, 'Links/CustomIcon/Url'
                                  ) == 'urn:tivo:image:suggestion-recording':
                        json_config[SeriesID][EpisodeID]['icon'] = 'suggestion'
                    elif tag_data(item, 'Links/CustomIcon/Url'
                                  ) == 'urn:tivo:image:in-progress-recording':
                        json_config[SeriesID][EpisodeID]['icon'] = 'inprogress'
                    else:
                        json_config[SeriesID][EpisodeID]['icon'] = 'normal'

                    url = urlparse.urljoin(
                        baseurl, json_config[SeriesID][EpisodeID]['url'])
                    json_config[SeriesID][EpisodeID]['url'] = url
                    if not url in basic_meta:
                        basic_meta[url] = metadata.from_container(item)
                        if 'detailsUrl' in json_config[SeriesID][EpisodeID]:
                            details_urls[url] = json_config[SeriesID][
                                EpisodeID]['detailsUrl']

                itemCount = tag_data(xmldoc, 'TiVoContainer/ItemCount')
                try:
                    logger.debug("Retrieved " + itemCount + " from " +
                                 tivo_name)
                    GotItems += int(itemCount)
                except ValueError:
                    GotItems += len(items)
Beispiel #14
0
    def NPL(self, handler, query):

        def getint(thing):
            try:
                result = int(thing)
            except:
                result = 0
            return result

        global basic_meta
        global details_urls
        shows_per_page = 50 # Change this to alter the number of shows returned
        folder = ''
        FirstAnchor = ''
        has_tivodecode = bool(config.get_bin('tivodecode'))

        if 'TiVo' in query:
            tivoIP = query['TiVo'][0]
            tsn = config.tivos_by_ip(tivoIP)
            attrs = config.tivos[tsn]
            tivo_name = attrs.get('name', tivoIP)
            tivo_mak = config.get_tsn('tivo_mak', tsn)

            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 = urlparse.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 (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', ip_port, 'tivo', tivo_mak)
                try:
                    page = self.tivo_open(theurl)
                except IOError, e:
                    handler.redir(UNABLE % (tivoIP, cgi.escape(str(e))), 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')
            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)))

                    url = urlparse.urljoin(baseurl, entry['Url'])
                    entry['Url'] = 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
                        if 'Details' in entry:
                            details_urls[url] = entry['Details']

                data.append(entry)
Beispiel #15
0
            metafile = open(outfile + '.txt', 'w')
            metadata.dump(metafile, meta)
            metafile.close()

        auth_handler.add_password('TiVo DVR', url, 'tivo', mak)
        try:
            if status[url]['ts_format']:
                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

        tivo_name = config.tivos[config.tivos_by_ip(tivoIP)].get('name', tivoIP)

        logger.info('[%s] Start getting "%s" from %s' %
                    (time.strftime('%d/%b/%Y %H:%M:%S'), outfile, tivo_name))

        if status[url]['decode']:
            fname = outfile
            if mswindows:
                fname = fname.encode('cp1252')
            tivodecode_path = config.get_bin('tivodecode')
            tcmd = [tivodecode_path, '-m', mak, '-o', fname, '-']
            tivodecode = subprocess.Popen(tcmd, stdin=subprocess.PIPE,
                                          bufsize=(512 * 1024))
            f = tivodecode.stdin
        else:
            f = open(outfile, 'wb')
Beispiel #16
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)
            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, 'Details/TotalItems')
            ItemStart = tag_data(xmldoc, 'ItemStart')
            ItemCount = tag_data(xmldoc, 'ItemCount')
            FirstAnchor = tag_data(items[0], 'Links/Content/Url')

            data = []
            for item in items:
                entry = {}
                entry['ContentType'] = tag_data(item, 'ContentType')
                for tag in ('CopyProtected', 'UniqueId'):
                    value = tag_data(item, tag)
                    if value:
                        entry[tag] = value
                if entry['ContentType'] == 'x-tivo-container/folder':
                    entry['Title'] = tag_data(item, 'Title')
                    entry['TotalItems'] = tag_data(item, 'TotalItems')
                    lc = tag_data(item, 'LastCaptureDate')
                    if not lc:
                        lc = tag_data(item, '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

                    entry['SourceSize'] = ('%.3f GB' %
                                           (float(entry['SourceSize']) /
                                            (1024**3)))

                    dur = int(entry['Duration']) / 1000
                    entry['Duration'] = ('%02d:%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)