From f9aa1b3878ac48b9527b46653ce75bd0342b7efc Mon Sep 17 00:00:00 2001 From: Andrew Date: Wed, 5 Nov 2025 04:55:38 +0300 Subject: [PATCH] private plugin installation --- src/mainwindow.py | 35 +++++++++ src/pluginthread.py | 162 ++++++++++++++++++++++++++++++++++++++++++ src/utils.py | 12 +++- translations/en_US.py | 8 ++- translations/ru_RU.py | 7 +- translations/uk_UA.py | 7 +- 6 files changed, 227 insertions(+), 4 deletions(-) create mode 100644 src/pluginthread.py diff --git a/src/mainwindow.py b/src/mainwindow.py index ad4bf4d..695cf23 100644 --- a/src/mainwindow.py +++ b/src/mainwindow.py @@ -9,6 +9,7 @@ from src.installationthread import InstallationThread from src.runaethread import RunAEThread from src.runexethread import RunExeThread from src.killaethread import KillAEThread +from src.pluginthread import PluginThread from src.removeaethread import RemoveAEThread from src.utils import ( check_aegnux_tip_marked, get_default_terminal, get_wine_bin_path_env, @@ -40,6 +41,11 @@ class MainWindow(MainWindowUI): self.remove_ae_thread = RemoveAEThread() self.remove_ae_thread.finished_signal.connect(self._finished) + self.plugin_thread = PluginThread() + self.plugin_thread.log_signal.connect(self._log) + self.plugin_thread.progress_signal.connect(self.progress_bar.setValue) + self.plugin_thread.finished_signal.connect(self._finished) + self.alt_t_action = QAction(self) self.alt_t_action.setShortcut(QKeySequence("Alt+T")) self.alt_t_action.triggered.connect(self.run_command_alt_t) @@ -56,6 +62,7 @@ class MainWindow(MainWindowUI): self.ae_action.triggered.connect(self.run_ae_button_clicked) self.exe_action.triggered.connect(self.run_exe_button_clicked) self.reg_action.triggered.connect(self.reg_button_clicked) + self.plugininst_action.triggered.connect(self.install_plugins_button_clicked) self.kill_action.triggered.connect(self.kill_ae_button_clicked) self.log_action.triggered.connect(self.toggle_logs) self.term_action.triggered.connect(self.run_command_alt_t) @@ -74,6 +81,7 @@ class MainWindow(MainWindowUI): self.runMenu.setEnabled(True) self.browseMenu.setEnabled(True) self.kill_action.setEnabled(True) + self.plugininst_action.setEnabled(True) self.term_action.setEnabled(True) else: @@ -85,11 +93,13 @@ class MainWindow(MainWindowUI): self.browseMenu.setEnabled(False) self.kill_action.setEnabled(False) self.term_action.setEnabled(False) + self.plugininst_action.setEnabled(False) def _construct_menubar(self): self.runMenu = self.menuBar().addMenu(gls('run_menu')) self.ae_action = self.runMenu.addAction(gls('ae_action')) self.exe_action = self.runMenu.addAction(gls('exe_action')) + self.plugininst_action = self.runMenu.addAction(gls('plugininst_action')) self.reg_action = self.runMenu.addAction(gls('reg_action')) self.browseMenu = self.menuBar().addMenu(gls('browse_menu')) @@ -108,6 +118,8 @@ class MainWindow(MainWindowUI): self.install_button.setEnabled(not lock) self.run_button.setEnabled(not lock) self.remove_aegnux_button.setEnabled(not lock) + + self.runMenu.setEnabled(not lock) @Slot() def toggle_logs(self): @@ -172,6 +184,29 @@ class MainWindow(MainWindowUI): self.progress_bar.show() self.install_thread.start() + @Slot() + def install_plugins_button_clicked(self): + QMessageBox.information( + self, + gls('plugin_note'), + gls('plugin_note_text') + ) + + filename, _ = QFileDialog.getOpenFileName( + self, + gls('offline_ae_zip_title'), + "", + "Zip Files (*.zip);;All Files (*)" + ) + if filename == '': + return + + self.plugin_thread.set_plugin_zip_filename(filename) + + self.lock_ui() + self.progress_bar.show() + self.plugin_thread.start() + @Slot() def run_ae_button_clicked(self): self.lock_ui() diff --git a/src/pluginthread.py b/src/pluginthread.py new file mode 100644 index 0000000..4692538 --- /dev/null +++ b/src/pluginthread.py @@ -0,0 +1,162 @@ +import os +import shutil +from src.processthread import ProcessThread +from src.utils import get_private_plugins_unpack_path, get_ae_plugins_dir, get_wineprefix_dir + + +class PluginThread(ProcessThread): + def __init__(self): + super().__init__() + + def set_plugin_zip_filename(self, filename: str): + self.plugin_zip_filename = filename + + def run(self): + self.log_signal.emit('[DEBUG] Unpacking plugins from the archive...') + self.progress_signal.emit(5) + self.remove_ppu_dir() + + ppu_dir = get_private_plugins_unpack_path() + self.unpack_zip(self.plugin_zip_filename, ppu_dir.as_posix()) + self.progress_signal.emit(15) + + self.install_aex_plugins() + self.install_cep_extensions() + self.install_presets() + self.run_installers() + + self.remove_ppu_dir() + self.progress_signal.emit(95) + self.log_signal.emit('[INFO] The plugins have been installed') + + self.finished_signal.emit(True) + self.progress_signal.emit(100) + + def install_aex_plugins(self): + self.log_signal.emit('[DEBUG] Installing aex plugins...') + + ppu_dir = get_private_plugins_unpack_path() + aex_src = ppu_dir.joinpath('aex') + + for item in os.listdir(aex_src.as_posix()): + if self._is_cancelled: + self.cancelled.emit() + return + src_path = aex_src.joinpath(item) + dst_path = get_ae_plugins_dir().joinpath(item) + + if os.path.isdir(src_path): + shutil.copytree(src_path, dst_path, dirs_exist_ok=True) + else: + shutil.copy2(src_path, dst_path) + + self.log_signal.emit('[INFO] AEX plugins installed') + self.progress_signal.emit(30) + + def install_presets(self): + self.log_signal.emit('[DEBUG] Installing presets...') + + ppu_dir = get_private_plugins_unpack_path() + preset_src = ppu_dir.joinpath('preset-backup') + preset_dest = get_wineprefix_dir().joinpath('drive_c/users/relative/Documents/Adobe/After Effects 2024/User Presets') + + os.makedirs(preset_dest, exist_ok=True) + for item in os.listdir(preset_src): + if self._is_cancelled: + self.cancelled.emit() + return + src_path = preset_src.joinpath(item) + dst_path = preset_dest.joinpath(item) + if os.path.isdir(src_path): + shutil.copytree(src_path, dst_path, dirs_exist_ok=True) + else: + shutil.copy2(src_path, dst_path) + + self.log_signal.emit("[INFO] Presets installed") + self.progress_signal.emit(55) + + def run_installers(self): + self.log_signal.emit('[DEBUG] Running installers...') + ppu_dir = get_private_plugins_unpack_path() + install_src = ppu_dir.joinpath('installer') + + installers_counter = 0 + + for exe in os.listdir(install_src.as_posix()): + if exe.endswith('.exe') and exe not in ['E3D.exe', 'saber.exe']: + self.progress_signal.emit(55 + installers_counter * 3) + + self.log_signal.emit(f"[INFO] Installing: {exe}") + self.run_command( + ['wine', exe, '/verysilent', '/suppressmsgboxes'], + install_src.as_posix(), True + ) + + self.progress_signal.emit(70) + + # Special handling for E3D and saber + for exe in ['E3D.exe', 'saber.exe']: + self.log_signal.emit(f"[INFO] Please manually install: {exe}") + self.run_command( + ['wine', exe], + install_src.as_posix(), True + ) + + self.progress_signal.emit(85) + self.copy_element_files() + + def copy_element_files(self): + self.log_signal.emit('[DEBUG] Copying Element files...') + + ppu_dir = get_private_plugins_unpack_path() + install_src = ppu_dir.joinpath('installer') + + video_copilot_dir = get_ae_plugins_dir().joinpath("VideoCopilot") + element_files = [ + ("Element.aex", "Element.aex"), + ("Element.license", "Element.license") + ] + + for src_name, dst_name in element_files: + src_path = install_src.joinpath(src_name) + if os.path.exists(src_path): + shutil.copy2(src_path, os.path.join(video_copilot_dir, dst_name)) + self.log_signal.emit(f"[INFO] {src_name} copied successfully") + + self.log_signal.emit("[INFO] Element installed") + self.progress_signal.emit(90) + + def install_cep_extensions(self): + self.log_signal.emit('[DEBUG] Installing CEP extensions...') + + ppu_dir = get_private_plugins_unpack_path() + cep_dir = ppu_dir.joinpath('CEP') + + cep_reg_file = cep_dir.joinpath("AddKeys.reg") + self.run_command(['wine', "regedit", cep_reg_file.as_posix()], in_prefix=True) + + self.install_flow() + + self.log_signal.emit("[INFO] CEP extensions installed") + self.progress_signal.emit(45) + + def install_flow(self): + ppu_dir = get_private_plugins_unpack_path() + cep_dir = ppu_dir.joinpath('CEP') + + self.log_signal.emit('[DEBUG] Installing Flow...') + + flow_src = cep_dir.joinpath("flowv1.4.2") + cep_dst = get_wineprefix_dir().joinpath("drive_c/Program Files (x86)/Common Files/Adobe/CEP/extensions") + + os.makedirs(cep_dst, exist_ok=True) + shutil.copytree(flow_src, os.path.join(cep_dst, "flowv1.4.2"), dirs_exist_ok=True) + self.log_signal.emit("[INFO] Flow installed") + + def remove_ppu_dir(self): + ppu_dir = get_private_plugins_unpack_path() + try: + shutil.rmtree(ppu_dir.as_posix()) + self.log_signal.emit('[DEBUG] Temporary folder removed successfully.') + except OSError as e: + self.log_signal.emit(f'[WARNING] Failed to remove temporary folder {ppu_dir}: {e}') \ No newline at end of file diff --git a/src/utils.py b/src/utils.py index f0e3cfb..c8f3b10 100644 --- a/src/utils.py +++ b/src/utils.py @@ -204,4 +204,14 @@ def check_aegnux_tip_marked(): def mark_aegnux_tip_as_shown(): with open(get_aegnux_tip_marked_flag_path(), 'w') as f: - f.write('Press ALT+T to open up a terminal') \ No newline at end of file + f.write('Press ALT+T to open up a terminal') + + +def get_private_plugins_unpack_path(): + aegnux_dir = get_aegnux_installation_dir() + ppu_dir = aegnux_dir.joinpath('private-plugins') + + if not os.path.exists(ppu_dir): + os.makedirs(ppu_dir) + + return ppu_dir \ No newline at end of file diff --git a/translations/en_US.py b/translations/en_US.py index e7646ac..d041fc9 100644 --- a/translations/en_US.py +++ b/translations/en_US.py @@ -32,5 +32,11 @@ STRINGS = { 'cep_action': 'CEP directory', 'offline_note': 'Offline note', 'offline_note_text': 'Note that the .zip archive should contain After Effects from Program Files (including AfterFX.exe).', - 'error': 'Error' + 'error': 'Error', + 'plugininst_action': 'Install private plugins from ZIP', + 'plugin_note': 'Private plugins note', + 'plugin_note_text': 'Those plugins are available at https://t.me/Aegnux', + 'done_title': 'Done!', + 'done_ae': 'AE has been installed.', + 'done_plugins': 'The plugins have been installed.' } \ No newline at end of file diff --git a/translations/ru_RU.py b/translations/ru_RU.py index aed4f35..1a9e507 100644 --- a/translations/ru_RU.py +++ b/translations/ru_RU.py @@ -32,5 +32,10 @@ STRINGS = { 'cep_action': 'Папка с CEP', 'offline_note': 'Внимание, оффлайн', 'offline_note_text': 'Учтите, что .zip-архив должен содержать After Effects из Program Files (включая AfterFX.exe).', - 'error': 'Ошибка' + 'error': 'Ошибка', + 'plugininst_action': 'Установить приватные плагины из ZIP', + 'plugin_note': 'Замечание к приватным плагинам', + 'plugin_note_text': 'Эти плагины доступны здесь: https://t.me/Aegnux', + 'done_ae': 'AE был установлен.', + 'done_plugins': 'Плагины были установлены.' } \ No newline at end of file diff --git a/translations/uk_UA.py b/translations/uk_UA.py index 64239d4..399193a 100644 --- a/translations/uk_UA.py +++ b/translations/uk_UA.py @@ -32,5 +32,10 @@ STRINGS = { "cep_action": "Папка з CEP", 'offline_note': 'Увага, офлайн', 'offline_note_text': 'Зауважте, що .zip-архів повинен містити After Effects з Program Files (включаючи AfterFX.exe).', - 'error': 'Помилка' + 'error': 'Помилка', + 'plugininst_action': 'Встановити приватні плагіни з ZIP', + 'plugin_note': 'Примітка до приватних плагінів', + 'plugin_note_text': 'Ці плагіни доступні тут: https://t.me/Aegnux', + 'done_ae': 'AE було встановлено.', + 'done_plugins': 'Плагіни було встановлено.' }