File indexing completed on 2025-01-05 03:51:27
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2014-05-24 0007 * Description : user script batch tool. 0008 * 0009 * SPDX-FileCopyrightText: 2009-2024 by Gilles Caulier <caulier dot gilles at gmail dot com> 0010 * SPDX-FileCopyrightText: 2014 by Hubert Law <hhclaw dot eb at gmail dot com> 0011 * 0012 * SPDX-License-Identifier: GPL-2.0-or-later 0013 * 0014 * ============================================================ */ 0015 0016 #include "userscript.h" 0017 0018 // Qt includes 0019 0020 #include <QDir> 0021 #include <QFile> 0022 #include <QLabel> 0023 #include <QWidget> 0024 #include <QProcess> 0025 #include <QPlainTextEdit> 0026 0027 // KDE includes 0028 0029 #include <klocalizedstring.h> 0030 0031 // Local includes 0032 0033 #include "digikam_globals.h" 0034 #include "digikam_debug.h" 0035 #include "dimg.h" 0036 #include "dcombobox.h" 0037 #include "dmetadata.h" 0038 #include "tagscache.h" 0039 #include "dlayoutbox.h" 0040 #include "filereadwritelock.h" 0041 0042 namespace DigikamBqmUserScriptPlugin 0043 { 0044 0045 class Q_DECL_HIDDEN UserScript::Private 0046 { 0047 public: 0048 0049 enum OutputFileType 0050 { 0051 Input = 0, 0052 JPEG, 0053 PNG, 0054 TIFF, 0055 JPEG2000, 0056 JPEGXL, 0057 AVIF, 0058 HEIF, 0059 WEBP 0060 }; 0061 0062 public: 0063 0064 explicit Private() 0065 : comboBox (nullptr), 0066 textEdit (nullptr), 0067 changeSettings(true) 0068 { 0069 } 0070 0071 DComboBox* comboBox; 0072 QPlainTextEdit* textEdit; 0073 0074 bool changeSettings; 0075 }; 0076 0077 UserScript::UserScript(QObject* const parent) 0078 : BatchTool(QLatin1String("UserScript"), CustomTool, parent), 0079 d (new Private) 0080 { 0081 } 0082 0083 UserScript::~UserScript() 0084 { 0085 delete d; 0086 } 0087 0088 BatchTool* UserScript::clone(QObject* const parent) const 0089 { 0090 return new UserScript(parent); 0091 } 0092 0093 void UserScript::registerSettingsWidget() 0094 { 0095 DVBox* const vbox = new DVBox; 0096 0097 QLabel* const label1 = new QLabel(vbox); 0098 label1->setText(i18n("Output file type:")); 0099 0100 d->comboBox = new DComboBox(vbox); 0101 d->comboBox->insertItem(Private::Input, i18n("Same as input")); 0102 d->comboBox->insertItem(Private::JPEG, i18n("JPEG")); 0103 d->comboBox->insertItem(Private::PNG, i18n("PNG")); 0104 d->comboBox->insertItem(Private::TIFF, i18n("TIFF")); 0105 d->comboBox->insertItem(Private::JPEG2000, i18n("JPEG 2000")); 0106 d->comboBox->insertItem(Private::JPEGXL, i18n("JPEG XL")); 0107 d->comboBox->insertItem(Private::AVIF, i18n("AVIF")); 0108 d->comboBox->insertItem(Private::HEIF, i18n("HEIF")); 0109 d->comboBox->insertItem(Private::WEBP, i18n("WEBP")); 0110 d->comboBox->setDefaultIndex(Private::Input); 0111 0112 QLabel* const label2 = new QLabel(vbox); 0113 label2->setText(i18n("Shell Script:")); 0114 0115 d->textEdit = new QPlainTextEdit(vbox); 0116 d->textEdit->setPlaceholderText(i18n("Enter script for execution. Use $INPUT and $OUTPUT for input / output filenames (with " 0117 "special characters escaped). These would be substituted before shell execution.")); 0118 0119 QLabel* const label3 = new QLabel(i18n("<b>Note:</b> Environment variables TITLE, COMMENTS, COLORLABEL, PICKLABEL, " 0120 "RATING and TAGSPATH (separated by ;) are available."), vbox); 0121 label3->setWordWrap(true); 0122 label3->setFrameStyle(QFrame::StyledPanel | QFrame::Raised); 0123 0124 QLabel* const space = new QLabel(vbox); 0125 vbox->setStretchFactor(space, 10); 0126 0127 m_settingsWidget = vbox; 0128 0129 connect(d->comboBox, SIGNAL(activated(int)), 0130 this, SLOT(slotSettingsChanged())); 0131 0132 connect(d->textEdit, SIGNAL(textChanged()), 0133 this, SLOT(slotSettingsChanged())); 0134 0135 BatchTool::registerSettingsWidget(); 0136 } 0137 0138 BatchToolSettings UserScript::defaultSettings() 0139 { 0140 BatchToolSettings settings; 0141 settings.insert(QLatin1String("Output filetype"), d->comboBox->defaultIndex()); 0142 settings.insert(QLatin1String("Script"), QString()); 0143 0144 return settings; 0145 } 0146 0147 void UserScript::slotAssignSettings2Widget() 0148 { 0149 d->changeSettings = false; 0150 d->comboBox->setCurrentIndex(settings()[QLatin1String("Output filetype")].toInt()); 0151 0152 QString txt = settings()[QLatin1String("Script")].toString(); 0153 0154 if (d->textEdit->toPlainText() != txt) 0155 { 0156 d->textEdit->setPlainText(txt); 0157 } 0158 0159 d->changeSettings = true; 0160 } 0161 0162 void UserScript::slotSettingsChanged() 0163 { 0164 if (d->changeSettings) 0165 { 0166 BatchToolSettings settings; 0167 settings.insert(QLatin1String("Output filetype"), d->comboBox->currentIndex()); 0168 settings.insert(QLatin1String("Script"), d->textEdit->toPlainText()); 0169 BatchTool::slotSettingsChanged(settings); 0170 } 0171 } 0172 0173 QString UserScript::outputSuffix () const 0174 { 0175 int filetype = settings()[QLatin1String("Output filetype")].toInt(); 0176 0177 switch (filetype) 0178 { 0179 case Private::JPEG: 0180 { 0181 return QLatin1String("jpg"); 0182 } 0183 0184 case Private::PNG: 0185 { 0186 return QLatin1String("png"); 0187 } 0188 0189 case Private::TIFF: 0190 { 0191 return QLatin1String("tif"); 0192 } 0193 0194 case Private::JPEG2000: 0195 { 0196 return QLatin1String("jp2"); 0197 } 0198 0199 case Private::JPEGXL: 0200 { 0201 return QLatin1String("jxl"); 0202 } 0203 0204 case Private::AVIF: 0205 { 0206 return QLatin1String("avif"); 0207 } 0208 0209 case Private::HEIF: 0210 { 0211 return QLatin1String("heic"); 0212 } 0213 0214 case Private::WEBP: 0215 { 0216 return QLatin1String("webp"); 0217 } 0218 0219 default: 0220 { 0221 break; 0222 } 0223 } 0224 0225 // Return "": use original type 0226 0227 return (BatchTool::outputSuffix()); 0228 } 0229 0230 bool UserScript::toolOperations() 0231 { 0232 QString script = settings()[QLatin1String("Script")].toString(); 0233 0234 if (script.isEmpty()) 0235 { 0236 setErrorDescription(i18n("User Script: No script.")); 0237 0238 return false; 0239 } 0240 0241 // Replace all occurrences of $INPUT and $OUTPUT in script to file names. Case sensitive. 0242 0243 script.replace(QLatin1String("$INPUT"), QLatin1Char('"') + 0244 QDir::toNativeSeparators(inputUrl().toLocalFile()) + 0245 QLatin1Char('"')); 0246 script.replace(QLatin1String("$OUTPUT"), QLatin1Char('"') + 0247 QDir::toNativeSeparators(outputUrl().toLocalFile()) + 0248 QLatin1Char('"')); 0249 0250 // Empties d->image, not to pass it to the next tool in chain 0251 0252 setImageData(DImg()); 0253 0254 QProcess process(this); 0255 0256 QProcessEnvironment env = adjustedEnvironmentForAppImage(); 0257 0258 QString tagPath = TagsCache::instance()->tagPaths(imageInfo().tagIds(), TagsCache::NoLeadingSlash, 0259 TagsCache::NoHiddenTags).join(QLatin1Char(';')); 0260 0261 // Populate env variables from metadata 0262 0263 env.insert(QLatin1String("COLORLABEL"), QString::number(imageInfo().colorLabel())); 0264 env.insert(QLatin1String("PICKLABEL"), QString::number(imageInfo().pickLabel())); 0265 env.insert(QLatin1String("RATING"), QString::number(imageInfo().rating())); 0266 env.insert(QLatin1String("COMMENTS"), imageInfo().comment()); 0267 env.insert(QLatin1String("TITLE"), imageInfo().title()); 0268 env.insert(QLatin1String("TAGSPATH"), tagPath); 0269 0270 process.setProcessEnvironment(env); 0271 0272 // call the shell script 0273 0274 #ifdef Q_OS_WIN 0275 0276 QString dir = QDir::temp().path(); 0277 SafeTemporaryFile* const temp = new SafeTemporaryFile(dir + QLatin1String("/UserScript-XXXXXX.cmd")); 0278 temp->setAutoRemove(false); 0279 temp->open(); 0280 QString scriptPath = temp->safeFilePath(); 0281 0282 // Crash fix: a QTemporaryFile is not properly closed until its destructor is called. 0283 0284 delete temp; 0285 0286 script.replace(QLatin1Char('\n'), QLatin1String("\r\n")); 0287 0288 QFile file(scriptPath); 0289 0290 if (file.open(QIODevice::WriteOnly)) 0291 { 0292 file.write(script.toUtf8()); 0293 file.close(); 0294 } 0295 else 0296 { 0297 setErrorDescription(i18n("User Script: File open error.")); 0298 0299 return false; 0300 } 0301 0302 process.start(QLatin1String("cmd.exe"), QStringList() << QLatin1String("/C") << scriptPath); 0303 0304 #else 0305 0306 process.start(QLatin1String("/bin/bash"), QStringList() << QLatin1String("-c") << script); 0307 0308 #endif 0309 0310 bool ret = true; 0311 0312 if (!process.waitForFinished(60000)) 0313 { 0314 setErrorDescription(i18n("User Script: Timeout from script.")); 0315 process.kill(); 0316 0317 ret = false; 0318 } 0319 0320 if (process.exitCode() == 0) 0321 { 0322 setErrorDescription(i18n("User Script: No error.")); 0323 } 0324 else if (process.exitCode() == -2) 0325 { 0326 setErrorDescription(i18n("User Script: Failed to start script.")); 0327 0328 ret = false; 0329 } 0330 else if (process.exitCode() == -1) 0331 { 0332 setErrorDescription(i18n("User Script: Script process crashed.")); 0333 0334 ret = false; 0335 } 0336 else if (process.exitCode() == 127) 0337 { 0338 setErrorDescription(i18n("User Script: Command not found.")); 0339 0340 ret = false; 0341 } 0342 else 0343 { 0344 setErrorDescription(i18n("User Script: Error code returned %1.", process.exitCode())); 0345 0346 ret = false; 0347 } 0348 0349 #ifdef Q_OS_WIN 0350 0351 file.remove(); 0352 0353 #endif 0354 0355 qCDebug(DIGIKAM_DPLUGIN_BQM_LOG) << "Script stdout" << process.readAllStandardOutput(); 0356 qCDebug(DIGIKAM_DPLUGIN_BQM_LOG) << "Script stderr" << process.readAllStandardError(); 0357 qCDebug(DIGIKAM_DPLUGIN_BQM_LOG) << "Script exit code:" << process.exitCode(); 0358 0359 return ret; 0360 } 0361 0362 } // namespace DigikamBqmUserScriptPlugin 0363 0364 #include "moc_userscript.cpp"