File indexing completed on 2025-01-19 03:53:16

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2019-09-18
0007  * Description : DarkTable raw import plugin.
0008  *
0009  * SPDX-FileCopyrightText: 2019-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  *
0011  * Lua script inspired from Darktable Gimp plugin by Tobias Ellinghaus
0012  *
0013  * SPDX-License-Identifier: GPL-2.0-or-later
0014  *
0015  * ============================================================ */
0016 
0017 #include "rawimportdarktableplugin.h"
0018 
0019 // Qt includes
0020 
0021 #include <QMessageBox>
0022 #include <QApplication>
0023 #include <QPointer>
0024 #include <QByteArray>
0025 #include <QTextStream>
0026 #include <QFileInfo>
0027 #include <QSettings>
0028 #include <QTemporaryFile>
0029 
0030 // KDE includes
0031 
0032 #include <klocalizedstring.h>
0033 
0034 // Local includes
0035 
0036 #include "digikam_debug.h"
0037 #include "digikam_globals_p.h"      // For KF6::Ki18n deprecated
0038 #include "dimg.h"
0039 #include "filteraction.h"
0040 #include "dfileoperations.h"
0041 #include "loadingdescription.h"
0042 
0043 namespace DigikamRawImportDarkTablePlugin
0044 {
0045 
0046 class Q_DECL_HIDDEN DarkTableRawImportPlugin::Private
0047 {
0048 public:
0049 
0050     explicit Private()
0051       : darktable(nullptr)
0052     {
0053     }
0054 
0055     static const QString luaScriptData;
0056 
0057     QProcess*            darktable;
0058     DImg                 decoded;
0059     LoadingDescription   props;
0060     QString              tempName;
0061     QTemporaryFile       luaFile;
0062 };
0063 
0064 const QString DarkTableRawImportPlugin::Private::luaScriptData = QLatin1String(
0065     "\n"                                                                                                    \
0066     "local dt = require \"darktable\"\n"                                                                    \
0067     "\n"                                                                                                    \
0068     "local min_api_version = \"2.1.0\"\n"                                                                   \
0069     "if dt.configuration.api_version_string < min_api_version then\n"                                       \
0070     "  dt.print(\"the exit export script requires at least darktable version 1.7.0\")\n"                    \
0071     "  dt.print_error(\"the exit export script requires at least darktable version 1.7.0\")\n"              \
0072     "  return\n"                                                                                            \
0073     "else\n"                                                                                                \
0074     "  dt.print(\"closing darktable will export the image and make image editor load it\")\n"               \
0075     "end\n"                                                                                                 \
0076     "\n"                                                                                                    \
0077     "local export_filename = dt.preferences.read(\"export_on_exit\", \"export_filename\", \"string\")\n"    \
0078     "\n"                                                                                                    \
0079     "function exit_function()\n"                                                                            \
0080     "  -- safegurad against someone using this with their library containing 50k images\n"                  \
0081     "  if #dt.database > 1 then\n"                                                                          \
0082     "    dt.print_error(\"too many images, only exporting the first\")\n"                                   \
0083     "  -- return\n"                                                                                         \
0084     "  end\n"                                                                                               \
0085     "\n"                                                                                                    \
0086     "  -- change the view first to force writing of the history stack\n"                                    \
0087     "  dt.gui.current_view(dt.gui.views.lighttable)\n"                                                      \
0088     "  -- now export\n"                                                                                     \
0089     "  local format = dt.new_format(\"png\")\n"                                                             \
0090     "  format.max_width = 0\n"                                                                              \
0091     "  format.max_height = 0\n"                                                                             \
0092     "  -- lets have the export in a loop so we could easily support > 1 images\n"                           \
0093     "  for _, image in ipairs(dt.database) do\n"                                                            \
0094     "    dt.print_error(\"exporting `\"..tostring(image)..\"' to `\"..export_filename..\"'\")\n"            \
0095     "    format:write_image(image, export_filename)\n"                                                      \
0096     "    break -- only export one image. see above for the reason\n"                                        \
0097     "  end\n"                                                                                               \
0098     "end\n"                                                                                                 \
0099     "\n"                                                                                                    \
0100     "if dt.configuration.api_version_string >= \"6.2.1\" then\n"                                            \
0101     "dt.register_event(\"fileraw\", \"exit\", exit_function)\n"                                             \
0102     "else\n"                                                                                                \
0103     "dt.register_event(\"exit\", exit_function)\n"                                                          \
0104     "end\n"                                                                                                 \
0105 );
0106 
0107 DarkTableRawImportPlugin::DarkTableRawImportPlugin(QObject* const parent)
0108     : DPluginRawImport(parent),
0109       d               (new Private)
0110 {
0111     d->luaFile.open();
0112     QTextStream stream(&d->luaFile);
0113     stream << d->luaScriptData;
0114     stream.flush();
0115 }
0116 
0117 DarkTableRawImportPlugin::~DarkTableRawImportPlugin()
0118 {
0119     delete d;
0120 }
0121 
0122 QString DarkTableRawImportPlugin::name() const
0123 {
0124     return QString::fromUtf8("Raw Import using DarkTable");
0125 }
0126 
0127 QString DarkTableRawImportPlugin::iid() const
0128 {
0129     return QLatin1String(DPLUGIN_IID);
0130 }
0131 
0132 QIcon DarkTableRawImportPlugin::icon() const
0133 {
0134     return QIcon::fromTheme(QLatin1String("image-x-adobe-dng"));
0135 }
0136 
0137 QString DarkTableRawImportPlugin::description() const
0138 {
0139     return QString::fromUtf8("A RAW import plugin based on DarkTable");
0140 }
0141 
0142 QString DarkTableRawImportPlugin::details() const
0143 {
0144     return QString::fromUtf8("<p>This RAW Import plugin use DarkTable tool to pre-process file in Image Editor.</p>"
0145                              "<p>It requires at least DarkTable version 1.7.0 to work.</p>"
0146                              "<p>See DarkTable web site for details: <a href='https://www.darktable.org/'>https://www.darktable.org/</a></p>");
0147 }
0148 
0149 QString DarkTableRawImportPlugin::handbookSection() const
0150 {
0151     return QLatin1String("setup_application");
0152 }
0153 
0154 QString DarkTableRawImportPlugin::handbookChapter() const
0155 {
0156     return QLatin1String("editor_settings");
0157 }
0158 
0159 QString DarkTableRawImportPlugin::handbookReference() const
0160 {
0161     return QLatin1String("setup-raw");
0162 }
0163 
0164 QList<DPluginAuthor> DarkTableRawImportPlugin::authors() const
0165 {
0166     return QList<DPluginAuthor>()
0167             << DPluginAuthor(QString::fromUtf8("Gilles Caulier"),
0168                              QString::fromUtf8("caulier dot gilles at gmail dot com"),
0169                              QString::fromUtf8("(C) 2019-2022"))
0170             ;
0171 }
0172 
0173 void DarkTableRawImportPlugin::setup(QObject* const /*parent*/)
0174 {
0175     // Nothing to do
0176 }
0177 
0178 QString DarkTableRawImportPlugin::getRawProgram() const
0179 {
0180     return DFileOperations::findExecutable(QLatin1String("darktable"));
0181 }
0182 
0183 bool DarkTableRawImportPlugin::run(const QString& filePath, const DRawDecoding& /*def*/)
0184 {
0185     QFileInfo fileInfo(filePath);
0186     d->props     = LoadingDescription(fileInfo.filePath(), LoadingDescription::ConvertForEditor);
0187     d->decoded   = DImg();
0188 
0189     QTemporaryFile tempFile;
0190     tempFile.open();
0191     d->tempName  = tempFile.fileName();
0192 
0193     d->darktable = new QProcess(this);
0194     d->darktable->setProcessChannelMode(QProcess::MergedChannels);
0195     d->darktable->setWorkingDirectory(fileInfo.path());
0196     d->darktable->setProcessEnvironment(adjustedEnvironmentForAppImage());
0197 
0198     connect(d->darktable, SIGNAL(errorOccurred(QProcess::ProcessError)),
0199             this, SLOT(slotErrorOccurred(QProcess::ProcessError)));
0200 
0201     connect(d->darktable, SIGNAL(finished(int,QProcess::ExitStatus)),
0202             this, SLOT(slotProcessFinished(int,QProcess::ExitStatus)));
0203 
0204     connect(d->darktable, SIGNAL(readyRead()),
0205             this, SLOT(slotProcessReadyRead()));
0206 
0207     // --------
0208 
0209     d->darktable->setProgram(getRawProgram());
0210     d->darktable->setArguments(QStringList() << QLatin1String("--library")
0211                                              << QLatin1String(":memory:")                                  // Run DarkTable to process only one file
0212                                              << QLatin1String("--luacmd")
0213                                              << QString::fromUtf8("dofile('%1')")
0214                                                 .arg(d->luaFile.fileName())                                // LUA script to run in DarkTable
0215                                              << QLatin1String("--conf")
0216                                              << QLatin1String("plugins/lighttable/export/icctype=3")       // Output color-space
0217                                              << QLatin1String("--conf")
0218                                              << QString::fromUtf8("lua/export_on_exit/export_filename=%1")
0219                                                 .arg(d->tempName)                                          // Output file
0220                                              << filePath);                                                 // Input file
0221 
0222     qCDebug(DIGIKAM_GENERAL_LOG) << "DarkTable arguments:" << d->darktable->arguments();
0223 
0224     d->darktable->start();
0225 
0226     return d->darktable->waitForStarted(10000);
0227 }
0228 
0229 void DarkTableRawImportPlugin::slotErrorOccurred(QProcess::ProcessError error)
0230 {
0231     switch (error)
0232     {
0233         case QProcess::FailedToStart:
0234         {
0235             qCDebug(DIGIKAM_GENERAL_LOG) << "DarkTable :: Process has failed to start";
0236             break;
0237         }
0238 
0239         case QProcess::Crashed:
0240         {
0241             qCDebug(DIGIKAM_GENERAL_LOG) << "DarkTable :: Process has crashed";
0242             break;
0243         }
0244 
0245         case QProcess::Timedout:
0246         {
0247             qCDebug(DIGIKAM_GENERAL_LOG) << "DarkTable :: Process time-out";
0248             break;
0249         }
0250 
0251         case QProcess::WriteError:
0252         {
0253             qCDebug(DIGIKAM_GENERAL_LOG) << "DarkTable :: Process write error";
0254             break;
0255         }
0256 
0257         case QProcess::ReadError:
0258         {
0259             qCDebug(DIGIKAM_GENERAL_LOG) << "DarkTable :: Process read error";
0260             break;
0261         }
0262 
0263         default:
0264         {
0265             qCDebug(DIGIKAM_GENERAL_LOG) << "DarkTable :: Process error unknown";
0266             break;
0267         }
0268     }
0269 }
0270 
0271 void DarkTableRawImportPlugin::slotProcessFinished(int code, QProcess::ExitStatus status)
0272 {
0273     qCDebug(DIGIKAM_GENERAL_LOG) << "DarkTable :: return code:" << code << ":: Exit status:" << status;
0274 
0275     d->decoded = DImg(d->tempName);
0276     d->decoded.setAttribute(QLatin1String("isReadOnly"), true);
0277 
0278     if (d->decoded.isNull())
0279     {
0280         QString message = i18n("Error to import RAW image with DarkTable\nClose this dialog to load RAW image with native import tool");
0281         QMessageBox::information(nullptr, qApp->applicationName(), message);
0282 
0283         qCDebug(DIGIKAM_GENERAL_LOG) << "Decoded image is null! Load with Native tool...";
0284         qCDebug(DIGIKAM_GENERAL_LOG) << d->props.filePath;
0285 
0286         Q_EMIT signalLoadRaw(d->props);
0287     }
0288     else
0289     {
0290         qCDebug(DIGIKAM_GENERAL_LOG) << "Decoded image is not null...";
0291         qCDebug(DIGIKAM_GENERAL_LOG) << d->props.filePath;
0292         d->props = LoadingDescription(d->tempName, LoadingDescription::ConvertForEditor);
0293 
0294         FilterAction action(QLatin1String("darktable:RawConverter"), 1, FilterAction::DocumentedHistory);
0295         action.setDisplayableName(QString::fromUtf8(I18N_NOOP("DarkTable Raw Conversion")));
0296         d->decoded.addFilterAction(action);
0297 
0298         Q_EMIT signalDecodedImage(d->props, d->decoded);
0299     }
0300 
0301     delete d->darktable;
0302     d->darktable = nullptr;
0303 
0304     QFile::remove(d->tempName);
0305 }
0306 
0307 void DarkTableRawImportPlugin::slotProcessReadyRead()
0308 {
0309     QByteArray data   = d->darktable->readAllStandardError();
0310     QStringList lines = QString::fromUtf8(data).split(QLatin1Char('\n'), QT_SKIP_EMPTY_PARTS);
0311 
0312     Q_FOREACH (const QString& one, lines)
0313     {
0314         qCDebug(DIGIKAM_GENERAL_LOG) << "DarkTable ::" << one;
0315     }
0316 }
0317 
0318 } // namespace DigikamRawImportDarkTablePlugin
0319 
0320 #include "moc_rawimportdarktableplugin.cpp"