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 }