Implement support for poster image of videos in PDF documents

With this commit Okular will show a so called poster image for PDF documents
containing movie annotations. The image will be a screenshot of the first frame
of the video.

BUGS: 301603
REVIEW: 105890
FIXED-IN: 4.10.0
This commit is contained in:
Tobias Koenig 2012-06-29 11:16:28 +02:00
parent aa042bd0f4
commit 8dbd83ab2a
12 changed files with 267 additions and 15 deletions

View file

@ -169,6 +169,7 @@ set(okularpart_SRCS
ui/searchwidget.cpp
ui/sidebar.cpp
ui/side_reviews.cpp
ui/snapshottaker.cpp
ui/thumbnaillist.cpp
ui/toc.cpp
ui/tocmodel.cpp

View file

@ -97,17 +97,30 @@ int main()
}
" HAVE_POPPLER_0_20)
check_cxx_source_compiles("
#include <poppler-qt4.h>
int main()
{
Poppler::MovieObject *movie = 0;
movie->showPosterImage();
return 0;
}
" HAVE_POPPLER_0_22)
set(CMAKE_REQUIRED_INCLUDES)
set(CMAKE_REQUIRED_LIBRARIES)
if (HAVE_POPPLER_0_20)
if (HAVE_POPPLER_0_22)
set(popplerVersionMessage "0.22")
elseif (HAVE_POPPLER_0_20)
set(popplerVersionMessage "0.20")
elseif (HAVE_POPPLER_0_16)
set(popplerVersionMessage "0.16")
elseif (HAVE_POPPLER_0_12_1)
set(popplerVersionMessage "0.12.1")
else (HAVE_POPPLER_0_20)
else (HAVE_POPPLER_0_22)
set(popplerVersionMessage "0.5.4")
endif (HAVE_POPPLER_0_20)
endif (HAVE_POPPLER_0_22)
if (NOT Poppler_FIND_QUIETLY)
message(STATUS "Found Poppler-Qt4: ${POPPLER_LIBRARY}, (>= ${popplerVersionMessage})")
endif (NOT Poppler_FIND_QUIETLY)

View file

@ -12,6 +12,7 @@
// qt/kde includes
#include <qdir.h>
#include <qimage.h>
#include <qstring.h>
#include <qtemporaryfile.h>
@ -30,7 +31,8 @@ class Movie::Private
m_playMode( PlayOnce ),
m_tmp( 0 ),
m_showControls( false ),
m_autoPlay( false )
m_autoPlay( false ),
m_showPosterImage( false )
{
}
@ -39,8 +41,10 @@ class Movie::Private
Rotation m_rotation;
PlayMode m_playMode;
QTemporaryFile *m_tmp;
QImage m_posterImage;
bool m_showControls : 1;
bool m_autoPlay : 1;
bool m_showPosterImage : 1;
};
Movie::Movie( const QString& fileName )
@ -130,3 +134,22 @@ bool Movie::autoPlay() const
return d->m_autoPlay;
}
void Movie::setShowPosterImage( bool show )
{
d->m_showPosterImage = show;
}
bool Movie::showPosterImage() const
{
return d->m_showPosterImage;
}
void Movie::setPosterImage( const QImage &image )
{
d->m_posterImage = image;
}
QImage Movie::posterImage() const
{
return d->m_posterImage;
}

View file

@ -16,6 +16,8 @@
#include <QtCore/QSize>
class QImage;
namespace Okular {
/**
@ -107,6 +109,34 @@ class OKULAR_EXPORT Movie
*/
bool autoPlay() const;
/**
* Sets whether to show a poster image.
*
* @since 4.10
*/
void setShowPosterImage( bool show );
/**
* Whether to show a poster image.
*
* @since 4.10
*/
bool showPosterImage() const;
/**
* Sets the poster image.
*
* @since 4.10
*/
void setPosterImage( const QImage &image );
/**
* Returns the poster image.
*
* @since 4.10
*/
QImage posterImage() const;
private:
class Private;
Private* const d;

View file

@ -6,3 +6,6 @@
/* Defined if we have the 0.20 version of the Poppler library */
#cmakedefine HAVE_POPPLER_0_20 1
/* Defined if we have the 0.22 version of the Poppler library */
#cmakedefine HAVE_POPPLER_0_22 1

View file

@ -180,6 +180,10 @@ Okular::Movie* createMovieFromPopplerMovie( const Poppler::MovieObject *popplerM
movie->setShowControls( popplerMovie->showControls() );
movie->setPlayMode( (Okular::Movie::PlayMode)popplerMovie->playMode() );
movie->setAutoPlay( false ); // will be triggered by external MovieAnnotation
#ifdef HAVE_POPPLER_0_22
movie->setShowPosterImage( popplerMovie->showPosterImage() );
movie->setPosterImage( popplerMovie->posterImage() );
#endif
return movie;
}

View file

@ -860,7 +860,7 @@ void PageView::notifySetup( const QVector< Okular::Page * > & pageSet, int setup
Okular::MovieAnnotation * movieAnn = static_cast< Okular::MovieAnnotation * >( a );
VideoWidget * vw = new VideoWidget( movieAnn, d->document, viewport() );
item->videoWidgets().insert( movieAnn->movie(), vw );
vw->hide();
vw->pageEntered();
}
}
}
@ -3961,7 +3961,7 @@ void PageView::slotRequestVisiblePixmaps( int newValue )
if ( vw->isPlaying() && viewportRectAtZeroZero.intersect( vw->geometry() ).isEmpty() ) {
vw->stop();
vw->hide();
vw->pageLeft();
}
}

View file

@ -316,7 +316,7 @@ void PresentationWidget::notifySetup( const QVector< Okular::Page * > & pageSet,
Okular::MovieAnnotation * movieAnn = static_cast< Okular::MovieAnnotation * >( a );
VideoWidget * vw = new VideoWidget( movieAnn, m_document, this );
frame->videoWidgets.insert( movieAnn->movie(), vw );
vw->hide();
vw->pageEntered();
}
}
frame->recalcGeometry( m_width, m_height, screenRatio );
@ -809,7 +809,7 @@ void PresentationWidget::changePage( int newPage )
Q_FOREACH ( VideoWidget *vw, m_frames[ m_frameIndex ]->videoWidgets )
{
vw->stop();
vw->hide();
vw->pageLeft();
}
// stop audio playback, if any

31
ui/snapshottaker.cpp Normal file
View file

@ -0,0 +1,31 @@
#include "snapshottaker.h"
#include <phonon/mediaobject.h>
#include <phonon/videowidget.h>
#include <QtGui/QImage>
SnapshotTaker::SnapshotTaker( const QString &url, QObject *parent )
: QObject( parent )
, m_player( new Phonon::VideoPlayer( Phonon::NoCategory, 0 ) )
{
m_player->load( url );
m_player->hide();
connect(m_player->mediaObject(), SIGNAL(stateChanged(Phonon::State, Phonon::State)),
this, SLOT(stateChanged(Phonon::State, Phonon::State)));
m_player->play();
}
void SnapshotTaker::stateChanged(Phonon::State newState, Phonon::State)
{
if (newState == Phonon::PlayingState) {
const QImage image = m_player->videoWidget()->snapshot();
if (!image.isNull())
emit finished( image );
m_player->stop();
deleteLater();
}
}

27
ui/snapshottaker.h Normal file
View file

@ -0,0 +1,27 @@
#ifndef SNAPSHOTTAKER_H
#define SNAPSHOTTAKER_H
#include <phonon/videoplayer.h>
#include <QtCore/QObject>
class QImage;
class SnapshotTaker : public QObject
{
Q_OBJECT
public:
SnapshotTaker( const QString &url, QObject *parent = 0 );
Q_SIGNALS:
void finished( const QImage &image );
private Q_SLOTS:
void stateChanged(Phonon::State, Phonon::State);
private:
Phonon::VideoPlayer *m_player;
};
#endif

View file

@ -11,10 +11,13 @@
// qt/kde includes
#include <qaction.h>
#include <qcoreapplication.h>
#include <qdir.h>
#include <qevent.h>
#include <qlabel.h>
#include <qlayout.h>
#include <qmenu.h>
#include <qstackedlayout.h>
#include <qtoolbar.h>
#include <qtoolbutton.h>
#include <qwidgetaction.h>
@ -29,6 +32,7 @@
#include "core/annotations.h"
#include "core/document.h"
#include "core/movie.h"
#include "snapshottaker.h"
static QAction* createToolBarButtonWithWidgetPopup( QToolBar* toolBar, QWidget *widget, const QIcon &icon )
{
@ -62,6 +66,9 @@ public:
void load();
void setupPlayPauseAction( PlayPauseMode mode );
void setPosterImage( const QImage& );
void takeSnapshot();
void videoStopped();
// slots
void finished();
@ -78,6 +85,8 @@ public:
QAction *stopAction;
QAction *seekSliderAction;
QAction *seekSliderMenuAction;
QStackedLayout *pageLayout;
QLabel *posterImagePage;
bool loaded : 1;
};
@ -121,6 +130,37 @@ void VideoWidget::Private::setupPlayPauseAction( PlayPauseMode mode )
}
}
void VideoWidget::Private::takeSnapshot()
{
const QString url = anno->movie()->url();
KUrl newurl;
if ( QDir::isRelativePath( url ) )
{
newurl = document->currentDocument();
newurl.setFileName( url );
}
else
{
newurl = url;
}
SnapshotTaker *taker = 0;
if ( newurl.isLocalFile() )
taker = new SnapshotTaker( newurl.toLocalFile(), q );
else
taker = new SnapshotTaker( newurl.url(), q );
q->connect( taker, SIGNAL( finished( const QImage& ) ), q, SLOT( setPosterImage( const QImage& ) ) );
}
void VideoWidget::Private::videoStopped()
{
if ( anno->movie()->showPosterImage() )
pageLayout->setCurrentIndex( 1 );
else
q->hide();
}
void VideoWidget::Private::finished()
{
switch ( anno->movie()->playMode() )
@ -132,7 +172,7 @@ void VideoWidget::Private::finished()
setupPlayPauseAction( PlayMode );
if ( anno->movie()->playMode() == Okular::Movie::PlayOnce )
controlBar->setVisible( false );
q->setVisible(false);
videoStopped();
break;
case Okular::Movie::PlayRepeat:
// repeat the playback
@ -158,6 +198,18 @@ void VideoWidget::Private::playOrPause()
}
}
void VideoWidget::Private::setPosterImage( const QImage &image )
{
if ( !image.isNull() )
{
// cache the snapshot image
anno->movie()->setPosterImage( image );
}
posterImagePage->setPixmap( QPixmap::fromImage( image ) );
q->show();
}
VideoWidget::VideoWidget( Okular::MovieAnnotation *movieann, Okular::Document *document, QWidget *parent )
: QWidget( parent ), d( new Private( movieann, document, this ) )
{
@ -165,15 +217,18 @@ VideoWidget::VideoWidget( Okular::MovieAnnotation *movieann, Okular::Document *d
// they should be tied to this widget, not spread around...
setAttribute( Qt::WA_NoMousePropagation );
QVBoxLayout *mainlay = new QVBoxLayout( this );
// Setup player page
QWidget *playerPage = new QWidget;
QVBoxLayout *mainlay = new QVBoxLayout( playerPage );
mainlay->setMargin( 0 );
mainlay->setSpacing( 0 );
d->player = new Phonon::VideoPlayer( Phonon::NoCategory, this );
d->player->installEventFilter( this );
d->player = new Phonon::VideoPlayer( Phonon::NoCategory, playerPage );
d->player->installEventFilter( playerPage );
mainlay->addWidget( d->player );
d->controlBar = new QToolBar( this );
d->controlBar = new QToolBar( playerPage );
d->controlBar->setIconSize( QSize( 16, 16 ) );
d->controlBar->setAutoFillBackground( true );
mainlay->addWidget( d->controlBar );
@ -203,6 +258,38 @@ VideoWidget::VideoWidget( Okular::MovieAnnotation *movieann, Okular::Document *d
connect( d->playPauseAction, SIGNAL(triggered()), this, SLOT(playOrPause()) );
d->geom = movieann->transformedBoundingRectangle();
// Setup poster image page
d->posterImagePage = new QLabel;
d->posterImagePage->setScaledContents( true );
d->posterImagePage->installEventFilter( this );
d->posterImagePage->setCursor( Qt::PointingHandCursor );
d->pageLayout = new QStackedLayout( this );
d->pageLayout->setMargin( 0 );
d->pageLayout->setSpacing( 0 );
d->pageLayout->addWidget( playerPage );
d->pageLayout->addWidget( d->posterImagePage );
if ( movieann->movie()->showPosterImage() )
{
d->pageLayout->setCurrentIndex( 1 );
const QImage posterImage = movieann->movie()->posterImage();
if ( posterImage.isNull() )
{
d->takeSnapshot();
}
else
{
d->setPosterImage( posterImage );
}
}
else
{
d->pageLayout->setCurrentIndex( 0 );
}
}
VideoWidget::~VideoWidget()
@ -227,14 +314,29 @@ bool VideoWidget::isPlaying() const
void VideoWidget::pageEntered()
{
if ( d->anno->movie()->autoPlay() ) {
if ( d->anno->movie()->showPosterImage() )
{
d->pageLayout->setCurrentIndex( 1 );
show();
}
if ( d->anno->movie()->autoPlay() )
{
show();
QMetaObject::invokeMethod(this, "play", Qt::QueuedConnection);
}
}
void VideoWidget::pageLeft()
{
d->videoStopped();
hide();
}
void VideoWidget::play()
{
d->pageLayout->setCurrentIndex( 0 );
d->load();
d->player->play();
d->stopAction->setEnabled( true );
@ -256,7 +358,7 @@ void VideoWidget::pause()
bool VideoWidget::eventFilter( QObject * object, QEvent * event )
{
if ( object == d->player )
if ( object == d->player || object == d->posterImagePage )
{
switch ( event->type() )
{
@ -272,6 +374,18 @@ bool VideoWidget::eventFilter( QObject * object, QEvent * event )
event->accept();
}
}
case QEvent::Wheel:
{
if ( object == d->posterImagePage )
{
QWheelEvent * we = static_cast< QWheelEvent * >( event );
// forward wheel events to parent widget
QWheelEvent *copy = new QWheelEvent( we->pos(), we->globalPos(), we->delta(), we->buttons(), we->modifiers(), we->orientation() );
QCoreApplication::postEvent( parentWidget(), copy );
}
break;
}
default: ;
}
}

View file

@ -35,6 +35,11 @@ class VideoWidget : public QWidget
*/
void pageEntered();
/**
* This method is called when the page the video widget is located on has been left.
*/
void pageLeft();
public slots:
void play();
void pause();
@ -48,6 +53,7 @@ class VideoWidget : public QWidget
private:
Q_PRIVATE_SLOT( d, void finished() )
Q_PRIVATE_SLOT( d, void playOrPause() )
Q_PRIVATE_SLOT( d, void setPosterImage( const QImage& ) )
// private storage
class Private;