From 4d02e88905b7b7212e40a323b8e483d7a9a93837 Mon Sep 17 00:00:00 2001 From: Timmy Welch Date: Fri, 27 May 2022 12:22:45 -0700 Subject: [PATCH] revamp scripts access --- comictaggerlib/fileselectionlist.py | 51 ++- comictaggerlib/main.py | 2 +- comictaggerlib/taggerwindow.py | 35 +- comictaggerlib/ui/taggerwindow.ui | 8 +- {scripts => comictaggerscripts}/README.txt | 0 {scripts => comictaggerscripts}/dupe.ui | 0 {scripts => comictaggerscripts}/find_dupes.py | 351 +++++++++++------- {scripts => comictaggerscripts}/inventory.py | 0 {scripts => comictaggerscripts}/mainwindow.ui | 0 {scripts => comictaggerscripts}/make_links.py | 0 .../move2folder.py | 0 {scripts => comictaggerscripts}/name_fixer.py | 0 {scripts => comictaggerscripts}/remove_ads.py | 0 {scripts => comictaggerscripts}/shrink.py | 0 .../validate_cover.py | 0 {scripts => comictaggerscripts}/xforms | 0 setup.py | 3 +- 17 files changed, 276 insertions(+), 174 deletions(-) rename {scripts => comictaggerscripts}/README.txt (100%) rename {scripts => comictaggerscripts}/dupe.ui (100%) rename {scripts => comictaggerscripts}/find_dupes.py (69%) rename {scripts => comictaggerscripts}/inventory.py (100%) rename {scripts => comictaggerscripts}/mainwindow.ui (100%) rename {scripts => comictaggerscripts}/make_links.py (100%) rename {scripts => comictaggerscripts}/move2folder.py (100%) rename {scripts => comictaggerscripts}/name_fixer.py (100%) rename {scripts => comictaggerscripts}/remove_ads.py (100%) rename {scripts => comictaggerscripts}/shrink.py (100%) rename {scripts => comictaggerscripts}/validate_cover.py (100%) rename {scripts => comictaggerscripts}/xforms (100%) 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 @@ -1245,6 +1245,7 @@ Window + @@ -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=[