QGIS API Documentation 3.99.0-Master (21b3aa880ba)
Loading...
Searching...
No Matches
qgsvideoexporter.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsvideoexporter.h
3 --------------------------
4 begin : November 2025
5 copyright : (C) 2025 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgsvideoexporter.h"
17
18#include "qgsfeedback.h"
19
20#include <QDirIterator>
21#include <QUrl>
22
23#include "moc_qgsvideoexporter.cpp"
24
25#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 )
26#include <QtMultimedia/QMediaCaptureSession>
27#include <QtMultimedia/QVideoFrameInput>
28#include <QtMultimedia/QVideoFrame>
29#else
30#include "qgsexception.h"
31#endif
32
34{
35#if QT_VERSION < QT_VERSION_CHECK( 6, 8, 0 )
36 return false;
37#else
38 return true;
39#endif
40}
41
42QgsVideoExporter::QgsVideoExporter( const QString &filename, QSize size, double framesPerSecond )
43 : mFileName( filename )
44 , mSize( size )
45 , mFramesPerSecond( framesPerSecond )
46 , mFrameDurationUs( static_cast< qint64>( 1000000 / framesPerSecond ) )
47{
48
49}
50
54
59
61{
62 return mFeedback;
63}
64
65void QgsVideoExporter::setInputFiles( const QStringList &files )
66{
67 mInputFiles = files;
68}
69
70void QgsVideoExporter::setInputFilesByPattern( const QString &directory, const QString &pattern )
71{
72 QDirIterator it( directory, pattern.isEmpty() ? QStringList() : QStringList{ pattern }, QDir::AllEntries | QDir::NoSymLinks | QDir::NoDotAndDotDot, QDirIterator::NoIteratorFlags );
73 mInputFiles.clear();
74 while ( it.hasNext() )
75 {
76 const QString fullPath = it.next();
77 mInputFiles << fullPath;
78 }
79
80 std::sort( mInputFiles.begin(), mInputFiles.end() );
81}
82
84{
85 return mInputFiles;
86}
87
88#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 )
89void QgsVideoExporter::setFileFormat( QMediaFormat::FileFormat format )
90{
91 mFormat = format;
92}
93
94QMediaFormat::FileFormat QgsVideoExporter::fileFormat() const
95{
96 return mFormat;
97}
98
99void QgsVideoExporter::setVideoCodec( QMediaFormat::VideoCodec codec )
100{
101 mCodec = codec;
102}
103
104QMediaFormat::VideoCodec QgsVideoExporter::videoCodec() const
105{
106 return mCodec;
107}
108
109QMediaRecorder::Error QgsVideoExporter::error() const
110{
111 return mError;
112}
113#endif
114
116{
117 return mErrorString;
118}
119
121{
122#if QT_VERSION < QT_VERSION_CHECK( 6, 8, 0 )
123 throw QgsNotSupportedException( QStringLiteral( "Writing video is not supported on this system" ) );
124#else
125 mSession = std::make_unique< QMediaCaptureSession >();
126 mRecorder = std::make_unique< QMediaRecorder >();
127 mVideoInput = std::make_unique< QVideoFrameInput >();
128 mSession->setVideoFrameInput( mVideoInput.get() );
129 mSession->setRecorder( mRecorder.get() );
130 mRecorder->setOutputLocation( QUrl::fromLocalFile( mFileName ) );
131
132 QMediaFormat mediaFormat;
133 mediaFormat.setFileFormat( mFormat );
134 mediaFormat.setVideoCodec( mCodec );
135 mRecorder->setMediaFormat( mediaFormat );
136
137 // TODO: expose
138 mRecorder->setQuality( QMediaRecorder::Quality::VeryHighQuality );
139 mRecorder->setVideoBitRate( 2000 );
140 mRecorder->setEncodingMode( QMediaRecorder::EncodingMode::TwoPassEncoding );
141
142 mRecorder->setVideoResolution( mSize );
143 mRecorder->setVideoFrameRate( mFramesPerSecond );
144
145 QObject::connect( mVideoInput.get(), &QVideoFrameInput::readyToSendVideoFrame, this, &QgsVideoExporter::feedFrames );
146 QObject::connect( mRecorder.get(), &QMediaRecorder::recorderStateChanged, this, &QgsVideoExporter::checkStatus );
147 QObject::connect( mRecorder.get(), &QMediaRecorder::errorOccurred, this, &QgsVideoExporter::handleError );
148
149 mRecorder->record();
150
151 if ( mFeedback )
152 {
153 mFeedback->setProgress( 0 );
154 }
155 feedFrames();
156#endif
157}
158
159void QgsVideoExporter::feedFrames()
160{
161#if QT_VERSION >= QT_VERSION_CHECK( 6, 8, 0 )
162 if ( !mRecorder
163 || !mVideoInput
164 || mRecorder->recorderState() != QMediaRecorder::RecorderState::RecordingState )
165 return;
166
167 while ( mCurrentFrameIndex < mInputFiles.count() )
168 {
169 const QImage frame( mInputFiles.at( mCurrentFrameIndex ) );
170 QVideoFrame videoFrame( frame );
171 const qint64 startUs = mCurrentFrameIndex * mFrameDurationUs;
172 videoFrame.setStartTime( startUs );
173 videoFrame.setEndTime( startUs + mFrameDurationUs );
174
175 const bool sent = mVideoInput->sendVideoFrame( videoFrame );
176 if ( !sent )
177 return;
178
179 mCurrentFrameIndex++;
180
181 if ( mFeedback )
182 {
183 mFeedback->setProgress( 100.0 * static_cast< double >( mCurrentFrameIndex ) / static_cast< double >( mInputFiles.count() ) );
184 if ( mFeedback->isCanceled() )
185 {
186 mRecorder->stop();
187 return;
188 }
189 }
190 }
191
192 if ( mCurrentFrameIndex >= mInputFiles.count() )
193 {
194 mRecorder->stop();
195 }
196#endif
197}
198
199#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 )
200void QgsVideoExporter::checkStatus( QMediaRecorder::RecorderState state )
201{
202 switch ( state )
203 {
204 case QMediaRecorder::StoppedState:
205 {
206 if ( mCurrentFrameIndex >= mInputFiles.count() )
207 {
208 emit finished();
209 }
210 break;
211 }
212
213 case QMediaRecorder::RecordingState:
214 case QMediaRecorder::PausedState:
215 break;
216 }
217}
218
219void QgsVideoExporter::handleError( QMediaRecorder::Error error, const QString &errorString )
220{
221 mError = error;
222 mErrorString = errorString;
223}
224#endif
Base class for feedback objects to be used for cancellation of something running in a worker thread.
Definition qgsfeedback.h:44
Custom exception class which is raised when an operation is not supported.
void setInputFiles(const QStringList &files)
Sets the list of input image files.
void finished()
Emitted when the video export finishes.
QMediaRecorder::Error error() const
Returns the last error received while writing the video.
QString errorString() const
Returns the string describing the last error received while writing the video.
~QgsVideoExporter() override
QgsVideoExporter(const QString &filename, QSize size, double framesPerSecond)
Constructor for QgsVideoExporter.
QStringList inputFiles() const
Returns the list of input image files.
double framesPerSecond() const
Returns the output video frames per second.
void writeVideo()
Starts the video export operation.
static bool isAvailable()
Returns true if the video export functionality is available on the current system.
QMediaFormat::VideoCodec videoCodec() const
Returns the output video codec.
void setInputFilesByPattern(const QString &directory, const QString &pattern)
Sets the input image files by searching a directory for files matching a pattern.
void setVideoCodec(QMediaFormat::VideoCodec codec)
Sets the output video codec.
void setFileFormat(QMediaFormat::FileFormat format)
Sets the output file format.
QMediaFormat::FileFormat fileFormat() const
Returns the output file format.
void setFeedback(QgsFeedback *feedback)
Sets an optional feedback object, for progress reports and cancellation support.
QSize size() const
Returns the output video frame size.
QgsFeedback * feedback()
Returns the optional feedback object.