def transcode(inFile, outFile, tsn=''): settings = {} settings['video_codec'] = select_videocodec(tsn) settings['video_br'] = select_videobr(tsn) settings['video_fps'] = select_videofps(inFile, tsn) settings['max_video_br'] = select_maxvideobr() settings['buff_size'] = select_buffsize() settings['aspect_ratio'] = ' '.join(select_aspect(inFile, tsn)) settings['audio_br'] = select_audiobr(tsn) settings['audio_fr'] = select_audiofr(inFile, tsn) settings['audio_ch'] = select_audioch(tsn) settings['audio_codec'] = select_audiocodec(inFile, tsn) settings['ffmpeg_pram'] = select_ffmpegprams(tsn) settings['format'] = select_format(tsn) cmd_string = config.getFFmpegTemplate(tsn) % settings cmd = [ffmpeg_path(), '-i', inFile] + cmd_string.split() print 'transcoding to tivo model '+tsn[:3]+' using ffmpeg command:' print ' '.join(cmd) debug_write(__name__, fn_attr(), ['ffmpeg command is ', ' '.join(cmd)]) ffmpeg = subprocess.Popen(cmd, stdout=subprocess.PIPE) try: shutil.copyfileobj(ffmpeg.stdout, outFile) except: kill(ffmpeg.pid)
def kill(pid): debug_write(__name__, fn_attr(), ['killing pid=', str(pid)]) if mswindows: win32kill(pid) else: import os, signal os.kill(pid, signal.SIGTERM)
def output_video(inFile, outFile, tsn=''): if tivo_compatable(inFile, tsn): debug_write(__name__, fn_attr(), [inFile, ' is tivo compatible']) f = file(inFile, 'rb') shutil.copyfileobj(f, outFile) f.close() else: debug_write(__name__, fn_attr(), [inFile, ' is not tivo compatible']) transcode(inFile, outFile, tsn)
def Reset(self, handler, query): config.reset() handler.server.reset() if 'last_page' in query: last_page = query['last_page'][0] else: last_page = 'Admin' subcname = query['Container'][0] cname = subcname.split('/')[0] handler.send_response(200) handler.end_headers() t = Template(file=os.path.join(SCRIPTDIR,'templates', 'redirect.tmpl')) t.container = cname t.time = '3' t.url = '/TiVoConnect?Command='+ last_page +'&Container=' + cname t.text = '<h3>The pyTivo Server has been soft reset.</h3> <br>pyTivo has reloaded the pyTivo.conf'+\ 'file and all changed should now be in effect. <br> The'+ \ '<a href="/TiVoConnect?Command='+ last_page +'&Container='+ cname +'"> previous</a> page will reload in 3 seconds.' handler.wfile.write(t) debug.debug_write(__name__, debug.fn_attr(), ['The pyTivo Server has been soft reset.']) debug.print_conf(__name__, debug.fn_attr())
def unsupported(self, query): if hack83 and 'Command' in query and 'Filter' in query: debug_write(__name__, fn_attr(), ['Unsupported request,', 'checking to see if it is video.']) command = query['Command'][0] plugin = GetPlugin('video') if ''.join(query['Filter']).find('video') >= 0 and \ hasattr(plugin, command): debug_write(__name__, fn_attr(), ['Unsupported request,', 'yup it is video', 'send to video plugin for it to sort out.']) method = getattr(plugin, command) method(self, query) return self.send_response(404) self.send_header('Content-type', 'text/html') self.end_headers() t = Template(file=os.path.join(SCRIPTDIR, 'templates', 'unsupported.tmpl')) t.query = query self.wfile.write(t)
def Reset(self, handler, query): config.reset() handler.server.reset() if 'last_page' in query: last_page = query['last_page'][0] else: last_page = 'Admin' subcname = query['Container'][0] cname = subcname.split('/')[0] handler.send_response(200) handler.end_headers() t = Template( file=os.path.join(SCRIPTDIR, 'templates', 'redirect.tmpl')) t.container = cname t.time = '3' t.url = '/TiVoConnect?Command=' + last_page + '&Container=' + cname t.text = '<h3>The pyTivo Server has been soft reset.</h3> <br>pyTivo has reloaded the pyTivo.conf'+\ 'file and all changed should now be in effect. <br> The'+ \ '<a href="/TiVoConnect?Command='+ last_page +'&Container='+ cname +'"> previous</a> page will reload in 3 seconds.' handler.wfile.write(t) debug.debug_write(__name__, debug.fn_attr(), ['The pyTivo Server has been soft reset.']) debug.print_conf(__name__, debug.fn_attr())
def hack(self, handler, query, subcname): debug_write(__name__, fn_attr(), ['new request ------------------------']) debug_write(__name__, fn_attr(), ['TiVo request is: \n', query]) queryAnchor = '' rightAnchor = '' leftAnchor = '' tsn = handler.headers.getheader('tsn', '') # not a tivo if not tsn: debug_write(__name__, fn_attr(), ['this was not a TiVo request.', 'Using default tsn.']) tsn = '123456789' # this breaks up the anchor item request into seperate parts if 'AnchorItem' in query and query['AnchorItem'] != ['Hack8.3']: queryAnchor = urllib.unquote_plus(''.join(query['AnchorItem'])) if queryAnchor.find('Container=') >= 0: # This is a folder queryAnchor = queryAnchor.split('Container=')[-1] else: # This is a file queryAnchor = queryAnchor.split('/', 1)[-1] leftAnchor, rightAnchor = queryAnchor.rsplit('/', 1) debug_write(__name__, fn_attr(), ['queryAnchor: ', queryAnchor, ' leftAnchor: ', leftAnchor, ' rightAnchor: ', rightAnchor]) try: path, state = self.request_history[tsn] except KeyError: # Never seen this tsn, starting new history debug_write(__name__, fn_attr(), ['New TSN.']) path = [] state = {} self.request_history[tsn] = (path, state) state['query'] = query state['page'] = '' state['time'] = int(time.time()) + 1000 debug_write(__name__, fn_attr(), ['our saved request is: \n', state['query']]) current_folder = subcname.split('/')[-1] # Begin figuring out what the request TiVo sent us means # There are 7 options that can occur # 1. at the root - This request is always accurate if len(subcname.split('/')) == 1: debug_write(__name__, fn_attr(), ['we are at the root.', 'Saving query, Clearing state[page].']) path[:] = [current_folder] state['query'] = query state['page'] = '' return query, path # 2. entering a new folder # If there is no AnchorItem in the request then we must be # entering a new folder. if 'AnchorItem' not in query: debug_write(__name__, fn_attr(), ['we are entering a new folder.', 'Saving query, setting time, setting state[page].']) path[:] = subcname.split('/') state['query'] = query state['time'] = int(time.time()) files, total, start = self.get_files(handler, query, self.video_file_filter) if files: state['page'] = files[0] else: state['page'] = '' return query, path # 3. Request a page after pyTivo sent a 302 code # we know this is the proper page if ''.join(query['AnchorItem']) == 'Hack8.3': debug_write(__name__, fn_attr(), ['requested page from 302 code.', 'Returning saved query.']) return state['query'], path # 4. this is a request for a file if 'ItemCount' in query and int(''.join(query['ItemCount'])) == 1: debug_write(__name__, fn_attr(), ['requested a file']) # Everything in this request is right except the container query['Container'] = ['/'.join(path)] state['page'] = '' return query, path # All remaining requests could be a second erroneous request for # each of the following we will pause to see if a correct # request is coming right behind it. # Sleep just in case the erroneous request came first this # allows a proper request to be processed first debug_write(__name__, fn_attr(), ['maybe erroneous request, sleeping.']) time.sleep(.25) # 5. scrolling in a folder # This could be a request to exit a folder or scroll up or down # within the folder # First we have to figure out if we are scrolling if 'AnchorOffset' in query: debug_write(__name__, fn_attr(), ['Anchor offset was in query.', 'leftAnchor needs to match ', '/'.join(path)]) if leftAnchor == str('/'.join(path)): debug_write(__name__, fn_attr(), ['leftAnchor matched.']) query['Container'] = ['/'.join(path)] files, total, start = self.get_files(handler, query, self.video_file_filter) debug_write(__name__, fn_attr(), ['saved page is= ', state['page'], ' top returned file is= ', files[0]]) # If the first file returned equals the top of the page # then we haven't scrolled pages if files[0] != str(state['page']): debug_write(__name__, fn_attr(), ['this is scrolling within a folder.']) state['page'] = files[0] return query, path # The only remaining options are exiting a folder or this is a # erroneous second request. # 6. this an extraneous request # this came within a second of a valid request; just use that # request. if (int(time.time()) - state['time']) <= 1: debug_write(__name__, fn_attr(), ['erroneous request, send a 302 error']) return None, path # 7. this is a request to exit a folder # this request came by itself; it must be to exit a folder else: debug_write(__name__, fn_attr(), ['over 1 second,', 'must be request to exit folder']) path.pop() state['query'] = {'Command': query['Command'], 'SortOrder': query['SortOrder'], 'ItemCount': query['ItemCount'], 'Filter': query['Filter'], 'Container': ['/'.join(path)]} return None, path # just in case we missed something. debug_write(__name__, fn_attr(), ['ERROR, should not have made it here. ', 'Trying to recover.']) return state['query'], path
def QueryContainer(self, handler, query): tsn = handler.headers.getheader('tsn', '') subcname = query['Container'][0] # If you are running 8.3 software you want to enable hack83 # in the config file if config.getHack83(): print '=' * 73 query, hackPath = self.hack(handler, query, subcname) hackPath = '/'.join(hackPath) print 'Tivo said:', subcname, '|| Hack said:', hackPath debug_write(__name__, fn_attr(), ['Tivo said: ', subcname, ' || Hack said: ', hackPath]) subcname = hackPath if not query: debug_write(__name__, fn_attr(), ['sending 302 redirect page']) handler.send_response(302) handler.send_header('Location ', 'http://' + handler.headers.getheader('host') + '/TiVoConnect?Command=QueryContainer&' + 'AnchorItem=Hack8.3&Container=' + hackPath) handler.end_headers() return # End hack mess cname = subcname.split('/')[0] if not handler.server.containers.has_key(cname) or \ not self.get_local_path(handler, query): handler.send_response(404) handler.end_headers() return container = handler.server.containers[cname] precache = container.get('precache', 'False').lower() == 'true' files, total, start = self.get_files(handler, query, self.video_file_filter) videos = [] local_base_path = self.get_local_base_path(handler, query) for file in files: mtime = datetime.fromtimestamp(os.stat(file).st_mtime) video = VideoDetails() video['captureDate'] = hex(int(time.mktime(mtime.timetuple()))) video['name'] = os.path.split(file)[1] video['path'] = file video['part_path'] = file.replace(local_base_path, '', 1) video['title'] = os.path.split(file)[1] video['is_dir'] = self.__isdir(file) if video['is_dir']: video['small_path'] = subcname + '/' + video['name'] video['total_items'] = self.__total_items(file) else: if precache or len(files) == 1 or file in transcode.info_cache: video['valid'] = transcode.supported_format(file) if video['valid']: video.update(self.__metadata_full(file, tsn)) else: video['valid'] = True video.update(self.__metadata_basic(file)) videos.append(video) handler.send_response(200) handler.end_headers() t = Template(file=os.path.join(SCRIPTDIR,'templates', 'container.tmpl')) t.container = cname t.name = subcname t.total = total t.start = start t.videos = videos t.quote = quote t.escape = escape t.crc = zlib.crc32 t.guid = config.getGUID() t.tivos = handler.tivos handler.wfile.write(t)
import time import mind from debug import debug_write, fn_attr SCRIPTDIR = os.path.dirname(__file__) CLASS_NAME = 'Video' extfile = os.path.join(SCRIPTDIR, 'video.ext') try: extensions = file(extfile).read().split() except: extensions = None if config.getHack83(): debug_write(__name__, fn_attr(), ['Hack83 is enabled.']) class Video(Plugin): CONTENT_TYPE = 'x-container/tivo-videos' # Used for 8.3's broken requests count = 0 request_history = {} def pre_cache(self, full_path): if Video.video_file_filter(self, full_path): transcode.supported_format(full_path) def video_file_filter(self, full_path, type=None): if os.path.isdir(full_path):
def supported_format(inFile): if video_info(inFile)[0]: return True else: debug_write(__name__, fn_attr(), ['FALSE, file not supported', inFile]) return False
def video_info(inFile): mtime = os.stat(inFile).st_mtime if inFile != videotest: if inFile in info_cache and info_cache[inFile][0] == mtime: debug_write(__name__, fn_attr(), ['CACHE HIT!', inFile]) return info_cache[inFile][1] if (inFile[-5:]).lower() == '.tivo': info_cache[inFile] = (mtime, (True, True, True, True, True, True, True, True, True, True)) debug_write(__name__, fn_attr(), ['VALID, ends in .tivo.', inFile]) return True, True, True, True, True, True, True, True, True, True cmd = [ffmpeg_path(), '-i', inFile ] ffmpeg = subprocess.Popen(cmd, stderr=subprocess.PIPE, stdout=subprocess.PIPE, stdin=subprocess.PIPE) # wait 10 sec if ffmpeg is not back give up for i in xrange(200): time.sleep(.05) if not ffmpeg.poll() == None: break if ffmpeg.poll() == None: kill(ffmpeg.pid) info_cache[inFile] = (mtime, (None, None, None, None, None, None, None, None, None, None)) return None, None, None, None, None, None, None, None, None, None output = ffmpeg.stderr.read() debug_write(__name__, fn_attr(), ['ffmpeg output=', output]) rezre = re.compile(r'.*Video: ([^,]+),.*') x = rezre.search(output) if x: codec = x.group(1) else: info_cache[inFile] = (mtime, (None, None, None, None, None, None, None, None, None, None)) debug_write(__name__, fn_attr(), ['failed at video codec']) return None, None, None, None, None, None, None, None, None, None rezre = re.compile(r'.*Video: .+, (\d+)x(\d+)[, ].*') x = rezre.search(output) if x: width = int(x.group(1)) height = int(x.group(2)) else: info_cache[inFile] = (mtime, (None, None, None, None, None, None, None, None, None, None)) debug_write(__name__, fn_attr(), ['failed at width/height']) return None, None, None, None, None, None, None, None, None, None rezre = re.compile(r'.*Video: .+, (.+) (?:fps|tb).*') x = rezre.search(output) if x: fps = x.group(1) else: info_cache[inFile] = (mtime, (None, None, None, None, None, None, None, None, None, None)) debug_write(__name__, fn_attr(), ['failed at fps']) return None, None, None, None, None, None, None, None, None, None # Allow override only if it is mpeg2 and frame rate was doubled to 59.94 if (not fps == '29.97') and (codec == 'mpeg2video'): # First look for the build 7215 version rezre = re.compile(r'.*film source: 29.97.*') x = rezre.search(output.lower() ) if x: debug_write(__name__, fn_attr(), ['film source: 29.97 setting fps to 29.97']) fps = '29.97' else: # for build 8047: rezre = re.compile(r'.*frame rate differs from container frame rate: 29.97.*') debug_write(__name__, fn_attr(), ['Bug in VideoReDo']) x = rezre.search(output.lower() ) if x: fps = '29.97' durre = re.compile(r'.*Duration: (.{2}):(.{2}):(.{2})\.(.),') d = durre.search(output) if d: millisecs = ((int(d.group(1))*3600) + (int(d.group(2))*60) + int(d.group(3)))*1000 + (int(d.group(4))*100) else: millisecs = 0 #get bitrate of source for tivo compatibility test. rezre = re.compile(r'.*bitrate: (.+) (?:kb/s).*') x = rezre.search(output) if x: kbps = x.group(1) else: kbps = None debug_write(__name__, fn_attr(), ['failed at kbps']) #get audio bitrate of source for tivo compatibility test. rezre = re.compile(r'.*Audio: .+, (.+) (?:kb/s).*') x = rezre.search(output) if x: akbps = x.group(1) else: akbps = None debug_write(__name__, fn_attr(), ['failed at akbps']) #get audio codec of source for tivo compatibility test. rezre = re.compile(r'.*Audio: ([^,]+),.*') x = rezre.search(output) if x: acodec = x.group(1) else: acodec = None debug_write(__name__, fn_attr(), ['failed at acodec']) #get audio frequency of source for tivo compatibility test. rezre = re.compile(r'.*Audio: .+, (.+) (?:Hz).*') x = rezre.search(output) if x: afreq = x.group(1) else: afreq = None debug_write(__name__, fn_attr(), ['failed at afreq']) #get par. rezre = re.compile(r'.*Video: .+PAR ([0-9]+):([0-9]+) DAR [0-9:]+.*') x = rezre.search(output) if x and x.group(1)!="0" and x.group(2)!="0": vpar = float(x.group(1))/float(x.group(2)) else: vpar = None info_cache[inFile] = (mtime, (codec, width, height, fps, millisecs, kbps, akbps, acodec, afreq, vpar)) debug_write(__name__, fn_attr(), ['Codec=', codec, ' width=', width, ' height=', height, ' fps=', fps, ' millisecs=', millisecs, ' kbps=', kbps, ' akbps=', akbps, ' acodec=', acodec, ' afreq=', afreq, ' par=', vpar]) return codec, width, height, fps, millisecs, kbps, akbps, acodec, afreq, vpar
def tivo_compatable(inFile, tsn = ''): supportedModes = [[720, 480], [704, 480], [544, 480], [480, 480], [352, 480]] type, width, height, fps, millisecs, kbps, akbps, acodec, afreq, vpar = video_info(inFile) #print type, width, height, fps, millisecs, kbps, akbps, acodec if (inFile[-5:]).lower() == '.tivo': debug_write(__name__, fn_attr(), ['TRUE, ends with .tivo.', inFile]) return True if not type == 'mpeg2video': #print 'Not Tivo Codec' debug_write(__name__, fn_attr(), ['FALSE, type', type, 'not mpeg2video.', inFile]) return False if os.path.splitext(inFile)[-1].lower() in ('.ts', '.mpv'): debug_write(__name__, fn_attr(), ['FALSE, ext', os.path.splitext(inFile)[-1],\ 'not tivo compatible.', inFile]) return False if acodec == 'dca': debug_write(__name__, fn_attr(), ['FALSE, acodec', acodec, ', not supported.', inFile]) return False if acodec != None: if not akbps or int(akbps) > config.getMaxAudioBR(tsn): debug_write(__name__, fn_attr(), ['FALSE,', akbps, 'kbps exceeds max audio bitrate.', inFile]) return False if kbps != None: abit = max('0', akbps) if int(kbps)-int(abit) > config.strtod(config.getMaxVideoBR())/1000: debug_write(__name__, fn_attr(), ['FALSE,', kbps, 'kbps exceeds max video bitrate.', inFile]) return False else: debug_write(__name__, fn_attr(), ['FALSE,', kbps, 'kbps not supported.', inFile]) return False if config.isHDtivo(tsn): if vpar != 1.0: if config.getPixelAR(0): if vpar != None or config.getPixelAR(1) != 1.0: debug_write(__name__, fn_attr(), ['FALSE,', vpar, 'not correct PAR,', inFile]) return False debug_write(__name__, fn_attr(), ['TRUE, HD Tivo detected, skipping remaining tests', inFile]) return True if not fps == '29.97': #print 'Not Tivo fps' debug_write(__name__, fn_attr(), ['FALSE,', fps, 'fps, should be 29.97.', inFile]) return False for mode in supportedModes: if (mode[0], mode[1]) == (width, height): #print 'Is TiVo!' debug_write(__name__, fn_attr(), ['TRUE,', width, 'x', height, 'is valid.', inFile]) return True #print 'Not Tivo dimensions' debug_write(__name__, fn_attr(), ['FALSE,', width, 'x', height, 'not in supported modes.', inFile]) return False
def select_aspect(inFile, tsn = ''): TIVO_WIDTH = config.getTivoWidth(tsn) TIVO_HEIGHT = config.getTivoHeight(tsn) type, width, height, fps, millisecs, kbps, akbps, acodec, afreq, vpar = video_info(inFile) debug_write(__name__, fn_attr(), ['tsn:', tsn]) aspect169 = config.get169Setting(tsn) debug_write(__name__, fn_attr(), ['aspect169:', aspect169]) optres = config.getOptres(tsn) debug_write(__name__, fn_attr(), ['optres:', optres]) if optres: optHeight = config.nearestTivoHeight(height) optWidth = config.nearestTivoWidth(width) if optHeight < TIVO_HEIGHT: TIVO_HEIGHT = optHeight if optWidth < TIVO_WIDTH: TIVO_WIDTH = optWidth d = gcd(height,width) ratio = (width*100)/height rheight, rwidth = height/d, width/d debug_write(__name__, fn_attr(), ['File=', inFile, ' Type=', type, ' width=', width, ' height=', height, ' fps=', fps, ' millisecs=', millisecs, ' ratio=', ratio, ' rheight=', rheight, ' rwidth=', rwidth, ' TIVO_HEIGHT=', TIVO_HEIGHT, 'TIVO_WIDTH=', TIVO_WIDTH]) multiplier16by9 = (16.0 * TIVO_HEIGHT) / (9.0 * TIVO_WIDTH) multiplier4by3 = (4.0 * TIVO_HEIGHT) / (3.0 * TIVO_WIDTH) if config.isHDtivo(tsn) and optres: if config.getPixelAR(0): if vpar == None: npar = config.getPixelAR(1) else: npar = vpar # adjust for pixel aspect ratio, if set, because TiVo expects square pixels if npar<1.0: return ['-s', str(width) + 'x' + str(int(math.ceil(height/npar)))] elif npar>1.0: # FFMPEG expects width to be a multiple of two return ['-s', str(int(math.ceil(width*npar/2.0)*2)) + 'x' + str(height)] if height <= TIVO_HEIGHT: # pass all resolutions to S3, except heights greater than conf height return [] # else, resize video. if (rwidth, rheight) in [(4, 3), (10, 11), (15, 11), (59, 54), (59, 72), (59, 36), (59, 54)]: debug_write(__name__, fn_attr(), ['File is within 4:3 list.']) return ['-aspect', '4:3', '-s', str(TIVO_WIDTH) + 'x' + str(TIVO_HEIGHT)] elif ((rwidth, rheight) in [(16, 9), (20, 11), (40, 33), (118, 81), (59, 27)]) and aspect169: debug_write(__name__, fn_attr(), ['File is within 16:9 list and 16:9 allowed.']) return ['-aspect', '16:9', '-s', str(TIVO_WIDTH) + 'x' + str(TIVO_HEIGHT)] else: settings = [] #If video is wider than 4:3 add top and bottom padding if (ratio > 133): #Might be 16:9 file, or just need padding on top and bottom if aspect169 and (ratio > 135): #If file would fall in 4:3 assume it is supposed to be 4:3 if (ratio > 177):#too short needs padding top and bottom endHeight = int(((TIVO_WIDTH*height)/width) * multiplier16by9) settings.append('-aspect') settings.append('16:9') if endHeight % 2: endHeight -= 1 if endHeight < TIVO_HEIGHT * 0.99: settings.append('-s') settings.append(str(TIVO_WIDTH) + 'x' + str(endHeight)) topPadding = ((TIVO_HEIGHT - endHeight)/2) if topPadding % 2: topPadding -= 1 settings.append('-padtop') settings.append(str(topPadding)) bottomPadding = (TIVO_HEIGHT - endHeight) - topPadding settings.append('-padbottom') settings.append(str(bottomPadding)) else: #if only very small amount of padding needed, then just stretch it settings.append('-s') settings.append(str(TIVO_WIDTH) + 'x' + str(TIVO_HEIGHT)) debug_write(__name__, fn_attr(), ['16:9 aspect allowed, file is wider than 16:9 padding top and bottom', ' '.join(settings)]) else: #too skinny needs padding on left and right. endWidth = int((TIVO_HEIGHT*width)/(height*multiplier16by9)) settings.append('-aspect') settings.append('16:9') if endWidth % 2: endWidth -= 1 if endWidth < (TIVO_WIDTH-10): settings.append('-s') settings.append(str(endWidth) + 'x' + str(TIVO_HEIGHT)) leftPadding = ((TIVO_WIDTH - endWidth)/2) if leftPadding % 2: leftPadding -= 1 settings.append('-padleft') settings.append(str(leftPadding)) rightPadding = (TIVO_WIDTH - endWidth) - leftPadding settings.append('-padright') settings.append(str(rightPadding)) else: #if only very small amount of padding needed, then just stretch it settings.append('-s') settings.append(str(TIVO_WIDTH) + 'x' + str(TIVO_HEIGHT)) debug_write(__name__, fn_attr(), ['16:9 aspect allowed, file is narrower than 16:9 padding left and right\n', ' '.join(settings)]) else: #this is a 4:3 file or 16:9 output not allowed settings.append('-aspect') settings.append('4:3') endHeight = int(((TIVO_WIDTH*height)/width) * multiplier4by3) if endHeight % 2: endHeight -= 1 if endHeight < TIVO_HEIGHT * 0.99: settings.append('-s') settings.append(str(TIVO_WIDTH) + 'x' + str(endHeight)) topPadding = ((TIVO_HEIGHT - endHeight)/2) if topPadding % 2: topPadding -= 1 settings.append('-padtop') settings.append(str(topPadding)) bottomPadding = (TIVO_HEIGHT - endHeight) - topPadding settings.append('-padbottom') settings.append(str(bottomPadding)) else: #if only very small amount of padding needed, then just stretch it settings.append('-s') settings.append(str(TIVO_WIDTH) + 'x' + str(TIVO_HEIGHT)) debug_write(__name__, fn_attr(), ['File is wider than 4:3 padding top and bottom\n', ' '.join(settings)]) return settings #If video is taller than 4:3 add left and right padding, this is rare. All of these files will always be sent in #an aspect ratio of 4:3 since they are so narrow. else: endWidth = int((TIVO_HEIGHT*width)/(height*multiplier4by3)) settings.append('-aspect') settings.append('4:3') if endWidth % 2: endWidth -= 1 if endWidth < (TIVO_WIDTH * 0.99): settings.append('-s') settings.append(str(endWidth) + 'x' + str(TIVO_HEIGHT)) leftPadding = ((TIVO_WIDTH - endWidth)/2) if leftPadding % 2: leftPadding -= 1 settings.append('-padleft') settings.append(str(leftPadding)) rightPadding = (TIVO_WIDTH - endWidth) - leftPadding settings.append('-padright') settings.append(str(rightPadding)) else: #if only very small amount of padding needed, then just stretch it settings.append('-s') settings.append(str(TIVO_WIDTH) + 'x' + str(TIVO_HEIGHT)) debug_write(__name__, fn_attr(), ['File is taller than 4:3 padding left and right\n', ' '.join(settings)]) return settings
def hack(self, handler, query, subcname): debug_write(__name__, fn_attr(), ['new request ------------------------']) debug_write(__name__, fn_attr(), ['TiVo request is: \n', query]) queryAnchor = '' rightAnchor = '' leftAnchor = '' tsn = handler.headers.getheader('tsn', '') # not a tivo if not tsn: debug_write(__name__, fn_attr(), ['this was not a TiVo request.', 'Using default tsn.']) tsn = '123456789' # this breaks up the anchor item request into seperate parts if 'AnchorItem' in query and query['AnchorItem'] != ['Hack8.3']: queryAnchor = urllib.unquote_plus(''.join(query['AnchorItem'])) if queryAnchor.find('Container=') >= 0: # This is a folder queryAnchor = queryAnchor.split('Container=')[-1] else: # This is a file queryAnchor = queryAnchor.split('/', 1)[-1] leftAnchor, rightAnchor = queryAnchor.rsplit('/', 1) debug_write(__name__, fn_attr(), [ 'queryAnchor: ', queryAnchor, ' leftAnchor: ', leftAnchor, ' rightAnchor: ', rightAnchor ]) try: path, state = self.request_history[tsn] except KeyError: # Never seen this tsn, starting new history debug_write(__name__, fn_attr(), ['New TSN.']) path = [] state = {} self.request_history[tsn] = (path, state) state['query'] = query state['page'] = '' state['time'] = int(time.time()) + 1000 debug_write(__name__, fn_attr(), ['our saved request is: \n', state['query']]) current_folder = subcname.split('/')[-1] # Begin figuring out what the request TiVo sent us means # There are 7 options that can occur # 1. at the root - This request is always accurate if len(subcname.split('/')) == 1: debug_write( __name__, fn_attr(), ['we are at the root.', 'Saving query, Clearing state[page].']) path[:] = [current_folder] state['query'] = query state['page'] = '' return query, path # 2. entering a new folder # If there is no AnchorItem in the request then we must be # entering a new folder. if 'AnchorItem' not in query: debug_write(__name__, fn_attr(), [ 'we are entering a new folder.', 'Saving query, setting time, setting state[page].' ]) path[:] = subcname.split('/') state['query'] = query state['time'] = int(time.time()) files, total, start = self.get_files(handler, query, self.video_file_filter) if files: state['page'] = files[0] else: state['page'] = '' return query, path # 3. Request a page after pyTivo sent a 302 code # we know this is the proper page if ''.join(query['AnchorItem']) == 'Hack8.3': debug_write( __name__, fn_attr(), ['requested page from 302 code.', 'Returning saved query.']) return state['query'], path # 4. this is a request for a file if 'ItemCount' in query and int(''.join(query['ItemCount'])) == 1: debug_write(__name__, fn_attr(), ['requested a file']) # Everything in this request is right except the container query['Container'] = ['/'.join(path)] state['page'] = '' return query, path # All remaining requests could be a second erroneous request for # each of the following we will pause to see if a correct # request is coming right behind it. # Sleep just in case the erroneous request came first this # allows a proper request to be processed first debug_write(__name__, fn_attr(), ['maybe erroneous request, sleeping.']) time.sleep(.25) # 5. scrolling in a folder # This could be a request to exit a folder or scroll up or down # within the folder # First we have to figure out if we are scrolling if 'AnchorOffset' in query: debug_write(__name__, fn_attr(), [ 'Anchor offset was in query.', 'leftAnchor needs to match ', '/'.join(path) ]) if leftAnchor == str('/'.join(path)): debug_write(__name__, fn_attr(), ['leftAnchor matched.']) query['Container'] = ['/'.join(path)] files, total, start = self.get_files(handler, query, self.video_file_filter) debug_write(__name__, fn_attr(), [ 'saved page is= ', state['page'], ' top returned file is= ', files[0] ]) # If the first file returned equals the top of the page # then we haven't scrolled pages if files[0] != str(state['page']): debug_write(__name__, fn_attr(), ['this is scrolling within a folder.']) state['page'] = files[0] return query, path # The only remaining options are exiting a folder or this is a # erroneous second request. # 6. this an extraneous request # this came within a second of a valid request; just use that # request. if (int(time.time()) - state['time']) <= 1: debug_write(__name__, fn_attr(), ['erroneous request, send a 302 error']) return None, path # 7. this is a request to exit a folder # this request came by itself; it must be to exit a folder else: debug_write(__name__, fn_attr(), ['over 1 second,', 'must be request to exit folder']) path.pop() state['query'] = { 'Command': query['Command'], 'SortOrder': query['SortOrder'], 'ItemCount': query['ItemCount'], 'Filter': query['Filter'], 'Container': ['/'.join(path)] } return None, path # just in case we missed something. debug_write( __name__, fn_attr(), ['ERROR, should not have made it here. ', 'Trying to recover.']) return state['query'], path
def QueryContainer(self, handler, query): tsn = handler.headers.getheader('tsn', '') subcname = query['Container'][0] # If you are running 8.3 software you want to enable hack83 # in the config file if config.getHack83(): print '=' * 73 query, hackPath = self.hack(handler, query, subcname) hackPath = '/'.join(hackPath) print 'Tivo said:', subcname, '|| Hack said:', hackPath debug_write(__name__, fn_attr(), ['Tivo said: ', subcname, ' || Hack said: ', hackPath]) subcname = hackPath if not query: debug_write(__name__, fn_attr(), ['sending 302 redirect page']) handler.send_response(302) handler.send_header( 'Location ', 'http://' + handler.headers.getheader('host') + '/TiVoConnect?Command=QueryContainer&' + 'AnchorItem=Hack8.3&Container=' + hackPath) handler.end_headers() return # End hack mess cname = subcname.split('/')[0] if not handler.server.containers.has_key(cname) or \ not self.get_local_path(handler, query): handler.send_response(404) handler.end_headers() return container = handler.server.containers[cname] precache = container.get('precache', 'False').lower() == 'true' files, total, start = self.get_files(handler, query, self.video_file_filter) videos = [] local_base_path = self.get_local_base_path(handler, query) for file in files: mtime = datetime.fromtimestamp(os.stat(file).st_mtime) video = VideoDetails() video['captureDate'] = hex(int(time.mktime(mtime.timetuple()))) video['name'] = os.path.split(file)[1] video['path'] = file video['part_path'] = file.replace(local_base_path, '', 1) video['title'] = os.path.split(file)[1] video['is_dir'] = self.__isdir(file) if video['is_dir']: video['small_path'] = subcname + '/' + video['name'] video['total_items'] = self.__total_items(file) else: if precache or len(files) == 1 or file in transcode.info_cache: video['valid'] = transcode.supported_format(file) if video['valid']: video.update(self.__metadata_full(file, tsn)) else: video['valid'] = True video.update(self.__metadata_basic(file)) videos.append(video) handler.send_response(200) handler.end_headers() t = Template( file=os.path.join(SCRIPTDIR, 'templates', 'container.tmpl')) t.container = cname t.name = subcname t.total = total t.start = start t.videos = videos t.quote = quote t.escape = escape t.crc = zlib.crc32 t.guid = config.getGUID() t.tivos = handler.tivos handler.wfile.write(t)
import time import mind from debug import debug_write, fn_attr SCRIPTDIR = os.path.dirname(__file__) CLASS_NAME = 'Video' extfile = os.path.join(SCRIPTDIR, 'video.ext') try: extensions = file(extfile).read().split() except: extensions = None if config.getHack83(): debug_write(__name__, fn_attr(), ['Hack83 is enabled.']) class Video(Plugin): CONTENT_TYPE = 'x-container/tivo-videos' # Used for 8.3's broken requests count = 0 request_history = {} def pre_cache(self, full_path): if Video.video_file_filter(self, full_path): transcode.supported_format(full_path) def video_file_filter(self, full_path, type=None):