Example #1
0
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)
Example #2
0
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)
Example #3
0
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)
Example #4
0
 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())
Example #5
0
    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)
Example #6
0
    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())
Example #7
0
    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
Example #8
0
    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)
Example #9
0
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):
Example #10
0
def supported_format(inFile):
    if video_info(inFile)[0]:
        return True
    else:
        debug_write(__name__, fn_attr(), ['FALSE, file not supported', inFile])
        return False
Example #11
0
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
Example #12
0
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
Example #13
0
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
Example #14
0
    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
Example #15
0
    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)
Example #16
0
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):