File indexing completed on 2024-04-14 03:47:57
0001 // SPDX-License-Identifier: LGPL-2.1-or-later 0002 // 0003 // SPDX-FileCopyrightText: 2013 Illya Kovalevskyy <illya.kovalevskyy@gmail.com> 0004 // SPDX-FileCopyrightText: 2014 Dennis Nienhüser <nienhueser@kde.org> 0005 // 0006 0007 #include "MovieCapture.h" 0008 #include "MarbleWidget.h" 0009 #include "MarbleDebug.h" 0010 0011 #include <QProcess> 0012 #include <QMessageBox> 0013 #include <QTimer> 0014 #include <QElapsedTimer> 0015 #include <QFile> 0016 0017 namespace Marble 0018 { 0019 0020 class MovieCapturePrivate 0021 { 0022 public: 0023 explicit MovieCapturePrivate(MarbleWidget *widget) : 0024 marbleWidget(widget), method(MovieCapture::TimeDriven) 0025 {} 0026 0027 /** 0028 * @brief This gets called when user doesn't have avconv/ffmpeg installed 0029 */ 0030 void missingToolsWarning() { 0031 QMessageBox::warning(marbleWidget, 0032 QObject::tr("Missing encoding tools"), 0033 QObject::tr("Marble requires additional software in order to " 0034 "create movies. Please get %1 " 0035 ).arg("<a href=\"https://libav.org/" 0036 "download.html\">avconv</a>"), 0037 QMessageBox::Ok); 0038 } 0039 0040 QTimer frameTimer; 0041 MarbleWidget *marbleWidget; 0042 QString encoderExec; 0043 QString destinationFile; 0044 QProcess process; 0045 MovieCapture::SnapshotMethod method; 0046 int fps; 0047 }; 0048 0049 MovieCapture::MovieCapture(MarbleWidget *widget, QObject *parent) : 0050 QObject(parent), 0051 d_ptr(new MovieCapturePrivate(widget)) 0052 { 0053 Q_D(MovieCapture); 0054 if( d->method == MovieCapture::TimeDriven ){ 0055 d->frameTimer.setInterval(1000/30); // fps = 30 (default) 0056 connect(&d->frameTimer, SIGNAL(timeout()), this, SLOT(recordFrame())); 0057 } 0058 d->fps = 30; 0059 MovieFormat avi( "avi", tr( "AVI (mpeg4)" ), "avi" ); 0060 MovieFormat flv( "flv", tr( "FLV" ), "flv" ); 0061 MovieFormat mkv( "matroska", tr( "Matroska (h264)" ), "mkv" ); 0062 MovieFormat mp4( "mp4", tr( "MPEG-4" ), "mp4" ); 0063 MovieFormat vob( "vob", tr( "MPEG-2 PS (VOB)" ), "vob" ); 0064 MovieFormat ogg( "ogg", tr( "OGG" ), "ogg" ); 0065 MovieFormat swf( "swf", tr( "SWF" ), "swf" ); 0066 m_supportedFormats << avi << flv << mkv << mp4 << vob << ogg << swf; 0067 } 0068 0069 MovieCapture::~MovieCapture() 0070 { 0071 delete d_ptr; 0072 } 0073 0074 void MovieCapture::setFps(int fps) 0075 { 0076 Q_D(MovieCapture); 0077 if( d->method == MovieCapture::TimeDriven ){ 0078 d->frameTimer.setInterval(1000/fps); 0079 } 0080 d->fps = fps; 0081 } 0082 0083 void MovieCapture::setFilename(const QString &path) 0084 { 0085 Q_D(MovieCapture); 0086 d->destinationFile = path; 0087 } 0088 0089 void MovieCapture::setSnapshotMethod(MovieCapture::SnapshotMethod method) 0090 { 0091 Q_D(MovieCapture); 0092 d->method = method; 0093 } 0094 0095 int MovieCapture::fps() const 0096 { 0097 Q_D(const MovieCapture); 0098 return d->fps; 0099 } 0100 0101 QString MovieCapture::destination() const 0102 { 0103 Q_D(const MovieCapture); 0104 return d->destinationFile; 0105 } 0106 0107 QVector<MovieFormat> MovieCapture::availableFormats() 0108 { 0109 Q_D(MovieCapture); 0110 static QVector<MovieFormat> availableFormats; 0111 if ( availableFormats.isEmpty() && checkToolsAvailability() ) { 0112 QProcess encoder(this); 0113 for ( const MovieFormat &format: m_supportedFormats ) { 0114 QString type = format.type(); 0115 QStringList args; 0116 args << "-h" << QLatin1String("muxer=") + type; 0117 encoder.start( d->encoderExec, args ); 0118 encoder.waitForFinished(); 0119 QString output = encoder.readAll(); 0120 bool isFormatAvailable = !output.contains(QLatin1String("Unknown format")); 0121 if( isFormatAvailable ) { 0122 availableFormats << format; 0123 } 0124 } 0125 } 0126 return availableFormats; 0127 } 0128 0129 MovieCapture::SnapshotMethod MovieCapture::snapshotMethod() const 0130 { 0131 Q_D(const MovieCapture); 0132 return d->method; 0133 } 0134 0135 bool MovieCapture::checkToolsAvailability() 0136 { 0137 Q_D(MovieCapture); 0138 static bool toolsAvailable = false; 0139 if (toolsAvailable == false) { 0140 QProcess encoder(this); 0141 encoder.start("avconv", QStringList() << "-version"); 0142 encoder.waitForFinished(); 0143 if ( !encoder.readAll().isEmpty() ) { // avconv have output when it's here 0144 d->encoderExec = "avconv"; 0145 toolsAvailable = true; 0146 } else { 0147 encoder.start("ffmpeg", QStringList() << "-version"); 0148 encoder.waitForFinished(); 0149 if ( !encoder.readAll().isEmpty() ) { 0150 d->encoderExec = "ffmpeg"; 0151 toolsAvailable = true; 0152 } 0153 } 0154 } 0155 return toolsAvailable; 0156 } 0157 0158 void MovieCapture::recordFrame() 0159 { 0160 Q_D(MovieCapture); 0161 QImage const screenshot = d->marbleWidget->mapScreenShot().toImage().convertToFormat(QImage::Format_RGB888); 0162 if (d->process.state() == QProcess::NotRunning) { 0163 QStringList const arguments = QStringList() 0164 << "-y" 0165 << "-r" << QString::number(fps()) 0166 << "-f" << "rawvideo" 0167 << "-pix_fmt" << "rgb24" 0168 << "-s" << QString("%1x%2").arg( screenshot.width() ).arg( screenshot.height() ) 0169 << "-i" << "pipe:" 0170 << "-b" << "2000k" 0171 << d->destinationFile; 0172 d->process.start( d->encoderExec, arguments ); 0173 connect(&d->process, SIGNAL(finished(int)), this, SLOT(processWrittenMovie(int))); 0174 } 0175 d->process.write( (char*) screenshot.bits(), screenshot.sizeInBytes() ); 0176 for (int i=0; i<30 && d->process.bytesToWrite()>0; ++i) { 0177 QElapsedTimer t; 0178 int then = d->process.bytesToWrite(); 0179 t.start(); 0180 d->process.waitForBytesWritten( 100 ); 0181 int span = t.elapsed(); 0182 int now = d->process.bytesToWrite(); 0183 int bytesWritten = then - now; 0184 double rate = ( bytesWritten * 1000.0 ) / ( qMax(1, span) * 1024 ); 0185 emit rateCalculated( rate ); 0186 } 0187 } 0188 0189 bool MovieCapture::startRecording() 0190 { 0191 Q_D(MovieCapture); 0192 0193 if( !checkToolsAvailability() ) { 0194 d->missingToolsWarning(); 0195 return false; 0196 } 0197 0198 if( d->method == MovieCapture::TimeDriven ){ 0199 d->frameTimer.start(); 0200 } 0201 recordFrame(); 0202 return true; 0203 } 0204 0205 void MovieCapture::stopRecording() 0206 { 0207 Q_D(MovieCapture); 0208 0209 d->frameTimer.stop(); 0210 d->process.closeWriteChannel(); 0211 } 0212 0213 void MovieCapture::cancelRecording() 0214 { 0215 Q_D(MovieCapture); 0216 0217 d->frameTimer.stop(); 0218 d->process.close(); 0219 QFile::remove( d->destinationFile ); 0220 } 0221 0222 void MovieCapture::processWrittenMovie(int exitCode) 0223 { 0224 if (exitCode != 0) { 0225 mDebug() << "[*] avconv finished with" << exitCode; 0226 emit errorOccured(); 0227 } 0228 } 0229 0230 } // namespace Marble 0231 0232 #include "moc_MovieCapture.cpp"