[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-commits
Subject:    [krita/krita/4.1] plugins/python/comics_project_management_tools: Fix several issues with the EPUB m
From:       Boudewijn Rempt <null () kde ! org>
Date:       2018-09-07 14:15:55
Message-ID: E1fyHYB-0003Xd-OD () code ! kde ! org
[Download RAW message or body]

Git commit e8fc7211894132621926d80df9ba54359c752231 by Boudewijn Rempt, on behalf of Wolthera van Hövell \
tot Westerflier. Committed on 07/09/2018 at 12:49.
Pushed by rempt into branch 'krita/4.1'.

Fix several issues with the EPUB metadata export.

* Add MARC-relators for use with the 'refines'.
* Add Series definitions and series number.
* Add UUID sharing between acbf and epub.
* Add a modiied and proper date stuff.

M  +5    -9    plugins/python/comics_project_management_tools/comics_export_dialog.py
M  +25   -1    plugins/python/comics_project_management_tools/comics_metadata_dialog.py
M  +10   -0    plugins/python/comics_project_management_tools/comics_project_manager_docker.py
M  +2    -1    plugins/python/comics_project_management_tools/comics_project_setup_wizard.py
M  +88   -8    plugins/python/comics_project_management_tools/exporters/CPMT_EPUB_exporter.py

https://commits.kde.org/krita/e8fc7211894132621926d80df9ba54359c752231

diff --git a/plugins/python/comics_project_management_tools/comics_export_dialog.py \
b/plugins/python/comics_project_management_tools/comics_export_dialog.py index c62d64149bb..b821644d378 \
                100644
--- a/plugins/python/comics_project_management_tools/comics_export_dialog.py
+++ b/plugins/python/comics_project_management_tools/comics_export_dialog.py
@@ -294,8 +294,6 @@ class comic_export_setting_dialog(QDialog):
         ACBFdocInfo = QGroupBox()
         ACBFdocInfo.setTitle(i18n("ACBF Document Info"))
         ACBFdocInfo.setLayout(ACBFform)
-        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() @@ -309,7 +307,6 @@ class comic_export_setting_dialog(QDialog):
         self.chkIncludeTranslatorComments.setToolTip(i18n("A PO file can contain translator's comments. \
If this is checked, the translations comments will be added as references into the ACBF file."))  \
self.lnTranslatorHeader = QLineEdit()  
-        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)
@@ -627,12 +624,13 @@ class comic_export_setting_dialog(QDialog):
                     listItems.append(QStandardItem())  # First name
                 self.ACBFauthorModel.appendRow(listItems)
 
-        if "acbfSource" in config.keys():
-            self.lnACBFSource.setText(config["acbfSource"])
-        if "acbfID" in config.keys():
+        if "uuid" in config.keys():
+            self.lnACBFID.setText(config["uuid"])
+        elif "acbfID" in config.keys():
             self.lnACBFID.setText(config["acbfID"])
         else:
-            self.lnACBFID.setText(QUuid.createUuid().toString())
+            config["uuid"] = QUuid.createUuid().toString()
+            self.lnACBFID.setText(config["uuid"])
         if "acbfVersion" in config.keys():
             self.spnACBFVersion.setValue(config["acbfVersion"])
         if "acbfHistory" in config.keys():
@@ -726,8 +724,6 @@ class comic_export_setting_dialog(QDialog):
                     author.pop(listEntries[i])
             authorList.append(author)
         config["acbfAuthor"] = authorList
-        config["acbfSource"] = self.lnACBFSource.text()
-        config["acbfID"] = self.lnACBFID.text()
         config["acbfVersion"] = self.spnACBFVersion.value()
         versionList = []
         for r in range(self.ACBFhistoryModel.rowCount()):
diff --git a/plugins/python/comics_project_management_tools/comics_metadata_dialog.py \
b/plugins/python/comics_project_management_tools/comics_metadata_dialog.py index d1cf198a697..0cd609b2f99 \
                100644
--- a/plugins/python/comics_project_management_tools/comics_metadata_dialog.py
+++ b/plugins/python/comics_project_management_tools/comics_metadata_dialog.py
@@ -28,7 +28,7 @@ import types
 from pathlib import Path  # For reading all the files in a directory.
 from PyQt5.QtGui import QStandardItem, QStandardItemModel, QImage, QIcon, QPixmap, QPainter, QPalette, \
QFontDatabase  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, QSize
+from PyQt5.QtCore import QDir, QLocale, QStringListModel, Qt, QDate, QSize, QUuid
 """
 multi entry completer cobbled together from the two examples on stackoverflow:3779720
 
@@ -397,6 +397,10 @@ class comic_meta_data_editor(QDialog):
         self.cmb_entry_type = QComboBox()
         self.cmb_entry_type.addItems(["IssueID", "SeriesID", "URL"])
         self.cmb_entry_type.setEditable(True)
+        self.ln_source = QLineEdit()
+        self.ln_source.setToolTip(i18n("Whether the comic 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.label_uuid = QLabel()
+        self.label_uuid.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.ln_database_entry = QLineEdit()  dbHorizontal = QHBoxLayout()
         dbHorizontal.addWidget(self.ln_database_name)
@@ -407,6 +411,8 @@ class comic_meta_data_editor(QDialog):
         publisherLayout.addRow(i18n("City:"), self.publishCity)
         publisherLayout.addRow(i18n("Date:"), publishDateLayout)
         publisherLayout.addRow(i18n("ISBN:"), self.isbn)
+        publisherLayout.addRow(i18n("Source:"), self.ln_source)
+        publisherLayout.addRow(i18n("UUID:"), self.label_uuid)
         publisherLayout.addRow(i18n("License:"), self.license)
         publisherLayout.addRow(i18n("Database:"), dataBaseReference)
 
@@ -619,6 +625,23 @@ class comic_meta_data_editor(QDialog):
             self.publishDate.setDate(QDate.fromString(config["publishingDate"], Qt.ISODate))
         if "isbn-number" in config.keys():
             self.isbn.setText(config["isbn-number"])
+        if "source" in config.keys():
+            self.ln_source.setText(config["source"])
+        elif "acbfSource" in config.keys():
+            self.ln_source.setText(config["acbfSource"])
+        if "uuid" in config.keys():
+            self.label_uuid.setText(config["uuid"])
+        else:
+            uuid = str()
+            if "acbfID" in config.keys():
+                uuid = config["acbfID"]
+                uuid = uuid.strip("{")
+                uuid = uuid.strip("}")
+                uuidVerify = uuid.split("-")
+                if len(uuidVerify[0])!=8 or len(uuidVerify[1])!=4 or len(uuidVerify[2])!=4 or \
len(uuidVerify[3])!=4 or len(uuidVerify[4])!=12: +                    uuid = \
QUuid.createUuid().toString() +            self.label_uuid.setText(uuid)
+            config["uuid"] = uuid
         if "license" in config.keys():
             self.license.setCurrentText(config["license"])
         else:
@@ -746,6 +769,7 @@ class comic_meta_data_editor(QDialog):
         config["publisherCity"] = self.publishCity.text()
         config["publishingDate"] = self.publishDate.date().toString(Qt.ISODate)
         config["isbn-number"] = self.isbn.text()
+        config["source"] = self.ln_source.text()
         config["license"] = self.license.currentText()
         if self.ln_database_name.text().isalnum() and self.ln_database_entry.text().isalnum():
             dbRef = {}
diff --git a/plugins/python/comics_project_management_tools/comics_project_manager_docker.py \
b/plugins/python/comics_project_management_tools/comics_project_manager_docker.py index \
                f3640ac9611..608d96a7458 100644
--- a/plugins/python/comics_project_management_tools/comics_project_manager_docker.py
+++ b/plugins/python/comics_project_management_tools/comics_project_manager_docker.py
@@ -780,6 +780,16 @@ class comics_project_manager_docker(DockWidget):
     """
 
     def slot_export(self):
+        
+        #ensure there is a unique identifier
+        if "uuid" not in self.setupDictionary.keys():
+            uuid = str()
+            if "acbfID" in self.setupDictionary.keys():
+                uuid = str(self.setupDictionary["acbfID"])
+            else:
+                uuid = QUuid.createUuid().toString()
+            self.setupDictionary["uuid"] = uuid
+        
         exporter = comics_exporter.comicsExporter()
         exporter.set_config(self.setupDictionary, self.projecturl)
         exportSuccess = exporter.export()
diff --git a/plugins/python/comics_project_management_tools/comics_project_setup_wizard.py \
b/plugins/python/comics_project_management_tools/comics_project_setup_wizard.py index \
                7312b246368..003be44cee3 100644
--- a/plugins/python/comics_project_management_tools/comics_project_setup_wizard.py
+++ b/plugins/python/comics_project_management_tools/comics_project_setup_wizard.py
@@ -26,7 +26,7 @@ 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.QtWidgets import QWidget, QWizard, QWizardPage, QHBoxLayout, QFormLayout, QFileDialog, \
                QLineEdit, QPushButton, QCheckBox, QLabel, QDialog
-from PyQt5.QtCore import QDate, QLocale
+from PyQt5.QtCore import QDate, QLocale, QUuid
 from krita import *
 from . import comics_metadata_dialog
 
@@ -171,6 +171,7 @@ class ComicsProjectSetupWizard():
             self.setupDictionary["exportLocation"] = self.exportDirectory
             self.setupDictionary["templateLocation"] = self.templateLocation
             self.setupDictionary["translationLocation"] = self.translationLocation
+            self.setupDictionary["uuid"] = QUuid.createUuid().toString()
 
             # Finally, write the dictionary into the json file.
             self.writeConfig()
diff --git a/plugins/python/comics_project_management_tools/exporters/CPMT_EPUB_exporter.py \
b/plugins/python/comics_project_management_tools/exporters/CPMT_EPUB_exporter.py index \
                7965cf8a8f4..4128692952e 100644
--- a/plugins/python/comics_project_management_tools/exporters/CPMT_EPUB_exporter.py
+++ b/plugins/python/comics_project_management_tools/exporters/CPMT_EPUB_exporter.py
@@ -25,6 +25,7 @@ import shutil
 import os
 from pathlib import Path
 from PyQt5.QtXml import QDomDocument, QDomElement, QDomText, QDomNodeList
+from PyQt5.QtCore import Qt, QDateTime
 
 def export(configDictionary = {}, projectURL = str(), pagesLocationList = []):
     path = Path(os.path.join(projectURL, configDictionary["exportLocation"]))
@@ -128,6 +129,10 @@ Write OPF metadata file
 
 def write_opf_file(path, configDictionary, htmlFiles, pagesList, coverpageurl, coverpagehtml):
     
+    # marc relators
+    # This has several entries removed to reduce it to the most relevant entries.
+    marcRelators = {"abr":i18n("Abridger"), "acp":i18n("Art copyist"), "act":i18n("Actor"), \
"adi":i18n("Art director"), "adp":i18n("Adapter"), "ann":i18n("Annotator"), "ant":i18n("Bibliographic \
antecedent"), "arc":i18n("Architect"), "ard":i18n("Artistic director"), "art":i18n("Artist"), \
"asn":i18n("Associated name"), "ato":i18n("Autographer"), "att":i18n("Attributed name"), \
"aud":i18n("Author of dialog"), "aut":i18n("Author"), "bdd":i18n("Binding designer"), \
"bjd":i18n("Bookjacket designer"), "bkd":i18n("Book designer"), "bkp":i18n("Book producer"), \
"blw":i18n("Blurb writer"), "bnd":i18n("Binder"), "bpd":i18n("Bookplate designer"), \
"bsl":i18n("Bookseller"), "cll":i18n("Calligrapher"), "clr":i18n("Colorist"), "cns":i18n("Censor"), \
"cov":i18n("Cover designer"), "cph":i18n("Copyright holder"), "cre":i18n("Creator"), \
"ctb":i18n("Contributor"), "cur":i18n("Curator"), "cwt":i18n("Commentator for written text"), \
"drm":i18n("Draftsman"), "dsr":i18n("Designer"), "dub":i18n("Dubious author"), "edt":i18n("Editor"), \
"etr":i18n("Etcher"), "exp":i18n("Expert"), "fnd":i18n("Funder"), "ill":i18n("Illustrator"), \
"ilu":i18n("Illuminator"), "ins":i18n("Inscriber"), "lse":i18n("Licensee"), "lso":i18n("Licensor"), \
"ltg":i18n("Lithographer"), "mdc":i18n("Metadata contact"), "oth":i18n("Other"), "own":i18n("Owner"), \
"pat":i18n("Patron"), "pbd":i18n("Publishing director"), "pbl":i18n("Publisher"), "prt":i18n("Printer"), \
"sce":i18n("Scenarist"), "scr":i18n("Scribe"), "spn":i18n("Sponsor"), "stl":i18n("Storyteller"), \
"trc":i18n("Transcriber"), "trl":i18n("Translator"), "tyd":i18n("Type designer"), \
"tyg":i18n("Typographer"), "wac":i18n("Writer of added commentary"), "wal":i18n("Writer of added \
lyrics"), "wam":i18n("Writer of accompanying material"), "wat":i18n("Writer of added text"), \
"win":i18n("Writer of introduction"), "wpr":i18n("Writer of preface"), "wst":i18n("Writer of \
supplementary textual content")} +    
     # opf file
     opfFile = QDomDocument()
     opfRoot = opfFile.createElement("package")
@@ -139,17 +144,72 @@ def write_opf_file(path, configDictionary, htmlFiles, pagesList, coverpageurl, c
     opfMeta = opfFile.createElement("metadata")
     opfMeta.setAttribute("xmlns:dc", "http://purl.org/dc/elements/1.1/")
 
+    # EPUB metadata requires a title, language and uuid
+
+    langString = "en-US"
     if "language" in configDictionary.keys():
-        bookLang = opfFile.createElement("dc:language")
-        bookLang.appendChild(opfFile.createTextNode(configDictionary["language"]))
-        opfMeta.appendChild(bookLang)
+        langString = configDictionary["language"]
+    
+    bookLang = opfFile.createElement("dc:language")
+    bookLang.appendChild(opfFile.createTextNode(langString))
+    opfMeta.appendChild(bookLang)
+
     bookTitle = opfFile.createElement("dc:title")
     if "title" in configDictionary.keys():
         bookTitle.appendChild(opfFile.createTextNode(str(configDictionary["title"])))
     else:
         bookTitle.appendChild(opfFile.createTextNode("Comic with no Name"))
     opfMeta.appendChild(bookTitle)
-
+    
+    # Generate series title and the like here too.
+    if "seriesName" in configDictionary.keys():
+        bookTitle.setAttribute("id", "#main")
+
+        refine = opfFile.createElement("meta")
+        refine.setAttribute("refines", "#main")
+        refine.setAttribute("property", "title-type")
+        refine.appendChild(opfFile.createTextNode("main"))
+        opfMeta.appendChild(refine)
+
+        refine2 = opfFile.createElement("meta")
+        refine2.setAttribute("refines", "#main")
+        refine2.setAttribute("property", "display-seq")
+        refine2.appendChild(opfFile.createTextNode("1"))
+        opfMeta.appendChild(refine2)
+
+        seriesTitle = opfFile.createElement("dc:title")
+        seriesTitle.appendChild(opfFile.createTextNode(str(configDictionary["seriesName"])))
+        seriesTitle.setAttribute("id", "#series")
+        opfMeta.appendChild(seriesTitle)
+
+        refineS = opfFile.createElement("meta")
+        refineS.setAttribute("refines", "#series")
+        refineS.setAttribute("property", "title-type")
+        refineS.appendChild(opfFile.createTextNode("collection"))
+        opfMeta.appendChild(refineS)
+
+        refineS2 = opfFile.createElement("meta")
+        refineS2.setAttribute("refines", "#series")
+        refineS2.setAttribute("property", "display-seq")
+        refineS2.appendChild(opfFile.createTextNode("2"))
+        opfMeta.appendChild(refineS2)
+
+        if "seriesNumber" in configDictionary.keys():
+            refineS3 = opfFile.createElement("meta")
+            refineS3.setAttribute("refines", "#series")
+            refineS3.setAttribute("property", "group-position")
+            refineS3.appendChild(opfFile.createTextNode(str(configDictionary["seriesNumber"])))
+            opfMeta.appendChild(refineS3)
+
+    uuid = str(configDictionary["uuid"])
+    uuid = uuid.strip("{")
+    uuid = uuid.strip("}")
+
+    # Append the id, and assign it as the bookID.
+    uniqueID = opfFile.createElement("dc:identifier")
+    uniqueID.appendChild(opfFile.createTextNode("urn:uuid:"+uuid))
+    uniqueID.setAttribute("id", "BookID")
+    opfMeta.appendChild(uniqueID)
 
     if "authorList" in configDictionary.keys():
         for authorE in range(len(configDictionary["authorList"])):
@@ -176,13 +236,31 @@ def write_opf_file(path, configDictionary, htmlFiles, pagesList, coverpageurl, c
                 role.setAttribute("refines", "cre" + str(authorE))
                 role.setAttribute("scheme", "marc:relators")
                 role.setAttribute("property", "role")
-                role.appendChild(opfFile.createTextNode(str(authorDict["role"])))
+                roleString = str(authorDict["role"])
+                if roleString in marcRelators.values():
+                    i = list(marcRelators.values()).index(roleString)
+                    roleString = list(marcRelators.keys())[i]
+                else:
+                    roleString = "oth"
+                role.appendChild(opfFile.createTextNode(roleString))
                 opfMeta.appendChild(role)
 
     if "publishingDate" in configDictionary.keys():
         date = opfFile.createElement("dc:date")
         date.appendChild(opfFile.createTextNode(configDictionary["publishingDate"]))
         opfMeta.appendChild(date)
+    
+    #Creation date
+    modified = opfFile.createElement("meta")
+    modified.setAttribute("property", "dcterms:modified")
+    modified.appendChild(opfFile.createTextNode(QDateTime.currentDateTimeUtc().toString(Qt.ISODate)))
+    opfMeta.appendChild(modified)
+    
+    if "source" in configDictionary.keys():
+        source = opfFile.createElement("dc:source")
+        date.appendChild(opfFile.createTextNode(configDictionary["source"]))
+        opfMeta.appendChild(source)
+    
     description = opfFile.createElement("dc:description")
     if "summary" in configDictionary.keys():
         description.appendChild(opfFile.createTextNode(configDictionary["summary"]))
@@ -190,13 +268,15 @@ def write_opf_file(path, configDictionary, htmlFiles, pagesList, coverpageurl, c
         description.appendChild(opfFile.createTextNode("There was no summary upon generation of this \
file."))  opfMeta.appendChild(description)
 
-    typeE = opfFile.createElement("dc:type")
-    typeE.appendChild(opfFile.createTextNode("Comic"))
-    opfMeta.appendChild(typeE)
+    # Type can be dictionary or index, or one of those edupub thingies. Not necessary for comics.
+    # typeE = opfFile.createElement("dc:type")
+    # opfMeta.appendChild(typeE)
     if "publisherName" in configDictionary.keys():
         publisher = opfFile.createElement("dc:publisher")
         publisher.appendChild(opfFile.createTextNode(configDictionary["publisherName"]))
         opfMeta.appendChild(publisher)
+    
+    
     if "isbn-number" in configDictionary.keys():
         publishISBN = opfFile.createElement("dc:identifier")
         publishISBN.appendChild(opfFile.createTextNode(str("urn:isbn:") + \
configDictionary["isbn-number"]))


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic