1 /*
2 This file is part of kde-thumbnailer-epub
3 Copyright (C) 2012-2017 Giacomo Barazzetti <giacomosrv@gmail.com>
4
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "epub.h"
20
21 #include <algorithm>
22 #include <functional>
23
24 #include <QtCore/QDebug>
25 #include <QtCore/QDir>
26 #include <QtGui/QImage>
27
28 const QStringList imageExt({"jpg", "jpeg", "png", "gif", "bmp"});
29 const QStringList xmlExt({"xhtml", "xhtm", "html", "htm", "xml"});
30 bool endsWith (const QString &coverUrl, const QStringList &extensions);
31
Epub(const QString & path)32 Epub::Epub(const QString &path)
33 : mZip(path)
34 {
35 qDebug() << "[epub thumb] > Opening" << path;
36 }
37
~Epub()38 Epub::~Epub()
39 {
40 mZip.close();
41 }
42
open()43 bool Epub::open()
44 {
45 if (mZip.open(QIODevice::ReadOnly)) {
46 qDebug() << "[epub thumb] Retrieving files list...";
47 loadFilesList(mZip.directory());
48 mFilesList.sort(Qt::CaseSensitive);
49
50 if (mFilesList.isEmpty()) {
51 qDebug() << "[epub thumb] > No files found.";
52 return false;
53 }
54
55 if (!loadOpf()) {
56 qDebug() << "[epub thumb] > No OPF file found.";
57 return false;
58 }
59
60 return true;
61 }
62
63 qDebug() << "[epub thumb] > Couldn't open";
64 return false;
65 }
66
getCoverImage(QImage & coverImage)67 bool Epub::getCoverImage(QImage &coverImage)
68 {
69 std::function<QString()> strategies[4] = {
70 std::bind(&Epub::getRefFromMetadata, this),
71 std::bind(&Epub::getRefFromGuide, this),
72 std::bind(&Epub::getRefFromSpine, this),
73 std::bind(&Epub::getRefByName, this)
74 };
75
76 for (auto strategy : strategies) {
77 QString ref = strategy();
78
79 if (!ref.isEmpty()) {
80 bool coverFound = false;
81
82 if (isImage(ref)) {
83 coverFound = true;
84 } else {
85 QString tRef = getRefFromXhtm(ref);
86 if (!tRef.isEmpty()) {
87 ref = tRef;
88 coverFound = true;
89 }
90 }
91
92 if (coverFound) {
93 qDebug() << "[epub thumb] Cover url:" << ref;
94
95 if (coverImage.loadFromData(getFile(ref))) {
96 qDebug() << "[epub thumb] > Generated.";
97 return true;
98 }
99 }
100 }
101 }
102
103 qDebug() << "[epub thumb] > No cover found.";
104 return false;
105 }
106
107 //-------------------------------------
108
loadFilesList(const KArchiveDirectory * dir,const QString & subPath)109 void Epub::loadFilesList(const KArchiveDirectory *dir, const QString &subPath)
110 {
111 for (const QString &fileName : dir->entries()) {
112 const KArchiveEntry *entry = dir->entry(fileName);
113 const QString tUrl = QDir(subPath).filePath(fileName);
114
115 if (entry->isFile()) {
116 if (fileName.endsWith("opf")) {
117 mOpfUrl = tUrl;
118 } else if (endsWith(fileName, imageExt) || endsWith(fileName, xmlExt)) {
119 mFilesList.append(tUrl);
120 }
121 } else {
122 loadFilesList(static_cast<const KArchiveDirectory*>(entry), tUrl);
123 }
124 }
125 }
126
loadOpf()127 bool Epub::loadOpf()
128 {
129 if (!mOpfUrl.isEmpty()) {
130 mOpf = QString(getFile(mOpfUrl));
131 mOpfQuery.setFocus(mOpf);
132 }
133
134 return !mOpf.isEmpty();
135 }
136
getRefFromMetadata()137 QString Epub::getRefFromMetadata()
138 {
139 qDebug() << "[epub thumb] Searching for id in metadata...";
140
141 const QString id = getAttrFromOpf("/package/metadata/meta[@name='cover']/@content/string()", "http://www.idpf.org/2007/opf");
142
143 if (!id.isEmpty()) {
144 qDebug() << "[epub thumb] Id:" << id;
145 return getAbsoluteUrl(getRefById(id));
146 }
147
148 qDebug() << "[epub thumb] Id not found/invalid.";
149 return QString();
150 }
151
getRefFromGuide()152 QString Epub::getRefFromGuide()
153 {
154 qDebug() << "[epub thumb] Searching for ref in guide...";
155
156 const QString ref = getAttrFromOpf("/package/guide/reference[@type='cover' or @title='cover']/@href/string()", "http://www.idpf.org/2007/opf");
157
158 if (!ref.isEmpty()) {
159 qDebug() << "[epub thumb] Ref:" << ref;
160 return getAbsoluteUrl(ref);
161 }
162
163 qDebug() << "[epub thumb] Ref not found/invalid.";
164 return QString();
165 }
166
getRefFromSpine()167 QString Epub::getRefFromSpine()
168 {
169 qDebug() << "[epub thumb] Searching for first xhtm in spine...";
170
171 const QString id = getAttrFromOpf("/package/spine/itemref[1]/@idref/string()", "http://www.idpf.org/2007/opf");
172
173 if (!id.isEmpty()) {
174 qDebug() << "[epub thumb] Id:" << id;
175 return getAbsoluteUrl(getRefById(id));
176 }
177
178 qDebug() << "[epub thumb] Id not found/invalid.";
179 return QString();
180 }
181
getRefByName() const182 QString Epub::getRefByName() const
183 {
184 qDebug() << "[epub thumb] Searching for cover by generic filename...";
185 return getAbsoluteUrl("cover");
186 }
187
getRefById(const QString & coverId)188 QString Epub::getRefById(const QString &coverId)
189 {
190 qDebug() << "[epub thumb] Searching for ref in manifest...";
191
192 //CHECK: query.bindVariable("myId", QXmlItem(QVariant(coverId)));
193 const QString query = "/package/manifest/item[@id='" + coverId + "']/@href/string()";
194 const QString ref = getAttrFromOpf(query, "http://www.idpf.org/2007/opf");
195
196 if (ref.isEmpty()) {
197 qDebug() << "[epub thumb] No ref found.";
198 } else {
199 qDebug() << "[epub thumb] Ref:" << ref;
200 }
201
202 return ref;
203 }
204
getAbsoluteUrl(const QString & url) const205 QString Epub::getAbsoluteUrl(const QString &url) const
206 {
207 QString fileUrl;
208
209 for (const QString &fileName : mFilesList) {
210 if (fileName.contains(url, Qt::CaseInsensitive)) {
211 fileUrl = fileName;
212 break;
213 }
214 }
215
216 return fileUrl;
217 }
218
isImage(const QString & ref) const219 bool Epub::isImage(const QString &ref) const
220 {
221 if (endsWith(ref, imageExt)) {
222 return true;
223 }
224
225 return false;
226 }
227
getRefFromXhtm(const QString & xhtmUrl) const228 QString Epub::getRefFromXhtm(const QString &xhtmUrl) const
229 {
230 QXmlQuery xHtmlQuery;
231 xHtmlQuery.setFocus(getFile(xhtmUrl));
232 xHtmlQuery.setQuery("(//*:image[1]/@*:href | //*:img[1]/@src)/string()");
233
234 QString ref;
235 xHtmlQuery.evaluateTo(&ref);
236 ref = ref.remove('\n');
237
238 // fixes relative path
239 if (ref.startsWith("../")) {
240 ref = ref.remove(0, 3);
241 }
242
243 return getAbsoluteUrl(ref);
244 }
245
getAttrFromOpf(const QString & query,const QString & namesp)246 QString Epub::getAttrFromOpf(const QString &query, const QString &namesp)
247 {
248 const QString tNamesp("declare default element namespace \"" + namesp + "\";");
249 mOpfQuery.setQuery(tNamesp + query);
250
251 QString attribute;
252 mOpfQuery.evaluateTo(&attribute);
253 attribute = attribute.remove('\n');
254
255 return attribute;
256 }
257
getFile(const QString & fileName) const258 QByteArray Epub::getFile(const QString &fileName) const
259 {
260 const KArchiveDirectory *dir = mZip.directory();
261 const KZipFileEntry *file = static_cast<const KZipFileEntry*>(dir->entry(fileName));
262
263 return file->data();
264 }
265
266 //--------------------
267
endsWith(const QString & coverUrl,const QStringList & extensions)268 bool endsWith (const QString &coverUrl, const QStringList &extensions)
269 {
270 return std::any_of(extensions.begin(), extensions.end(), [&coverUrl](const QString& ext) -> bool {
271 return coverUrl.endsWith("." + ext, Qt::CaseInsensitive);
272 });
273 }
274