Marble

MovieCapture.cpp
1// SPDX-License-Identifier: LGPL-2.1-or-later
2//
3// SPDX-FileCopyrightText: 2013 Illya Kovalevskyy <illya.kovalevskyy@gmail.com>
4// SPDX-FileCopyrightText: 2014 Dennis Nienhüser <nienhueser@kde.org>
5//
6
7#include "MovieCapture.h"
8#include "MarbleWidget.h"
9#include "MarbleDebug.h"
10
11#include <QProcess>
12#include <QMessageBox>
13#include <QTimer>
14#include <QElapsedTimer>
15#include <QFile>
16
17namespace Marble
18{
19
20class MovieCapturePrivate
21{
22public:
23 explicit MovieCapturePrivate(MarbleWidget *widget) :
24 marbleWidget(widget), method(MovieCapture::TimeDriven)
25 {}
26
27 /**
28 * @brief This gets called when user doesn't have avconv/ffmpeg installed
29 */
30 void missingToolsWarning() {
31 QMessageBox::warning(marbleWidget,
32 QObject::tr("Missing encoding tools"),
33 QObject::tr("Marble requires additional software in order to "
34 "create movies. Please get %1 "
35 ).arg("<a href=\"https://libav.org/"
36 "download.html\">avconv</a>"),
38 }
39
40 QTimer frameTimer;
41 MarbleWidget *marbleWidget;
42 QString encoderExec;
43 QString destinationFile;
44 QProcess process;
45 MovieCapture::SnapshotMethod method;
46 int fps;
47};
48
49MovieCapture::MovieCapture(MarbleWidget *widget, QObject *parent) :
50 QObject(parent),
51 d_ptr(new MovieCapturePrivate(widget))
52{
53 Q_D(MovieCapture);
54 if( d->method == MovieCapture::TimeDriven ){
55 d->frameTimer.setInterval(1000/30); // fps = 30 (default)
56 connect(&d->frameTimer, SIGNAL(timeout()), this, SLOT(recordFrame()));
57 }
58 d->fps = 30;
59 MovieFormat avi( "avi", tr( "AVI (mpeg4)" ), "avi" );
60 MovieFormat flv( "flv", tr( "FLV" ), "flv" );
61 MovieFormat mkv( "matroska", tr( "Matroska (h264)" ), "mkv" );
62 MovieFormat mp4( "mp4", tr( "MPEG-4" ), "mp4" );
63 MovieFormat vob( "vob", tr( "MPEG-2 PS (VOB)" ), "vob" );
64 MovieFormat ogg( "ogg", tr( "OGG" ), "ogg" );
65 MovieFormat swf( "swf", tr( "SWF" ), "swf" );
66 m_supportedFormats << avi << flv << mkv << mp4 << vob << ogg << swf;
67}
68
69MovieCapture::~MovieCapture()
70{
71 delete d_ptr;
72}
73
74void MovieCapture::setFps(int fps)
75{
76 Q_D(MovieCapture);
77 if( d->method == MovieCapture::TimeDriven ){
78 d->frameTimer.setInterval(1000/fps);
79 }
80 d->fps = fps;
81}
82
83void MovieCapture::setFilename(const QString &path)
84{
85 Q_D(MovieCapture);
86 d->destinationFile = path;
87}
88
89void MovieCapture::setSnapshotMethod(MovieCapture::SnapshotMethod method)
90{
91 Q_D(MovieCapture);
92 d->method = method;
93}
94
95int MovieCapture::fps() const
96{
97 Q_D(const MovieCapture);
98 return d->fps;
99}
100
101QString MovieCapture::destination() const
102{
103 Q_D(const MovieCapture);
104 return d->destinationFile;
105}
106
107QVector<MovieFormat> MovieCapture::availableFormats()
108{
109 Q_D(MovieCapture);
110 static QVector<MovieFormat> availableFormats;
111 if ( availableFormats.isEmpty() && checkToolsAvailability() ) {
112 QProcess encoder(this);
113 for ( const MovieFormat &format: m_supportedFormats ) {
114 QString type = format.type();
115 QStringList args;
116 args << "-h" << QLatin1String("muxer=") + type;
117 encoder.start( d->encoderExec, args );
118 encoder.waitForFinished();
119 QString output = encoder.readAll();
120 bool isFormatAvailable = !output.contains(QLatin1String("Unknown format"));
121 if( isFormatAvailable ) {
122 availableFormats << format;
123 }
124 }
125 }
126 return availableFormats;
127}
128
129MovieCapture::SnapshotMethod MovieCapture::snapshotMethod() const
130{
131 Q_D(const MovieCapture);
132 return d->method;
133}
134
135bool MovieCapture::checkToolsAvailability()
136{
137 Q_D(MovieCapture);
138 static bool toolsAvailable = false;
139 if (toolsAvailable == false) {
140 QProcess encoder(this);
141 encoder.start("avconv", QStringList() << "-version");
142 encoder.waitForFinished();
143 if ( !encoder.readAll().isEmpty() ) { // avconv have output when it's here
144 d->encoderExec = "avconv";
145 toolsAvailable = true;
146 } else {
147 encoder.start("ffmpeg", QStringList() << "-version");
148 encoder.waitForFinished();
149 if ( !encoder.readAll().isEmpty() ) {
150 d->encoderExec = "ffmpeg";
151 toolsAvailable = true;
152 }
153 }
154 }
155 return toolsAvailable;
156}
157
158void MovieCapture::recordFrame()
159{
160 Q_D(MovieCapture);
161 QImage const screenshot = d->marbleWidget->mapScreenShot().toImage().convertToFormat(QImage::Format_RGB888);
162 if (d->process.state() == QProcess::NotRunning) {
163 QStringList const arguments = QStringList()
164 << "-y"
165 << "-r" << QString::number(fps())
166 << "-f" << "rawvideo"
167 << "-pix_fmt" << "rgb24"
168 << "-s" << QString("%1x%2").arg( screenshot.width() ).arg( screenshot.height() )
169 << "-i" << "pipe:"
170 << "-b" << "2000k"
171 << d->destinationFile;
172 d->process.start( d->encoderExec, arguments );
173 connect(&d->process, SIGNAL(finished(int)), this, SLOT(processWrittenMovie(int)));
174 }
175 d->process.write( (char*) screenshot.bits(), screenshot.sizeInBytes() );
176 for (int i=0; i<30 && d->process.bytesToWrite()>0; ++i) {
178 int then = d->process.bytesToWrite();
179 t.start();
180 d->process.waitForBytesWritten( 100 );
181 int span = t.elapsed();
182 int now = d->process.bytesToWrite();
183 int bytesWritten = then - now;
184 double rate = ( bytesWritten * 1000.0 ) / ( qMax(1, span) * 1024 );
185 emit rateCalculated( rate );
186 }
187}
188
189bool MovieCapture::startRecording()
190{
191 Q_D(MovieCapture);
192
193 if( !checkToolsAvailability() ) {
194 d->missingToolsWarning();
195 return false;
196 }
197
198 if( d->method == MovieCapture::TimeDriven ){
199 d->frameTimer.start();
200 }
201 recordFrame();
202 return true;
203}
204
205void MovieCapture::stopRecording()
206{
207 Q_D(MovieCapture);
208
209 d->frameTimer.stop();
210 d->process.closeWriteChannel();
211}
212
213void MovieCapture::cancelRecording()
214{
215 Q_D(MovieCapture);
216
217 d->frameTimer.stop();
218 d->process.close();
219 QFile::remove( d->destinationFile );
220}
221
222void MovieCapture::processWrittenMovie(int exitCode)
223{
224 if (exitCode != 0) {
225 mDebug() << "[*] avconv finished with" << exitCode;
226 emit errorOccured();
227 }
228}
229
230} // namespace Marble
231
232#include "moc_MovieCapture.cpp"
This file contains the headers for MarbleWidget.
Type type(const QSqlDatabase &db)
QString path(const QString &relativePath)
Binds a QML item to a specific geodetic location in screen coordinates.
qint64 elapsed() const const
bool remove()
uchar * bits()
QImage convertToFormat(Format format, Qt::ImageConversionFlags flags) &&
int height() const const
qsizetype sizeInBytes() const const
int width() const const
bool isEmpty() const const
StandardButton warning(QWidget *parent, const QString &title, const QString &text, StandardButtons buttons, StandardButton defaultButton)
QString tr(const char *sourceText, const char *disambiguation, int n)
QString arg(Args &&... args) const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QString number(double n, char format, int precision)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:18:17 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.