File indexing completed on 2024-04-28 08:44:13

0001 /*
0002     SPDX-FileCopyrightText: 2015 Jean-Baptiste Mardelle <jb@kdenlive.org>
0003     This file is part of Kdenlive. See www.kdenlive.org.
0004 
0005     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006 */
0007 
0008 #include "recmanager.h"
0009 //#include "capture/mltdevicecapture.h"
0010 #include "core.h"
0011 #include "dialogs/profilesdialog.h"
0012 #include "kdenlivesettings.h"
0013 #include "monitor.h"
0014 
0015 #include "klocalizedstring.h"
0016 #include <KMessageBox>
0017 
0018 #include <QCheckBox>
0019 #include <QComboBox>
0020 #include <QDir>
0021 #include <QFile>
0022 #include <QMenu>
0023 #include <QScreen>
0024 #include <QStandardPaths>
0025 #include <QToolBar>
0026 #include <QToolButton>
0027 #include <QWidgetAction>
0028 
0029 RecManager::RecManager(Monitor *parent)
0030     : QObject(parent)
0031     , m_monitor(parent)
0032     , m_recToolbar(new QToolBar(parent))
0033 {
0034     m_playAction = m_recToolbar->addAction(QIcon::fromTheme(QStringLiteral("media-playback-start")), i18n("Preview"));
0035     m_playAction->setCheckable(true);
0036     connect(m_playAction, &QAction::toggled, this, &RecManager::slotPreview);
0037 
0038     m_recAction = new QAction(QIcon::fromTheme(QStringLiteral("media-record")), i18n("Record"));
0039     m_recAction->setCheckable(true);
0040     connect(m_recAction, &QAction::toggled, this, &RecManager::slotRecord);
0041 
0042     m_showLogAction = new QAction(i18n("Show log"), this);
0043     connect(m_showLogAction, &QAction::triggered, this, &RecManager::slotShowLog);
0044 
0045     m_recVideo = new QCheckBox(i18n("Video"));
0046     m_recAudio = new QCheckBox(i18n("Audio"));
0047     m_recToolbar->addWidget(m_recVideo);
0048     m_recToolbar->addWidget(m_recAudio);
0049     m_recAudio->setChecked(KdenliveSettings::v4l_captureaudio());
0050     m_recVideo->setChecked(KdenliveSettings::v4l_capturevideo());
0051 
0052     QWidget *spacer = new QWidget(parent);
0053     spacer->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
0054     m_recToolbar->addWidget(spacer);
0055 
0056     m_audio_device = new QComboBox(parent);
0057     QStringList audioDevices = pCore->getAudioCaptureDevices();
0058     m_audio_device->addItems(audioDevices);
0059     connect(m_audio_device, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &RecManager::slotAudioDeviceChanged);
0060     QString selectedDevice = KdenliveSettings::defaultaudiocapture();
0061     int selectedIndex = m_audio_device->findText(selectedDevice);
0062     if (!selectedDevice.isNull() && selectedIndex > -1) {
0063         m_audio_device->setCurrentIndex(selectedIndex);
0064     }
0065     m_recToolbar->addWidget(m_audio_device);
0066 
0067     m_audioCaptureSlider = new QSlider(Qt::Vertical);
0068     m_audioCaptureSlider->setRange(0, 100);
0069     m_audioCaptureSlider->setValue(KdenliveSettings::audiocapturevolume());
0070     connect(m_audioCaptureSlider, &QSlider::valueChanged, this, &RecManager::slotSetVolume);
0071     auto *widgetslider = new QWidgetAction(parent);
0072     widgetslider->setText(i18n("Audio Capture Volume"));
0073     widgetslider->setDefaultWidget(m_audioCaptureSlider);
0074     auto *menu = new QMenu(parent);
0075     menu->addAction(widgetslider);
0076     m_audioCaptureButton = new QToolButton(parent);
0077     m_audioCaptureButton->setMenu(menu);
0078     m_audioCaptureButton->setToolTip(i18n("Audio Capture Volume"));
0079     m_audioCaptureButton->setPopupMode(QToolButton::InstantPopup);
0080     QIcon icon;
0081     if (KdenliveSettings::audiocapturevolume() == 0) {
0082         icon = QIcon::fromTheme(QStringLiteral("audio-volume-muted"));
0083     } else {
0084         icon = QIcon::fromTheme(QStringLiteral("audio-volume-medium"));
0085     }
0086     m_audioCaptureButton->setIcon(icon);
0087     m_recToolbar->addWidget(m_audioCaptureButton);
0088     m_recToolbar->addSeparator();
0089 
0090     m_device_selector = new QComboBox(parent);
0091     // TODO: re-implement firewire / decklink capture
0092     // m_device_selector->addItems(QStringList() << i18n("Firewire") << i18n("Webcam") << i18n("Screen Grab") << i18n("Blackmagic Decklink"));
0093     m_device_selector->addItem(i18n("Webcam"), Video4Linux);
0094     m_device_selector->addItem(i18n("Screen Grab"), ScreenGrab);
0095     selectedIndex = m_device_selector->findData(KdenliveSettings::defaultcapture());
0096 
0097     if (selectedIndex > -1) {
0098         m_device_selector->setCurrentIndex(selectedIndex);
0099     }
0100     connect(m_device_selector, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &RecManager::slotVideoDeviceChanged);
0101     m_recToolbar->addWidget(m_device_selector);
0102 
0103     QAction *configureRec = m_recToolbar->addAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("Configure Recording"));
0104     connect(configureRec, &QAction::triggered, this, &RecManager::showRecConfig);
0105     m_recToolbar->addSeparator();
0106     m_switchRec = m_recToolbar->addAction(QIcon::fromTheme(QStringLiteral("list-add")), i18n("Show Record Control"));
0107     m_switchRec->setCheckable(true);
0108     connect(m_switchRec, &QAction::toggled, m_monitor, &Monitor::slotSwitchRec);
0109     m_recToolbar->setVisible(false);
0110     slotVideoDeviceChanged();
0111     connect(m_monitor, &Monitor::screenChanged, this, &RecManager::slotSetScreen);
0112 }
0113 
0114 RecManager::~RecManager() = default;
0115 
0116 void RecManager::showRecConfig()
0117 {
0118     Q_EMIT pCore->showConfigDialog(Kdenlive::PageCapture, m_device_selector->currentData().toInt());
0119 }
0120 
0121 QToolBar *RecManager::toolbar() const
0122 {
0123     return m_recToolbar;
0124 }
0125 
0126 QAction *RecManager::recAction() const
0127 {
0128     return m_recAction;
0129 }
0130 
0131 void RecManager::stopCapture()
0132 {
0133     if (m_captureProcess) {
0134         slotRecord(false);
0135     } else if (pCore->getMediaCaptureState() == 1 && (m_checkAudio || m_checkVideo)) {
0136         // QMediaRecorder::RecordingState value is 1
0137         pCore->stopMediaCapture(-1, m_checkAudio, m_checkVideo);
0138         m_monitor->slotOpenClip(nullptr);
0139     }
0140 }
0141 
0142 void RecManager::stop()
0143 {
0144     if (m_captureProcess) {
0145         // Don't stop screen rec when hiding rec toolbar
0146     } else {
0147         stopCapture();
0148         m_switchRec->setChecked(false);
0149     }
0150     toolbar()->setVisible(false);
0151 }
0152 
0153 void RecManager::slotRecord(bool record)
0154 {
0155     /*if (m_device_selector->currentData().toInt() == Video4Linux) {
0156         if (record) {
0157             m_checkAudio = m_recAudio->isChecked();
0158             m_checkVideo = m_recVideo->isChecked();
0159             pCore->startMediaCapture(m_checkAudio, m_checkVideo);
0160         } else {
0161             stopCapture();
0162         }
0163         return;
0164     }*/
0165     if (!record) {
0166         if (!m_captureProcess) {
0167             return;
0168         }
0169         m_captureProcess->write("q");
0170         if (!m_captureProcess->waitForFinished()) {
0171             m_captureProcess->terminate();
0172             QTimer::singleShot(1500, m_captureProcess, &QProcess::kill);
0173         }
0174         return;
0175     }
0176     if (m_captureProcess) {
0177         return;
0178     }
0179     m_recError.clear();
0180 
0181     QString extension = KdenliveSettings::grab_extension();
0182     QDir captureFolder;
0183     if (KdenliveSettings::capturetoprojectfolder() < 2) {
0184         captureFolder = QDir(pCore->getProjectFolderName());
0185     } else {
0186         captureFolder = QDir(KdenliveSettings::capturefolder());
0187     }
0188 
0189     QFileInfo checkCaptureFolder(captureFolder.absolutePath());
0190     if (!checkCaptureFolder.isWritable()) {
0191         Q_EMIT warningMessage(i18n("The directory %1, could not be created.\nPlease "
0192                                    "make sure you have the required permissions.",
0193                                    captureFolder.absolutePath()));
0194         m_recAction->blockSignals(true);
0195         m_recAction->setChecked(false);
0196         m_recAction->blockSignals(false);
0197         return;
0198     }
0199 
0200     m_captureProcess = new QProcess;
0201     connect(m_captureProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &RecManager::slotProcessStatus);
0202     connect(m_captureProcess, &QProcess::readyReadStandardError, this, &RecManager::slotReadProcessInfo);
0203 
0204     QString path = captureFolder.absoluteFilePath("capture0000." + extension);
0205     int i = 1;
0206     while (QFile::exists(path)) {
0207         QString num = QString::number(i).rightJustified(4, '0', false);
0208         path = captureFolder.absoluteFilePath("capture" + num + QLatin1Char('.') + extension);
0209         ++i;
0210     }
0211     m_captureFile = QUrl::fromLocalFile(path);
0212     QStringList captureArgs;
0213 #ifdef Q_OS_WIN
0214     captureArgs << QStringLiteral("-f") << QStringLiteral("gdigrab") << QStringLiteral("-framerate") << QString::number(KdenliveSettings::grab_fps())
0215                 << QStringLiteral("-i") << QStringLiteral("desktop");
0216     if (KdenliveSettings::grab_parameters().contains(QLatin1String("alsa"))) {
0217         // Add audio device
0218         QString params = captureArgs.join(QLatin1Char(' '));
0219         params.append(QStringLiteral(" ") + KdenliveSettings::grab_parameters().simplified());
0220         // Use Windows dshow for audio capture
0221         params.replace(QLatin1String("alsa"), QStringLiteral("dshow"));
0222         // Remove vorbis codec
0223         params.replace(QLatin1String("-acodec libvorbis"), QString());
0224 
0225         // Find first audio device
0226         QProcess tst;
0227         tst.setProcessChannelMode(QProcess::MergedChannels);
0228         tst.start(KdenliveSettings::ffmpegpath(), {"-hide_banner", "-list_devices", "true", "-f", "dshow", "-i", "dummy"});
0229         tst.waitForStarted();
0230         tst.waitForFinished();
0231         QString dshowOutput = QString::fromUtf8(tst.readAllStandardOutput());
0232         // KMessageBox::information(QApplication::activeWindow(), dshowOutput);
0233         if (dshowOutput.contains(QLatin1String("DirectShow audio devices"))) {
0234             dshowOutput = dshowOutput.section(QLatin1String("DirectShow audio devices"), 1);
0235             qDebug() << "GOT FILTERED DSOW1: " << dshowOutput;
0236             // dshowOutput = dshowOutput.section(QLatin1Char('"'), 3, 3);
0237             if (dshowOutput.contains(QLatin1Char('"'))) {
0238                 dshowOutput = QString("audio=\"%1\"").arg(dshowOutput.section(QLatin1Char('"'), 1, 1));
0239                 qDebug().noquote() << "GOT FILTERED DSOW2: " << dshowOutput;
0240                 // captureArgs << params.split(QLatin1Char(' '));
0241                 // captureArgs.replace(captureArgs.indexOf(QLatin1String("default")), dshowOutput);
0242                 params.replace(QLatin1String("default"), dshowOutput);
0243             }
0244         } else {
0245             qDebug() << KdenliveSettings::ffmpegpath() << "=== GOT DSHOW DEVICES: " << dshowOutput;
0246             captureArgs << params.split(QLatin1Char(' '));
0247         }
0248         params.replace(QStringLiteral("\\\\"), QStringLiteral("\\"));
0249         params.replace(QStringLiteral("\\\""), QStringLiteral("\""));
0250         qDebug().noquote() << "== STARTING WIN CAPTURE: " << params << "\n___________";
0251         params.append(QStringLiteral(" ") + path);
0252         qDebug().noquote() << "== STARTING WIN CAPTURE: " << params << "\n___________";
0253         m_captureProcess->setNativeArguments(params);
0254         m_captureProcess->start(KdenliveSettings::ffmpegpath(), {});
0255     } else if (!KdenliveSettings::grab_parameters().simplified().isEmpty()) {
0256         captureArgs << KdenliveSettings::grab_parameters().simplified().split(QLatin1Char(' '));
0257         captureArgs << path;
0258         m_captureProcess->start(KdenliveSettings::ffmpegpath(), captureArgs);
0259     }
0260 #else
0261     captureArgs << QStringLiteral("-f") << QStringLiteral("x11grab");
0262     if (KdenliveSettings::grab_follow_mouse()) {
0263         captureArgs << QStringLiteral("-follow_mouse") << QStringLiteral("centered");
0264     }
0265     if (!KdenliveSettings::grab_hide_frame()) {
0266         captureArgs << QStringLiteral("-show_region") << QStringLiteral("1");
0267     }
0268     QString captureSize = QStringLiteral(":0.0");
0269     if (KdenliveSettings::grab_capture_type() == 0) {
0270         // Full screen capture
0271         QRect screenSize = QApplication::screens().at(m_screenIndex)->geometry();
0272         captureArgs << QStringLiteral("-s") << QString::number(screenSize.width()) + QLatin1Char('x') + QString::number(screenSize.height());
0273         captureSize.append(QLatin1Char('+') + QString::number(screenSize.left()) + QLatin1Char('.') + QString::number(screenSize.top()));
0274     } else {
0275         // Region capture
0276         captureArgs << QStringLiteral("-s")
0277                     << QString::number(KdenliveSettings::grab_width()) + QLatin1Char('x') + QString::number(KdenliveSettings::grab_height());
0278         captureSize.append(QLatin1Char('+') + QString::number(KdenliveSettings::grab_offsetx()) + QLatin1Char(',') +
0279                            QString::number(KdenliveSettings::grab_offsety()));
0280     }
0281     if (KdenliveSettings::grab_hide_mouse()) {
0282         captureSize.append(QStringLiteral("+nomouse"));
0283     }
0284     // fps
0285     captureArgs << QStringLiteral("-r") << QString::number(KdenliveSettings::grab_fps());
0286     captureArgs << QStringLiteral("-i") << captureSize;
0287     if (!KdenliveSettings::grab_parameters().simplified().isEmpty()) {
0288         captureArgs << KdenliveSettings::grab_parameters().simplified().split(QLatin1Char(' '));
0289     }
0290     qDebug() << "== STARTING X11 CAPTURE: " << captureArgs << "\n___________";
0291     captureArgs << path;
0292     m_captureProcess->start(KdenliveSettings::ffmpegpath(), captureArgs);
0293 #endif
0294 
0295     if (!m_captureProcess->waitForStarted()) {
0296         // Problem launching capture app
0297         Q_EMIT warningMessage(i18n("Failed to start the capture application:\n%1", KdenliveSettings::ffmpegpath()));
0298         // delete m_captureProcess;
0299     }
0300 }
0301 
0302 void RecManager::slotProcessStatus(int exitCode, QProcess::ExitStatus exitStatus)
0303 {
0304     m_recAction->setEnabled(true);
0305     m_recAction->setChecked(false);
0306     m_device_selector->setEnabled(true);
0307     if (exitStatus == QProcess::CrashExit) {
0308         Q_EMIT warningMessage(i18n("Capture crashed, please check your parameters"), -1, QList<QAction *>() << m_showLogAction);
0309     } else {
0310         if (exitCode != 0 && exitCode != 255) {
0311             Q_EMIT warningMessage(i18n("Capture crashed, please check your parameters"), -1, QList<QAction *>() << m_showLogAction);
0312         } else {
0313             // Capture successful, add clip to project
0314             Q_EMIT addClipToProject(m_captureFile);
0315         }
0316     }
0317     if (m_captureProcess) {
0318         delete m_captureProcess;
0319         m_captureProcess = nullptr;
0320     }
0321 }
0322 
0323 void RecManager::slotReadProcessInfo()
0324 {
0325     QString data = m_captureProcess->readAllStandardError().simplified();
0326     m_recError.append(data + QLatin1Char('\n'));
0327 }
0328 
0329 void RecManager::slotAudioDeviceChanged(int)
0330 {
0331     QString currentDevice = m_audio_device->currentText();
0332     KdenliveSettings::setDefaultaudiocapture(currentDevice);
0333 }
0334 
0335 void RecManager::slotSetVolume(int volume)
0336 {
0337     KdenliveSettings::setAudiocapturevolume(volume);
0338     QIcon icon;
0339 
0340     if (volume == 0) {
0341         icon = QIcon::fromTheme(QStringLiteral("audio-volume-muted"));
0342     } else {
0343         icon = QIcon::fromTheme(QStringLiteral("audio-volume-medium"));
0344     }
0345     m_audioCaptureButton->setIcon(icon);
0346 }
0347 void RecManager::slotVideoDeviceChanged(int)
0348 {
0349     int currentItem = m_device_selector->currentData().toInt();
0350     KdenliveSettings::setDefaultcapture(currentItem);
0351     switch (currentItem) {
0352     case Video4Linux:
0353         m_playAction->setEnabled(true);
0354         break;
0355     case BlackMagic:
0356         m_playAction->setEnabled(false);
0357         break;
0358     default:
0359         m_playAction->setEnabled(false);
0360         break;
0361     }
0362     /*
0363     m_previewSettings->setEnabled(ix == Video4Linux || ix == BlackMagic);
0364     control_frame->setVisible(ix == Video4Linux);
0365     monitor_box->setVisible(ix == ScreenBag && monitor_box->count() > 0);
0366     m_playAction->setVisible(ix != ScreenBag);
0367     m_fwdAction->setVisible(ix == Firewire);
0368     m_discAction->setVisible(ix == Firewire);
0369     m_rewAction->setVisible(ix == Firewire);
0370     m_recAction->setEnabled(ix != Firewire);
0371     m_logger.setVisible(ix == BlackMagic);
0372     if (m_captureDevice) {
0373         // MLT capture still running, abort
0374         m_monitorManager->clearScopeSource();
0375         m_captureDevice->stop();
0376         delete m_captureDevice;
0377         m_captureDevice = nullptr;
0378     }
0379 
0380     // The m_videoBox container has to be shown once before the MLT consumer is build, or preview will fail
0381     switch (ix) {
0382     case ScreenBag:
0383     }
0384     */
0385 }
0386 
0387 Mlt::Producer *RecManager::createV4lProducer()
0388 {
0389     QString profilePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/profiles/video4linux");
0390     Mlt::Profile *vidProfile = new Mlt::Profile(profilePath.toUtf8().constData());
0391     bool profileUsed = false;
0392     Mlt::Producer *prod = nullptr;
0393     if (m_recVideo->isChecked()) {
0394         prod = new Mlt::Producer(*vidProfile, QStringLiteral("video4linux2:%1").arg(KdenliveSettings::video4vdevice()).toUtf8().constData());
0395         if ((prod == nullptr) || !prod->is_valid()) {
0396             return nullptr;
0397         }
0398         prod->set("width", vidProfile->width());
0399         prod->set("height", vidProfile->height());
0400         prod->set("framerate", vidProfile->fps());
0401         /*p->set("standard", ui->v4lStandardCombo->currentText().toLatin1().constData());
0402         p->set("channel", ui->v4lChannelSpinBox->value());
0403         p->set("audio_ix", ui->v4lAudioComboBox->currentIndex());*/
0404         prod->set("force_seekable", 0);
0405         profileUsed = true;
0406     }
0407     if (m_recAudio->isChecked() && (prod != nullptr) && prod->is_valid()) {
0408         // Add audio track
0409         Mlt::Producer *audio = new Mlt::Producer(
0410             *vidProfile,
0411             QStringLiteral("alsa:%1?channels=%2").arg(KdenliveSettings::v4l_alsadevicename()).arg(KdenliveSettings::alsachannels()).toUtf8().constData());
0412         audio->set("mlt_service", "avformat-novalidate");
0413         audio->set("audio_index", 0);
0414         audio->set("video_index", -1);
0415         auto *tractor = new Mlt::Tractor(*vidProfile);
0416         tractor->set_track(*prod, 0);
0417         delete prod;
0418         tractor->set_track(*audio, 1);
0419         delete audio;
0420         prod = new Mlt::Producer(tractor->get_producer());
0421         delete tractor;
0422         profileUsed = true;
0423     }
0424     if (!profileUsed) {
0425         delete vidProfile;
0426     }
0427     return prod;
0428 }
0429 
0430 void RecManager::slotSetScreen(int screenIndex)
0431 {
0432     m_screenIndex = screenIndex;
0433 }
0434 
0435 void RecManager::slotPreview(bool preview)
0436 {
0437     if (m_device_selector->currentData().toInt() == Video4Linux) {
0438         if (preview) {
0439             std::shared_ptr<Mlt::Producer> prod(createV4lProducer());
0440             if (prod && prod->is_valid()) {
0441                 m_monitor->updateClipProducer(prod);
0442             } else {
0443                 Q_EMIT warningMessage(i18n("Capture crashed, please check your parameters"));
0444             }
0445         } else {
0446             m_monitor->slotOpenClip(nullptr);
0447         }
0448     }
0449 
0450     /*
0451        buildMltDevice(path);
0452 
0453        bool isXml;
0454        producer = getV4lXmlPlaylist(profile, &isXml);
0455 
0456        //producer =
0457     QString("avformat-novalidate:video4linux2:%1?width:%2&height:%3&frame_rate:%4").arg(KdenliveSettings::video4vdevice()).arg(profile.width).arg(profile.height).arg((double)
0458     profile.frame_rate_num / profile.frame_rate_den);
0459        if (!m_captureDevice->slotStartPreview(producer, isXml)) {
0460            // v4l capture failed to start
0461            video_frame->setText(i18n("Failed to start Video4Linux,\ncheck your parameters…"));
0462 
0463        } else {
0464            m_playAction->setEnabled(false);
0465            m_stopAction->setEnabled(true);
0466            m_isPlaying = true;
0467        }
0468     }*/
0469 }
0470 
0471 void RecManager::slotShowLog()
0472 {
0473     KMessageBox::information(QApplication::activeWindow(), m_recError);
0474 }