def select_audiocodec(isQuery, inFile, tsn=""): if inFile[-5:].lower() == ".tivo": return "-acodec copy" vInfo = video_info(inFile) codectype = vInfo["vCodec"] codec = config.get_tsn("audio_codec", tsn) if not codec: # Default, compatible with all TiVo's codec = "ac3" if vInfo["aCodec"] in ("ac3", "liba52", "mp2"): aKbps = vInfo["aKbps"] if aKbps == None: if not isQuery: aKbps = audio_check(inFile, tsn) else: codec = "TBD" if aKbps != None and int(aKbps) <= config.getMaxAudioBR(tsn): # compatible codec and bitrate, do not reencode audio codec = "copy" copy_flag = config.get_tsn("copy_ts", tsn) copyts = " -copyts" if (codec == "copy" and codectype == "mpeg2video" and not copy_flag) or ( copy_flag and copy_flag.lower() == "false" ): copyts = "" return "-acodec " + codec + copyts
def select_audiocodec(isQuery, inFile, tsn=''): if inFile[-5:].lower() == '.tivo': return '-acodec copy' vInfo = video_info(inFile) codectype = vInfo['vCodec'] codec = config.get_tsn('audio_codec', tsn) if not codec: # Default, compatible with all TiVo's codec = 'ac3' if vInfo['aCodec'] in ('ac3', 'liba52', 'mp2'): aKbps = vInfo['aKbps'] if aKbps == None: if not isQuery: aKbps = audio_check(inFile, tsn) else: codec = 'TBD' if aKbps != None and int(aKbps) <= config.getMaxAudioBR(tsn): # compatible codec and bitrate, do not reencode audio codec = 'copy' copy_flag = config.get_tsn('copy_ts', tsn) copyts = ' -copyts' if ((codec == 'copy' and codectype == 'mpeg2video' and not copy_flag) or (copy_flag and copy_flag.lower() == 'false')): copyts = '' return '-acodec ' + codec + copyts
def getMind(tsn=None): username = config.get_tsn('tivo_username', tsn) password = config.get_tsn('tivo_password', tsn) if not username or not password: raise Exception("tivo_username and tivo_password required") return Mind(username, password, tsn)
def tivo_compatible_audio(vInfo, inFile, tsn, mime=''): message = (True, '') while True: codec = vInfo.get('aCodec', '') if codec == None: debug('No audio stream detected') break if mime == 'video/mp4': if codec not in ('mpeg4aac', 'libfaad', 'mp4a', 'aac', 'ac3', 'liba52'): message = (False, 'aCodec %s not compatible' % codec) break if vInfo['aCodec'] in ('mpeg4aac', 'libfaad', 'mp4a', 'aac') and (vInfo['aCh'] == None or vInfo['aCh'] > 2): message = (False, 'aCodec %s is only supported with 2 or less channels, the track has %s channels' % (codec, vInfo['aCh'])) break audio_lang = config.get_tsn('audio_lang', tsn) if audio_lang: if vInfo['mapAudio'][0][0] != select_audiolang(inFile, tsn)[-3:]: message = (False, '%s preferred audio track exists' % audio_lang) break if mime == 'video/bif': if codec != 'wmav2': message = (False, 'aCodec %s not compatible' % codec) break if inFile[-5:].lower() == '.tivo': break if mime == 'video/x-tivo-mpeg-ts': if codec not in ('ac3', 'liba52', 'mp2', 'aac_latm'): message = (False, 'aCodec %s not compatible' % codec) break if codec not in ('ac3', 'liba52', 'mp2'): message = (False, 'aCodec %s not compatible' % codec) break if (not vInfo['aKbps'] or int(vInfo['aKbps']) > config.getMaxAudioBR(tsn)): message = (False, '%s kbps exceeds max audio bitrate' % vInfo['aKbps']) break audio_lang = config.get_tsn('audio_lang', tsn) if audio_lang: if vInfo['mapAudio'][0][0] != select_audiolang(inFile, tsn)[-3:]: message = (False, '%s preferred audio track exists' % audio_lang) break return message
def tivo_compatible_audio(vInfo, inFile, tsn, mime=""): message = (True, "") while True: codec = vInfo.get("aCodec", "") if codec == None: debug("No audio stream detected") break if mime == "video/mp4": if codec not in ("mpeg4aac", "libfaad", "mp4a", "aac", "ac3", "liba52"): message = (False, "aCodec %s not compatible" % codec) break if vInfo["aCodec"] in ("mpeg4aac", "libfaad", "mp4a", "aac") and (vInfo["aCh"] == None or vInfo["aCh"] > 2): message = ( False, "aCodec %s is only supported with 2 or less channels, the track has %s channels" % (codec, vInfo["aCh"]), ) break audio_lang = config.get_tsn("audio_lang", tsn) if audio_lang: if vInfo["mapAudio"][0][0] != select_audiolang(inFile, tsn)[-3:]: message = (False, "%s preferred audio track exists" % audio_lang) break if mime == "video/bif": if codec != "wmav2": message = (False, "aCodec %s not compatible" % codec) break if inFile[-5:].lower() == ".tivo": break if mime == "video/x-tivo-mpeg-ts": if codec not in ("ac3", "liba52", "mp2", "aac_latm"): message = (False, "aCodec %s not compatible" % codec) break if codec not in ("ac3", "liba52", "mp2"): message = (False, "aCodec %s not compatible" % codec) break if not vInfo["aKbps"] or int(vInfo["aKbps"]) > config.getMaxAudioBR(tsn): message = (False, "%s kbps exceeds max audio bitrate" % vInfo["aKbps"]) break audio_lang = config.get_tsn("audio_lang", tsn) if audio_lang: if vInfo["mapAudio"][0][0] != select_audiolang(inFile, tsn)[-3:]: message = (False, "%s preferred audio track exists" % audio_lang) break return message
def tivo_compatible_audio(vInfo, inFile, tsn, mime=""): message = (True, "") while True: codec = vInfo["aCodec"] if mime == "video/mp4": if codec not in ("mpeg4aac", "libfaad", "mp4a", "aac", "ac3", "liba52"): message = (False, "aCodec %s not compatible" % codec) break if mime == "video/bif": if codec != "wmav2": message = (False, "aCodec %s not compatible" % codec) break if inFile[-5:].lower() == ".tivo": break if codec not in ("ac3", "liba52", "mp2"): message = (False, "aCodec %s not compatible" % codec) break if not vInfo["aKbps"] or int(vInfo["aKbps"]) > config.getMaxAudioBR(tsn): message = (False, "%s kbps exceeds max audio bitrate" % vInfo["aKbps"]) break audio_lang = config.get_tsn("audio_lang", tsn) if audio_lang: if vInfo["mapAudio"][0][0] != select_audiolang(inFile, tsn)[-3:]: message = (False, "%s preferred audio track exists" % audio_lang) break return message
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)
def select_audiolang(inFile, tsn): vInfo = video_info(inFile) audio_lang = config.get_tsn('audio_lang', tsn) debug('audio_lang: %s' % audio_lang) if vInfo['mapAudio']: # default to first detected audio stream to begin with stream = vInfo['mapAudio'][0][0] if audio_lang != None and vInfo['mapVideo'] != None: langmatch_curr = [] langmatch_prev = vInfo['mapAudio'][:] for lang in audio_lang.replace(' ', '').lower().split(','): for s, l in langmatch_prev: if lang in s + l.replace(' ', '').lower(): langmatch_curr.append((s, l)) stream = s # if only 1 item matched we're done if len(langmatch_curr) == 1: break # if more than 1 item matched copy the curr area to the prev # array we only need to look at the new shorter list from # now on elif len(langmatch_curr) > 1: langmatch_prev = langmatch_curr[:] # if we drop out of the loop with more than 1 item default to # the first item if len(langmatch_prev) > 1: stream = langmatch_prev[0][0] # don't let FFmpeg auto select audio stream, pyTivo defaults to # first detected if stream: debug('selected audio stream: %s' % stream) return '-map ' + vInfo['mapVideo'] + ' -map ' + stream # if no audio is found debug('selected audio stream: None detected') return ''
def select_audiocodec(isQuery, inFile, tsn='', mime=''): if inFile[-5:].lower() == '.tivo': return '-acodec copy' vInfo = video_info(inFile) codectype = vInfo['vCodec'] codec = config.get_tsn('audio_codec', tsn) if not codec: # Default, compatible with all TiVo's codec = 'ac3' if mime == 'video/mp4': compatiblecodecs = ('mpeg4aac', 'libfaad', 'mp4a', 'aac', 'ac3', 'liba52') else: compatiblecodecs = ('ac3', 'liba52', 'mp2') if vInfo['aCodec'] in compatiblecodecs: aKbps = vInfo['aKbps'] aCh = vInfo['aCh'] if aKbps == None: if vInfo['aCodec'] in ('mpeg4aac', 'libfaad', 'mp4a', 'aac'): # along with the channel check below this should # pass any AAC audio that has undefined 'aKbps' and # is <= 2 channels. Should be TiVo compatible. codec = 'copy' elif not isQuery: vInfoQuery = audio_check(inFile, tsn) if vInfoQuery == None: aKbps = None aCh = None else: aKbps = vInfoQuery['aKbps'] aCh = vInfoQuery['aCh'] else: codec = 'TBA' if aKbps and int(aKbps) <= config.getMaxAudioBR(tsn): # compatible codec and bitrate, do not reencode audio codec = 'copy' if vInfo['aCodec'] != 'ac3' and (aCh == None or aCh > 2): codec = 'ac3' copy_flag = config.get_tsn('copy_ts', tsn) copyts = ' -copyts' if ((codec == 'copy' and codectype == 'mpeg2video' and not copy_flag) or (copy_flag and copy_flag.lower() == 'false')): copyts = '' return '-acodec ' + codec + copyts
def select_videofps(inFile, tsn): vInfo = video_info(inFile) fps = "-r 29.97" # default if config.isHDtivo(tsn) and vInfo["vFps"] in GOOD_MPEG_FPS: fps = " " video_fps = config.get_tsn("video_fps", tsn) if video_fps != None: fps = "-r " + video_fps return fps
def select_audioch(inFile, tsn): ch = config.get_tsn('audio_ch', tsn) if ch: return '-ac ' + ch # AC-3 max channels is 5.1 if video_info(inFile)['aCh'] > 6: debug('Too many audio channels for AC-3, using 5.1 instead') return '-ac 6' return ''
def select_videofps(inFile, tsn): vInfo = video_info(inFile) fps = '-r 29.97' # default if config.isHDtivo(tsn) and vInfo['vFps'] in GOOD_MPEG_FPS: fps = ' ' video_fps = config.get_tsn('video_fps', tsn) if video_fps != None: fps = '-r ' + video_fps return fps
def select_audiofr(inFile, tsn): freq = '48000' # default vInfo = video_info(inFile) if vInfo['aFreq'] == '44100': # compatible frequency freq = vInfo['aFreq'] audio_fr = config.get_tsn('audio_fr', tsn) if audio_fr != None: freq = audio_fr return '-ar ' + freq
def select_audiofr(inFile, tsn): freq = "48000" # default vInfo = video_info(inFile) if not vInfo["aFreq"] == None and vInfo["aFreq"] in ("44100", "48000"): # compatible frequency freq = vInfo["aFreq"] audio_fr = config.get_tsn("audio_fr", tsn) if audio_fr != None: freq = audio_fr return "-ar " + freq
def __est_size(self, full_path, tsn='', mime=''): # Size is estimated by taking audio and video bit rate adding 2% if transcode.tivo_compatible(full_path, tsn, mime)[0]: return int(os.stat(unicode(full_path, 'utf-8')).st_size) else: # Must be re-encoded if config.get_tsn('audio_codec', tsn) == None: audioBPS = config.getMaxAudioBR(tsn) * 1000 else: audioBPS = config.strtod(config.getAudioBR(tsn)) videoBPS = transcode.select_videostr(full_path, tsn) bitrate = audioBPS + videoBPS return int( (self.__duration(full_path) / 1000) * (bitrate * 1.02 / 8))
def __est_size(self, full_path, tsn='', mime=''): # Size is estimated by taking audio and video bit rate adding 2% if transcode.tivo_compatible(full_path, tsn, mime)[0]: return int(os.stat(unicode(full_path, 'utf-8')).st_size) else: # Must be re-encoded if config.get_tsn('audio_codec', tsn) == None: audioBPS = config.getMaxAudioBR(tsn) * 1000 else: audioBPS = config.strtod(config.getAudioBR(tsn)) videoBPS = transcode.select_videostr(full_path, tsn) bitrate = audioBPS + videoBPS return int((self.__duration(full_path) / 1000) * (bitrate * 1.02 / 8))
def select_audiolang(inFile, tsn): vInfo = video_info(inFile) audio_lang = config.get_tsn('audio_lang', tsn) if audio_lang != None and vInfo['mapVideo'] != None: stream = vInfo['mapAudio'][0][0] langmatch = [] for lang in audio_lang.replace(' ','').lower().split(','): for s, l in vInfo['mapAudio']: if lang in s + l.replace(' ','').lower(): langmatch.append(s) stream = s break if langmatch: break if stream is not '': return '-map ' + vInfo['mapVideo'] + ' -map ' + stream return ''
def select_audiolang(inFile, tsn): vInfo = video_info(inFile) audio_lang = config.get_tsn("audio_lang", tsn) if audio_lang != None and vInfo["mapVideo"] != None: stream = vInfo["mapAudio"][0][0] langmatch = [] for lang in audio_lang.replace(" ", "").lower().split(","): for s, l in vInfo["mapAudio"]: if lang in s + l.replace(" ", "").lower(): langmatch.append(s) stream = s break if langmatch: break if stream is not "": return "-map " + vInfo["mapVideo"] + " -map " + stream return ""
def tivo_compatible_audio(vInfo, inFile, tsn, mime=''): message = (True, '') while True: codec = vInfo.get('aCodec', '') if mime == 'video/mp4': if codec not in ('mpeg4aac', 'libfaad', 'mp4a', 'aac', 'ac3', 'liba52'): message = (False, 'aCodec %s not compatible' % codec) break if mime == 'video/bif': if codec != 'wmav2': message = (False, 'aCodec %s not compatible' % codec) break if inFile[-5:].lower() == '.tivo': break if mime == 'video/x-tivo-mpeg-ts' and codec not in ('ac3', 'liba52'): message = (False, 'aCodec %s not compatible' % codec) break if codec not in ('ac3', 'liba52', 'mp2'): message = (False, 'aCodec %s not compatible' % codec) break if (not vInfo['aKbps'] or int(vInfo['aKbps']) > config.getMaxAudioBR(tsn)): message = (False, '%s kbps exceeds max audio bitrate' % vInfo['aKbps']) break audio_lang = config.get_tsn('audio_lang', tsn) if audio_lang: if vInfo['mapAudio'][0][0] != select_audiolang(inFile, tsn)[-3:]: message = (False, '%s preferred audio track exists' % audio_lang) break return message
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)
def select_audiolang(inFile, tsn): vInfo = video_info(inFile) audio_lang = config.get_tsn("audio_lang", tsn) debug("audio_lang: %s" % audio_lang) if vInfo["mapAudio"]: # default to first detected audio stream to begin with stream = vInfo["mapAudio"][0][0] debug("set first detected audio stream by default: %s" % stream) if audio_lang != None and vInfo["mapVideo"] != None: langmatch_curr = [] langmatch_prev = vInfo["mapAudio"][:] for lang in audio_lang.replace(" ", "").lower().split(","): debug("matching lang: %s" % lang) for s, l in langmatch_prev: if lang in s + l.replace(" ", "").lower(): debug("matched: %s" % s + l.replace(" ", "").lower()) langmatch_curr.append((s, l)) # if only 1 item matched we're done if len(langmatch_curr) == 1: stream = langmatch_curr[0][0] debug("found exactly one match: %s" % stream) break # if more than 1 item matched copy the curr area to the prev # array we only need to look at the new shorter list from # now on elif len(langmatch_curr) > 1: langmatch_prev = langmatch_curr[:] # default to the first item matched thus far stream = langmatch_curr[0][0] debug("remember first match: %s" % stream) langmatch_curr = [] # don't let FFmpeg auto select audio stream, pyTivo defaults to # first detected if stream: debug("selected audio stream: %s" % stream) return "-map " + vInfo["mapVideo"] + " -map " + stream # if no audio is found debug("selected audio stream: None detected") return ""
def tivo_compatible_audio(vInfo, inFile, tsn, mime=''): message = (True, '') while True: codec = vInfo.get('aCodec', '') if codec == None: debug('No audio stream detected') break if inFile[-5:].lower() == '.tivo': break if mime == 'video/x-tivo-mpeg-ts': if codec not in ('ac3', 'liba52', 'mp2', 'aac_latm'): message = (False, 'aCodec %s not compatible' % codec) break if codec not in ('ac3', 'liba52', 'mp2'): message = (False, 'aCodec %s not compatible' % codec) break if (not vInfo['aKbps'] or int(vInfo['aKbps']) > config.getMaxAudioBR(tsn)): message = (False, '%s kbps exceeds max audio bitrate' % vInfo['aKbps']) break audio_lang = config.get_tsn('audio_lang', tsn) if audio_lang: if vInfo['mapAudio'][0][0] != select_audiolang(inFile, tsn)[-3:]: message = (False, '%s preferred audio track exists' % audio_lang) break return message
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)
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)
def select_audioch(tsn): ch = config.get_tsn("audio_ch", tsn) if ch: return "-ac " + ch return ""
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)
try: f = open(unicode(path, 'utf-8'), 'rb') tivo_header = bytearray(f.read(16)) if tivo_header[0:4].decode("utf-8") == 'TiVo': is_tivo_file = True try: if (tivo_header[7] & 0x20 != 0): is_tivo_ts = True except Exception, msg: test = 0 tivo_header_size = struct.unpack_from('>L', tivo_header, 10)[0] f.close() except: pass tivo_mak = config.get_tsn('tivo_mak', tsn) has_tivolibre = bool(config.get_bin('tivolibre')) has_tivodecode = bool(config.get_bin('tivodecode')) use_tivolibre = False if has_tivolibre and bool(config.get_server('tivolibre_upload', True)): use_tivolibre = True if 'Format' in query: mime = query['Format'][0] needs_converion = (((is_tivo_file and is_tivo_ts) or (is_tivo_file and not has_tivolibre)) and mime == 'video/mpeg') compatible = (not needs_converion and transcode.tivo_compatible(path, tsn, mime)[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)
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)
def send_file(self, handler, path, query): global status self.cleanup_status() # Keep status object from getting too big mime = 'video/x-tivo-mpeg' tsn = handler.headers.get('tsn', '') try: assert(tsn) tivo_name = config.tivos[tsn].get('name', tsn) except: tivo_name = handler.address_string() if not tivo_name in status: status[tivo_name] = {} is_tivo_file = False tivo_header_size = 0 is_tivo_ts = False try: with open(path, 'rb') as f: tivo_header = f.read(16) if tivo_header[0:4] == b'TiVo': is_tivo_file = True tivo_header_size = struct.unpack_from('>L', tivo_header, 10)[0] if (tivo_header[7] & 0x20 != 0): is_tivo_ts = True except: pass tivo_mak = config.get_tsn('tivo_mak', tsn) has_tivolibre = bool(config.get_bin('tivolibre')) has_tivodecode = bool(config.get_bin('tivodecode')) use_tivolibre = False if has_tivolibre and bool(config.get_server('tivolibre_upload', True)): use_tivolibre = True if 'Format' in query: mime = query['Format'][0] needs_tivodecode = (((is_tivo_file and is_tivo_ts) or (is_tivo_file and not has_tivolibre)) and mime == 'video/mpeg') compatible = (not needs_tivodecode and transcode.tivo_compatible(path, tsn, mime)[0]) try: # "bytes=XXX-" offset = int(handler.headers.get('Range')[6:-1]) except: offset = 0 if needs_tivodecode: valid = bool((has_tivodecode or has_tivolibre) and tivo_mak) else: valid = True if valid and offset: valid = ((compatible and offset < os.path.getsize(path)) or (not compatible and transcode.is_resumable(path, offset))) if status[tivo_name][path]: # Don't let the TiVo loop over and over in the same spot valid = (offset != status[tivo_name][path]['offset']) status[tivo_name][path]['error'] = 'Repeat offset call' #faking = (mime in ['video/x-tivo-mpeg-ts', 'video/x-tivo-mpeg'] and faking = (mime == 'video/x-tivo-mpeg' and not (is_tivo_file and compatible)) thead = '' if faking: thead = self.tivo_header(tsn, path, mime) size = os.path.getsize(path) + len(thead) if compatible: handler.send_response(200) handler.send_header('Content-Length', size - offset) handler.send_header('Content-Range', 'bytes %d-%d/%d' % (offset, size - offset - 1, size)) else: handler.send_response(206) handler.send_header('Transfer-Encoding', 'chunked') handler.send_header('Content-Type', mime) handler.end_headers() logger.info('[%s] Start sending "%s" to %s' % (time.strftime('%d/%b/%Y %H:%M:%S'), path, tivo_name)) if valid: start_time = time.time() last_interval = start_time now = start_time count = 0 output = 0 if path in status[tivo_name]: status[tivo_name][path]['active'] = True status[tivo_name][path]['offset'] = offset else: status[tivo_name][path] = {'active': True, 'decrypting': False, 'transcoding': False, 'offset': offset, 'start': start_time, 'end': start_time, 'rate': 0, 'size': size, 'output': 0, 'error': '', } if compatible: logger.debug('"%s" is tivo compatible' % path) f = open(path, 'rb') tivolibre = None if not offset: if faking: handler.wfile.write(thead) count += len(thead) output += len(thead) elif tivo_header_size > 0: block = f.read(tivo_header_size) handler.wfile.write(block) count += len(block) output += len(block) try: if is_tivo_file and use_tivolibre: status[tivo_name][path]['decrypting'] = True f.close() tivolibre_path = config.get_bin('tivolibre') tcmd = [tivolibre_path, '-m', tivo_mak, '-i', path] tivolibre = subprocess.Popen(tcmd, stdout=subprocess.PIPE, bufsize=(512 * 1024)) f = tivolibre.stdout if offset: if tivolibre: raise Exception('tivolibre does not support offset') offset -= len(thead) f.seek(offset) while True: block = f.read(512 * 1024) if not block: break handler.wfile.write(block) count += len(block) output += len(block) now = time.time() elapsed = now - last_interval if elapsed >= 1: status[tivo_name][path]['rate'] = (count * 8.0) / elapsed status[tivo_name][path]['output'] += count count = 0 last_interval = now if tivolibre: tivolibre.wait() except Exception as msg: status[tivo_name][path]['error'] = str(msg) if tivolibre: tivolibre.kill() tivolibre.wait() logger.info(msg) f.close() else: logger.debug('"%s" is not tivo compatible' % path) status[tivo_name][path]['transcoding'] = True if offset: count = transcode.resume_transfer(path, handler.wfile, offset, status[tivo_name][path]) else: count = transcode.transcode(False, path, handler.wfile, status[tivo_name][path], is_tivo_file, tsn, mime, thead) end_time = time.time() elapsed = end_time - status[tivo_name][path]['start'] rate = count * 8.0 / elapsed # bits / sec status[tivo_name][path]['active'] = False status[tivo_name][path]['end'] = end_time status[tivo_name][path]['rate'] = rate logger.info('[{timestamp:%d/%b/%Y %H:%M:%S}] Done sending "{fname}" to {tivo_name}, ' '{mbps[0]:.2f} {mbps[1]}B/s ({num_bytes[0]:.3f} {num_bytes[1]}Bytes / {seconds:.0f} s)' .format(timestamp=datetime.fromtimestamp(end_time), fname=path, tivo_name=tivo_name, num_bytes=prefix_bin_qty(count), mbps=prefix_bin_qty(rate / 8), seconds=elapsed)) else: logger.info('Invalid file "{}" requested by {}'.format(path, tivo_name)) try: if not compatible: handler.wfile.write(b'0\r\n\r\n') handler.wfile.flush() except Exception as msg: logger.exception('Exception writing an empty response for an incompatible file')
def tivo_compatible_audio(vInfo, inFile, tsn, mime=''): message = (True, '') while True: codec = vInfo.get('aCodec', '') if codec == None: debug('No audio stream detected') break if mime == 'video/mp4': if codec not in ('mpeg4aac', 'libfaad', 'mp4a', 'aac', 'ac3', 'liba52'): message = (False, 'aCodec %s not compatible' % codec) break if vInfo['aCodec'] in ('mpeg4aac', 'libfaad', 'mp4a', 'aac') and (vInfo['aCh'] == None or vInfo['aCh'] > 2): message = ( False, 'aCodec %s is only supported with 2 or less channels, the track has %s channels' % (codec, vInfo['aCh'])) break audio_lang = config.get_tsn('audio_lang', tsn) if audio_lang: if vInfo['mapAudio'][0][0] != select_audiolang(inFile, tsn)[-3:]: message = (False, '%s preferred audio track exists' % audio_lang) break if mime == 'video/bif': if codec != 'wmav2': message = (False, 'aCodec %s not compatible' % codec) break if inFile[-5:].lower() == '.tivo': break if mime == 'video/x-tivo-mpeg-ts': if codec not in ('ac3', 'liba52', 'mp2', 'aac_latm'): message = (False, 'aCodec %s not compatible' % codec) break if codec not in ('ac3', 'liba52', 'mp2'): message = (False, 'aCodec %s not compatible' % codec) break if (not vInfo['aKbps'] or int(vInfo['aKbps']) > config.getMaxAudioBR(tsn)): message = (False, '%s kbps exceeds max audio bitrate' % vInfo['aKbps']) break audio_lang = config.get_tsn('audio_lang', tsn) if audio_lang: if vInfo['mapAudio'][0][0] != select_audiolang(inFile, tsn)[-3:]: message = (False, '%s preferred audio track exists' % audio_lang) break return message
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))
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)
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')
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)
def select_audioch(tsn): ch = config.get_tsn('audio_ch', tsn) if ch: return '-ac ' + ch return ''