File indexing completed on 2024-04-21 04:21:20
0001 // SPDX-FileCopyrightText: 2006-2022 Jesper K. Pedersen <jesper.pedersen@kdab.com> 0002 // SPDX-FileCopyrightText: 2007 Dirk Mueller <mueller@kde.org> 0003 // SPDX-FileCopyrightText: 2007-2008 Laurent Montel <montel@kde.org> 0004 // SPDX-FileCopyrightText: 2007-2010 Jan Kundrát <jkt@flaska.net> 0005 // SPDX-FileCopyrightText: 2007-2010 Tuomas Suutari <tuomas@nepnep.net> 0006 // SPDX-FileCopyrightText: 2008 Henner Zeller <h.zeller@acm.org> 0007 // SPDX-FileCopyrightText: 2009-2013 Miika Turkia <miika.turkia@gmail.com> 0008 // SPDX-FileCopyrightText: 2012-2023 Johannes Zarl-Zierl <johannes@zarl-zierl.at> 0009 // SPDX-FileCopyrightText: 2013 Dominik Broj <broj.dominik@gmail.com> 0010 // SPDX-FileCopyrightText: 2016-2022 Tobias Leupold <tl@stonemx.de> 0011 0012 // SPDX-License-Identifier: GPL-2.0-or-later 0013 0014 #include "Generator.h" 0015 0016 #include "ImageSizeCheckBox.h" 0017 #include "Logging.h" 0018 #include "Setup.h" 0019 0020 #include <DB/CategoryCollection.h> 0021 #include <DB/ImageDB.h> 0022 #include <DB/ImageInfo.h> 0023 #include <ImageManager/AsyncLoader.h> 0024 #include <ImportExport/Export.h> 0025 #include <MainWindow/Window.h> 0026 #include <Utilities/FileUtil.h> 0027 #include <kpabase/FileExtensions.h> 0028 #include <kpaexif/Info.h> 0029 0030 #include <KConfig> 0031 #include <KConfigGroup> 0032 #include <KIO/CopyJob> 0033 #include <KLocalizedString> 0034 #include <KMessageBox> 0035 #include <kio_version.h> 0036 #if KIO_VERSION > QT_VERSION_CHECK(5, 69, 0) 0037 #include <KIO/CommandLauncherJob> 0038 #include <KIO/JobUiDelegate> 0039 #endif 0040 #include <KRun> 0041 #include <QApplication> 0042 #include <QDebug> 0043 #include <QDir> 0044 #include <QDomDocument> 0045 #include <QFile> 0046 #include <QList> 0047 #include <QMimeDatabase> 0048 #include <QStandardPaths> 0049 #include <sys/types.h> 0050 #include <sys/wait.h> 0051 0052 namespace 0053 { 0054 QString readFile(const QString &fileName) 0055 { 0056 if (fileName.isEmpty()) { 0057 KMessageBox::error(nullptr, i18n("<p>No file name given!</p>")); 0058 return QString(); 0059 } 0060 0061 QFile file(fileName); 0062 if (!file.open(QIODevice::ReadOnly)) { 0063 // KMessageBox::error( nullptr, i18n("Could not open file %1").arg( fileName ) ); 0064 return QString(); 0065 } 0066 0067 QTextStream stream(&file); 0068 QString content = stream.readAll(); 0069 file.close(); 0070 0071 return content; 0072 } 0073 } // namespace 0074 0075 HTMLGenerator::Generator::Generator(const Setup &setup, QWidget *parent) 0076 : QProgressDialog(parent) 0077 , m_waitCounter(0) 0078 , m_total(0) 0079 , m_tempDirHandle() 0080 , m_tempDir(m_tempDirHandle.path()) 0081 , m_hasEnteredLoop(false) 0082 { 0083 setLabelText(i18n("Generating images for HTML page ")); 0084 m_setup = setup; 0085 m_eventLoop = new QEventLoop; 0086 m_avconv = QStandardPaths::findExecutable(QString::fromUtf8("avconv")); 0087 if (m_avconv.isNull()) 0088 m_avconv = QStandardPaths::findExecutable(QString::fromUtf8("ffmpeg")); 0089 m_tempDirHandle.setAutoRemove(true); 0090 } 0091 0092 HTMLGenerator::Generator::~Generator() 0093 { 0094 delete m_eventLoop; 0095 } 0096 void HTMLGenerator::Generator::generate() 0097 { 0098 qCDebug(HTMLGeneratorLog) << "Generating gallery" << m_setup.title() << "containing" << m_setup.imageList().size() << "entries in" << m_setup.baseDir(); 0099 // Generate .kim file 0100 if (m_setup.generateKimFile()) { 0101 qCDebug(HTMLGeneratorLog) << "Generating .kim file..."; 0102 bool ok; 0103 QString destURL = m_setup.destURL(); 0104 0105 ImportExport::Export exp(m_setup.imageList(), kimFileName(false), 0106 false, -1, ImportExport::ManualCopy, 0107 destURL + QDir::separator() + m_setup.outputDir(), true, &ok); 0108 if (!ok) { 0109 qCDebug(HTMLGeneratorLog) << ".kim file failed!"; 0110 return; 0111 } 0112 } 0113 0114 // prepare the progress dialog 0115 m_total = m_waitCounter = calculateSteps(); 0116 setMaximum(m_total); 0117 setValue(0); 0118 connect(this, &QProgressDialog::canceled, this, &Generator::slotCancelGenerate); 0119 0120 m_filenameMapper.reset(); 0121 0122 qCDebug(HTMLGeneratorLog) << "Generating content pages..."; 0123 // Iterate over each of the image sizes needed. 0124 for (QList<ImageSizeCheckBox *>::ConstIterator sizeIt = m_setup.activeResolutions().begin(); 0125 sizeIt != m_setup.activeResolutions().end(); ++sizeIt) { 0126 bool ok = generateIndexPage((*sizeIt)->width(), (*sizeIt)->height()); 0127 if (!ok) 0128 return; 0129 const DB::FileNameList imageList = m_setup.imageList(); 0130 for (int index = 0; index < imageList.size(); ++index) { 0131 DB::FileName current = imageList.at(index); 0132 DB::FileName prev; 0133 DB::FileName next; 0134 if (index != 0) 0135 prev = imageList.at(index - 1); 0136 if (index != imageList.size() - 1) 0137 next = imageList.at(index + 1); 0138 ok = generateContentPage((*sizeIt)->width(), (*sizeIt)->height(), 0139 prev, current, next); 0140 if (!ok) 0141 return; 0142 } 0143 } 0144 0145 // Now generate the thumbnail images 0146 qCDebug(HTMLGeneratorLog) << "Generating thumbnail images..."; 0147 for (const DB::FileName &fileName : m_setup.imageList()) { 0148 if (wasCanceled()) 0149 return; 0150 0151 createImage(fileName, m_setup.thumbSize()); 0152 } 0153 0154 if (wasCanceled()) 0155 return; 0156 0157 if (m_waitCounter > 0) { 0158 m_hasEnteredLoop = true; 0159 m_eventLoop->exec(); 0160 } 0161 0162 if (wasCanceled()) 0163 return; 0164 0165 qCDebug(HTMLGeneratorLog) << "Linking image file..."; 0166 bool ok = linkIndexFile(); 0167 if (!ok) 0168 return; 0169 0170 qCDebug(HTMLGeneratorLog) << "Copying theme files..."; 0171 // Copy over the mainpage.css, imagepage.css 0172 QString themeDir, themeAuthor, themeName; 0173 getThemeInfo(&themeDir, &themeName, &themeAuthor); 0174 QDir dir(themeDir); 0175 QStringList files = dir.entryList(QDir::Files); 0176 if (files.count() < 1) 0177 qCWarning(HTMLGeneratorLog) << QString::fromLatin1("theme '%1' doesn't have enough files to be a theme").arg(themeDir); 0178 0179 for (QStringList::Iterator it = files.begin(); it != files.end(); ++it) { 0180 if (*it == QString::fromLatin1("kphotoalbum.theme") || *it == QString::fromLatin1("mainpage.html") || *it == QString::fromLatin1("imagepage.html")) 0181 continue; 0182 QString from = QString::fromLatin1("%1%2").arg(themeDir, *it); 0183 QString to = m_tempDir.filePath(*it); 0184 ok = Utilities::copyOrOverwrite(from, to); 0185 if (!ok) { 0186 KMessageBox::error(this, i18n("Error copying %1 to %2", from, to)); 0187 return; 0188 } 0189 } 0190 0191 // Copy files over to destination. 0192 QString outputDir = m_setup.baseDir() + QString::fromLatin1("/") + m_setup.outputDir(); 0193 qCDebug(HTMLGeneratorLog) << "Copying files from" << m_tempDir.path() << "to final location" << outputDir << "..."; 0194 KIO::CopyJob *job = KIO::move(QUrl::fromLocalFile(m_tempDir.path()), QUrl::fromUserInput(outputDir)); 0195 connect(job, &KIO::CopyJob::result, this, &Generator::showBrowser); 0196 0197 m_eventLoop->exec(); 0198 return; 0199 } 0200 0201 bool HTMLGenerator::Generator::generateIndexPage(int width, int height) 0202 { 0203 QString themeDir, themeAuthor, themeName; 0204 getThemeInfo(&themeDir, &themeName, &themeAuthor); 0205 QString content = readFile(QString::fromLatin1("%1mainpage.html").arg(themeDir)); 0206 if (content.isEmpty()) 0207 return false; 0208 0209 // Adding the copyright comment after DOCTYPE not before (HTML standard requires the DOCTYPE to be first within the document) 0210 QRegExp rx(QString::fromLatin1("^(<!DOCTYPE[^>]*>)")); 0211 int position; 0212 0213 rx.setCaseSensitivity(Qt::CaseInsensitive); 0214 position = rx.indexIn(content); 0215 if ((position += rx.matchedLength()) < 0) 0216 content = QString::fromLatin1("<!--\nMade with KPhotoAlbum. (https://www.kphotoalbum.org/)\nCopyright © Jesper K. Pedersen\nTheme %1 by %2\n-->\n").arg(themeName, themeAuthor) + content; 0217 else 0218 content.insert(position, QString::fromLatin1("\n<!--\nMade with KPhotoAlbum. (https://www.kphotoalbum.org/)\nCopyright © Jesper K. Pedersen\nTheme %1 by %2\n-->\n").arg(themeName, themeAuthor)); 0219 0220 content.replace(QString::fromLatin1("**DESCRIPTION**"), m_setup.description()); 0221 content.replace(QString::fromLatin1("**TITLE**"), m_setup.title()); 0222 0223 QString copyright; 0224 if (!m_setup.copyright().isEmpty()) 0225 copyright = QString::fromLatin1("© %1").arg(m_setup.copyright()); 0226 else 0227 copyright = QString::fromLatin1(" "); 0228 content.replace(QString::fromLatin1("**COPYRIGHT**"), copyright); 0229 0230 QString kimLink = QString::fromLatin1("Share and Enjoy <a href=\"%1\">KPhotoAlbum export file</a>").arg(kimFileName(true)); 0231 if (m_setup.generateKimFile()) 0232 content.replace(QString::fromLatin1("**KIMFILE**"), kimLink); 0233 else 0234 content.remove(QString::fromLatin1("**KIMFILE**")); 0235 QDomDocument doc; 0236 0237 QDomElement elm; 0238 QDomElement col; 0239 0240 // -------------------------------------------------- Thumbnails 0241 // Initially all of the HTML generation was done using QDom, but it turned out in the end 0242 // to be much less code simply concatenating strings. This part, however, is easier using QDom 0243 // so we keep it using QDom. 0244 int count = 0; 0245 int cols = m_setup.numOfCols(); 0246 int minWidth = 0; 0247 int minHeight = 0; 0248 int enableVideo = 0; 0249 QString first, last, images; 0250 0251 images += QString::fromLatin1("var gallery=new Array()\nvar width=%1\nvar height=%2\nvar tsize=%3\nvar inlineVideo=%4\nvar generatedVideo=%5\n").arg(width).arg(height).arg(m_setup.thumbSize()).arg(m_setup.inlineMovies()).arg(m_setup.html5VideoGenerate()); 0252 minImageSize(minWidth, minHeight); 0253 if (minWidth == 0 && minHeight == 0) { // full size only 0254 images += QString::fromLatin1("var minPage=\"index-fullsize.html\"\n"); 0255 } else { 0256 images += QString::fromLatin1("var minPage=\"index-%1x%2.html\"\n").arg(minWidth).arg(minHeight); 0257 } 0258 0259 QDomElement row; 0260 for (const DB::FileName &fileName : m_setup.imageList()) { 0261 const DB::ImageInfoPtr info = DB::ImageDB::instance()->info(fileName); 0262 if (wasCanceled()) 0263 return false; 0264 0265 if (count % cols == 0) { 0266 row = doc.createElement(QString::fromLatin1("tr")); 0267 row.setAttribute(QString::fromLatin1("class"), QString::fromLatin1("thumbnail-row")); 0268 doc.appendChild(row); 0269 count = 0; 0270 } 0271 0272 col = doc.createElement(QString::fromLatin1("td")); 0273 col.setAttribute(QString::fromLatin1("class"), QString::fromLatin1("thumbnail-col")); 0274 row.appendChild(col); 0275 0276 if (first.isEmpty()) 0277 first = namePage(width, height, fileName); 0278 else 0279 last = namePage(width, height, fileName); 0280 0281 if (!KPABase::isVideo(fileName)) { 0282 QMimeDatabase db; 0283 images += QString::fromLatin1("gallery.push([\"%1\", \"%2\", \"%3\", \"%4\", \"") 0284 .arg(nameImage(fileName, width)) 0285 .arg(nameImage(fileName, m_setup.thumbSize())) 0286 .arg(nameImage(fileName, maxImageSize())) 0287 .arg(db.mimeTypeForFile(nameImage(fileName, maxImageSize())).name()); 0288 } else { 0289 QMimeDatabase db; 0290 images += QString::fromLatin1("gallery.push([\"%1\", \"%2\", \"%3\"") 0291 .arg(nameImage(fileName, m_setup.thumbSize())) 0292 .arg(nameImage(fileName, m_setup.thumbSize())) 0293 .arg(nameImage(fileName, maxImageSize())); 0294 if (m_setup.html5VideoGenerate()) { 0295 images += QString::fromLatin1(", \"%1\", \"") 0296 .arg(QString::fromLatin1("video/ogg")); 0297 } else { 0298 images += QString::fromLatin1(", \"%1\", \"") 0299 .arg(db.mimeTypeForFile(fileName.relative(), QMimeDatabase::MatchExtension).name()); 0300 } 0301 enableVideo = 1; 0302 } 0303 0304 // -------------------------------------------------- Description 0305 if (!info->description().isEmpty() && m_setup.includeCategory(QString::fromLatin1("**DESCRIPTION**"))) { 0306 images += QString::fromLatin1("%1\", \"") 0307 .arg(info->description() 0308 .replace(QString::fromLatin1("\n$"), QString::fromLatin1("")) 0309 .replace(QString::fromLatin1("\n"), QString::fromLatin1(" ")) 0310 .replace(QString::fromLatin1("\""), QString::fromLatin1("\\\""))); 0311 } else { 0312 images += QString::fromLatin1("\", \""); 0313 } 0314 QString description = populateDescription(DB::ImageDB::instance()->categoryCollection()->categories(), info); 0315 0316 if (!description.isEmpty()) { 0317 description = QString::fromLatin1("<ul>%1</ul>").arg(description); 0318 } else { 0319 description = QString::fromLatin1(""); 0320 } 0321 0322 description.replace(QString::fromLatin1("\n$"), QString::fromLatin1("")); 0323 description.replace(QString::fromLatin1("\n"), QString::fromLatin1(" ")); 0324 description.replace(QString::fromLatin1("\""), QString::fromLatin1("\\\"")); 0325 0326 images += description; 0327 images += QString::fromLatin1("\"]);\n"); 0328 0329 QDomElement href = doc.createElement(QString::fromLatin1("a")); 0330 href.setAttribute(QString::fromLatin1("href"), 0331 namePage(width, height, fileName)); 0332 col.appendChild(href); 0333 0334 QDomElement img = doc.createElement(QString::fromLatin1("img")); 0335 img.setAttribute(QString::fromLatin1("src"), 0336 nameImage(fileName, m_setup.thumbSize())); 0337 img.setAttribute(QString::fromLatin1("alt"), 0338 nameImage(fileName, m_setup.thumbSize())); 0339 href.appendChild(img); 0340 ++count; 0341 } 0342 0343 // Adding TD elements to match the selected column amount for valid HTML 0344 if (count % cols != 0) { 0345 for (int i = count; i % cols != 0; ++i) { 0346 col = doc.createElement(QString::fromLatin1("td")); 0347 col.setAttribute(QString::fromLatin1("class"), QString::fromLatin1("thumbnail-col")); 0348 QDomText sp = doc.createTextNode(QString::fromLatin1(" ")); 0349 col.appendChild(sp); 0350 row.appendChild(col); 0351 } 0352 } 0353 0354 content.replace(QString::fromLatin1("**THUMBNAIL-TABLE**"), doc.toString()); 0355 0356 images += QString::fromLatin1("var enableVideo=%1\n").arg(enableVideo ? 1 : 0); 0357 content.replace(QString::fromLatin1("**JSIMAGES**"), images); 0358 if (!first.isEmpty()) 0359 content.replace(QString::fromLatin1("**FIRST**"), first); 0360 if (!last.isEmpty()) 0361 content.replace(QString::fromLatin1("**LAST**"), last); 0362 0363 // -------------------------------------------------- Resolutions 0364 QString resolutions; 0365 QList<ImageSizeCheckBox *> actRes = m_setup.activeResolutions(); 0366 std::sort(actRes.begin(), actRes.end()); 0367 0368 if (actRes.count() > 1) { 0369 resolutions += QString::fromLatin1("Resolutions: "); 0370 for (QList<ImageSizeCheckBox *>::ConstIterator sizeIt = actRes.constBegin(); 0371 sizeIt != actRes.constEnd(); ++sizeIt) { 0372 0373 int w = (*sizeIt)->width(); 0374 int h = (*sizeIt)->height(); 0375 QString page = QString::fromLatin1("index-%1.html").arg(ImageSizeCheckBox::text(w, h, true)); 0376 QString text = (*sizeIt)->text(false); 0377 0378 resolutions += QString::fromLatin1(" "); 0379 if (width == w && height == h) { 0380 resolutions += text; 0381 } else { 0382 resolutions += QString::fromLatin1("<a href=\"%1\">%2</a>").arg(page).arg(text); 0383 } 0384 } 0385 } 0386 0387 content.replace(QString::fromLatin1("**RESOLUTIONS**"), resolutions); 0388 0389 if (wasCanceled()) 0390 return false; 0391 0392 // -------------------------------------------------- write to file 0393 QString fileName = m_tempDir.filePath( 0394 QString::fromLatin1("index-%1.html") 0395 .arg(ImageSizeCheckBox::text(width, height, true))); 0396 bool ok = writeToFile(fileName, content); 0397 0398 if (!ok) 0399 return false; 0400 0401 return true; 0402 } 0403 0404 bool HTMLGenerator::Generator::generateContentPage(int width, int height, 0405 const DB::FileName &prev, const DB::FileName ¤t, const DB::FileName &next) 0406 { 0407 QString themeDir, themeAuthor, themeName; 0408 getThemeInfo(&themeDir, &themeName, &themeAuthor); 0409 QString content = readFile(QString::fromLatin1("%1imagepage.html").arg(themeDir)); 0410 if (content.isEmpty()) 0411 return false; 0412 0413 const DB::ImageInfoPtr info = DB::ImageDB::instance()->info(current); 0414 // Note(jzarl): is there any reason why currentFile could be different from current? 0415 const DB::FileName currentFile = info->fileName(); 0416 0417 // Adding the copyright comment after DOCTYPE not before (HTML standard requires the DOCTYPE to be first within the document) 0418 QRegExp rx(QString::fromLatin1("^(<!DOCTYPE[^>]*>)")); 0419 int position; 0420 0421 rx.setCaseSensitivity(Qt::CaseInsensitive); 0422 position = rx.indexIn(content); 0423 if ((position += rx.matchedLength()) < 0) 0424 content = QString::fromLatin1("<!--\nMade with KPhotoAlbum. (https://www.kphotoalbum.org/)\nCopyright © Jesper K. Pedersen\nTheme %1 by %2\n-->\n").arg(themeName, themeAuthor) + content; 0425 else 0426 content.insert(position, QString::fromLatin1("\n<!--\nMade with KPhotoAlbum. (https://www.kphotoalbum.org/)\nCopyright © Jesper K. Pedersen\nTheme %1 by %2\n-->\n").arg(themeName, themeAuthor)); 0427 0428 // TODO: Hardcoded non-standard category names is not good practice 0429 QString title = QString::fromLatin1(""); 0430 QString name = QString::fromLatin1("Common Name"); 0431 const auto itemsOfCategory = info->itemsOfCategory(name); 0432 if (!itemsOfCategory.empty()) { 0433 title += QStringList(itemsOfCategory.begin(), itemsOfCategory.end()).join(QLatin1String(" - ")); 0434 } else { 0435 name = QString::fromLatin1("Latin Name"); 0436 if (!itemsOfCategory.empty()) { 0437 title += QStringList(itemsOfCategory.begin(), itemsOfCategory.end()).join(QString::fromLatin1(" - ")); 0438 } else { 0439 title = info->label(); 0440 } 0441 } 0442 content.replace(QString::fromLatin1("**TITLE**"), title); 0443 0444 // Image or video content 0445 if (KPABase::isVideo(currentFile)) { 0446 QString videoFile = createVideo(currentFile); 0447 QString videoBase = videoFile.replace(QRegExp(QString::fromLatin1("\\..*")), QString::fromLatin1("")); 0448 if (m_setup.inlineMovies()) 0449 if (m_setup.html5Video()) 0450 content.replace(QString::fromLatin1("**IMAGE_OR_VIDEO**"), QString::fromLatin1("<video controls><source src=\"%4\" type=\"video/mp4\" /><source src=\"%5\" type=\"video/ogg\" /><object data=\"%1\"><img src=\"%2\" alt=\"download\"/></object></video><a href=\"%3\"><img src=\"download.png\" /></a>").arg(QString::fromLatin1("%1.mp4").arg(videoBase)).arg(createImage(current, 256)).arg(QString::fromLatin1("%1.mp4").arg(videoBase)).arg(QString::fromLatin1("%1.mp4").arg(videoBase)).arg(QString::fromLatin1("%1.ogg").arg(videoBase))); 0451 else 0452 content.replace(QString::fromLatin1("**IMAGE_OR_VIDEO**"), QString::fromLatin1("<object data=\"%1\"><img src=\"%2\"/></object>" 0453 "<a href=\"%3\"><img src=\"download.png\"/></a>") 0454 .arg(videoFile) 0455 .arg(createImage(current, 256)) 0456 .arg(videoFile)); 0457 else 0458 content.replace(QString::fromLatin1("**IMAGE_OR_VIDEO**"), QString::fromLatin1("<a href=\"**NEXTPAGE**\"><img src=\"%2\"/></a>" 0459 "<a href=\"%1\"><img src=\"download.png\"/></a>") 0460 .arg(videoFile, createImage(current, 256))); 0461 } else 0462 content.replace(QString::fromLatin1("**IMAGE_OR_VIDEO**"), 0463 QString::fromLatin1("<a href=\"**NEXTPAGE**\"><img src=\"%1\" alt=\"%1\"/></a>") 0464 .arg(createImage(current, width))); 0465 0466 // -------------------------------------------------- Links 0467 QString link; 0468 0469 // prev link 0470 if (!prev.isNull()) 0471 link = i18n("<a href=\"%1\">prev</a>", namePage(width, height, prev)); 0472 else 0473 link = i18n("prev"); 0474 content.replace(QString::fromLatin1("**PREV**"), link); 0475 0476 // PENDING(blackie) These next 5 line also exists exactly like that in HTMLGenerator::Generator::generateIndexPage. Please refactor. 0477 // prevfile 0478 if (!prev.isNull()) 0479 link = namePage(width, height, prev); 0480 else 0481 link = i18n("prev"); 0482 content.replace(QString::fromLatin1("**PREVFILE**"), link); 0483 0484 // index link 0485 link = i18n("<a href=\"index-%1.html\">index</a>", ImageSizeCheckBox::text(width, height, true)); 0486 content.replace(QString::fromLatin1("**INDEX**"), link); 0487 0488 // indexfile 0489 link = QString::fromLatin1("index-%1.html").arg(ImageSizeCheckBox::text(width, height, true)); 0490 content.replace(QString::fromLatin1("**INDEXFILE**"), link); 0491 0492 // Next Link 0493 if (!next.isNull()) 0494 link = i18n("<a href=\"%1\">next</a>", namePage(width, height, next)); 0495 else 0496 link = i18n("next"); 0497 content.replace(QString::fromLatin1("**NEXT**"), link); 0498 0499 // Nextfile 0500 if (!next.isNull()) 0501 link = namePage(width, height, next); 0502 else 0503 link = i18n("next"); 0504 content.replace(QString::fromLatin1("**NEXTFILE**"), link); 0505 0506 if (!next.isNull()) 0507 link = namePage(width, height, next); 0508 else 0509 link = QString::fromLatin1("index-%1.html").arg(ImageSizeCheckBox::text(width, height, true)); 0510 0511 content.replace(QString::fromLatin1("**NEXTPAGE**"), link); 0512 0513 // -------------------------------------------------- Resolutions 0514 QString resolutions; 0515 const QList<ImageSizeCheckBox *> &actRes = m_setup.activeResolutions(); 0516 if (actRes.count() > 1) { 0517 for (QList<ImageSizeCheckBox *>::ConstIterator sizeIt = actRes.begin(); 0518 sizeIt != actRes.end(); ++sizeIt) { 0519 int w = (*sizeIt)->width(); 0520 int h = (*sizeIt)->height(); 0521 QString page = namePage(w, h, currentFile); 0522 QString text = (*sizeIt)->text(false); 0523 resolutions += QString::fromLatin1(" "); 0524 0525 if (width == w && height == h) 0526 resolutions += text; 0527 else 0528 resolutions += QString::fromLatin1("<a href=\"%1\">%2</a>").arg(page).arg(text); 0529 } 0530 } 0531 content.replace(QString::fromLatin1("**RESOLUTIONS**"), resolutions); 0532 0533 // -------------------------------------------------- Copyright 0534 QString copyright; 0535 0536 if (!m_setup.copyright().isEmpty()) 0537 copyright = QString::fromLatin1("© %1").arg(m_setup.copyright()); 0538 else 0539 copyright = QString::fromLatin1(" "); 0540 content.replace(QString::fromLatin1("**COPYRIGHT**"), QString::fromLatin1("%1").arg(copyright)); 0541 0542 // -------------------------------------------------- Description 0543 QString description = populateDescription(DB::ImageDB::instance()->categoryCollection()->categories(), info); 0544 0545 if (!description.isEmpty()) 0546 content.replace(QString::fromLatin1("**DESCRIPTION**"), QString::fromLatin1("<ul>\n%1\n</ul>").arg(description)); 0547 else 0548 content.replace(QString::fromLatin1("**DESCRIPTION**"), QString::fromLatin1("")); 0549 0550 // -------------------------------------------------- write to file 0551 QString fileName = m_tempDir.filePath(namePage(width, height, currentFile)); 0552 bool ok = writeToFile(fileName, content); 0553 if (!ok) 0554 return false; 0555 0556 return true; 0557 } 0558 0559 QString HTMLGenerator::Generator::namePage(int width, int height, const DB::FileName &fileName) 0560 { 0561 QString name = m_filenameMapper.uniqNameFor(fileName); 0562 QString base = QFileInfo(name).completeBaseName(); 0563 return QString::fromLatin1("%1-%2.html").arg(base, ImageSizeCheckBox::text(width, height, true)); 0564 } 0565 0566 QString HTMLGenerator::Generator::nameImage(const DB::FileName &fileName, int size) 0567 { 0568 QString name = m_filenameMapper.uniqNameFor(fileName); 0569 QString base = QFileInfo(name).completeBaseName(); 0570 if (size == maxImageSize() && !KPABase::isVideo(fileName)) { 0571 if (name.endsWith(QString::fromLatin1(".jpg"), Qt::CaseSensitive) || name.endsWith(QString::fromLatin1(".jpeg"), Qt::CaseSensitive)) 0572 return name; 0573 else 0574 return base + QString::fromLatin1(".jpg"); 0575 } else if (size == maxImageSize() && KPABase::isVideo(fileName)) { 0576 return name; 0577 } else 0578 return QString::fromLatin1("%1-%2.jpg").arg(base).arg(size); 0579 } 0580 0581 QString HTMLGenerator::Generator::createImage(const DB::FileName &fileName, int size) 0582 { 0583 const DB::ImageInfoPtr info = DB::ImageDB::instance()->info(fileName); 0584 if (m_generatedFiles.contains(qMakePair(fileName, size))) { 0585 m_waitCounter--; 0586 } else { 0587 ImageManager::ImageRequest *request = new ImageManager::ImageRequest(fileName, QSize(size, size), 0588 info->angle(), this); 0589 request->setPriority(ImageManager::BatchTask); 0590 ImageManager::AsyncLoader::instance()->load(request); 0591 m_generatedFiles.insert(qMakePair(fileName, size)); 0592 } 0593 0594 return nameImage(fileName, size); 0595 } 0596 0597 QString HTMLGenerator::Generator::createVideo(const DB::FileName &fileName) 0598 { 0599 setValue(m_total - m_waitCounter); 0600 qApp->processEvents(); 0601 0602 QString baseName = nameImage(fileName, maxImageSize()); 0603 QString destName = m_tempDir.filePath(baseName); 0604 if (!m_copiedVideos.contains(fileName)) { 0605 if (m_setup.html5VideoGenerate()) { 0606 // TODO: shouldn't we use avconv library directly instead of KRun 0607 // TODO: should check that the avconv (ffmpeg takes the same parameters on older systems) and ffmpeg2theora exist 0608 // TODO: Figure out avconv parameters to get rid of ffmpeg2theora 0609 auto *uiParent = MainWindow::Window::theMainWindow(); 0610 const auto avCmd = QString::fromLatin1("%1 -y -i %2 -vcodec libx264 -b 250k -bt 50k -acodec libfaac -ab 56k -ac 2 -s %3 %4") 0611 .arg(m_avconv) 0612 .arg(fileName.absolute()) 0613 .arg(QString::fromLatin1("320x240")) 0614 .arg(destName.replace(QRegExp(QString::fromLatin1("\\..*")), QString::fromLatin1(".mp4"))); 0615 const auto f2tCmd = QString::fromLatin1("ffmpeg2theora -v 7 -o %1 -x %2 %3") 0616 .arg(destName.replace(QRegExp(QString::fromLatin1("\\..*")), QString::fromLatin1(".ogg"))) 0617 .arg(QString::fromLatin1("320")) 0618 .arg(fileName.absolute()); 0619 #if KIO_VERSION <= QT_VERSION_CHECK(5, 69, 0) 0620 KRun::runCommand(avCmd, uiParent); 0621 KRun::runCommand(f2tCmd, uiParent); 0622 #else 0623 KIO::CommandLauncherJob *avJob = new KIO::CommandLauncherJob(avCmd); 0624 avJob->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, uiParent)); 0625 avJob->start(); 0626 KIO::CommandLauncherJob *f2tJob = new KIO::CommandLauncherJob(f2tCmd); 0627 f2tJob->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, uiParent)); 0628 f2tJob->start(); 0629 #endif 0630 } else 0631 Utilities::copyOrOverwrite(fileName.absolute(), destName); 0632 m_copiedVideos.insert(fileName); 0633 } 0634 return baseName; 0635 } 0636 0637 QString HTMLGenerator::Generator::kimFileName(bool relative) 0638 { 0639 if (relative) 0640 return QString::fromLatin1("%2.kim").arg(m_setup.outputDir()); 0641 else 0642 return m_tempDir.filePath(QString::fromLatin1("%2.kim").arg(m_setup.outputDir())); 0643 } 0644 0645 bool HTMLGenerator::Generator::writeToFile(const QString &fileName, const QString &str) 0646 { 0647 QFile file(fileName); 0648 if (!file.open(QIODevice::WriteOnly)) { 0649 KMessageBox::error(this, i18n("Could not create file '%1'.", fileName), 0650 i18n("Could Not Create File")); 0651 return false; 0652 } 0653 0654 QByteArray data = str.toUtf8(); 0655 file.write(data); 0656 file.close(); 0657 return true; 0658 } 0659 0660 bool HTMLGenerator::Generator::linkIndexFile() 0661 { 0662 ImageSizeCheckBox *resolution = m_setup.activeResolutions()[0]; 0663 QString fromFile = QString::fromLatin1("index-%1.html") 0664 .arg(resolution->text(true)); 0665 fromFile = m_tempDir.filePath(fromFile); 0666 QString destFile = m_tempDir.filePath(QString::fromLatin1("index.html")); 0667 bool ok = Utilities::copyOrOverwrite(fromFile, destFile); 0668 if (!ok) { 0669 KMessageBox::error(this, i18n("<p>Unable to copy %1 to %2</p>", fromFile, destFile)); 0670 0671 return false; 0672 } 0673 return ok; 0674 } 0675 0676 void HTMLGenerator::Generator::slotCancelGenerate() 0677 { 0678 ImageManager::AsyncLoader::instance()->stop(this); 0679 m_waitCounter = 0; 0680 if (m_hasEnteredLoop) 0681 m_eventLoop->exit(); 0682 } 0683 0684 void HTMLGenerator::Generator::pixmapLoaded(ImageManager::ImageRequest *request, const QImage &image) 0685 { 0686 const DB::FileName fileName = request->databaseFileName(); 0687 const QSize imgSize = request->size(); 0688 const bool loadedOK = request->loadedOK(); 0689 0690 setValue(m_total - m_waitCounter); 0691 0692 m_waitCounter--; 0693 0694 int size = imgSize.width(); 0695 QString file = m_tempDir.filePath(nameImage(fileName, size)); 0696 0697 bool success = loadedOK && image.save(file, "JPEG"); 0698 if (!success) { 0699 // We better stop the imageloading. In case this is a full disk, we will just get all images loaded, while this 0700 // error box is showing, resulting in a bunch of error messages, and memory running out due to all the hanging 0701 // pixmapLoaded methods. 0702 slotCancelGenerate(); 0703 KMessageBox::error(this, i18n("Unable to write image '%1'.", file)); 0704 } 0705 0706 if (!KPABase::isVideo(fileName)) { 0707 try { 0708 auto imageInfo = DB::ImageDB::instance()->info(fileName); 0709 Exif::writeExifInfoToFile(fileName, file, imageInfo->description()); 0710 } catch (...) { 0711 } 0712 } 0713 0714 if (m_waitCounter == 0 && m_hasEnteredLoop) { 0715 m_eventLoop->exit(); 0716 } 0717 } 0718 0719 int HTMLGenerator::Generator::calculateSteps() 0720 { 0721 int count = m_setup.activeResolutions().count(); 0722 return m_setup.imageList().size() * (1 + count); // 1 thumbnail + 1 real image 0723 } 0724 0725 void HTMLGenerator::Generator::getThemeInfo(QString *baseDir, QString *name, QString *author) 0726 { 0727 *baseDir = m_setup.themePath(); 0728 KConfig themeconfig(QString::fromLatin1("%1/kphotoalbum.theme").arg(*baseDir), KConfig::SimpleConfig); 0729 KConfigGroup config = themeconfig.group("theme"); 0730 0731 *name = config.readEntry("Name"); 0732 *author = config.readEntry("Author"); 0733 } 0734 0735 int HTMLGenerator::Generator::maxImageSize() 0736 { 0737 int res = 0; 0738 for (QList<ImageSizeCheckBox *>::ConstIterator sizeIt = m_setup.activeResolutions().begin(); 0739 sizeIt != m_setup.activeResolutions().end(); ++sizeIt) { 0740 res = qMax(res, (*sizeIt)->width()); 0741 } 0742 return res; 0743 } 0744 0745 void HTMLGenerator::Generator::minImageSize(int &width, int &height) 0746 { 0747 width = height = 0; 0748 for (QList<ImageSizeCheckBox *>::ConstIterator sizeIt = m_setup.activeResolutions().begin(); 0749 sizeIt != m_setup.activeResolutions().end(); ++sizeIt) { 0750 if ((width == 0) && ((*sizeIt)->width() > 0)) { 0751 width = (*sizeIt)->width(); 0752 height = (*sizeIt)->height(); 0753 } else if ((*sizeIt)->width() > 0) { 0754 width = qMin(width, (*sizeIt)->width()); 0755 height = qMin(height, (*sizeIt)->height()); 0756 } 0757 } 0758 } 0759 0760 void HTMLGenerator::Generator::showBrowser() 0761 { 0762 if (m_setup.generateKimFile()) 0763 ImportExport::Export::showUsageDialog(); 0764 0765 if (!m_setup.baseURL().isEmpty()) 0766 new KRun(QUrl::fromUserInput(QString::fromLatin1("%1/%2/index.html").arg(m_setup.baseURL(), m_setup.outputDir())), 0767 MainWindow::Window::theMainWindow()); 0768 0769 m_eventLoop->exit(); 0770 } 0771 0772 QString HTMLGenerator::Generator::populateDescription(QList<DB::CategoryPtr> categories, const DB::ImageInfoPtr info) 0773 { 0774 QString description; 0775 0776 if (m_setup.includeCategory(QString::fromLatin1("**DATE**"))) 0777 description += QString::fromLatin1("<li> <b>%1</b> %2</li>").arg(i18n("Date"), info->date().toString()); 0778 0779 for (QList<DB::CategoryPtr>::Iterator it = categories.begin(); it != categories.end(); ++it) { 0780 if ((*it)->isSpecialCategory()) { 0781 continue; 0782 } 0783 0784 const auto name = (*it)->name(); 0785 const auto itemsOfCategory = info->itemsOfCategory(name); 0786 if (!itemsOfCategory.empty() && m_setup.includeCategory(name)) { 0787 const QStringList itemsList(itemsOfCategory.begin(), itemsOfCategory.end()); 0788 description += QStringLiteral(" <li> <b>%1:</b> %2</li>").arg(name, itemsList.join(QLatin1String(", "))); 0789 } 0790 } 0791 0792 if (!info->description().isEmpty() && m_setup.includeCategory(QString::fromLatin1("**DESCRIPTION**"))) { 0793 description += QString::fromLatin1(" <li> <b>Description:</b> %1</li>").arg(info->description()); 0794 } 0795 0796 return description; 0797 } 0798 0799 // vi:expandtab:tabstop=4 shiftwidth=4: 0800 0801 #include "moc_Generator.cpp"