# builtin import sys import os # third party import win32gui from PyQt5.QtWidgets import QApplication, QPushButton, QSlider, QVBoxLayout, QWidget, QGroupBox, QTextEdit, QGridLayout from PyQt5.QtWidgets import QFileDialog from PyQt5.QtCore import Qt import pymiere from pymiere import wrappers default_folder = r"C:\Users\Quentin\Desktop\temp\playblast" def swap_focus(func): """ Use this decorator for any method that does an action in premiere. Not needed for queries but for any action that will change something in the UI we have to give focus to Premiere """ def wrapper(self, *args, **kwargs): win32gui.SetForegroundWindow(self.premiere_window_id) result = func(self, *args, **kwargs) win32gui.SetForegroundWindow(self.ui_id) return result return wrapper class HorizontalGroup(QGroupBox): """ UI wrapper for QGroupBox with integrated GridLayout""" def __init__(self, *args): super(HorizontalGroup, self).__init__(*args) self.internal_layout = QGridLayout() self.setLayout(self.internal_layout) def addWidget(self, widget, x, y): self.internal_layout.addWidget(widget, x, y) class PymiereControl(QWidget): """ main window """ def __init__(self): super(PymiereControl, self).__init__() # get UI ids for swapping focus self.premiere_window_id = win32gui.FindWindow("Premiere Pro", None) self.ui_id = self.winId() # create UI layout = QVBoxLayout() # player player = HorizontalGroup("Playback") play = QPushButton("Play/Pause") forward = QPushButton("Forward 10 frames") backward = QPushButton("Backward 10 frames") play.clicked.connect(self.play_func) forward.clicked.connect(self.forward_func) backward.clicked.connect(self.backward_func) player.addWidget(play, 0, 0) player.addWidget(backward, 0, 1) player.addWidget(forward, 0, 2) # selection selection = HorizontalGroup("Selection") refresh_selection = QPushButton("Refresh") refresh_selection.clicked.connect(self.refresh_selection_func) self.selection_info = QTextEdit() change_path = QPushButton("Change media path") change_path.clicked.connect(self.change_path_func) move_clips = QPushButton("Move selected by : ") move_clips.clicked.connect(self.move_selected_func) self.move_seconds = QSlider(Qt.Horizontal) self.move_seconds.setTickInterval(1) self.move_seconds.setMinimum(-5) self.move_seconds.setMaximum(5) self.move_seconds.setTickPosition(QSlider.TicksBelow) selection.addWidget(refresh_selection, 0, 0) selection.addWidget(self.selection_info, 0, 1) selection.addWidget(change_path, 1, 0) selection.addWidget(move_clips, 2, 0) selection.addWidget(self.move_seconds, 2, 1) # import/export io = HorizontalGroup("I/O") import_file = QPushButton("Import") import_file.clicked.connect(self.import_func) import_insert_file = QPushButton("Import + insert") import_insert_file.clicked.connect(self.import_insert_func) export_frame = QPushButton("Export current frame as PNG") export_frame.clicked.connect(self.export_frame_func) export_encoder = QPushButton("Export region of sequence") export_encoder.clicked.connect(self.export_encoder_func) open = QPushButton("Open project") open.clicked.connect(self.open_func) save = QPushButton("Save project") save.clicked.connect(self.save_func) saveas = QPushButton("Save project as") saveas.clicked.connect(self.saveas_func) close = QPushButton("Close project") close.clicked.connect(self.close_func) io.addWidget(import_file, 0, 0) io.addWidget(import_insert_file, 0, 1) io.addWidget(export_frame, 1, 0) io.addWidget(export_encoder, 1, 1) io.addWidget(open, 2, 0) io.addWidget(close, 2, 1) io.addWidget(save, 3, 0) io.addWidget(saveas, 3, 1) # miscelanious miscellaneous = HorizontalGroup("Miscellaneous") add_effect = QPushButton("Add effect on clip") add_effect.clicked.connect(self.add_effect_func) miscellaneous.addWidget(add_effect, 1, 0) layout.addWidget(player) layout.addWidget(selection) layout.addWidget(io) layout.addWidget(miscellaneous) self.setLayout(layout) self.setWindowFlags(Qt.WindowStaysOnTopHint) # avoid doing the query for active sequence in each method self.__active_sequence = pymiere.objects.app.project.activeSequence if wrappers.check_active_sequence(crash=False) else None @property def active_sequence(self): if self.__active_sequence is None: wrappers.check_active_sequence() self.__active_sequence = pymiere.objects.app.project.activeSequence return self.__active_sequence @swap_focus def play_func(self, *args): """ Press play/pause button for main player """ pymiere.objects.qe.startPlayback() @swap_focus def forward_func(self, *args, frames=10): """ Put the timeline cursor x frame later """ current_time = self.active_sequence.getPlayerPosition() current_time.seconds += 1 / 25 * frames self.active_sequence.setPlayerPosition(current_time.ticks) def backward_func(self, *args, frames=10): """ Put the timeline cursor x frame before """ current_time = self.active_sequence.getPlayerPosition() current_time.seconds -= 1 / 25 * frames self.active_sequence.setPlayerPosition(current_time.ticks) def refresh_selection_func(self, *args): """ Refresh list of selected clip in UI, should be triggered by event in the futur """ clips = self.active_sequence.getSelection() result = list(set([clip.name for clip in clips])) self.selection_info.setText("\n".join(result)) @swap_focus def change_path_func(self, *args, new_path="D:/3d/simulations/bumperCars/demo/wallAway.0101.jpeg"): """ Change the path of selected clips to this new media """ clips = self.active_sequence.getSelection() new_path = os.path.normpath(new_path) for clip in clips: item = clip.projectItem # clips are linked to a project item current_path = item.getMediaPath() if new_path == current_path: # already using the new path... continue if not item.canChangeMediaPath(): continue item.changeMediaPath(new_path, overrideChecks=True) @swap_focus def move_selected_func(self, *args): clips = self.active_sequence.getSelection() seconds = self.move_seconds.value() for clip in clips: wrappers.move_clip(clip, seconds) @swap_focus def add_effect_func(self, *args): """ add a swirl effect and change parameteres """ qe_project = pymiere.objects.qe.project first_track = qe_project.getActiveSequence().getVideoTrackAt(0) index = 0 clip = None while clip is None or clip.type == "Empty": clip = first_track.getItemAt(index) index += 1 clip.addVideoEffect(qe_project.getVideoEffectByName("Twirl")) twirl = self.active_sequence.videoTracks[0].clips[0].components[2] for property in twirl.properties: if property.displayName == "Angle": property.setValue(50, True) @swap_focus def import_func(self, *args): file_to_import = os.path.normpath(QFileDialog.getOpenFileName(caption="Choose a file to import...", directory=default_folder)[0]) root_bin = pymiere.objects.app.project.getInsertionBin() pymiere.objects.app.project.importFiles([file_to_import], True, root_bin, True) result = root_bin.findItemsMatchingMediaPath(file_to_import, True) if len(result) == 0: raise ImportError("Failed to find the imported items") if len(result) != 1: raise ValueError("Import sucessfull but there are more than one clips matching path {} in the root bin".format(file_to_import)) return result[0] @swap_focus def import_insert_func(self, *args): item = self.import_func() self.insert_func(project_item=item) def insert_func(self, *args, project_item): current_time = self.active_sequence.getPlayerPosition() self.active_sequence.insertClip(project_item, current_time, 0, 0) @swap_focus def export_frame_func(self, *args): active_sequence_qe = pymiere.objects.qe.project.getActiveSequence() time = active_sequence_qe.CTI.timecode filename = QFileDialog.getSaveFileName(caption="Export png frame there...", filter="Image (*.png)", directory=default_folder)[0] filename = os.path.normpath(filename) if os.path.isfile(filename): os.remove(filename) active_sequence_qe.exportFramePNG(time, filename) @swap_focus def export_encoder_func(self, *args): # No because I have not installed Adobe encoder for CC 2019... # encoder = pymiere.objects.app.encoder # encoder.launchEncoder() self.active_sequence.exportAsMediaDirect( "C:\\Users\\Quentin\\Desktop\\temp\\test.mp4", # output path "C:\\Program Files\\Adobe\\Adobe Premiere Pro CC 2019\\MediaIO\\systempresets\\4C4F5249_6D706732\\HD 720p 25.epr", # encoding preset path (.epr file) 1 ) def close_func(self, *args): self.__active_sequence = None pymiere.objects.app.project.closeDocument() def open_func(self, *args): self.__active_sequence = None filepath = os.path.normpath(QFileDialog.getOpenFileName(caption="Choose a project to open...", directory=default_folder)[0]) # if not filepath.endswith(".pproj"): # raise ValueError("Given path was not a valid premiere pro document") pymiere.objects.app.openDocument(filepath) def save_func(self, *args): pymiere.objects.app.project.save() def saveas_func(self, *args): filename = QFileDialog.getSaveFileName(caption="Choose new location to save project...", filter="Project (*.pproj)", directory=default_folder)[0] filename = os.path.normpath(filename) if os.path.isfile(filename): os.remove(filename) pymiere.objects.app.project.saveAs(filename) if __name__ == "__main__": # handle pyqt silent exceptions sys._excepthook = sys.excepthook def exception_hook(exctype, value, traceback): print(exctype, value, traceback) sys._excepthook(exctype, value, traceback) sys.exit(1) sys.excepthook = exception_hook app = QApplication([]) pymiere_ui = PymiereControl() pymiere_ui.show() app.exec_()