Exemple #1
0
    def loadOptions(self):
        '''
        Load all the options from the qube job object into the Job model.
        '''

        job = self.job
        seqFile = self.loadOption('sequence', required=True, isFullPath=True)
        job.audioFile = self.loadOption('audioFile',
                                        required=False,
                                        isFullPath=True)
        job.outputFile = self.loadOption('outputFile',
                                         required=True,
                                         isFolderPath=True)
        job.preset = self.loadOption('preset', required=True, isFullPath=True)
        job.selfContained = self.loadOption('selfContained', isBool=True)
        job.smartUpdate = self.loadOption('smartUpdate', isBool=True)
        job.fillMissingFrames = self.loadOption('fillMissingFrames',
                                                isBool=True)
        job.transcoderFolder = self.loadOption('transcoderFolder',
                                               required=True)
        job.frameRange = self.loadOption('frameRange', required=True)

        if self.errors:
            logger.error('Unable to load job options:\n\t' +
                         '\n\t'.join(self.errors))
        else:
            self.job.sequence = sequenceTools.Sequence(seqFile)
            logger.info('Job Options Loaded Successfully')

        logger.debug('Job after loading all options: \n' + str(job))
Exemple #2
0
 def setupSequences(self):
     self.mySequences = []
     for outputPath in self.outputs:
         logging.debug("Output Path: " + str(outputPath))
         firstFrame = outputPath.replace("[", "").replace("]", "")
         firstFrame = firstFrame.replace("#", "0")
         self.mySequences.append(sequenceTools.Sequence(firstFrame))
Exemple #3
0
 def onSequenceUpdate(self, evt=None):
     if self.initComplete:
         sequence = self.itemMap['imageSequence'].GetValue()
         self.loadRecentSettings(sequence)
         if len(self.recentSettings) > 0:
             self.applyRecentSetting()
         mySequence = sequenceTools.Sequence(sequence)
         result = mySequence.getBounds()
         frameRange = result['start'] + '-' + result['end']
         self.itemMap['frameRange'].SetValue(frameRange)
Exemple #4
0
def setupSequenceJob(qubeJobTemplate,
                     sequenceInitFile,
                     outputFile,
                     preset,
                     selfContained=True,
                     frameRange='ALL',
                     audioFile='',
                     smartUpdate=True,
                     fillMissingFrames=True,
                     transcoderFolder='',
                     segmentDuration=200,
                     maxSegmentsPerOutput=20,
                     maxSegmentTolerance=5):
    '''
    Setup a qube job dictionary based on the input.
    Required Inputs:
        qubeJobTemplate (dictionary)
            Template qube job dictionary to build from.
        sequenceInitFile (string)
            One image from the input image sequence.
            Can be any image from the sequence.
        outputFile (string)
            The destination file for the output
            of the transcoder.
        preset (string)
            The blender file that serves as the template
            for the transcoding process.
    Optional:
        selfContained (boolean)
            Determines if the outputFile should be a
            reference quicktime movie or self-contained.
            Self-contained movies are much larger and take
            more time to create.  Referenced quicktimes
            are much smaller, and much faster to create.
            However, referenced quicktimes must maintain
            their connectiong their associated inputs.
        frameRange (string)
            The output frame range to render from the input
            image sequence. Ex: 1-10
        audioFile (string)
            The audio file to be added to the output file.
            This audio should match the exact timeline of
            the input image sequence.
        smartUpdate (boolean)
            Automatically update only the segments and outputs
            that have been changed since the last transcode.
        transcoderFolder (string)
            The folder in which to store all files related
            to the transcoding process.  This includes the
            segmented movies and the blender projects. If
            creating a referenced output file, these are
            the segments that movie will reference.
        fillMissingFrames (boolean)
            Automatically fill in missing frames with the
            last frame that exists.  This is useful for
            creating quicktimes from sequences rendered on
            every nth frame.
    Advanced:
        segmentDuration (integer)
            Frame count for each segment.
        maxSegmentsPerOutput (integer)
            Maximum number of segments that can be in each
            output file.  If the number of segments needed
            for the sequence exceeds this amount, the output
            file is split into multiple segments of this
            length.
        maxSegmentTolerance (integer)
            If the maxSegmentsPerOutput limit is reached,
            check that the input sequence exceeds this tolerance
            value as well. If not, keep the outputFile as one file.

    Agenda
        The agenda is setup in 3 main sections:
            Initialization:
                Purpose
                    This single subjobs loads the input sequence
                    into the provided blender scene preset.
                    This is done once, then all subsequent
                    jobs reference the resulting scene file.
                Package
                    None
                resultPackage
                    None
                Naming
                    Initialize
            Segments:
                Purpose
                    These subjobs each create their assigned
                    segment of the image sequence.
                Package
                    frameRange (string)
                        Range of frames to render for this segment.
                    segmentFile (string)
                        Destination path for the segment file.
                resultPackage
                    changes (boolean)
                        Returns if any changes were made for
                        this segment.
                    segmentFile (string)
                        Destination path for the segment file
                        that actually rendered.  Sometimes file
                        issues occur where the output file can't
                        be overwritten, so we automatically
                        compensate for this.
                Naming
                    Segment: (frameRange)
            Final Outputs:
                Purpose
                    These subjobs render the output files.
                    They are split up based on the number of segments
                    and the max segments per output.  They are placed
                    in the agenda right after their dependent segments
                    have been processed.
                Package
                    segmentSubjobs (list of strings)
                        List of the names of the dependant
                        segment subjobs.
                    outputFile (string)
                        destination for the output
                resultPackage
                    outputPaths (string)
                        Path to the final output file.
                Naming
                    Output: (outputFile)

    Callbacks
        Callbacks are added to unblock subjobs when they are
        ready to be processed.
            Initialization subjob completion
                Once the initialization is complete, all
                segment subjobs are unblocked.
            Segment subjobs complete.
                Once all segments that pertain to a final
                output are complete, that output subjob
                is unblocked.
            Job retried
                If the job is retried





    '''
    ''' ---- Pre-Processing For Agenda ---- '''

    logger.debug('Setup Sequence: ' + str(locals()))
    ''' General '''
    mySequence = sequenceTools.Sequence(sequenceInitFile, frameRange)
    sequenceName = mySequence.getName()
    if not transcoderFolder:
        transcoderFolder = os.path.join(os.path.dirname(outputFile),
                                        '_Transcoder/')
    ''' Initialize '''
    init = qb.Work()
    init['name'] = 'Initialize'
    ''' Segments

    Use the qube chunk method to split up the frame range.
    Then prep each segment:
        Add the frameRange to the package.
        Add the segmentFile to the package.
        Change the subjob name to Segment: (frameRange)
        Submit as blocked, because they will be unblocked
            once the initialize command is completed.
    '''
    segments = qb.genchunks(segmentDuration,
                            '1-' + str(mySequence.getDuration()))
    for segment in segments:
        segment['package'] = {}
        segment['package']['frameRange'] = segment['name']

        outputFolder, outputName, outputExtension = splitPath(outputFile)
        segmentFile = os.path.join(transcoderFolder, 'Segments/')
        segmentFile += outputName + '/'
        segmentFile += "Segment" + segment['name'].split(
            '-')[0] + outputExtension
        segment['package']['segmentFile'] = segmentFile

        segment['status'] = 'blocked'
        segment['name'] = 'Segment:' + segment['name']
    ''' Final Outputs '''
    finalOutputSegments = chunkWithTolerance(segments, maxSegmentsPerOutput,
                                             maxSegmentTolerance)

    finalOutputs = []
    count = 1
    for outputSegment in finalOutputSegments:
        output = qb.Work()
        output['package'] = {}

        segmentSubjobs = []
        for segment in outputSegment:
            segmentSubjobs.append(segment['name'])
        output['package']['segmentSubjobs'] = segmentSubjobs

        outputFolder, outputName, outputExtension = splitPath(outputFile)
        finalOutputFile = outputFolder + outputName
        if len(finalOutputSegments) > 1:
            finalOutputFile += '_' + chr(64 + count)
        finalOutputFile += outputExtension
        output['package']['outputFile'] = finalOutputFile

        output['status'] = 'blocked'
        output['name'] = 'Output: ' + os.path.basename(finalOutputFile)

        count += 1

        finalOutputs.append(output)
    '''
    Callbacks
        1 - Unblock the segments when the initialize command is completed.
        2 - Unblock the outputs when the dependant segments are completed.
    '''

    callbacks = []
    ''' Unblock Segments '''
    callback = {}
    callback['triggers'] = 'complete-work-self-Initialize'
    callback['language'] = 'python'

    code = 'import qb\n'
    for segment in segments:
        code += '%s%s%s' % ('\nqb.workunblock(\'%s:', segment['name'],
                            '\' % qb.jobid())')
    code += '\nqb.unblock(qb.jobid())'
    callback['code'] = code

    callbacks.append(callback)
    ''' Unblock Outputs '''
    for finalOutput in finalOutputs:
        callback = {}
        triggers = []

        for segment in finalOutput['package']['segmentSubjobs']:
            triggers.append('complete-work-self-' + segment)
        callback['triggers'] = ' and '.join(triggers)
        callback['language'] = 'python'

        code = 'import qb\n'
        code += '%s%s%s' % ('\nqb.workunblock(\'%s:', finalOutput['name'],
                            '\' % qb.jobid())')
        code += '\nqb.unblock(qb.jobid())'
        callback['code'] = code

        callbacks.append(callback)
    ''' ---- Now put the job together ---- '''

    job = qubeJobTemplate
    ''' General '''
    job['name'] = 'Quicktime: ' + sequenceName
    job['prototype'] = 'Submit Transcoder'
    ''' Package '''
    job['package'] = {}
    job['package']['sequence'] = sequenceInitFile
    job['package']['audioFile'] = audioFile
    job['package']['outputFile'] = outputFile
    job['package']['preset'] = preset
    job['package']['selfContained'] = selfContained
    job['package']['smartUpdate'] = smartUpdate
    job['package']['fillMissingFrames'] = fillMissingFrames
    job['package']['frameRange'] = '1-' + str(mySequence.getDuration())
    job['package']['transcoderFolder'] = transcoderFolder
    ''' Agenda '''
    job['agenda'] = []
    job['agenda'].append(init)
    job['agenda'].extend(segments)
    ''' Place the final outputs after their last segment. '''
    for outputNum, output in enumerate(finalOutputs):
        lastSegmentName = output['package']['segmentSubjobs'][-1]
        lastSegmentIndex = None
        for index, segment in enumerate(segments):
            if segment['name'] == lastSegmentName:
                lastSegmentIndex = index
                break
        if lastSegmentIndex != None:
            job['agenda'].insert(
                lastSegmentIndex + 2 + outputNum,
                output)  # +2 for Initialization and last segment
        else:
            logger.error("ERROR: Unable to find last segment for output " +
                         output['name'])
    ''' Callbacks '''
    if not job.get('callbacks', None):
        job['callbacks'] = []
    job['callbacks'].extend(callbacks)

    return job
Exemple #5
0
def main():
    logger.info("Blender Loaded, Processing...")
    exitCode = 7
    ''' Get the arguments after the '--' separator. '''
    args = sys.argv[(sys.argv.index('--') + 1):]
    logger.debug("Arguments: \n\t" + '\n\t'.join(args))

    try:
        sequence = str(args[0])
        sceneFile = str(args[1])
        fillMissingFrames = bool(args[2])
    except:
        logger.error('Invalid Input Parameters.')

    if sequence:
        ''' Load the Image Sequence files from the folder into a dictionary. '''
        logger.info("Loading sequence...")
        mySequence = sequenceTools.Sequence(sequence)
        '''
        Create an array containing dictionaries for each frame.
        {'name':frameFileName}
        '''
        myFiles = []
        frameNumbers = mySequence.getFrames(fillMissing=fillMissingFrames)
        for pathToFrame in mySequence.getFrameFilenames(frameNumbers):
            myFrame = {'name': os.path.basename(pathToFrame)}
            myFiles.append(myFrame)

        logger.debug('Image Sequence File List: ' + str(myFiles))

        if (len(myFiles) < 1):
            logger.error('No sequence files in folder.')
            sys.exit(1)
        else:
            '''
            Check for missing frames. If fillMissingFrames is on, then we
            continue with missing frames.
            '''
            missingFrames = mySequence.getMissingFrames()
            if missingFrames:
                logger.warning('Sequence Missing Frames: ' +
                               mySequence.convertListToRanges(missingFrames))
            if fillMissingFrames:
                logger.warning(
                    'Fill missing frames enabled, ignoring missing frames.')

            if len(missingFrames) < 0 or fillMissingFrames:
                logger.info('Sequence loaded!')

                logger.info('Setting up blender scene...')
                ''' Setup an Image Strip for the input image sequence. '''
                bpy.ops.sequencer.image_strip_add( \
                        directory = mySequence.folder, \
                        files = myFiles, \
                        frame_start = 0, \
                        channel = 2, \
                        filemode = 9)
                '''
                Check the premultiply checkbox so the alpha
                from the image sequence to show up properly on the quicktime.
                '''
                myscene = bpy.data.scenes[0]
                stripName = myFiles[0].get("name")
                if (len(stripName) > 21):
                    stripName = stripName[
                        0:21]  # Image Strip names are limited to 21 characters
                mystrip = myscene.sequence_editor.sequences[stripName]
                mystrip.use_premultiply = True
                ''' Set the length of the scene to the length of the sequence. '''
                myscene.frame_end = mystrip.frame_final_duration

                # Save the scene for rendering the segments
                bpy.ops.wm.save_mainfile(filepath=sceneFile, compress=True)
                logger.info('Blender Scene Saved to ' + sceneFile)

                logger.info('Blender Scene Complete!')
                exitCode = 0

        return exitCode
Exemple #6
0
def postDialog(cmdjob, values):
    '''
    Prepare the output of the dialog.
    Each rqitem is separated into its own job.
    Store all history related info to a separate plist file
        Project History
        Email History
    
    Contents of each job:
        Universal:
            sourceProjectPath - original AE project file
            renderProjectPath - AE Project to render from
            user - set to email address contents before @
            email - set to @fellowshipchurch.com if @ not specified.
            notes - user specified notes for the job
            chunkSize - chunk size for the agenda
            quality - render quality for the project, based on a custom script
            callbacks:
                mail

        RQ Item Specific:
            rqIndex - rqIndex to render
            cpus - based on output type (only 1 if mov)
            outputFiles - list of outputFiles for that rqItem
            agenda - frames split up based on chunkSize for the job
            frameCount - total number of frames to render
    '''
    valuesPkg = values.setdefault('package', {})

    pValue = 0
    pIncrement = 10
    maxProgress = len(valuesPkg['aeProject']['rqItems']) * pIncrement * 2 + 50
    pDlg = wx.ProgressDialog('Submitting Project...',
                             'Saving prefs...',
                             maximum=maxProgress)
    pDlg.SetSize((300, -1))

    try:
        '''
        ----------------------------------------------------------------------------------------
        First, save any history related items to the Elevate Plist
        ----------------------------------------------------------------------------------------
        '''
        elevatePrefs = {}

        elevatePrefs['projectHistory'] = valuesPkg['aeProject'][
            'projectHistory']
        emailList = set(cmdjob.options['email']['choices'])
        emailList.add(valuesPkg['email'])
        if len(emailList) > 10:
            emailList = emailList[0:9]
        elevatePrefs['emailHistory'] = list(emailList)
        logging.debug("emailHistory: %s" % elevatePrefs['emailHistory'])
        plistlib.writePlist(elevatePrefs, ELEVATEPREFSFILE)
        '''
        ----------------------------------------------------------------------------------------
        Second, setup everything that will apply to all the rqItems
        ----------------------------------------------------------------------------------------
        '''
        pValue += pIncrement
        pDlg.Update(pValue, "Copying original project...")
        '''
        Create a copy of the original project to render from
        '''
        sourceProjPath = valuesPkg['aeProject']['projectPath']

        logging.debug("Making a copy of the project for rendering...")

        #Create the time string to be placed on the end of the AE file
        fileTimeStr = time.strftime("_%m%d%y_%H%M%S", time.gmtime())

        #Copy the file to the project files folder and add the time on the end
        sourceFolderPath, sourceProjName = os.path.split(sourceProjPath)
        newFolderPath = os.path.join(sourceFolderPath,
                                     QUBESUBMISSIONSFOLDERNAME)
        newProjName = os.path.splitext(
            sourceProjName)[0] + fileTimeStr + '.aep'
        newProjPath = os.path.join(newFolderPath, newProjName)

        try:
            if not (os.path.exists(newFolderPath)):
                os.mkdir(newFolderPath)
        except:
            raise ("Unable to create the folder %s" % newFolderPath)

        try:
            shutil.copy2(sourceProjPath, newProjPath)
            logging.info("Project file copied to %s" % newProjPath)
        except:
            raise ("Unable to create a copy of the project under %s" %
                   newProjPath)

        valuesPkg['sourceProjectPath'] = str(sourceProjPath)
        logging.debug("sourceProjectPath: %s" % valuesPkg['sourceProjectPath'])
        valuesPkg['renderProjectPath'] = str(newProjPath)
        logging.debug("renderProjectPath: %s" % valuesPkg['renderProjectPath'])
        '''
        Setup the email, user, notes, chunkSize, quality, and callbacks.
        '''
        pValue += pIncrement
        pDlg.Update(pValue, "Setting up qube jobs...")

        if "@" not in valuesPkg['email']:
            valuesPkg['email'] += "@fellowshipchurch.com"
        values['mailaddress'] = valuesPkg['email']
        values['user'] = valuesPkg['email'].split("@")[0]
        values['notes'] = valuesPkg.get('notes', '').strip()
        values['callbacks'] = [{
            'triggers': 'done-job-self',
            'language': 'mail'
        }]
        '''
        ----------------------------------------------------------------------------------------
        Third, setup each rqItem's job
        ----------------------------------------------------------------------------------------
        '''
        '''
        Setup the name, rqIndex, outputFiles, agenda and cpus.
        '''
        rqJobs = []
        seqPattern = re.compile("\[#+\]")
        for rqItem in valuesPkg['aeProject']['rqItems']:

            outPaths = []
            for item in rqItem['outFilePaths']:
                outPaths.append(str(item))

            sequence = True
            if ".mov" in ",".join(outPaths):
                sequence = False

            pValue += pIncrement
            pDlg.Update(pValue, "Setting up RQ Item: %s..." % rqItem['comp'])

            rqiValues = copy.deepcopy(values)
            rqiPkg = rqiValues.setdefault('package', {})
            rqiPkg['rqIndex'] = str(rqItem['index'])
            rqiValues['name'] = "%s %s-%s" % (values['name'], rqItem['index'],
                                              rqItem['comp'])
            rqiPkg['outputFiles'] = ",".join(outPaths)
            logging.debug("Output File Paths: %s" % rqItem['outFilePaths'])
            agendaRange = str(
                "%s-%s" % (rqItem['startTime'], int(rqItem['stopTime']) - 1))
            logging.debug("Agenda Range: %s" % agendaRange)
            chunkSize = 30
            if not sequence:
                rqiValues['cpus'] = 1
                chunkSize = 1000000
            rqiValues['agenda'] = qb.genchunks(chunkSize, agendaRange)
            logging.debug("Agenda: %s" % rqiValues['agenda'])
            rqiValues['agenda'][-1]['resultpackage'] = {
                'outputPaths': ",".join(outPaths)
            }
            rqiPkg['frameCount'] = int(rqItem['stopTime']) - int(
                rqItem['startTime']) - 1
            '''
            If it's a sequence, mark all existing frames as complete.
            '''
            if sequence:

                for path in outPaths:
                    pValue += pIncrement
                    pDlg.Update(
                        pValue, "Finding missing frames for %s..." %
                        os.path.basename(path))

                    seqPad = len(seqPattern.findall(path)[-1]) - 2
                    initFrame = sequenceTools.padFrame(rqItem['startTime'],
                                                       seqPad)
                    initPath = seqPattern.sub(initFrame, path)
                    logging.debug("initPath: %s" % initPath)
                    seq = sequenceTools.Sequence(initPath,
                                                 frameRange=agendaRange)
                    missingFrames = seq.getMissingFrames()
                    logging.debug("Missing Frames: %s" % missingFrames)

                    for task in rqiValues['agenda']:
                        logging.debug("Name: %s" % task['name'])
                        if "-" in task['name']:
                            tStart, tEnd = task['name'].split("-")
                        else:
                            tStart = tEnd = task['name']
                        tRange = range(int(tStart), int(tEnd) + 1)
                        found = False
                        for frame in tRange:
                            if frame in missingFrames:
                                found = True
                        if not found:
                            task['status'] = 'complete'
                            if task.has_key("resultPackage"):
                                task['resultpackage'][
                                    'progress'] = '1'  # 100% chunk progress
                            else:
                                task['resultpackage'] = {'progress': '1'}
                            logging.debug("Marking task as complete: %s" %
                                          task)
            '''
            Delete any unecessary attributes
            '''
            rqiPkg['aeProject'] = None
            del rqiPkg['aeProject']
            rqiPkg['notes'] = None
            del rqiPkg['notes']
            rqiPkg['gui'] = None
            del rqiPkg['gui']

            rqJobs.append(rqiValues)

        logging.debug("rqJobs: %s" % rqJobs)

        pValue += pIncrement
        pDlg.Update(pValue, "Submitting Jobs to qube...")

        submittedJobs = qb.submit(rqJobs)
        logging.debug("Submitted Jobs: %s" % submittedJobs)

        pValue += pIncrement
        pDlg.Update(pValue, "Refreshing Qube...")

        # Update the Qube GUI
        request = qbCache.QbServerRequest(
            action="jobinfo",
            value=[i['id'] for i in submittedJobs],
            method='reload')
        qbCache.QbServerRequestQueue.put(request)

        pDlg.Update(maxProgress, "Complete!")
    except Exception, e:
        exc_type, exc_value, exc_traceback = sys.exc_info()
        dlg = wx.MessageDialog(None, "Unable to submit jobs %s" % e, "Error",
                               wx.OK | wx.ICON_ERROR)
        logging.error(
            repr(traceback.format_exception(exc_type, exc_value,
                                            exc_traceback)))
        dlg.ShowModal()