shinestacker 1.2.0__py3-none-any.whl → 1.3.0__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.
- shinestacker/_version.py +1 -1
- shinestacker/algorithms/align.py +148 -115
- shinestacker/algorithms/align_auto.py +64 -0
- shinestacker/algorithms/align_parallel.py +296 -0
- shinestacker/algorithms/balance.py +14 -13
- shinestacker/algorithms/base_stack_algo.py +11 -2
- shinestacker/algorithms/multilayer.py +14 -15
- shinestacker/algorithms/noise_detection.py +13 -14
- shinestacker/algorithms/pyramid.py +4 -4
- shinestacker/algorithms/pyramid_auto.py +16 -10
- shinestacker/algorithms/pyramid_tiles.py +19 -11
- shinestacker/algorithms/stack.py +30 -26
- shinestacker/algorithms/stack_framework.py +200 -178
- shinestacker/algorithms/vignetting.py +16 -13
- shinestacker/app/main.py +7 -3
- shinestacker/config/constants.py +63 -26
- shinestacker/config/gui_constants.py +1 -1
- shinestacker/core/core_utils.py +4 -0
- shinestacker/core/framework.py +114 -33
- shinestacker/gui/action_config.py +57 -5
- shinestacker/gui/action_config_dialog.py +156 -17
- shinestacker/gui/base_form_dialog.py +2 -2
- shinestacker/gui/folder_file_selection.py +101 -0
- shinestacker/gui/gui_images.py +10 -10
- shinestacker/gui/gui_run.py +13 -11
- shinestacker/gui/main_window.py +10 -5
- shinestacker/gui/menu_manager.py +4 -0
- shinestacker/gui/new_project.py +171 -74
- shinestacker/gui/project_controller.py +13 -9
- shinestacker/gui/project_converter.py +4 -2
- shinestacker/gui/project_editor.py +72 -53
- shinestacker/gui/select_path_widget.py +1 -1
- shinestacker/gui/sys_mon.py +96 -0
- shinestacker/gui/tab_widget.py +3 -3
- shinestacker/gui/time_progress_bar.py +4 -3
- shinestacker/retouch/exif_data.py +1 -1
- shinestacker/retouch/image_editor_ui.py +2 -0
- {shinestacker-1.2.0.dist-info → shinestacker-1.3.0.dist-info}/METADATA +6 -6
- {shinestacker-1.2.0.dist-info → shinestacker-1.3.0.dist-info}/RECORD +43 -39
- {shinestacker-1.2.0.dist-info → shinestacker-1.3.0.dist-info}/WHEEL +0 -0
- {shinestacker-1.2.0.dist-info → shinestacker-1.3.0.dist-info}/entry_points.txt +0 -0
- {shinestacker-1.2.0.dist-info → shinestacker-1.3.0.dist-info}/licenses/LICENSE +0 -0
- {shinestacker-1.2.0.dist-info → shinestacker-1.3.0.dist-info}/top_level.txt +0 -0
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
# pylint: disable=C0114, C0115, C0116, R0904, R1702, R0917, R0913, R0902, E0611, E1131, E1121
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, R0903, R0904, R1702, R0917, R0913, R0902, E0611, E1131, E1121
|
|
2
2
|
import os
|
|
3
3
|
from dataclasses import dataclass
|
|
4
|
-
from PySide6.QtWidgets import
|
|
5
|
-
|
|
6
|
-
from PySide6.QtCore import Qt, QObject, Signal
|
|
4
|
+
from PySide6.QtWidgets import QListWidget, QMessageBox, QDialog, QListWidgetItem, QLabel
|
|
5
|
+
from PySide6.QtCore import Qt, QObject, Signal, QEvent
|
|
7
6
|
from .. config.constants import constants
|
|
8
7
|
from .colors import ColorPalette
|
|
9
8
|
from .action_config_dialog import ActionConfigDialog
|
|
@@ -75,28 +74,49 @@ def new_row_after_clone(job, action_row, is_sub_action, cloned):
|
|
|
75
74
|
|
|
76
75
|
|
|
77
76
|
class ProjectUndoManager(QObject):
|
|
78
|
-
set_enabled_undo_action_requested = Signal(bool)
|
|
77
|
+
set_enabled_undo_action_requested = Signal(bool, str)
|
|
79
78
|
|
|
80
79
|
def __init__(self, parent=None):
|
|
81
80
|
super().__init__(parent)
|
|
82
81
|
self._undo_buffer = []
|
|
83
82
|
|
|
84
|
-
def add(self, item):
|
|
85
|
-
self._undo_buffer.append(item)
|
|
86
|
-
self.set_enabled_undo_action_requested.emit(True)
|
|
83
|
+
def add(self, item, description):
|
|
84
|
+
self._undo_buffer.append((item, description))
|
|
85
|
+
self.set_enabled_undo_action_requested.emit(True, description)
|
|
87
86
|
|
|
88
87
|
def pop(self):
|
|
89
88
|
last = self._undo_buffer.pop()
|
|
90
89
|
if len(self._undo_buffer) == 0:
|
|
91
|
-
self.set_enabled_undo_action_requested.emit(False)
|
|
92
|
-
|
|
90
|
+
self.set_enabled_undo_action_requested.emit(False, '')
|
|
91
|
+
else:
|
|
92
|
+
self.set_enabled_undo_action_requested.emit(True, self._undo_buffer[-1][1])
|
|
93
|
+
return last[0]
|
|
93
94
|
|
|
94
95
|
def filled(self):
|
|
95
96
|
return len(self._undo_buffer) != 0
|
|
96
97
|
|
|
97
98
|
def reset(self):
|
|
98
99
|
self._undo_buffer = []
|
|
99
|
-
self.set_enabled_undo_action_requested.emit(False)
|
|
100
|
+
self.set_enabled_undo_action_requested.emit(False, '')
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class HandCursorListWidget(QListWidget):
|
|
104
|
+
def __init__(self, parent=None):
|
|
105
|
+
super().__init__(parent)
|
|
106
|
+
self.setMouseTracking(True)
|
|
107
|
+
self.viewport().setMouseTracking(True)
|
|
108
|
+
|
|
109
|
+
def event(self, event):
|
|
110
|
+
if event.type() == QEvent.HoverMove:
|
|
111
|
+
pos = event.position().toPoint()
|
|
112
|
+
item = self.itemAt(pos)
|
|
113
|
+
if item:
|
|
114
|
+
self.viewport().setCursor(Qt.PointingHandCursor)
|
|
115
|
+
else:
|
|
116
|
+
self.viewport().setCursor(Qt.ArrowCursor)
|
|
117
|
+
elif event.type() == QEvent.Leave:
|
|
118
|
+
self.viewport().setCursor(Qt.ArrowCursor)
|
|
119
|
+
return super().event(event)
|
|
100
120
|
|
|
101
121
|
|
|
102
122
|
class ProjectEditor(QObject):
|
|
@@ -115,15 +135,15 @@ class ProjectEditor(QObject):
|
|
|
115
135
|
self._project = None
|
|
116
136
|
self._copy_buffer = None
|
|
117
137
|
self._current_file_path = ''
|
|
118
|
-
self._job_list =
|
|
119
|
-
self._action_list =
|
|
138
|
+
self._job_list = HandCursorListWidget()
|
|
139
|
+
self._action_list = HandCursorListWidget()
|
|
120
140
|
self.dialog = None
|
|
121
141
|
|
|
122
142
|
def reset_undo(self):
|
|
123
143
|
self.undo_manager.reset()
|
|
124
144
|
|
|
125
|
-
def add_undo(self, item):
|
|
126
|
-
self.undo_manager.add(item)
|
|
145
|
+
def add_undo(self, item, description=''):
|
|
146
|
+
self.undo_manager.add(item, description)
|
|
127
147
|
|
|
128
148
|
def pop_undo(self):
|
|
129
149
|
return self.undo_manager.pop()
|
|
@@ -131,10 +151,13 @@ class ProjectEditor(QObject):
|
|
|
131
151
|
def filled_undo(self):
|
|
132
152
|
return self.undo_manager.filled()
|
|
133
153
|
|
|
134
|
-
def
|
|
154
|
+
def set_modified(self, modified):
|
|
155
|
+
self._modified = modified
|
|
156
|
+
|
|
157
|
+
def mark_as_modified(self, modified=True, description=''):
|
|
135
158
|
self._modified = modified
|
|
136
159
|
if modified:
|
|
137
|
-
self.add_undo(self._project.clone())
|
|
160
|
+
self.add_undo(self._project.clone(), description)
|
|
138
161
|
self.modified_signal.emit(modified)
|
|
139
162
|
|
|
140
163
|
def modified(self):
|
|
@@ -241,37 +264,29 @@ class ProjectEditor(QObject):
|
|
|
241
264
|
txt = f"{job.params.get('name', '(job)')}"
|
|
242
265
|
if html:
|
|
243
266
|
txt = f"<b>{txt}</b>"
|
|
244
|
-
in_path = get_action_input_path(job)
|
|
245
|
-
|
|
267
|
+
in_path = get_action_input_path(job)[0]
|
|
268
|
+
if os.path.isabs(in_path):
|
|
269
|
+
in_path = ".../" + os.path.basename(in_path)
|
|
270
|
+
ico = constants.ACTION_ICONS[constants.ACTION_JOB]
|
|
271
|
+
return txt + (f" [{ico}Job: 📁 {in_path} → 📂 ...]" if long_name else "")
|
|
246
272
|
|
|
247
273
|
def action_text(self, action, is_sub_action=False, indent=True, long_name=False, html=False):
|
|
248
|
-
|
|
249
|
-
constants.ACTION_COMBO: '⚡',
|
|
250
|
-
constants.ACTION_NOISEDETECTION: '🌫',
|
|
251
|
-
constants.ACTION_FOCUSSTACK: '🎯',
|
|
252
|
-
constants.ACTION_FOCUSSTACKBUNCH: '🖇',
|
|
253
|
-
constants.ACTION_MULTILAYER: '🎞️',
|
|
254
|
-
constants.ACTION_MASKNOISE: '🎭',
|
|
255
|
-
constants.ACTION_VIGNETTING: '⭕️',
|
|
256
|
-
constants.ACTION_ALIGNFRAMES: '📐',
|
|
257
|
-
constants.ACTION_BALANCEFRAMES: '🌈'
|
|
258
|
-
}
|
|
259
|
-
ico = icon_map.get(action.type_name, '')
|
|
274
|
+
ico = constants.ACTION_ICONS.get(action.type_name, '')
|
|
260
275
|
if is_sub_action and indent:
|
|
261
276
|
txt = self.INDENT_SPACE
|
|
262
|
-
if ico == '':
|
|
263
|
-
ico = '🟣'
|
|
264
277
|
else:
|
|
265
278
|
txt = ''
|
|
266
|
-
if ico == '':
|
|
267
|
-
ico = '🔵'
|
|
268
279
|
if action.params.get('name', '') != '':
|
|
269
280
|
txt += f"{action.params['name']}"
|
|
270
281
|
if html:
|
|
271
282
|
txt = f"<b>{txt}</b>"
|
|
272
|
-
in_path, out_path = get_action_input_path(action), get_action_output_path(action)
|
|
273
|
-
|
|
274
|
-
|
|
283
|
+
in_path, out_path = get_action_input_path(action)[0], get_action_output_path(action)[0]
|
|
284
|
+
if os.path.isabs(in_path):
|
|
285
|
+
in_path = ".../" + os.path.basename(in_path)
|
|
286
|
+
if os.path.isabs(out_path):
|
|
287
|
+
out_path = ".../" + os.path.basename(out_path)
|
|
288
|
+
return f"{txt} [{ico}{action.type_name}" + \
|
|
289
|
+
(f": 📁 <i>{in_path}</i> → 📂 <i>{out_path}</i>]"
|
|
275
290
|
if long_name and not is_sub_action else "]")
|
|
276
291
|
|
|
277
292
|
def get_job_at(self, index):
|
|
@@ -320,7 +335,7 @@ class ProjectEditor(QObject):
|
|
|
320
335
|
new_index = job_index + delta
|
|
321
336
|
if 0 <= new_index < self.num_project_jobs():
|
|
322
337
|
jobs = self.project_jobs()
|
|
323
|
-
self.mark_as_modified()
|
|
338
|
+
self.mark_as_modified(True, "Shift Job")
|
|
324
339
|
jobs.insert(new_index, jobs.pop(job_index))
|
|
325
340
|
self.refresh_ui_signal.emit(new_index, -1)
|
|
326
341
|
|
|
@@ -330,12 +345,12 @@ class ProjectEditor(QObject):
|
|
|
330
345
|
if not pos.is_sub_action:
|
|
331
346
|
new_index = pos.action_index + delta
|
|
332
347
|
if 0 <= new_index < len(pos.actions):
|
|
333
|
-
self.mark_as_modified()
|
|
348
|
+
self.mark_as_modified(True, "Shift Action")
|
|
334
349
|
pos.actions.insert(new_index, pos.actions.pop(pos.action_index))
|
|
335
350
|
else:
|
|
336
351
|
new_index = pos.sub_action_index + delta
|
|
337
352
|
if 0 <= new_index < len(pos.sub_actions):
|
|
338
|
-
self.mark_as_modified()
|
|
353
|
+
self.mark_as_modified(True, "Shift Sub-action")
|
|
339
354
|
pos.sub_actions.insert(new_index, pos.sub_actions.pop(pos.sub_action_index))
|
|
340
355
|
new_row = new_row_after_insert(action_row, pos, delta)
|
|
341
356
|
self.refresh_ui_signal.emit(job_row, new_row)
|
|
@@ -357,7 +372,7 @@ class ProjectEditor(QObject):
|
|
|
357
372
|
if 0 <= job_index < self.num_project_jobs():
|
|
358
373
|
job_clone = self.project_job(job_index).clone(self.CLONE_POSTFIX)
|
|
359
374
|
new_job_index = job_index + 1
|
|
360
|
-
self.mark_as_modified()
|
|
375
|
+
self.mark_as_modified(True, "Duplicate Job")
|
|
361
376
|
self.project_jobs().insert(new_job_index, job_clone)
|
|
362
377
|
self.set_current_job(new_job_index)
|
|
363
378
|
self.set_current_action(new_job_index)
|
|
@@ -367,7 +382,7 @@ class ProjectEditor(QObject):
|
|
|
367
382
|
job_row, action_row, pos = self.get_current_action()
|
|
368
383
|
if not pos.actions:
|
|
369
384
|
return
|
|
370
|
-
self.mark_as_modified()
|
|
385
|
+
self.mark_as_modified(True, "Duplicate Action")
|
|
371
386
|
job = self.project_job(job_row)
|
|
372
387
|
if pos.is_sub_action:
|
|
373
388
|
cloned = pos.sub_action.clone(self.CLONE_POSTFIX)
|
|
@@ -398,7 +413,7 @@ class ProjectEditor(QObject):
|
|
|
398
413
|
reply = None
|
|
399
414
|
if not confirm or reply == QMessageBox.Yes:
|
|
400
415
|
self.take_job(current_index)
|
|
401
|
-
self.mark_as_modified()
|
|
416
|
+
self.mark_as_modified(True, "Delete Job")
|
|
402
417
|
current_job = self.project_jobs().pop(current_index)
|
|
403
418
|
self.clear_action_list()
|
|
404
419
|
self.refresh_ui_signal.emit(-1, -1)
|
|
@@ -420,10 +435,11 @@ class ProjectEditor(QObject):
|
|
|
420
435
|
else:
|
|
421
436
|
reply = None
|
|
422
437
|
if not confirm or reply == QMessageBox.Yes:
|
|
423
|
-
self.mark_as_modified()
|
|
424
438
|
if pos.is_sub_action:
|
|
439
|
+
self.mark_as_modified(True, "Delete Action")
|
|
425
440
|
pos.action.pop_sub_action(pos.sub_action_index)
|
|
426
441
|
else:
|
|
442
|
+
self.mark_as_modified(True, "Delete Sub-action")
|
|
427
443
|
self.project_job(job_row).pop_sub_action(pos.action_index)
|
|
428
444
|
new_row = new_row_after_delete(action_row, pos)
|
|
429
445
|
self.refresh_ui_signal.emit(job_row, new_row)
|
|
@@ -446,7 +462,7 @@ class ProjectEditor(QObject):
|
|
|
446
462
|
job_action = ActionConfig("Job")
|
|
447
463
|
self.dialog = self.action_config_dialog(job_action)
|
|
448
464
|
if self.dialog.exec() == QDialog.Accepted:
|
|
449
|
-
self.mark_as_modified()
|
|
465
|
+
self.mark_as_modified(True, "Add Job")
|
|
450
466
|
self.project_jobs().append(job_action)
|
|
451
467
|
self.add_list_item(self.job_list(), job_action, False)
|
|
452
468
|
self.set_current_job(self.job_list_count() - 1)
|
|
@@ -467,7 +483,7 @@ class ProjectEditor(QObject):
|
|
|
467
483
|
action.parent = self.get_current_job()
|
|
468
484
|
self.dialog = self.action_config_dialog(action)
|
|
469
485
|
if self.dialog.exec() == QDialog.Accepted:
|
|
470
|
-
self.mark_as_modified()
|
|
486
|
+
self.mark_as_modified("Add Action")
|
|
471
487
|
self.project_job(current_index).add_sub_action(action)
|
|
472
488
|
self.add_list_item(self.action_list(), action, False)
|
|
473
489
|
self.enable_delete_action_signal.emit(False)
|
|
@@ -507,7 +523,7 @@ class ProjectEditor(QObject):
|
|
|
507
523
|
sub_action = ActionConfig(type_name)
|
|
508
524
|
self.dialog = self.action_config_dialog(sub_action)
|
|
509
525
|
if self.dialog.exec() == QDialog.Accepted:
|
|
510
|
-
self.mark_as_modified()
|
|
526
|
+
self.mark_as_modified("Add Sub-action")
|
|
511
527
|
action.add_sub_action(sub_action)
|
|
512
528
|
self.on_job_selected(current_job_index)
|
|
513
529
|
self.set_current_action(current_action_index)
|
|
@@ -535,7 +551,7 @@ class ProjectEditor(QObject):
|
|
|
535
551
|
job_index = self.current_job_index()
|
|
536
552
|
if 0 <= job_index < self.num_project_jobs():
|
|
537
553
|
new_job_index = job_index
|
|
538
|
-
self.mark_as_modified()
|
|
554
|
+
self.mark_as_modified(True, "Paste Job")
|
|
539
555
|
self.project_jobs().insert(new_job_index, self.copy_buffer())
|
|
540
556
|
self.set_current_job(new_job_index)
|
|
541
557
|
self.set_current_action(new_job_index)
|
|
@@ -547,13 +563,13 @@ class ProjectEditor(QObject):
|
|
|
547
563
|
if not pos.is_sub_action:
|
|
548
564
|
if self.copy_buffer().type_name not in constants.ACTION_TYPES:
|
|
549
565
|
return
|
|
550
|
-
self.mark_as_modified()
|
|
566
|
+
self.mark_as_modified(True, "Paste Action")
|
|
551
567
|
pos.actions.insert(pos.action_index, self.copy_buffer())
|
|
552
568
|
else:
|
|
553
569
|
if pos.action.type_name != constants.ACTION_COMBO or \
|
|
554
570
|
self.copy_buffer().type_name not in constants.SUB_ACTION_TYPES:
|
|
555
571
|
return
|
|
556
|
-
self.mark_as_modified()
|
|
572
|
+
self.mark_as_modified(True, "Paste Sub-action")
|
|
557
573
|
pos.sub_actions.insert(pos.sub_action_index, self.copy_buffer())
|
|
558
574
|
new_row = new_row_after_paste(action_row, pos)
|
|
559
575
|
self.refresh_ui_signal.emit(job_row, new_row)
|
|
@@ -597,7 +613,10 @@ class ProjectEditor(QObject):
|
|
|
597
613
|
action_row = -1
|
|
598
614
|
if current_action:
|
|
599
615
|
if current_action.enabled() != enabled:
|
|
600
|
-
|
|
616
|
+
if enabled:
|
|
617
|
+
self.mark_as_modified(True, "Enable")
|
|
618
|
+
else:
|
|
619
|
+
self.mark_as_modified(True, "Disable")
|
|
601
620
|
current_action.set_enabled(enabled)
|
|
602
621
|
self.refresh_ui_signal.emit(job_row, action_row)
|
|
603
622
|
|
|
@@ -608,7 +627,7 @@ class ProjectEditor(QObject):
|
|
|
608
627
|
self.set_enabled(False)
|
|
609
628
|
|
|
610
629
|
def set_enabled_all(self, enable=True):
|
|
611
|
-
self.mark_as_modified()
|
|
630
|
+
self.mark_as_modified(True, "Enable All")
|
|
612
631
|
job_row = self.current_job_index()
|
|
613
632
|
action_row = self.current_action_index()
|
|
614
633
|
for j in self.project_jobs():
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, E0611
|
|
2
|
+
import psutil
|
|
3
|
+
from PySide6.QtWidgets import QWidget, QHBoxLayout, QLabel, QProgressBar, QSizePolicy
|
|
4
|
+
from PySide6.QtCore import QTimer, Qt
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class StatusBarSystemMonitor(QWidget):
|
|
8
|
+
def __init__(self, parent=None):
|
|
9
|
+
super().__init__(parent)
|
|
10
|
+
self.setup_ui()
|
|
11
|
+
self.setup_timer()
|
|
12
|
+
self.setFixedHeight(28)
|
|
13
|
+
|
|
14
|
+
def setup_ui(self):
|
|
15
|
+
bar_width = 100
|
|
16
|
+
bar_height = 20
|
|
17
|
+
layout = QHBoxLayout()
|
|
18
|
+
layout.setSpacing(10)
|
|
19
|
+
layout.setContentsMargins(0, 2, 0, 0)
|
|
20
|
+
layout.setAlignment(Qt.AlignLeft)
|
|
21
|
+
layout.setAlignment(Qt.AlignCenter)
|
|
22
|
+
cpu_widget = QWidget()
|
|
23
|
+
cpu_widget.setFixedSize(bar_width, bar_height)
|
|
24
|
+
self.cpu_bar = QProgressBar(cpu_widget)
|
|
25
|
+
self.cpu_bar.setRange(0, 100)
|
|
26
|
+
self.cpu_bar.setTextVisible(False)
|
|
27
|
+
self.cpu_bar.setGeometry(0, 0, bar_width, bar_height)
|
|
28
|
+
self.cpu_bar.setStyleSheet("""
|
|
29
|
+
QProgressBar {
|
|
30
|
+
border: 1px solid #cccccc;
|
|
31
|
+
border-radius: 5px;
|
|
32
|
+
background: #f0f0f0;
|
|
33
|
+
}
|
|
34
|
+
QProgressBar::chunk {
|
|
35
|
+
background-color: #3498db;
|
|
36
|
+
border-radius: 5px;
|
|
37
|
+
}
|
|
38
|
+
""")
|
|
39
|
+
self.cpu_label = QLabel("CPU: --%", cpu_widget)
|
|
40
|
+
self.cpu_label.setAlignment(Qt.AlignCenter)
|
|
41
|
+
self.cpu_label.setGeometry(0, 0, bar_width, bar_height)
|
|
42
|
+
self.cpu_label.setStyleSheet("""
|
|
43
|
+
QLabel {
|
|
44
|
+
color: #2c3e50;
|
|
45
|
+
font-weight: bold;
|
|
46
|
+
background: transparent;
|
|
47
|
+
font-size: 12px;
|
|
48
|
+
}
|
|
49
|
+
""")
|
|
50
|
+
mem_widget = QWidget()
|
|
51
|
+
mem_widget.setFixedSize(bar_width, bar_height)
|
|
52
|
+
self.mem_bar = QProgressBar(mem_widget)
|
|
53
|
+
self.mem_bar.setRange(0, 100)
|
|
54
|
+
self.mem_bar.setTextVisible(False)
|
|
55
|
+
self.mem_bar.setGeometry(0, 0, bar_width, bar_height)
|
|
56
|
+
self.mem_bar.setStyleSheet("""
|
|
57
|
+
QProgressBar {
|
|
58
|
+
border: 1px solid #cccccc;
|
|
59
|
+
border-radius: 5px;
|
|
60
|
+
background: #f0f0f0;
|
|
61
|
+
}
|
|
62
|
+
QProgressBar::chunk {
|
|
63
|
+
background-color: #2ecc71;
|
|
64
|
+
border-radius: 5px;
|
|
65
|
+
}
|
|
66
|
+
""")
|
|
67
|
+
self.mem_label = QLabel("MEM: --%", mem_widget)
|
|
68
|
+
self.mem_label.setAlignment(Qt.AlignCenter)
|
|
69
|
+
self.mem_label.setGeometry(0, 0, bar_width, bar_height)
|
|
70
|
+
self.mem_label.setStyleSheet("""
|
|
71
|
+
QLabel {
|
|
72
|
+
color: #2c3e50;
|
|
73
|
+
font-weight: bold;
|
|
74
|
+
background: transparent;
|
|
75
|
+
font-size: 12px;
|
|
76
|
+
}
|
|
77
|
+
""")
|
|
78
|
+
layout.addWidget(cpu_widget)
|
|
79
|
+
layout.addWidget(mem_widget)
|
|
80
|
+
layout.addStretch()
|
|
81
|
+
self.setLayout(layout)
|
|
82
|
+
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
|
83
|
+
|
|
84
|
+
def setup_timer(self):
|
|
85
|
+
self.timer = QTimer()
|
|
86
|
+
self.timer.timeout.connect(self.update_stats)
|
|
87
|
+
self.timer.start(1000)
|
|
88
|
+
|
|
89
|
+
def update_stats(self):
|
|
90
|
+
cpu_percent = psutil.cpu_percent()
|
|
91
|
+
memory = psutil.virtual_memory()
|
|
92
|
+
mem_percent = memory.percent
|
|
93
|
+
self.cpu_bar.setValue(int(cpu_percent))
|
|
94
|
+
self.cpu_label.setText(f"CPU: {cpu_percent:.1f}%")
|
|
95
|
+
self.mem_bar.setValue(int(mem_percent))
|
|
96
|
+
self.mem_label.setText(f"MEM: {mem_percent:.1f}%")
|
shinestacker/gui/tab_widget.py
CHANGED
|
@@ -12,10 +12,10 @@ class TabWidgetWithPlaceholder(QWidget):
|
|
|
12
12
|
|
|
13
13
|
def __init__(self, parent=None):
|
|
14
14
|
super().__init__(parent)
|
|
15
|
-
self.
|
|
16
|
-
self.
|
|
15
|
+
self.main_layout = QVBoxLayout(self)
|
|
16
|
+
self.main_layout.setContentsMargins(0, 0, 0, 0)
|
|
17
17
|
self.stacked_widget = QStackedWidget()
|
|
18
|
-
self.
|
|
18
|
+
self.main_layout.addWidget(self.stacked_widget)
|
|
19
19
|
self.tab_widget = QTabWidget()
|
|
20
20
|
self.stacked_widget.addWidget(self.tab_widget)
|
|
21
21
|
self.placeholder = QLabel()
|
|
@@ -39,13 +39,14 @@ class TimerProgressBar(QProgressBar):
|
|
|
39
39
|
""")
|
|
40
40
|
|
|
41
41
|
def time_str(self, secs):
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
xsecs = int(secs * 10)
|
|
43
|
+
x = xsecs % 10
|
|
44
|
+
ss = xsecs // 10
|
|
44
45
|
s = ss % 60
|
|
45
46
|
mm = ss // 60
|
|
46
47
|
m = mm % 60
|
|
47
48
|
h = mm // 60
|
|
48
|
-
t_str = f"{s:02d}
|
|
49
|
+
t_str = f"{s:02d}.{x:1d}s"
|
|
49
50
|
if m > 0:
|
|
50
51
|
t_str = f"{m:02d}:{t_str}"
|
|
51
52
|
if h > 0:
|
|
@@ -9,7 +9,7 @@ from .. gui.base_form_dialog import BaseFormDialog
|
|
|
9
9
|
|
|
10
10
|
class ExifData(BaseFormDialog):
|
|
11
11
|
def __init__(self, exif, parent=None):
|
|
12
|
-
super().__init__("EXIF data", parent)
|
|
12
|
+
super().__init__("EXIF data", parent=parent)
|
|
13
13
|
self.exif = exif
|
|
14
14
|
self.create_form()
|
|
15
15
|
button_container = QWidget()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: shinestacker
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: ShineStacker
|
|
5
5
|
Author-email: Luca Lista <luka.lista@gmail.com>
|
|
6
6
|
License-Expression: LGPL-3.0
|
|
@@ -20,6 +20,7 @@ Requires-Dist: numpy
|
|
|
20
20
|
Requires-Dist: opencv_python
|
|
21
21
|
Requires-Dist: pillow
|
|
22
22
|
Requires-Dist: psdtags
|
|
23
|
+
Requires-Dist: psutil
|
|
23
24
|
Requires-Dist: PySide6
|
|
24
25
|
Requires-Dist: scipy
|
|
25
26
|
Requires-Dist: tifffile
|
|
@@ -69,6 +70,10 @@ The GUI has two main working areas:
|
|
|
69
70
|
|
|
70
71
|
<img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/gui-retouch.png' width="600" referrerpolicy="no-referrer">
|
|
71
72
|
|
|
73
|
+
# Resources
|
|
74
|
+
|
|
75
|
+
🌍 [Website on WordPress](https://shinestacker.wordpress.com) • 📖 [Main documentation](https://shinestacker.readthedocs.io) • 📝 [Changelog](https://github.com/lucalista/shinestacker/blob/main/CHANGELOG.md)
|
|
76
|
+
|
|
72
77
|
# Note for macOS users
|
|
73
78
|
|
|
74
79
|
**The following note is only relevant if you download the application as compressed archive from the [release page](https://github.com/lucalista/shinestacker/releases).**
|
|
@@ -88,11 +93,6 @@ xattr -cr ~/Downloads/shinestacker/shinestacker.app
|
|
|
88
93
|
|
|
89
94
|
macOS adds a quarantine flag to all files downloaded from the internet. The above command removes that flag while preserving all other application functionality.
|
|
90
95
|
|
|
91
|
-
# Resources
|
|
92
|
-
|
|
93
|
-
🌍 [Website on WordPress](https://shinestacker.wordpress.com) • 📖 [Main documentation](https://shinestacker.readthedocs.io) • 📝 [Changelog](https://github.com/lucalista/shinestacker/blob/main/CHANGELOG.md)
|
|
94
|
-
|
|
95
|
-
|
|
96
96
|
# Credits
|
|
97
97
|
|
|
98
98
|
The first version of the core focus stack algorithm was initially inspired by the [Laplacian pyramids method](https://github.com/sjawhar/focus-stacking) implementation by Sami Jawhar that was used under permission of the author. The implementation in the latest releases was rewritten from the original code.
|
|
@@ -1,60 +1,64 @@
|
|
|
1
1
|
shinestacker/__init__.py,sha256=uq2fjAw2z_6TpH3mOcWFZ98GoEPRsNhTAK8N0MMm_e8,448
|
|
2
|
-
shinestacker/_version.py,sha256=
|
|
2
|
+
shinestacker/_version.py,sha256=tP8c5-8yPCRUk61qFQy1AQFkbfy99N-tga3OUiJT1MA,21
|
|
3
3
|
shinestacker/algorithms/__init__.py,sha256=1FwVJ3w9GGbFFkjYJRUedTvcdE4j0ieSgaH9RC9iCY4,877
|
|
4
|
-
shinestacker/algorithms/align.py,sha256=
|
|
5
|
-
shinestacker/algorithms/
|
|
6
|
-
shinestacker/algorithms/
|
|
4
|
+
shinestacker/algorithms/align.py,sha256=tVgLzn0vV9sIrFj4fZezBYHxhK2o_xrjCm8k55OoAYQ,23514
|
|
5
|
+
shinestacker/algorithms/align_auto.py,sha256=wIv9iSOhzxdQiPo0GG1Dv9WSLgFGjCJM7I2uqMl-cGc,3093
|
|
6
|
+
shinestacker/algorithms/align_parallel.py,sha256=3tGhrSr9IPz8fv0FUW-AAq9-e3BEJadwS9DYTZm-Sgo,14812
|
|
7
|
+
shinestacker/algorithms/balance.py,sha256=KJ8eXWYyqRVQa7_iZWQhZZ9BfO4wNve5nhZxunK7B5k,23583
|
|
8
|
+
shinestacker/algorithms/base_stack_algo.py,sha256=jiqckBGQnP536OPfTW0Kzfawcpk1L-bByhCjoyQyBGw,2841
|
|
7
9
|
shinestacker/algorithms/denoise.py,sha256=GL3Z4_6MHxSa7Wo4ZzQECZS87tHBFqO0sIVF_jPuYQU,426
|
|
8
10
|
shinestacker/algorithms/depth_map.py,sha256=m0_Qm8FLDeSWyQEMNx29PzXp_VFGar7gY3jWxq_10t8,5713
|
|
9
11
|
shinestacker/algorithms/exif.py,sha256=SM4ZDDe8hCJ3xY6053FNndOiwzEStzdp0WrXurlcHVc,9429
|
|
10
|
-
shinestacker/algorithms/multilayer.py,sha256=
|
|
11
|
-
shinestacker/algorithms/noise_detection.py,sha256=
|
|
12
|
-
shinestacker/algorithms/pyramid.py,sha256=
|
|
13
|
-
shinestacker/algorithms/pyramid_auto.py,sha256=
|
|
14
|
-
shinestacker/algorithms/pyramid_tiles.py,sha256=
|
|
12
|
+
shinestacker/algorithms/multilayer.py,sha256=WlB4L5oY9qra3w7Qahg-tqO6S_s3pMB_LmGR8PPR_7w,9904
|
|
13
|
+
shinestacker/algorithms/noise_detection.py,sha256=KSdMDER5GNOCTD6DIAbjJvRDFvrVorul3xr5maXtCh8,9298
|
|
14
|
+
shinestacker/algorithms/pyramid.py,sha256=drGLeGRQ2_QMmbpOFaFI7kSOSuvraOcsWJNTXbBUT6k,8838
|
|
15
|
+
shinestacker/algorithms/pyramid_auto.py,sha256=TxIkOrzS2i9Dz994L_YYeL4CNRRjTWkygGL8KDjDuWo,6602
|
|
16
|
+
shinestacker/algorithms/pyramid_tiles.py,sha256=mjmvJil0olQJSUWDMR5Hkuu1PbI_TomTUZi8lsC10cU,12356
|
|
15
17
|
shinestacker/algorithms/sharpen.py,sha256=h7PMJBYxucg194Usp_6pvItPUMFYbT-ebAc_-7XBFUw,949
|
|
16
|
-
shinestacker/algorithms/stack.py,sha256=
|
|
17
|
-
shinestacker/algorithms/stack_framework.py,sha256=
|
|
18
|
+
shinestacker/algorithms/stack.py,sha256=V9YX0CbNWrgAo7_uti64rmmuwU6RsRcjDoBpsES4aSE,5137
|
|
19
|
+
shinestacker/algorithms/stack_framework.py,sha256=L5fXv07CuO4gHBBnoHS6U9ajWB-nDYCKBroutAQzd2U,13890
|
|
18
20
|
shinestacker/algorithms/utils.py,sha256=jImR2XF73gsLRZMic0kv8cyCuO2Zq21tX4kUhaTcfzI,11301
|
|
19
|
-
shinestacker/algorithms/vignetting.py,sha256=
|
|
21
|
+
shinestacker/algorithms/vignetting.py,sha256=MwhsTqmNMc6GdQl_Bbuyo8IUQPn1OoeGcWj1L6Jjybc,10274
|
|
20
22
|
shinestacker/algorithms/white_balance.py,sha256=PMKsBtxOSn5aRr_Gkx1StHS4eN6kBN2EhNnhg4UG24g,501
|
|
21
23
|
shinestacker/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
24
|
shinestacker/app/about_dialog.py,sha256=pkH7nnxUP8yc0D3vRGd1jRb5cwi1nDVbQRk_OC9yLk8,4144
|
|
23
25
|
shinestacker/app/gui_utils.py,sha256=08TrCj2gFGsNsF6hG7ySO2y7wcQakM5PzERkeplqNFs,2344
|
|
24
26
|
shinestacker/app/help_menu.py,sha256=g8lKG_xZmXtNQaC3SIRzyROKVWva_PLEgZsQWh6zUcQ,499
|
|
25
|
-
shinestacker/app/main.py,sha256=
|
|
27
|
+
shinestacker/app/main.py,sha256=VPZx_qcmFc1YmWicFjKcfQrH1br0VR5ZGuE64nsD3n0,10414
|
|
26
28
|
shinestacker/app/open_frames.py,sha256=bsu32iJSYJQLe_tQQbvAU5DuMDVX6MRuNdE7B5lojZc,1488
|
|
27
29
|
shinestacker/app/project.py,sha256=W0u715LZne_PNJvg9msSy27ybIjgDXiEAQdJ7_6BjYI,2774
|
|
28
30
|
shinestacker/app/retouch.py,sha256=ZQ-nRKnHo6xurcP34RNqaAWkmuGBjJ5jE05hTQ_ycis,2482
|
|
29
31
|
shinestacker/config/__init__.py,sha256=aXxi-LmAvXd0daIFrVnTHE5OCaYeK1uf1BKMr7oaXQs,197
|
|
30
32
|
shinestacker/config/config.py,sha256=eBko2D3ADhLTIm9X6hB_a_WsIjwgfE-qmBVkhP1XSvc,1636
|
|
31
|
-
shinestacker/config/constants.py,sha256=
|
|
32
|
-
shinestacker/config/gui_constants.py,sha256=
|
|
33
|
+
shinestacker/config/constants.py,sha256=EEdr7pZg4JpbIjUWaP7kJQfTuBB85FN739myDNAfn8A,8301
|
|
34
|
+
shinestacker/config/gui_constants.py,sha256=i2dHeGRnY-Wc3dVjpIEKNNxEQYhfn18IEUcvl96r89I,2575
|
|
33
35
|
shinestacker/core/__init__.py,sha256=IUEIx6SQ3DygDEHN3_E6uKpHjHtUa4a_U_1dLd_8yEU,484
|
|
34
36
|
shinestacker/core/colors.py,sha256=kr_tJA1iRsdck2JaYDb2lS-codZ4Ty9gdu3kHfiWvuM,1340
|
|
35
|
-
shinestacker/core/core_utils.py,sha256=
|
|
37
|
+
shinestacker/core/core_utils.py,sha256=BlHbvQmDQXSONNkDx0zq_xiDTsfrS0N7r1DBTUPm8CE,1523
|
|
36
38
|
shinestacker/core/exceptions.py,sha256=2-noG-ORAGdvDhL8jBQFs0xxZS4fI6UIkMqrWekgk2c,1618
|
|
37
|
-
shinestacker/core/framework.py,sha256=
|
|
39
|
+
shinestacker/core/framework.py,sha256=QaTfnzEUHwzlbyFG7KzeyteckTSWHWEEJE4d5Tc8H18,11015
|
|
38
40
|
shinestacker/core/logging.py,sha256=9SuSSy9Usbh7zqmLYMqkmy-VBkOJW000lwqAR0XQs30,3067
|
|
39
41
|
shinestacker/gui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
40
|
-
shinestacker/gui/action_config.py,sha256
|
|
41
|
-
shinestacker/gui/action_config_dialog.py,sha256=
|
|
42
|
-
shinestacker/gui/base_form_dialog.py,sha256=
|
|
42
|
+
shinestacker/gui/action_config.py,sha256=BhKssL0xHPdNkE5hDkBy7Uw5rZOLZ8PU8hz-Nt_CdwA,19038
|
|
43
|
+
shinestacker/gui/action_config_dialog.py,sha256=SbYL1fxyI-N5Pt_vgkNtJw1s2f-3gd9LlfLnxw4qtDI,41894
|
|
44
|
+
shinestacker/gui/base_form_dialog.py,sha256=KAUQNtmJazttmOIe4E4pFifbtvcByTAhtCmcIYeA4UE,766
|
|
43
45
|
shinestacker/gui/colors.py,sha256=m0pQQ-uvtIN1xmb_-N06BvC7pZYZZnq59ZSEJwutHuk,1432
|
|
44
46
|
shinestacker/gui/flow_layout.py,sha256=3yBU_z7VtvHKpx1H97CHVd81eq9pe1Dcja2EZBGGKcI,3791
|
|
45
|
-
shinestacker/gui/
|
|
47
|
+
shinestacker/gui/folder_file_selection.py,sha256=V6Pdu6EtQpBzCmh_yTibJ75by6Nf-4YHgAAWWTYl-VA,3966
|
|
48
|
+
shinestacker/gui/gui_images.py,sha256=k39DpdsxcmYoRdHNNZj6OpFAas0GOHS4JSG542wfheg,5728
|
|
46
49
|
shinestacker/gui/gui_logging.py,sha256=kiZcrC2AFYCWgPZo0O5SKw-E5cFrezwf4anS3HjPuNw,8168
|
|
47
|
-
shinestacker/gui/gui_run.py,sha256=
|
|
48
|
-
shinestacker/gui/main_window.py,sha256=
|
|
49
|
-
shinestacker/gui/menu_manager.py,sha256=
|
|
50
|
-
shinestacker/gui/new_project.py,sha256=
|
|
51
|
-
shinestacker/gui/project_controller.py,sha256=
|
|
52
|
-
shinestacker/gui/project_converter.py,sha256=
|
|
53
|
-
shinestacker/gui/project_editor.py,sha256=
|
|
50
|
+
shinestacker/gui/gui_run.py,sha256=MQPE7muBPw3KTrGDsg-MBcC6nNFYuvvojfXfq84kR8o,15759
|
|
51
|
+
shinestacker/gui/main_window.py,sha256=GUrGhZerYXCMwMeB2K51oSHaJAgtMTPj6AfOgkkmwoc,24521
|
|
52
|
+
shinestacker/gui/menu_manager.py,sha256=ZsND0e-vM263-6unwKUtYAtLbb4YgvIQabh5lCiT2ow,10179
|
|
53
|
+
shinestacker/gui/new_project.py,sha256=Gwg57Ze3D5AQeTCbuI2oNLMqrDnpFRIfNOfWAbO3nUk,16145
|
|
54
|
+
shinestacker/gui/project_controller.py,sha256=hvSNGrQM-yDHH3e132oouxBtgRv7mUG7lrigUl21BsA,16043
|
|
55
|
+
shinestacker/gui/project_converter.py,sha256=Gmna0HwbvACcXiX74TaQYumif8ZV8sZ2APLTMM-L1mU,7436
|
|
56
|
+
shinestacker/gui/project_editor.py,sha256=j7bBH4u5g6CO8Mv541ceDrs7GKsWZ-eVKpCPTKF6zVY,25368
|
|
54
57
|
shinestacker/gui/project_model.py,sha256=eRUmH3QmRzDtPtZoxgT6amKzN8_5XzwjHgEJeL-_JOE,4263
|
|
55
|
-
shinestacker/gui/select_path_widget.py,sha256=
|
|
56
|
-
shinestacker/gui/
|
|
57
|
-
shinestacker/gui/
|
|
58
|
+
shinestacker/gui/select_path_widget.py,sha256=HSwgSr702w5Et4c-6nkRXnIpm1KFqKJetAF5xQNa5zI,1017
|
|
59
|
+
shinestacker/gui/sys_mon.py,sha256=h_Mg99bceXPSX_oK8t_IuRJoSsVo3uthcTnwe8tOYhM,3378
|
|
60
|
+
shinestacker/gui/tab_widget.py,sha256=VgRmuktWXCgbXbV7c1Tho0--W5_EmmzXPfzRZgwhGfg,2965
|
|
61
|
+
shinestacker/gui/time_progress_bar.py,sha256=7_sllrQgayjRh__mwJ0-4lghXIakuRAx8wWucJ6olYs,3028
|
|
58
62
|
shinestacker/gui/ico/focus_stack_bkg.png,sha256=Q86TgqvKEi_IzKI8m6aZB2a3T40UkDtexf2PdeBM9XE,163151
|
|
59
63
|
shinestacker/gui/ico/shinestacker.icns,sha256=3IshIOv0uFexYsAEPkE9xiyuw8mB5X5gffekOUhFlt0,45278
|
|
60
64
|
shinestacker/gui/ico/shinestacker.ico,sha256=8IMRk-toObWUz8iDXA-zHBWQ8Ps3vXN5u5ZEyw7sP3c,109613
|
|
@@ -72,11 +76,11 @@ shinestacker/retouch/brush_preview.py,sha256=QKD3pL7n7YJbIibinUFYKv7lkyq_AWLpt6o
|
|
|
72
76
|
shinestacker/retouch/brush_tool.py,sha256=nxnEuvTioPNw1WeWsT20X1zl-LNZ8i-1ExOcihikEjk,8618
|
|
73
77
|
shinestacker/retouch/denoise_filter.py,sha256=TDUHzhRKlKvCa3D5SCYCZKTpjcl81kGwmONsgSDtO1k,440
|
|
74
78
|
shinestacker/retouch/display_manager.py,sha256=XPbOBmoYc_jNA791WkWkOSaFHb0ztCZechl2p2KSlwQ,9597
|
|
75
|
-
shinestacker/retouch/exif_data.py,sha256=
|
|
79
|
+
shinestacker/retouch/exif_data.py,sha256=LF-fRXW-reMq-xJ_QRE5j8DC2LVGKIlC6MR3QbC1cdg,1896
|
|
76
80
|
shinestacker/retouch/file_loader.py,sha256=z02-A8_uDZxayI1NFTxT2GVUvEBWStchX9hlN1o5-0U,4784
|
|
77
81
|
shinestacker/retouch/filter_manager.py,sha256=SdYIZkZBUvuB6wDG0moGWav5sfEvIcB9ioUJR5wJFts,388
|
|
78
82
|
shinestacker/retouch/icon_container.py,sha256=6gw1HO1bC2FrdB4dc_iH81DQuLjzuvRGksZ2hKLT9yA,585
|
|
79
|
-
shinestacker/retouch/image_editor_ui.py,sha256=
|
|
83
|
+
shinestacker/retouch/image_editor_ui.py,sha256=eYOHR_ihekQ7bWZUk7jXqNDpi5WYOAyTgvi3_QxnmTE,29914
|
|
80
84
|
shinestacker/retouch/image_viewer.py,sha256=3ebrLHTDtGd_EbIT2nNFRUjH836rblmmK7jZ62YcJ2U,19564
|
|
81
85
|
shinestacker/retouch/io_gui_handler.py,sha256=pT-49uP0GROMOjZ70LoMLgXHnqSDq8ieAlAKGw0t1TM,11418
|
|
82
86
|
shinestacker/retouch/io_manager.py,sha256=JUAA--AK0mVa1PTErJTnBFjaXIle5Qs7Ow0Wkd8at0o,2437
|
|
@@ -86,9 +90,9 @@ shinestacker/retouch/undo_manager.py,sha256=_ekbcOLcPbQLY7t-o8wf-b1uA6OPY9rRyLM-
|
|
|
86
90
|
shinestacker/retouch/unsharp_mask_filter.py,sha256=uFnth8fpZFGhdIgJCnS8x5v6lBQgJ3hX0CBke9pFXeM,3510
|
|
87
91
|
shinestacker/retouch/vignetting_filter.py,sha256=MA97rQkSL0D-Nh-n2L4AiPR064RoTROkvza4tw84g9U,3658
|
|
88
92
|
shinestacker/retouch/white_balance_filter.py,sha256=glMBYlmrF-i_OrB3sGUpjZE6X4FQdyLC4GBy2bWtaFc,6056
|
|
89
|
-
shinestacker-1.
|
|
90
|
-
shinestacker-1.
|
|
91
|
-
shinestacker-1.
|
|
92
|
-
shinestacker-1.
|
|
93
|
-
shinestacker-1.
|
|
94
|
-
shinestacker-1.
|
|
93
|
+
shinestacker-1.3.0.dist-info/licenses/LICENSE,sha256=pWgb-bBdsU2Gd2kwAXxketnm5W_2u8_fIeWEgojfrxs,7651
|
|
94
|
+
shinestacker-1.3.0.dist-info/METADATA,sha256=lJFdzbmkUcsMTN05Lyivq-uM3-IuKUyi-PEfR8JptyA,6972
|
|
95
|
+
shinestacker-1.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
96
|
+
shinestacker-1.3.0.dist-info/entry_points.txt,sha256=SY6g1LqtMmp23q1DGwLUDT_dhLX9iss8DvWkiWLyo_4,166
|
|
97
|
+
shinestacker-1.3.0.dist-info/top_level.txt,sha256=MhijwnBVX5psfsyX8JZjqp3SYiWPsKe69f3Gnyze4Fw,13
|
|
98
|
+
shinestacker-1.3.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|