def updateTasks(self, ftrackEntity): '''Update task with the provided *ftrackEntity*''' self.currentTask = ftrackEntity try: shotpath = self.currentTask.getName() taskParents = self.currentTask.getParents() for parent in taskParents: shotpath = '{0}.{1}'.format(parent.getName(), shotpath) self.ui.AssetTaskComboBox.clear() tasks = self.currentTask.getTasks() curIndex = 0 ftrackuser = ftrack.User(getpass.getuser()) taskids = [x.getId() for x in ftrackuser.getTasks()] for i in range(len(tasks)): assetTaskItem = QtGui.QStandardItem(tasks[i].getName()) assetTaskItem.id = tasks[i].getId() self.ui.AssetTaskComboBoxModel.appendRow(assetTaskItem) if (os.environ.get('FTRACK_TASKID') == assetTaskItem.id): curIndex = i else: if assetTaskItem.id in taskids: curIndex = i self.ui.AssetTaskComboBox.setCurrentIndex(curIndex) except: print 'Not a task'
def callback(event): """ This plugin creates a template folder structure on disk. """ for entity in event['data'].get('entities', []): if entity.get('entityType') == 'task' and entity['action'] == 'add': task = ftrack.Task(id=entity['entityId']) user = ftrack.User(id=event['source']['user']['id']) taskName = task.getName().lower() if task.getObjectType() == 'Task': project = task.getProject() projFolder = getProjectFolder(project) shotName = task.getParent().getName() if task.getParent().get('objecttypename') == 'Asset Build': taskFolder = createAssetFolders(task, projFolder) shotName = '{0}_{1}'.format(shotName, taskName) else: shotsFolder = os.path.join(projFolder, 'shots') shotFolder = getShotFolder(task) if shotFolder == '': return sceneFolder = os.path.join(shotsFolder, shotFolder, 'scene') taskFolder = os.path.join(sceneFolder, taskName) if not os.path.exists(taskFolder): os.makedirs(taskFolder) try: os.chmod(taskFolder, 0777) except: print "could not change directory permission for %s" % taskFolder templateFolder = os.path.join(projFolder, 'template_files') createTemplateFiles(templateFolder, task, taskFolder, shotName, user) syncToCpt(taskFolder.rstrip('/'))
def _updateAssignedList(self): '''Update assigned list.''' self.assignedTimeLogList.clearItems() # Local import to allow configuring of ftrack API at runtime. import ftrack # TODO: Get logged in user from ftrack API. # NOTE: The specific environment variable is used as it is explicitly # set post login by the application. username = os.environ.get('LOGNAME') if not username: return assignedTasks = ftrack.User(username).getTasks( states=['NOT_STARTED', 'IN_PROGRESS', 'BLOCKED']) formattedTasks = [ dict({ 'title': task.getName(), 'description': self._getPath(task), 'data': task }) for task in assignedTasks ] formattedTasks = sorted(formattedTasks, key=operator.itemgetter( 'description', 'title')) for task in formattedTasks: self.assignedTimeLogList.addItem(task)
def launch(self, event): """ Called when action is executed """ selection = event['data'].get('selection', []) task = ftrack.Task(selection[0]['entityId']) user = ftrack.User(id=event['source']['user']['id']) taskType = task.getType().getName().lower() taskFolder = self.getTaskFolder(task) if not os.path.exists(taskFolder): os.makedirs(taskFolder) shotName = task.getParent().getName() publishFile = '' newFilePath = os.path.join(taskFolder, '%s_v01.mb' % shotName) if taskType == 'previz': publishFile = self.copyFromLayoutPublish(taskFolder, 'previz') if os.path.exists(publishFile) and not os.path.exists(newFilePath): shutil.copy(publishFile, newFilePath) os.chmod(newFilePath, 0666) elif taskType == 'animation': previzDir = os.path.join( taskFolder.split('animation')[0], 'previz') previzFiles = [ f for f in glob.glob(os.path.join(previzDir, '*_v[0-9]*.mb')) ] # get latest previz file if previzFiles: maxVersion = 1 for f in previzFiles: try: version = int(self.version_get(f, 'v')[1]) except ValueError: continue if version >= maxVersion: publishFile = f maxVersion = version publishFile = os.path.join(previzDir, publishFile) # Else get latest layout publish file else: publishFile = self.copyFromLayoutPublish( taskFolder, 'animation') # Copy over the latest publish file. if os.path.exists(publishFile) and not os.path.exists(newFilePath): shutil.copy(publishFile, newFilePath) os.chmod(newFilePath, 0666) elif taskType == 'lighting': mayapy = '/usr/autodesk/maya2016/bin/mayapy' if os.path.exists(mayapy): self.buildLightingScene(mayapy, task.getId(), taskFolder, user, newFilePath, shotName) metadata = task.getMeta() metadata['filename'] = newFilePath task.setMeta(metadata) return {'success': True, 'message': 'Action launched successfully.'}
def create_job(event): values = event['data']['values'] job = ftrack.createJob('Generating Titles', 'queued', ftrack.User(id=event['source']['user']['id'])) job.setStatus('running') image_magick_dir = r'K:\development\tools\image-magick' errors = '' # Generateting sources and destinations for item in event['data']['selection']: try: entity = ftrack.AssetVersion(item['entityId']) # adding path to errors path = '' parents = entity.getParents() parents.reverse() for p in parents: path += p.getName() + '/' path += 'v' + str(entity.getVersion()).zfill(3) input_file = entity.getComponent().getFilesystemPath() output_file = ntpath.basename(input_file) # get version string version_string = '.v' + version_get(input_file, 'v')[1] if values['strip_version'] == 'True': output_file = output_file.replace(version_string, '') # get titles text south_west_text = 'Task: "%s"' % path[:-5] south_east_text = 'Version: "%s"' % version_string[1:] north_west_text = 'Status: "%s"' % entity.getStatus().getName() if entity.getAsset().getType().getShort() == 'img': src = os.listdir(os.path.dirname(input_file))[0] input_file = os.path.join(os.path.dirname(input_file), src) output_file = re.sub(r'.%04d', '', output_file) output_file = os.path.splitext(output_file)[0] + '.png' output_file = os.path.join(values['output_directory'], output_file) generate_title(image_magick_dir, output_file, input_file, '25', south_west_text, south_east_text, north_west_text) except: errors += path + '\n' errors += traceback.format_exc() + '\n' # generate error report if errors: temp_txt = os.path.join(values['output_directory'], 'errors.txt') f = open(temp_txt, 'w') f.write(errors) f.close() job.setStatus('done')
def callback(event): for entity in event['data'].get('entities', []): # Filter non-assetversions if entity.get('entityType') == 'task' and entity['action'] == 'update': if 'statusid' not in entity.get('keys'): return # Find task if it exists try: task = ftrack.Task(id=entity.get('entityId')) except: return # remove status assigned users if task.getMeta('assignees'): for userid in task.getMeta('assignees').split(','): try: task.unAssignUser(ftrack.User(userid)) except: pass # getting status named group task_status_name = task.getStatus().get('name').lower() project = task.getParents()[-1] status_group = None for group in project.getAllocatedGroups(): if group.getSubgroups(): if group.get('name').lower() == task_status_name: status_group = group users = [] if status_group: for group in status_group.getSubgroups(): task_type_name = task.getType().get('name').lower() if task_type_name == group.get('name').lower(): # assigning new users for member in group.getMembers(): try: task.assignUser(member) users.append(member.get('userid')) except: pass # storing new assignees value = '' for user in users: value += user + ',' try: value = value[:-1] except: pass task.setMeta('assignees', value=value)
def launch(self, event): """ Called when action is executed """ selection = event['data'].get('selection', []) user = ftrack.User(id=event['source']['user']['id']) task = ftrack.Task(id=selection[0]['entityId']) shot = task.getParent() if 'values' in event['data']: values = event['data']['values'] self.uploadToFtrack(values['file_path'], values['asset_name'], values['note'], values['add_slate'], selection[0]['entityId'], shot, user) return { 'success': True, 'message': 'Action completed successfully' } return { 'items': [{ 'value': '##{0}##'.format('File Path'.capitalize()), 'type': 'label' }, { 'label': 'Folder', 'type': 'text', 'value': '', 'name': 'file_path' }, { 'label':'Add Slate', 'type':'enumerator', 'value': 'No', 'name':'add_slate', 'data':[{ 'label': 'Yes', 'value': 'Yes' }, { 'label': 'No', 'value': 'No' }] }, { 'label': 'Asset Name', 'type': 'text', 'value': 'ReviewAsset', 'name': 'asset_name' }, { 'label': 'Add Note', 'type': 'textarea', 'value': 'Done: \nTo Do:', 'name': 'note' }] }
def launch(self, event): """ Called when action is executed """ user = ftrack.User(id=event['source']['user']['id']) if 'values' in event['data']: values = event['data']['values'] path = values['file_path'] value = values['xfer_loc'] queue = values['queue'] #if os.path.exists(path): self.cptSync(path, value, user, queue) return {'success': True, 'message': 'Starting File Sync'} return { 'items': [{ 'value': '##{0}##'.format( 'Enter a file/folder to transfer.'.capitalize()), 'type': 'label' }, { 'label': 'Path:', 'type': 'text', 'value': '', 'name': 'file_path' }, { 'label': 'Transfer Location:', 'type': 'enumerator', 'value': 0, 'name': 'xfer_loc', 'data': [{ 'label': 'CPT -> JHB', 'value': 0 }, { 'label': 'JHB -> CPT', 'value': 1 }] }, { 'label': 'Add to overnight queue', 'name': 'queue', 'type': 'boolean', 'value': False }] }
def create_job(self, event): job = ftrack.createJob("Generating Feedback", "queued", ftrack.User(id=event["source"]["user"]["id"])) job.setStatus("running") try: f = self.generate_feedback(event) job.createAttachment(f, fileName=f) os.remove(f) except: self.logger.error(traceback.format_exc()) job.setStatus("failed") else: job.setStatus("done")
def sendEmail( userId, subject, message, recipients=[], filename='', ): sender = ftrack.User(userId).getEmail() msg = MIMEMultipart() msg['Subject'] = subject msg['From'] = sender msg['Reply-to'] = sender msg['To'] = ', '.join(recipients) # That is what u see if dont have an email reader: msg.preamble = 'Multipart massage.\n' # This is the textual part: part = MIMEText(message) msg.attach(part) filetype = os.path.splitext(filename)[-1] if filetype is not '': if filetype == '.pdf': # This is the binary part: part = MIMEApplication(open(filename, "rb").read(), "pdf", name="reviewsession.pdf") part.add_header('Content-Disposition', 'attachment', filename="reviewsession.pdf") msg.attach(part) elif filetype == '.html': f = file(filename) attachment = MIMEText(f.read()) attachment.add_header('Content-Disposition', 'attachment', filename="reviewsession.html") msg.attach(attachment) # add any smtp server server = SMTP('smtp.gmail.com:587') server.ehlo() server.starttls() # log in with your credentials server.login(YOUR_USERNAME, YOUR_PASSWORD) server.sendmail(msg['From'], msg['To'], msg.as_string()) server.quit()
def getEntity(entityType=None, entityId=None): if entityType is None or entityId is None: return None if entityType == 'user': return ftrack.User(entityId) if entityType == 'show': return ftrack.Project(entityId) elif entityType == 'task': return ftrack.Task(entityId) elif entityType == 'list': return ftrack.List(entityId) elif entityType == 'reviewsession': return ftrack.ReviewSession(entityId) else: return None
def create_job(event): job = ftrack.createJob("Create Structure", "queued", ftrack.User(id=event["source"]["user"]["id"])) job.setStatus("running") try: for item in event["data"]["selection"]: # Geting any object types entity_id = item["entityId"] entity = session.get("TypedContext", entity_id) if not entity: entity_type = item["entityType"].lower() if entity_type == "show": entity = session.get("Project", entity_id) if entity_type == "assetversion": entity = session.get("AssetVersion", entity_id) if entity_type == "component": entity = session.get("Component", entity_id) templates = ftrack_template.discover_templates() valid_templates = ftrack_template.format( {}, templates, entity, return_mode="all" ) print "Creating Directories/Files:" for path, template in valid_templates: if template.isfile: if not os.path.exists(os.path.dirname(path)): print os.path.dirname(path) os.makedirs(os.path.dirname(path)) if not os.path.exists(path): print path shutil.copy(template.source, path) else: if not os.path.exists(path): print path os.makedirs(path) except: print traceback.format_exc() job.setStatus("failed") else: job.setStatus("done")
def callback(event): """ This plugin sets the task status from the version status update. """ for entity in event['data'].get('entities', []): if (entity.get('entityType') == 'reviewsessionobject' and entity['action'] == 'add'): version = None try: version_id = entity['changes']['version_id']['new'] version = ftrack.AssetVersion(version_id) except: return user = ftrack.User(event['source']['user']['id']) os.environ['LOGNAME'] = user.getUsername() myclass = Thread(version, user) myclass.start()
def create_job(event): job = ftrack.createJob("Version Up Tasks", "queued", ftrack.User(id=event["source"]["user"]["id"])) job.setStatus("running") try: for item in event["data"]["selection"]: task = ftrack.Task(item["entityId"]) asset = task.getParent().createAsset(task.getName(), "scene", task=task) asset.createVersion(taskid=task.getId()) asset.publish() except: job.setStatus("failed") else: job.setStatus("done")
def __init__(self, username, parent=None): '''Instantiate user name and logo widget using *username*.''' super(User, self).__init__(parent=parent) self.setObjectName('ftrack-userid-widget') self.main_layout = QtWidgets.QHBoxLayout() self.main_layout.setContentsMargins(0, 0, 0, 0) self.main_layout.setAlignment(QtCore.Qt.AlignRight) self.setLayout(self.main_layout) self.label = QtWidgets.QLabel(self) self.image = thumbnail.User(self) self.image.setFixedSize(35, 35) self.main_layout.addWidget(self.label) self.main_layout.addWidget(self.image) self.image.load(username) if username not in NAME_CACHE: NAME_CACHE[username] = ftrack.User(username).getName().title() self.label.setText(NAME_CACHE[username])
def callback(event): for entity in event['data'].get('entities', []): # Filter non-assetversions if entity.get('entityType') == 'task' and entity['action'] == 'update': if 'statusid' not in entity.get('keys'): return # Find task if it exists task = None try: task = ftrack.Task(id=entity.get('entityId')) except: return new_status = ftrack.Status(entity["changes"]["statusid"]["new"]) # To Pending Changes if new_status.getName().lower() == "pending changes": user = ftrack.User(id=event["source"]["user"]["id"]) job = ftrack.createJob("Version Up Tasks", "queued", user) job.setStatus("running") try: asset = task.getParent().createAsset(task.getName(), "scene", task=task) asset.createVersion(taskid=task.getId()) asset.publish() except: job.setStatus("failed") else: job.setStatus("done")
def launch(self, event): msg = 'Breakdown successfull. Click Job for details.' ftrack.EVENT_HUB.publishReply(event, data={ 'success': True, 'message': msg }) temp_dir = tempfile.gettempdir() file_path = os.path.join(temp_dir, 'ftrack_version_breakdown.txt') with open(file_path, 'w') as f: output = '' v = ftrack.AssetVersion('6dec6756-8f94-11e5-929c-42010af00048') output += '/'.join([ v.getParent().getParent().getName(), v.getParent().getName(), str(v.getVersion()).zfill(3) ]) output += ':\n' count = 0 sessions = [] project_id = v.getParents()[-1].getId() for session in ftrack.getReviewSessions(project_id): for obj in session.getObjects(): if obj.get('version_id') == v.getId(): count += 1 sessions.append(session.get('name')) output += '\tSession Usage:\t{0}\n'.format(count) output += '\tSessions:\t\t{0}\n'.format(list(set(sessions))) f.write(output) user = ftrack.User(id=event['source']['user']['id']) job = ftrack.createJob('Breakdown', 'done', user) job.createAttachment(file_path)
def constructWidget(self): '''Return widget instance to test.''' widget = QtGui.QWidget() layout = QtGui.QVBoxLayout() widget.setLayout(layout) layout.setContentsMargins(0, 0, 0, 0) allAssignedTasks = ftrack.User( os.environ.get('LOGNAME') ).getTasks( states=['IN_PROGRESS', 'BLOCKED'] ) assignedTasksList = ftrack_connect.ui.widget.time_log_list.TimeLogList( title='Assigned tasks' ) layout.addWidget(assignedTasksList, stretch=1) for task in allAssignedTasks: assignedTasksList.addItem({ 'title': task.getName(), 'description': self._getPath(task), 'data': task }) divider = QtGui.QFrame() divider.setFrameShape(QtGui.QFrame.HLine) layout.addWidget(divider) self.selectedTasklabel = QtGui.QLabel() assignedTasksList.itemSelected.connect(self._setLabelText) layout.addWidget(self.selectedTasklabel) return widget
def createTimelogBreakdown(entityType=None, entity=None, userId=None, values=None): description = u'Generating timelog report' job = ftrack.createJob( description=description, status='running', user=userId ) try: html = "" range=values['date_range'] report=values['report_type'] filterOnUserId=values['user'] today = datetime.date.today() date_range = (today - datetime.timedelta(days=1), today) if range == "yesterday": date_range = (today - datetime.timedelta(days=2), today - datetime.timedelta(days=1)) if range == "this_week": date_range = getWeek(today) if range == "prev_week": date_range = getWeek(today - datetime.timedelta(weeks=1)) if range == "this_month": date_range = getMonth(today) if range == "prev_month": date_range = getMonth(today.replace(day=1) - datetime.timedelta(days=1)) if range == "this_year": date_range = (datetime.date(datetime.date.today().year, 1, 1), datetime.date(datetime.date.today().year, 12, 31)) if range == "prev_year": date_range = (datetime.date(datetime.date.today().year-1, 1, 1), datetime.date(datetime.date.today().year-1, 12, 31)) title = '' if filterOnUserId != 'all': title = entity.getName() + " <small>[" + ftrack.User(filterOnUserId).getName() + "] " + date_range[0].strftime('%d') + " " + date_range[0].strftime('%B') + ", " + date_range[0].strftime('%Y') + " - " + date_range[1].strftime('%d') + " " + date_range[1].strftime('%B') + ", " + date_range[1].strftime('%Y') + " </small>" else: title = entity.getName() + " <small>"+ date_range[0].strftime('%d') + " " + date_range[0].strftime('%B') + ", " + date_range[0].strftime('%Y') + " - " + date_range[1].strftime('%d') + " " + date_range[1].strftime('%B') + ", " + date_range[1].strftime('%Y') + " </small>" # HTML Header html = " <html><head>\ <style>\ @page { padding: 10pt}\ @page { margin: 20px; }\ @page { size: A3}\ @page { size: A3 landscape }\ body{margin:30px; padding: 10px}\ img { page-break-inside: avoid; }\ .break { clear:both; page-break-after:always; }\ td, th { page-break-inside: avoid; }\ </style>\ <link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css'>\ <link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css'>\ <script type='text/javascript' src='https://www.google.com/jsapi'></script>" # Load Google charts html = html + "\ <script type='text/javascript'>\ google.load('visualization', '1.0', {'packages':['timeline', 'calendar', 'corechart']});\ google.setOnLoadCallback(drawChart);" # Create Google Timeline object html = html + "\ var chartTimeline = null;\ var dataTableTimeline = null;\ var optionsTimeline = null;\ function drawChart() {\ var timeline = document.getElementById('timeline');\ chartTimeline = new google.visualization.Timeline(timeline);\ \ dataTableTimeline = new google.visualization.DataTable();\ dataTableTimeline.addColumn({ type: 'string', id: 'Task' });\ dataTableTimeline.addColumn({ type: 'string', id: 'User' });\ dataTableTimeline.addColumn({ type: 'date', id: 'Start' });\ dataTableTimeline.addColumn({ type: 'date', id: 'End' });\ dataTableTimeline.addRows([" # Helpers to keep track of accumulated timelogs/duration accumulatedSecondsPerTask = {} accumulatedSecondsPerDay = {} accumulatedNonBillableSecondsPerDay = {} accumulatedSecondsPerUser = {} billable = 0 totalSeconds = 0 # If selection is a task/folder, add [filtered] timelog events. if entityType != 'user': tasks = [] user=None if filterOnUserId != 'all': user = ftrack.User(filterOnUserId) tasks = getTasks(entityType=entityType, entity=entity) #tasks = getTasks(entityType=entityType, entity=entity, users=[user.getUsername()]) else: tasks = getTasks(entityType=entityType, entity=entity) job.setDescription("Generating timelog report") numberOfTasks = len(tasks) for i, task in enumerate(tasks): if numberOfTasks > 100 and i % 10 == 0: job.setDescription("Generating timelog report - {0:.2f}%".format((float(i)/numberOfTasks)*100)) timelogs = task.getTimelogs(start=date_range[0], end=date_range[1], includeChildren=False) if len(timelogs) == 0: continue taskname = task.getName() taskId = task.getId() isBillable = task.getType().get('isbillable') for timelog in timelogs: u = timelog.getUser() if user != None: if u.getId() != filterOnUserId: continue start=timelog.get('start') duration = timelog.get('duration') end=start + datetime.timedelta(seconds = duration) # a fail safe - Google charts will crash if end < start if end < start: end = start + datetime.timedelta(seconds = 60) # accumulate helpers totalSeconds += duration if isBillable: billable += duration else: if start.date() in accumulatedNonBillableSecondsPerDay: accumulatedNonBillableSecondsPerDay[start.date()] += duration else: accumulatedNonBillableSecondsPerDay[start.date()] = duration #create or update dictionary keys/values if start.date() in accumulatedSecondsPerDay: accumulatedSecondsPerDay[start.date()] += duration else: accumulatedSecondsPerDay[start.date()] = duration if taskId in accumulatedSecondsPerTask: accumulatedSecondsPerTask[taskId] += duration else: accumulatedSecondsPerTask[taskId] = duration if u.getId() in accumulatedSecondsPerUser: accumulatedSecondsPerUser[u.getId()] += duration else: accumulatedSecondsPerUser[u.getId()] = duration # add row to timeline object html=html + "\ [ '"+ taskname +"', '" + u.getName() + "', new Date(" + start.strftime('%Y') + "," + str(int(start.strftime('%m'))-1) + "," + start.strftime('%d') + ","+start.strftime('%H') + "," + start.strftime('%M') + "," + start.strftime('%S')+"), new Date(" + end.strftime('%Y') + "," + str(int(end.strftime('%m'))-1) + "," + end.strftime('%d') + ","+end.strftime('%H') + "," + end.strftime('%M') + "," + end.strftime('%S')+")]," #[ '{0}', '{1}', new Date({2},{3},{4},{5},{6},{7}), new Date({8},{9},{10},{11},{12},{13})],".format(taskname, u.getName(), start.strftime('%Y'), str(int(start.strftime('%m'))-1), start.strftime('%d'), start.strftime('%H'), start.strftime('%M'), start.strftime('%S'), end.strftime('%Y'), str(int(end.strftime('%m'))-1), end.strftime('%d'), end.strftime('%H'), end.strftime('%M'), end.strftime('%S')) #[ '"+ taskname +"', '" + u.getName() + "', new Date(" + start.strftime('%Y') + "," + str(int(start.strftime('%m'))-1) + "," + start.strftime('%d') + ","+start.strftime('%H') + "," + start.strftime('%M') + "," + start.strftime('%S')+"), new Date(" + end.strftime('%Y') + "," + str(int(end.strftime('%m'))-1) + "," + end.strftime('%d') + ","+end.strftime('%H') + "," + end.strftime('%M') + "," + end.strftime('%S')+")]," # If selection is a user, add [filtered] user timelog events. else: user = entity timelogs = user.getTimelogs(start=date_range[0], end=date_range[1], includeChildren=True) job.setDescription("Generating timelog report") for timelog in timelogs: try: taskId = timelog.get('context_id') task = ftrack.Task(taskId) isBillable = task.getType().get('isbillable') start=timelog.get('start') duration = timelog.get('duration') end=start + datetime.timedelta(seconds = duration) # a fail safe - Google charts will crash if end < start if end < start: end = start + datetime.timedelta(seconds = 60) # accumulate helpers totalSeconds += duration if isBillable: billable += duration else: if start.date() in accumulatedNonBillableSecondsPerDay: accumulatedNonBillableSecondsPerDay[start.date()] += duration else: accumulatedNonBillableSecondsPerDay[start.date()] = duration #create or update dictionary keys/values if start.date() in accumulatedSecondsPerDay: accumulatedSecondsPerDay[start.date()] += duration else: accumulatedSecondsPerDay[start.date()] = duration if taskId in accumulatedSecondsPerTask: accumulatedSecondsPerTask[taskId] += duration else: accumulatedSecondsPerTask[taskId] = duration if user.getId() in accumulatedSecondsPerUser: accumulatedSecondsPerUser[user.getId()] += duration else: accumulatedSecondsPerUser[user.getId()] = duration # add row to timeline object html=html + "\ [ '{0}', '', new Date({1},{2},{3},{4},{5},{6}), new Date({7},{8},{9},{10},{11},{12})],".format(task.getName(), start.strftime('%Y'), str(int(start.strftime('%m'))-1), start.strftime('%d'), start.strftime('%H'), start.strftime('%M'), start.strftime('%S'), end.strftime('%Y'), str(int(end.strftime('%m'))-1), end.strftime('%d'), end.strftime('%H'), end.strftime('%M'), end.strftime('%S')) #[ '" + task.getName() + "', '', new Date(" + start.strftime('%Y') + "," + str(int(start.strftime('%m'))-1) + "," + start.strftime('%d') + ","+start.strftime('%H') + "," + start.strftime('%M') + "," + start.strftime('%S')+"), new Date(" + end.strftime('%Y') + "," + str(int(end.strftime('%m'))-1) + "," + end.strftime('%d') + ","+end.strftime('%H') + "," + end.strftime('%M') + "," + end.strftime('%S')+")]," except: pass html = html + "]);\ optionsTimeline = {\ timeline: { rowLabelStyle: {fontName: 'Helvetica', fontSize: 10, color: '#000000'}},\ avoidOverlappingGridLines: false,\ enableInteractivity: true,\ };\ chartTimeline.draw(dataTableTimeline, optionsTimeline);" # Create Google Pie chart options used by all Pie Charts in document html = html + "\ var optionsPieChart = {\ legend: {position: 'labeled'},\ is3D: false,\ pieHole: 0,\ chartArea:{width:'92%',height:'92%'},\ pieSliceText: 'value'\ };" # Create Google Pie chart object - Total hours html = html + "\ var dataBillable = google.visualization.arrayToDataTable([\ ['Task', 'Hours'],\ ['Billable', {0:.2f}],\ ['Non-billable',{1:.2f}]]);\ \ var chartBillable = new google.visualization.PieChart(document.getElementById('billable'));\ chartBillable.draw(dataBillable, optionsPieChart);".format(billable/3600, (totalSeconds-billable)/3600) # Create Google Pie chart object - User who've logged most hours html = html + "\ var dataUserMostHours = google.visualization.arrayToDataTable([\ ['User', 'Hours']," # Loop through accumulatedSecondsPerUser and add records to chart u = sorted(accumulatedSecondsPerUser.items(),key=itemgetter(1),reverse=True) for i, t in enumerate(u): html = html + "\ ['{0}',{1:.2f}],".format(ftrack.User(t[0]).getName().encode('ascii', 'replace'), t[1]/3600) if i == 9: break html = html + "\ ]);\ var chartUserMostHours = new google.visualization.PieChart(document.getElementById('userMostHours'));\ chartUserMostHours.draw(dataUserMostHours, optionsPieChart);" # Create Google Pie chart object - Tasks with most hours logged html = html + "\ var dataTaskMostHours = google.visualization.arrayToDataTable([\ ['Task', 'Hours']," # Loop through accumulatedSecondsPerTask and add records to chart l = sorted(accumulatedSecondsPerTask.items(),key=itemgetter(1),reverse=True) for i, t in enumerate(l): html = html + "\ ['{0}',{1:.2f}],".format(ftrack.Task(t[0]).getName(), t[1]/3600) if i == 9: break html = html + "\ ]);\ var chartTaskMostHours = new google.visualization.PieChart(document.getElementById('taskMostHours'));\ chartTaskMostHours.draw(dataTaskMostHours, optionsPieChart);" # Create Google Calendar object - Heatmap with hours per day html = html + "\ var dataTableCalendar = new google.visualization.DataTable();\ dataTableCalendar.addColumn({ type: 'date', id: 'Date' });\ dataTableCalendar.addColumn({ type: 'number', id: 'Won/Loss' });\ dataTableCalendar.addRows([" # Loop through accumulatedSecondsPerDay and add records to chart for day, duration in accumulatedSecondsPerDay.items(): # adjusting months to match javascript months that starts on 0 html=html + "\ [ new Date({0},{1},{2}),{3:.2f}],".format(day.strftime('%Y'), str(int(day.strftime('%m'))-1), day.strftime('%d'), duration/3600) html= html + "]);\ var chartCalendar = new google.visualization.Calendar(document.getElementById('calendar'));\ var optionsCalendar = {\ title: '',\ height: 200,\ yearLabel: 'none'\ };\ \ chartCalendar.draw(dataTableCalendar, optionsCalendar);" # Create Google Area Chart object - Show looged hours per day html= html + "\ var data = google.visualization.arrayToDataTable([\ ['Date', 'Total hours','Non-billable hours']," for day, duration in sorted(accumulatedSecondsPerDay.items()): html=html + "\ ['{0}',{1:.2f}, {2:.2f}],".format(day.strftime('%d %b, %Y'), duration/3600, (accumulatedNonBillableSecondsPerDay[day]/3600 if day in accumulatedNonBillableSecondsPerDay.keys() else 0)) #[ new Date({0},{1},{2}),{3:.2f}, {4:.2f}],".format(day.strftime('%Y'), str(int(day.strftime('%m'))-1), day.strftime('%d'), duration/3600, (accumulatedNonBillableSecondsPerDay[day] if day in accumulatedNonBillableSecondsPerDay.keys() else 0)) html=html + "\ ]);\ \ var optionsAreaChart = {\ vAxis: {minValue: 0},\ isStacked: false,\ curveType: 'function',\ legend: { position: 'bottom' }\ };\ \ var chartAreaChart = new google.visualization.SteppedAreaChart(document.getElementById('areachart'));\ chartAreaChart.draw(data, optionsAreaChart);" html = html + "\ }" # add event listener to expand Timeline div based on SVG height attribute html = html + "\ window.addEventListener('load', function(){\ var svglist = document.getElementsByTagName('svg');\ var svg = svglist[svglist.length-1];\ console.log(svg.clientHeight || svg.parentNode.clientHeight);\ document.getElementById('timeline').setAttribute('style','height:'+(svg.clientHeight+60 || svg.parentNode.clientHeight+60)+'px');\ chartTimeline.draw(dataTableTimeline, optionsTimeline);\ });\ </script>" # HTML Body html= html + "\ </head><body>\ <ol class='breadcrumb'>" # Create Boothstrap Breadcrum parents = [] try: parents = entity.getParents() parents.reverse() except: pass for parent in parents: html = html + "<li>" + parent.getName() + "</li>" html= html + "\ <li class='active'>" + entity.getName() + "</li>\ </ol>\ <div class='page-header'>\ <h1>"+title+"</h1>\ </div>\ <div class='row'>\ <div class='col-sm-6 col-md-4'>\ <div class='panel panel-default'>\ <div class='panel-heading'><h3 class='panel-title'>"+ '{:.2f}'.format(totalSeconds/3600) + " total hours</h3></div>\ <div class='panel-body'>\ <div id='billable' style='text-align:center'></div>\ </div>\ </div>\ </div>\ <div class='col-sm-6 col-md-4'>\ <div class='panel panel-default'>\ <div class='panel-heading'><h3 class='panel-title'>Who logged most hours?</h3></div>\ <div class='panel-body'>\ <div id='userMostHours' style='text-align:center'></div>\ </div>\ </div>\ </div>\ <div class='col-sm-6 col-md-4'>\ <div class='panel panel-default'>\ <div class='panel-heading'><h3 class='panel-title'>Task with most hours logged</h3></div>\ <div class='panel-body'>\ <div id='taskMostHours' style='text-align:center'></div>\ </div>\ </div>\ </div>\ </div>\ <div class='panel panel-default'>\ <div class='panel-heading'><h3 class='panel-title'>Calendar heatmap</h3></div>\ <div class='panel-body' style='overflow:hidden;'>\ <div id='calendar' style='margin:auto; width:920px;'></div>\ </div>\ </div>\ <div class='panel panel-default'>\ <div class='panel-heading'><h3 class='panel-title'>Hours logged per day</h3></div>\ <div class='panel-body' style='overflow:hidden;'>\ <div id='areachart' style=''></div>\ </div>\ </div>\ <div class='break'></div>\ <div class='panel panel-default'>\ <div class='panel-heading'><h3 class='panel-title'>Timeline</h3></div>\ <div class='panel-body'>\ <div id='timeline' style='height:100%'></div>\ </div>\ </div>\ </body>\ </html>" filename = "timelogs-{0}.html".format(str(uuid.uuid1())) html_file= open(filename,"w") html_file.write(html.encode("utf-8")) html_file.close() job.createAttachment(filename, fileName=filename) job.setDescription("Timelog report") job.setStatus('done') os.remove(filename) except: print 'Failed:' job.setDescription("Timelog report") job.setStatus('failed')
def _download(self, reference): '''Return thumbnail from *reference*.''' url = ftrack.User(reference).getThumbnail() return super(User, self)._download(url)
def launch(self, event): msg = "Breakdown successfull. Click Job for details." ftrack.EVENT_HUB.publishReply(event, data={ "success": True, "message": msg }) selection = event["data"]["selection"] temp_dir = tempfile.gettempdir() file_path = os.path.join(temp_dir, "ftrack_version_breakdown.txt") with open(file_path, "w") as f: output = "" v = ftrack.AssetVersion(selection[0]["entityId"]) versions = v.getAsset().getVersions() ids = [] data = { "None": { "name": "Uncategorized", "notes": [], "versions": [] } } for cate in ftrack.getNoteCategories(): data[cate.get("entityId")] = { "name": cate.get("name"), "notes": [], "versions": [] } for v in versions: ids.append(v.getId()) for note in v.getNotes(): if note.get("categoryid"): data[note.get("categoryid")]["notes"].append(note) data[note.get("categoryid")]["versions"].append(v) else: data["None"]["versions"].append(v) data["None"]["notes"].append(note) output += "/".join( [v.getParent().getParent().getName(), v.getParent().getName()]) output += ":\n" count = 0 sessions = [] project_id = v.getParents()[-1].getId() for session in ftrack.getReviewSessions(project_id): for obj in session.getObjects(): if obj.get("version_id") in ids: count += 1 sessions.append(session.get("name")) output += "\tReview Session Usage:\t{0}\n".format(count) output += "\tReview Sessions:\t{0}\n".format(list(set(sessions))) output += "\tNotes:\n" for entry in data: output += "\t\t" + data[entry]["name"] + ":\n" amount = len(data[entry]["notes"]) output += "\t\t\tNotes Amount:{0}\n".format(amount) output += "\t\t\tVersions " amount = len(set(data[entry]["versions"])) output += "Amount:{0}\n".format(amount) for note in data[entry]["notes"]: index = data[entry]["notes"].index(note) version_string = data[entry]["versions"][index] version_string = version_string.getVersion() version_string = "v" + str(version_string).zfill(3) output += "\t\t\t{0}:\n".format(version_string) text = note.getText().replace("\n", " ") output += "\t\t\t\t{0}\n".format(text) f.write(output) user = ftrack.User(id=event["source"]["user"]["id"]) job = ftrack.createJob("Breakdown", "done", user) try: job.createAttachment(file_path) except: self.logger.info(traceback.format_exc())
def createPDF(userId=None, entityType=None, entity=None, values=None): description = u'Review summary' job = ftrack.createJob(description=description, status='running', user=userId) email = bool(values['email']) subject = values['subject'] message = values['message'] #replace {session} with session name subject = subject.replace("{session}", entity.get('name')) message = message.replace("{session}", entity.get('name')) try: html = "\ <html>\ <head>\ <style media='all'>\ @page { padding: 0pt}\ @page { margin: 0pt; }\ @page { size: A4}\ img { page-break-inside: avoid; }\ .break { clear:both; page-break-after:always; }\ td, th { page-break-inside: avoid; word-wrap: break-word; }\ </style>\ <link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css' media='all'>\ <link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css' media='all'>\ </head>\ <body>\ <div class='jumbotron' style='padding:20px; padding-top;0px; margin-bottom:0px'>\ <h1>" + entity.get('name') + "</h1>\ <p>" + entity.get('description') + "</p>\ <div class='media' style='margin-top:5px; margin-bottom: 5px'>\ <div class='media-left'>\ <img width='40px' src='" + xstr( ftrack.User(entity.get('created_by')).getThumbnail() ) + "' class='media-object img-circle' style='width:40px;'>\ </div>\ <div class='media-body text-muted'>\ Created by " + ftrack.User( entity.get('created_by')).getName() + "<br/>\ " + str( entity.get('created_at').strftime('%A %d, %Y')) + "\ </div>\ </div>\ </div>\ <table class='table table-striped' style='' >\ <tr>\ <th style='min-width:3px; max-width:3px; width:3px'>#</th>\ <th>Media information</th>\ <th>Comments</th>\ </tr>" lst = getEntityChildren(entityType=entityType, entity=entity) for i, reviewSessionObject in enumerate(lst): html = html + "\ <tr>\ <td class=''>\ <h4 class='text-muted' style='margin-top:0px'>" + str( i + 1) + "</h4>\ </td>\ <td style='width:250px'>\ <div class='thumbnail'>\ <img class='img-responsive' src='" + xstr( reviewSessionObject[0].getThumbnail()) + "'>\ <div class='caption'>\ <small><strong>" + getName( entityType=entityType, entity=reviewSessionObject[0]) + "</strong></small>\ <p class='text-muted small'>" + getPath( entityType=entityType, entity=reviewSessionObject[0]) + "</p>\ <p>\ <div style='margin-top:10px'>\ <span class='text-success glyphicon glyphicon-thumbs-up' style='padding-right:5px' aria-hidden='true'></span><strong><span style='padding-right:10px'>0</span></strong><span class='text-danger glyphicon glyphicon-thumbs-down' style='padding-right:5px' aria-hidden='true'></span><strong>0</strong>\ </div>\ </p>\ </div>\ </div>\ </td>\ <td style=''>\ <small>\ <ul class='media-list'>" notes = reviewSessionObject[1].getNotes() if not len(notes): html = html + "\ <li class='media' style='max-width:430px;'>\ <p class='lead text-muted text-center' style='padding-top:60px'>\ Bummer, there are no comments here!\ </p>\ </li>" for note in notes: html = html + "\ <li class='media' style='max-width:430px;'>\ <div class='media-left'>\ <img src='https://www.ftrack.com/wp-content/uploads/haz2.png' class='media-object img-circle' style='width:40px'>\ </div>\ <div class='media-body'>\ <h4 class='media-heading'>\ Collaborator\ </h4>\ <small class='text-muted'>" + str( note.getDate().strftime('%I:%M%p, %A %d, %Y')) + "</small>\ <p>" + note.getText() + "</p>" frame = note.getMeta('reviewFrame') if frame is not None: html = html + "\ <p><span class='label label-primary'>Frame " + str( json.loads(frame)['number']) + "</span></p>" attachments = note.getAttachments() for a in attachments: html = html + "\ <img src='" + a.getURL( ) + "' class='' style='max-width:120px; margin-bottom:5px'>" replies = note.getNotes() for reply in replies: html = html + "\ <div class='media' style='max-width:380px;'>\ <div class='media-left'>\ <img src='https://www.ftrack.com/wp-content/uploads/fl.png' class='media-object img-circle' style='width:40px'>\ </div>\ <div class='media-body'>\ <h4 class='media-heading'>\ Collaborator\ </h4>\ <small class='text-muted'>" + str( reply.getDate().strftime( '%I:%M%p, %A %d, %Y')) + "</small>\ <p>" + reply.getText( ) + "</p>\ </div>\ </div>" html = html + "\ </div>\ </li>" html = html + "\ <br/>\ </ul>\ </small>\ </td>\ </tr>" html = html + "\ </table>\ </body>\ </html>" # html alternative to create PDF (see below) filename = "review-session-{0}.html".format(str(uuid.uuid1())) html_file = open(filename, "w") html_file.write(html.encode("utf-8")) html_file.close() # signup for docraptor (free trial) or use other PDF generator library # install docraptor with "pip install python-docraptor" # docraptor = DocRaptor(ADD YOUR API KEY HERE) # filename = "review-session-{0}.pdf".format(str(uuid.uuid1())) # resp = docraptor.create({ # 'document_content': html, # 'document_type':'pdf', # 'test': False, # 'strict': 'none', # 'async': True, # 'prince_options': {'media': 'screen', 'insecure':False, 'input':'html'} # }) # status_id = resp['status_id'] # resp = docraptor.status(status_id) # while resp['status'] != 'completed': # time.sleep(3) # resp = docraptor.status(status_id) # f = open(filename, "w+b") # f.write(docraptor.download(resp['download_key']).content) # f.seek(0) job.createAttachment(f, fileName=filename) job.setStatus('done') if email: sendEmail(userId=userId, subject=subject, message=message, recipients=[ftrack.User(userId).getEmail()], filename="filename") os.remove(filename) except: job.setStatus('failed')
def create_job(event): user_id = event["source"]["user"]["id"] ftrack_user = ftrack.User(id=user_id) job = ftrack.createJob("Collecting Assets", "queued", ftrack_user) job.setStatus("running") values = event["data"]["values"] errors = "" # collecting sources and destinations for item in event["data"]["selection"]: try: entity = ftrack.AssetVersion(item["entityId"]) # adding path to errors parent_path = "" parents = entity.getParents() parents.reverse() for p in parents: parent_path += p.getName() + "/" parent_number = int(values["parent_number"]) parent_prefix = "" for p in reversed(list(reversed(parents))[:parent_number]): parent_prefix += p.getName() + "." component_name = values["component_name"] try: component = entity.getComponent(name=component_name) except: continue src = get_file_for_component(component) # copying sources to destinations if entity.getAsset().getType().getShort() == "img": dir_name = entity.getParent().getParent().getName() if parent_prefix: dir_name = parent_prefix asset_dir = os.path.join(values["collection_directory"], dir_name) if os.path.exists(asset_dir): # delete existing files shutil.rmtree(asset_dir) os.makedirs(asset_dir) for f in os.listdir(os.path.dirname(src)): path = os.path.join(os.path.dirname(src), f) basename = format_basename(src, values["file_formatting"]) basename = parent_prefix + re.sub(r".%04d", "", basename) dst = os.path.join(asset_dir, basename) shutil.copy(path, dst) else: basename = format_basename(src, values['file_formatting']) basename = parent_prefix + basename dst = os.path.join(values["collection_directory"], basename) shutil.copy(src, dst) except: errors += parent_path + "\n" errors += traceback.format_exc() + "\n" # generate error report if errors: temp_txt = os.path.join(values["collection_directory"], "errors.txt") f = open(temp_txt, "w") f.write(errors) f.close() job.setStatus("done")
def launch(self, event): userId = event['source']['user']['id'] data = event['data'] selection = data.get('selection', []) entityId = selection[0]['entityId'] entityType = selection[0]['entityType'] entity = getEntity(entityType=entityType, entityId=entityId) if 'values' in event['data']: # Do something with the values or return a new form. values = event['data']['values'] ftrack.EVENT_HUB.publishReply(event, data={ 'success': True, 'message': 'Action was successfull' }) create(userId=userId, entityType=entityType, entity=entity, values=values) return return { 'items': [{ 'type': 'label', 'value': 'Your selection: ' + getEntityPath(entityType=entityType, entity=entity) }, { 'type': 'label', 'value': '___' }, { 'label': 'Email PDF to collaborators', 'type': 'enumerator', 'name': 'email', 'value': '0', 'data': [{ 'label': 'No', 'value': '0' }, { 'label': 'Yes', 'value': '1' }] }, { 'label': 'Email subject', 'type': 'text', 'value': 'Review session notes - {session}', 'name': 'subject' }, { 'label': 'Message', 'name': 'message', 'value': 'Hello,\nhere are the review notes for {{session}}.\n\nThank you,\n{0}' .format(ftrack.User(userId).getName()), 'type': 'textarea' }] }
def launch(self, event): """Callback method for DJVView action.""" # Launching application if "values" in event["data"]: applicationIdentifier = event["data"]["applicationIdentifier"] application = self.applicationStore.getApplication( applicationIdentifier ) context = event["data"].copy() context["source"] = event["source"] command = self.launcher._getApplicationLaunchCommand( application, context ) success = True message = '{0} application started.'.format(application['label']) command.append(event["data"]["values"]["path"]) try: options = dict( env={}, close_fds=True ) # Ensure subprocess is detached so closing connect will not # also close launched applications. if sys.platform == 'win32': options['creationflags'] = subprocess.CREATE_NEW_CONSOLE else: options['preexec_fn'] = os.setsid self.logger.debug( 'Launching {0} with options {1}'.format(command, options) ) process = subprocess.Popen(command, **options) except (OSError, TypeError): self.logger.exception( '{0} application could not be started with command "{1}".' .format(applicationIdentifier, command) ) success = False message = '{0} application could not be started.'.format( application['label'] ) else: self.logger.debug( '{0} application started. (pid={1})'.format( applicationIdentifier, process.pid ) ) return { 'success': success, 'message': message } data = event["data"] data["items"] = [] # Starting a job to show user the progress of scanning for files. job = ftrack.createJob("DJV: Scanning for files.", "queued", ftrack.User(id=event["source"]["user"]["id"])) job.setStatus("running") try: ftrack.EVENT_HUB.publish( ftrack.Event( topic='djvview.launch', data=data ), synchronous=True ) session = get_shared_session() session.event_hub.publish( ftrack_api.event.base.Event( topic='djvview.launch', data=data ), synchronous=True ) except: job.setStatus("failed") else: job.setStatus("done") return { "items": [ { "label": "Items to view", "type": "enumerator", "name": "path", "data": sorted( data["items"], key=itemgetter("label"), reverse=True ) } ] }
def constructWidget(self): '''Return widget instance to test.''' widget = QtGui.QWidget() layout = QtGui.QVBoxLayout() widget.setLayout(layout) notificationList = _notification_list.Notification() notificationList.setStyleSheet(''' QPushButton { border: 1px solid black; border-radius: 2px; background-color: #505050; color: #F0F0F0; padding: 4px; min-width: 80px; } QFrame { background-color: #2A2A2A; color: #969696; } QLabel { background-color: #323232; } QFrame#notification-list { border: 0; margin: 20px 0 0 0; } QFrame#notification-list QTableWidget { background-color: transparent; border: 0; } QFrame#notification-list QTableWidget::item { background-color: #323232; border-bottom: 1px solid #282828; padding: 0; } ''') layout.addWidget(notificationList) notificationList.addContext('157271fc-5fc6-11e2-a771-f23c91df25eb', 'task', False) notificationList.addContext( ftrack.User(getpass.getuser()).getId(), 'user', False) notificationList.addContext('01cfdcaa-c5ea-11e1-adec-f23c91df25eb', 'asset', False) notificationList.addContext('01cfdcaa-c5ea-11e1-adec-f23c91df25es', 'asset', False) notificationList.removeContext('01cfdcaa-c5ea-11e1-adec-f23c91df25es', False) notificationList.reload() def getEvents(event): '''Return events for context.''' context = notificationList._context cases = [] events = [] session = ftrack_api.Session() if context['asset']: cases.append('(feeds.owner_id in ({asset_ids}) and action is ' '"asset.published")'.format( asset_ids=','.join(context['asset']))) if context['task']: cases.append( 'parent_id in ({task_ids}) and action in ' '("change.status.shot", "change.status.task")'.format( task_ids=','.join(context['task']))) cases.append( '(parent_id in ({task_ids}) and action in ' '("update.custom_attribute.fend", "update.custom_attribute.fstart"))' .format(task_ids=','.join(context['task']))) if context['user']: cases.append('(feeds.owner_id in ({user_ids}) and action is ' '"db.append.task:user" and feeds.distance is "0" ' 'and feeds.relation is "assigned")'.format( user_ids=','.join(context['user']))) if cases: events = session.query( 'select id, action, parent_id, parent_type, created_at, data ' 'from Event where {0}'.format(' or '.join(cases))).all() events.sort(key=lambda event: event['created_at'], reverse=True) return events ftrack.EVENT_HUB.subscribe('topic=ftrack.crew.notification.get-events', getEvents) return widget