def find_all_assets(self, force_update=False, force_login=True): """ Returns a list of all assets in the project :param force_update: bool, Whether assets cache updated must be forced or not :param force_login: bool, Whether logging to production tracker is forced or not :return: variant, ArtellaAsset or list(ArtellaAsset) """ self._check_project() if self.__class__._assets and not force_update: return self.__class__._assets python.clear_list(self.__class__._assets) if not artellapipe.Tracker().is_logged() and force_login: artellapipe.Tracker().login() if not artellapipe.Tracker().is_logged(): LOGGER.warning( 'Impossible to find assets of current project because user is not log into production tracker' ) return None tracker = artellapipe.Tracker() assets_list = tracker.all_project_assets() if not assets_list: LOGGER.warning("No assets found in current project!") return None for asset_data in assets_list: new_asset = self.create_asset(asset_data) if not new_asset: continue self.__class__._assets.append(new_asset) return self.__class__._assets
def find_all_sequences(self, force_update=False, force_login=True): """ Returns a list of all sequences in the current project :param force_update: bool, Whether sequences cache updated must be forced or not :param force_login: bool, Whether logging to production tracker is forced or not :return: list(ArtellaSequence)) """ self._check_project() if self.sequences and not force_update: return self.sequences python.clear_list(self.__class__._sequences) if not artellapipe.Tracker().is_logged() and force_login: artellapipe.Tracker().login() if not artellapipe.Tracker().is_logged(): LOGGER.warning( 'Impossible to find sequences of current project because user is not log into production tracker') return None tracker = artellapipe.Tracker() sequences_list = tracker.all_project_sequences() if not sequences_list: LOGGER.warning('No sequences found in current project!') return None for sequence_data in sequences_list: new_sequence = self.create_sequence(sequence_data) self.__class__._sequences.append(new_sequence) return self.sequences
def get_assets_in_shot(self, shot, force_login=True): """ Returns all the assets contained in given shot breakdown defined in production tracker :param shot: :return: """ if not artellapipe.Tracker().is_logged() and force_login: artellapipe.Tracker().login() if not artellapipe.Tracker().is_logged(): LOGGER.warning( 'Impossible to find assets of current project because user is not log into production tracker' ) return None tracker = artellapipe.Tracker() assets_in_shots = tracker.all_assets_in_shot(shot) if not assets_in_shots: LOGGER.warning('No assets found in shot breakdown') return None found_assets = list() for asset_data in assets_in_shots: new_asset = self.create_asset(asset_data) found_assets.append(new_asset) return found_assets
def _create_login_form(self): """ Internal callback function that checks current Kitsu login status """ email = artellapipe.Tracker().email password = artellapipe.Tracker().password store_credentials = artellapipe.Tracker().store_credentials return KitsuLoginForm(email=email, password=password, store_credentials=store_credentials)
def ui(self): super(ArtellaAssetsLibrary, self).ui() supported_files = self.config.get('supported_files') self._library_widget = self.LIBRARY_WIDGET( project=self._project, supported_files=supported_files) self.main_layout.addWidget(self._library_widget) artellapipe.Tracker().logged.connect(self._on_valid_login) artellapipe.Tracker().unlogged.connect(self._on_valid_unlogin)
def setup_signals(self): self._project_artella_btn.clicked.connect(self._on_open_project_in_artella) self._project_folder_btn.clicked.connect(self._on_open_project_folder) self._settings_btn.clicked.connect(self._on_open_settings) self._assets_widget.assetAdded.connect(self._on_asset_added) self._attrs_stack.animFinished.connect(self._on_attrs_stack_anim_finished) self._shots_widget.shotAdded.connect(self._on_shot_added) self._settings_widget.closed.connect(self._on_close_settings) artellapipe.Tracker().logged.connect(self._on_valid_login) artellapipe.Tracker().unlogged.connect(self._on_valid_unlogin)
def _check_tracker(self, force_login=True): """ Internal function that checks whether or not production tracking is ready to be used with this manager :return: bool """ if not artellapipe.Tracker().is_logged() and force_login: artellapipe.Tracker().login() if not artellapipe.Tracker().is_logged(): LOGGER.warning( 'Impossible to find casting of current project because user is not log into production tracker' ) return None
def _on_kitsu_login_accepted(self, email, password, store_credentials): """ Internal callback function that is called when the user successfully log into Kitsu through Kitsu login form :param email: variant, str or None :param password: variant, str or None :param store_credentials: bool """ artellapipe.Tracker().email = email artellapipe.Tracker().password = password artellapipe.Tracker().store_credentials = store_credentials index = self._main_stack.indexOf(self._kitsu_waiter) self._main_stack.slide_in_index(index, force=True)
def get_ocurrences_of_asset_in_shot(self, asset_name, shot_name, force_update=False): """ Returns the number of ocurrences of given asset in given shot :param asset_name: str, name of the asset :param shot_name: str, name of the shot :return: int or None """ if not self._check_project(): return None asset = artellapipe.AssetsMgr().find_asset(asset_name) if not asset: LOGGER.warning( 'Impossible to return occurrences because asset "{}" does not exists!' .format(asset_name)) return None shot = shots.ShotsManager().find_shot(shot_name) if not shot: LOGGER.warning( 'Impossible to return occurrences because shot "{}" does not exists!' .format(shot_name)) return None shot_id = shot.get_id() tracker = artellapipe.Tracker() total_occurrences = tracker.get_occurrences_of_asset_in_shot( shot_id, asset_name, force_update=force_update) return total_occurrences
def get_all_task_statuses(self): """ Returns all task statuses for current project :return: list(str) """ return artellapipe.Tracker().all_task_statuses()
def _on_open_kitsu_login(self): """ Internal callback function that is called when the user presses the Kitsu button """ if artellapipe.Tracker().is_logged(): pass # user_data = artellapipe.Tracker().user_data # self._ballon = KitsuUserBalloon(user_data=user_data) # rect_btn = self._kitsu_btn.geometry() # rect_balloon = self._ballon.geometry() # pos = QCursor.pos() # pos.setX(pos.x() - (self._kitsu_btn.width() / 2) - 20) # rect_balloon.setRect( # pos.x(), pos.y(), rect_btn.width(), rect_btn.height() # ) # self._ballon.setGeometry(rect_balloon) # self._ballon.show() else: login_dialog = logindialog.KitsuLoginDialog(project=self._project, parent=self._window) login_dialog.validLogin.connect(self._on_kitsu_login) login_dialog.invalidLogin.connect(self._on_kitsu_logout) login_dialog.canceledLogin.connect(self._on_kitsu_cancel) self._slider_panel = panel.SliderPanel('Kitsu Login', parent=self._window) self._slider_panel.position = 'right' self._slider_panel.setFixedWidth(315) self._slider_panel.set_widget(login_dialog) self._slider_panel.show()
def _kitsu_login(self, *args, **kwargs): """ Internal function that is called by Kitsu Worker to execute login :param data: :return: """ email = artellapipe.Tracker().email password = artellapipe.Tracker().password if not email or not password: LOGGER.warning('Impossible to login into Kitsu because user and password are not given!') return valid_login = artellapipe.Tracker().login(email=email, password=password) return valid_login
def _on_kitsu_worker_failure(self, uid, msg): """ Internal callback function that is called when Kitsu worker fails :param uid: str :param msg: str """ artellapipe.Tracker().reset_user_info() LOGGER.error('{} | {}'.format(uid, msg)) self._main_stack.slide_in_index(0)
def try_kitsu_login(self): """ Function that tries to log into Kitsu """ valid_login = artellapipe.Tracker().login() if valid_login: self._kitsu_login() return True return False
def get_tasks_for_shot(self, shot_name): """ Returns all tasks attached to the given shot :param shot_name: str :return: """ shot_found = artellapipe.ShotsMgr().find_shot(shot_name) if not shot_found: LOGGER.warning('No shot found with name: "{}"!'.format(shot_name)) return None return artellapipe.Tracker().get_tasks_in_shot(shot_found.get_id())
def run(self): try: if self._preview_id and self._path: if not os.path.isfile(self._path) or self._force: artellapipe.Tracker().download_preview_file_thumbnail( self._preview_id, self._path) if not self._path or not os.path.isfile(self._path): icon = QIcon() else: icon_pixmap = QPixmap(self._path) icon = QIcon(icon_pixmap) self.signals.triggered.emit(icon) except Exception as exc: LOGGER.error('Cannot load thumbnail image: {}!'.format(exc))
def _on_kitsu_worker_completed(self, uid, valid_login): """ Internal callback function that is called when Kitsu worker finishes :param uid: str :param valid_login: bool """ if valid_login: if not artellapipe.Tracker().is_logged(): LOGGER.warning('Something went wrong during Kitsu login') return False if artellapipe.Tracker().email and artellapipe.Tracker().password: store_credentials = bool(artellapipe.Tracker().store_credentials) self._project.settings.set('kitsu_store_credentials', store_credentials) self.validLogin.emit() self.close() else: qtutils.show_error( self, 'Error while logging into Kitsu', 'Kitsu credentials are not valid. Try again please!') self._main_stack.slide_in_index(0) return False return True
def find_all_shots(self, force_update=False, force_login=True): """ Returns all shots of the project :param force_update: bool, Whether shots cache updated must be forced or not :param force_login: bool, Whether logging to production tracker is forced or not :return: list(ArtellaShot) """ self._check_project() if self.shots and not force_update: return self.shots python.clear_list(self.__class__._shots) if not artellapipe.Tracker().is_logged() and force_login: artellapipe.Tracker().login() if not artellapipe.Tracker().is_logged(): LOGGER.warning( 'Impossible to find shots of current project because user is not log into production tracker' ) return None tracker = artellapipe.Tracker() shots_list = tracker.all_project_shots() if not shots_list: LOGGER.warning('No shots found in current project!') return None for shot_data in shots_list: new_shot = self.create_shot(shot_data) self.__class__._shots.append(new_shot) self.__class__._shots.sort(key=lambda x: x.get_start_frame(), reverse=True) return self.shots
def update_kitsu_status(self): """ Synchronizes current Kitsu status between UserInfo and current project """ if not self._project: LOGGER.warning( 'Impossible to update Kitsu Status because Project is not defined!' ) return if artellapipe.Tracker().is_logged(): self._kitsu_btn.setIcon(self._kitsu_on_icon) self._kitsu_logout_btn.setVisible(True) else: self._kitsu_btn.setIcon(self._kitsu_off_icon) self._kitsu_logout_btn.setVisible(False)
def _on_kitsu_logout(self): """ Internal callback function that is called when the user presses the logout button """ remove_credentials = False res = qtutils.show_question( self, 'Kitsu Logout', 'Do you want to remove Kitsu stored credentials?') if res == QMessageBox.Yes: remove_credentials = True valid = artellapipe.Tracker().logout( remove_credentials=remove_credentials) if not valid: LOGGER.warning('Error while logging out from Kitsu') return self.update_kitsu_status() self.logout.emit()
def get_task_status_for_shot(self, shot_name, task_name): """ Returns the status of the given task in the given shot :param shot_name: str, name of the shot :param task_name: str, name of the task :return: str, status name """ tasks_for_shot = self.get_tasks_for_shot(shot_name) if not tasks_for_shot: return task_found = None for task in tasks_for_shot: if task.name == task_name: task_found = task break if not task_found: return return artellapipe.Tracker().get_task_status(task_found.id)
def ui(self): super(ArtellaAssetsManager, self).ui() # Create Top Menu Bar self._menu_bar = self._setup_menubar() if not self._menu_bar: self._menu_bar = QMenuBar(self) self.main_layout.addWidget(self._menu_bar) sep = QFrame() sep.setFrameShape(QFrame.HLine) sep.setFrameShadow(QFrame.Raised) self.main_layout.addWidget(sep) self._main_stack = stack.SlidingStackedWidget(parent=self) self._attrs_stack = stack.SlidingStackedWidget(parent=self) self._shots_stack = stack.SlidingStackedWidget(parent=self) no_items_widget = QFrame() no_items_widget.setFrameShape(QFrame.StyledPanel) no_items_widget.setFrameShadow(QFrame.Sunken) no_items_layout = QVBoxLayout() no_items_layout.setContentsMargins(0, 0, 0, 0) no_items_layout.setSpacing(0) no_items_widget.setLayout(no_items_layout) no_items_lbl = QLabel() no_items_pixmap = tpDcc.ResourcesMgr().pixmap('no_asset_selected') no_items_lbl.setPixmap(no_items_pixmap) no_items_lbl.setAlignment(Qt.AlignCenter) no_items_layout.addItem(QSpacerItem(0, 10, QSizePolicy.Preferred, QSizePolicy.Expanding)) no_items_layout.addWidget(no_items_lbl) no_items_layout.addItem(QSpacerItem(0, 10, QSizePolicy.Preferred, QSizePolicy.Expanding)) no_shot_selected_widget = QFrame() no_shot_selected_widget.setFrameShape(QFrame.StyledPanel) no_shot_selected_widget.setFrameShadow(QFrame.Sunken) no_shot_selected_layout = QVBoxLayout() no_shot_selected_layout.setContentsMargins(0, 0, 0, 0) no_shot_selected_layout.setSpacing(0) no_shot_selected_widget.setLayout(no_shot_selected_layout) no_shot_selected_lbl = QLabel() no_sequence_selected_pixmap = tpDcc.ResourcesMgr().pixmap('no_shot_selected') no_shot_selected_lbl.setPixmap(no_sequence_selected_pixmap) no_shot_selected_lbl.setAlignment(Qt.AlignCenter) no_shot_selected_layout.addItem(QSpacerItem(0, 10, QSizePolicy.Preferred, QSizePolicy.Expanding)) no_shot_selected_layout.addWidget(no_shot_selected_lbl) no_shot_selected_layout.addItem(QSpacerItem(0, 10, QSizePolicy.Preferred, QSizePolicy.Expanding)) no_assets_widget = QWidget() no_assets_layout = QVBoxLayout() no_assets_layout.setContentsMargins(2, 2, 2, 2) no_assets_layout.setSpacing(2) no_assets_widget.setLayout(no_assets_layout) no_assets_frame = QFrame() no_assets_frame.setFrameShape(QFrame.StyledPanel) no_assets_frame.setFrameShadow(QFrame.Sunken) no_assets_frame_layout = QHBoxLayout() no_assets_frame_layout.setContentsMargins(2, 2, 2, 2) no_assets_frame_layout.setSpacing(2) no_assets_frame.setLayout(no_assets_frame_layout) no_assets_layout.addWidget(no_assets_frame) no_assets_found_label = QLabel() no_assets_found_pixmap = tpDcc.ResourcesMgr().pixmap('no_assets_found') no_assets_found_label.setPixmap(no_assets_found_pixmap) no_assets_frame_layout.addItem(QSpacerItem(10, 0, QSizePolicy.Expanding, QSizePolicy.Preferred)) no_assets_frame_layout.addWidget(no_assets_found_label) no_assets_frame_layout.addItem(QSpacerItem(10, 0, QSizePolicy.Expanding, QSizePolicy.Preferred)) self._waiter = waiter.ArtellaWaiter() self._user_info_layout = QVBoxLayout() self._user_info_layout.setContentsMargins(0, 0, 0, 0) self._user_info_layout.setSpacing(0) self._user_info_widget = QWidget() self._user_info_widget.setLayout(self._user_info_layout) self._shots_info_layout = QVBoxLayout() self._shots_info_layout.setContentsMargins(0, 0, 0, 0) self._shots_info_layout.setSpacing(0) self._shots_info_widget = QWidget() self._shots_info_widget.setLayout(self._shots_info_layout) self._tab_widget = tabs.TearOffTabWidget() self._tab_widget.setTabsClosable(False) self._tab_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self._tab_widget.setMinimumHeight(330) self._assets_widget = self.ASSET_WIDGET_CLASS(project=self._project, show_viewer_menu=True) self._shots_widget = self.SHOTS_WIDGET_CLASS(project=self._project) self._settings_widget = AssetsManagerSettingsWidget(settings=self.settings) assets_widget = QWidget() assets_layout = QVBoxLayout() assets_layout.setContentsMargins(0, 0, 0, 0) assets_layout.setSpacing(0) assets_widget.setLayout(assets_layout) shots_widget = QWidget() shots_layout = QVBoxLayout() shots_layout.setContentsMargins(0, 0, 0, 0) shots_layout.setSpacing(0) shots_widget.setLayout(shots_layout) self.main_layout.addWidget(self._main_stack) self._main_stack.addWidget(no_assets_widget) self._main_stack.addWidget(self._tab_widget) self._main_stack.addWidget(self._settings_widget) self._attrs_stack.addWidget(no_items_widget) self._attrs_stack.addWidget(self._waiter) self._attrs_stack.addWidget(self._user_info_widget) self._shots_stack.addWidget(no_shot_selected_widget) self._shots_stack.addWidget(self._shots_info_widget) assets_splitter = QSplitter(Qt.Horizontal) assets_splitter.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) assets_splitter.addWidget(self._assets_widget) assets_splitter.addWidget(self._attrs_stack) shots_splitter = QSplitter(Qt.Horizontal) shots_splitter.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) shots_splitter.addWidget(self._shots_widget) shots_splitter.addWidget(self._shots_stack) self._tab_widget.addTab(assets_splitter, 'Assets') self._tab_widget.addTab(shots_splitter, 'Sequences | Shots') if not artellapipe.Tracker().needs_login(): self._main_stack.slide_in_index(1)
def stamp_image(self, source, output, config_dict=None): res_x = image.get_image_width(source) res_y = image.get_image_height(source) top_band = config_dict.get('top_band', None) bottom_band = config_dict.get('bottom_band', None) # font_file = config_dict.get('text_font', 'Arial.ttf') font_file = 'Arial.ttf' font_family = config_dict.get('text_font_family', 'Regular') text_margin_x = config_dict.get('text_margin_x', 300) text_margin_y = config_dict.get('text_margin_y', 100) framecode = config_dict.get('framecode', '00:00:00:00') top_band_height = 0 if top_band and os.path.isfile(top_band): top_band_height = image.get_image_height(top_band) res_y += top_band_height bottom_band_height = 0 if bottom_band and os.path.isfile(bottom_band): bottom_band_height = image.get_image_height(bottom_band) res_y += bottom_band_height font_db = QFontDatabase() font_db.addApplicationFont(font_file) text_font = QFont(font_family) font_size = 32 font_height = QFontMetrics(text_font).height() user = str(artellapipe.Tracker().get_user_name()) shot_name = config_dict.get('shot_name', '') or '' task_name = config_dict.get('task_name', '') or '' task_comment = str(config_dict.get('task_comment', '')) if artellapipe.Tracker().is_tracking_available(): fps = artellapipe.Tracker().get_project_fps() else: fps = 24 camera = str(config_dict.get('camera', 'No camera')) start_frame = str(config_dict.get('start_frame', None)) focal_length = None if camera and camera != 'No camera' and tp.Dcc.object_exists(camera): focal_length = tp.Dcc.get_camera_focal_length(camera) sequence = fileseq.findSequenceOnDisk(source) frames_dict = OrderedDict() total_frames = len(sequence) for i in range(total_frames): sequence_frame = sequence[i] empty_frame_path = self._get_temp_file_path( sequence_frame, 'empty') empty_frame = image.create_empty_image( empty_frame_path, resolution_x=res_x, resolution_y=res_y, background_color=[92, 92, 92]) frames_dict[sequence_frame] = empty_frame frame_outputs = dict() for i, (frame, empty_frame) in enumerate(frames_dict.items()): # Overlay playblast stream = ffmpeglib.overlay_inputs(empty_frame, frame, y=top_band_height) # Overlay top and bottom bands if top_band: stream = ffmpeglib.overlay_inputs(stream, top_band) if bottom_band: stream = ffmpeglib.overlay_inputs(stream, bottom_band, y=res_y - bottom_band_height) # Draw task and comment texts stream = ffmpeglib.draw_text(stream, task_name, x=40, y=text_margin_y + font_height, font_file=font_file, font_size=font_size) stream = ffmpeglib.draw_text(stream, task_comment, x=40, y=text_margin_y + (font_height * 2) + 25, font_file=font_file, font_size=font_size) # Draw frame current_frame = int(float(start_frame)) + i fps_text = '{} ({})'.format(current_frame, i + 1) stream = ffmpeglib.draw_text(stream, fps_text, x=40, y=res_y - font_height - text_margin_y - 70, font_file=font_file, font_size=font_size) # Draw camera name and focal length texts camera_text_width = QFontMetrics(text_font).width(camera) stream = ffmpeglib.draw_text(stream, camera, x=res_x / 2 - camera_text_width, y=res_y - font_height - text_margin_y - 70, font_file=font_file, font_size=font_size) fps_text = 'FPS: {}'.format(str(int(float(fps)))) if focal_length: fps_text = ' Focal Length: {}'.format(focal_length) fps_focal_length_width = QFontMetrics(text_font).width( str(fps_text)) stream = ffmpeglib.draw_text(stream, fps_text, x=res_x / 2 - fps_focal_length_width, y=res_y - font_height - text_margin_y - 20, font_file=font_file, font_size=font_size) # Draw user and shot texts user_text_width = QFontMetrics(text_font).width(user) shot_text_width = QFontMetrics(text_font).width(shot_name) user_shot_text_width = max(user_text_width, shot_text_width) stream = ffmpeglib.draw_text( stream, user, x=res_x - user_shot_text_width - text_margin_x, y=res_y - font_height - text_margin_y - 70, font_file=font_file, font_size=font_size) stream = ffmpeglib.draw_text( stream, shot_name, x=res_x - user_shot_text_width - text_margin_x, y=res_y - font_height - text_margin_y - 20, font_file=font_file, font_size=font_size) new_file_path = self._get_temp_file_path(empty_frame, 'main', index=i) frame_save = ffmpeglib.save_to_file(stream, new_file_path) frame_outputs[new_file_path] = frame_save if not frame_outputs: return frame_paths = frame_outputs.keys() frame_outs = frame_outputs.values() ffmpeglib.run_multiples_outputs_at_once(frame_outs) for frame_path in frame_paths: if not frame_path or not os.path.isfile(frame_path): LOGGER.warning( 'Some frames were not generated properly. Aborting operation ...' ) return video_file_path = self._get_temp_file_path(source, 'video') if not video_file_path.endswith('.mp4'): video_file_path_split = os.path.splitext(video_file_path) video_file_path = '{}.mp4'.format(video_file_path_split[0]) ffmpeglib.create_video_from_sequence_file(frame_paths[0], video_file_path) if not os.path.isfile(video_file_path): LOGGER.error('Error while stamping playblast video ...') return # scale = ffmpeglib.scale_video(video_file_path, 1920, 1080) # ffmpeglib.save_to_file(scale, output, run_stream=True) draw_timestamp_stream = ffmpeglib.draw_timestamp_on_video( video_file_path, text='Time: ', x=40, y=res_y - font_height - text_margin_y - 20, font_file=font_file, font_size=font_size, timecode_rate=fps, timecode=framecode) ffmpeglib.save_to_file(draw_timestamp_stream, output, run_stream=True) return output