diff --git a/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/comics_export_dialog.py b/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/comics_export_dialog.py index ad9fdd16f9..3036e53fc0 100644 --- a/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/comics_export_dialog.py +++ b/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/comics_export_dialog.py @@ -1,370 +1,370 @@ """ Part of the comics project management tools (CPMT). A dialog for editing the exporter settings. """ -from PyQt5.QtGui import * -from PyQt5.QtWidgets import * -from PyQt5.QtCore import * +from PyQt5.QtGui import QStandardItem, QStandardItemModel, QColor +from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QGroupBox, QFormLayout, QCheckBox, QComboBox, QSpinBox, QWidget, QVBoxLayout, QTabWidget, QPushButton, QLineEdit, QLabel, QListView +from PyQt5.QtCore import Qt, QUuid from krita import * """ A generic widget to make selecting size easier. It works by initialising with a config name(like "scale"), and then optionally setting the config with a dictionary. Then, afterwards, you get the config with a dictionary, with the config name being the entry the values are under. """ class comic_export_resize_widget(QGroupBox): configName = "" def __init__(self, configName, batch=False, fileType=True): super().__init__() self.configName = configName self.setTitle("Adjust Workingfile") formLayout = QFormLayout() self.setLayout(formLayout) self.crop = QCheckBox(i18n("Crop files before resize.")) self.cmbFile = QComboBox() self.cmbFile.addItems(["png", "jpg", "webp"]) self.resizeMethod = QComboBox() self.resizeMethod.addItems([i18n("Percentage"), i18n("DPI"), i18n("Maximum Width"), i18n("Maximum Height")]) self.resizeMethod.currentIndexChanged.connect(self.slot_set_enabled) self.spn_DPI = QSpinBox() self.spn_DPI.setMaximum(1200) self.spn_DPI.setSuffix(i18n(" DPI")) self.spn_DPI.setValue(72) self.spn_PER = QSpinBox() if batch is True: self.spn_PER.setMaximum(1000) else: self.spn_PER.setMaximum(100) self.spn_PER.setSuffix(" %") self.spn_PER.setValue(100) self.spn_width = QSpinBox() self.spn_width.setMaximum(99999) self.spn_width.setSuffix(" px") self.spn_width.setValue(800) self.spn_height = QSpinBox() self.spn_height.setMaximum(99999) self.spn_height.setSuffix(" px") self.spn_height.setValue(800) if batch is False: formLayout.addRow("", self.crop) if fileType is True and configName != "TIFF": formLayout.addRow(i18n("File Type"), self.cmbFile) formLayout.addRow(i18n("Method:"), self.resizeMethod) formLayout.addRow(i18n("DPI:"), self.spn_DPI) formLayout.addRow(i18n("Percentage:"), self.spn_PER) formLayout.addRow(i18n("Width:"), self.spn_width) formLayout.addRow(i18n("Height:"), self.spn_height) self.slot_set_enabled() def slot_set_enabled(self): method = self.resizeMethod.currentIndex() self.spn_DPI.setEnabled(False) self.spn_PER.setEnabled(False) self.spn_width.setEnabled(False) self.spn_height.setEnabled(False) if method is 0: self.spn_PER.setEnabled(True) if method is 1: self.spn_DPI.setEnabled(True) if method is 2: self.spn_width.setEnabled(True) if method is 3: self.spn_height.setEnabled(True) def set_config(self, config): if self.configName in config.keys(): mConfig = config[self.configName] if "Method" in mConfig.keys(): self.resizeMethod.setCurrentIndex(mConfig["Method"]) if "FileType" in mConfig.keys(): self.cmbFile.setCurrentText(mConfig["FileType"]) if "Crop" in mConfig.keys(): self.crop.setChecked(mConfig["Crop"]) if "DPI" in mConfig.keys(): self.spn_DPI.setValue(mConfig["DPI"]) if "Percentage" in mConfig.keys(): self.spn_PER.setValue(mConfig["Percentage"]) if "Width" in mConfig.keys(): self.spn_width.setValue(mConfig["Width"]) if "Height" in mConfig.keys(): self.spn_height.setValue(mConfig["Height"]) self.slot_set_enabled() def get_config(self, config): mConfig = {} mConfig["Method"] = self.resizeMethod.currentIndex() if self.configName == "TIFF": mConfig["FileType"] = "tiff" else: mConfig["FileType"] = self.cmbFile.currentText() mConfig["Crop"] = self.crop.isChecked() mConfig["DPI"] = self.spn_DPI.value() mConfig["Percentage"] = self.spn_PER.value() mConfig["Width"] = self.spn_width.value() mConfig["Height"] = self.spn_height.value() config[self.configName] = mConfig return config """ Quick combobox for selecting the color label. """ class labelSelector(QComboBox): def __init__(self): super(labelSelector, self).__init__() lisOfColors = [] lisOfColors.append(Qt.transparent) lisOfColors.append(QColor(91, 173, 220)) lisOfColors.append(QColor(151, 202, 63)) lisOfColors.append(QColor(247, 229, 61)) lisOfColors.append(QColor(255, 170, 63)) lisOfColors.append(QColor(177, 102, 63)) lisOfColors.append(QColor(238, 50, 51)) lisOfColors.append(QColor(191, 106, 209)) lisOfColors.append(QColor(118, 119, 114)) self.itemModel = QStandardItemModel() for color in lisOfColors: item = QStandardItem() item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled) item.setCheckState(Qt.Unchecked) item.setText(" ") item.setData(color, Qt.BackgroundColorRole) self.itemModel.appendRow(item) self.setModel(self.itemModel) def getLabels(self): listOfIndexes = [] for i in range(self.itemModel.rowCount()): index = self.itemModel.index(i, 0) item = self.itemModel.itemFromIndex(index) if item.checkState(): listOfIndexes.append(i) return listOfIndexes def setLabels(self, listOfIndexes): for i in listOfIndexes: index = self.itemModel.index(i, 0) item = self.itemModel.itemFromIndex(index) item.setCheckState(True) class comic_export_setting_dialog(QDialog): def __init__(self): super().__init__() self.setLayout(QVBoxLayout()) self.setWindowTitle(i18n("Export settings")) buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) buttons.accepted.connect(self.accept) buttons.rejected.connect(self.reject) mainWidget = QTabWidget() self.layout().addWidget(mainWidget) self.layout().addWidget(buttons) # Set basic crop settings # Set which layers to remove before export. mainExportSettings = QWidget() mainExportSettings.setLayout(QVBoxLayout()) groupExportCrop = QGroupBox(i18n("Crop settings")) formCrop = QFormLayout() groupExportCrop.setLayout(formCrop) self.chk_toOutmostGuides = QCheckBox(i18n("Crop to outmost guides")) self.chk_toOutmostGuides.setChecked(True) self.chk_toOutmostGuides.setToolTip(i18n("This will crop to the outmost guides if possible and otherwise use the underlying crop settings.")) formCrop.addRow("", self.chk_toOutmostGuides) btn_fromSelection = QPushButton(i18n("Set margins from active selection")) btn_fromSelection.clicked.connect(self.slot_set_margin_from_selection) # This doesn't work. formCrop.addRow("", btn_fromSelection) self.spn_marginLeft = QSpinBox() self.spn_marginLeft.setMaximum(99999) self.spn_marginLeft.setSuffix(" px") formCrop.addRow(i18n("Left:"), self.spn_marginLeft) self.spn_marginTop = QSpinBox() self.spn_marginTop.setMaximum(99999) self.spn_marginTop.setSuffix(" px") formCrop.addRow(i18n("Top:"), self.spn_marginTop) self.spn_marginRight = QSpinBox() self.spn_marginRight.setMaximum(99999) self.spn_marginRight.setSuffix(" px") formCrop.addRow(i18n("Right:"), self.spn_marginRight) self.spn_marginBottom = QSpinBox() self.spn_marginBottom.setMaximum(99999) self.spn_marginBottom.setSuffix(" px") formCrop.addRow(i18n("Bottom:"), self.spn_marginBottom) groupExportLayers = QGroupBox(i18n("Layers")) formLayers = QFormLayout() groupExportLayers.setLayout(formLayers) self.cmbLabelsRemove = labelSelector() formLayers.addRow(i18n("Label for removal:"), self.cmbLabelsRemove) mainExportSettings.layout().addWidget(groupExportCrop) mainExportSettings.layout().addWidget(groupExportLayers) mainWidget.addTab(mainExportSettings, i18n("General")) # CBZ, crop, resize, which metadata to add. CBZexportSettings = QWidget() CBZexportSettings.setLayout(QVBoxLayout()) self.CBZactive = QCheckBox(i18n("Export to CBZ")) CBZexportSettings.layout().addWidget(self.CBZactive) self.CBZgroupResize = comic_export_resize_widget("CBZ") CBZexportSettings.layout().addWidget(self.CBZgroupResize) self.CBZactive.clicked.connect(self.CBZgroupResize.setEnabled) CBZgroupMeta = QGroupBox(i18n("Metadata to add")) CBZexportSettings.layout().addWidget(CBZgroupMeta) CBZgroupMeta.setLayout(QFormLayout()) mainWidget.addTab(CBZexportSettings, "CBZ") # ACBF, crop, resize, creator name, version history, panel layer, text layers. ACBFExportSettings = QWidget() ACBFform = QFormLayout() ACBFExportSettings.setLayout(QVBoxLayout()) ACBFdocInfo = QGroupBox() ACBFdocInfo.setTitle(i18n("ACBF Document Info")) ACBFdocInfo.setLayout(ACBFform) self.lnACBFAuthor = QLineEdit() self.lnACBFAuthor.setToolTip(i18n("The person responsible for the generation of the CBZ.")) self.lnACBFSource = QLineEdit() self.lnACBFSource.setToolTip(i18n("Whether the acbf file is an adaption of an existing source, and if so, how to find information about that source. So for example, for an adapted webcomic, the official website url should go here.")) self.lnACBFID = QLabel() self.lnACBFID.setToolTip(i18n("By default this will be filled with a generated universal unique identifier. The ID by itself is merely so that comic book library management programs can figure out if this particular comic is already in their database and whether it has been rated. Of course, the UUID can be changed into something else by manually changing the json, but this is advanced usage.")) self.spnACBFVersion = QSpinBox() self.ACBFhistoryModel = QStandardItemModel() acbfHistoryList = QListView() acbfHistoryList.setModel(self.ACBFhistoryModel) btn_add_history = QPushButton(i18n("Add history entry")) btn_add_history.clicked.connect(self.slot_add_history_item) ACBFform.addRow(i18n("Author-name:"), self.lnACBFAuthor) ACBFform.addRow(i18n("Source:"), self.lnACBFSource) ACBFform.addRow(i18n("ACBF UID:"), self.lnACBFID) ACBFform.addRow(i18n("Version:"), self.spnACBFVersion) ACBFform.addRow(i18n("Version History:"), acbfHistoryList) ACBFform.addRow("", btn_add_history) ACBFExportSettings.layout().addWidget(ACBFdocInfo) mainWidget.addTab(ACBFExportSettings, "ACBF") # Epub export, crop, resize, other questions. EPUBexportSettings = QWidget() EPUBexportSettings.setLayout(QVBoxLayout()) self.EPUBactive = QCheckBox(i18n("Export to EPUB")) EPUBexportSettings.layout().addWidget(self.EPUBactive) self.EPUBgroupResize = comic_export_resize_widget("EPUB") EPUBexportSettings.layout().addWidget(self.EPUBgroupResize) self.EPUBactive.clicked.connect(self.EPUBgroupResize.setEnabled) mainWidget.addTab(EPUBexportSettings, "EPUB") # For Print. Crop, no resize. TIFFExportSettings = QWidget() TIFFExportSettings.setLayout(QVBoxLayout()) self.TIFFactive = QCheckBox(i18n("Export to TIFF")) TIFFExportSettings.layout().addWidget(self.TIFFactive) self.TIFFgroupResize = comic_export_resize_widget("TIFF") TIFFExportSettings.layout().addWidget(self.TIFFgroupResize) self.TIFFactive.clicked.connect(self.TIFFgroupResize.setEnabled) mainWidget.addTab(TIFFExportSettings, "TIFF") # SVG, crop, resize, embed vs link. #SVGExportSettings = QWidget() #mainWidget.addTab(SVGExportSettings, "SVG") def slot_add_history_item(self): newItem = QStandardItem() - newItem.setText("v" + self.spnACBFVersion.value() + "-" + i18n("in this version...")) + newItem.setText("v" + str(self.spnACBFVersion.value()) + "-" + i18n("in this version...")) self.ACBFhistoryModel.appendRow(newItem) def slot_set_margin_from_selection(self): doc = Application.activeDocument() if doc is not None: if doc.selection() is not None: self.spn_marginLeft.setValue(doc.selection().x()) self.spn_marginTop.setValue(doc.selection().y()) self.spn_marginRight.setValue(doc.width() - (doc.selection().x() + doc.selection().width())) self.spn_marginBottom.setValue(doc.height() - (doc.selection().y() + doc.selection().height())) """ Load the UI values from the config dictionary given. """ def setConfig(self, config): if "cropToGuides" in config.keys(): self.chk_toOutmostGuides.setChecked(config["cropToGuides"]) if "cropLeft" in config.keys(): self.spn_marginLeft.setValue(config["cropLeft"]) if "cropTop" in config.keys(): self.spn_marginTop.setValue(config["cropTop"]) if "cropRight" in config.keys(): self.spn_marginRight.setValue(config["cropRight"]) if "cropBottom" in config.keys(): self.spn_marginBottom.setValue(config["cropBottom"]) if "labelsToRemove" in config.keys(): self.cmbLabelsRemove.setLabels(config["labelsToRemove"]) self.CBZgroupResize.set_config(config) if "CBZactive" in config.keys(): self.CBZactive.setChecked(config["CBZactive"]) self.EPUBgroupResize.set_config(config) if "EPUBactive" in config.keys(): self.EPUBactive.setChecked(config["EPUBactive"]) self.TIFFgroupResize.set_config(config) if "TIFFactive" in config.keys(): self.TIFFactive.setChecked(config["TIFFactive"]) if "acbfAuthor" in config.keys(): self.lnACBFAuthor.setText(config["acbfAuthor"]) if "acbfSource" in config.keys(): self.lnACBFSource.setText(config["acbfSource"]) if "acbfID" in config.keys(): self.lnACBFID.setText(config["acbfID"]) else: self.lnACBFID.setText(QUuid.createUuid().toString()) if "acbfVersion" in config.keys(): self.spnACBFVersion.setValue(config["acbfVersion"]) if "acbfHistory" in config.keys(): for h in config["acbfHistory"]: item = QStandardItem() item.setText(h) self.ACBFhistoryModel.appendRow(item) self.CBZgroupResize.setEnabled(self.CBZactive.isChecked()) """ Store the GUI values into the config dictionary given. @return the config diactionary filled with new values. """ def getConfig(self, config): config["cropToGuides"] = self.chk_toOutmostGuides.isChecked() config["cropLeft"] = self.spn_marginLeft.value() config["cropTop"] = self.spn_marginTop.value() config["cropBottom"] = self.spn_marginRight.value() config["cropRight"] = self.spn_marginBottom.value() config["labelsToRemove"] = self.cmbLabelsRemove.getLabels() config["CBZactive"] = self.CBZactive.isChecked() config = self.CBZgroupResize.get_config(config) config["EPUBactive"] = self.EPUBactive.isChecked() config = self.EPUBgroupResize.get_config(config) config["TIFFactive"] = self.TIFFactive.isChecked() config = self.TIFFgroupResize.get_config(config) config["acbfAuthor"] = self.lnACBFAuthor.text() config["acbfSource"] = self.lnACBFSource.text() config["acbfID"] = self.lnACBFID.text() config["acbfVersion"] = self.spnACBFVersion.value() versionList = [] for r in range(self.ACBFhistoryModel.rowCount()): index = self.ACBFhistoryModel.index(r, 0) versionList.append(self.ACBFhistoryModel.data(index, Qt.DisplayRole)) config["acbfHistory"] = versionList return config diff --git a/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/comics_exporter.py b/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/comics_exporter.py index 7675608c48..02ef557f78 100644 --- a/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/comics_exporter.py +++ b/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/comics_exporter.py @@ -1,1083 +1,1084 @@ """ Part of the comics project management tools (CPMT). An exporter that take the comicsConfig and uses it to generate several files. """ import sys import os from pathlib import Path import json import zipfile import xml.etree.ElementTree as ET import shutil -from PyQt5.QtGui import * # For the progress dialog. +from PyQt5.QtWidgets import QLabel, QProgressDialog # For the progress dialog. +from PyQt5.QtCore import QElapsedTimer, QDateTime, QLocale, Qt from krita import * class sizesCalculator(): def __init__(self): pass def get_scale_from_resize_config(self, config, listSizes): listScaleTo = listSizes oldWidth = listSizes[0] oldHeight = listSizes[1] oldXDPI = listSizes[2] oldYDPI = listSizes[3] if "Method" in config.keys(): method = config["Method"] if method is 0: # percentage percentage = config["Percentage"] / 100 listScaleTo[0] = round(oldWidth * percentage) listScaleTo[1] = round(oldHeight * percentage) if method is 1: # dpi DPI = config["DPI"] listScaleTo[0] = round((oldWidth / oldXDPI) * DPI) listScaleTo[1] = round((oldHeight / oldYDPI) * DPI) listScaleTo[2] = DPI listScaleTo[3] = DPI if method is 2: # maximum width width = config["Width"] listScaleTo[0] = width listScaleTo[1] = round((oldHeight / oldWidth) * width) if method is 3: # maximum height height = config["Height"] listScaleTo[1] = height listScaleTo[0] = round((oldWidth / oldHeight) * height) return listScaleTo class comicsExporter(): acbfLocation = str() cometLocation = str() comicRackInfo = str() comic_book_info_json_dump = str() pagesLocationList = {} def __init__(self): pass def set_config(self, config, projectURL): self.configDictionary = config self.projectURL = projectURL self.pagesLocationList = {} self.acbfLocation = str() self.cometLocation = str() self.comicRackInfo = str() self.comic_book_info_json_dump = str() def export(self): export_success = False path = Path(self.projectURL) exportPath = path / self.configDictionary["exportLocation"] if Path(exportPath / "metadata").exists() is False: Path(exportPath / "metadata").mkdir() sizesList = {} if "CBZ" in self.configDictionary.keys(): if self.configDictionary["CBZactive"]: sizesList["CBZ"] = self.configDictionary["CBZ"] if "EPUB" in self.configDictionary.keys(): if self.configDictionary["EPUBactive"]: sizesList["EPUB"] = self.configDictionary["EPUB"] if "TIFF" in self.configDictionary.keys(): if self.configDictionary["TIFFactive"]: sizesList["TIFF"] = self.configDictionary["TIFF"] export_success = self.save_out_pngs(sizesList) if export_success: export_success = self.export_to_acbf() if export_success: if "CBZ" in sizesList.keys(): export_success = self.export_to_cbz() print("Exported to CBZ", export_success) if "EPUB" in sizesList.keys(): export_success = self.export_to_epub() print("Exported to EPUB", export_success) return export_success def export_to_acbf(self): self.write_acbf_meta_data() return True def export_to_cbz(self): export_success = self.write_comet_meta_data() export_success = self.write_comic_rack_info() export_success = self.write_comic_book_info_json() self.package_cbz() return export_success """ Create an epub folder, finally, package to a epubzip. """ def export_to_epub(self): path = Path(os.path.join(self.projectURL, self.configDictionary["exportLocation"])) exportPath = path / "EPUB-files" metaInf = exportPath / "META-INF" oebps = exportPath / "OEBPS" imagePath = oebps / "Images" stylesPath = oebps / "Styles" textPath = oebps / "Text" if exportPath.exists() is False: exportPath.mkdir() metaInf.mkdir() oebps.mkdir() imagePath.mkdir() stylesPath.mkdir() textPath.mkdir() mimetype = open(str(Path(exportPath / "mimetype")), mode="w") mimetype.write("application/epub+zip") mimetype.close() container = ET.ElementTree() cRoot = ET.Element("container") cRoot.set("version", "1.0") cRoot.set("xmlns", "urn:oasis:names:tc:opendocument:xmlns:container") container._setroot(cRoot) rootFiles = ET.Element("rootfiles") rootfile = ET.Element("rootfile") rootfile.set("full-path", "OEBPS/content.opf") rootfile.set("media-type", "application/oebps-package+xml") rootFiles.append(rootfile) cRoot.append(rootFiles) container.write(str(Path(metaInf / "container.xml")), encoding="utf-8", xml_declaration=True) # copyimages to images pagesList = [] if "EPUB" in self.pagesLocationList.keys(): coverNumber = self.configDictionary["pages"].index(self.configDictionary["cover"]) for p in self.pagesLocationList["EPUB"]: if os.path.exists(p): shutil.copy2(p, str(imagePath)) pagesList.append(str(Path(imagePath / os.path.basename(p)))) if len(self.pagesLocationList["EPUB"]) >= coverNumber: coverpageurl = pagesList[coverNumber] else: print("CPMT: Couldn't find the location for the epub files.") return False # for each image, make an xml file htmlFiles = [] for i in range(len(pagesList)): pageName = "Page" + str(i) + ".xhtml" doc = ET.ElementTree() html = ET.Element("html") doc._setroot(html) html.set("xmlns", "http://www.w3.org/1999/xhtml") html.set("xmlns:epub", "http://www.idpf.org/2007/ops") head = ET.Element("head") html.append(head) body = ET.Element("body") img = ET.Element("img") img.set("src", os.path.relpath(pagesList[i], str(textPath))) body.append(img) if pagesList[i] != coverpageurl: pagenumber = ET.Element("p") pagenumber.text = "Page " + str(i) body.append(pagenumber) html.append(body) filename = str(Path(textPath / pageName)) doc.write(filename, encoding="utf-8", xml_declaration=True) if pagesList[i] == coverpageurl: coverpagehtml = os.path.relpath(filename, str(oebps)) htmlFiles.append(filename) # opf file opfFile = ET.ElementTree() opfRoot = ET.Element("package") opfRoot.set("version", "3.0") opfRoot.set("unique-identifier", "BookId") opfRoot.set("xmlns", "http://www.idpf.org/2007/opf") opfFile._setroot(opfRoot) # metadata opfMeta = ET.Element("metadata") opfMeta.set("xmlns:dc", "http://purl.org/dc/elements/1.1/") if "language" in self.configDictionary.keys(): bookLang = ET.Element("dc:language") bookLang.text = self.configDictionary["language"] opfMeta.append(bookLang) bookTitle = ET.Element("dc:title") if "title" in self.configDictionary.keys(): bookTitle.text = str(self.configDictionary["title"]) else: bookTitle.text = "Comic with no Name" opfMeta.append(bookTitle) if "authorList" in self.configDictionary.keys(): for authorE in range(len(self.configDictionary["authorList"])): authorDict = self.configDictionary["authorList"][authorE] authorType = "dc:creator" if "role" in authorDict.keys(): if str(authorDict["role"]).lower() in ["editor", "assistant editor", "proofreader", "beta"]: authorType = "dc:contributor" author = ET.Element(authorType) authorName = [] if "last-name" in authorDict.keys(): authorName.append(authorDict["last-name"]) if "first-name" in authorDict.keys(): authorName.append(authorDict["first-name"]) if "initials" in authorDict.keys(): authorName.append(authorDict["initials"]) if "nickname" in authorDict.keys(): authorName.append("(" + authorDict["nickname"] + ")") author.text = ", ".join(authorName) opfMeta.append(author) if "role" in authorDict.keys(): author.set("id", "cre" + str(authorE)) role = ET.Element("meta") role.set("refines", "cre" + str(authorE)) role.set("scheme", "marc:relators") role.set("property", "role") role.text = str(authorDict["role"]) opfMeta.append(role) if "publishingDate" in self.configDictionary.keys(): date = ET.Element("dc:date") date.text = self.configDictionary["publishingDate"] opfMeta.append(date) description = ET.Element("dc:description") if "summary" in self.configDictionary.keys(): description.text = self.configDictionary["summary"] else: description.text = "There was no summary upon generation of this file." opfMeta.append(description) type = ET.Element("dc:type") type.text = "Comic" opfMeta.append(type) if "publisherName" in self.configDictionary.keys(): publisher = ET.Element("dc:publisher") publisher.text = self.configDictionary["publisherName"] opfMeta.append(publisher) if "isbn-number" in self.configDictionary.keys(): publishISBN = ET.Element("dc:identifier") publishISBN.text = str("urn:isbn:") + self.configDictionary["isbn-number"] opfMeta.append(publishISBN) if "license" in self.configDictionary.keys(): rights = ET.Element("dc:rights") rights.text = self.configDictionary["license"] opfMeta.append(rights) if "genre" in self.configDictionary.keys(): for g in self.configDictionary["genre"]: subject = ET.Element("dc:subject") subject.text = g opfMeta.append(subject) if "characters" in self.configDictionary.keys(): for name in self.configDictionary["characters"]: char = ET.Element("dc:subject") char.text = name opfMeta.append(char) if "format" in self.configDictionary.keys(): for format in self.configDictionary["format"]: f = ET.Element("dc:subject") f.text = format opfMeta.append(f) if "otherKeywords" in self.configDictionary.keys(): for key in self.configDictionary["otherKeywords"]: word = ET.Element("dc:subject") word.text = key opfMeta.append(word) opfRoot.append(opfMeta) opfManifest = ET.Element("manifest") toc = ET.Element("item") toc.set("id", "ncx") toc.set("href", "toc.ncx") toc.set("media-type", "application/x-dtbncx+xml") opfManifest.append(toc) for p in htmlFiles: item = ET.Element("item") item.set("id", os.path.basename(p)) item.set("href", os.path.relpath(p, str(oebps))) item.set("media-type", "application/xhtml+xml") opfManifest.append(item) for p in pagesList: item = ET.Element("item") item.set("id", os.path.basename(p)) item.set("href", os.path.relpath(p, str(oebps))) item.set("media-type", "image/png") if os.path.basename(p) == os.path.basename(coverpageurl): item.set("properties", "cover-image") opfManifest.append(item) opfRoot.append(opfManifest) opfSpine = ET.Element("spine") opfSpine.set("toc", "ncx") for p in htmlFiles: item = ET.Element("itemref") item.set("idref", os.path.basename(p)) opfSpine.append(item) opfRoot.append(opfSpine) opfGuide = ET.Element("guide") if coverpagehtml is not None and coverpagehtml.isspace() is False and len(coverpagehtml) > 0: item = ET.Element("reference") item.set("type", "cover") item.set("title", "Cover") item.set("href", coverpagehtml) opfRoot.append(opfGuide) opfFile.write(str(Path(oebps / "content.opf")), encoding="utf-8", xml_declaration=True) # toc tocDoc = ET.ElementTree() ncx = ET.Element("ncx") ncx.set("version", "2005-1") ncx.set("xmlns", "http://www.daisy.org/z3986/2005/ncx/") tocDoc._setroot(ncx) tocHead = ET.Element("head") metaID = ET.Element("meta") metaID.set("content", "ID_UNKNOWN") metaID.set("name", "dtb:uid") tocHead.append(metaID) metaDepth = ET.Element("meta") metaDepth.set("content", str(0)) metaDepth.set("name", "dtb:depth") tocHead.append(metaDepth) metaTotal = ET.Element("meta") metaTotal.set("content", str(0)) metaTotal.set("name", "dtb:totalPageCount") tocHead.append(metaTotal) metaMax = ET.Element("meta") metaMax.set("content", str(0)) metaMax.set("name", "dtb:maxPageNumber") tocHead.append(metaDepth) ncx.append(tocHead) docTitle = ET.Element("docTitle") text = ET.Element("text") if "title" in self.configDictionary.keys(): text.text = str(self.configDictionary["title"]) else: text.text = "Comic with no Name" docTitle.append(text) ncx.append(docTitle) navmap = ET.Element("navMap") navPoint = ET.Element("navPoint") navPoint.set("id", "navPoint-1") navPoint.set("playOrder", "1") navLabel = ET.Element("navLabel") navLabelText = ET.Element("text") navLabelText.text = "Start" navLabel.append(navLabelText) navContent = ET.Element("content") navContent.set("src", os.path.relpath(htmlFiles[0], str(oebps))) navPoint.append(navLabel) navPoint.append(navContent) navmap.append(navPoint) ncx.append(navmap) tocDoc.write(str(Path(oebps / "toc.ncx")), encoding="utf-8", xml_declaration=True) self.package_epub() return True def save_out_pngs(self, sizesList): if "cropToGuides" not in self.configDictionary.keys(): self.configDictionary["cropToGuides"] = False if "pages" in self.configDictionary.keys(): if len(sizesList.keys()) < 1: print("CPMT: Export failed because there's no export methods set.") return False else: for key in sizesList.keys(): self.pagesLocationList[key] = [] path = Path(self.projectURL) exportPath = path / self.configDictionary["exportLocation"] pagesList = self.configDictionary["pages"] fileName = str(exportPath) progress = QProgressDialog("Preparing export.", str(), 0, len(pagesList)) progress.setWindowTitle("Exporting comic...") progress.setCancelButton(None) timer = QElapsedTimer() timer.start() for p in range(0, len(pagesList)): progress.setValue(p) timePassed = timer.elapsed() if (p > 0): timeEstimated = (len(pagesList) - p) * (timePassed / p) passedString = str(int(timePassed / 60000)) + ":" + format(int(timePassed / 1000), "02d") + ":" + format(timePassed % 1000, "03d") estimatedString = str(int(timeEstimated / 60000)) + ":" + format(int(timeEstimated / 1000), "02d") + ":" + format(int(timeEstimated % 1000), "03d") progress.setLabelText(str(i18n("{pages} of {pagesTotal} done. \nTime passed: {passedString}:\n Estimated:{estimated}")).format(pages=p, pagesTotal=len(pagesList), passedString=passedString, estimated=estimatedString)) url = os.path.join(self.projectURL, pagesList[p]) page = Application.openDocument(url) labelList = self.configDictionary["labelsToRemove"] root = page.rootNode() self.removeLayers(labelList, node=root) page.refreshProjection() page.flatten() while page.isIdle() is False: page.waitForDone() if page.isIdle(): # page crop for key in sizesList.keys(): w = sizesList[key] # copy over data projection = Application.createDocument(page.width(), page.height(), page.name(), page.colorModel(), page.colorDepth(), page.colorProfile()) batchsave = Application.batchmode() Application.setBatchmode(True) projection.activeNode().setPixelData(page.pixelData(0, 0, page.width(), page.height()), 0, 0, page.width(), page.height()) # crop if w["Crop"] is True: listHGuides = page.horizontalGuides() listHGuides.sort() listVGuides = page.verticalGuides() listVGuides.sort() if self.configDictionary["cropToGuides"] and len(listVGuides) > 0: cropx = listVGuides[0] cropw = listVGuides[-1] - cropx else: cropx = self.configDictionary["cropLeft"] cropw = page.width() - self.configDictionary["cropRight"] - cropx if self.configDictionary["cropToGuides"] and len(listHGuides) > 0: cropy = listHGuides[0] croph = listHGuides[-1] - cropy else: cropy = self.configDictionary["cropTop"] croph = page.height() - self.configDictionary["cropBottom"] - cropy projection.crop(cropx, cropy, cropw, croph) projection.waitForDone() # resize appropriately res = page.resolution() listScales = [projection.width(), projection.height(), res, res] sizesCalc = sizesCalculator() listScales = sizesCalc.get_scale_from_resize_config(config=w, listSizes=listScales) projection.scaleImage(listScales[0], listScales[1], listScales[2], listScales[3], "bicubic") projection.waitForDone() if key is not "TIFF": if projection.colorModel() is not "RGBA" or projection.colorModel() is not "GRAYA" or projection.colorDepth() is not "U8": projection.setColorSpace("RGBA", "U8", "sRGB built-in") projection.refreshProjection() else: if projection.colorDepth() is not "U8" or projection.colorDepth() is not "U16": projection.setColorSpace(page.colorModel(), "U16", page.colorProfile()) projection.refreshProjection() # save folderName = str(key + "-" + w["FileType"]) if Path(exportPath / folderName).exists() is False: Path.mkdir(exportPath / folderName) fn = os.path.join(str(Path(exportPath / folderName)), "page_" + format(p, "03d") + "_" + str(listScales[0]) + "x" + str(listScales[1]) + "." + w["FileType"]) if projection.isIdle(): projection.activeNode().save(fn, projection.resolution(), projection.resolution()) projection.waitForDone() self.pagesLocationList[key].append(fn) # close Application.setBatchmode(batchsave) projection.close() page.close() progress.setValue(len(pagesList)) # TODO: Check what or whether memory leaks are still caused and otherwise remove the entry below. print("CPMT: Export has finished. There are memory leaks, but the source is not obvious due wild times on git master. Last attempt to fix was august 2017") return True print("CPMT: Export not happening because there aren't any pages.") return False def removeLayers(self, labels, node): if node.colorLabel() in labels: node.remove() else: if len(node.childNodes()) > 0: for child in node.childNodes(): self.removeLayers(labels, node=child) """ Write the Advanced Comic Book Data xml file. http://acbf.wikia.com/wiki/ACBF_Specifications """ def write_acbf_meta_data(self): acbfGenreList = ["science_fiction", "fantasy", "adventure", "horror", "mystery", "crime", "military", "real_life", "superhero", "humor", "western", "manga", "politics", "caricature", "sports", "history", "biography", "education", "computer", "religion", "romance", "children", "non-fiction", "adult", "alternative", "other"] acbfAuthorRolesList = ["Writer", "Adapter", "Artist", "Penciller", "Inker", "Colorist", "Letterer", "Cover Artist", "Photographer", "Editor", "Assistant Editor", "Translator", "Other"] title = self.configDictionary["projectName"] if "title" in self.configDictionary.keys(): title = self.configDictionary["title"] location = str(os.path.join(self.projectURL, self.configDictionary["exportLocation"], "metadata", title + ".acbf")) document = ET.ElementTree() root = ET.Element("ACBF") root.set("xmlns", "http://www.fictionbook-lib.org/xml/acbf/1.0") document._setroot(root) meta = ET.Element("meta-data") bookInfo = ET.Element("book-info") if "authorList" in self.configDictionary.keys(): for authorE in range(len(self.configDictionary["authorList"])): author = ET.Element("author") authorDict = self.configDictionary["authorList"][authorE] if "first-name" in authorDict.keys(): authorN = ET.Element("first-name") authorN.text = str(authorDict["first-name"]) author.append(authorN) if "last-name" in authorDict.keys(): authorN = ET.Element("last-name") authorN.text = str(authorDict["last-name"]) author.append(authorN) if "initials" in authorDict.keys(): authorN = ET.Element("middle-name") authorN.text = str(authorDict["initials"]) author.append(authorN) if "nickname" in authorDict.keys(): authorN = ET.Element("nickname") authorN.text = str(authorDict["nickname"]) author.append(authorN) if "homepage" in authorDict.keys(): authorN = ET.Element("homepage") authorN.text = str(authorDict["homepage"]) author.append(authorN) if "email" in authorDict.keys(): authorN = ET.Element("email") authorN.text = str(authorDict["email"]) author.append(authorN) if "role" in authorDict.keys(): if str(authorDict["role"]).title() in acbfAuthorRolesList: author.set("activity", str(authorDict["role"])) bookInfo.append(author) bookTitle = ET.Element("book-title") if "title" in self.configDictionary.keys(): bookTitle.text = str(self.configDictionary["title"]) else: bookTitle.text = "Comic with no Name" bookInfo.append(bookTitle) extraGenres = [] if "genre" in self.configDictionary.keys(): for genre in self.configDictionary["genre"]: genreModified = str(genre).lower() genreModified.replace(" ", "_") if genreModified in acbfGenreList: bookGenre = ET.Element("genre") bookGenre.text = genreModified bookInfo.append(bookGenre) else: extraGenres.append(genre) annotation = ET.Element("annotation") if "summary" in self.configDictionary.keys(): paragraphList = str(self.configDictionary["summary"]).split("\n") for para in paragraphList: p = ET.Element("p") p.text = para annotation.append(p) else: p = ET.Element("p") p.text = "There was no summary upon generation of this file." annotation.append(p) bookInfo.append(annotation) if "characters" in self.configDictionary.keys(): character = ET.Element("characters") for name in self.configDictionary["characters"]: char = ET.Element("name") char.text = name character.append(char) bookInfo.append(character) keywords = ET.Element("keywords") stringKeywordsList = [] for key in extraGenres: stringKeywordsList.append(str(key)) if "otherKeywords" in self.configDictionary.keys(): for key in self.configDictionary["otherKeywords"]: stringKeywordsList.append(str(key)) if "format" in self.configDictionary.keys(): for key in self.configDictionary["format"]: stringKeywordsList.append(str(key)) keywords.text = ", ".join(stringKeywordsList) bookInfo.append(keywords) coverpageurl = "" coverpage = ET.Element("coverpage") if "pages" in self.configDictionary.keys(): if "cover" in self.configDictionary.keys(): pageList = [] pageList = self.configDictionary["pages"] coverNumber = pageList.index(self.configDictionary["cover"]) image = ET.Element("image") if len(self.pagesLocationList["CBZ"]) >= coverNumber: coverpageurl = self.pagesLocationList["CBZ"][coverNumber] image.set("href", os.path.basename(coverpageurl)) coverpage.append(image) bookInfo.append(coverpage) if "language" in self.configDictionary.keys(): language = ET.Element("languages") textlayer = ET.Element("text-layer") textlayer.set("lang", self.configDictionary["language"]) textlayer.set("show", "False") language.append(textlayer) bookInfo.append(language) #database = ET.Element("databaseref") # bookInfo.append(database) if "seriesName" in self.configDictionary.keys(): sequence = ET.Element("sequence") sequence.set("title", self.configDictionary["seriesName"]) if "seriesVolume" in self.configDictionary.keys(): sequence.set("volume", str(self.configDictionary["seriesVolume"])) if "seriesNumber" in self.configDictionary.keys(): sequence.text = str(self.configDictionary["seriesNumber"]) else: sequence.text = 0 bookInfo.append(sequence) contentrating = ET.Element("content-rating") if "rating" in self.configDictionary.keys(): contentrating.text = self.configDictionary["rating"] else: contentrating.text = "Unrated." if "ratingSystem" in self.configDictionary.keys(): contentrating.set("type", self.configDictionary["ratingSystem"]) bookInfo.append(contentrating) meta.append(bookInfo) publisherInfo = ET.Element("publish-info") if "publisherName" in self.configDictionary.keys(): publisherName = ET.Element("publisher") publisherName.text = self.configDictionary["publisherName"] publisherInfo.append(publisherName) if "publishingDate" in self.configDictionary.keys(): publishingDate = ET.Element("publish-date") publishingDate.set("value", self.configDictionary["publishingDate"]) publishingDate.text = QDate.fromString(self.configDictionary["publishingDate"], Qt.ISODate).toString(Qt.SystemLocaleLongDate) publisherInfo.append(publishingDate) if "publisherCity" in self.configDictionary.keys(): publishCity = ET.Element("city") publishCity.text = self.configDictionary["publisherCity"] publisherInfo.append(publishCity) if "isbn-number" in self.configDictionary.keys(): publishISBN = ET.Element("isbn") publishISBN.text = self.configDictionary["isbn-number"] publisherInfo.append(publishISBN) if "license" in self.configDictionary.keys(): license = self.configDictionary["license"] if license.isspace() is False and len(license) > 0: publishLicense = ET.Element("license") publishLicense.text = self.configDictionary["license"] publisherInfo.append(publishLicense) meta.append(publisherInfo) documentInfo = ET.Element("document-info") acbfAuthor = ET.Element("author") if "acbfAuthor" in self.configDictionary.keys(): acbfAuthor.text = self.configDictionary["acbfAuthor"] else: acbfAuthor.text = "Anon" documentInfo.append(acbfAuthor) acbfDate = ET.Element("creation-date") now = QDate.currentDate() acbfDate.set("value", now.toString(Qt.ISODate)) acbfDate.text = now.toString(Qt.SystemLocaleLongDate) documentInfo.append(acbfDate) acbfSource = ET.Element("source") if "acbfSource" in self.configDictionary.keys(): acbfSource.text = self.configDictionary["acbfSource"] documentInfo.append(acbfSource) acbfID = ET.Element("id") if "acbfID" in self.configDictionary.keys(): acbfID.text = self.configDictionary["acbfID"] documentInfo.append(acbfID) acbfVersion = ET.Element("version") if "acbfVersion" in self.configDictionary.keys(): acbfVersion.text = str(self.configDictionary["acbfVersion"]) documentInfo.append(acbfVersion) acbfHistory = ET.Element("history") if "acbfHistory" in self.configDictionary.keys(): for h in self.configDictionary["acbfHistory"]: p = ET.Element("p") p.text = h acbfHistory.append(p) documentInfo.append(acbfHistory) meta.append(documentInfo) root.append(meta) body = ET.Element("body") for page in self.pagesLocationList["CBZ"]: if page is not coverpageurl: pg = ET.Element("page") image = ET.Element("image") image.set("href", os.path.basename(page)) pg.append(image) body.append(pg) root.append(body) document.write(location, encoding="UTF-8", xml_declaration=True) self.acbfLocation = location return True """ Write a CoMet xml file to url """ def write_comet_meta_data(self): title = self.configDictionary["projectName"] if "title" in self.configDictionary.keys(): title = self.configDictionary["title"] location = str(os.path.join(self.projectURL, self.configDictionary["exportLocation"], "metadata", title + " CoMet.xml")) document = ET.ElementTree() root = ET.Element("comet") root.set("xmlns:comet", "http://www.denvog.com/comet/") root.set("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance") root.set("xsi:schemaLocation", "http://www.denvog.com http://www.denvog.com/comet/comet.xsd") document._setroot(root) title = ET.Element("title") if "title" in self.configDictionary.keys(): title.text = self.configDictionary["title"] else: title.text = "Untitled Comic" root.append(title) description = ET.Element("description") if "summary" in self.configDictionary.keys(): description.text = self.configDictionary["summary"] else: description.text = "There was no summary upon generation of this file." root.append(description) if "seriesName" in self.configDictionary.keys(): series = ET.Element("series") series.text = self.configDictionary["seriesName"] root.append(series) if "seriesNumber" in self.configDictionary.keys(): issue = ET.Element("issue") issue.text = str(self.configDictionary["seriesName"]) root.append(issue) if "seriesVolume" in self.configDictionary.keys(): volume = ET.Element("volume") volume.text = str(self.configDictionary["seriesVolume"]) root.append(volume) if "publisherName" in self.configDictionary.keys(): pubisher = ET.Element("publisher") pubisher.text = self.configDictionary["publisherName"] root.append(pubisher) if "publishingDate" in self.configDictionary.keys(): date = ET.Element("date") date.text = self.configDictionary["publishingDate"] root.append(date) if "genre" in self.configDictionary.keys(): for genreE in self.configDictionary["genre"]: genre = ET.Element("genre") genre.text = genreE root.append(genre) if "characters" in self.configDictionary.keys(): for char in self.configDictionary["characters"]: character = ET.Element("character") character.text = char root.append(character) if "format" in self.configDictionary.keys(): format = ET.Element("format") format.text = ",".join(self.configDictionary["format"]) root.append(format) if "language" in self.configDictionary.keys(): language = ET.Element("language") language.text = self.configDictionary["language"] root.append(language) if "rating" in self.configDictionary.keys(): rating = ET.Element("rating") rating.text = self.configDictionary["rating"] root.append(rating) #rights = ET.Element("rights") if "pages" in self.configDictionary.keys(): pages = ET.Element("pages") pages.text = str(len(self.configDictionary["pages"])) root.append(pages) if "isbn-number" in self.configDictionary.keys(): identifier = ET.Element("identifier") identifier.text = self.configDictionary["isbn-number"] root.append(identifier) if "authorList" in self.configDictionary.keys(): for authorE in range(len(self.configDictionary["authorList"])): author = ET.Element("creator") authorDict = self.configDictionary["authorList"][authorE] if "role" in authorDict.keys(): if str(authorDict["role"]).lower() in ["writer", "penciller", "editor", "assistant editor", "cover artist", "letterer", "inker", "colorist"]: if str(authorDict["role"]).lower() is "cover artist": author = ET.Element("coverDesigner") elif str(authorDict["role"]).lower() is "assistant editor": author = ET.Element("editor") else: author = ET.Element(str(authorDict["role"]).lower()) stringName = [] if "last-name" in authorDict.keys(): stringName.append(authorDict["last-name"]) if "first-name" in authorDict.keys(): stringName.append(authorDict["first-name"]) if "last-name" in authorDict.keys(): stringName.append("(" + authorDict["nickname"] + ")") author.text = ",".join(stringName) root.append(author) if "pages" in self.configDictionary.keys(): if "cover" in self.configDictionary.keys(): pageList = [] pageList = self.configDictionary["pages"] coverNumber = pageList.index(self.configDictionary["cover"]) if len(self.pagesLocationList["CBZ"]) >= coverNumber: coverImage = ET.Element("coverImage") coverImage.text = os.path.basename(self.pagesLocationList["CBZ"][coverNumber]) root.append(coverImage) readingDirection = ET.Element("readingDirection") readingDirection.text = "ltr" if "readingDirection" in self.configDictionary.keys(): if self.configDictionary["readingDirection"] is "rightToLeft": readingDirection.text = "rtl" root.append(readingDirection) document.write(location, encoding="UTF-8", xml_declaration=True) self.cometLocation = location return True """ The comicrack information is sorta... incomplete, so no idea if the following is right... I can't check in any case: It is a windows application. """ def write_comic_rack_info(self): location = str(os.path.join(self.projectURL, self.configDictionary["exportLocation"], "metadata", "ComicInfo.xml")) document = ET.ElementTree() root = ET.Element("ComicInfo") root.set("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance") root.set("xmlns:xsd", "http://www.w3.org/2001/XMLSchema") title = ET.Element("Title") if "title" in self.configDictionary.keys(): title.text = self.configDictionary["title"] else: title.text = "Untitled Comic" root.append(title) description = ET.Element("Summary") if "summary" in self.configDictionary.keys(): description.text = self.configDictionary["summary"] else: description.text = "There was no summary upon generation of this file." root.append(description) if "seriesNumber" in self.configDictionary.keys(): number = ET.Element("Number") number.text = str(self.configDictionary["seriesNumber"]) root.append(number) if "publishingDate" in self.configDictionary.keys(): date = QDate.fromString(self.configDictionary["publishingDate"], Qt.ISODate) publishYear = ET.Element("Year") publishYear.text = str(date.year()) publishMonth = ET.Element("Month") publishMonth.text = str(date.month()) root.append(publishYear) root.append(publishMonth) if "format" in self.configDictionary.keys(): for form in self.configDictionary["format"]: formattag = ET.Element("Format") formattag.text = str(form) root.append(formattag) if "otherKeywords" in self.configDictionary.keys(): tags = ET.Element("Tags") tags.text = ", ".join(self.configDictionary["otherKeywords"]) root.append(tags) if "authorList" in self.configDictionary.keys(): for authorE in range(len(self.configDictionary["authorList"])): author = ET.Element("Writer") authorDict = self.configDictionary["authorList"][authorE] if "role" in authorDict.keys(): if str(authorDict["role"]).lower() in ["writer", "penciller", "editor", "assistant editor", "cover artist", "letterer", "inker", "colorist"]: if str(authorDict["role"]).lower() is "cover artist": author = ET.Element("CoverArtist") elif str(authorDict["role"]).lower() is "assistant editor": author = ET.Element("Editor") else: author = ET.Element(str(authorDict["role"]).title()) stringName = [] if "last-name" in authorDict.keys(): stringName.append(authorDict["last-name"]) if "first-name" in authorDict.keys(): stringName.append(authorDict["first-name"]) if "last-name" in authorDict.keys(): stringName.append("(" + authorDict["nickname"] + ")") author.text = ",".join(stringName) root.append(author) if "publisherName" in self.configDictionary.keys(): publisher = ET.Element("Publisher") publisher.text = self.configDictionary["publisherName"] root.append(publisher) if "genre" in self.configDictionary.keys(): for genreE in self.configDictionary["genre"]: genre = ET.Element("Genre") genre.text = genreE root.append(genre) blackAndWhite = ET.Element("BlackAndWhite") blackAndWhite.text = "No" root.append(blackAndWhite) readingDirection = ET.Element("Manga") readingDirection.text = "No" if "readingDirection" in self.configDictionary.keys(): if self.configDictionary["readingDirection"] is "rightToLeft": readingDirection.text = "Yes" root.append(readingDirection) if "characters" in self.configDictionary.keys(): for char in self.configDictionary["characters"]: character = ET.Element("Character") character.text = char root.append(character) if "pages" in self.configDictionary.keys(): pagecount = ET.Element("PageCount") pagecount.text = str(len(self.configDictionary["pages"])) root.append(pagecount) pages = ET.Element("Pages") covernumber = 0 if "pages" in self.configDictionary.keys() and "cover" in self.configDictionary.keys(): covernumber = self.configDictionary["pages"].index(self.configDictionary["cover"]) for i in range(len(self.pagesLocationList["CBZ"])): page = ET.Element("Page") page.set("Image", str(i)) if i is covernumber: page.set("Type", "FrontCover") pages.append(page) root.append(pages) document._setroot(root) document.write(location, encoding="UTF-8", xml_declaration=True) self.comicRackInfo = location return True """ Another metadata format but then a json dump stored into the zipfile comment. Doesn't seem to be supported much. :/ https://code.google.com/archive/p/comicbookinfo/wikis/Example.wiki """ def write_comic_book_info_json(self): self.comic_book_info_json_dump = str() basedata = {} metadata = {} authorList = [] taglist = [] if "authorList" in self.configDictionary.keys(): for authorE in range(len(self.configDictionary["authorList"])): author = {} authorDict = self.configDictionary["authorList"][authorE] stringName = [] if "last-name" in authorDict.keys(): stringName.append(authorDict["last-name"]) if "first-name" in authorDict.keys(): stringName.append(authorDict["first-name"]) if "nickname" in authorDict.keys(): stringName.append("(" + authorDict["nickname"] + ")") author["person"] = ",".join(stringName) if "role" in authorDict.keys(): author["role"] = str(authorDict["role"]).title() authorList.append(author) if "characters" in self.configDictionary.keys(): for character in self.configDictionary["characters"]: taglist.append(character) if "format" in self.configDictionary.keys(): for item in self.configDictionary["format"]: taglist.append(item) if "otherKeywords" in self.configDictionary.keys(): for item in self.configDictionary["otherKeywords"]: taglist.append(item) if "seriesName" in self.configDictionary.keys(): metadata["series"] = self.configDictionary["seriesName"] if "title" in self.configDictionary.keys(): metadata["title"] = self.configDictionary["title"] else: metadata["title"] = "Unnamed comic" if "publisherName" in self.configDictionary.keys(): metadata["publisher"] = self.configDictionary["publisherName"] if "publishingDate" in self.configDictionary.keys(): date = QDate.fromString(self.configDictionary["publishingDate"], Qt.ISODate) metadata["publicationMonth"] = date.month() metadata["publicationYear"] = date.year() if "seriesNumber" in self.configDictionary.keys(): metadata["issue"] = self.configDictionary["seriesNumber"] if "seriesVolume" in self.configDictionary.keys(): metadata["volume"] = self.configDictionary["seriesVolume"] if "genre" in self.configDictionary.keys(): metadata["genre"] = self.configDictionary["genre"] if "language" in self.configDictionary.keys(): metadata["language"] = QLocale.languageToString(QLocale(self.configDictionary["language"]).language()) metadata["credits"] = authorList metadata["tags"] = taglist if "summary" in self.configDictionary.keys(): metadata["comments"] = self.configDictionary["summary"] else: metadata["comments"] = "File generated without summary" basedata["appID"] = "Krita" basedata["lastModified"] = QDateTime.currentDateTimeUtc().toString(Qt.ISODate) basedata["ComicBookInfo/1.0"] = metadata self.comic_book_info_json_dump = json.dumps(basedata) return True def package_cbz(self): title = self.configDictionary["projectName"] if "title" in self.configDictionary.keys(): title = self.configDictionary["title"] url = os.path.join(self.projectURL, self.configDictionary["exportLocation"], title + ".cbz") cbzArchive = zipfile.ZipFile(url, mode="w", compression=zipfile.ZIP_STORED) cbzArchive.write(self.acbfLocation, os.path.basename(self.acbfLocation)) cbzArchive.write(self.cometLocation, os.path.basename(self.cometLocation)) cbzArchive.write(self.comicRackInfo, os.path.basename(self.comicRackInfo)) cbzArchive.comment = self.comic_book_info_json_dump.encode("utf-8") if "CBZ" in self.pagesLocationList.keys(): for page in self.pagesLocationList["CBZ"]: if (os.path.exists(page)): cbzArchive.write(page, os.path.basename(page)) cbzArchive.close() pass def package_epub(self): title = self.configDictionary["projectName"] if "title" in self.configDictionary.keys(): title = self.configDictionary["title"] url = os.path.join(self.projectURL, self.configDictionary["exportLocation"], title) epub = os.path.join(self.projectURL, self.configDictionary["exportLocation"], "EPUB-files") shutil.make_archive(base_name=url, format="zip", root_dir=epub) shutil.move(src=str(url + ".zip"), dst=str(url + ".epub")) diff --git a/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/comics_metadata_dialog.py b/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/comics_metadata_dialog.py index 98547de977..3976722ab2 100644 --- a/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/comics_metadata_dialog.py +++ b/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/comics_metadata_dialog.py @@ -1,592 +1,592 @@ """ Part of the comics project management tools (CPMT). This is a metadata editor that helps out setting the proper metadata """ import sys import os # For finding the script location. import csv from pathlib import Path # For reading all the files in a directory. -from PyQt5.QtGui import * -from PyQt5.QtWidgets import * -from PyQt5.QtCore import * +from PyQt5.QtGui import QStandardItem, QStandardItemModel +from PyQt5.QtWidgets import QComboBox, QCompleter, QStyledItemDelegate, QLineEdit, QDialog, QDialogButtonBox, QVBoxLayout, QFormLayout, QTabWidget, QWidget, QPlainTextEdit, QHBoxLayout, QSpinBox, QDateEdit, QPushButton, QLabel, QTableView +from PyQt5.QtCore import QDir, QLocale, QStringListModel, Qt, QDate """ mult entry completer cobbled together from the two examples on stackoverflow:3779720 This allows us to let people type in comma seperated lists and get completion for those. """ class multi_entry_completer(QCompleter): punctuation = "," def __init__(self, parent=None): super(QCompleter, self).__init__(parent) def pathFromIndex(self, index): path = QCompleter.pathFromIndex(self, index) string = str(self.widget().text()) split = string.split(self.punctuation) if len(split) > 1: path = "%s, %s" % (",".join(split[:-1]), path) return path def splitPath(self, path): split = str(path.split(self.punctuation)[-1]) if split.startswith(" "): split = split[1:] if split.endswith(" "): split = split[:-1] return [split] """ Language combobox that can take locale codes and get the right language for it and visa-versa. """ class language_combo_box(QComboBox): languageList = [] codesList = [] def __init__(self, parent=None): super(QComboBox, self).__init__(parent) mainP = os.path.dirname(__file__) languageP = os.path.join(mainP, "isoLanguagesList.csv") if (os.path.exists(languageP)): file = open(languageP, "r", newline="", encoding="utf8") languageReader = csv.reader(file) for row in languageReader: self.languageList.append(row[0]) self.codesList.append(row[1]) self.addItem(row[0]) def codeForCurrentEntry(self): if self.currentText() in self.languageList: return self.codesList[self.languageList.index(self.currentText())] def setEntryToCode(self, code): if (code == "C" and "en" in self.codesList): self.setCurrentIndex(self.codesList.index("en")) if code in self.codesList: self.setCurrentIndex(self.codesList.index(code)) """ A combobox that fills up with licenses from a CSV, and also sets tooltips from that csv. """ class license_combo_box(QComboBox): def __init__(self, parent=None): super(QComboBox, self).__init__(parent) mainP = os.path.dirname(__file__) languageP = os.path.join(mainP, "LicenseList.csv") model = QStandardItemModel() if (os.path.exists(languageP)): file = open(languageP, "r", newline="", encoding="utf8") languageReader = csv.reader(file) for row in languageReader: license = QStandardItem(row[0]) license.setToolTip(row[1]) model.appendRow(license) self.setModel(model) """ Allows us to set completers on the author roles. """ class author_delegate(QStyledItemDelegate): completerStrings = [] completerColumn = 0 def __init__(self, parent=None): super(QStyledItemDelegate, self).__init__(parent) def setCompleterData(self, completerStrings, completerColumn): self.completerStrings = completerStrings self.completerColumn = completerColumn def createEditor(self, parent, option, index): editor = QLineEdit(parent) if index.column() == self.completerColumn: editor.setCompleter(QCompleter(self.completerStrings)) editor.completer().setCaseSensitivity(False) return editor """ A comic project metadata editing dialog that can take our config diactionary and set all the relevant information. To help our user, the dialog loads up lists of keywords to populate several autocompletion methods. """ class comic_meta_data_editor(QDialog): configGroup = "ComicsProjectManagementTools" def __init__(self): super().__init__() # Get the keys for the autocompletion. self.genreKeysList = [] self.characterKeysList = [] self.ratingKeysList = {} self.formatKeysList = [] self.otherKeysList = [] self.authorRoleList = [] mainP = Path(os.path.abspath(__file__)).parent self.get_auto_completion_keys(mainP) extraKeyP = Path(QDir.homePath()) / Application.readSetting(self.configGroup, "extraKeysLocation", str()) self.get_auto_completion_keys(extraKeyP) # Setup the dialog. self.setLayout(QVBoxLayout()) mainWidget = QTabWidget() self.layout().addWidget(mainWidget) self.setWindowTitle(i18n("Comic Metadata")) buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.layout().addWidget(buttons) buttons.accepted.connect(self.accept) buttons.rejected.connect(self.reject) # Title, concept, summary, genre, characters, format, rating, language, series, other keywords metadataPage = QWidget() mformLayout = QFormLayout() metadataPage.setLayout(mformLayout) self.lnTitle = QLineEdit() self.lnTitle.setToolTip(i18n("The proper title of the comic.")) self.teSummary = QPlainTextEdit() self.teSummary.setToolTip(i18n("What will you tell others to entice them to read your comic?")) self.lnGenre = QLineEdit() genreCompletion = multi_entry_completer() genreCompletion.setModel(QStringListModel(self.genreKeysList)) self.lnGenre.setCompleter(genreCompletion) genreCompletion.setCaseSensitivity(False) self.lnGenre.setToolTip(i18n("The genre of the work. Prefilled values are from the ACBF, but you can fill in your own. Seperate genres with commas. Try to limit the amount to about two or three")) self.lnCharacters = QLineEdit() characterCompletion = multi_entry_completer() characterCompletion.setModel(QStringListModel(self.characterKeysList)) characterCompletion.setCaseSensitivity(False) characterCompletion.setFilterMode(Qt.MatchContains) # So that if there is a list of names with last names, people can type in a last name. self.lnCharacters.setCompleter(characterCompletion) self.lnCharacters.setToolTip(i18n("The names of the characters that this comic revolves around. Comma seperate.")) self.lnFormat = QLineEdit() formatCompletion = multi_entry_completer() formatCompletion.setModel(QStringListModel(self.formatKeysList)) formatCompletion.setCaseSensitivity(False) self.lnFormat.setCompleter(formatCompletion) ratingLayout = QHBoxLayout() self.cmbRatingSystem = QComboBox() self.cmbRatingSystem.addItems(self.ratingKeysList.keys()) self.cmbRatingSystem.setEditable(True) self.cmbRating = QComboBox() self.cmbRating.setEditable(True) self.cmbRatingSystem.currentIndexChanged.connect(self.slot_refill_ratings) ratingLayout.addWidget(self.cmbRatingSystem) ratingLayout.addWidget(self.cmbRating) self.lnSeriesName = QLineEdit() self.lnSeriesName.setToolTip(i18n("If this is part of a series, enter the name of the series and the number.")) self.spnSeriesNumber = QSpinBox() self.spnSeriesNumber.setPrefix("No. ") self.spnSeriesVol = QSpinBox() self.spnSeriesVol.setPrefix("Vol. ") seriesLayout = QHBoxLayout() seriesLayout.addWidget(self.lnSeriesName) seriesLayout.addWidget(self.spnSeriesVol) seriesLayout.addWidget(self.spnSeriesNumber) otherCompletion = multi_entry_completer() otherCompletion.setModel(QStringListModel(self.otherKeysList)) otherCompletion.setCaseSensitivity(False) otherCompletion.setFilterMode(Qt.MatchContains) self.lnOtherKeywords = QLineEdit() self.lnOtherKeywords.setCompleter(otherCompletion) self.lnOtherKeywords.setToolTip(i18n("Other keywords that don't fit in the previously mentioned sets. As always, comma seperate")) self.cmbLanguage = language_combo_box() self.cmbReadingMode = QComboBox() self.cmbReadingMode.addItem(i18n("Left to Right")) self.cmbReadingMode.addItem(i18n("Right to Left")) self.cmbCoverPage = QComboBox() self.cmbCoverPage.setToolTip(i18n("Which page is the cover page? This will be empty if there's no pages.")) mformLayout.addRow(i18n("Title:"), self.lnTitle) mformLayout.addRow(i18n("Cover Page:"), self.cmbCoverPage) mformLayout.addRow(i18n("Summary:"), self.teSummary) mformLayout.addRow(i18n("Language:"), self.cmbLanguage) mformLayout.addRow(i18n("Reading Direction:"), self.cmbReadingMode) mformLayout.addRow(i18n("Genre:"), self.lnGenre) mformLayout.addRow(i18n("Characters:"), self.lnCharacters) mformLayout.addRow(i18n("Format:"), self.lnFormat) mformLayout.addRow(i18n("Rating:"), ratingLayout) mformLayout.addRow(i18n("Series:"), seriesLayout) mformLayout.addRow(i18n("Other:"), self.lnOtherKeywords) mainWidget.addTab(metadataPage, i18n("Work")) # The page for the authors. authorPage = QWidget() authorPage.setLayout(QVBoxLayout()) explaination = QLabel(i18n("The following is a table of the authors that contributed to this comic. You can set their nickname, proper names (first, middle, last), Role (Penciller, Inker, etc), email and homepage.")) explaination.setWordWrap(True) self.authorModel = QStandardItemModel(0, 7) labels = [i18n("Nick Name"), i18n("Given Name"), i18n("Middle Name"), i18n("Family Name"), i18n("Role"), i18n("Email"), i18n("Homepage")] self.authorModel.setHorizontalHeaderLabels(labels) self.authorTable = QTableView() self.authorTable.setModel(self.authorModel) self.authorTable.verticalHeader().setDragEnabled(True) self.authorTable.verticalHeader().setDropIndicatorShown(True) self.authorTable.verticalHeader().setSectionsMovable(True) self.authorTable.verticalHeader().sectionMoved.connect(self.slot_reset_author_row_visual) delegate = author_delegate() delegate.setCompleterData(self.authorRoleList, 4) self.authorTable.setItemDelegate(delegate) author_button_layout = QWidget() author_button_layout.setLayout(QHBoxLayout()) btn_add_author = QPushButton(i18n("Add Author")) btn_add_author.clicked.connect(self.slot_add_author) btn_remove_author = QPushButton(i18n("Remove Author")) btn_remove_author.clicked.connect(self.slot_remove_author) author_button_layout.layout().addWidget(btn_add_author) author_button_layout.layout().addWidget(btn_remove_author) authorPage.layout().addWidget(explaination) authorPage.layout().addWidget(self.authorTable) authorPage.layout().addWidget(author_button_layout) mainWidget.addTab(authorPage, i18n("Authors")) # The page with publisher information. publisherPage = QWidget() publisherLayout = QFormLayout() publisherPage.setLayout(publisherLayout) self.publisherName = QLineEdit() self.publisherName.setToolTip(i18n("The name of the company, group or person who is responsible for the final version the reader gets.")) publishDateLayout = QHBoxLayout() self.publishDate = QDateEdit() self.publishDate.setDisplayFormat(QLocale().system().dateFormat()) currentDate = QPushButton(i18n("Set Today")) currentDate.setToolTip(i18n("Sets the publish date to the current date.")) currentDate.clicked.connect(self.slot_set_date) publishDateLayout.addWidget(self.publishDate) publishDateLayout.addWidget(currentDate) self.publishCity = QLineEdit() self.publishCity.setToolTip(i18n("Traditional publishers are always mentioned in source with the city they are located.")) self.isbn = QLineEdit() self.license = license_combo_box() # Maybe ought to make this a QLineEdit... self.license.setEditable(True) self.license.completer().setCompletionMode(QCompleter.PopupCompletion) publisherLayout.addRow(i18n("Name:"), self.publisherName) publisherLayout.addRow(i18n("City:"), self.publishCity) publisherLayout.addRow(i18n("Date:"), publishDateLayout) publisherLayout.addRow(i18n("ISBN:"), self.isbn) publisherLayout.addRow(i18n("License:"), self.license) mainWidget.addTab(publisherPage, i18n("Publisher")) """ Ensure that the drag and drop of authors doesn't mess up the labels. """ def slot_reset_author_row_visual(self): headerLabelList = [] - for i in self.authorTable.verticalHeader().count(): + for i in range(self.authorTable.verticalHeader().count()): logicalI = self.authorTable.verticalHeader().logicalIndex(i) headerLabelList.append(str(logicalI + 1)) self.authorModel.setVerticalHeaderLabels(headerLabelList) """ Set the publish date to the current date. """ def slot_set_date(self): self.publishDate.setDate(QDate().currentDate()) """ Append keys to autocompletion lists from the directory mainP. """ def get_auto_completion_keys(self, mainP=Path()): genre = Path(mainP / "key_genre") characters = Path(mainP / "key_characters") rating = Path(mainP / "key_rating") format = Path(mainP / "key_format") keywords = Path(mainP / "key_other") authorRole = Path(mainP / "key_author_roles") if genre.exists(): for t in list(genre.glob('**/*.txt')): file = open(str(t), "r", errors="replace") for l in file: self.genreKeysList.append(str(l).strip("\n")) file.close() if characters.exists(): for t in list(characters.glob('**/*.txt')): file = open(str(t), "r", errors="replace") for l in file: self.characterKeysList.append(str(l).strip("\n")) file.close() if format.exists(): for t in list(format.glob('**/*.txt')): file = open(str(t), "r", errors="replace") for l in file: self.formatKeysList.append(str(l).strip("\n")) file.close() if rating.exists(): for t in list(rating.glob('**/*.csv')): file = open(str(t), "r", newline="", encoding="utf-8") ratings = csv.reader(file) title = os.path.basename(str(t)) r = 0 for row in ratings: listItem = [] if r is 0: title = row[1] else: listItem = self.ratingKeysList[title] item = [] item.append(row[0]) item.append(row[1]) listItem.append(item) self.ratingKeysList[title] = listItem r += 1 file.close() if keywords.exists(): for t in list(keywords.glob('**/*.txt')): file = open(str(t), "r", errors="replace") for l in file: self.otherKeysList.append(str(l).strip("\n")) file.close() if authorRole.exists(): for t in list(authorRole.glob('**/*.txt')): file = open(str(t), "r", errors="replace") for l in file: self.authorRoleList.append(str(l).strip("\n")) file.close() """ Refill the ratings box. This is called whenever the rating system changes. """ def slot_refill_ratings(self): if self.cmbRatingSystem.currentText() in self.ratingKeysList.keys(): self.cmbRating.clear() model = QStandardItemModel() for i in self.ratingKeysList[self.cmbRatingSystem.currentText()]: item = QStandardItem() item.setText(i[0]) item.setToolTip(i[1]) model.appendRow(item) self.cmbRating.setModel(model) """ Add an author with default values initialised. """ def slot_add_author(self): listItems = [] listItems.append(QStandardItem(i18n("Anon"))) # Nick name listItems.append(QStandardItem(i18n("John"))) # First name listItems.append(QStandardItem()) # Middle name listItems.append(QStandardItem(i18n("Doe"))) # Last name listItems.append(QStandardItem()) # role listItems.append(QStandardItem()) # email listItems.append(QStandardItem()) # homepage self.authorModel.appendRow(listItems) """ Remove the selected author from the author list. """ def slot_remove_author(self): self.authorModel.removeRow(self.authorTable.currentIndex().row()) """ Load the UI values from the config dictionary given. """ def setConfig(self, config): if "title" in config.keys(): self.lnTitle.setText(config["title"]) self.teSummary.clear() if "pages" in config.keys(): self.cmbCoverPage.clear() for page in config["pages"]: self.cmbCoverPage.addItem(page) if "cover" in config.keys(): if config["cover"] in config["pages"]: self.cmbCoverPage.setCurrentText(config["cover"]) if "summary" in config.keys(): self.teSummary.appendPlainText(config["summary"]) if "genre" in config.keys(): self.lnGenre.setText(", ".join(config["genre"])) if "characters" in config.keys(): self.lnCharacters.setText(", ".join(config["characters"])) if "format" in config.keys(): self.lnFormat.setText(", ".join(config["format"])) if "rating" in config.keys(): self.cmbRating.setCurrentText(config["rating"]) else: self.cmbRating.setCurrentText("") if "ratingSystem" in config.keys(): self.cmbRatingSystem.setCurrentText(config["ratingSystem"]) else: self.cmbRatingSystem.setCurrentText("") if "otherKeywords" in config.keys(): self.lnOtherKeywords.setText(", ".join(config["otherKeywords"])) if "seriesName" in config.keys(): self.lnSeriesName.setText(config["seriesName"]) if "seriesVolume" in config.keys(): self.spnSeriesVol.setValue(config["seriesVolume"]) if "seriesNumber" in config.keys(): self.spnSeriesNumber.setValue(config["seriesNumber"]) if "language" in config.keys(): code = config["language"] if "_" in code: code = code.split("_")[0] self.cmbLanguage.setEntryToCode(code) if "readingDirection" in config.keys(): if config["readingDirection"] is "leftToRight": self.cmbReadingMode.setCurrentIndex(0) else: self.cmbReadingMode.setCurrentIndex(1) else: self.cmbReadingMode.setCurrentIndex(QLocale(self.cmbLanguage.codeForCurrentEntry()).textDirection()) if "publisherName" in config.keys(): self.publisherName.setText(config["publisherName"]) if "publisherCity" in config.keys(): self.publishCity.setText(config["publisherCity"]) if "publishingDate" in config.keys(): self.publishDate.setDate(QDate.fromString(config["publishingDate"], Qt.ISODate)) if "isbn-number" in config.keys(): self.isbn.setText(config["isbn-number"]) if "license" in config.keys(): self.license.setCurrentText(config["license"]) else: self.license.setCurrentText("") # I would like to keep it ambiguous whether the artist has thought about the license or not. if "authorList" in config.keys(): authorList = config["authorList"] for i in range(len(authorList)): author = authorList[i] if len(author.keys()) > 0: listItems = [] authorNickName = QStandardItem() if "nickname" in author.keys(): authorNickName.setText(author["nickname"]) pass listItems.append(authorNickName) authorFirstName = QStandardItem() if "first-name" in author.keys(): authorFirstName.setText(author["first-name"]) pass listItems.append(authorFirstName) authorMiddleName = QStandardItem() if "initials" in author.keys(): authorMiddleName.setText(author["initials"]) pass listItems.append(authorMiddleName) authorLastName = QStandardItem() if "last-name" in author.keys(): authorLastName.setText(author["last-name"]) pass listItems.append(authorLastName) authorRole = QStandardItem() if "role" in author.keys(): authorRole.setText(author["role"]) pass listItems.append(authorRole) authorEMail = QStandardItem() if "email" in author.keys(): authorEMail.setText(author["email"]) pass listItems.append(authorEMail) authorHomePage = QStandardItem() if "homepage" in author.keys(): authorHomePage.setText(author["homepage"]) pass listItems.append(authorHomePage) self.authorModel.appendRow(listItems) else: self.slot_add_author() """ Store the GUI values into the config dictionary given. @return the config diactionary filled with new values. """ def getConfig(self, config): text = self.lnTitle.text() if len(text) > 0 and text.isspace() is False: config["title"] = text elif "title" in config.keys(): config.pop("title") config["cover"] = self.cmbCoverPage.currentText() listkeys = self.lnGenre.text() if len(listkeys) > 0 and listkeys.isspace() is False: config["genre"] = self.lnGenre.text().split(", ") elif "genre" in config.keys(): config.pop("genre") listkeys = self.lnCharacters.text() if len(listkeys) > 0 and listkeys.isspace() is False: config["characters"] = self.lnCharacters.text().split(", ") elif "characters" in config.keys(): config.pop("characters") listkeys = self.lnFormat.text() if len(listkeys) > 0 and listkeys.isspace() is False: config["format"] = self.lnFormat.text().split(", ") elif "format" in config.keys(): config.pop("format") config["ratingSystem"] = self.cmbRatingSystem.currentText() config["rating"] = self.cmbRating.currentText() listkeys = self.lnOtherKeywords.text() if len(listkeys) > 0 and listkeys.isspace() is False: config["otherKeywords"] = self.lnOtherKeywords.text().split(", ") elif "characters" in config.keys(): config.pop("otherKeywords") text = self.teSummary.toPlainText() if len(text) > 0 and text.isspace() is False: config["summary"] = text elif "summary" in config.keys(): config.pop("summary") if len(self.lnSeriesName.text()) > 0: config["seriesName"] = self.lnSeriesName.text() config["seriesNumber"] = self.spnSeriesNumber.value() if self.spnSeriesVol.value() > 0: config["seriesVolume"] = self.spnSeriesVol.value() config["language"] = str(self.cmbLanguage.codeForCurrentEntry()) if self.cmbReadingMode is Qt.LeftToRight: config["readingDirection"] = "leftToRight" else: config["readingDirection"] = "rightToLeft" authorList = [] for row in range(self.authorTable.verticalHeader().count()): logicalIndex = self.authorTable.verticalHeader().logicalIndex(row) listEntries = ["nickname", "first-name", "initials", "last-name", "role", "email", "homepage"] author = {} for i in range(len(listEntries)): entry = self.authorModel.data(self.authorModel.index(logicalIndex, i)) if entry is None: entry = " " if entry.isspace() is False and len(entry) > 0: author[listEntries[i]] = entry elif listEntries[i] in author.keys(): author.pop(listEntries[i]) authorList.append(author) config["authorList"] = authorList config["publisherName"] = self.publisherName.text() config["publisherCity"] = self.publishCity.text() config["publishingDate"] = self.publishDate.date().toString(Qt.ISODate) config["isbn-number"] = self.isbn.text() config["license"] = self.license.currentText() return config diff --git a/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/comics_project_manager_docker.py b/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/comics_project_manager_docker.py index 178235482b..2af3bbfa35 100644 --- a/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/comics_project_manager_docker.py +++ b/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/comics_project_manager_docker.py @@ -1,664 +1,665 @@ """ Part of the comics project management tools (CPMT). This is a docker that helps you organise your comics project. """ import sys import json import os import zipfile # quick readin gof documents import shutil import xml.etree.ElementTree as ET -from PyQt5.QtGui import * -from PyQt5.QtWidgets import * +from PyQt5.QtCore import QElapsedTimer +from PyQt5.QtGui import QStandardItem, QStandardItemModel, QImage, QIcon, QPixmap +from PyQt5.QtWidgets import QHBoxLayout, QVBoxLayout, QTableView, QToolButton, QMenu, QAction, QPushButton, QSpacerItem, QSizePolicy, QWidget, QAbstractItemView, QProgressDialog, QDialog, QFileDialog, QDialogButtonBox, qApp import math from krita import * from . import comics_metadata_dialog, comics_exporter, comics_export_dialog, comics_project_setup_wizard, comics_template_dialog, comics_project_settings_dialog, comics_project_page_viewer class comics_project_manager_docker(DockWidget): setupDictionary = {} stringName = i18n("Comics Manager") projecturl = None def __init__(self): super().__init__() self.setWindowTitle(self.stringName) # Setup layout: self.baseLayout = QHBoxLayout() widget = QWidget() widget.setLayout(self.baseLayout) self.setWidget(widget) self.buttonLayout = QVBoxLayout() self.baseLayout.addLayout(self.buttonLayout) # Comic page list and pages model self.comicPageList = QTableView() self.comicPageList.verticalHeader().setSectionsMovable(True) self.comicPageList.verticalHeader().setDragEnabled(True) self.comicPageList.verticalHeader().setDragDropMode(QAbstractItemView.InternalMove) self.comicPageList.setAcceptDrops(True) self.pagesModel = QStandardItemModel() self.comicPageList.doubleClicked.connect(self.slot_open_page) self.comicPageList.setIconSize(QSize(100, 100)) # self.comicPageList.itemDelegate().closeEditor.connect(self.slot_write_description) self.pagesModel.layoutChanged.connect(self.slot_write_config) self.pagesModel.rowsInserted.connect(self.slot_write_config) self.pagesModel.rowsRemoved.connect(self.slot_write_config) self.comicPageList.verticalHeader().sectionMoved.connect(self.slot_write_config) self.comicPageList.setModel(self.pagesModel) self.baseLayout.addWidget(self.comicPageList) self.btn_project = QToolButton() self.btn_project.setPopupMode(QToolButton.MenuButtonPopup) self.btn_project.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) menu_project = QMenu() self.action_new_project = QAction(i18n("New Project"), None) self.action_new_project.triggered.connect(self.slot_new_project) self.action_load_project = QAction(i18n("Open Project"), None) self.action_load_project.triggered.connect(self.slot_open_config) menu_project.addAction(self.action_new_project) menu_project.addAction(self.action_load_project) self.btn_project.setMenu(menu_project) self.btn_project.setDefaultAction(self.action_load_project) self.buttonLayout.addWidget(self.btn_project) # Settings dropdown with actions for the different settings menus. self.btn_settings = QToolButton() self.btn_settings.setPopupMode(QToolButton.MenuButtonPopup) self.btn_settings.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.action_edit_project_settings = QAction(i18n("Project Settings"), None) self.action_edit_project_settings.triggered.connect(self.slot_edit_project_settings) self.action_edit_meta_data = QAction(i18n("Meta Data"), None) self.action_edit_meta_data.triggered.connect(self.slot_edit_meta_data) self.action_edit_export_settings = QAction(i18n("Export Settings"), None) self.action_edit_export_settings.triggered.connect(self.slot_edit_export_settings) menu_settings = QMenu() menu_settings.addAction(self.action_edit_project_settings) menu_settings.addAction(self.action_edit_meta_data) menu_settings.addAction(self.action_edit_export_settings) self.btn_settings.setDefaultAction(self.action_edit_project_settings) self.btn_settings.setMenu(menu_settings) self.buttonLayout.addWidget(self.btn_settings) self.btn_settings.setDisabled(True) # Add page drop down with different page actions. self.btn_add_page = QToolButton() self.btn_add_page.setPopupMode(QToolButton.MenuButtonPopup) self.btn_add_page.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.action_add_page = QAction(i18n("Add Page"), None) self.action_add_page.triggered.connect(self.slot_add_new_page_single) self.action_add_template = QAction(i18n("Add Page From Template"), None) self.action_add_template.triggered.connect(self.slot_add_new_page_from_template) self.action_add_existing = QAction(i18n("Add Existing Pages"), None) self.action_add_existing.triggered.connect(self.slot_add_page_from_url) self.action_remove_selected_page = QAction(i18n("Remove Page"), None) self.action_remove_selected_page.triggered.connect(self.slot_remove_selected_page) self.action_resize_all_pages = QAction(i18n("Batch Resize"), None) self.action_resize_all_pages.triggered.connect(self.slot_batch_resize) self.btn_add_page.setDefaultAction(self.action_add_page) self.action_show_page_viewer = QAction(i18n("View Page In Window"), None) self.action_show_page_viewer.triggered.connect(self.slot_show_page_viewer) self.action_scrape_authors = QAction(i18n("Scrape Author Info"), None) self.action_scrape_authors.setToolTip(i18n("Search for author information in documents and add it to the author list. This doesn't check for duplicates.")) self.action_scrape_authors.triggered.connect(self.slot_scrape_author_list) actionList = [] menu_page = QMenu() actionList.append(self.action_add_page) actionList.append(self.action_add_template) actionList.append(self.action_add_existing) actionList.append(self.action_remove_selected_page) actionList.append(self.action_resize_all_pages) actionList.append(self.action_show_page_viewer) actionList.append(self.action_scrape_authors) menu_page.addActions(actionList) self.btn_add_page.setMenu(menu_page) self.buttonLayout.addWidget(self.btn_add_page) self.btn_add_page.setDisabled(True) self.comicPageList.setContextMenuPolicy(Qt.ActionsContextMenu) self.comicPageList.addActions(actionList) # Export button that... exports. self.btn_export = QPushButton(i18n("Export Comic")) self.btn_export.clicked.connect(self.slot_export) self.buttonLayout.addWidget(self.btn_export) self.btn_export.setDisabled(True) self.btn_project_url = QPushButton(i18n("Copy Location")) self.btn_project_url.setToolTip(i18n("Copies the path of the project to the clipboard. Useful for quickly copying to a file manager or the like.")) self.btn_project_url.clicked.connect(self.slot_copy_project_url) self.btn_project_url.setDisabled(True) self.buttonLayout.addWidget(self.btn_project_url) self.page_viewer_dialog = comics_project_page_viewer.comics_project_page_viewer() Application.notifier().imageSaved.connect(self.slot_check_for_page_update) self.buttonLayout.addItem(QSpacerItem(0, 0, QSizePolicy.Minimum, QSizePolicy.MinimumExpanding)) """ Open the config file and load the json file into a dictionary. """ def slot_open_config(self): self.path_to_config = QFileDialog.getOpenFileName(caption=i18n("Please select the json comic config file."), filter=str(i18n("json files") + "(*.json)"))[0] if os.path.exists(self.path_to_config) is True: configFile = open(self.path_to_config, "r", newline="", encoding="utf-16") self.setupDictionary = json.load(configFile) self.projecturl = os.path.dirname(str(self.path_to_config)) configFile.close() self.load_config() """ Further config loading. """ def load_config(self): self.setWindowTitle(self.stringName + ": " + str(self.setupDictionary["projectName"])) self.fill_pages() self.btn_settings.setEnabled(True) self.btn_add_page.setEnabled(True) self.btn_export.setEnabled(True) self.btn_project_url.setEnabled(True) """ Fill the pages model with the pages from the pages list. """ def fill_pages(self): self.loadingPages = True self.pagesModel.clear() self.pagesModel.setHorizontalHeaderLabels([i18n("Page"), i18n("Description")]) pagesList = [] if "pages" in self.setupDictionary.keys(): pagesList = self.setupDictionary["pages"] progress = QProgressDialog() progress.setMinimum(0) progress.setMaximum(len(pagesList)) progress.setWindowTitle(i18n("Loading pages...")) for url in pagesList: absurl = os.path.join(self.projecturl, url) if (os.path.exists(absurl)): #page = Application.openDocument(absurl) page = zipfile.ZipFile(absurl, "r") thumbnail = QImage.fromData(page.read("preview.png")) pageItem = QStandardItem() dataList = self.get_description_and_title(page.read("documentinfo.xml")) if (dataList[0].isspace() or len(dataList[0]) < 1): dataList[0] = os.path.basename(url) pageItem.setText(dataList[0]) pageItem.setDragEnabled(True) pageItem.setDropEnabled(False) pageItem.setEditable(False) pageItem.setIcon(QIcon(QPixmap.fromImage(thumbnail))) pageItem.setToolTip(url) page.close() description = QStandardItem() description.setText(dataList[1]) description.setEditable(False) listItem = [] listItem.append(pageItem) listItem.append(description) self.pagesModel.appendRow(listItem) self.comicPageList.resizeRowsToContents() self.comicPageList.resizeColumnToContents(0) self.comicPageList.setColumnWidth(1, 256) progress.setValue(progress.value() + 1) progress.setValue(len(pagesList)) self.loadingPages = False """ Function that takes the documentinfo.xml and parses it for the title, subject and abstract tags, to get the title and description. @returns a stringlist with the name on 0 and the description on 1. """ def get_description_and_title(self, string): xmlDoc = ET.fromstring(string) calligra = str("{http://www.calligra.org/DTD/document-info}") name = "" if ET.iselement(xmlDoc[0].find(calligra + 'title')): name = xmlDoc[0].find(calligra + 'title').text if name is None: name = " " desc = "" if ET.iselement(xmlDoc[0].find(calligra + 'subject')): desc = xmlDoc[0].find(calligra + 'subject').text if desc is None or desc.isspace() or len(desc) < 1: if ET.iselement(xmlDoc[0].find(calligra + 'abstract')): desc = str(xmlDoc[0].find(calligra + 'abstract').text) if desc.startswith(""): desc = desc[:-len("]]>")] return [name, desc] """ Scrapes authors from the author data in the document info and puts them into the author list. Doesn't check for duplicates. """ def slot_scrape_author_list(self): listOfAuthors = [] if "authorList" in self.setupDictionary.keys(): listOfAuthors = self.setupDictionary["authorList"] if "pages" in self.setupDictionary.keys(): for relurl in self.setupDictionary["pages"]: absurl = os.path.join(self.projecturl, relurl) page = zipfile.ZipFile(absurl, "r") xmlDoc = ET.fromstring(page.read("documentinfo.xml")) calligra = str("{http://www.calligra.org/DTD/document-info}") authorelem = xmlDoc.find(calligra + 'author') author = {} if ET.iselement(authorelem.find(calligra + 'full-name')): author["nickname"] = str(authorelem.find(calligra + 'full-name').text) if ET.iselement(authorelem.find(calligra + 'email')): author["email"] = str(authorelem.find(calligra + 'email').text) if ET.iselement(authorelem.find(calligra + 'position')): author["role"] = str(authorelem.find(calligra + 'position').text) listOfAuthors.append(author) page.close() self.setupDictionary["authorList"] = listOfAuthors """ Edit the general project settings like the project name, concept, pages location, export location, template location, metadata """ def slot_edit_project_settings(self): dialog = comics_project_settings_dialog.comics_project_details_editor(self.projecturl) dialog.setConfig(self.setupDictionary, self.projecturl) if dialog.exec_() == QDialog.Accepted: self.setupDictionary = dialog.getConfig(self.setupDictionary) self.slot_write_config() self.setWindowTitle(self.stringName + ": " + str(self.setupDictionary["projectName"])) """ This allows users to select existing pages and add them to the pages list. The pages are currently not copied to the pages folder. Useful for existing projects. """ def slot_add_page_from_url(self): # get the pages. urlList = QFileDialog.getOpenFileNames(caption=i18n("Which existing pages to add?"), directory=self.projecturl, filter=str(i18n("Krita files") + "(*.kra)"))[0] # get the existing pages list. pagesList = [] if "pages" in self.setupDictionary.keys(): pagesList = self.setupDictionary["pages"] # And add each url in the url list to the pages list and the model. for url in urlList: if self.projecturl not in urlList: newUrl = os.path.join(self.projecturl, self.setupDictionary["pagesLocation"], os.path.basename(url)) shutil.move(url, newUrl) url = newUrl relative = os.path.relpath(url, self.projecturl) if url not in pagesList: page = zipfile.ZipFile(url, "r") thumbnail = QImage.fromData(page.read("preview.png")) dataList = self.get_description_and_title(page.read("documentinfo.xml")) if (dataList[0].isspace() or len(dataList[0]) < 1): dataList[0] = os.path.basename(url) newPageItem = QStandardItem() newPageItem.setIcon(QIcon(QPixmap.fromImage(thumbnail))) newPageItem.setDragEnabled(True) newPageItem.setDropEnabled(False) newPageItem.setEditable(False) newPageItem.setText(dataList[0]) newPageItem.setToolTip(relative) page.close() description = QStandardItem() description.setText(dataList[1]) description.setEditable(False) listItem = [] listItem.append(newPageItem) listItem.append(description) self.pagesModel.appendRow(listItem) self.comicPageList.resizeRowsToContents() self.comicPageList.resizeColumnToContents(0) """ Remove the selected page from the list of pages. This does not remove it from disk(far too dangerous). """ def slot_remove_selected_page(self): index = self.comicPageList.currentIndex() self.pagesModel.removeRow(index.row()) """ This function adds a new page from the default template. If there's no default template, or the file does not exist, it will show the create/import template dialog. It will remember the selected item as the default template. """ def slot_add_new_page_single(self): templateUrl = "templatepage" templateExists = False if "singlePageTemplate" in self.setupDictionary.keys(): templateUrl = self.setupDictionary["singlePageTemplate"] if os.path.exists(os.path.join(self.projecturl, templateUrl)): templateExists = True if templateExists is False: if "templateLocation" not in self.setupDictionary.keys(): self.setupDictionary["templateLocation"] = os.path.relpath(QFileDialog.getExistingDirectory(caption=i18n("Where are the templates located?"), options=QFileDialog.ShowDirsOnly), self.projecturl) templateDir = os.path.join(self.projecturl, self.setupDictionary["templateLocation"]) template = comics_template_dialog.comics_template_dialog(templateDir) if template.exec_() == QDialog.Accepted: templateUrl = os.path.relpath(template.url(), self.projecturl) self.setupDictionary["singlePageTemplate"] = templateUrl if os.path.exists(os.path.join(self.projecturl, templateUrl)): self.add_new_page(templateUrl) """ This function always asks for a template showing the new template window. This allows users to have multiple different templates created for back covers, spreads, other and have them accesible, while still having the convenience of a singular "add page" that adds a default. """ def slot_add_new_page_from_template(self): if "templateLocation" not in self.setupDictionary.keys(): self.setupDictionary["templateLocation"] = os.path.relpath(QFileDialog.getExistingDirectory(caption=i18n("Where are the templates located?"), options=QFileDialog.ShowDirsOnly), self.projecturl) templateDir = os.path.join(self.projecturl, self.setupDictionary["templateLocation"]) template = comics_template_dialog.comics_template_dialog(templateDir) if template.exec_() == QDialog.Accepted: templateUrl = os.path.relpath(template.url(), self.projecturl) self.add_new_page(templateUrl) """ This is the actual function that adds the template using the template url. It will attempt to name the new page projectName+number, and tries to get the first possible number that is not in the pages list. If such a file already exists it will only append the file. """ def add_new_page(self, templateUrl): # check for page list and or location. pagesList = [] if "pages" in self.setupDictionary.keys(): pagesList = self.setupDictionary["pages"] if (str(self.setupDictionary["pagesLocation"]).isspace()): self.setupDictionary["pagesLocation"] = os.path.relpath(QFileDialog.getExistingDirectory(caption=i18n("Where should the pages go?"), options=QFileDialog.ShowDirsOnly), self.projecturl) # Search for the possible name. pageName = str(self.setupDictionary["projectName"]) + str(format(len(pagesList), "03d")) url = os.path.join(str(self.setupDictionary["pagesLocation"]), pageName + ".kra") pageNumber = 0 if (url in pagesList): while (url in pagesList): pageNumber += 1 pageName = str(self.setupDictionary["projectName"]) + str(format(pageNumber, "03d")) url = os.path.join(str(self.setupDictionary["pagesLocation"]), pageName + ".kra") # open the page by opening the template and resaving it, or just opening it. absoluteUrl = os.path.join(self.projecturl, url) if (os.path.exists(absoluteUrl)): newPage = Application.openDocument(absoluteUrl) else: booltemplateExists = os.path.exists(os.path.join(self.projecturl, templateUrl)) if booltemplateExists is False: templateUrl = os.path.relpath(QFileDialog.getOpenFileName(caption=i18n("Which image should be the basis the new page?"), directory=self.projecturl, filter=str(i18n("Krita files") + "(*.kra)"))[0], self.projecturl) newPage = Application.openDocument(os.path.join(self.projecturl, templateUrl)) newPage.setFileName(absoluteUrl) newPage.setName(pageName) newPage.exportImage(absoluteUrl, InfoObject()) # Get out the extra data for the standard item. newPageItem = QStandardItem() newPageItem.setIcon(QIcon(QPixmap.fromImage(newPage.thumbnail(100, 100)))) newPageItem.setDragEnabled(True) newPageItem.setDropEnabled(False) newPageItem.setEditable(False) newPageItem.setText(pageName) newPageItem.setToolTip(url) # close page document. newPage.waitForDone() if newPage.isIdle(): newPage.close() # add item to page. description = QStandardItem() description.setText(str("")) description.setEditable(False) listItem = [] listItem.append(newPageItem) listItem.append(description) self.pagesModel.appendRow(listItem) self.comicPageList.resizeRowsToContents() self.comicPageList.resizeColumnToContents(0) """ Write to the json configuratin file. This also checks the current state of the pages list. """ def slot_write_config(self): # Don't load when the pages are still being loaded, otherwise we'll be overwriting our own pages list. if (self.loadingPages is False): print("CPMT: writing comic configuration...") # Generate a pages list from the pagesmodel. # Because we made the drag-and-drop use the tableview header, we need to first request the logicalIndex # for the visualIndex, and then request the items for the logical index in the pagesmodel. # After that, we rename the verticalheader to have the appropriate numbers it will have when reloading. pagesList = [] listOfHeaderLabels = [] for i in range(self.pagesModel.rowCount()): iLogical = self.comicPageList.verticalHeader().logicalIndex(i) index = self.pagesModel.index(iLogical, 0) if index.isValid() is False: index = self.pagesModel.index(i, 0) url = str(self.pagesModel.data(index, role=Qt.ToolTipRole)) if url not in pagesList: pagesList.append(url) listOfHeaderLabels.append(str(index.row() + 1)) self.pagesModel.setVerticalHeaderLabels(listOfHeaderLabels) self.setupDictionary["pages"] = pagesList # Save to our json file. configFile = open(self.path_to_config, "w", newline="", encoding="utf-16") json.dump(self.setupDictionary, configFile, indent=4, sort_keys=True, ensure_ascii=False) configFile.close() print("CPMT: done") """ Open a page in the pagesmodel in Krita. """ def slot_open_page(self, index): if index.column() is 0: # Get the absolute url from the relative one in the pages model. absoluteUrl = os.path.join(self.projecturl, str(self.pagesModel.data(index, role=Qt.ToolTipRole))) # Make sure the page exists. if os.path.exists(absoluteUrl): page = Application.openDocument(absoluteUrl) # Set the title to the filename if it was empty. It looks a bit neater. if page.name().isspace or len(page.name()) < 1: page.setName(str(self.pagesModel.data(index, role=Qt.DisplayRole))) # Add views for the document so the user can use it. Application.activeWindow().addView(page) Application.setActiveDocument(page) else: print("CPMT: The page cannot be opened because the file doesn't exist:", absoluteUrl) """ Call up the metadata editor dialog. Only when the dialog is "Accepted" will the metadata be saved. """ def slot_edit_meta_data(self): dialog = comics_metadata_dialog.comic_meta_data_editor() dialog.setConfig(self.setupDictionary) if (dialog.exec_() == QDialog.Accepted): self.setupDictionary = dialog.getConfig(self.setupDictionary) self.slot_write_config() """ An attempt at making the description editable from the comic pages list. It is currently not working because ZipFile has no overwrite mechanism, and I don't have the energy to write one yet. """ def slot_write_description(self, index): for row in range(self.pagesModel.rowCount()): index = self.pagesModel.index(row, 1) indexUrl = self.pagesModel.index(row, 0) absoluteUrl = os.path.join(self.projecturl, str(self.pagesModel.data(indexUrl, role=Qt.ToolTipRole))) page = zipfile.ZipFile(absoluteUrl, "a") xmlDoc = ET.ElementTree() ET.register_namespace("", "http://www.calligra.org/DTD/document-info") location = os.path.join(self.projecturl, "documentinfo.xml") xmlDoc.parse(location) xmlroot = ET.fromstring(page.read("documentinfo.xml")) calligra = "{http://www.calligra.org/DTD/document-info}" aboutelem = xmlroot.find(calligra + 'about') if ET.iselement(aboutelem.find(calligra + 'subject')): desc = aboutelem.find(calligra + 'subject') desc.text = self.pagesModel.data(index, role=Qt.EditRole) xmlstring = ET.tostring(xmlroot, encoding='unicode', method='xml', short_empty_elements=False) page.writestr(zinfo_or_arcname="documentinfo.xml", data=xmlstring) for document in Application.documents(): if str(document.fileName()) == str(absoluteUrl): document.setDocumentInfo(xmlstring) page.close() """ Calls up the export settings dialog. Only when accepted will the configuration be written. """ def slot_edit_export_settings(self): dialog = comics_export_dialog.comic_export_setting_dialog() dialog.setConfig(self.setupDictionary) if (dialog.exec_() == QDialog.Accepted): self.setupDictionary = dialog.getConfig(self.setupDictionary) self.slot_write_config() """ Export the comic. Won't work without export settings set. """ def slot_export(self): exporter = comics_exporter.comicsExporter() exporter.set_config(self.setupDictionary, self.projecturl) exportSuccess = exporter.export() if exportSuccess: print("CPMT: Export success! The files have been written to the export folder!") """ Calls up the comics project setup wizard so users can create a new json file with the basic information. """ def slot_new_project(self): setup = comics_project_setup_wizard.ComicsProjectSetupWizard() setup.showDialog() def slot_check_for_page_update(self, url): if "pages" in self.setupDictionary.keys(): relUrl = os.path.relpath(url, self.projecturl) if relUrl in self.setupDictionary["pages"]: index = self.pagesModel.index(self.setupDictionary["pages"].index(relUrl), 0) index2 = self.pagesModel.index(index.row(), 1) if index.isValid(): pageItem = self.pagesModel.itemFromIndex(index) page = zipfile.ZipFile(url, "r") dataList = self.get_description_and_title(page.read("documentinfo.xml")) if (dataList[0].isspace() or len(dataList[0]) < 1): dataList[0] = os.path.basename(url) thumbnail = QImage.fromData(page.read("preview.png")) pageItem.setIcon(QIcon(QPixmap.fromImage(thumbnail))) pageItem.setText(dataList[0]) self.pagesModel.setItem(index.row(), index.column(), pageItem) self.pagesModel.setData(index2, str(dataList[1]), Qt.DisplayRole) """ Resize all the pages in the pages list. It will show a dialog with the options for resizing. Then, it will try to pop up a progress dialog while resizing. The progress dialog shows the remaining time and pages. """ def slot_batch_resize(self): dialog = QDialog() dialog.setWindowTitle(i18n("Risize all pages.")) buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) buttons.accepted.connect(dialog.accept) buttons.rejected.connect(dialog.reject) sizesBox = comics_export_dialog.comic_export_resize_widget("Scale", batch=True, fileType=False) exporterSizes = comics_exporter.sizesCalculator() dialog.setLayout(QVBoxLayout()) dialog.layout().addWidget(sizesBox) dialog.layout().addWidget(buttons) if dialog.exec_() == QDialog.Accepted: progress = QProgressDialog(i18n("Resizing pages..."), str(), 0, len(self.setupDictionary["pages"])) progress.setWindowTitle(i18n("Resizing pages.")) progress.setCancelButton(None) timer = QElapsedTimer() timer.start() config = {} config = sizesBox.get_config(config) for p in range(len(self.setupDictionary["pages"])): absoluteUrl = os.path.join(self.projecturl, self.setupDictionary["pages"][p]) progress.setValue(p) timePassed = timer.elapsed() if (p > 0): timeEstimated = (len(self.setupDictionary["pages"]) - p) * (timePassed / p) passedString = str(int(timePassed / 60000)) + ":" + format(int(timePassed / 1000), "02d") + ":" + format(timePassed % 1000, "03d") estimatedString = str(int(timeEstimated / 60000)) + ":" + format(int(timeEstimated / 1000), "02d") + ":" + format(int(timeEstimated % 1000), "03d") progress.setLabelText(str(i18n("{pages} of {pagesTotal} done. \nTime passed: {passedString}:\n Estimated:{estimated}")).format(pages=p, pagesTotal=len(self.setupDictionary["pages"]), passedString=passedString, estimated=estimatedString)) if os.path.exists(absoluteUrl): doc = Application.openDocument(absoluteUrl) listScales = exporterSizes.get_scale_from_resize_config(config["Scale"], [doc.width(), doc.height(), doc.resolution(), doc.resolution()]) doc.scaleImage(listScales[0], listScales[1], listScales[2], listScales[3], "bicubic") doc.waitForDone() doc.save() doc.waitForDone() doc.close() def slot_show_page_viewer(self): index = self.comicPageList.currentIndex() if index.column() is not 0: index = self.pagesModel.index(index.row(), 0) # Get the absolute url from the relative one in the pages model. absoluteUrl = os.path.join(self.projecturl, str(self.pagesModel.data(index, role=Qt.ToolTipRole))) # Make sure the page exists. if os.path.exists(absoluteUrl): page = zipfile.ZipFile(absoluteUrl, "r") image = QImage.fromData(page.read("mergedimage.png")) self.page_viewer_dialog.update_image(image) self.page_viewer_dialog.show() page.close() """ Function to copy the current project location into the clipboard. This is useful for users because they'll be able to use that url to quickly move to the project location in outside applications. """ def slot_copy_project_url(self): if self.projecturl is not None: clipboard = qApp.clipboard() clipboard.setText(str(self.projecturl)) """ This is required by the dockwidget class, otherwise unused. """ def canvasChanged(self, canvas): pass """ Add docker to program """ Application.addDockWidgetFactory(DockWidgetFactory("comics_project_manager_docker", DockWidgetFactoryBase.DockRight, comics_project_manager_docker)) diff --git a/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/comics_project_page_viewer.py b/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/comics_project_page_viewer.py index 825b1f887a..0fbd1161dd 100644 --- a/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/comics_project_page_viewer.py +++ b/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/comics_project_page_viewer.py @@ -1,48 +1,48 @@ """ Part of the comics project management tools (CPMT). This is a docker that shows your comic pages. """ -from PyQt5.QtGui import * -from PyQt5.QtWidgets import * -from PyQt5.QtCore import * +from PyQt5.QtGui import QImage, QPainter +from PyQt5.QtWidgets import QDialog, QWidget, QVBoxLayout, QSizePolicy +from PyQt5.QtCore import QSize, Qt from krita import * class page_viewer(QWidget): def __init__(self, parent=None, flags=None): super(page_viewer, self).__init__(parent) self.image = QImage() self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) def set_image(self, image=QImage()): self.image = image self.update() def paintEvent(self, event): painter = QPainter(self) image = self.image.scaled(self.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation) painter.drawImage(0, 0, image) def sizeHint(self): return QSize(256, 256) class comics_project_page_viewer(QDialog): currentPageNumber = 0 def __init__(self): super().__init__() self.setModal(False) self.setWindowTitle(i18n("Comics page viewer.")) self.setMinimumSize(200, 200) self.listOfImages = [QImage()] self.setLayout(QVBoxLayout()) self.viewer = page_viewer() self.layout().addWidget(self.viewer) def update_image(self, image=QImage()): self.viewer.set_image(image) diff --git a/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/comics_project_settings_dialog.py b/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/comics_project_settings_dialog.py index e147804313..0d518d4d01 100644 --- a/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/comics_project_settings_dialog.py +++ b/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/comics_project_settings_dialog.py @@ -1,172 +1,171 @@ """ Part of the comics project management tools (CPMT). A dialog for editing the general project settings. """ import os -from PyQt5.QtGui import * -from PyQt5.QtWidgets import * -from PyQt5.QtCore import * +from PyQt5.QtWidgets import QWidget, QDialog, QDialogButtonBox, QHBoxLayout, QFormLayout, QPushButton, QLabel, QLineEdit, QToolButton, QFrame, QAction, QFileDialog, QComboBox, QSizePolicy +from PyQt5.QtCore import QDir, Qt, pyqtSignal from krita import * """ A Widget that contains both a qlabel and a button for selecting a path. """ class path_select(QWidget): projectUrl = "" question = i18n("Which folder?") """ emits when a new directory has been chosen. """ locationChanged = pyqtSignal() """ Initialise the widget. @param question is the question asked when selecting a directory. @param project url is the url to which the label is relative. """ def __init__(self, parent=None, flags=None, question=str(), projectUrl=None): super(path_select, self).__init__(parent) self.setLayout(QHBoxLayout()) self.location = QLabel() self.button = QToolButton() # Until we have a proper icon self.layout().addWidget(self.location) self.layout().addWidget(self.button) self.layout().setContentsMargins(0, 0, 0, 0) self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum) self.location.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken) self.button.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.location.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum) self.location.setAlignment(Qt.AlignRight) self.location.setLineWidth(1) if projectUrl is None: self.projectUrl = QDir.homePath() else: self.projectUrl = projectUrl self.question = question self.action_change_folder = QAction(i18n("Change Folder")) self.action_change_folder.setIconText("...") self.action_change_folder.triggered.connect(self.slot_change_location) self.button.setDefaultAction(self.action_change_folder) """ pops up a directory chooser widget, and when a directory is chosen a locationChanged signal is emited. """ def slot_change_location(self): location = QFileDialog.getExistingDirectory(caption=self.question, directory=self.projectUrl) if location is not None and location.isspace() is False and len(location) > 0: location = os.path.relpath(location, self.projectUrl) self.location.setText(location) self.locationChanged.emit() """ Set the location. @param path - the location relative to the projectUrl. """ def setLocation(self, path=str()): self.location.setText(path) """ Get the location. @returns a string with the location relative to the projectUrl. """ def getLocation(self): return str(self.location.text()) """ Dialog for editing basic proect details like the project name, default template, template location, etc. """ class comics_project_details_editor(QDialog): configGroup = "ComicsProjectManagementTools" """ Initialise the editor. @param projectUrl - The directory to which all paths are relative. """ def __init__(self, projectUrl=str()): super().__init__() self.projectUrl = projectUrl layout = QFormLayout() self.setLayout(layout) self.setWindowTitle(i18n("Comic Project Settings")) buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) buttons.accepted.connect(self.accept) buttons.rejected.connect(self.reject) self.lnProjectName = QLineEdit() self.lnProjectConcept = QLineEdit() self.cmb_defaultTemplate = QComboBox() self.pagesLocation = path_select(question=i18n("Where should the pages go?"), projectUrl=self.projectUrl) self.exportLocation = path_select(question=i18n("Where should the export go?"), projectUrl=self.projectUrl) self.templateLocation = path_select(question=i18n("Where are the templates?"), projectUrl=self.projectUrl) self.keyLocation = path_select(question=i18n("Where are the extra auto-completion keys located?")) self.keyLocation.setToolTip(i18n("The location for extra autocompletion keys in the meta-data editor. Point this at a folder containing key_characters/key_format/key_genre/key_rating/key_author_roles/key_other with inside txt files(csv for tating) containing the extra auto-completion keys, each on a new line. This path is stored in the krita configuration, and not the project configuration.")) self.templateLocation.locationChanged.connect(self.refill_templates) layout.addRow(i18n("Project Name:"), self.lnProjectName) layout.addRow(i18n("Project Concept:"), self.lnProjectConcept) layout.addRow(i18n("Pages Folder:"), self.pagesLocation) layout.addRow(i18n("Export Folder:"), self.exportLocation) layout.addRow(i18n("Template Folder:"), self.templateLocation) layout.addRow(i18n("Default Template:"), self.cmb_defaultTemplate) layout.addRow(i18n("Extra Keys Folder:"), self.keyLocation) self.layout().addWidget(buttons) """ Fill the templates doc with the kra files found in the templates directory. Might want to extend this to other files as well, as they basically get resaved anyway... """ def refill_templates(self): self.cmb_defaultTemplate.clear() templateLocation = os.path.join(self.projectUrl, self.templateLocation.getLocation()) for entry in os.scandir(templateLocation): if entry.name.endswith('.kra') and entry.is_file(): name = os.path.relpath(entry.path, templateLocation) self.cmb_defaultTemplate.addItem(name) """ Load the UI values from the config dictionary given. """ def setConfig(self, config, projectUrl): self.projectUrl = projectUrl if "projectName"in config.keys(): self.lnProjectName.setText(config["projectName"]) if "concept"in config.keys(): self.lnProjectConcept.setText(config["concept"]) if "pagesLocation" in config.keys(): self.pagesLocation.setLocation(config["pagesLocation"]) if "exportLocation" in config.keys(): self.exportLocation.setLocation(config["exportLocation"]) if "templateLocation" in config.keys(): self.templateLocation.setLocation(config["templateLocation"]) self.refill_templates() self.keyLocation.setLocation(Application.readSetting(self.configGroup, "extraKeysLocation", str())) """ Store the GUI values into the config dictionary given. @return the config diactionary filled with new values. """ def getConfig(self, config): config["projectName"] = self.lnProjectName.text() config["concept"] = self.lnProjectConcept.text() config["pagesLocation"] = self.pagesLocation.getLocation() config["exportLocation"] = self.exportLocation.getLocation() config["templateLocation"] = self.templateLocation.getLocation() config["singlePageTemplate"] = os.path.join(self.templateLocation.getLocation(), self.cmb_defaultTemplate.currentText()) Application.writeSetting(self.configGroup, "extraKeysLocation", self.keyLocation.getLocation()) return config diff --git a/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/comics_project_setup_wizard.py b/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/comics_project_setup_wizard.py index faa680bbdf..c86f7f6411 100644 --- a/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/comics_project_setup_wizard.py +++ b/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/comics_project_setup_wizard.py @@ -1,177 +1,176 @@ """ Part of the comics project management tools (CPMT). This is a wizard that helps you set up a comics project in Krita. """ -import sys + import json # For writing to json. import os # For finding the script location. from pathlib import Path # For reading all the files in a directory. import random # For selecting two random words from a list. -from PyQt5.QtGui import * -from PyQt5.QtWidgets import * -from PyQt5.QtCore import * +from PyQt5.QtWidgets import QWidget, QWizard, QWizardPage, QHBoxLayout, QFormLayout, QFileDialog, QLineEdit, QPushButton, QCheckBox, QLabel, QDialog +from PyQt5.QtCore import QDate, QLocale from krita import * from . import comics_metadata_dialog """ The actual wizard. """ class ComicsProjectSetupWizard(): setupDictionary = {} projectDirectory = "" def __init__(self): # super().__init__(parent) # Search the location of the script for the two lists that are used with the projectname generator. mainP = Path(__file__).parent self.generateListA = [] self.generateListB = [] if Path(mainP / "projectGenLists" / "listA.txt").exists(): for l in open(str(mainP / "projectGenLists" / "listA.txt"), "r"): if l.isspace() == False: self.generateListA.append(l.strip("\n")) if Path(mainP / "projectGenLists" / "listB.txt").exists(): for l in open(str(mainP / "projectGenLists" / "listB.txt"), "r"): if l.isspace() == False: self.generateListB.append(l.strip("\n")) def showDialog(self): # Initialise the setup directory empty toavoid exceptions. self.setupDictionary = {} # ask for a project directory. self.projectDirectory = QFileDialog.getExistingDirectory(caption=i18n("Where should the comic project go?"), options=QFileDialog.ShowDirsOnly) if os.path.exists(self.projectDirectory) is False: return self.pagesDirectory = os.path.relpath(self.projectDirectory, self.projectDirectory) self.exportDirectory = os.path.relpath(self.projectDirectory, self.projectDirectory) wizard = QWizard() wizard.setWindowTitle(i18n("Comic Project Setup")) wizard.setOption(QWizard.IndependentPages, True) # Set up the UI for the wizard basicsPage = QWizardPage() basicsPage.setTitle(i18n("Basic Comic Project Settings")) formLayout = QFormLayout() basicsPage.setLayout(formLayout) projectLayout = QHBoxLayout() self.lnProjectName = QLineEdit() basicsPage.registerField("Project Name*", self.lnProjectName) self.lnProjectName.setToolTip(i18n("A Project name. This can be different from the eventual title")) btnRandom = QPushButton() btnRandom.setText(i18n("Generate")) btnRandom.setToolTip(i18n("If you cannot come up with a project name, our highly sophisticated project name generator will serve to give a classy yet down to earth name.")) btnRandom.clicked.connect(self.slot_generate) projectLayout.addWidget(self.lnProjectName) projectLayout.addWidget(btnRandom) lnConcept = QLineEdit() lnConcept.setToolTip(i18n("What is your comic about? This is mostly for your own convenience so don't worry about what it says too much.")) self.cmbLanguage = comics_metadata_dialog.language_combo_box() self.cmbLanguage.setToolTip(i18n("The main language the comic is in")) self.cmbLanguage.setEntryToCode(str(QLocale.system().name()).split("_")[0]) self.lnProjectDirectory = QLabel(self.projectDirectory) self.chkMakeProjectDirectory = QCheckBox(i18n("Make a new directory with the project name.")) self.chkMakeProjectDirectory.setToolTip(i18n("This allows you to select a generic comics project directory, in which a new folder will be made for the project using the given project name.")) self.chkMakeProjectDirectory.setChecked(True) self.lnPagesDirectory = QLineEdit() self.lnPagesDirectory.setText(i18n("pages")) self.lnPagesDirectory.setToolTip(i18n("The name for the folder where the pages are contained. If it doesn't exist, it will be created.")) self.lnExportDirectory = QLineEdit() self.lnExportDirectory.setText(i18n("export")) self.lnExportDirectory.setToolTip(i18n("The name for the folder where the export is put. If it doesn't exist, it will be created.")) self.lnTemplateLocation = QLineEdit() self.lnTemplateLocation.setText(i18n("templates")) self.lnTemplateLocation.setToolTip(i18n("The name for the folder where the page templates are sought in.")) formLayout.addRow(i18n("Comic Concept:"), lnConcept) formLayout.addRow(i18n("Project Name:"), projectLayout) formLayout.addRow(i18n("Main Language:"), self.cmbLanguage) buttonMetaData = QPushButton(i18n("Meta Data")) buttonMetaData.clicked.connect(self.slot_edit_meta_data) wizard.addPage(basicsPage) foldersPage = QWizardPage() foldersPage.setTitle(i18n("Folder names and other.")) folderFormLayout = QFormLayout() foldersPage.setLayout(folderFormLayout) folderFormLayout.addRow(i18n("Project Directory:"), self.lnProjectDirectory) folderFormLayout.addRow("", self.chkMakeProjectDirectory) folderFormLayout.addRow(i18n("Pages Directory"), self.lnPagesDirectory) folderFormLayout.addRow(i18n("Export Directory"), self.lnExportDirectory) folderFormLayout.addRow(i18n("Template Directory"), self.lnTemplateLocation) folderFormLayout.addRow("", buttonMetaData) wizard.addPage(foldersPage) # Execute the wizard, and after wards... if (wizard.exec_()): # First get the directories, check if the directories exist, and oterwise make them. self.pagesDirectory = self.lnPagesDirectory.text() self.exportDirectory = self.lnExportDirectory.text() self.templateLocation = self.lnTemplateLocation.text() projectPath = Path(self.projectDirectory) # Only make a project directory if the checkbox for that has been checked. if self.chkMakeProjectDirectory.isChecked(): projectPath = projectPath / self.lnProjectName.text() if projectPath.exists() is False: projectPath.mkdir() self.projectDirectory = str(projectPath) if Path(projectPath / self.pagesDirectory).exists() is False: Path(projectPath / self.pagesDirectory).mkdir() if Path(projectPath / self.exportDirectory).exists() is False: Path(projectPath / self.exportDirectory).mkdir() if Path(projectPath / self.templateLocation).exists() is False: Path(projectPath / self.templateLocation).mkdir() # Then store the information into the setup diactionary. self.setupDictionary["projectName"] = self.lnProjectName.text() self.setupDictionary["concept"] = lnConcept.text() self.setupDictionary["language"] = str(self.cmbLanguage.codeForCurrentEntry()) self.setupDictionary["pagesLocation"] = self.pagesDirectory self.setupDictionary["exportLocation"] = self.exportDirectory self.setupDictionary["templateLocation"] = self.templateLocation # Finally, write the dictionary into the json file. self.writeConfig() """ This calls up the metadata dialog, for if people already have information they want to type in at the setup stage. Not super likely, but the organisation and management aspect of the comic manager means we should give the option to organise as smoothly as possible. """ def slot_edit_meta_data(self): dialog = comics_metadata_dialog.comic_meta_data_editor() self.setupDictionary["language"] = str(self.cmbLanguage.codeForCurrentEntry()) dialog.setConfig(self.setupDictionary) dialog.setConfig(self.setupDictionary) if (dialog.exec_() == QDialog.Accepted): self.setupDictionary = dialog.getConfig(self.setupDictionary) self.cmbLanguage.setEntryToCode(self.setupDictionary["language"]) """ Write the actual config to the chosen project directory. """ def writeConfig(self): print("CPMT: writing comic configuration...") print(self.projectDirectory) configFile = open(os.path.join(self.projectDirectory, "comicConfig.json"), "w", newline="", encoding="utf-16") json.dump(self.setupDictionary, configFile, indent=4, sort_keys=True, ensure_ascii=False) configFile.close() print("CPMT: done") """ As you may be able to tell, the random projectname generator is hardly sophisticated. It picks a word from a list of names of figures from Greek Mythology, and a name from a list of vegetables and fruits and combines the two camelcased. It makes for good codenames at the least. """ def slot_generate(self): if len(self.generateListA) > 0 and len(self.generateListB) > 0: nameA = self.generateListA[random.randint(0, len(self.generateListA) - 1)] nameB = self.generateListB[random.randint(0, len(self.generateListB) - 1)] self.lnProjectName.setText(str(nameA.title() + nameB.title())) diff --git a/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/comics_template_dialog.py b/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/comics_template_dialog.py index 433c637940..ef45a9c8af 100644 --- a/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/comics_template_dialog.py +++ b/plugins/extensions/pykrita/plugin/plugins/comics_project_management_tools/comics_template_dialog.py @@ -1,283 +1,283 @@ """ Part of the comics project management tools (CPMT). Template dialog """ import os import shutil -from PyQt5.QtGui import * -from PyQt5.QtWidgets import * -from PyQt5.QtCore import * +#from PyQt5.QtGui import * +from PyQt5.QtWidgets import QDialog, QComboBox, QDialogButtonBox, QVBoxLayout, QFormLayout, QGridLayout, QWidget, QPushButton, QHBoxLayout, QLabel, QSpinBox, QDoubleSpinBox, QLineEdit, QTabWidget +from PyQt5.QtCore import QLocale, Qt, QByteArray from krita import * """ Quick and dirty QComboBox subclassing that handles unitconversion for us. """ class simpleUnitBox(QComboBox): pixels = i18n("Pixels") inches = i18n("Inches") centimeter = i18n("Centimeter") millimeter = i18n("millimeter") def __init__(self): super(simpleUnitBox, self).__init__() self.addItem(self.pixels) self.addItem(self.inches) self.addItem(self.centimeter) self.addItem(self.millimeter) if QLocale().system().measurementSystem() is QLocale.MetricSystem: self.setCurrentIndex(2) # set to centimeter if metric system. else: self.setCurrentIndex(1) def pixelsForUnit(self, unit, DPI): if (self.currentText() == self.pixels): return unit elif (self.currentText() == self.inches): return self.inchesToPixels(unit, DPI) elif (self.currentText() == self.centimeter): return self.centimeterToPixels(unit, DPI) elif (self.currentText() == self.millimeter): return self.millimeterToPixels(unit, DPI) def inchesToPixels(self, inches, DPI): return DPI * inches def centimeterToInches(self, cm): return cm / 2.54 def centimeterToPixels(self, cm, DPI): return self.inchesToPixels(self.centimeterToInches(cm), DPI) def millimeterToCentimeter(self, mm): return mm / 10 def millimeterToPixels(self, mm, DPI): return self.inchesToPixels(self.centimeterToInches(self.millimeterToCentimeter(mm)), DPI) class comics_template_dialog(QDialog): templateDirectory = str() templates = QComboBox() buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) def __init__(self, templateDirectory): super().__init__() self.templateDirectory = templateDirectory self.setWindowTitle(i18n("Add new template")) self.setLayout(QVBoxLayout()) self.templates.setEnabled(False) self.fill_templates() self.buttons.accepted.connect(self.accept) self.buttons.rejected.connect(self.reject) self.buttons.button(QDialogButtonBox.Ok).setEnabled(False) mainWidget = QWidget() self.layout().addWidget(mainWidget) self.layout().addWidget(self.buttons) mainWidget.setLayout(QVBoxLayout()) btn_create = QPushButton(i18n("Create a template")) btn_create.clicked.connect(self.slot_create_template) btn_import = QPushButton(i18n("Import templates")) btn_import.clicked.connect(self.slot_import_template) mainWidget.layout().addWidget(self.templates) mainWidget.layout().addWidget(btn_create) mainWidget.layout().addWidget(btn_import) def fill_templates(self): self.templates.clear() for entry in os.scandir(self.templateDirectory): if entry.name.endswith('.kra') and entry.is_file(): name = os.path.relpath(entry.path, self.templateDirectory) self.templates.addItem(name) if self.templates.model().rowCount() > 0: self.templates.setEnabled(True) self.buttons.button(QDialogButtonBox.Ok).setEnabled(True) def slot_create_template(self): create = comics_template_create(self.templateDirectory) if create.exec_() == QDialog.Accepted: if (create.prepare_krita_file()): self.fill_templates() def slot_import_template(self): filenames = QFileDialog.getOpenFileNames(caption=i18n("Which files should be added to the template folder?"), directory=self.templateDirectory, filter=str(i18n("Krita files") + "(*.kra)"))[0] for file in filenames: shutil.copy2(file, self.templateDirectory) self.fill_templates() def url(self): return os.path.join(self.templateDirectory, self.templates.currentText()) class comics_template_create(QDialog): urlSavedTemplate = str() templateDirectory = str() def __init__(self, templateDirectory): super().__init__() self.templateDirectory = templateDirectory self.setWindowTitle(i18n("Create new template")) self.setLayout(QVBoxLayout()) buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) buttons.accepted.connect(self.accept) buttons.rejected.connect(self.reject) mainWidget = QWidget() self.layout().addWidget(mainWidget) self.layout().addWidget(buttons) mainWidget.setLayout(QHBoxLayout()) explanation = QLabel(i18n("This allows you to make a template document with guides.\nThe width and height are the size of the live-area, the safe area is the live area minus the margins, and the full image is the live area plus the bleeds.")) explanation.setWordWrap(True) elements = QWidget() elements.setLayout(QVBoxLayout()) mainWidget.layout().addWidget(elements) elements.layout().addWidget(explanation) self.templateName = QLineEdit() elements.layout().addWidget(self.templateName) self.DPI = QSpinBox() self.DPI.setMaximum(1200) self.DPI.setValue(300) self.spn_width = QDoubleSpinBox() self.spn_width.setMaximum(10000) self.spn_height = QDoubleSpinBox() self.spn_height.setMaximum(10000) self.widthUnit = simpleUnitBox() self.heightUnit = simpleUnitBox() widgetSize = QWidget() sizeForm = QFormLayout() sizeForm.addRow(i18n("DPI:"), self.DPI) widthLayout = QHBoxLayout() widthLayout.addWidget(self.spn_width) widthLayout.addWidget(self.widthUnit) sizeForm.addRow(i18n("Width:"), widthLayout) heightLayout = QHBoxLayout() heightLayout.addWidget(self.spn_height) heightLayout.addWidget(self.heightUnit) sizeForm.addRow(i18n("Height:"), heightLayout) widgetSize.setLayout(sizeForm) elements.layout().addWidget(widgetSize) marginAndBleed = QTabWidget() elements.layout().addWidget(marginAndBleed) margins = QWidget() marginForm = QGridLayout() margins.setLayout(marginForm) self.marginLeft = QDoubleSpinBox() self.marginLeft.setMaximum(1000) self.marginLeftUnit = simpleUnitBox() self.marginRight = QDoubleSpinBox() self.marginRight.setMaximum(1000) self.marginRightUnit = simpleUnitBox() self.marginTop = QDoubleSpinBox() self.marginTop.setMaximum(1000) self.marginTopUnit = simpleUnitBox() self.marginBottom = QDoubleSpinBox() self.marginBottom.setMaximum(1000) self.marginBottomUnit = simpleUnitBox() marginForm.addWidget(QLabel(i18n("Left:")), 0, 0, Qt.AlignRight) marginForm.addWidget(self.marginLeft, 0, 1) marginForm.addWidget(self.marginLeftUnit, 0, 2) marginForm.addWidget(QLabel(i18n("Top:")), 1, 0, Qt.AlignRight) marginForm.addWidget(self.marginTop, 1, 1) marginForm.addWidget(self.marginTopUnit, 1, 2) marginForm.addWidget(QLabel(i18n("Right:")), 2, 0, Qt.AlignRight) marginForm.addWidget(self.marginRight, 2, 1) marginForm.addWidget(self.marginRightUnit, 2, 2) marginForm.addWidget(QLabel(i18n("Bottom:")), 3, 0, Qt.AlignRight) marginForm.addWidget(self.marginBottom, 3, 1) marginForm.addWidget(self.marginBottomUnit, 3, 2) marginAndBleed.addTab(margins, i18n("Margins")) bleeds = QWidget() bleedsForm = QGridLayout() bleeds.setLayout(bleedsForm) self.bleedLeft = QDoubleSpinBox() self.bleedLeft.setMaximum(1000) self.bleedLeftUnit = simpleUnitBox() self.bleedRight = QDoubleSpinBox() self.bleedRight.setMaximum(1000) self.bleedRightUnit = simpleUnitBox() self.bleedTop = QDoubleSpinBox() self.bleedTop.setMaximum(1000) self.bleedTopUnit = simpleUnitBox() self.bleedBottom = QDoubleSpinBox() self.bleedBottom.setMaximum(1000) self.bleedBottomUnit = simpleUnitBox() bleedsForm.addWidget(QLabel(i18n("Left:")), 0, 0, Qt.AlignRight) bleedsForm.addWidget(self.bleedLeft, 0, 1) bleedsForm.addWidget(self.bleedLeftUnit, 0, 2) bleedsForm.addWidget(QLabel(i18n("Top:")), 1, 0, Qt.AlignRight) bleedsForm.addWidget(self.bleedTop, 1, 1) bleedsForm.addWidget(self.bleedTopUnit, 1, 2) bleedsForm.addWidget(QLabel(i18n("Right:")), 2, 0, Qt.AlignRight) bleedsForm.addWidget(self.bleedRight, 2, 1) bleedsForm.addWidget(self.bleedRightUnit, 2, 2) bleedsForm.addWidget(QLabel(i18n("Bottom:")), 3, 0, Qt.AlignRight) bleedsForm.addWidget(self.bleedBottom, 3, 1) bleedsForm.addWidget(self.bleedBottomUnit, 3, 2) marginAndBleed.addTab(bleeds, i18n("Bleeds")) def prepare_krita_file(self): wBase = self.widthUnit.pixelsForUnit(self.spn_width.value(), self.DPI.value()) bL = self.bleedLeftUnit.pixelsForUnit(self.bleedLeft.value(), self.DPI.value()) bR = self.bleedRightUnit.pixelsForUnit(self.bleedRight.value(), self.DPI.value()) mL = self.marginLeftUnit.pixelsForUnit(self.marginLeft.value(), self.DPI.value()) mR = self.marginRightUnit.pixelsForUnit(self.marginRight.value(), self.DPI.value()) hBase = self.heightUnit.pixelsForUnit(self.spn_height.value(), self.DPI.value()) bT = self.bleedTopUnit.pixelsForUnit(self.bleedTop.value(), self.DPI.value()) bB = self.bleedBottomUnit.pixelsForUnit(self.bleedBottom.value(), self.DPI.value()) mT = self.marginTopUnit.pixelsForUnit(self.marginTop.value(), self.DPI.value()) mB = self.marginBottomUnit.pixelsForUnit(self.marginBottom.value(), self.DPI.value()) template = Application.createDocument((wBase + bL + bR), (hBase + bT + bB), self.templateName.text(), "RGBA", "U8", "sRGB built-in") template.setResolution(self.DPI.value()) backgroundNode = template.activeNode() backgroundNode.setName(i18n("Background")) pixelByteArray = QByteArray() pixelByteArray = backgroundNode.pixelData(0, 0, (wBase + bL + bR), (hBase + bT + bB)) white = int(255) pixelByteArray.fill(white.to_bytes(1, byteorder='little')) backgroundNode.setPixelData(pixelByteArray, 0, 0, (wBase + bL + bR), (hBase + bT + bB)) backgroundNode.setLocked(True) sketchNode = template.createNode(i18n("Sketch"), "paintlayer") template.rootNode().setChildNodes([backgroundNode, sketchNode]) verticalGuides = [] verticalGuides.append(bL) verticalGuides.append(bL + mL) verticalGuides.append((bL + wBase) - mR) verticalGuides.append(bL + wBase) horizontalGuides = [] horizontalGuides.append(bT) horizontalGuides.append(bT + mT) horizontalGuides.append((bT + hBase) - mB) horizontalGuides.append(bT + hBase) template.setHorizontalGuides(horizontalGuides) template.setVerticalGuides(verticalGuides) template.setGuidesVisible(True) template.setGuidesLocked(True) self.urlSavedTemplate = os.path.join(self.templateDirectory, self.templateName.text() + ".kra") succes = template.exportImage(self.urlSavedTemplate, InfoObject()) print("CPMT: Template", self.templateName.text(), "made and saved.") template.waitForDone() template.close() return succes def url(self): return self.urlSavedTemplate