File indexing completed on 2025-01-05 03:53:05

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2021-07-24
0007  * Description : a MJPEG frame generator.
0008  *
0009  * SPDX-FileCopyrightText: 2021-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  * SPDX-FileCopyrightText: 2021      by Quoc Hưng Tran <quochungtran1999 at gmail dot com>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 #include "mjpegframetask.h"
0017 
0018 // C++ includes
0019 
0020 #include <cmath>
0021 
0022 // Qt includes
0023 
0024 #include <QString>
0025 #include <QBuffer>
0026 #include <QApplication>
0027 #include <QIcon>
0028 
0029 // Local includes
0030 
0031 #include "digikam_debug.h"
0032 #include "previewloadthread.h"
0033 #include "frameutils.h"
0034 #include "frameosd.h"
0035 #include "vidslidesettings.h"
0036 
0037 namespace DigikamGenericMjpegStreamPlugin
0038 {
0039 
0040 class Q_DECL_HIDDEN MjpegFrameTask::Private
0041 {
0042 public:
0043 
0044     explicit Private(const MjpegStreamSettings& set)
0045         : settings    (set),
0046           failedToLoad(false)
0047     {
0048         VidSlideSettings::VidType type = (VidSlideSettings::VidType)settings.outSize;
0049 
0050         /**
0051          * NOTE: QIcon depend of X11 under Linux which is not re-rentrant.
0052          * Load this image here in first from main thread.
0053          */
0054         brokenImg = QIcon::fromTheme(QLatin1String("view-preview")).pixmap(VidSlideSettings::videoSizeFromType(type)).toImage();
0055         endImg    = QIcon::fromTheme(QLatin1String("window-close")).pixmap(VidSlideSettings::videoSizeFromType(type)).toImage();
0056     }
0057 
0058     MjpegStreamSettings settings;     ///< The MJPEG stream settings.
0059     QImage              brokenImg;    ///< Image to push as frame if current item from list cannot be loaded.
0060     QImage              endImg;       ///< Image to push as frame when stream is complete.
0061     bool                failedToLoad; ///< determinate if image is loaded
0062 };
0063 
0064 MjpegFrameTask::MjpegFrameTask(const MjpegStreamSettings& settings)
0065     : ActionJob(nullptr),
0066       d        (new Private(settings))
0067 {
0068 }
0069 
0070 MjpegFrameTask::~MjpegFrameTask()
0071 {
0072     delete d;
0073 }
0074 
0075 QByteArray MjpegFrameTask::imageToJPEGArray(const QImage& frame) const
0076 {
0077     QByteArray outbuf;
0078     QBuffer buffer(&outbuf);
0079     buffer.open(QIODevice::WriteOnly);
0080     frame.save(&buffer, "JPEG", d->settings.quality);
0081 
0082     return outbuf;
0083 }
0084 
0085 QImage MjpegFrameTask::loadImageFromPreviewCache(const QString& path) const
0086 {
0087     QImage qimg;
0088     qCDebug(DIGIKAM_GENERAL_LOG) << "MjpegStream: Generate frame for" << path;
0089 
0090     DImg dimg = PreviewLoadThread::loadHighQualitySynchronously(path);
0091 
0092     if (dimg.isNull())
0093     {
0094         // Generate an error frame.
0095 
0096         qimg            = d->brokenImg;
0097         d->failedToLoad = true;
0098         qCWarning(DIGIKAM_GENERAL_LOG) << "MjpegStream: Failed to load" << path;
0099     }
0100     else
0101     {
0102         // Generate real preview frame.
0103 
0104         qimg = dimg.copyQImage();
0105     }
0106 
0107     // Resize output image to the wanted dimensions.
0108 
0109     VidSlideSettings::VidType type = (VidSlideSettings::VidType)d->settings.outSize;
0110     qimg                           = FrameUtils::makeScaledImage(qimg, VidSlideSettings::videoSizeFromType(type));
0111 
0112     return qimg;
0113 }
0114 
0115 void MjpegFrameTask::run()
0116 {
0117     QImage qiimg;   // Current image in stream.
0118     QImage qtimg;   // Current transition image.
0119     QImage qoimg;   // Next image in stream.
0120 
0121     FrameOsd osd;
0122 
0123     VidSlideSettings::VidType type     = (VidSlideSettings::VidType)d->settings.outSize;
0124     QSize JPEGsize                     = VidSlideSettings::videoSizeFromType(type);
0125     int imgFrames                      = d->settings.delay * d->settings.rate;
0126     bool oneLoopDone                   = false;
0127 
0128     TransitionMngr transmngr;
0129     transmngr.setOutputSize(JPEGsize);
0130 
0131     EffectMngr effmngr;
0132     effmngr.setOutputSize(JPEGsize);
0133     effmngr.setFrames(imgFrames);               // Ex: 30 frames at 10 img/s => 3 s of effect
0134 
0135     do
0136     {
0137         // To stream in loop forever.
0138 
0139         for (int i = 0 ; ((i < d->settings.inputImages.count()) && !m_cancel) ; ++i)
0140         {
0141             // One loop stream all items one by one from the ordered list
0142 
0143             if ((i == 0) && !oneLoopDone)
0144             {
0145                 // If we use transition, the first item at the first loop must be merged from a black image.
0146 
0147                 qiimg = FrameUtils::makeFramedImage(QString(), JPEGsize);
0148             }
0149 
0150             QString ofile;
0151 
0152             if (i < d->settings.inputImages.count())
0153             {
0154                 // The current item to pass to the next stage from a transition
0155 
0156                 ofile = d->settings.inputImages[i].toLocalFile();
0157             }
0158 
0159             qoimg      = loadImageFromPreviewCache(ofile);
0160 
0161             // Apply transition between images
0162 
0163             transmngr.setInImage(qiimg);
0164             transmngr.setOutImage(qoimg);
0165             transmngr.setTransition(d->settings.transition);
0166 
0167             int ttmout = 0;
0168 
0169             do
0170             {
0171                 // Loop over all stages to make the transition
0172 
0173                 qtimg = transmngr.currentFrame(ttmout);
0174 
0175                 Q_EMIT signalFrameChanged(imageToJPEGArray(qtimg));
0176 
0177                 QThread::msleep(lround(1000.0 / d->settings.rate));
0178             }
0179             while ((ttmout != -1) && !m_cancel);
0180 
0181             // Append OSD overlay on frame generated by effect.
0182 
0183             QUrl itemUrl = d->settings.inputImages[i];
0184 
0185             // Apply effect on frame
0186 
0187             int count  = 0;
0188             int itmout = 0;
0189             effmngr.setImage(qoimg);
0190             effmngr.setEffect(d->settings.effect);
0191 
0192             do
0193             {
0194                 // Loop over all stages to make the effect
0195 
0196                 qiimg = effmngr.currentFrame(itmout);
0197 
0198                 if (!d->failedToLoad)
0199                 {
0200                     if ((JPEGsize.width() >= 1024) && (JPEGsize.height() >= 576))
0201                     {
0202                         osd.insertOsdToFrame(qiimg,
0203                                              itemUrl,
0204                                              d->settings.osdSettings,
0205                                              d->settings.iface);
0206                     }
0207                 }
0208                 else
0209                 {
0210                     osd.insertMessageOsdToFrame(qiimg,
0211                                                 JPEGsize,
0212                                                 QLatin1String("Failed to load image"));
0213                 }
0214 
0215                 Q_EMIT signalFrameChanged(imageToJPEGArray(qiimg));
0216 
0217                 count++;
0218 
0219                 QThread::msleep(lround(1000.0 / d->settings.rate));
0220             }
0221             while ((count < imgFrames) && !m_cancel);
0222 
0223             d->failedToLoad = false;
0224             oneLoopDone     = true;        // At least one loop is done.
0225         }
0226     }
0227     while (!m_cancel && d->settings.loop);
0228 
0229     osd.insertMessageOsdToFrame(d->endImg,
0230                                 JPEGsize,
0231                                 QLatin1String("End of stream"));
0232 
0233     Q_EMIT signalFrameChanged(imageToJPEGArray(d->endImg));
0234     qCDebug(DIGIKAM_GENERAL_LOG) << "MjpegStream: end of stream";
0235 
0236     Q_EMIT signalDone();
0237 }
0238 
0239 } // namespace DigikamGenericMjpegStreamPlugin
0240 
0241 #include "moc_mjpegframetask.cpp"