File indexing completed on 2025-03-09 03:52:04

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2017-05-25
0007  * Description : a tool to generate video slideshow from images.
0008  *
0009  * SPDX-FileCopyrightText: 2017-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  *
0011  * SPDX-License-Identifier: GPL-2.0-or-later
0012  *
0013  * ============================================================ */
0014 
0015 #include "vidslideoutputpage.h"
0016 
0017 // Qt includes
0018 
0019 #include <QIcon>
0020 #include <QLabel>
0021 #include <QUrl>
0022 #include <QTimer>
0023 #include <QWidget>
0024 #include <QApplication>
0025 #include <QStyle>
0026 #include <QComboBox>
0027 #include <QCheckBox>
0028 #include <QSpinBox>
0029 #include <QGridLayout>
0030 #include <QStandardItemModel>
0031 #include <QStandardPaths>
0032 
0033 // KDE includes
0034 
0035 #include <klocalizedstring.h>
0036 
0037 // Local includes
0038 
0039 #include "digikam_config.h"
0040 #include "digikam_debug.h"
0041 #include "frameosdwidget.h"
0042 #include "vidslidewizard.h"
0043 #include "dfileselector.h"
0044 #include "filesaveconflictbox.h"
0045 #include "ffmpeglauncher.h"
0046 #include "dexpanderbox.h"
0047 
0048 #ifdef HAVE_MEDIAPLAYER
0049 
0050 #   include "audplayerwdg.h"
0051 
0052 #endif
0053 
0054 namespace DigikamGenericVideoSlideShowPlugin
0055 {
0056 
0057 class Q_DECL_HIDDEN VidSlideOutputPage::Private
0058 {
0059 public:
0060 
0061     explicit Private(QWizard* const dialog)
0062     {
0063         wizard = dynamic_cast<VidSlideWizard*>(dialog);
0064 
0065         if (wizard)
0066         {
0067             settings = wizard->settings();
0068         }
0069     }
0070 
0071     DFileSelector*       audioUrl    = nullptr;
0072     DFileSelector*       destUrl     = nullptr;
0073     FileSaveConflictBox* conflictBox = nullptr;
0074     QComboBox*           playerVal   = nullptr;
0075     QComboBox*           formatVal   = nullptr;
0076     VidSlideWizard*      wizard      = nullptr;
0077     VidSlideSettings*    settings    = nullptr;
0078     QLabel*              duration    = nullptr;
0079     QTimer*              trigUpdate  = nullptr;
0080     QCheckBox*           equalize    = nullptr;
0081     QSpinBox*            strength    = nullptr;
0082     FrameOsdWidget*      frameOsd    = nullptr;
0083     DExpanderBox*        expanderBox = nullptr;
0084 
0085 #ifdef HAVE_MEDIAPLAYER
0086 
0087     AudPlayerWdg*        audioPlayer = nullptr;
0088 
0089 #endif
0090 
0091 };
0092 
0093 VidSlideOutputPage::VidSlideOutputPage(QWizard* const dialog, const QString& title)
0094     : DWizardPage(dialog, title),
0095       d          (new Private(dialog))
0096 {
0097     setObjectName(QLatin1String("OutputPage"));
0098 
0099     d->trigUpdate             = new QTimer(this);
0100     d->trigUpdate->setSingleShot(true);
0101 
0102     d->expanderBox   = new DExpanderBox;
0103     d->expanderBox->setObjectName(QLatin1String("VideoSlideShow Output Settings Expander"));
0104 
0105     // --------------------
0106 
0107     QWidget* const outputBox    = new QWidget(d->expanderBox);
0108     QGridLayout* const formGrid = new QGridLayout(outputBox);
0109 
0110     QLabel* const formatLabel   = new QLabel(outputBox);
0111     formatLabel->setWordWrap(false);
0112     formatLabel->setText(i18n("Media Container Format:"));
0113     d->formatVal                = new QComboBox(outputBox);
0114     d->formatVal->setEditable(false);
0115     d->formatVal->setToolTip(i18n("This control allows to choose the format of the media container to host video stream and soundtrack."));
0116     formatLabel->setBuddy(d->formatVal);
0117 
0118     QLabel* const playerLabel = new QLabel(outputBox);
0119     playerLabel->setWordWrap(false);
0120     playerLabel->setText(i18n("Open in Player:"));
0121     d->playerVal              = new QComboBox(outputBox);
0122     d->playerVal->setEditable(false);
0123 
0124     QMap<VidSlideSettings::VidPlayer, QString> map2                = VidSlideSettings::videoPlayerNames();
0125     QMap<VidSlideSettings::VidPlayer, QString>::const_iterator it2 = map2.constBegin();
0126 
0127     while (it2 != map2.constEnd())
0128     {
0129         d->playerVal->addItem(it2.value(), (int)it2.key());
0130         ++it2;
0131     }
0132 
0133 #ifndef HAVE_MEDIAPLAYER
0134 
0135     auto* const model = qobject_cast<QStandardItemModel*>(d->playerVal->model());
0136 
0137     if (model)
0138     {
0139         auto* const item = model->item(VidSlideSettings::INTERNAL);
0140 
0141         if (item)
0142         {
0143             item->setEnabled(false);
0144         }
0145     }
0146 
0147 #endif
0148 
0149     playerLabel->setBuddy(d->playerVal);
0150 
0151     formGrid->addWidget(formatLabel,     0, 0, 1, 1);
0152     formGrid->addWidget(d->formatVal,    0, 1, 1, 1);
0153     formGrid->addWidget(playerLabel,     1, 0, 1, 1);
0154     formGrid->addWidget(d->playerVal,    1, 1, 1, 1);
0155 
0156     d->expanderBox->addItem(outputBox,
0157                             QIcon::fromTheme(QLatin1String("video-mp4")),
0158                             i18n("Output"),
0159                             QLatin1String("Output"), true);
0160 
0161     // --------------------
0162 
0163     QWidget* const videoBox     = new QWidget(d->expanderBox);
0164     QGridLayout* const normGrid = new QGridLayout(videoBox);
0165 
0166     d->equalize                 = new QCheckBox(i18n("Equalize Video Luminosity"), videoBox);
0167 
0168     QLabel* const strengthLabel = new QLabel(videoBox);
0169     strengthLabel->setWordWrap(false);
0170     strengthLabel->setEnabled(false);
0171     strengthLabel->setText(i18n("Equalization Strength:"));
0172     d->strength                 = new QSpinBox(videoBox);
0173     d->strength->setEnabled(false);
0174     d->strength->setRange(0, 10);
0175     d->strength->setToolTip(i18n("This controls strength of equalization. Setting this option to zero effectively does nothing."));
0176     strengthLabel->setBuddy(d->strength);
0177 
0178     QLabel* const equalNote     = new QLabel(videoBox);
0179     equalNote->setWordWrap(true);
0180     equalNote->setText(i18n("<i>These settings apply temporal midway video equalization effect while "
0181                             "encoding frames as video. This adjusts the sequence of frames to have "
0182                             "the same histograms, while maintaining their dynamics as much as possible. "
0183                             "It’s useful for e.g. matching exposures from a timelapse sequence.</i>"));
0184 
0185     normGrid->addWidget(d->equalize,     0, 0, 1, 2);
0186     normGrid->addWidget(strengthLabel,   1, 0, 1, 1);
0187     normGrid->addWidget(d->strength,     1, 1, 1, 1);
0188     normGrid->addWidget(equalNote,       2, 0, 1, 2);
0189 
0190     d->expanderBox->addItem(videoBox,
0191                             QIcon::fromTheme(QLatin1String("view-media-equalizer")),
0192                             i18n("Frames Normalization"),
0193                             QLatin1String("Normalization"), true);
0194 
0195     // --------------------
0196 
0197     QWidget* const audioBox      = new QWidget(d->expanderBox);
0198     QGridLayout* const audioGrid = new QGridLayout(audioBox);
0199 
0200     QLabel* const audioLabel = new QLabel(audioBox);
0201     audioLabel->setWordWrap(false);
0202     audioLabel->setText(i18n("Soundtrack File:"));
0203 
0204     d->audioUrl              = new DFileSelector(audioBox);
0205     d->audioUrl->setFileDlgMode(QFileDialog::ExistingFile);
0206     d->audioUrl->setFileDlgOptions(QFileDialog::ReadOnly);
0207     d->audioUrl->setFileDlgFilter(QLatin1String("*.mp3 *.ogg *.wav *.flac *.m4a"));
0208     d->audioUrl->setFileDlgTitle(i18nc("@title:window", "Select Audio Track"));
0209     audioLabel->setBuddy(d->audioUrl);
0210 
0211     QLabel* const durationLabel = new QLabel(i18n("Soundtrack Duration:"), audioBox);
0212     d->duration                 = new QLabel(QLatin1String("---"), audioBox);
0213     d->duration->setAlignment(Qt::AlignRight);
0214 
0215 #ifdef HAVE_MEDIAPLAYER
0216 
0217     QLabel* const previewLabel = new QLabel(i18n("Soundtrack Preview:"), audioBox);
0218     d->audioPlayer             = new AudPlayerWdg(audioBox);
0219 
0220 #endif
0221 
0222     QLabel* const audioNote     = new QLabel(audioBox);
0223     audioNote->setWordWrap(true);
0224     audioNote->setText(i18n("<i>Notes about soundtrack: if the audio length is smaller than video, it will be "
0225                             "played in loop. If the audio length is largest than video, it will be trimmed. "
0226                             "Leave this setting empty if you don't want a soundtrack to the media.</i>"));
0227 
0228     audioGrid->addWidget(audioLabel,      0, 0, 1, 1);
0229     audioGrid->addWidget(d->audioUrl,     0, 1, 1, 2);
0230     audioGrid->addWidget(durationLabel,   1, 0, 1, 1);
0231     audioGrid->addWidget(d->duration,     1, 2, 1, 1);
0232 
0233 #ifdef HAVE_MEDIAPLAYER
0234 
0235     audioGrid->addWidget(previewLabel,    2, 0, 1, 1);
0236     audioGrid->addWidget(d->audioPlayer,  2, 2, 1, 1);
0237 
0238 #endif
0239 
0240     audioGrid->addWidget(audioNote,       3, 0, 1, 3);
0241     audioGrid->setColumnStretch(1, 10);
0242 
0243     d->expanderBox->addItem(audioBox,
0244                             QIcon::fromTheme(QLatin1String("audio-mp3")),
0245                             i18n("Audio Track"),
0246                             QLatin1String("Soundtrack"), true);
0247 
0248     // --------------------
0249 
0250     d->frameOsd = new FrameOsdWidget(d->expanderBox);
0251 
0252     d->expanderBox->addItem(d->frameOsd,
0253                             QIcon::fromTheme(QLatin1String("draw-text")),
0254                             i18n("On Screen Display"),
0255                             QLatin1String("OSD"), true);
0256 
0257     // --------------------
0258 
0259     QWidget* const destBox      = new QWidget(d->expanderBox);
0260     QGridLayout* const destGrid = new QGridLayout(destBox);
0261 
0262     QLabel* const fileLabel = new QLabel(destBox);
0263     fileLabel->setWordWrap(false);
0264     fileLabel->setText(i18n("Destination folder:"));
0265 
0266     d->destUrl              = new DFileSelector(destBox);
0267     d->destUrl->setFileDlgMode(QFileDialog::Directory);
0268     d->destUrl->setFileDlgOptions(QFileDialog::ShowDirsOnly);
0269     d->destUrl->setFileDlgTitle(i18nc("@title:window", "Destination Folder"));
0270     d->destUrl->lineEdit()->setPlaceholderText(i18n("Output Destination Path"));
0271     fileLabel->setBuddy(d->destUrl);
0272 
0273     QLabel* const outputLbl = new QLabel(destBox);
0274     outputLbl->setText(i18n("The video output file name will be generated automatically."));
0275     d->conflictBox          = new FileSaveConflictBox(destBox);
0276 
0277     destGrid->addWidget(fileLabel,      0, 0, 1, 1);
0278     destGrid->addWidget(d->destUrl,     0, 1, 1, 1);
0279     destGrid->addWidget(outputLbl,      1, 0, 1, 2);
0280     destGrid->addWidget(d->conflictBox, 2, 0, 1, 2);
0281 
0282     d->expanderBox->addItem(destBox,
0283                             QIcon::fromTheme(QLatin1String("folder-new")),
0284                             i18n("Target File"),
0285                             QLatin1String("File"), true);
0286 
0287     d->expanderBox->addStretch();
0288 
0289     setPageWidget(d->expanderBox);
0290     setLeftBottomPix(QIcon::fromTheme(QLatin1String("folder-video")));
0291 
0292     connect(d->trigUpdate, SIGNAL(timeout()),
0293             this, SIGNAL(completeChanged()));
0294 
0295     connect(d->destUrl->lineEdit(), &QLineEdit::textEdited,
0296             this, &VidSlideOutputPage::slotTriggerUpdate);
0297 
0298     connect(d->destUrl, SIGNAL(signalUrlSelected(QUrl)),
0299             this, SLOT(slotTriggerUpdate()));
0300 
0301     connect(d->audioUrl->lineEdit(), &QLineEdit::textEdited,
0302             this, &VidSlideOutputPage::slotTriggerUpdate);
0303 
0304     connect(d->audioUrl, SIGNAL(signalUrlSelected(QUrl)),
0305             this, SLOT(slotTriggerUpdate()));
0306 
0307     connect(d->equalize, SIGNAL(toggled(bool)),
0308             strengthLabel, SLOT(setEnabled(bool)));
0309 
0310     connect(d->equalize, SIGNAL(toggled(bool)),
0311             d->strength, SLOT(setEnabled(bool)));
0312 }
0313 
0314 VidSlideOutputPage::~VidSlideOutputPage()
0315 {
0316     delete d;
0317 }
0318 
0319 void VidSlideOutputPage::initializePage()
0320 {
0321     // Populate Formats List
0322 
0323     QMap<VidSlideSettings::VidFormat, QString> map                = VidSlideSettings::videoFormatNames();
0324     QMap<VidSlideSettings::VidFormat, QString>::const_iterator it = map.constBegin();
0325 
0326     QStringList formats = d->settings->ffmpegFormats.keys();
0327     int currentFormat   = d->settings->vFormat;
0328 
0329     while (it != map.constEnd())
0330     {
0331         d->formatVal->addItem(it.value(), (int)it.key());
0332 
0333         // Disable entry if FFmpeg format is not available.
0334 
0335         VidSlideSettings tmp;
0336         tmp.vFormat = (VidSlideSettings::VidFormat)it.key();
0337 
0338         if (!formats.contains(tmp.videoFormat()))
0339         {
0340             d->formatVal->setItemData((int)it.key(), false, Qt::UserRole-1);
0341         }
0342         else
0343         {
0344             if ((int)it.key() == currentFormat)
0345             {
0346                 d->formatVal->setCurrentIndex(currentFormat);
0347             }
0348         }
0349 
0350         ++it;
0351     }
0352 
0353     d->audioUrl->setFileDlgPath(d->settings->audioTrack);
0354 
0355 #ifdef HAVE_MEDIAPLAYER
0356 
0357     d->audioPlayer->setAudioFile(d->settings->audioTrack);
0358 
0359 #endif
0360 
0361     d->destUrl->setFileDlgPath(d->settings->outputDir);
0362     d->conflictBox->setConflictRule(d->settings->conflictRule);
0363     d->playerVal->setCurrentIndex(d->settings->outputPlayer);
0364     d->equalize->setChecked(d->settings->equalize);
0365     d->strength->setValue(d->settings->strength);
0366 
0367     d->frameOsd->setSettings(d->settings->osdSettings);
0368 }
0369 
0370 bool VidSlideOutputPage::validatePage()
0371 {
0372     if (d->destUrl->fileDlgPath().isEmpty())
0373     {
0374         return false;
0375     }
0376 
0377     d->settings->vFormat      = (VidSlideSettings::VidFormat)d->formatVal->currentIndex();
0378     d->settings->outputDir    = d->destUrl->fileDlgPath();
0379     d->settings->audioTrack   = d->audioUrl->fileDlgPath();
0380     d->settings->conflictRule = d->conflictBox->conflictRule();
0381     d->settings->outputPlayer = (VidSlideSettings::VidPlayer)d->playerVal->currentIndex();
0382     d->settings->equalize     = d->equalize->isChecked();
0383     d->settings->strength     = d->strength->value();
0384 
0385     d->settings->osdSettings  = d->frameOsd->settings();
0386 
0387     return true;
0388 }
0389 
0390 void VidSlideOutputPage::slotTriggerUpdate()
0391 {
0392     d->trigUpdate->start(1000);
0393 }
0394 
0395 bool VidSlideOutputPage::isComplete() const
0396 {
0397     d->duration->setText(QLatin1String("---"));
0398 
0399     QString apath = d->audioUrl->fileDlgPath();
0400 
0401 #ifdef HAVE_MEDIAPLAYER
0402 
0403     d->audioPlayer->setAudioFile(apath);
0404     d->audioPlayer->setDisabled(apath.isEmpty());
0405     
0406 
0407 #endif
0408 
0409     if (!apath.isEmpty())
0410     {
0411         FFmpegLauncher ffmpeg;
0412         ffmpeg.setSettings(d->wizard->settings());
0413         d->wizard->settings()->soundtrackLength = ffmpeg.soundTrackLength(apath);
0414 
0415         if (d->wizard->settings()->soundtrackLength.isValid())
0416         {
0417             d->duration->setText(d->wizard->settings()->soundtrackLength.toString());
0418         }
0419     }
0420 
0421     return (!d->destUrl->fileDlgPath().isEmpty());
0422 }
0423 
0424 } // namespace DigikamGenericVideoSlideShowPlugin
0425 
0426 #include "moc_vidslideoutputpage.cpp"