1/*
2 SPDX-FileCopyrightText: 2023 Joshua Goins <josh@redstrate.com>
3
4 SPDX-License-Identifier: LGPL-2.1-or-later
5*/
6
7
8#include "kritaextractor.h"
9#include "kfilemetadata_debug.h"
10
11#include <KZip>
12#include <QXmlStreamReader>
13
14using namespace KFileMetaData;
15
16KritaExtractor::KritaExtractor(QObject* parent)
17 : ExtractorPlugin(parent)
18{
19}
20
21QStringList KritaExtractor::mimetypes() const
22{
23 return {QStringLiteral("application/x-krita")};
24}
25
26void KritaExtractor::extract(ExtractionResult* result)
27{
28 // Krita files are secretly zip files
29 KZip zip(result->inputUrl());
30 if (!zip.open(mode: QIODevice::ReadOnly)) {
31 qCDebug(KFILEMETADATA_LOG) << "Failed to open" << zip.fileName() << "-" << zip.errorString();
32 return;
33 }
34
35 result->addType(type: Type::Image);
36
37 if (!result->inputFlags().testFlag(flag: ExtractionResult::ExtractMetaData)) {
38 return;
39 }
40
41 // Read main image information, e.g width and height
42 {
43 const KArchiveFile *entry = zip.directory()->file(name: QLatin1String("maindoc.xml"));
44 if (!entry) {
45 return;
46 }
47
48 std::unique_ptr<QIODevice> fileDevice{entry->createDevice()};
49
50 // There is only one element of the maindoc we care about: the IMAGE element.
51 // The document width and height are stored as attributes.
52 QXmlStreamReader xml{fileDevice.get()};
53 while (xml.readNextStartElement()) {
54 if (xml.name() == QLatin1String("IMAGE")) {
55 bool ok = false;
56 const int width = xml.attributes().value(qualifiedName: QLatin1String("width")).toInt(ok: &ok);
57 if (ok && width != 0) {
58 result->add(property: Property::Width, value: width);
59 }
60
61 const int height = xml.attributes().value(qualifiedName: QLatin1String("height")).toInt(ok: &ok);
62 if (ok && height != 0) {
63 result->add(property: Property::Height, value: height);
64 }
65
66 break;
67 }
68 }
69 }
70
71 // Read extra metadata entered by the author, e.g. title and description
72 {
73 const KArchiveFile *entry = zip.directory()->file(name: QLatin1String("documentinfo.xml"));
74 if (!entry) {
75 return;
76 }
77
78 std::unique_ptr<QIODevice> fileDevice{entry->createDevice()};
79
80 // The documentinfo xml schema is very simple, every field in the GUI is exposed
81 // as an element of the same name. The one exception is "description" which seems
82 // to be stored under <abstract>.
83 static const QHash<QStringView, Property::Property> propertyMapping {{
84 {QStringLiteral("title"), Property::Title},
85 {QStringLiteral("license"), Property::License},
86 {QStringLiteral("keyword"), Property::Keywords},
87 {QStringLiteral("abstract"), Property::Description},
88 }};
89
90 QXmlStreamReader xml{fileDevice.get()};
91 if (!xml.readNextStartElement() || xml.name() != QStringLiteral("document-info")) {
92 return;
93 }
94
95 while (xml.readNextStartElement()) {
96 const QStringView elementName = xml.name();
97
98 if (elementName == QStringLiteral("about")) {
99 while (xml.readNextStartElement()) {
100 const QStringView childElementName = xml.name();
101
102 const Property::Property property = propertyMapping.value(key: childElementName);
103 if (property != Property::Empty) {
104 const QString value = xml.readElementText();
105 result->add(property, value);
106 } else if (childElementName == QStringLiteral("creation-date")) {
107 const QString value = xml.readElementText();
108
109 const QDateTime creationDate = QDateTime::fromString(string: value, format: Qt::ISODate);
110 if (creationDate.isValid()) {
111 result->add(property: Property::CreationDate, value: creationDate);
112 }
113 } else {
114 xml.skipCurrentElement();
115 }
116 }
117 } else if (elementName == QStringLiteral("author")) {
118 while (xml.readNextStartElement()) {
119 const QStringView childElementName = xml.name();
120
121 if (childElementName == QStringLiteral("full-name")) {
122 const QString value = xml.readElementText();
123 if (!value.isEmpty()) {
124 result->add(property: Property::Author, value);
125 }
126 } else {
127 xml.skipCurrentElement();
128 }
129 }
130 } else {
131 xml.skipCurrentElement();
132 }
133 }
134 }
135}
136
137#include "moc_kritaextractor.cpp"
138

source code of kfilemetadata/src/extractors/kritaextractor.cpp