File indexing completed on 2025-01-05 03:57:28
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2014-09-22 0007 * Description : Slideshow video viewer based on QtMultimedia 0008 * 0009 * SPDX-FileCopyrightText: 2014-2023 Gilles Caulier <caulier dot gilles at gmail dot com> 0010 * 0011 * SPDX-License-Identifier: GPL-2.0-or-later 0012 * 0013 * ============================================================ */ 0014 0015 #include "slidevideo.h" 0016 0017 // Qt includes 0018 0019 #include <QApplication> 0020 #include <QProxyStyle> 0021 #include <QGridLayout> 0022 #include <QString> 0023 #include <QSlider> 0024 #include <QStyle> 0025 #include <QLabel> 0026 #include <QTransform> 0027 #include <QGraphicsView> 0028 #include <QGraphicsScene> 0029 #include <QGraphicsVideoItem> 0030 #include <QVideoSink> 0031 #include <QVideoFrame> 0032 #include <QAudioOutput> 0033 #include <QMediaMetaData> 0034 0035 // KDE includes 0036 0037 #include <klocalizedstring.h> 0038 #include <ksharedconfig.h> 0039 #include <kconfiggroup.h> 0040 0041 // Local includes 0042 0043 #include "digikam_debug.h" 0044 #include "dlayoutbox.h" 0045 #include "metaengine.h" 0046 0047 namespace Digikam 0048 { 0049 0050 class Q_DECL_HIDDEN SlideVideoStyle : public QProxyStyle 0051 { 0052 Q_OBJECT 0053 0054 public: 0055 0056 using QProxyStyle::QProxyStyle; 0057 0058 int styleHint(QStyle::StyleHint hint, 0059 const QStyleOption* option = nullptr, 0060 const QWidget* widget = nullptr, 0061 QStyleHintReturn* returnData = nullptr) const override 0062 { 0063 if (hint == QStyle::SH_Slider_AbsoluteSetButtons) 0064 { 0065 return (Qt::LeftButton | Qt::MiddleButton | Qt::RightButton); 0066 } 0067 0068 return QProxyStyle::styleHint(hint, option, widget, returnData); 0069 } 0070 }; 0071 0072 class Q_DECL_HIDDEN SlideVideo::Private 0073 { 0074 public: 0075 0076 Private() = default; 0077 0078 DInfoInterface* iface = nullptr; 0079 0080 QGraphicsScene* videoScene = nullptr; 0081 QGraphicsView* videoView = nullptr; 0082 QGraphicsVideoItem* videoItem = nullptr; 0083 QMediaPlayer* player = nullptr; 0084 QAudioOutput* audio = nullptr; 0085 0086 QSlider* slider = nullptr; 0087 QSlider* volume = nullptr; 0088 QLabel* tlabel = nullptr; 0089 0090 DHBox* indicator = nullptr; 0091 0092 int videoOrientation = 0; 0093 0094 public: 0095 0096 void adjustVideoSize() 0097 { 0098 videoItem->resetTransform(); 0099 0100 QSizeF nativeSize = videoItem->nativeSize(); 0101 int mediaOrientation = videoMediaOrientation(); 0102 0103 if ((nativeSize.width() < 1.0) || 0104 (nativeSize.height() < 1.0)) 0105 { 0106 return; 0107 } 0108 0109 if ((mediaOrientation == 90) || 0110 (mediaOrientation == 270)) 0111 { 0112 nativeSize.transpose(); 0113 } 0114 0115 double ratio = (nativeSize.width() / 0116 nativeSize.height()); 0117 0118 if (videoView->width() > videoView->height()) 0119 { 0120 QSizeF vsize(videoView->height() * ratio, 0121 videoView->height()); 0122 videoItem->setSize(vsize); 0123 } 0124 else 0125 { 0126 QSizeF vsize(videoView->width(), 0127 videoView->width() / ratio); 0128 videoItem->setSize(vsize); 0129 } 0130 0131 videoView->setSceneRect(0, 0, videoItem->size().width(), 0132 videoItem->size().height()); 0133 0134 QPointF center = videoItem->boundingRect().center(); 0135 videoItem->setTransformOriginPoint(center); 0136 videoItem->setRotation(videoOrientation); 0137 0138 videoView->fitInView(videoItem, Qt::KeepAspectRatio); 0139 videoView->centerOn(videoItem); 0140 videoView->raise(); 0141 }; 0142 0143 int videoMediaOrientation() const 0144 { 0145 int orientation = 0; 0146 QVariant val = player->metaData().value(QMediaMetaData::Orientation); 0147 0148 if (!val.isNull()) 0149 { 0150 orientation = val.toInt(); 0151 } 0152 0153 return orientation; 0154 }; 0155 0156 void setVideoItemOrientation(int orientation) 0157 { 0158 videoOrientation = orientation; 0159 adjustVideoSize(); 0160 }; 0161 }; 0162 0163 SlideVideo::SlideVideo(QWidget* const parent) 0164 : QWidget(parent), 0165 d (new Private) 0166 { 0167 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); 0168 0169 setMouseTracking(true); 0170 0171 d->videoScene = new QGraphicsScene(this); 0172 d->videoView = new QGraphicsView(d->videoScene); 0173 d->videoView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 0174 d->videoView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 0175 d->videoView->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); 0176 d->videoItem = new QGraphicsVideoItem(); 0177 d->player = new QMediaPlayer(this); 0178 d->audio = new QAudioOutput; 0179 d->player->setAudioOutput(d->audio); 0180 d->player->setVideoOutput(d->videoItem); 0181 d->videoScene->addItem(d->videoItem); 0182 0183 d->videoItem->setAspectRatioMode(Qt::IgnoreAspectRatio); 0184 d->videoView->setMouseTracking(true); 0185 0186 d->indicator = new DHBox; 0187 d->slider = new QSlider(Qt::Horizontal, d->indicator); 0188 d->slider->setStyle(new SlideVideoStyle()); 0189 d->slider->setRange(0, 0); 0190 d->slider->setAutoFillBackground(true); 0191 d->tlabel = new QLabel(d->indicator); 0192 d->tlabel->setText(QLatin1String("00:00:00 / 00:00:00")); 0193 d->tlabel->setAutoFillBackground(true); 0194 QLabel* const spk = new QLabel(d->indicator); 0195 spk->setPixmap(QIcon::fromTheme(QLatin1String("audio-volume-high")).pixmap(22, 22)); 0196 d->volume = new QSlider(Qt::Horizontal, d->indicator); 0197 d->volume->setRange(0, 100); 0198 d->volume->setValue(50); 0199 d->indicator->setStretchFactor(d->slider, 10); 0200 d->indicator->setAutoFillBackground(true); 0201 d->indicator->setSpacing(4); 0202 0203 QGridLayout* const grid = new QGridLayout(this); 0204 grid->addWidget(d->videoView, 0, 0, 10, 1); 0205 grid->addWidget(d->indicator, 0, 0, 1, 1); // Widget will be over player to not change layout when visibility is changed. 0206 grid->setRowStretch(0, 1); 0207 grid->setRowStretch(1, 100); 0208 grid->setContentsMargins(QMargins()); 0209 0210 KSharedConfig::Ptr config = KSharedConfig::openConfig(); 0211 KConfigGroup group = config->group(QLatin1String("Media Player Settings")); 0212 int volume = group.readEntry("Volume", 50); 0213 0214 d->volume->setValue(volume); 0215 d->audio->setVolume(volume / 100.0F); 0216 0217 // -------------------------------------------------------------------------- 0218 0219 connect(d->slider, SIGNAL(sliderMoved(int)), 0220 this, SLOT(slotPosition(int))); 0221 0222 connect(d->slider, SIGNAL(valueChanged(int)), 0223 this, SLOT(slotPosition(int))); 0224 0225 connect(d->volume, SIGNAL(valueChanged(int)), 0226 this, SLOT(slotVolumeChanged(int))); 0227 0228 connect(d->player, SIGNAL(playbackStateChanged(QMediaPlayer::PlaybackState)), 0229 this, SLOT(slotPlayerStateChanged(QMediaPlayer::PlaybackState))); 0230 0231 connect(d->player, SIGNAL(mediaStatusChanged(QMediaPlayer::MediaStatus)), 0232 this, SLOT(slotMediaStatusChanged(QMediaPlayer::MediaStatus))); 0233 0234 connect(d->player, SIGNAL(positionChanged(qint64)), 0235 this, SLOT(slotPositionChanged(qint64))); 0236 0237 connect(d->player, SIGNAL(durationChanged(qint64)), 0238 this, SLOT(slotDurationChanged(qint64))); 0239 0240 connect(d->player, SIGNAL(errorOccurred(QMediaPlayer::Error,QString)), 0241 this, SLOT(slotHandlePlayerError(QMediaPlayer::Error,QString))); 0242 0243 connect(d->videoItem, SIGNAL(nativeSizeChanged(QSizeF)), 0244 this, SLOT(slotNativeSizeChanged())); 0245 0246 // -------------------------------------------------------------------------- 0247 0248 layout()->activate(); 0249 resize(sizeHint()); 0250 show(); 0251 } 0252 0253 SlideVideo::~SlideVideo() 0254 { 0255 stop(); 0256 delete d; 0257 } 0258 0259 void SlideVideo::setInfoInterface(DInfoInterface* const iface) 0260 { 0261 d->iface = iface; 0262 } 0263 0264 void SlideVideo::setCurrentUrl(const QUrl& url) 0265 { 0266 d->player->stop(); 0267 0268 int orientation = 0; 0269 0270 if (d->iface) 0271 { 0272 DItemInfo info(d->iface->itemInfo(url)); 0273 0274 orientation = info.orientation(); 0275 } 0276 0277 switch (orientation) 0278 { 0279 case MetaEngine::ORIENTATION_ROT_90: 0280 case MetaEngine::ORIENTATION_ROT_90_HFLIP: 0281 case MetaEngine::ORIENTATION_ROT_90_VFLIP: 0282 { 0283 d->videoOrientation = 90; 0284 break; 0285 } 0286 0287 case MetaEngine::ORIENTATION_ROT_180: 0288 { 0289 d->videoOrientation = 180; 0290 break; 0291 } 0292 0293 case MetaEngine::ORIENTATION_ROT_270: 0294 { 0295 d->videoOrientation = 270; 0296 break; 0297 } 0298 0299 default: 0300 { 0301 d->videoOrientation = 0; 0302 break; 0303 } 0304 } 0305 0306 d->player->setSource(url); 0307 d->player->play(); 0308 0309 qCDebug(DIGIKAM_GENERAL_LOG) << "Slide video with QtMultimedia started:" << d->player->source(); 0310 0311 showIndicator(false); 0312 } 0313 0314 void SlideVideo::showIndicator(bool b) 0315 { 0316 d->indicator->setVisible(b); 0317 d->indicator->raise(); 0318 } 0319 0320 void SlideVideo::slotPlayerStateChanged(QMediaPlayer::PlaybackState newState) 0321 { 0322 if (newState == QMediaPlayer::PlayingState) 0323 { 0324 int rotate = d->videoMediaOrientation(); 0325 0326 qCDebug(DIGIKAM_GENERAL_LOG) << "Found video orientation with QtMultimedia:" 0327 << rotate; 0328 0329 rotate = (-rotate) + d->videoOrientation; 0330 0331 if ((rotate > 270) || (rotate < 0)) 0332 { 0333 rotate = d->videoOrientation; 0334 } 0335 0336 d->setVideoItemOrientation(rotate); 0337 } 0338 } 0339 0340 void SlideVideo::slotMediaStatusChanged(QMediaPlayer::MediaStatus status) 0341 { 0342 switch (status) 0343 { 0344 case QMediaPlayer::EndOfMedia: 0345 { 0346 qCDebug(DIGIKAM_GENERAL_LOG) << "Slide video with QtMultimedia completed:" << d->player->source(); 0347 0348 Q_EMIT signalVideoFinished(); 0349 0350 break; 0351 } 0352 0353 case QMediaPlayer::LoadingMedia: 0354 { 0355 qCDebug(DIGIKAM_GENERAL_LOG) << "Slide video with QtMultimedia media loaded:" << d->player->source(); 0356 0357 Q_EMIT signalVideoLoaded(true); 0358 0359 break; 0360 } 0361 0362 case QMediaPlayer::InvalidMedia: 0363 { 0364 qCDebug(DIGIKAM_GENERAL_LOG) << "Slide video with QtMultimedia media invalid:" << d->player->source(); 0365 0366 Q_EMIT signalVideoLoaded(false); 0367 0368 break; 0369 } 0370 0371 default: 0372 { 0373 break; 0374 } 0375 } 0376 } 0377 0378 void SlideVideo::pause(bool b) 0379 { 0380 if (!b && !d->player->isPlaying()) 0381 { 0382 d->player->play(); 0383 return; 0384 } 0385 0386 d->player->pause(); 0387 } 0388 0389 void SlideVideo::stop() 0390 { 0391 d->player->stop(); 0392 d->player->setSource(QUrl()); 0393 } 0394 0395 void SlideVideo::slotPositionChanged(qint64 position) 0396 { 0397 if (!d->slider->isSliderDown()) 0398 { 0399 d->slider->blockSignals(true); 0400 d->slider->setValue(position); 0401 d->slider->blockSignals(false); 0402 } 0403 0404 d->tlabel->setText(QString::fromLatin1("%1 / %2") 0405 .arg(QTime(0, 0, 0).addMSecs(position).toString(QLatin1String("HH:mm:ss"))) 0406 .arg(QTime(0, 0, 0).addMSecs(d->slider->maximum()).toString(QLatin1String("HH:mm:ss")))); 0407 0408 Q_EMIT signalVideoPosition(position); 0409 } 0410 0411 void SlideVideo::slotVolumeChanged(int volume) 0412 { 0413 d->audio->setVolume(volume / 100.0F); 0414 } 0415 0416 void SlideVideo::slotDurationChanged(qint64 duration) 0417 { 0418 qint64 max = qMax((qint64)1, duration); 0419 d->slider->setRange(0, max); 0420 0421 Q_EMIT signalVideoDuration(duration); 0422 } 0423 0424 void SlideVideo::slotPosition(int position) 0425 { 0426 if (d->player->isSeekable()) 0427 { 0428 d->player->setPosition((qint64)position); 0429 } 0430 } 0431 0432 void SlideVideo::slotHandlePlayerError(QMediaPlayer::Error, const QString& str) 0433 { 0434 qCDebug(DIGIKAM_GENERAL_LOG) << "QtMultimedia Error: " << str; 0435 } 0436 0437 void SlideVideo::slotNativeSizeChanged() 0438 { 0439 d->adjustVideoSize(); 0440 } 0441 0442 void SlideVideo::resizeEvent(QResizeEvent* e) 0443 { 0444 QWidget::resizeEvent(e); 0445 d->adjustVideoSize(); 0446 } 0447 0448 } // namespace Digikam 0449 0450 #include "slidevideo.moc" 0451 0452 #include "moc_slidevideo.cpp"