shinestacker 0.3.3__py3-none-any.whl → 0.3.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of shinestacker might be problematic. Click here for more details.

Files changed (71) hide show
  1. shinestacker/__init__.py +2 -1
  2. shinestacker/_version.py +1 -1
  3. shinestacker/algorithms/__init__.py +3 -2
  4. shinestacker/algorithms/align.py +102 -64
  5. shinestacker/algorithms/balance.py +89 -42
  6. shinestacker/algorithms/base_stack_algo.py +41 -0
  7. shinestacker/algorithms/core_utils.py +6 -6
  8. shinestacker/algorithms/denoise.py +4 -1
  9. shinestacker/algorithms/depth_map.py +28 -39
  10. shinestacker/algorithms/exif.py +43 -38
  11. shinestacker/algorithms/multilayer.py +48 -28
  12. shinestacker/algorithms/noise_detection.py +34 -26
  13. shinestacker/algorithms/pyramid.py +42 -42
  14. shinestacker/algorithms/sharpen.py +1 -0
  15. shinestacker/algorithms/stack.py +42 -42
  16. shinestacker/algorithms/stack_framework.py +118 -66
  17. shinestacker/algorithms/utils.py +12 -11
  18. shinestacker/algorithms/vignetting.py +52 -25
  19. shinestacker/algorithms/white_balance.py +1 -0
  20. shinestacker/app/about_dialog.py +6 -2
  21. shinestacker/app/app_config.py +1 -0
  22. shinestacker/app/gui_utils.py +20 -0
  23. shinestacker/app/help_menu.py +2 -1
  24. shinestacker/app/main.py +9 -18
  25. shinestacker/app/open_frames.py +5 -4
  26. shinestacker/app/project.py +5 -16
  27. shinestacker/app/retouch.py +5 -17
  28. shinestacker/core/colors.py +4 -4
  29. shinestacker/core/core_utils.py +1 -1
  30. shinestacker/core/exceptions.py +2 -1
  31. shinestacker/core/framework.py +46 -33
  32. shinestacker/core/logging.py +9 -10
  33. shinestacker/gui/action_config.py +253 -197
  34. shinestacker/gui/actions_window.py +36 -35
  35. shinestacker/gui/colors.py +1 -0
  36. shinestacker/gui/gui_images.py +7 -3
  37. shinestacker/gui/gui_logging.py +3 -2
  38. shinestacker/gui/gui_run.py +53 -38
  39. shinestacker/gui/main_window.py +69 -25
  40. shinestacker/gui/new_project.py +35 -2
  41. shinestacker/gui/project_converter.py +21 -20
  42. shinestacker/gui/project_editor.py +51 -52
  43. shinestacker/gui/project_model.py +15 -23
  44. shinestacker/retouch/{filter_base.py → base_filter.py} +7 -4
  45. shinestacker/retouch/brush.py +1 -0
  46. shinestacker/retouch/brush_gradient.py +17 -3
  47. shinestacker/retouch/brush_preview.py +14 -10
  48. shinestacker/retouch/brush_tool.py +28 -19
  49. shinestacker/retouch/denoise_filter.py +3 -2
  50. shinestacker/retouch/display_manager.py +11 -5
  51. shinestacker/retouch/exif_data.py +1 -0
  52. shinestacker/retouch/file_loader.py +13 -9
  53. shinestacker/retouch/filter_manager.py +1 -0
  54. shinestacker/retouch/image_editor.py +14 -48
  55. shinestacker/retouch/image_editor_ui.py +10 -5
  56. shinestacker/retouch/image_filters.py +4 -2
  57. shinestacker/retouch/image_viewer.py +33 -31
  58. shinestacker/retouch/io_gui_handler.py +25 -13
  59. shinestacker/retouch/io_manager.py +3 -2
  60. shinestacker/retouch/layer_collection.py +79 -23
  61. shinestacker/retouch/shortcuts_help.py +1 -0
  62. shinestacker/retouch/undo_manager.py +7 -0
  63. shinestacker/retouch/unsharp_mask_filter.py +3 -2
  64. shinestacker/retouch/white_balance_filter.py +11 -6
  65. {shinestacker-0.3.3.dist-info → shinestacker-0.3.5.dist-info}/METADATA +18 -6
  66. shinestacker-0.3.5.dist-info/RECORD +86 -0
  67. shinestacker-0.3.3.dist-info/RECORD +0 -85
  68. {shinestacker-0.3.3.dist-info → shinestacker-0.3.5.dist-info}/WHEEL +0 -0
  69. {shinestacker-0.3.3.dist-info → shinestacker-0.3.5.dist-info}/entry_points.txt +0 -0
  70. {shinestacker-0.3.3.dist-info → shinestacker-0.3.5.dist-info}/licenses/LICENSE +0 -0
  71. {shinestacker-0.3.3.dist-info → shinestacker-0.3.5.dist-info}/top_level.txt +0 -0
@@ -1,13 +1,13 @@
1
+ # pylint: disable=C0114, C0115, C0116, E0611, R0914, R0912, R0915, W0718
1
2
  import os.path
2
3
  import os
4
+ import traceback
3
5
  import json
4
6
  import jsonpickle
5
- import traceback
6
7
  from PySide6.QtWidgets import QMessageBox, QFileDialog, QDialog
7
8
  from .. core.core_utils import get_app_base_path
8
9
  from .. config.constants import constants
9
10
  from .project_model import ActionConfig
10
- from .action_config import ActionConfigDialog
11
11
  from .project_editor import ProjectEditor
12
12
  from .new_project import NewProjectDialog
13
13
  from .project_model import Project
@@ -16,9 +16,6 @@ from .project_model import Project
16
16
  class ActionsWindow(ProjectEditor):
17
17
  def __init__(self):
18
18
  super().__init__()
19
- self._current_file = None
20
- self._current_file_wd = ''
21
- self._modified_project = False
22
19
  self.update_title()
23
20
 
24
21
  def update_title(self):
@@ -31,7 +28,7 @@ class ActionsWindow(ProjectEditor):
31
28
 
32
29
  def mark_as_modified(self):
33
30
  self._modified_project = True
34
- self._project_buffer.append(self.project.clone())
31
+ self.project_buffer.append(self.project.clone())
35
32
  self.update_title()
36
33
 
37
34
  def close_project(self):
@@ -123,19 +120,21 @@ class ActionsWindow(ProjectEditor):
123
120
  if not self._check_unsaved_changes():
124
121
  return
125
122
  if file_path is False:
126
- file_path, _ = QFileDialog.getOpenFileName(self, "Open Project", "", "Project Files (*.fsp);;All Files (*)")
123
+ file_path, _ = QFileDialog.getOpenFileName(
124
+ self, "Open Project", "", "Project Files (*.fsp);;All Files (*)")
127
125
  if file_path:
128
126
  try:
129
127
  self._current_file = file_path
130
- self._current_file_wd = '' if os.path.isabs(file_path) else os.path.dirname(file_path)
128
+ self._current_file_wd = '' if os.path.isabs(file_path) \
129
+ else os.path.dirname(file_path)
131
130
  if not os.path.isabs(self._current_file_wd):
132
131
  self._current_file_wd = os.path.abspath(self._current_file_wd)
133
132
  self._current_file = os.path.basename(self._current_file)
134
- file = open(file_path, 'r')
133
+ with open(file_path, 'r', encoding="utf-8") as file:
134
+ json_obj = json.load(file)
135
135
  pp = file_path.split('/')
136
136
  if len(pp) > 1:
137
137
  os.chdir('/'.join(pp[:-1]))
138
- json_obj = json.load(file)
139
138
  project = Project.from_dict(json_obj['project'])
140
139
  if project is None:
141
140
  raise RuntimeError(f"Project from file {file_path} produced a null project.")
@@ -155,21 +154,23 @@ class ActionsWindow(ProjectEditor):
155
154
  if 'working_path' in job.params.keys():
156
155
  working_path = job.params['working_path']
157
156
  if not os.path.isdir(working_path):
158
- QMessageBox.warning(self, "Working path not found",
159
- f'''The working path specified in the project file for the job:
160
- "{job.params['name']}"
161
- was not found.\n
162
- Please, select a valid working path.''')
157
+ QMessageBox.warning(
158
+ self, "Working path not found",
159
+ f'''The working path specified in the project file for the job:
160
+ "{job.params['name']}"
161
+ was not found.\n
162
+ Please, select a valid working path.''')
163
163
  self.edit_action(job)
164
164
  for action in job.sub_actions:
165
165
  if 'working_path' in job.params.keys():
166
166
  working_path = job.params['working_path']
167
167
  if working_path != '' and not os.path.isdir(working_path):
168
- QMessageBox.warning(self, "Working path not found",
169
- f'''The working path specified in the project file for the job:
170
- "{job.params['name']}"
171
- was not found.\n
172
- Please, select a valid working path.''')
168
+ QMessageBox.warning(
169
+ self, "Working path not found",
170
+ f'''The working path specified in the project file for the job:
171
+ "{job.params['name']}"
172
+ was not found.\n
173
+ Please, select a valid working path.''')
173
174
  self.edit_action(action)
174
175
 
175
176
  def current_file_name(self):
@@ -182,7 +183,8 @@ class ActionsWindow(ProjectEditor):
182
183
  self.save_project_as()
183
184
 
184
185
  def save_project_as(self):
185
- file_path, _ = QFileDialog.getSaveFileName(self, "Save Project As", "", "Project Files (*.fsp);;All Files (*)")
186
+ file_path, _ = QFileDialog.getSaveFileName(
187
+ self, "Save Project As", "", "Project Files (*.fsp);;All Files (*)")
186
188
  if file_path:
187
189
  if not file_path.endswith('.fsp'):
188
190
  file_path += '.fsp'
@@ -191,6 +193,7 @@ class ActionsWindow(ProjectEditor):
191
193
  self._current_file = file_path
192
194
  self._modified_project = False
193
195
  self.update_title()
196
+ os.chdir(os.path.dirname(file_path))
194
197
 
195
198
  def do_save(self, file_path):
196
199
  try:
@@ -198,9 +201,10 @@ class ActionsWindow(ProjectEditor):
198
201
  'project': self.project.to_dict(),
199
202
  'version': 1
200
203
  })
201
- path = f"{self._current_file_wd}/{file_path}" if self._current_file_wd != '' else file_path
202
- f = open(path, 'w')
203
- f.write(json_obj)
204
+ path = f"{self._current_file_wd}/{file_path}" \
205
+ if self._current_file_wd != '' else file_path
206
+ with open(path, 'w', encoding="utf-8") as f:
207
+ f.write(json_obj)
204
208
  self._modified_project = False
205
209
  except Exception as e:
206
210
  QMessageBox.critical(self, "Error", f"Cannot save file:\n{str(e)}")
@@ -215,18 +219,14 @@ class ActionsWindow(ProjectEditor):
215
219
  if reply == QMessageBox.Save:
216
220
  self.save_project()
217
221
  return True
218
- elif reply == QMessageBox.Discard:
219
- return True
220
- else:
221
- return False
222
- else:
223
- return True
222
+ return reply == QMessageBox.Discard
223
+ return True
224
224
 
225
225
  def on_job_edit(self, item):
226
226
  index = self.job_list.row(item)
227
227
  if 0 <= index < len(self.project.jobs):
228
228
  job = self.project.jobs[index]
229
- dialog = ActionConfigDialog(job, self._current_file_wd, self)
229
+ dialog = self.action_config_dialog(job)
230
230
  if dialog.exec() == QDialog.Accepted:
231
231
  current_row = self.job_list.currentRow()
232
232
  if current_row >= 0:
@@ -257,8 +257,9 @@ class ActionsWindow(ProjectEditor):
257
257
  break
258
258
  if current_action:
259
259
  if not is_sub_action:
260
- self.set_enabled_sub_actions_gui(current_action.type_name == constants.ACTION_COMBO)
261
- dialog = ActionConfigDialog(current_action, self._current_file_wd, self)
260
+ self.set_enabled_sub_actions_gui(
261
+ current_action.type_name == constants.ACTION_COMBO)
262
+ dialog = self.action_config_dialog(current_action)
262
263
  if dialog.exec() == QDialog.Accepted:
263
264
  self.on_job_selected(job_index)
264
265
  self.refresh_ui()
@@ -273,14 +274,14 @@ class ActionsWindow(ProjectEditor):
273
274
  if self.job_list.hasFocus():
274
275
  current_action = job
275
276
  elif self.action_list.hasFocus():
276
- job_row, action_row, pos = self.get_current_action()
277
+ job_row, _action_row, pos = self.get_current_action()
277
278
  if pos.actions is not None:
278
279
  current_action = pos.action if not pos.is_sub_action else pos.sub_action
279
280
  if current_action is not None:
280
281
  self.edit_action(current_action)
281
282
 
282
283
  def edit_action(self, action):
283
- dialog = ActionConfigDialog(action, self._current_file_wd, self)
284
+ dialog = self.action_config_dialog(action)
284
285
  if dialog.exec() == QDialog.Accepted:
285
286
  self.on_job_selected(self.job_list.currentRow())
286
287
  self.mark_as_modified()
@@ -1,3 +1,4 @@
1
+ # pylint: disable=C0114, C0115, C0116, R0903, E0611
1
2
  from PySide6.QtGui import QColor
2
3
 
3
4
 
@@ -1,3 +1,4 @@
1
+ # pylint: disable=C0114, C0115, C0116, E0611, W0718, E1101, C0103
1
2
  import webbrowser
2
3
  import subprocess
3
4
  import os
@@ -75,7 +76,8 @@ class GuiImageView(QWidget):
75
76
  self.setLayout(self.layout)
76
77
  pixmap = QPixmap(file_path)
77
78
  if pixmap:
78
- scaled_pixmap = pixmap.scaledToWidth(gui_constants.GUI_IMG_WIDTH, Qt.SmoothTransformation)
79
+ scaled_pixmap = pixmap.scaledToWidth(
80
+ gui_constants.GUI_IMG_WIDTH, Qt.SmoothTransformation)
79
81
  self.image_label.setPixmap(scaled_pixmap)
80
82
  else:
81
83
  raise RuntimeError(f"Can't load file: {file_path}.")
@@ -112,7 +114,8 @@ class GuiOpenApp(QWidget):
112
114
  self.setLayout(self.layout)
113
115
  pixmap = QPixmap(file_path)
114
116
  if pixmap:
115
- scaled_pixmap = pixmap.scaledToWidth(gui_constants.GUI_IMG_WIDTH, Qt.SmoothTransformation)
117
+ scaled_pixmap = pixmap.scaledToWidth(
118
+ gui_constants.GUI_IMG_WIDTH, Qt.SmoothTransformation)
116
119
  self.image_label.setPixmap(scaled_pixmap)
117
120
  else:
118
121
  raise RuntimeError(f"Can't load file: {file_path}.")
@@ -134,7 +137,8 @@ class GuiOpenApp(QWidget):
134
137
  try:
135
138
  os.system(f"{self.app} -f {self.file_path} &")
136
139
  except Exception as e:
137
- raise RuntimeError(f"Can't open file {self.file_path} with app: {self.app}.\n{str(e)}")
140
+ raise RuntimeError(
141
+ f"Can't open file {self.file_path} with app: {self.app}.\n{str(e)}") from e
138
142
  else:
139
143
  app = None
140
144
  stacked_widget = self.parent().window().findChild(QStackedWidget)
@@ -1,3 +1,4 @@
1
+ # pylint: disable=C0114, C0115, C0116, E0611, W0212, R0903
1
2
  import logging
2
3
  from PySide6.QtWidgets import QWidget, QTextEdit, QMessageBox, QStatusBar
3
4
  from PySide6.QtGui import QTextCursor, QTextOption, QFont
@@ -87,8 +88,8 @@ class SimpleHtmlHandler(QObject, logging.Handler):
87
88
  try:
88
89
  msg = self.format(record)
89
90
  self.html_signal.emit(msg)
90
- except Exception as e:
91
- logging.error(f"Logging error: {e}")
91
+ except RuntimeError as e:
92
+ logging.error(msg=f"Logging error: {e}")
92
93
 
93
94
 
94
95
  class GuiLogger(QWidget):
@@ -1,3 +1,4 @@
1
+ # pylint: disable=C0114, C0115, C0116, E0611, R0903, R0915, R0914, R0917, R0913, R0902
1
2
  import time
2
3
  from PySide6.QtWidgets import (QWidget, QPushButton, QVBoxLayout, QHBoxLayout, QProgressBar,
3
4
  QMessageBox, QScrollArea, QSizePolicy, QFrame, QLabel, QComboBox)
@@ -50,6 +51,7 @@ class TimerProgressBar(QProgressBar):
50
51
  super().setValue(0)
51
52
  self.set_running_style()
52
53
  self._start_time = -1
54
+ self._current_time = -1
53
55
 
54
56
  def set_style(self, bar_color=None):
55
57
  if bar_color is None:
@@ -77,11 +79,11 @@ class TimerProgressBar(QProgressBar):
77
79
  m = (ss % 3600) // 60
78
80
  s = (ss % 3600) % 60
79
81
  x = secs - ss
80
- t_str = "{:02d}".format(s) + "{:.1f}s".format(x).lstrip('0')
82
+ t_str = f"{s:02d}" + f"{x:.1f}s".lstrip('0')
81
83
  if m > 0:
82
- t_str = "{:02d}:{}".format(m, t_str)
84
+ t_str = f"{m:02d}:{t_str}"
83
85
  if h > 0:
84
- t_str = "{:02d}:{}".format(h, t_str)
86
+ t_str = f"{h:02d}:{t_str}"
85
87
  if m > 0 or h > 0:
86
88
  t_str = t_str.lstrip('0')
87
89
  elif 0 < s < 10:
@@ -114,9 +116,11 @@ class TimerProgressBar(QProgressBar):
114
116
  self.check_time(self.maximum())
115
117
  self.setValue(self.maximum())
116
118
 
119
+ # pylint: disable=C0103
117
120
  def setValue(self, val):
118
121
  self.check_time(val)
119
122
  super().setValue(val)
123
+ # pylint: enable=C0103
120
124
 
121
125
  def set_running_style(self):
122
126
  self.set_style(action_running_color)
@@ -192,7 +196,9 @@ class RunWindow(QTextEditLogger):
192
196
  self.retouch_widget.setStyleSheet(BLUE_COMBO_STYLE)
193
197
  self.retouch_widget.addItems(options)
194
198
  self.retouch_widget.setEnabled(False)
195
- self.retouch_widget.currentIndexChanged.connect(lambda: self.retouch(self.retouch_paths[self.retouch_widget.currentIndex() - 1]))
199
+ self.retouch_widget.currentIndexChanged.connect(
200
+ lambda: self.retouch(
201
+ self.retouch_paths[self.retouch_widget.currentIndex() - 1]))
196
202
  self.status_bar.addPermanentWidget(self.retouch_widget)
197
203
 
198
204
  self.stop_button = QPushButton("Stop")
@@ -238,40 +244,40 @@ class RunWindow(QTextEditLogger):
238
244
  self.close_window_callback(self.id_str())
239
245
 
240
246
  @Slot(int, str)
241
- def handle_before_action(self, id, name):
242
- if 0 <= id < len(self.color_widgets[self.row_widget_id]):
243
- self.color_widgets[self.row_widget_id][id].set_color(*action_running_color.tuple())
247
+ def handle_before_action(self, run_id, _name):
248
+ if 0 <= run_id < len(self.color_widgets[self.row_widget_id]):
249
+ self.color_widgets[self.row_widget_id][run_id].set_color(*action_running_color.tuple())
244
250
  self.progress_bar.start(1)
245
- if id == -1:
251
+ if run_id == -1:
246
252
  self.progress_bar.set_running_style()
247
253
 
248
254
  @Slot(int, str)
249
- def handle_after_action(self, id, name):
250
- if 0 <= id < len(self.color_widgets[self.row_widget_id]):
251
- self.color_widgets[self.row_widget_id][id].set_color(*action_done_color.tuple())
255
+ def handle_after_action(self, run_id, _name):
256
+ if 0 <= run_id < len(self.color_widgets[self.row_widget_id]):
257
+ self.color_widgets[self.row_widget_id][run_id].set_color(*action_done_color.tuple())
252
258
  self.progress_bar.stop()
253
- if id == -1:
259
+ if run_id == -1:
254
260
  self.row_widget_id += 1
255
261
  self.progress_bar.set_done_style()
256
262
 
257
263
  @Slot(int, str, str)
258
- def handle_step_counts(self, id, name, steps):
264
+ def handle_step_counts(self, _run_id, _name, steps):
259
265
  self.progress_bar.start(steps)
260
266
 
261
267
  @Slot(int, str)
262
- def handle_begin_steps(self, id, name):
268
+ def handle_begin_steps(self, _run_id, _name):
263
269
  self.progress_bar.start(1)
264
270
 
265
271
  @Slot(int, str)
266
- def handle_end_steps(self, id, name):
272
+ def handle_end_steps(self, _run_id, _name):
267
273
  self.progress_bar.stop()
268
274
 
269
275
  @Slot(int, str, str)
270
- def handle_after_step(self, id, name, step):
276
+ def handle_after_step(self, _run_id, _name, step):
271
277
  self.progress_bar.setValue(step)
272
278
 
273
279
  @Slot(int, str, str)
274
- def handle_save_plot(self, id, name, path):
280
+ def handle_save_plot(self, _run_id, name, path):
275
281
  label = QLabel(name, self)
276
282
  label.setStyleSheet("QLabel {margin-top: 5px; font-weight: bold;}")
277
283
  self.image_layout.addWidget(label)
@@ -290,10 +296,12 @@ class RunWindow(QTextEditLogger):
290
296
  self.image_area_widget.setFixedWidth(needed_width)
291
297
  self.right_area.updateGeometry()
292
298
  self.image_area_widget.updateGeometry()
293
- QTimer.singleShot(0, lambda: self.right_area.verticalScrollBar().setValue(self.right_area.verticalScrollBar().maximum()))
299
+ QTimer.singleShot(
300
+ 0, lambda: self.right_area.verticalScrollBar().setValue(
301
+ self.right_area.verticalScrollBar().maximum()))
294
302
 
295
303
  @Slot(int, str, str, str)
296
- def handle_open_app(self, id, name, app, path):
304
+ def handle_open_app(self, _run_id, name, app, path):
297
305
  label = QLabel(name, self)
298
306
  label.setStyleSheet("QLabel {margin-top: 5px; font-weight: bold;}")
299
307
  self.image_layout.addWidget(label)
@@ -306,7 +314,9 @@ class RunWindow(QTextEditLogger):
306
314
  self.image_area_widget.setFixedWidth(needed_width)
307
315
  self.right_area.updateGeometry()
308
316
  self.image_area_widget.updateGeometry()
309
- QTimer.singleShot(0, lambda: self.right_area.verticalScrollBar().setValue(self.right_area.verticalScrollBar().maximum()))
317
+ QTimer.singleShot(
318
+ 0, lambda: self.right_area.verticalScrollBar().setValue(
319
+ self.right_area.verticalScrollBar().maximum()))
310
320
 
311
321
 
312
322
  class RunWorker(LogWorker):
@@ -336,40 +346,41 @@ class RunWorker(LogWorker):
336
346
  }
337
347
  self.tag = ""
338
348
 
339
- def before_action(self, id, name):
340
- self.before_action_signal.emit(id, name)
349
+ def before_action(self, run_id, name):
350
+ self.before_action_signal.emit(run_id, name)
341
351
 
342
- def after_action(self, id, name):
343
- self.after_action_signal.emit(id, name)
352
+ def after_action(self, run_id, name):
353
+ self.after_action_signal.emit(run_id, name)
344
354
 
345
- def step_counts(self, id, name, steps):
346
- self.step_counts_signal.emit(id, name, steps)
355
+ def step_counts(self, run_id, name, steps):
356
+ self.step_counts_signal.emit(run_id, name, steps)
347
357
 
348
- def begin_steps(self, id, name):
349
- self.begin_steps_signal.emit(id, name)
358
+ def begin_steps(self, run_id, name):
359
+ self.begin_steps_signal.emit(run_id, name)
350
360
 
351
- def end_steps(self, id, name):
352
- self.end_steps_signal.emit(id, name)
361
+ def end_steps(self, run_id, name):
362
+ self.end_steps_signal.emit(run_id, name)
353
363
 
354
- def after_step(self, id, name, step):
355
- self.after_step_signal.emit(id, name, step)
364
+ def after_step(self, run_id, name, step):
365
+ self.after_step_signal.emit(run_id, name, step)
356
366
 
357
- def save_plot(self, id, name, path):
358
- self.save_plot_signal.emit(id, name, path)
367
+ def save_plot(self, run_id, name, path):
368
+ self.save_plot_signal.emit(run_id, name, path)
359
369
 
360
- def open_app(self, id, name, app, path):
361
- self.open_app_signal.emit(id, name, app, path)
370
+ def open_app(self, run_id, name, app, path):
371
+ self.open_app_signal.emit(run_id, name, app, path)
362
372
 
363
- def check_running(self, id, name):
373
+ def check_running(self, _run_id, _name):
364
374
  return self.status == constants.STATUS_RUNNING
365
375
 
366
376
  def run(self):
377
+ # pylint: disable=line-too-long
367
378
  self.status_signal.emit(f"{self.tag} running...", constants.RUN_ONGOING, "", 0)
368
379
  self.html_signal.emit(f'''
369
380
  <div style="margin: 2px 0; font-family: {constants.LOG_FONTS_STR};">
370
381
  <span style="color: #{ColorPalette.DARK_BLUE.hex()}; font-style: italic; font-weigt: bold;">{self.tag} begins</span>
371
382
  </div>
372
- ''')
383
+ ''') # noqa
373
384
  status, error_message = self.do_run()
374
385
  if status == constants.RUN_FAILED:
375
386
  message = f"{self.tag} failed"
@@ -380,11 +391,15 @@ class RunWorker(LogWorker):
380
391
  elif status == constants.RUN_STOPPED:
381
392
  message = f"{self.tag} stopped"
382
393
  color = "#" + ColorPalette.DARK_RED.hex()
394
+ else:
395
+ message = ''
396
+ color = "#000000"
383
397
  self.html_signal.emit(f'''
384
398
  <div style="margin: 2px 0; font-family: {constants.LOG_FONTS_STR};">
385
399
  <span style="color: {color}; font-style: italic; font-weight: bold;">{message}</span>
386
400
  </div>
387
401
  ''')
402
+ # pylint: enable=line-too-long
388
403
  self.end_signal.emit(status, self.id_str, message)
389
404
  self.status_signal.emit(message, status, error_message, 0)
390
405
 
@@ -1,3 +1,4 @@
1
+ # pylint: disable=C0114, C0115, C0116, E0611, R0902, R0915, R0904, R0914, R0912, E1101, W0201
1
2
  import os
2
3
  import subprocess
3
4
  from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QTabWidget, QLabel, QMessageBox,
@@ -90,6 +91,7 @@ class TabWidgetWithPlaceholder(QWidget):
90
91
  else:
91
92
  self.stacked_widget.setCurrentIndex(0)
92
93
 
94
+ # pylint: disable=C0103
93
95
  def addTab(self, widget, label):
94
96
  result = self.tab_widget.addTab(widget, label)
95
97
  self.update_placeholder_visibility()
@@ -117,6 +119,7 @@ class TabWidgetWithPlaceholder(QWidget):
117
119
 
118
120
  def indexOf(self, widget):
119
121
  return self.tab_widget.indexOf(widget)
122
+ # pylint: enable=C0103
120
123
 
121
124
 
122
125
  class MainWindow(ActionsWindow, LogManager):
@@ -289,17 +292,44 @@ class MainWindow(ActionsWindow, LogManager):
289
292
  self.run_all_jobs_action.triggered.connect(self.run_all_jobs)
290
293
  menu.addAction(self.run_all_jobs_action)
291
294
 
295
+ def add_action_combined_actions(self):
296
+ self.add_action(constants.ACTION_COMBO)
297
+
298
+ def add_action_noise_detection(self):
299
+ self.add_action(constants.ACTION_NOISEDETECTION)
300
+
301
+ def add_action_focus_stack(self):
302
+ self.add_action(constants.ACTION_FOCUSSTACK)
303
+
304
+ def add_action_focus_stack_bunch(self):
305
+ self.add_action(constants.ACTION_FOCUSSTACKBUNCH)
306
+
307
+ def add_action_multilayer(self):
308
+ self.add_action(constants.ACTION_MULTILAYER)
309
+
310
+ def add_sub_action_make_noise(self):
311
+ self.add_sub_action(constants.ACTION_MASKNOISE)
312
+
313
+ def add_sub_action_vignetting(self):
314
+ self.add_sub_action(constants.ACTION_VIGNETTING)
315
+
316
+ def add_sub_action_align_frames(self):
317
+ self.add_sub_action(constants.ACTION_ALIGNFRAMES)
318
+
319
+ def add_sub_action_balance_frames(self):
320
+ self.add_sub_action(constants.ACTION_BALANCEFRAMES)
321
+
292
322
  def add_actions_menu(self, menubar):
293
323
  menu = menubar.addMenu("&Actions")
294
324
  add_action_menu = QMenu("Add Action", self)
295
325
  for action in constants.ACTION_TYPES:
296
326
  entry_action = QAction(action, self)
297
327
  entry_action.triggered.connect({
298
- constants.ACTION_COMBO: self.add_action_CombinedActions,
299
- constants.ACTION_NOISEDETECTION: self.add_action_NoiseDetection,
300
- constants.ACTION_FOCUSSTACK: self.add_action_FocusStack,
301
- constants.ACTION_FOCUSSTACKBUNCH: self.add_action_FocusStackBunch,
302
- constants.ACTION_MULTILAYER: self.add_action_MultiLayer
328
+ constants.ACTION_COMBO: self.add_action_combined_actions,
329
+ constants.ACTION_NOISEDETECTION: self.add_action_noise_detection,
330
+ constants.ACTION_FOCUSSTACK: self.add_action_focus_stack,
331
+ constants.ACTION_FOCUSSTACKBUNCH: self.add_action_focus_stack_bunch,
332
+ constants.ACTION_MULTILAYER: self.add_action_multilayer
303
333
  }[action])
304
334
  add_action_menu.addAction(entry_action)
305
335
  menu.addMenu(add_action_menu)
@@ -308,10 +338,10 @@ class MainWindow(ActionsWindow, LogManager):
308
338
  for action in constants.SUB_ACTION_TYPES:
309
339
  entry_action = QAction(action, self)
310
340
  entry_action.triggered.connect({
311
- constants.ACTION_MASKNOISE: self.add_sub_action_MakeNoise,
312
- constants.ACTION_VIGNETTING: self.add_sub_action_Vignetting,
313
- constants.ACTION_ALIGNFRAMES: self.add_sub_action_AlignFrames,
314
- constants.ACTION_BALANCEFRAMES: self.add_sub_action_BalanceFrames
341
+ constants.ACTION_MASKNOISE: self.add_sub_action_make_noise,
342
+ constants.ACTION_VIGNETTING: self.add_sub_action_vignetting,
343
+ constants.ACTION_ALIGNFRAMES: self.add_sub_action_align_frames,
344
+ constants.ACTION_BALANCEFRAMES: self.add_sub_action_balance_frames
315
345
  }[action])
316
346
  entry_action.setEnabled(False)
317
347
  self.sub_action_menu_entries.append(entry_action)
@@ -330,7 +360,8 @@ class MainWindow(ActionsWindow, LogManager):
330
360
  self.action_selector.setEnabled(False)
331
361
  toolbar.addWidget(self.action_selector)
332
362
  self.add_action_entry_action = QAction("Add Action", self)
333
- self.add_action_entry_action.setIcon(QIcon(os.path.join(self.script_dir, "img/plus-round-line-icon.png")))
363
+ self.add_action_entry_action.setIcon(
364
+ QIcon(os.path.join(self.script_dir, "img/plus-round-line-icon.png")))
334
365
  self.add_action_entry_action.setToolTip("Add action")
335
366
  self.add_action_entry_action.triggered.connect(self.add_action)
336
367
  self.add_action_entry_action.setEnabled(False)
@@ -340,7 +371,8 @@ class MainWindow(ActionsWindow, LogManager):
340
371
  self.sub_action_selector.setEnabled(False)
341
372
  toolbar.addWidget(self.sub_action_selector)
342
373
  self.add_sub_action_entry_action = QAction("Add Sub Action", self)
343
- self.add_sub_action_entry_action.setIcon(QIcon(os.path.join(self.script_dir, "img/plus-round-line-icon.png")))
374
+ self.add_sub_action_entry_action.setIcon(
375
+ QIcon(os.path.join(self.script_dir, "img/plus-round-line-icon.png")))
344
376
  self.add_sub_action_entry_action.setToolTip("Add sub action")
345
377
  self.add_sub_action_entry_action.triggered.connect(self.add_sub_action)
346
378
  self.add_sub_action_entry_action.setEnabled(False)
@@ -351,6 +383,7 @@ class MainWindow(ActionsWindow, LogManager):
351
383
  toolbar.addAction(self.run_job_action)
352
384
  toolbar.addAction(self.run_all_jobs_action)
353
385
 
386
+ # pylint: disable=C0103
354
387
  def contextMenuEvent(self, event):
355
388
  item = self.job_list.itemAt(self.job_list.viewport().mapFrom(self, event.pos()))
356
389
  current_action = None
@@ -362,7 +395,7 @@ class MainWindow(ActionsWindow, LogManager):
362
395
  if item:
363
396
  index = self.action_list.row(item)
364
397
  self.action_list.setCurrentRow(index)
365
- job_row, action_row, pos = self.get_action_at(index)
398
+ _job_row, _action_row, pos = self.get_action_at(index)
366
399
  current_action = pos.action if not pos.is_sub_action else pos.sub_action
367
400
  if current_action:
368
401
  menu = QMenu(self)
@@ -375,7 +408,8 @@ class MainWindow(ActionsWindow, LogManager):
375
408
  menu.addAction(edit_config_action)
376
409
  menu.addSeparator()
377
410
  self.current_action_working_path, name = get_action_working_path(current_action)
378
- if self.current_action_working_path != '' and os.path.exists(self.current_action_working_path):
411
+ if self.current_action_working_path != '' and \
412
+ os.path.exists(self.current_action_working_path):
379
413
  action_name = "Browse Working Path" + (f" > {name}" if name != '' else '')
380
414
  self.browse_working_path_action = QAction(action_name)
381
415
  self.browse_working_path_action.triggered.connect(self.browse_working_path_path)
@@ -383,7 +417,8 @@ class MainWindow(ActionsWindow, LogManager):
383
417
  ip, name = get_action_input_path(current_action)
384
418
  if ip != '':
385
419
  ips = ip.split(constants.PATH_SEPARATOR)
386
- self.current_action_input_path = constants.PATH_SEPARATOR.join([f"{self.current_action_working_path}/{ip}" for ip in ips])
420
+ self.current_action_input_path = constants.PATH_SEPARATOR.join(
421
+ [f"{self.current_action_working_path}/{ip}" for ip in ips])
387
422
  p_exists = False
388
423
  for p in self.current_action_input_path.split(constants.PATH_SEPARATOR):
389
424
  if os.path.exists(p):
@@ -410,20 +445,25 @@ class MainWindow(ActionsWindow, LogManager):
410
445
  if len(retouch_path) > 0:
411
446
  menu.addSeparator()
412
447
  self.job_retouch_path_action = QAction("Retouch path")
413
- self.job_retouch_path_action.triggered.connect(lambda job: self.run_retouch_path(current_action, retouch_path))
448
+ self.job_retouch_path_action.triggered.connect(
449
+ lambda job: self.run_retouch_path(current_action, retouch_path))
414
450
  menu.addAction(self.job_retouch_path_action)
415
451
  menu.exec(event.globalPos())
452
+ # pylint: enable=C0103
416
453
 
417
454
  def get_icon(self, icon):
418
455
  return QIcon(os.path.join(self.script_dir, f"img/{icon}.png"))
419
456
 
420
457
  def get_retouch_path(self, job):
421
458
  frames_path = [get_action_output_path(action)[0]
422
- for action in job.sub_actions if action.type_name == constants.ACTION_COMBO]
459
+ for action in job.sub_actions
460
+ if action.type_name == constants.ACTION_COMBO]
423
461
  bunches_path = [get_action_output_path(action)[0]
424
- for action in job.sub_actions if action.type_name == constants.ACTION_FOCUSSTACKBUNCH]
462
+ for action in job.sub_actions
463
+ if action.type_name == constants.ACTION_FOCUSSTACKBUNCH]
425
464
  stack_path = [get_action_output_path(action)[0]
426
- for action in job.sub_actions if action.type_name == constants.ACTION_FOCUSSTACK]
465
+ for action in job.sub_actions
466
+ if action.type_name == constants.ACTION_FOCUSSTACK]
427
467
  if len(bunches_path) > 0:
428
468
  stack_path += [bunches_path[0]]
429
469
  elif len(frames_path) > 0:
@@ -434,7 +474,7 @@ class MainWindow(ActionsWindow, LogManager):
434
474
  stack_path = [f"{wp}/{s}" for s in stack_path]
435
475
  return stack_path
436
476
 
437
- def run_retouch_path(self, job, retouch_path):
477
+ def run_retouch_path(self, _job, retouch_path):
438
478
  self.retouch_callback(retouch_path)
439
479
 
440
480
  def browse_path(self, path):
@@ -444,9 +484,9 @@ class MainWindow(ActionsWindow, LogManager):
444
484
  if running_under_windows():
445
485
  os.startfile(os.path.normpath(p))
446
486
  elif running_under_macos():
447
- subprocess.run(['open', p])
487
+ subprocess.run(['open', p], check=True)
448
488
  else:
449
- subprocess.run(['xdg-open', p])
489
+ subprocess.run(['xdg-open', p], check=True)
450
490
 
451
491
  def browse_working_path_path(self):
452
492
  self.browse_path(self.current_action_working_path)
@@ -501,13 +541,14 @@ class MainWindow(ActionsWindow, LogManager):
501
541
  w = self.tab_widget.widget(i)
502
542
  if w.id_str() == id_str:
503
543
  return i, w
544
+ return None, None
504
545
 
505
546
  def get_tab_at_position(self, id_str):
506
- i, w = self.get_tab_and_position(id_str)
547
+ _i, w = self.get_tab_and_position(id_str)
507
548
  return w
508
549
 
509
550
  def get_tab_position(self, id_str):
510
- i, w = self.get_tab_and_position(id_str)
551
+ i, _w = self.get_tab_and_position(id_str)
511
552
  return i
512
553
 
513
554
  def do_handle_end_message(self, status, id_str, message):
@@ -581,11 +622,14 @@ class MainWindow(ActionsWindow, LogManager):
581
622
  self.start_thread(worker)
582
623
  self._workers.append(worker)
583
624
  else:
584
- QMessageBox.warning(self, "Can't run Job", "Job " + job.params["name"] + " is disabled.")
625
+ QMessageBox.warning(
626
+ self, "Can't run Job",
627
+ "Job " + job.params["name"] + " is disabled.")
585
628
  return
586
629
 
587
630
  def run_all_jobs(self):
588
- labels = [[(self.action_text(a), a.enabled() and job.enabled()) for a in job.sub_actions] for job in self.project.jobs]
631
+ labels = [[(self.action_text(a), a.enabled() and
632
+ job.enabled()) for a in job.sub_actions] for job in self.project.jobs]
589
633
  project_name = ".".join(self.current_file_name().split(".")[:-1])
590
634
  if project_name == '':
591
635
  project_name = '[new]'