File indexing completed on 2025-01-05 03:57:27
0001 /* ============================================================ 0002 * 0003 * This file is a part of digiKam project 0004 * https://www.digikam.org 0005 * 0006 * Date : 2006-20-12 0007 * Description : a view to host media player based on QtAVPlayer. 0008 * 0009 * SPDX-FileCopyrightText: 2006-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 "mediaplayerview.h" 0016 0017 // Qt includes 0018 0019 #include <QApplication> 0020 #include <QVBoxLayout> 0021 #include <QMouseEvent> 0022 #include <QProxyStyle> 0023 #include <QPushButton> 0024 #include <QFileInfo> 0025 #include <QToolBar> 0026 #include <QAction> 0027 #include <QSlider> 0028 #include <QLabel> 0029 #include <QFrame> 0030 #include <QStyle> 0031 0032 // KDE includes 0033 0034 #include <klocalizedstring.h> 0035 #include <ksharedconfig.h> 0036 #include <kconfiggroup.h> 0037 0038 // Local includes 0039 0040 #include "digikam_globals.h" 0041 #include "digikam_debug.h" 0042 #include "thememanager.h" 0043 #include "dlayoutbox.h" 0044 #include "metaengine.h" 0045 #include "dmetadata.h" 0046 0047 namespace Digikam 0048 { 0049 0050 class Q_DECL_HIDDEN MediaPlayerMouseClickFilter : public QObject 0051 { 0052 Q_OBJECT 0053 0054 public: 0055 0056 explicit MediaPlayerMouseClickFilter(QObject* const parent) 0057 : QObject (parent), 0058 m_parent(parent) 0059 { 0060 } 0061 0062 protected: 0063 0064 bool eventFilter(QObject* obj, QEvent* event) override 0065 { 0066 if ((event->type() == QEvent::MouseButtonPress) || (event->type() == QEvent::MouseButtonDblClick)) 0067 { 0068 bool singleClick = qApp->style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick); 0069 QMouseEvent* const mouseEvent = dynamic_cast<QMouseEvent*>(event); 0070 0071 if (m_parent && mouseEvent) 0072 { 0073 MediaPlayerView* const mplayer = dynamic_cast<MediaPlayerView*>(m_parent); 0074 0075 if (mplayer) 0076 { 0077 if ((mouseEvent->button() == Qt::LeftButton) && 0078 ((singleClick && (event->type() == QEvent::MouseButtonPress)) || 0079 (!singleClick && (event->type() == QEvent::MouseButtonDblClick)))) 0080 { 0081 mplayer->slotEscapePressed(); 0082 } 0083 else if ((mouseEvent->button() == Qt::RightButton) && 0084 (event->type() == QEvent::MouseButtonPress)) 0085 { 0086 mplayer->slotRotateVideo(); 0087 } 0088 0089 return true; 0090 } 0091 } 0092 } 0093 0094 return QObject::eventFilter(obj, event); 0095 } 0096 0097 private: 0098 0099 QObject* m_parent = nullptr; 0100 }; 0101 0102 // -------------------------------------------------------- 0103 0104 class Q_DECL_HIDDEN PlayerVideoStyle : public QProxyStyle 0105 { 0106 Q_OBJECT 0107 0108 public: 0109 0110 using QProxyStyle::QProxyStyle; 0111 0112 int styleHint(QStyle::StyleHint hint, 0113 const QStyleOption* option = nullptr, 0114 const QWidget* widget = nullptr, 0115 QStyleHintReturn* returnData = nullptr) const override 0116 { 0117 if (hint == QStyle::SH_Slider_AbsoluteSetButtons) 0118 { 0119 return (Qt::LeftButton | Qt::MiddleButton | Qt::RightButton); 0120 } 0121 0122 return QProxyStyle::styleHint(hint, option, widget, returnData); 0123 } 0124 }; 0125 0126 // -------------------------------------------------------- 0127 0128 class Q_DECL_HIDDEN MediaPlayerView::Private 0129 { 0130 0131 public: 0132 0133 enum MediaPlayerViewMode 0134 { 0135 ErrorView = 0, 0136 PlayerView 0137 }; 0138 0139 public: 0140 0141 Private() = default; 0142 0143 QFrame* errorView = nullptr; 0144 QFrame* playerView = nullptr; 0145 0146 QAction* prevAction = nullptr; 0147 QAction* nextAction = nullptr; 0148 QAction* playAction = nullptr; 0149 QAction* grabAction = nullptr; 0150 0151 QPushButton* loopPlay = nullptr; 0152 0153 QToolBar* toolBar = nullptr; 0154 0155 DInfoInterface* iface = nullptr; 0156 0157 DVideoWidget* videoWidget = nullptr; 0158 0159 QSlider* slider = nullptr; 0160 QSlider* volume = nullptr; 0161 QLabel* tlabel = nullptr; 0162 QUrl currentItem; 0163 0164 qint64 capturePosition = 0; 0165 qint64 sliderTime = 0; 0166 0167 bool playLoop = false; 0168 }; 0169 0170 MediaPlayerView::MediaPlayerView(QWidget* const parent) 0171 : QStackedWidget(parent), 0172 d (new Private) 0173 { 0174 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); 0175 0176 const int spacing = qMin(QApplication::style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing), 0177 QApplication::style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing)); 0178 0179 d->prevAction = new QAction(QIcon::fromTheme(QLatin1String("go-previous")), 0180 i18nc("go to previous image", "Back"), this); 0181 d->nextAction = new QAction(QIcon::fromTheme(QLatin1String("go-next")), 0182 i18nc("go to next image", "Forward"), this); 0183 d->playAction = new QAction(QIcon::fromTheme(QLatin1String("media-playback-start")), 0184 i18nc("pause/play video", "Pause/Play"), this); 0185 d->grabAction = new QAction(QIcon::fromTheme(QLatin1String("view-preview")), 0186 i18nc("capture video frame", "Capture"), this); 0187 0188 d->errorView = new QFrame(this); 0189 QLabel* const errorMsg = new QLabel(i18n("An error has occurred with the media player..."), this); 0190 0191 errorMsg->setAlignment(Qt::AlignCenter); 0192 d->errorView->setFrameStyle(QFrame::StyledPanel | QFrame::Plain); 0193 d->errorView->setLineWidth(1); 0194 0195 QVBoxLayout* const vbox1 = new QVBoxLayout(d->errorView); 0196 vbox1->addWidget(errorMsg, 10); 0197 vbox1->setContentsMargins(QMargins()); 0198 vbox1->setSpacing(spacing); 0199 0200 insertWidget(Private::ErrorView, d->errorView); 0201 0202 // -------------------------------------------------------------------------- 0203 0204 d->playerView = new QFrame(this); 0205 d->playerView->setFrameStyle(QFrame::StyledPanel | QFrame::Plain); 0206 d->playerView->setLineWidth(1); 0207 d->playerView->installEventFilter(this); 0208 0209 d->videoWidget = new DVideoWidget(this); 0210 0211 DHBox* const hbox = new DHBox(this); 0212 d->slider = new QSlider(Qt::Horizontal, hbox); 0213 d->slider->setStyle(new PlayerVideoStyle()); 0214 d->slider->setRange(0, 0); 0215 d->tlabel = new QLabel(hbox); 0216 d->tlabel->setText(QLatin1String("00:00:00 / 00:00:00")); 0217 d->loopPlay = new QPushButton(hbox); 0218 d->loopPlay->setIcon(QIcon::fromTheme(QLatin1String("media-playlist-normal"))); 0219 d->loopPlay->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); 0220 d->loopPlay->setToolTip(i18n("Toggle playing in a loop")); 0221 d->loopPlay->setFocusPolicy(Qt::NoFocus); 0222 d->loopPlay->setMinimumSize(22, 22); 0223 d->loopPlay->setCheckable(true); 0224 QLabel* const spk = new QLabel(hbox); 0225 spk->setPixmap(QIcon::fromTheme(QLatin1String("audio-volume-high")).pixmap(22, 22)); 0226 d->volume = new QSlider(Qt::Horizontal, hbox); 0227 d->volume->setRange(0, 100); 0228 d->volume->setValue(50); 0229 0230 hbox->setStretchFactor(d->slider, 10); 0231 hbox->setContentsMargins(0, 0, 0, spacing); 0232 hbox->setSpacing(spacing); 0233 0234 QVBoxLayout* const vbox2 = new QVBoxLayout(d->playerView); 0235 vbox2->addWidget(d->videoWidget, 10); 0236 vbox2->addWidget(hbox, 0); 0237 vbox2->setContentsMargins(QMargins()); 0238 vbox2->setSpacing(spacing); 0239 0240 insertWidget(Private::PlayerView, d->playerView); 0241 0242 d->toolBar = new QToolBar(this); 0243 d->toolBar->addAction(d->prevAction); 0244 d->toolBar->addAction(d->nextAction); 0245 d->toolBar->addAction(d->playAction); 0246 d->toolBar->addAction(d->grabAction); 0247 d->toolBar->setStyleSheet(toolButtonStyleSheet()); 0248 0249 setPreviewMode(Private::PlayerView); 0250 0251 d->errorView->installEventFilter(new MediaPlayerMouseClickFilter(this)); 0252 d->videoWidget->view()->installEventFilter(new MediaPlayerMouseClickFilter(this)); 0253 0254 KSharedConfig::Ptr config = KSharedConfig::openConfig(); 0255 KConfigGroup group = config->group(QLatin1String("Media Player Settings")); 0256 int volume = group.readEntry("Volume", 50); 0257 0258 d->videoWidget->audioOutput()->setVolume(volume); 0259 d->volume->setValue(volume); 0260 0261 // -------------------------------------------------------------------------- 0262 0263 connect(ThemeManager::instance(), SIGNAL(signalThemeChanged()), 0264 this, SLOT(slotThemeChanged())); 0265 0266 connect(d->prevAction, SIGNAL(triggered()), 0267 this, SIGNAL(signalPrevItem())); 0268 0269 connect(d->nextAction, SIGNAL(triggered()), 0270 this, SIGNAL(signalNextItem())); 0271 0272 connect(d->playAction, SIGNAL(triggered()), 0273 this, SLOT(slotPausePlay())); 0274 0275 connect(d->grabAction, SIGNAL(triggered()), 0276 this, SLOT(slotCapture())); 0277 0278 connect(d->slider, SIGNAL(sliderMoved(int)), 0279 this, SLOT(slotPosition(int))); 0280 0281 connect(d->slider, SIGNAL(valueChanged(int)), 0282 this, SLOT(slotPosition(int))); 0283 0284 connect(d->volume, SIGNAL(valueChanged(int)), 0285 this, SLOT(slotVolumeChanged(int))); 0286 0287 connect(d->loopPlay, SIGNAL(toggled(bool)), 0288 this, SLOT(slotLoopToggled(bool))); 0289 0290 connect(d->videoWidget->player(), SIGNAL(stateChanged(QAVPlayer::State)), 0291 this, SLOT(slotPlayerStateChanged(QAVPlayer::State))); 0292 0293 connect(d->videoWidget, SIGNAL(positionChanged(qint64)), 0294 this, SLOT(slotPositionChanged(qint64))); 0295 0296 connect(d->videoWidget->player(), SIGNAL(durationChanged(qint64)), 0297 this, SLOT(slotDurationChanged(qint64))); 0298 0299 connect(d->videoWidget->player(), SIGNAL(errorOccurred(QAVPlayer::Error,QString)), 0300 this, SLOT(slotHandlePlayerError(QAVPlayer::Error,QString))); 0301 0302 connect(d->videoWidget->player(), SIGNAL(mediaStatusChanged(QAVPlayer::MediaStatus)), 0303 this, SLOT(slotMediaStatusChanged(QAVPlayer::MediaStatus))); 0304 } 0305 0306 MediaPlayerView::~MediaPlayerView() 0307 { 0308 escapePreview(); 0309 delete d; 0310 } 0311 0312 void MediaPlayerView::setInfoInterface(DInfoInterface* const iface) 0313 { 0314 d->iface = iface; 0315 } 0316 0317 void MediaPlayerView::reload() 0318 { 0319 d->videoWidget->player()->stop(); 0320 d->videoWidget->player()->setSource(d->currentItem.toLocalFile()); 0321 d->videoWidget->player()->play(); 0322 } 0323 0324 void MediaPlayerView::slotPlayerStateChanged(QAVPlayer::State newState) 0325 { 0326 if (newState == QAVPlayer::PlayingState) 0327 { 0328 d->playAction->setIcon(QIcon::fromTheme(QLatin1String("media-playback-pause"))); 0329 } 0330 else if (newState == QAVPlayer::PausedState) 0331 { 0332 d->playAction->setIcon(QIcon::fromTheme(QLatin1String("media-playback-start"))); 0333 } 0334 else if (newState == QAVPlayer::StoppedState) 0335 { 0336 if (d->playLoop) 0337 { 0338 reload(); 0339 } 0340 else 0341 { 0342 d->playAction->setIcon(QIcon::fromTheme(QLatin1String("media-playback-start"))); 0343 } 0344 } 0345 } 0346 0347 void MediaPlayerView::slotMediaStatusChanged(QAVPlayer::MediaStatus newStatus) 0348 { 0349 if (newStatus == QAVPlayer::InvalidMedia) 0350 { 0351 setPreviewMode(Private::ErrorView); 0352 } 0353 else if (newStatus == QAVPlayer::LoadedMedia) 0354 { 0355 int rotate = d->videoWidget->videoMediaOrientation(); 0356 0357 qCDebug(DIGIKAM_GENERAL_LOG) << "Video orientation from QtAVPlayer:" 0358 << rotate; 0359 0360 rotate = (-rotate) + d->videoWidget->videoItemOrientation(); 0361 0362 if ((rotate > 270) || (rotate < 0)) 0363 { 0364 rotate = d->videoWidget->videoItemOrientation(); 0365 } 0366 0367 d->videoWidget->setVideoItemOrientation(rotate); 0368 } 0369 } 0370 0371 void MediaPlayerView::escapePreview() 0372 { 0373 d->videoWidget->player()->stop(); 0374 d->videoWidget->player()->setSource(QString()); 0375 } 0376 0377 void MediaPlayerView::slotThemeChanged() 0378 { 0379 QPalette palette; 0380 palette.setColor(d->errorView->backgroundRole(), qApp->palette().color(QPalette::Base)); 0381 d->errorView->setPalette(palette); 0382 0383 QPalette palette2; 0384 palette2.setColor(d->playerView->backgroundRole(), qApp->palette().color(QPalette::Base)); 0385 d->playerView->setPalette(palette2); 0386 } 0387 0388 void MediaPlayerView::slotEscapePressed() 0389 { 0390 escapePreview(); 0391 0392 Q_EMIT signalEscapePreview(); 0393 } 0394 0395 void MediaPlayerView::slotRotateVideo() 0396 { 0397 if (d->videoWidget->player()->state() == QAVPlayer::PlayingState) 0398 { 0399 int orientation = 0; 0400 0401 switch (d->videoWidget->videoItemOrientation()) 0402 { 0403 case 0: 0404 { 0405 orientation = 90; 0406 break; 0407 } 0408 0409 case 90: 0410 { 0411 orientation = 180; 0412 break; 0413 } 0414 0415 case 180: 0416 { 0417 orientation = 270; 0418 break; 0419 } 0420 0421 default: 0422 { 0423 orientation = 0; 0424 break; 0425 } 0426 } 0427 0428 d->videoWidget->setVideoItemOrientation(orientation); 0429 } 0430 } 0431 0432 void MediaPlayerView::slotPausePlay() 0433 { 0434 if (d->videoWidget->player()->state() != QAVPlayer::PlayingState) 0435 { 0436 d->videoWidget->player()->play(); 0437 return; 0438 } 0439 0440 if (d->videoWidget->player()->state() != QAVPlayer::PausedState) 0441 { 0442 d->videoWidget->player()->pause(); 0443 } 0444 } 0445 0446 void MediaPlayerView::slotCapture() 0447 { 0448 if (d->videoWidget->player()->state() != QAVPlayer::StoppedState) 0449 { 0450 int capturePosition = d->videoWidget->player()->position(); 0451 QVideoFrame frame = d->videoWidget->videoFrame(); 0452 QImage image = frame.image(); 0453 0454 if (!image.isNull() && d->currentItem.isValid()) 0455 { 0456 QFileInfo info(d->currentItem.toLocalFile()); 0457 QString tempPath = QString::fromUtf8("%1/%2-%3.digikamtempfile.jpg") 0458 .arg(info.path()) 0459 .arg(info.baseName()) 0460 .arg(capturePosition); 0461 0462 if (image.save(tempPath, "JPG", 100)) 0463 { 0464 QScopedPointer<DMetadata> meta(new DMetadata); 0465 0466 if (meta->load(tempPath)) 0467 { 0468 QDateTime dateTime; 0469 MetaEngine::ImageOrientation orientation = MetaEngine::ORIENTATION_NORMAL; 0470 0471 if (d->iface) 0472 { 0473 DItemInfo dinfo(d->iface->itemInfo(d->currentItem)); 0474 0475 dateTime = dinfo.dateTime(); 0476 orientation = (MetaEngine::ImageOrientation)dinfo.orientation(); 0477 } 0478 else 0479 { 0480 QScopedPointer<DMetadata> meta2(new DMetadata); 0481 0482 if (meta2->load(d->currentItem.toLocalFile())) 0483 { 0484 dateTime = meta2->getItemDateTime(); 0485 orientation = meta2->getItemOrientation(); 0486 } 0487 } 0488 0489 if (dateTime.isValid()) 0490 { 0491 dateTime = dateTime.addMSecs(capturePosition); 0492 } 0493 else 0494 { 0495 dateTime = QDateTime::currentDateTime(); 0496 } 0497 0498 if (orientation == MetaEngine::ORIENTATION_UNSPECIFIED) 0499 { 0500 orientation = MetaEngine::ORIENTATION_NORMAL; 0501 } 0502 0503 meta->setImageDateTime(dateTime, true); 0504 meta->setItemDimensions(image.size()); 0505 meta->setItemOrientation(orientation); 0506 meta->save(tempPath, true); 0507 } 0508 0509 QString finalPath = QString::fromUtf8("%1/%2-%3.jpg") 0510 .arg(info.path()) 0511 .arg(info.baseName()) 0512 .arg(capturePosition); 0513 0514 if (QFile::rename(tempPath, finalPath)) 0515 { 0516 if (d->iface) 0517 { 0518 d->iface->slotMetadataChangedForUrl(QUrl::fromLocalFile(finalPath)); 0519 } 0520 } 0521 else 0522 { 0523 QFile::remove(tempPath); 0524 } 0525 } 0526 } 0527 } 0528 } 0529 0530 int MediaPlayerView::previewMode() 0531 { 0532 return indexOf(currentWidget()); 0533 } 0534 0535 void MediaPlayerView::setPreviewMode(int mode) 0536 { 0537 if ((mode != Private::ErrorView) && (mode != Private::PlayerView)) 0538 { 0539 return; 0540 } 0541 0542 setCurrentIndex(mode); 0543 0544 d->toolBar->adjustSize(); 0545 d->toolBar->raise(); 0546 } 0547 0548 void MediaPlayerView::setCurrentItem(const QUrl& url, bool hasPrevious, bool hasNext) 0549 { 0550 d->prevAction->setEnabled(hasPrevious); 0551 d->nextAction->setEnabled(hasNext); 0552 d->videoWidget->adjustVideoSize(); 0553 0554 if (url.isEmpty()) 0555 { 0556 d->videoWidget->player()->stop(); 0557 d->currentItem = url; 0558 0559 return; 0560 } 0561 0562 if (d->currentItem == url) 0563 { 0564 return; 0565 } 0566 0567 d->currentItem = url; 0568 0569 d->videoWidget->player()->stop(); 0570 0571 int orientation = 0; 0572 0573 if (d->iface) 0574 { 0575 DItemInfo info(d->iface->itemInfo(url)); 0576 0577 orientation = info.orientation(); 0578 } 0579 0580 switch (orientation) 0581 { 0582 case MetaEngine::ORIENTATION_ROT_90: 0583 case MetaEngine::ORIENTATION_ROT_90_HFLIP: 0584 case MetaEngine::ORIENTATION_ROT_90_VFLIP: 0585 { 0586 d->videoWidget->setVideoItemOrientation(90); 0587 break; 0588 } 0589 0590 case MetaEngine::ORIENTATION_ROT_180: 0591 { 0592 d->videoWidget->setVideoItemOrientation(180); 0593 break; 0594 } 0595 0596 case MetaEngine::ORIENTATION_ROT_270: 0597 { 0598 d->videoWidget->setVideoItemOrientation(270); 0599 break; 0600 } 0601 0602 default: 0603 { 0604 d->videoWidget->setVideoItemOrientation(0); 0605 break; 0606 } 0607 } 0608 0609 d->videoWidget->player()->setSource(d->currentItem.toLocalFile()); 0610 setPreviewMode(Private::PlayerView); 0611 d->videoWidget->player()->seek(10); 0612 d->videoWidget->player()->play(); 0613 } 0614 0615 void MediaPlayerView::slotPositionChanged(qint64 position) 0616 { 0617 if ((d->sliderTime < position) && 0618 ((d->sliderTime + 100) > position)) 0619 { 0620 return; 0621 } 0622 0623 d->sliderTime = position; 0624 0625 if (!d->slider->isSliderDown()) 0626 { 0627 d->slider->blockSignals(true); 0628 d->slider->setValue(position); 0629 d->slider->blockSignals(false); 0630 } 0631 0632 d->tlabel->setText(QString::fromLatin1("%1 / %2") 0633 .arg(QTime(0, 0, 0).addMSecs(position).toString(QLatin1String("HH:mm:ss"))) 0634 .arg(QTime(0, 0, 0).addMSecs(d->slider->maximum()).toString(QLatin1String("HH:mm:ss")))); 0635 } 0636 0637 void MediaPlayerView::slotVolumeChanged(int volume) 0638 { 0639 d->videoWidget->audioOutput()->setVolume((qreal)volume / 100.0); 0640 0641 if (objectName() != QLatin1String("main_media_player")) 0642 { 0643 return; 0644 } 0645 0646 KSharedConfig::Ptr config = KSharedConfig::openConfig(); 0647 KConfigGroup group = config->group(QLatin1String("Media Player Settings")); 0648 group.writeEntry("Volume", volume); 0649 } 0650 0651 void MediaPlayerView::slotLoopToggled(bool loop) 0652 { 0653 if (loop) 0654 { 0655 d->loopPlay->setIcon(QIcon::fromTheme(QLatin1String("media-playlist-repeat"))); 0656 } 0657 else 0658 { 0659 d->loopPlay->setIcon(QIcon::fromTheme(QLatin1String("media-playlist-normal"))); 0660 } 0661 0662 d->playLoop = loop; 0663 } 0664 0665 void MediaPlayerView::slotDurationChanged(qint64 duration) 0666 { 0667 qint64 max = qMax((qint64)1, duration); 0668 d->slider->setRange(0, max); 0669 } 0670 0671 void MediaPlayerView::slotPosition(int position) 0672 { 0673 if (d->videoWidget->player()->isSeekable()) 0674 { 0675 d->videoWidget->player()->seek((qint64)position); 0676 } 0677 } 0678 0679 void MediaPlayerView::slotHandlePlayerError(QAVPlayer::Error /*err*/, const QString& message) 0680 { 0681 setPreviewMode(Private::ErrorView); 0682 qCDebug(DIGIKAM_GENERAL_LOG) << "QtAVPlayer Error: " << message; 0683 } 0684 0685 void MediaPlayerView::slotNativeSizeChanged() 0686 { 0687 d->videoWidget->adjustVideoSize(); 0688 } 0689 0690 bool MediaPlayerView::eventFilter(QObject* watched, QEvent* event) 0691 { 0692 if ((watched == d->playerView) && (event->type() == QEvent::Resize)) 0693 { 0694 d->videoWidget->adjustVideoSize(); 0695 } 0696 0697 return QStackedWidget::eventFilter(watched, event); 0698 } 0699 0700 } // namespace Digikam 0701 0702 #include "mediaplayerview.moc" 0703 0704 #include "moc_mediaplayerview.cpp"