Marble

MovieCapture.cpp
1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2013 Illya Kovalevskyy <[email protected]>
4 // SPDX-FileCopyrightText: 2014 Dennis Nienhüser <[email protected]>
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 
17 namespace Marble
18 {
19 
20 class MovieCapturePrivate
21 {
22 public:
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 
49 MovieCapture::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 
69 MovieCapture::~MovieCapture()
70 {
71  delete d_ptr;
72 }
73 
74 void 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 
83 void MovieCapture::setFilename(const QString &path)
84 {
85  Q_D(MovieCapture);
86  d->destinationFile = path;
87 }
88 
89 void MovieCapture::setSnapshotMethod(MovieCapture::SnapshotMethod method)
90 {
91  Q_D(MovieCapture);
92  d->method = method;
93 }
94 
95 int MovieCapture::fps() const
96 {
97  Q_D(const MovieCapture);
98  return d->fps;
99 }
100 
101 QString MovieCapture::destination() const
102 {
103  Q_D(const MovieCapture);
104  return d->destinationFile;
105 }
106 
107 QVector<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 
129 MovieCapture::SnapshotMethod MovieCapture::snapshotMethod() const
130 {
131  Q_D(const MovieCapture);
132  return d->method;
133 }
134 
135 bool 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 
158 void 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 #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
176  d->process.write( (char*) screenshot.bits(), screenshot.sizeInBytes() );
177 #else
178  d->process.write( (char*) screenshot.bits(), screenshot.byteCount() );
179 #endif
180  for (int i=0; i<30 && d->process.bytesToWrite()>0; ++i) {
181  QElapsedTimer t;
182  int then = d->process.bytesToWrite();
183  t.start();
184  d->process.waitForBytesWritten( 100 );
185  int span = t.elapsed();
186  int now = d->process.bytesToWrite();
187  int bytesWritten = then - now;
188  double rate = ( bytesWritten * 1000.0 ) / ( qMax(1, span) * 1024 );
189  emit rateCalculated( rate );
190  }
191 }
192 
193 bool MovieCapture::startRecording()
194 {
195  Q_D(MovieCapture);
196 
197  if( !checkToolsAvailability() ) {
198  d->missingToolsWarning();
199  return false;
200  }
201 
202  if( d->method == MovieCapture::TimeDriven ){
203  d->frameTimer.start();
204  }
205  recordFrame();
206  return true;
207 }
208 
209 void MovieCapture::stopRecording()
210 {
211  Q_D(MovieCapture);
212 
213  d->frameTimer.stop();
214  d->process.closeWriteChannel();
215 }
216 
217 void MovieCapture::cancelRecording()
218 {
219  Q_D(MovieCapture);
220 
221  d->frameTimer.stop();
222  d->process.close();
223  QFile::remove( d->destinationFile );
224 }
225 
226 void MovieCapture::processWrittenMovie(int exitCode)
227 {
228  if (exitCode != 0) {
229  mDebug() << "[*] avconv finished with" << exitCode;
230  emit errorOccured();
231  }
232 }
233 
234 } // namespace Marble
235 
236 #include "moc_MovieCapture.cpp"
qsizetype sizeInBytes() const const
bool isEmpty() const const
QString number(int n, int base)
int height() const const
bool remove()
Type type(const QSqlDatabase &db)
QMessageBox::StandardButton warning(QWidget *parent, const QString &title, const QString &text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton)
uchar * bits()
QImage convertToFormat(QImage::Format format, Qt::ImageConversionFlags flags) const &const
Binds a QML item to a specific geodetic location in screen coordinates.
qint64 elapsed() const const
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
QString tr(const char *sourceText, const char *disambiguation, int n)
int byteCount() const const
Q_D(Todo)
QDebug mDebug()
a function to replace qDebug() in Marble library code
Definition: MarbleDebug.cpp:31
int width() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Thu May 26 2022 04:07:50 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.