diff --git a/comictaggerlib/fileselectionlist.py b/comictaggerlib/fileselectionlist.py
index cf78629..7e51f3c 100644
--- a/comictaggerlib/fileselectionlist.py
+++ b/comictaggerlib/fileselectionlist.py
@@ -108,6 +108,34 @@ class FileSelectionList(QtWidgets.QWidget):
def deselect_all(self) -> None:
self.twList.setRangeSelected(QtWidgets.QTableWidgetSelectionRange(0, 0, self.twList.rowCount() - 1, 5), False)
+ def remove_paths(self, file_list: list[str]) -> None:
+ flist = file_list
+ current_removed = False
+ self.twList.setSortingEnabled(False)
+ for row in reversed(range(self.twList.rowCount())):
+ print(row)
+ ca = self.get_archive_by_row(row)
+ print(flist, str(ca.path.absolute()))
+ if ca and str(ca.path.absolute()) in flist:
+ flist.remove(str(ca.path.absolute()))
+ self.twList.removeRow(row)
+ if row == self.twList.currentRow():
+ current_removed = True
+
+ self.twList.setSortingEnabled(True)
+
+ self.items_removed(current_removed)
+
+ def items_removed(self, current_removed: bool):
+ if self.twList.rowCount() > 0 and current_removed:
+ # since on a removal, we select row 0, make sure callback occurs if
+ # we're already there
+ if self.twList.currentRow() == 0:
+ self.current_item_changed_cb(self.twList.currentItem(), None)
+ self.twList.selectRow(0)
+ elif self.twList.rowCount() <= 0:
+ self.listCleared.emit()
+
def remove_archive_list(self, ca_list: list[ComicArchive]) -> None:
self.twList.setSortingEnabled(False)
current_removed = False
@@ -120,15 +148,7 @@ class FileSelectionList(QtWidgets.QWidget):
self.twList.removeRow(row)
break
self.twList.setSortingEnabled(True)
-
- if self.twList.rowCount() > 0 and current_removed:
- # since on a removal, we select row 0, make sure callback occurs if
- # we're already there
- if self.twList.currentRow() == 0:
- self.current_item_changed_cb(self.twList.currentItem(), None)
- self.twList.selectRow(0)
- elif self.twList.rowCount() <= 0:
- self.listCleared.emit()
+ self.items_removed(current_removed)
def get_archive_by_row(self, row: int) -> Optional[ComicArchive]:
if row >= 0:
@@ -166,14 +186,7 @@ class FileSelectionList(QtWidgets.QWidget):
self.twList.setSortingEnabled(True)
self.twList.currentItemChanged.connect(self.current_item_changed_cb)
- if self.twList.rowCount() > 0:
- # since on a removal, we select row 0, make sure callback occurs if
- # we're already there
- if self.twList.currentRow() == 0:
- self.current_item_changed_cb(self.twList.currentItem(), None)
- self.twList.selectRow(0)
- else:
- self.listCleared.emit()
+ self.items_removed(True)
def add_path_list(self, pathlist: list[str]) -> None:
@@ -345,11 +358,11 @@ class FileSelectionList(QtWidgets.QWidget):
fi.ca.read_cix()
fi.ca.has_cbi()
- def get_selected_archive_list(self) -> List[ComicArchive]:
+ def get_archive_list(self, all_comics=False) -> List[ComicArchive]:
ca_list: List[ComicArchive] = []
for r in range(self.twList.rowCount()):
item = self.twList.item(r, FileSelectionList.dataColNum)
- if item.isSelected():
+ if item.isSelected() or all_comics:
fi: FileInfo = item.data(QtCore.Qt.ItemDataRole.UserRole)
ca_list.append(fi.ca)
diff --git a/comictaggerlib/main.py b/comictaggerlib/main.py
index 3ec1ab4..5427133 100755
--- a/comictaggerlib/main.py
+++ b/comictaggerlib/main.py
@@ -202,7 +202,7 @@ def ctmain() -> None:
QtWidgets.QApplication.processEvents()
try:
- tagger_window = TaggerWindow(opts.file_list, SETTINGS, opts=opts)
+ tagger_window = TaggerWindow(opts.files, SETTINGS, opts=opts)
tagger_window.setWindowIcon(QtGui.QIcon(ComicTaggerSettings.get_graphic("app.png")))
tagger_window.show()
diff --git a/comictaggerlib/taggerwindow.py b/comictaggerlib/taggerwindow.py
index ba78a33..ab64063 100644
--- a/comictaggerlib/taggerwindow.py
+++ b/comictaggerlib/taggerwindow.py
@@ -49,7 +49,6 @@ from comictaggerlib.fileselectionlist import FileInfo, FileSelectionList
from comictaggerlib.issueidentifier import IssueIdentifier
from comictaggerlib.logwindow import LogWindow
from comictaggerlib.optionalmsgdialog import OptionalMessageDialog
-from comictaggerlib.options import Options
from comictaggerlib.pagebrowser import PageBrowserWindow
from comictaggerlib.pagelisteditor import PageListEditor
from comictaggerlib.renamewindow import RenameWindow
@@ -76,13 +75,15 @@ class TaggerWindow(QtWidgets.QMainWindow):
file_list: list[str],
settings: ComicTaggerSettings,
parent: Optional[QtWidgets.QWidget] = None,
- opts: Optional[Options] = None,
+ opts = None,
) -> None:
super().__init__(parent)
uic.loadUi(ComicTaggerSettings.get_ui_file("taggerwindow.ui"), self)
self.settings = settings
+ self.window_ref = {}
+
# prevent multiple instances
socket = QtNetwork.QLocalSocket(self)
socket.connectToServer(settings.install_id)
@@ -148,10 +149,10 @@ class TaggerWindow(QtWidgets.QMainWindow):
self.setWindowIcon(QtGui.QIcon(ComicTaggerSettings.get_graphic("app.png")))
# TODO: this needs to be looked at
- if opts is not None and opts.data_style is not None:
+ if opts is not None and opts.type is not None:
# respect the command line option tag type
- settings.last_selected_save_data_style = opts.data_style
- settings.last_selected_load_data_style = opts.data_style
+ settings.last_selected_save_data_style = opts.type
+ settings.last_selected_load_data_style = opts.type
self.save_data_style = settings.last_selected_save_data_style
self.load_data_style = settings.last_selected_load_data_style
@@ -387,6 +388,8 @@ Have fun!
self.actionPageBrowser.setStatusTip("Show the page browser")
self.actionPageBrowser.triggered.connect(self.show_page_browser)
+ self.actionFind_Duplicate_Comics.triggered.connect(self.find_dupes)
+
# Help Menu
self.actionAbout.setStatusTip("Show the " + self.appName + " info")
self.actionAbout.triggered.connect(self.about_app)
@@ -420,8 +423,20 @@ Have fun!
self.toolBar.addAction(self.actionPageBrowser)
self.toolBar.addAction(self.actionAutoImprint)
+ def find_dupes(self):
+ import comictaggerscripts.find_dupes
+ window = comictaggerscripts.find_dupes.main((str(x.path.absolute()) for x in self.fileSelectionList.get_archive_list(True)), self.settings)
+ window.closed.connect(self.finish_dupes)
+ window.show()
+ self.window_ref['comictaggerscripts.find_dupes'] = window
+
+ def finish_dupes(self, files: list[str]):
+ self.fileSelectionList.remove_paths(files)
+ if 'comictaggerscripts.find_dupes' in self.window_ref:
+ del self.window_ref['comictaggerscripts.find_dupes']
+
def repackage_archive(self) -> None:
- ca_list = self.fileSelectionList.get_selected_archive_list()
+ ca_list = self.fileSelectionList.get_archive_list()
rar_count = 0
for ca in ca_list:
if ca.is_rar():
@@ -1485,7 +1500,7 @@ Please choose options below, and select OK.
def remove_tags(self, style: int) -> None:
# remove the indicated tags from the archive
- ca_list = self.fileSelectionList.get_selected_archive_list()
+ ca_list = self.fileSelectionList.get_archive_list()
has_md_count = 0
for ca in ca_list:
if ca.has_metadata(style):
@@ -1560,7 +1575,7 @@ Please choose options below, and select OK.
def copy_tags(self) -> None:
# copy the indicated tags in the archive
- ca_list = self.fileSelectionList.get_selected_archive_list()
+ ca_list = self.fileSelectionList.get_archive_list()
has_src_count = 0
src_style = self.load_data_style
@@ -1787,7 +1802,7 @@ Please choose options below, and select OK.
return success, match_results
def auto_tag(self) -> None:
- ca_list = self.fileSelectionList.get_selected_archive_list()
+ ca_list = self.fileSelectionList.get_archive_list()
style = self.save_data_style
if len(ca_list) == 0:
@@ -2011,7 +2026,7 @@ Please choose options below, and select OK to Auto-Tag.
QtWidgets.QApplication.restoreOverrideCursor()
def rename_archive(self) -> None:
- ca_list = self.fileSelectionList.get_selected_archive_list()
+ ca_list = self.fileSelectionList.get_archive_list()
if len(ca_list) == 0:
QtWidgets.QMessageBox.information(self, "Rename", "No archives selected!")
diff --git a/comictaggerlib/ui/taggerwindow.ui b/comictaggerlib/ui/taggerwindow.ui
index c8981e7..24201e1 100644
--- a/comictaggerlib/ui/taggerwindow.ui
+++ b/comictaggerlib/ui/taggerwindow.ui
@@ -1182,7 +1182,7 @@
0
0
1096
- 21
+ 30
@@ -1455,6 +1456,11 @@
Parse Filename and split words
+
+
+ Find Duplicate Comics
+
+
diff --git a/scripts/README.txt b/comictaggerscripts/README.txt
similarity index 100%
rename from scripts/README.txt
rename to comictaggerscripts/README.txt
diff --git a/scripts/dupe.ui b/comictaggerscripts/dupe.ui
similarity index 100%
rename from scripts/dupe.ui
rename to comictaggerscripts/dupe.ui
diff --git a/scripts/find_dupes.py b/comictaggerscripts/find_dupes.py
similarity index 69%
rename from scripts/find_dupes.py
rename to comictaggerscripts/find_dupes.py
index db3d35b..0a0301d 100755
--- a/scripts/find_dupes.py
+++ b/comictaggerscripts/find_dupes.py
@@ -3,22 +3,26 @@
import argparse
import hashlib
+import os
import shutil
import signal
-from pathlib import Path
+import sys
+import tempfile
+import typing
from operator import itemgetter
-from typing import Dict, List
+from pathlib import Path
+from typing import Optional
import filetype
-import typing
from PyQt5 import QtCore, QtGui, QtWidgets, uic
-from comictaggerlib.ui.qtutils import centerWindowOnParent
-
-from comictaggerlib.comicarchive import *
-from comictaggerlib.settings import *
-from comictaggerlib.imagehasher import ImageHasher
+from comicapi import utils
+from comicapi.comicarchive import ComicArchive, MetaDataStyle
+from comicapi.genericmetadata import GenericMetadata
from comictaggerlib.filerenamer import FileRenamer
+from comictaggerlib.imagehasher import ImageHasher
+from comictaggerlib.settings import ComicTaggerSettings
+from comictaggerlib.ui.qtutils import center_window_on_parent
root = 1 << 31 - 1
something = 1 << 31 - 1
@@ -36,14 +40,15 @@ class ImageMeta:
class Duplicate:
"""docstring for Duplicate"""
- imageHashes: Dict[str, ImageMeta]
+
+ imageHashes: dict[str, ImageMeta]
def __init__(self, path, metadata: GenericMetadata, ca: ComicArchive, cover):
self.path = path
self.digest = ""
- self.ca = ca
+ self.ca: ComicArchive = ca
self.metadata = metadata
- self.imageHashes = dict()
+ self.imageHashes = {}
self.duplicateImages = set()
self.extras = set()
self.extractedPath = ""
@@ -53,32 +58,35 @@ class Duplicate:
self.imageCount = 0
self.cover = cover
blake2b = hashlib.blake2b(digest_size=16)
- for f in open(self.path, "rb"):
- blake2b.update(f)
+
+ with open(self.path, "rb") as f:
+ for line in f:
+ blake2b.update(line)
self.digest = blake2b.hexdigest()
def extract(self, directory):
- if self.ca.seemsToBeAComicArchive():
+ if self.ca.seems_to_be_a_comic_archive():
self.extractedPath = directory
- for filepath in self.ca.archiver.getArchiveFilenameList():
+ for filepath in self.ca.archiver.get_filename_list():
filename = os.path.basename(filepath)
if filename.lower() in ["comicinfo.xml"]:
continue
self.fileCount += 1
- archived_file = self.ca.archiver.readArchiveFile(filepath)
+ archived_file = self.ca.archiver.read_file(filepath)
image_type = filetype.image_match(archived_file)
if image_type is not None:
self.imageCount += 1
file_hash = hashlib.blake2b(archived_file, digest_size=16).hexdigest().upper()
- if file_hash in self.imageHashes.keys():
+ if file_hash in self.imageHashes:
self.duplicateImages.add(filename)
else:
image_hash = ImageHasher(data=archived_file, width=12, height=12).average_hash()
- self.imageHashes[file_hash] = ImageMeta(os.path.join(self.extractedPath, filename), file_hash,
- image_hash, image_type.extension)
+ self.imageHashes[file_hash] = ImageMeta(
+ os.path.join(self.extractedPath, filename), file_hash, image_hash, image_type.extension
+ )
else:
self.extras.add(filename)
@@ -101,8 +109,8 @@ class Duplicate:
class Tree(QtCore.QAbstractListModel):
- def __init__(self, item: List[List[Duplicate]]):
- super(Tree, self).__init__()
+ def __init__(self, item: list[list[Duplicate]]):
+ super().__init__()
self.rootItem = item
def rowCount(self, index: QtCore.QModelIndex = ...) -> int:
@@ -122,38 +130,49 @@ class Tree(QtCore.QAbstractListModel):
return QtCore.QVariant()
f = FileRenamer(self.rootItem[index.row()][0].metadata)
- f.setTemplate("{series} #{issue} - {title} ({year})")
- if role == QtCore.Qt.DisplayRole:
- return f.determineName('')
- elif role == QtCore.Qt.UserRole:
- return f.determineName('')
+ f.set_template("{series} #{issue} - {title} ({year})")
+ if role in [QtCore.Qt.DisplayRole, QtCore.Qt.UserRole]:
+ return f.determine_name("")
return QtCore.QVariant()
class MainWindow(QtWidgets.QMainWindow):
- def __init__(self, file_list, style, work_path, parent=None):
+ closed = QtCore.pyqtSignal(list)
+
+ def __init__(self, file_list, style, work_path, settings, parent=None):
super().__init__(parent)
- uic.loadUi(ComicTaggerSettings.getUIFile("../../scripts/mainwindow.ui"), self)
+ uic.loadUi(ComicTaggerSettings.get_ui_file("../../comictaggerscripts/mainwindow.ui"), self)
self.dupes = []
+ self.removed: list[str] = []
self.firstRun = 0
- self.dupe_set_list: List[List[Duplicate]] = list()
+ self.dupe_set_list: list[list[Duplicate]] = []
self.style = style
if work_path == "":
- work_path = tempfile.mkdtemp()
- self.work_path = work_path
+ self.work_path = tempfile.mkdtemp()
+ else:
+ self.work_path = work_path
self.initFiles = file_list
+ self.settings = settings
self.dupe_set_qlist.clicked.connect(self.dupe_set_clicked)
self.dupe_set_qlist.doubleClicked.connect(self.dupe_set_double_clicked)
self.actionCompare_Comic.triggered.connect(self.compare_action)
- def comic_deleted(self, archive_path):
- self.update_dupes()
+ def __del__(self):
+ print("fuck this")
+ shutil.rmtree(self.work_path, True)
+
+ def closeEvent(self, event: QtGui.QCloseEvent):
+ self.closed.emit(self.removed)
+ event.accept()
+
+ def comic_deleted(self, archive_path: str):
+ self.removed.append(archive_path)
def update_dupes(self):
# print("updating duplicates")
- new_set_list = list()
+ new_set_list = []
for dupe in self.dupe_set_list:
- dupe_list = list()
+ dupe_list = []
for d in dupe:
QtCore.QCoreApplication.processEvents()
if os.path.exists(d.path):
@@ -165,16 +184,20 @@ class MainWindow(QtWidgets.QMainWindow):
new_set_list.append(dupe_list)
else:
dupe_list[0].clean()
- self.dupe_set_list: List[List[Duplicate]] = new_set_list
+ self.dupe_set_list: list[list[Duplicate]] = new_set_list
self.dupe_set_qlist.setModel(Tree(self.dupe_set_list))
+ if new_set_list:
+ self.dupe_set_qlist.setSelection(QtCore.QRect(0, 0, 0, 1), QtCore.QItemSelectionModel.ClearAndSelect)
- self.dupe_set_qlist.setSelection(QtCore.QRect(0, 0, 0, 1), QtCore.QItemSelectionModel.ClearAndSelect)
- self.dupe_set_clicked(self.dupe_set_qlist.model().index(0, 0))
+ self.dupe_set_clicked(self.dupe_set_qlist.model().index(0, 0))
+ else:
+ self.clear_dupe_list()
def compare(self, i):
if len(self.dupe_set_list) > i:
dw = DupeWindow(self.dupe_set_list[i], self.work_path, self)
dw.closed.connect(self.update_dupes)
+ dw.comic_deleted.connect(self.comic_deleted)
dw.show()
def compare_action(self, b):
@@ -185,25 +208,35 @@ class MainWindow(QtWidgets.QMainWindow):
def dupe_set_double_clicked(self, index: QtCore.QModelIndex):
self.compare(index.row())
- def dupe_set_clicked(self, index: QtCore.QModelIndex):
+ def clear_dupe_list(self):
for f in self.dupe_list.children():
- f.deleteLater()
- self.dupe_set_list[index.row()].sort(key=lambda k: k.digest)
- for i, f in enumerate(self.dupe_set_list[index.row()]):
- color = "black"
- if i > 0:
- if self.dupe_set_list[index.row()][i - 1].digest == f.digest:
- color = "green"
- elif i == 0:
- if len(self.dupe_set_list[index.row()]) > 1:
- if self.dupe_set_list[index.row()][i + 1].digest == f.digest:
+ if isinstance(f, DupeImage):
+ f.deleteLater()
+
+ def dupe_set_clicked(self, index: QtCore.QModelIndex):
+ self.clear_dupe_list()
+ if self.dupe_set_list:
+ self.dupe_set_list[index.row()].sort(key=lambda k: k.digest)
+ for i, f in enumerate(self.dupe_set_list[index.row()]):
+ color = "black"
+ if i > 0:
+ if self.dupe_set_list[index.row()][i - 1].digest == f.digest:
color = "green"
- ql = DupeImage(duplicate=f, style=f".path {{color: black;}}.hash {{color: {color};}}",
- parent=self.dupe_list)
- ql.deleted.connect(self.update_dupes)
- ql.setMinimumWidth(300)
- ql.setMinimumHeight(500)
- self.dupe_list.layout().addWidget(ql)
+ elif i == 0:
+ if len(self.dupe_set_list[index.row()]) > 1:
+ if self.dupe_set_list[index.row()][i + 1].digest == f.digest:
+ color = "green"
+ ql = DupeImage(
+ duplicate=f, style=f".path {{color: black;}}.hash {{color: {color};}}", parent=self.dupe_list
+ )
+ ql.deleted.connect(self.update_dupes)
+ ql.deleted.connect(self.comic_deleted)
+ ql.setMinimumWidth(300)
+ ql.setMinimumHeight(500)
+ print(self.dupe_list.layout())
+
+ layout = self.dupe_list.layout()
+ layout.addWidget(ql)
def showEvent(self, event: QtGui.QShowEvent):
if self.firstRun == 0:
@@ -211,15 +244,20 @@ class MainWindow(QtWidgets.QMainWindow):
self.load_files(self.initFiles)
if len(self.dupe_set_list) < 1:
- dialog = QtWidgets.QMessageBox(QtWidgets.QMessageBox.NoIcon, "ComicTagger Duplicate finder",
- "No duplicate comics found", QtWidgets.QMessageBox.Ok, parent=self)
+ dialog = QtWidgets.QMessageBox(
+ QtWidgets.QMessageBox.NoIcon,
+ "ComicTagger Duplicate finder",
+ "No duplicate comics found",
+ QtWidgets.QMessageBox.Ok,
+ parent=self,
+ )
dialog.setWindowModality(QtCore.Qt.ApplicationModal)
qw = QtWidgets.QWidget()
qw.setFixedWidth(90)
dialog.layout().addWidget(qw, 3, 2, 1, 3)
dialog.exec()
- QtWidgets.QApplication.quit()
- sys.exit(0)
+ QtCore.QTimer.singleShot(1, self.close)
+ self.close()
self.dupe_set_qlist.setSelection(QtCore.QRect(0, 0, 0, 1), QtCore.QItemSelectionModel.ClearAndSelect)
self.dupe_set_clicked(self.dupe_set_qlist.model().index(0, 0))
@@ -230,7 +268,7 @@ class MainWindow(QtWidgets.QMainWindow):
dialog.setWindowModality(QtCore.Qt.ApplicationModal)
dialog.setMinimumDuration(300)
dialog.setMinimumWidth(400)
- centerWindowOnParent(dialog)
+ center_window_on_parent(dialog)
comic_list = []
max_name_len = 2
@@ -240,28 +278,31 @@ class MainWindow(QtWidgets.QMainWindow):
break
dialog.setValue(dialog.value() + 1)
dialog.setLabelText(filename)
- ca = ComicArchive(path=filename, rar_exe_path=settings.rar_exe_path,
- default_image_path=ComicTaggerSettings.getGraphic('nocover.png'))
- if ca.seemsToBeAComicArchive() and ca.hasMetadata(self.style):
+ ca = ComicArchive(
+ path=filename,
+ rar_exe_path=self.settings.rar_exe_path,
+ default_image_path=ComicTaggerSettings.get_graphic("nocover.png"),
+ )
+ if ca.seems_to_be_a_comic_archive() and ca.has_metadata(self.style):
# fmt_str = "{{0:{0}}}".format(max_name_len)
# print(fmt_str.format(filename) + "\r", end='', file=sys.stderr)
# sys.stderr.flush()
- md = ca.readMetadata(self.style)
- cover = ca.getPage(0)
+ md = ca.read_metadata(self.style)
+ cover = ca.get_page(0)
comic_list.append((make_key(md), filename, ca, md, cover))
# max_name_len = len(filename)
comic_list.sort(key=itemgetter(0), reverse=False)
# look for duplicate blocks
- dupe_set = list()
+ dupe_set = []
prev_key = ""
dialog.setWindowTitle("Finding Duplicates")
dialog.setMaximum(len(comic_list))
dialog.setValue(dialog.minimum())
- set_list = list()
+ set_list = []
for new_key, filename, ca, md, cover in comic_list:
dialog.setValue(dialog.value() + 1)
QtCore.QCoreApplication.processEvents()
@@ -277,7 +318,7 @@ class MainWindow(QtWidgets.QMainWindow):
# only add if the dupe list has 2 or more
if len(dupe_set) > 1:
set_list.append(dupe_set)
- dupe_set = list()
+ dupe_set = []
dupe_set.append((filename, ca, md, cover))
prev_key = new_key
@@ -287,7 +328,7 @@ class MainWindow(QtWidgets.QMainWindow):
set_list.append(dupe_set)
for d_set in set_list:
- new_set = list()
+ new_set = []
for filename, ca, md, cover in d_set:
new_set.append(Duplicate(filename, md, ca, cover))
self.dupe_set_list.append(new_set)
@@ -300,7 +341,7 @@ class MainWindow(QtWidgets.QMainWindow):
# working_dir = os.path.join(self.tmp, "working")
# s = False
# # while working and len(dupe_set) > 1:
- # remaining = list()
+ # remaining = []
# for dupe_set in self.dupe_set_list:
# not_deleted = True
# if os.path.exists(working_dir):
@@ -332,17 +373,18 @@ class MainWindow(QtWidgets.QMainWindow):
class DupeWindow(QtWidgets.QWidget):
closed = QtCore.pyqtSignal()
+ comic_deleted = QtCore.pyqtSignal(str)
- def __init__(self, duplicates: List[Duplicate], tmp, parent=None):
+ def __init__(self, duplicates: list[Duplicate], tmp, parent=None):
super().__init__(parent, QtCore.Qt.Window)
- uic.loadUi(ComicTaggerSettings.getUIFile("../../scripts/dupe.ui"), self)
+ uic.loadUi(ComicTaggerSettings.get_ui_file("../../comictaggerscripts/dupe.ui"), self)
for f in self.comic1Image.children():
f.deleteLater()
for f in self.comic2Image.children():
f.deleteLater()
self.deleting = -1
- self.duplicates = duplicates
+ self.duplicates: list[Duplicate] = duplicates
self.dupe1 = -1
self.dupe2 = -1
@@ -394,7 +436,7 @@ class DupeWindow(QtWidgets.QWidget):
self.display_dupe()
def update_dupes(self):
- dupes: List[Duplicate] = list()
+ dupes: list[Duplicate] = []
for f in self.duplicates:
if os.path.exists(f.path):
dupes.append(f)
@@ -403,6 +445,7 @@ class DupeWindow(QtWidgets.QWidget):
self.duplicates = dupes
if len(self.duplicates) < 2:
self.close()
+ self.dupeList.clear()
for i, dupe in enumerate(self.duplicates):
item = QtWidgets.QListWidgetItem()
@@ -418,14 +461,16 @@ class DupeWindow(QtWidgets.QWidget):
def delete_1(self):
self.duplicates[self.dupe1].delete()
+ self.comic_deleted.emit(self.duplicates[self.dupe1].path)
self.update_dupes()
def delete_2(self):
self.duplicates[self.dupe2].delete()
+ self.comic_deleted.emit(self.duplicates[self.dupe2].path)
self.update_dupes()
def display_dupe(self):
- for f in range(self.pageList.rowCount()):
+ for _ in range(self.pageList.rowCount()):
self.pageList.removeRow(0)
for h in self.duplicates[self.dupe1].imageHashes.values():
row = self.pageList.rowCount()
@@ -488,39 +533,44 @@ class DupeWindow(QtWidgets.QWidget):
if image_hash.file_hash == score_hash.file_hash:
file_color = "green"
style = f"""
-.page {{
-color: {page_color};
-}}
-.size {{
-color: {size_color};
-}}
-.type {{
-color: {type_color};
-}}
-.file {{
-color: {file_color};
-}}
-.image {{
-color: {image_color};
-}}
-"""
- text = "name: {{duplicate.path}}
" \
- "page count: {len}
" \
- "size/type: {{width}}x{{height}}/{meta.type}
" \
- "file_hash: {meta.file_hash}
" \
- "image_hash: {meta.image_hash}" \
- .format(meta=image_hash, style=style, len=len(self.duplicates[self.dupe1].imageHashes))
+ .page {{
+ color: {page_color};
+ }}
+ .size {{
+ color: {size_color};
+ }}
+ .type {{
+ color: {type_color};
+ }}
+ .file {{
+ color: {file_color};
+ }}
+ .image {{
+ color: {image_color};
+ }}"""
+ text = ( # this is a format string that creates a format string
+ "name: {{duplicate.path}}
"
+ "page count: {len}
"
+ "size/type: {{width}}x{{height}}/{meta.type}
"
+ "file_hash: {meta.file_hash}
"
+ "image_hash: {meta.image_hash}".format(
+ meta=image_hash, style=style, len=len(self.duplicates[self.dupe1].imageHashes)
+ )
+ )
self.comic1Image.setDuplicate(self.duplicates[self.dupe1])
self.comic1Image.setImage(image_hash.name)
self.comic1Image.setText(text)
self.comic1Image.setLabelStyle(style)
- text = "name: {{duplicate.path}}
" \
- "page count: {len}
" \
- "size/type: {{width}}x{{height}}/{score.type}
" \
- "file_hash: {score.file_hash}
" \
- "image_hash: {score.image_hash}" \
- .format(score=score_hash, style=style, len=len(self.duplicates[self.dupe2].imageHashes))
+ text = (
+ "name: {{duplicate.path}}
"
+ "page count: {len}
"
+ "size/type: {{width}}x{{height}}/{score.type}
"
+ "file_hash: {score.file_hash}
"
+ "image_hash: {score.image_hash}".format(
+ score=score_hash, style=style, len=len(self.duplicates[self.dupe2].imageHashes)
+ )
+ )
self.comic2Image.setDuplicate(self.duplicates[self.dupe2])
self.comic2Image.setImage(score_hash.name)
self.comic2Image.setText(text)
@@ -539,20 +589,29 @@ class QQlabel(QtWidgets.QLabel):
self.setMaximumWidth(pixmap.width())
self.setMaximumHeight(pixmap.height())
super().setPixmap(
- self.image.scaled(self.width(), self.height(), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation))
+ self.image.scaled(self.width(), self.height(), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)
+ )
def resizeEvent(self, a0: QtGui.QResizeEvent) -> None:
if self.image is not None:
- super().setPixmap(self.image.scaled(self.width(), self.height(), QtCore.Qt.KeepAspectRatio,
- QtCore.Qt.SmoothTransformation))
+ super().setPixmap(
+ self.image.scaled(
+ self.width(), self.height(), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation
+ )
+ )
class DupeImage(QtWidgets.QWidget):
deleted = QtCore.pyqtSignal(str)
- def __init__(self, duplicate: Duplicate, style=".path {color: black;}.hash {color: black;}",
- text="path: {duplicate.path}
hash: {duplicate.digest}",
- image="cover", parent=None):
+ def __init__(
+ self,
+ duplicate: Duplicate,
+ style=".path {color: black;}.hash {color: black;}",
+ text="path: {duplicate.path}
hash: {duplicate.digest}",
+ image="cover",
+ parent=None,
+ ):
super().__init__(parent)
self.setLayout(QtWidgets.QVBoxLayout())
self.image = QQlabel()
@@ -589,14 +648,16 @@ class DupeImage(QtWidgets.QWidget):
self.duplicate = duplicate
self.setImage("cover")
self.label.setText(
- f"" + self.text.format(duplicate=self.duplicate, width=self.iWidth,
- height=self.iHeight))
+ f""
+ + self.text.format(duplicate=self.duplicate, width=self.iWidth, height=self.iHeight)
+ )
def setText(self, text):
self.text = text
self.label.setText(
- f"" + self.text.format(duplicate=self.duplicate, width=self.iWidth,
- height=self.iHeight))
+ f""
+ + self.text.format(duplicate=self.duplicate, width=self.iWidth, height=self.iHeight)
+ )
def setImage(self, image):
if self.duplicate is not None:
@@ -612,8 +673,9 @@ class DupeImage(QtWidgets.QWidget):
def setLabelStyle(self, style):
self.labelStyle = style
self.label.setText(
- f"" + self.text.format(duplicate=self.duplicate, width=self.iWidth,
- height=self.iHeight))
+ f""
+ + self.text.format(duplicate=self.duplicate, width=self.iWidth, height=self.iHeight)
+ )
def extract(dupe_set, directory):
@@ -621,11 +683,11 @@ def extract(dupe_set, directory):
dupe.extract(unique_dir(os.path.join(directory, os.path.basename(dupe.path))))
-def compare_dupe(dupe1: Dict[str, ImageMeta], dupe2: Dict[str, ImageMeta]):
+def compare_dupe(dupe1: dict[str, ImageMeta], dupe2: dict[str, ImageMeta]):
for k, image1 in dupe1.items():
score = sys.maxsize
file_hash = ""
- for k2, image2 in dupe2.items():
+ for _, image2 in dupe2.items():
tmp = ImageHasher.hamming_distance(image1.image_hash, image2.image_hash)
if tmp < score:
score = tmp
@@ -645,44 +707,49 @@ def unique_dir(file_name):
while True:
if not os.path.lexists(file_name):
return file_name
- file_name = file_name_parts[0] + ' (' + str(counter) + ')' + file_name_parts[1]
+ file_name = file_name_parts[0] + " (" + str(counter) + ")" + file_name_parts[1]
counter += 1
-app = None
-settings = ComicTaggerSettings()
+def parse_args(args: list[str], config=True):
+ parser = argparse.ArgumentParser(description="ComicTagger Duplicate comparison script")
+ parser.add_argument(
+ "-w",
+ metavar="workdir",
+ type=str,
+ nargs=1,
+ default=tempfile.mkdtemp(),
+ help="work directory, will be deleted on close",
+ )
+ parser.add_argument("paths", metavar="PATH", type=str, nargs="+", help="Path(s) to search for duplicates")
+ if config:
+ parser.add_argument(
+ "--config",
+ help="""Config directory defaults to ~/.ComicTagger\non Linux/Mac and %%APPDATA%% on Windows\n""",
+ )
+ return parser.parse_args(args)
-def main():
- signal.signal(signal.SIGINT, sigint_handler)
-
- parser = argparse.ArgumentParser(description='ComicTagger Duplicate comparison script')
- parser.add_argument('-w', metavar='workdir', type=str, nargs=1, default=tempfile.mkdtemp(), help='work directory')
- parser.add_argument('paths', metavar='PATH', type=str, nargs='+', help='Path(s) to search for duplicates')
- args = parser.parse_args()
+def main(args: Optional[list[str]] = None, settings: Optional[ComicTaggerSettings] = None):
+ opts = parse_args(args)
+ if not settings:
+ settings = ComicTaggerSettings(opts.config)
style = MetaDataStyle.CIX
- global app
- workdir = args.w
- app = QtWidgets.QApplication(sys.argv)
- file_list = utils.get_recursive_filelist(args.paths)
+ file_list = utils.get_recursive_filelist(opts.paths)
- timer = QtCore.QTimer()
- timer.start(50) # You may change this if you wish.
- timer.timeout.connect(lambda: None) # Let the interpreter run each 500 ms.
-
- window = MainWindow(file_list, style, workdir)
- window.show()
- app.exec()
- shutil.rmtree(workdir, True)
+ return MainWindow(file_list, style, opts.w, settings)
def sigint_handler(*args):
"""Handler for the SIGINT signal."""
- sys.stderr.write('\r')
+ sys.stderr.write("\r")
QtWidgets.QApplication.quit()
-
if __name__ == "__main__":
- main()
+ signal.signal(signal.SIGINT, sigint_handler)
+ app = QtWidgets.QApplication([])
+ window = main()
+ window.show()
+ app.exec()
diff --git a/scripts/inventory.py b/comictaggerscripts/inventory.py
similarity index 100%
rename from scripts/inventory.py
rename to comictaggerscripts/inventory.py
diff --git a/scripts/mainwindow.ui b/comictaggerscripts/mainwindow.ui
similarity index 100%
rename from scripts/mainwindow.ui
rename to comictaggerscripts/mainwindow.ui
diff --git a/scripts/make_links.py b/comictaggerscripts/make_links.py
similarity index 100%
rename from scripts/make_links.py
rename to comictaggerscripts/make_links.py
diff --git a/scripts/move2folder.py b/comictaggerscripts/move2folder.py
similarity index 100%
rename from scripts/move2folder.py
rename to comictaggerscripts/move2folder.py
diff --git a/scripts/name_fixer.py b/comictaggerscripts/name_fixer.py
similarity index 100%
rename from scripts/name_fixer.py
rename to comictaggerscripts/name_fixer.py
diff --git a/scripts/remove_ads.py b/comictaggerscripts/remove_ads.py
similarity index 100%
rename from scripts/remove_ads.py
rename to comictaggerscripts/remove_ads.py
diff --git a/scripts/shrink.py b/comictaggerscripts/shrink.py
similarity index 100%
rename from scripts/shrink.py
rename to comictaggerscripts/shrink.py
diff --git a/scripts/validate_cover.py b/comictaggerscripts/validate_cover.py
similarity index 100%
rename from scripts/validate_cover.py
rename to comictaggerscripts/validate_cover.py
diff --git a/scripts/xforms b/comictaggerscripts/xforms
similarity index 100%
rename from scripts/xforms
rename to comictaggerscripts/xforms
diff --git a/setup.py b/setup.py
index 08cc0cf..2873c7b 100644
--- a/setup.py
+++ b/setup.py
@@ -53,7 +53,8 @@ setup(
author="ComicTagger team",
author_email="comictagger@gmail.com",
url="https://github.com/comictagger/comictagger",
- packages=["comictaggerlib", "comicapi"],
+ packages=["comictaggerlib", "comicapi", "comictaggerscripts"],
+ package_dir={"comictaggerscripts": "scripts"},
package_data={"comictaggerlib": ["ui/*", "graphics/*"], "comicapi": ["data/*"]},
entry_points=dict(console_scripts=["comictagger=comictaggerlib.main:ctmain"]),
classifiers=[