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

KDE's Doxygen guidelines are available online.