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"