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"