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"