diff --git a/src/capture/mediacapture.cpp b/src/capture/mediacapture.cpp index f2d3d0370..b801bff62 100644 --- a/src/capture/mediacapture.cpp +++ b/src/capture/mediacapture.cpp @@ -1,279 +1,331 @@ /* Copyright (C) 2019 Akhil K Gangadharan This file is part of Kdenlive. See www.kdenlive.org. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "mediacapture.h" +#include "kdenlivesettings.h" +#include "core.h" #include +#include #include #include MediaCapture::MediaCapture(QObject *parent) : QObject(parent) , m_volume(1.) + , currentState(-1) + , m_audioDevice("default:") + , m_path(QUrl()) + , m_recordState(0) { m_probe = std::make_unique(this); connect(m_probe.get(), &QAudioProbe::audioBufferProbed, this, &MediaCapture::processBuffer); } MediaCapture::~MediaCapture() = default; void MediaCapture::displayErrorMessage() { qDebug() << " !!!!!!!!!!!!!!!! ERROR : QMediarecorder - Capture failed"; } void MediaCapture::recordAudio(bool record) { if (!m_audioRecorder) { m_audioRecorder = std::make_unique(this); + m_probe->setSource(m_audioRecorder.get()); + connect(m_audioRecorder.get(), &QAudioRecorder::stateChanged, [&] (QMediaRecorder::State state) { + m_recordState = state; + emit recordStateChanged(); + }); } - m_probe->setSource(m_audioRecorder.get()); if (record && m_audioRecorder->state() == QMediaRecorder::StoppedState) { + setAudioCaptureDevice(); m_audioRecorder->setAudioInput(m_audioDevice); - m_audioRecorder->setVolume(m_volume); + setCaptureOutputLocation(); m_audioRecorder->setOutputLocation(m_path); + setAudioVolume(); + m_audioRecorder->setVolume(m_volume); + connect(m_audioRecorder.get(), SIGNAL(error(QMediaRecorder::Error)), this, SLOT(displayErrorMessage())); QAudioEncoderSettings audioSettings; - audioSettings.setBitRate(48000); + audioSettings.setBitRate(48000); // Bit rate is set to 48,0000 QString container = "audio/x-wav"; m_audioRecorder->setEncodingSettings(audioSettings, QVideoEncoderSettings(), container); m_audioRecorder->record(); } else if (m_audioRecorder->state() != QMediaRecorder::PausedState) { m_audioRecorder->stop(); m_audioRecorder.reset(); + m_levels.clear(); + emit levelsChanged(); + m_recordState = QMediaRecorder::StoppedState; + emit recordStateChanged(); } else { m_audioRecorder->record(); } } void MediaCapture::recordVideo(bool record) { // TO DO - fix video capture if (!m_videoRecorder) { QList availableCameras = QCameraInfo::availableCameras(); foreach (const QCameraInfo &cameraInfo, availableCameras) { if (cameraInfo == QCameraInfo::defaultCamera()) { m_camera = std::make_unique(cameraInfo, this); break; } } m_videoRecorder = std::make_unique(m_camera.get(), this); } if (record && m_videoRecorder->state() == QMediaRecorder::StoppedState) { - connect(m_videoRecorder.get(), SIGNAL(error(QMediaRecorder::Error)), this, SLOT(displayErrorMessage())); - + setCaptureOutputLocation(); m_videoRecorder->setOutputLocation(m_path); + connect(m_videoRecorder.get(), SIGNAL(error(QMediaRecorder::Error)), this, SLOT(displayErrorMessage())); m_camera->setCaptureMode(QCamera::CaptureVideo); m_camera->start(); // QString container = "video/mpeg"; // By default, Qt chooses appropriate parameters m_videoRecorder->record(); } else { m_videoRecorder->stop(); m_camera->stop(); m_videoRecorder.reset(); m_camera.reset(); } } -void MediaCapture::setCaptureOutputLocation(QUrl path) +void MediaCapture::setCaptureOutputLocation() +{ + QDir captureFolder; + if (KdenliveSettings::capturetoprojectfolder()) { + captureFolder = QDir(pCore->getProjectFolderName()); + } else { + captureFolder = QDir(KdenliveSettings::capturefolder()); + } + QString extension; + if (m_videoRecorder.get() != nullptr) { + extension = QStringLiteral(".mpeg"); + } else if (m_audioRecorder.get() != nullptr) { + extension = QStringLiteral(".wav"); + } + + QString path = captureFolder.absoluteFilePath("capture0000" + extension); + int fileCount = 1; + while (QFile::exists(path)) { + QString num = QString::number(fileCount).rightJustified(4, '0', false); + path = captureFolder.absoluteFilePath("capture" + num + extension); + ++fileCount; + } + m_path = QUrl::fromLocalFile(path); +} + +QUrl MediaCapture::getCaptureOutputLocation() { - m_path = std::move(path); + return m_path; } QStringList MediaCapture::getAudioCaptureDevices() { - m_audioRecorder = std::make_unique(this); - QStringList audioDevices = m_audioRecorder->audioInputs(); - m_audioRecorder.reset(); + std::unique_ptr audioRecorder = std::make_unique(this); + QStringList audioDevices = audioRecorder->audioInputs(); + audioRecorder.reset(); return audioDevices; } -void MediaCapture::setAudioCaptureDevice(QString audioDevice) +void MediaCapture::setAudioCaptureDevice() { - m_audioDevice = std::move(audioDevice); + QString deviceName = KdenliveSettings::defaultaudiocapture(); + if(!deviceName.isNull()) { + m_audioDevice = deviceName; + } } -void MediaCapture::setAudioVolume(qreal volume) +void MediaCapture::setAudioVolume() { - m_volume = volume; + m_volume = KdenliveSettings::audiocapturevolume()/100.0; } int MediaCapture::getState() { if (m_audioRecorder != nullptr) { - return m_audioRecorder->state(); + currentState = m_audioRecorder->state(); } - if (m_videoRecorder != nullptr) { - return m_videoRecorder->state(); + else if (m_videoRecorder != nullptr) { + currentState = m_videoRecorder->state(); } - return -1; + return currentState; } template QVector getBufferLevels(const T *buffer, int frames, int channels) { QVector max_values; max_values.fill(0, channels); for (int i = 0; i < frames; ++i) { for (int j = 0; j < channels; ++j) { qreal value = qAbs(qreal(buffer[i * channels + j])); if (value > max_values.at(j)) { max_values.replace(j, value); } } } return max_values; } // This function returns the maximum possible sample value for a given audio format qreal getPeakValue(const QAudioFormat &format) { // Note: Only the most common sample formats are supported if (!format.isValid()) { return qreal(0); } if (format.codec() != "audio/pcm") { return qreal(0); } switch (format.sampleType()) { case QAudioFormat::Unknown: break; case QAudioFormat::Float: if (format.sampleSize() != 32) { // other sample formats are not supported return qreal(0); } return qreal(1.00003); case QAudioFormat::SignedInt: if (format.sampleSize() == 32) { return qreal(INT_MAX); } if (format.sampleSize() == 16) { return qreal(SHRT_MAX); } if (format.sampleSize() == 8) { return qreal(CHAR_MAX); } break; case QAudioFormat::UnSignedInt: if (format.sampleSize() == 32) { return qreal(UINT_MAX); } if (format.sampleSize() == 16) { return qreal(USHRT_MAX); } if (format.sampleSize() == 8) { return qreal(UCHAR_MAX); } break; } return qreal(0); } QVector getBufferLevels(const QAudioBuffer &buffer) { QVector values; if (!buffer.format().isValid() || buffer.format().byteOrder() != QAudioFormat::LittleEndian) { return values; } if (buffer.format().codec() != "audio/pcm") { return values; } int channelCount = buffer.format().channelCount(); values.fill(0, channelCount); qreal peak_value = getPeakValue(buffer.format()); if (qFuzzyCompare(peak_value, qreal(0))) { return values; } switch (buffer.format().sampleType()) { case QAudioFormat::Unknown: case QAudioFormat::UnSignedInt: if (buffer.format().sampleSize() == 32) { values = getBufferLevels(buffer.constData(), buffer.frameCount(), channelCount); } if (buffer.format().sampleSize() == 16) { values = getBufferLevels(buffer.constData(), buffer.frameCount(), channelCount); } if (buffer.format().sampleSize() == 8) { values = getBufferLevels(buffer.constData(), buffer.frameCount(), channelCount); } for (double &value : values) { value = qAbs(value - peak_value / 2) / (peak_value / 2); } break; case QAudioFormat::Float: if (buffer.format().sampleSize() == 32) { values = getBufferLevels(buffer.constData(), buffer.frameCount(), channelCount); for (double &value : values) { value /= peak_value; } } break; case QAudioFormat::SignedInt: if (buffer.format().sampleSize() == 32) { values = getBufferLevels(buffer.constData(), buffer.frameCount(), channelCount); } if (buffer.format().sampleSize() == 16) { values = getBufferLevels(buffer.constData(), buffer.frameCount(), channelCount); } if (buffer.format().sampleSize() == 8) { values = getBufferLevels(buffer.constData(), buffer.frameCount(), channelCount); } for (double &value : values) { value /= peak_value; } break; } return values; } void MediaCapture::processBuffer(const QAudioBuffer &buffer) { m_levels = getBufferLevels(buffer); emit levelsChanged(); } QVector MediaCapture::levels() const { return m_levels; } +int MediaCapture::recordState() const +{ + return m_recordState; +} + bool MediaCapture::isRecording() const { if (m_audioRecorder && m_audioRecorder->state() == QMediaRecorder::RecordingState) { return true; } if (m_videoRecorder && m_videoRecorder->state() == QMediaRecorder::RecordingState) { return true; } return false; } diff --git a/src/capture/mediacapture.h b/src/capture/mediacapture.h index 7f961a43e..bb376322f 100644 --- a/src/capture/mediacapture.h +++ b/src/capture/mediacapture.h @@ -1,83 +1,86 @@ /* Copyright (C) 2019 Akhil K Gangadharan This file is part of Kdenlive. See www.kdenlive.org. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef MEDIACAPTURE_H #define MEDIACAPTURE_H #include #include #include #include #include #include #include #include #include #include class QAudioRecorder; class QAudioProbe; class MediaCapture : public QObject { Q_OBJECT Q_PROPERTY(QVector levels READ levels NOTIFY levelsChanged) public: MediaCapture(QObject *parent); ~MediaCapture() override; void recordAudio(bool /*record*/); void recordVideo(bool /*record*/); /** @brief Returns true if a recording is in progress **/ bool isRecording() const; /** @brief Sets m_path to selected output location **/ - void setCaptureOutputLocation(QUrl /*path*/); - /** @brief Sets m_device to selected audio capture device **/ - void setAudioCaptureDevice(QString /*audioDevice*/); + void setCaptureOutputLocation(); + /** @brief Returns m_path **/ + QUrl getCaptureOutputLocation(); + /** @brief Sets m_audioDevice to selected audio capture device **/ + void setAudioCaptureDevice(); /** @brief Sets m_volume to selected audio capture volume **/ - void setAudioVolume(qreal /*volume*/); + void setAudioVolume(); /** @brief Returns list of audio devices available for capture **/ QStringList getAudioCaptureDevices(); - /** @brief Returns QMediaRecorder::State value **/ + /** @brief Sets currentState to QMediaRecorder::State value and returns it **/ int getState(); + int currentState; Q_INVOKABLE QVector levels() const; public slots: void displayErrorMessage(); private: std::unique_ptr m_audioRecorder; std::unique_ptr m_videoRecorder; std::unique_ptr m_camera; std::unique_ptr m_probe; QString m_audioDevice; qreal m_volume; QUrl m_path; QVector m_levels; private slots: void processBuffer(const QAudioBuffer &buffer); signals: void levelsChanged(); }; #endif diff --git a/src/core.cpp b/src/core.cpp index e5be4ef8b..1642da363 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -1,726 +1,727 @@ /* Copyright (C) 2014 Till Theato This file is part of kdenlive. See www.kdenlive.org. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. */ #include "core.h" #include "bin/bin.h" #include "bin/projectitemmodel.h" #include "capture/mediacapture.h" #include "doc/docundostack.hpp" #include "doc/kdenlivedoc.h" #include "jobs/jobmanager.h" #include "kdenlive_debug.h" #include "kdenlivesettings.h" #include "library/librarywidget.h" #include "mainwindow.h" #include "mltconnection.h" #include "mltcontroller/clipcontroller.h" #include "monitor/monitormanager.h" #include "profiles/profilemodel.hpp" #include "profiles/profilerepository.hpp" #include "project/projectmanager.h" #include "timeline2/model/timelineitemmodel.hpp" #include "timeline2/view/timelinecontroller.h" #include "timeline2/view/timelinewidget.h" #include #include #include #include +#include #include #include #ifdef Q_OS_MAC #include #endif std::unique_ptr Core::m_self; Core::Core() : m_thumbProfile(nullptr) , m_capture(new MediaCapture(this)) { } void Core::prepareShutdown() { m_guiConstructed = false; } Core::~Core() { if (m_monitorManager) { delete m_monitorManager; } // delete m_binWidget; if (m_projectManager) { delete m_projectManager; } ClipController::mediaUnavailable.reset(); } void Core::build(const QString &MltPath) { if (m_self) { return; } m_self.reset(new Core()); m_self->initLocale(); qRegisterMetaType("audioShortVector"); qRegisterMetaType>("QVector"); qRegisterMetaType("MessageType"); qRegisterMetaType("stringMap"); qRegisterMetaType("audioByteArray"); qRegisterMetaType>("QList"); qRegisterMetaType>("std::shared_ptr"); qRegisterMetaType>(); qRegisterMetaType("QDomElement"); qRegisterMetaType("requestClipInfo"); // Open connection with Mlt MltConnection::construct(MltPath); // load the profile from disk ProfileRepository::get()->refresh(); // load default profile m_self->m_profile = KdenliveSettings::default_profile(); if (m_self->m_profile.isEmpty()) { m_self->m_profile = ProjectManager::getDefaultProjectFormat(); KdenliveSettings::setDefault_profile(m_self->m_profile); } // Init producer shown for unavailable media // TODO make it a more proper image, it currently causes a crash on exit ClipController::mediaUnavailable = std::make_shared(ProfileRepository::get()->getProfile(m_self->m_profile)->profile(), "color:blue"); ClipController::mediaUnavailable->set("length", 99999999); m_self->m_projectItemModel = ProjectItemModel::construct(); // Job manager must be created before bin to correctly connect m_self->m_jobManager.reset(new JobManager(m_self.get())); } void Core::initGUI(const QUrl &Url) { m_guiConstructed = true; m_profile = KdenliveSettings::default_profile(); m_currentProfile = m_profile; profileChanged(); m_mainWindow = new MainWindow(); // load default profile and ask user to select one if not found. if (m_profile.isEmpty()) { m_profile = ProjectManager::getDefaultProjectFormat(); profileChanged(); KdenliveSettings::setDefault_profile(m_profile); } if (!ProfileRepository::get()->profileExists(m_profile)) { KMessageBox::sorry(m_mainWindow, i18n("The default profile of Kdenlive is not set or invalid, press OK to set it to a correct value.")); // TODO this simple widget should be improved and probably use profileWidget // we get the list of profiles QVector> all_profiles = ProfileRepository::get()->getAllProfiles(); QStringList all_descriptions; for (const auto &profile : all_profiles) { all_descriptions << profile.first; } // ask the user bool ok; QString item = QInputDialog::getItem(m_mainWindow, i18n("Select Default Profile"), i18n("Profile:"), all_descriptions, 0, false, &ok); if (ok) { ok = false; for (const auto &profile : all_profiles) { if (profile.first == item) { m_profile = profile.second; ok = true; } } } if (!ok) { KMessageBox::error( m_mainWindow, i18n("The given profile is invalid. We default to the profile \"dv_pal\", but you can change this from Kdenlive's settings panel")); m_profile = QStringLiteral("dv_pal"); } KdenliveSettings::setDefault_profile(m_profile); profileChanged(); } m_projectManager = new ProjectManager(this); m_binWidget = new Bin(m_projectItemModel, m_mainWindow); m_library = new LibraryWidget(m_projectManager, m_mainWindow); connect(m_library, SIGNAL(addProjectClips(QList)), m_binWidget, SLOT(droppedUrls(QList))); connect(this, &Core::updateLibraryPath, m_library, &LibraryWidget::slotUpdateLibraryPath); m_monitorManager = new MonitorManager(this); // Producer queue, creating MLT::Producers on request /* m_producerQueue = new ProducerQueue(m_binController); connect(m_producerQueue, &ProducerQueue::gotFileProperties, m_binWidget, &Bin::slotProducerReady); connect(m_producerQueue, &ProducerQueue::replyGetImage, m_binWidget, &Bin::slotThumbnailReady); connect(m_producerQueue, &ProducerQueue::requestProxy, [this](const QString &id){ m_binWidget->startJob(id, AbstractClipJob::PROXYJOB);}); connect(m_producerQueue, &ProducerQueue::removeInvalidClip, m_binWidget, &Bin::slotRemoveInvalidClip, Qt::DirectConnection); connect(m_producerQueue, SIGNAL(addClip(QString, QMap)), m_binWidget, SLOT(slotAddUrl(QString, QMap))); connect(m_binController.get(), SIGNAL(createThumb(QDomElement, QString, int)), m_producerQueue, SLOT(getFileProperties(QDomElement, QString, int))); connect(m_binWidget, &Bin::producerReady, m_producerQueue, &ProducerQueue::slotProcessingDone, Qt::DirectConnection); // TODO connect(m_producerQueue, SIGNAL(removeInvalidProxy(QString,bool)), m_binWidget, SLOT(slotRemoveInvalidProxy(QString,bool)));*/ m_mainWindow->init(); projectManager()->init(Url, QString()); if (qApp->isSessionRestored()) { // NOTE: we are restoring only one window, because Kdenlive only uses one MainWindow m_mainWindow->restore(1, false); } QMetaObject::invokeMethod(pCore->projectManager(), "slotLoadOnOpen", Qt::QueuedConnection); m_mainWindow->show(); } std::unique_ptr &Core::self() { if (!m_self) { qDebug() << "Error : Core has not been created"; } return m_self; } MainWindow *Core::window() { return m_mainWindow; } ProjectManager *Core::projectManager() { return m_projectManager; } MonitorManager *Core::monitorManager() { return m_monitorManager; } Monitor *Core::getMonitor(int id) { if (id == Kdenlive::ClipMonitor) { return m_monitorManager->clipMonitor(); } return m_monitorManager->projectMonitor(); } Bin *Core::bin() { return m_binWidget; } void Core::selectBinClip(const QString &clipId, int frame, const QPoint &zone) { m_binWidget->selectClipById(clipId, frame, zone); } std::shared_ptr Core::jobManager() { return m_jobManager; } LibraryWidget *Core::library() { return m_library; } void Core::initLocale() { QLocale systemLocale = QLocale(); #ifndef Q_OS_MAC setlocale(LC_NUMERIC, nullptr); #else setlocale(LC_NUMERIC_MASK, nullptr); #endif // localeconv()->decimal_point does not give reliable results on Windows #ifndef Q_OS_WIN char *separator = localeconv()->decimal_point; if (QString::fromUtf8(separator) != QChar(systemLocale.decimalPoint())) { // qCDebug(KDENLIVE_LOG)<<"------\n!!! system locale is not similar to Qt's locale... be prepared for bugs!!!\n------"; // HACK: There is a locale conflict, so set locale to C // Make sure to override exported values or it won't work qputenv("LANG", "C"); #ifndef Q_OS_MAC setlocale(LC_NUMERIC, "C"); #else setlocale(LC_NUMERIC_MASK, "C"); #endif systemLocale = QLocale::c(); } #endif systemLocale.setNumberOptions(QLocale::OmitGroupSeparator); QLocale::setDefault(systemLocale); } std::unique_ptr &Core::getMltRepository() { return MltConnection::self()->getMltRepository(); } std::unique_ptr &Core::getCurrentProfile() const { return ProfileRepository::get()->getProfile(m_currentProfile); } const QString &Core::getCurrentProfilePath() const { return m_currentProfile; } bool Core::setCurrentProfile(const QString &profilePath) { if (m_currentProfile == profilePath) { // no change required return true; } if (ProfileRepository::get()->profileExists(profilePath)) { m_currentProfile = profilePath; m_thumbProfile.reset(); // inform render widget m_mainWindow->updateRenderWidgetProfile(); if (m_guiConstructed && m_mainWindow->getCurrentTimeline()->controller()->getModel()) { m_mainWindow->getCurrentTimeline()->controller()->getModel()->updateProfile(&getCurrentProfile()->profile()); checkProfileValidity(); } return true; } return false; } void Core::checkProfileValidity() { int offset = (getCurrentProfile()->profile().width() % 8) + (getCurrentProfile()->profile().height() % 2); if (offset > 0) { // Profile is broken, warn user if (m_binWidget) { m_binWidget->displayBinMessage(i18n("Your project profile is invalid, rendering might fail."), KMessageWidget::Warning); } } } double Core::getCurrentSar() const { return getCurrentProfile()->sar(); } double Core::getCurrentDar() const { return getCurrentProfile()->dar(); } double Core::getCurrentFps() const { return getCurrentProfile()->fps(); } QSize Core::getCurrentFrameDisplaySize() const { return {(int)(getCurrentProfile()->height() * getCurrentDar() + 0.5), getCurrentProfile()->height()}; } QSize Core::getCurrentFrameSize() const { return {getCurrentProfile()->width(), getCurrentProfile()->height()}; } void Core::requestMonitorRefresh() { if (!m_guiConstructed) return; m_monitorManager->refreshProjectMonitor(); } void Core::refreshProjectRange(QSize range) { if (!m_guiConstructed) return; m_monitorManager->refreshProjectRange(range); } int Core::getItemPosition(const ObjectId &id) { if (!m_guiConstructed) return 0; switch (id.first) { case ObjectType::TimelineClip: if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) { return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipPosition(id.second); } break; case ObjectType::TimelineComposition: if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isComposition(id.second)) { return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getCompositionPosition(id.second); } break; case ObjectType::BinClip: return 0; break; default: qDebug() << "ERROR: unhandled object type"; } return 0; } int Core::getItemIn(const ObjectId &id) { if (!m_guiConstructed) { qDebug() << "/ / // QUERYING ITEM IN BUT GUI NOT BUILD!!"; return 0; } switch (id.first) { case ObjectType::TimelineClip: if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) { return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipIn(id.second); } break; case ObjectType::TimelineComposition: return 0; break; case ObjectType::BinClip: return 0; break; default: qDebug() << "ERROR: unhandled object type"; } return 0; } PlaylistState::ClipState Core::getItemState(const ObjectId &id) { if (!m_guiConstructed) return PlaylistState::Disabled; switch (id.first) { case ObjectType::TimelineClip: if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) { return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipState(id.second); } break; case ObjectType::TimelineComposition: return PlaylistState::VideoOnly; break; case ObjectType::BinClip: return m_binWidget->getClipState(id.second); break; case ObjectType::TimelineTrack: return m_mainWindow->getCurrentTimeline()->controller()->getModel()->isAudioTrack(id.second) ? PlaylistState::AudioOnly : PlaylistState::VideoOnly; default: qDebug() << "ERROR: unhandled object type"; break; } return PlaylistState::Disabled; } int Core::getItemDuration(const ObjectId &id) { if (!m_guiConstructed) return 0; switch (id.first) { case ObjectType::TimelineClip: if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) { return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipPlaytime(id.second); } break; case ObjectType::TimelineComposition: if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isComposition(id.second)) { return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getCompositionPlaytime(id.second); } break; case ObjectType::BinClip: return (int)m_binWidget->getClipDuration(id.second); break; default: qDebug() << "ERROR: unhandled object type"; } return 0; } int Core::getItemTrack(const ObjectId &id) { if (!m_guiConstructed) return 0; switch (id.first) { case ObjectType::TimelineClip: case ObjectType::TimelineComposition: return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getItemTrackId(id.second); break; default: qDebug() << "ERROR: unhandled object type"; } return 0; } void Core::refreshProjectItem(const ObjectId &id) { if (!m_guiConstructed || m_mainWindow->getCurrentTimeline()->loading) return; switch (id.first) { case ObjectType::TimelineClip: if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isClip(id.second)) { m_mainWindow->getCurrentTimeline()->controller()->refreshItem(id.second); } break; case ObjectType::TimelineComposition: if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isComposition(id.second)) { m_mainWindow->getCurrentTimeline()->controller()->refreshItem(id.second); } break; case ObjectType::TimelineTrack: if (m_mainWindow->getCurrentTimeline()->controller()->getModel()->isTrack(id.second)) { requestMonitorRefresh(); } break; case ObjectType::BinClip: m_monitorManager->refreshClipMonitor(); break; default: qDebug() << "ERROR: unhandled object type"; } } bool Core::hasTimelinePreview() const { if (!m_guiConstructed) { return false; } return m_mainWindow->getCurrentTimeline()->controller()->renderedChunks().size() > 0; } KdenliveDoc *Core::currentDoc() { return m_projectManager->current(); } int Core::projectDuration() const { if (!m_guiConstructed) { return 0; } return m_mainWindow->getCurrentTimeline()->controller()->duration(); } void Core::profileChanged() { GenTime::setFps(getCurrentFps()); } void Core::pushUndo(const Fun &undo, const Fun &redo, const QString &text) { undoStack()->push(new FunctionalUndoCommand(undo, redo, text)); } void Core::pushUndo(QUndoCommand *command) { undoStack()->push(command); } void Core::displayMessage(const QString &message, MessageType type, int timeout) { if (m_mainWindow) { m_mainWindow->displayMessage(message, type, timeout); } else { qDebug() << message; } } void Core::displayBinMessage(const QString &text, int type, const QList &actions) { m_binWidget->doDisplayMessage(text, (KMessageWidget::MessageType)type, actions); } void Core::displayBinLogMessage(const QString &text, int type, const QString &logInfo) { m_binWidget->doDisplayMessage(text, (KMessageWidget::MessageType)type, logInfo); } void Core::clearAssetPanel(int itemId) { if (m_guiConstructed) m_mainWindow->clearAssetPanel(itemId); } std::shared_ptr Core::getItemEffectStack(int itemType, int itemId) { if (!m_guiConstructed) return nullptr; switch (itemType) { case (int)ObjectType::TimelineClip: return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipEffectStack(itemId); case (int)ObjectType::TimelineTrack: return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getTrackEffectStackModel(itemId); break; case (int)ObjectType::BinClip: return m_binWidget->getClipEffectStack(itemId); default: return nullptr; } } std::shared_ptr Core::undoStack() { return projectManager()->undoStack(); } QMap Core::getVideoTrackNames() { if (!m_guiConstructed) return QMap(); return m_mainWindow->getCurrentTimeline()->controller()->getTrackNames(true); } QPair Core::getCompositionATrack(int cid) const { if (!m_guiConstructed) return {}; return m_mainWindow->getCurrentTimeline()->controller()->getCompositionATrack(cid); } bool Core::compositionAutoTrack(int cid) const { return m_mainWindow->getCurrentTimeline()->controller()->compositionAutoTrack(cid); } void Core::setCompositionATrack(int cid, int aTrack) { if (!m_guiConstructed) return; m_mainWindow->getCurrentTimeline()->controller()->setCompositionATrack(cid, aTrack); } std::shared_ptr Core::projectItemModel() { return m_projectItemModel; } void Core::invalidateRange(QSize range) { if (!m_mainWindow || m_mainWindow->getCurrentTimeline()->loading) return; m_mainWindow->getCurrentTimeline()->controller()->invalidateZone(range.width(), range.height()); } void Core::invalidateItem(ObjectId itemId) { if (!m_mainWindow || m_mainWindow->getCurrentTimeline()->loading) return; switch (itemId.first) { case ObjectType::TimelineClip: case ObjectType::TimelineComposition: m_mainWindow->getCurrentTimeline()->controller()->invalidateItem(itemId.second); break; case ObjectType::TimelineTrack: // TODO: invalidate all clips in track break; default: // bin clip should automatically be reloaded, compositions should not have effects break; } } double Core::getClipSpeed(int id) const { return m_mainWindow->getCurrentTimeline()->controller()->getModel()->getClipSpeed(id); } void Core::updateItemKeyframes(ObjectId id) { if (id.first == ObjectType::TimelineClip && m_mainWindow) { m_mainWindow->getCurrentTimeline()->controller()->updateClip(id.second, {TimelineModel::KeyframesRole}); } } void Core::updateItemModel(ObjectId id, const QString &service) { if (m_mainWindow && !m_mainWindow->getCurrentTimeline()->loading && service.startsWith(QLatin1String("fade")) && id.first == ObjectType::TimelineClip) { bool startFade = service == QLatin1String("fadein") || service == QLatin1String("fade_from_black"); m_mainWindow->getCurrentTimeline()->controller()->updateClip(id.second, {startFade ? TimelineModel::FadeInRole : TimelineModel::FadeOutRole}); } } void Core::showClipKeyframes(ObjectId id, bool enable) { if (id.first == ObjectType::TimelineClip) { m_mainWindow->getCurrentTimeline()->controller()->showClipKeyframes(id.second, enable); } else if (id.first == ObjectType::TimelineComposition) { m_mainWindow->getCurrentTimeline()->controller()->showCompositionKeyframes(id.second, enable); } } Mlt::Profile *Core::thumbProfile() { if (!m_thumbProfile) { m_thumbProfile = std::make_unique(m_currentProfile.toStdString().c_str()); m_thumbProfile->set_height(200); int width = 200 * m_thumbProfile->dar(); if (width % 8 > 0) { width += 8 - width % 8; } m_thumbProfile->set_width(width); } return m_thumbProfile.get(); } int Core::getTimelinePosition() const { if (m_mainWindow && m_guiConstructed) { return m_mainWindow->getCurrentTimeline()->controller()->timelinePosition(); } return 0; } void Core::triggerAction(const QString &name) { QAction *action = m_mainWindow->actionCollection()->action(name); if (action) { action->trigger(); } } void Core::clean() { m_self.reset(); } -void Core::startMediaCapture(bool checkAudio, bool checkVideo, QUrl path, QString audioDevice) +void Core::startMediaCapture(bool checkAudio, bool checkVideo) { - m_capture->setCaptureOutputLocation(path); - m_capture->setAudioCaptureDevice(audioDevice); if (checkAudio && checkVideo) { m_capture->recordVideo(true); } else if (checkAudio) { m_capture->recordAudio(true); } + m_mediaCaptureFile = m_capture->getCaptureOutputLocation(); } -void Core::stopMediaCapture(bool checkAudio, bool checkVideo) +const QString Core::stopMediaCapture(bool checkAudio, bool checkVideo) { if (checkAudio && checkVideo) { m_capture->recordVideo(false); - } else if (checkAudio && !checkVideo) { + } else if (checkAudio) { m_capture->recordAudio(false); } + return m_capture->getCaptureOutputLocation().toLocalFile(); } QStringList Core::getAudioCaptureDevices() { return m_capture->getAudioCaptureDevices(); } int Core::getMediaCaptureState() { return m_capture->getState(); } bool Core::isMediaCapturing() { return m_capture->isRecording(); } -void Core::setAudioCaptureVolume(int volume) +MediaCapture *Core::getAudioDevice() { - m_capture->setAudioVolume(volume / 100.0); + return m_capture.get(); } -MediaCapture *Core::getAudioDevice() +QString Core::getProjectFolderName() { - return m_capture.get(); + return m_monitorManager->getProjectFolder(); } diff --git a/src/core.h b/src/core.h index 232152b34..8ed8d801b 100644 --- a/src/core.h +++ b/src/core.h @@ -1,227 +1,230 @@ /* Copyright (C) 2014 Till Theato This file is part of kdenlive. See www.kdenlive.org. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. */ #ifndef CORE_H #define CORE_H #include "definitions.h" #include "kdenlivecore_export.h" #include "undohelper.hpp" #include #include #include #include class Bin; class DocUndoStack; class EffectStackModel; class JobManager; class KdenliveDoc; class LibraryWidget; class MainWindow; class MediaCapture; class Monitor; class MonitorManager; class ProfileModel; class ProjectItemModel; class ProjectManager; namespace Mlt { class Repository; class Profile; } // namespace Mlt #define EXIT_RESTART (42) #define pCore Core::self() /** * @class Core * @brief Singleton that provides access to the different parts of Kdenlive * * Needs to be initialize before any widgets are created in MainWindow. * Plugins should be loaded after the widget setup. */ class /*KDENLIVECORE_EXPORT*/ Core : public QObject { Q_OBJECT public: Core(const Core &) = delete; Core &operator=(const Core &) = delete; Core(Core &&) = delete; Core &operator=(Core &&) = delete; ~Core() override; /** * @brief Setup the basics of the application, in particular the connection * with Mlt * @param MltPath (optional) path to MLT environment */ static void build(const QString &MltPath = QString()); /** * @brief Init the GUI part of the app and show the main window * @param Url (optional) file to open * If Url is present, it will be opened, otherwise, if openlastproject is * set, latest project will be opened. If no file is open after trying this, * a default new file will be created. */ void initGUI(const QUrl &Url); /** @brief Returns a pointer to the singleton object. */ static std::unique_ptr &self(); /** @brief Delete the global core instance */ static void clean(); /** @brief Returns a pointer to the main window. */ MainWindow *window(); /** @brief Returns a pointer to the project manager. */ ProjectManager *projectManager(); /** @brief Returns a pointer to the current project. */ KdenliveDoc *currentDoc(); /** @brief Returns a pointer to the monitor manager. */ MonitorManager *monitorManager(); /** @brief Returns a pointer to the view of the project bin. */ Bin *bin(); /** @brief Select a clip in the Bin from its id. */ void selectBinClip(const QString &id, int frame = -1, const QPoint &zone = QPoint()); /** @brief Returns a pointer to the model of the project bin. */ std::shared_ptr projectItemModel(); /** @brief Returns a pointer to the job manager. Please do not store it. */ std::shared_ptr jobManager(); /** @brief Returns a pointer to the library. */ LibraryWidget *library(); /** @brief Returns a pointer to MLT's repository */ std::unique_ptr &getMltRepository(); /** @brief Returns a pointer to the current profile */ std::unique_ptr &getCurrentProfile() const; const QString &getCurrentProfilePath() const; /** @brief Define the active profile * @returns true if profile exists, false if not found */ bool setCurrentProfile(const QString &profilePath); /** @brief Returns Sample Aspect Ratio of current profile */ double getCurrentSar() const; /** @brief Returns Display Aspect Ratio of current profile */ double getCurrentDar() const; /** @brief Returns frame rate of current profile */ double getCurrentFps() const; /** @brief Returns the frame size (width x height) of current profile */ QSize getCurrentFrameSize() const; /** @brief Returns the frame display size (width x height) of current profile */ QSize getCurrentFrameDisplaySize() const; /** @brief Request project monitor refresh */ void requestMonitorRefresh(); /** @brief Request project monitor refresh if current position is inside range*/ void refreshProjectRange(QSize range); /** @brief Request project monitor refresh if referenced item is under cursor */ void refreshProjectItem(const ObjectId &id); /** @brief Returns a reference to a monitor (clip or project monitor) */ Monitor *getMonitor(int id); /** @brief This function must be called whenever the profile used changes */ void profileChanged(); /** @brief Create and push and undo object based on the corresponding functions Note that if you class permits and requires it, you should use the macro PUSH_UNDO instead*/ void pushUndo(const Fun &undo, const Fun &redo, const QString &text); void pushUndo(QUndoCommand *command); /** @brief display a user info/warning message in statusbar */ void displayMessage(const QString &message, MessageType type, int timeout = -1); /** @brief Clear asset view if itemId is displayed. */ void clearAssetPanel(int itemId); /** @brief Returns the effectstack of a given bin clip. */ std::shared_ptr getItemEffectStack(int itemType, int itemId); int getItemPosition(const ObjectId &id); int getItemIn(const ObjectId &id); int getItemTrack(const ObjectId &id); int getItemDuration(const ObjectId &id); /** @brief Returns the capabilities of a clip: AudioOnly, VideoOnly or Disabled if both are allowed */ PlaylistState::ClipState getItemState(const ObjectId &id); /** @brief Get a list of video track names with indexes */ QMap getVideoTrackNames(); /** @brief Returns the composition A track (MLT index / Track id) */ QPair getCompositionATrack(int cid) const; void setCompositionATrack(int cid, int aTrack); /* @brief Return true if composition's a_track is automatic (no forced track) */ bool compositionAutoTrack(int cid) const; std::shared_ptr undoStack(); double getClipSpeed(int id) const; /** @brief Mark an item as invalid for timeline preview */ void invalidateItem(ObjectId itemId); void invalidateRange(QSize range); void prepareShutdown(); /** the keyframe model changed (effect added, deleted, active effect changed), inform timeline */ void updateItemKeyframes(ObjectId id); /** A fade for clip id changed, update timeline */ void updateItemModel(ObjectId id, const QString &service); /** Show / hide keyframes for a timeline clip */ void showClipKeyframes(ObjectId id, bool enable); Mlt::Profile *thumbProfile(); /** @brief Returns the current project duration */ int projectDuration() const; /** @brief Returns true if current project has some rendered timeline preview */ bool hasTimelinePreview() const; /** @brief Returns current timeline cursor position */ int getTimelinePosition() const; /** @brief Handles audio and video capture **/ - void startMediaCapture(bool, bool, QUrl, QString); + void startMediaCapture(bool, bool); void stopMediaCapture(bool, bool); QStringList getAudioCaptureDevices(); int getMediaCaptureState(); bool isMediaCapturing(); void setAudioCaptureVolume(int); MediaCapture *getAudioDevice(); + /** @brief Returns Project Folder name for capture output location */ + QString getProjectFolderName(); private: explicit Core(); static std::unique_ptr m_self; /** @brief Makes sure Qt's locale and system locale settings match. */ void initLocale(); MainWindow *m_mainWindow{nullptr}; ProjectManager *m_projectManager{nullptr}; MonitorManager *m_monitorManager{nullptr}; std::shared_ptr m_projectItemModel; std::shared_ptr m_jobManager; Bin *m_binWidget{nullptr}; LibraryWidget *m_library{nullptr}; /** @brief Current project's profile path */ QString m_currentProfile; QString m_profile; std::unique_ptr m_thumbProfile; bool m_guiConstructed = false; /** @brief Check that the profile is valid (width is a multiple of 8 and height a multiple of 2 */ void checkProfileValidity(); std::unique_ptr m_capture; + QUrl m_mediaCaptureFile; public slots: void triggerAction(const QString &name); /** @brief display a user info/warning message in the project bin */ void displayBinMessage(const QString &text, int type, const QList &actions = QList()); void displayBinLogMessage(const QString &text, int type, const QString &logInfo); signals: void coreIsReady(); void updateLibraryPath(); }; #endif diff --git a/src/kdenlivesettings.kcfg b/src/kdenlivesettings.kcfg index c5a03d725..ebf54e1a8 100644 --- a/src/kdenlivesettings.kcfg +++ b/src/kdenlivesettings.kcfg @@ -1,996 +1,996 @@ 0 4 false true 1 false true false 00:00:05:00 00:00:05:00 00:00:00:01 false false false false false 00:00:05:00 00:00:01:00 true 2 2 false false false false 1000 2000 800 0 0 true false false 140 1 25 false true true true false false true false 0 true true true true false 0 0 #999999 100 true 1 false 0 1 2 1 /tmp/ false true $HOME true - + - 0 + default: 100 0 0 /dev/video0 2 default 0 true false 0 0 0 false 0 0 1280 720 15.0 true false false 0 0 capture false 3 false true 0 true 0 25 true false false false true true true false false false false 0x15 0x05 0 0 false 0x07 true true false true #000000 true 320 240 true false false false true 5 3 false false false 10 0 false false true false false true true true 0 onefield nearest volume,lift_gamma_gain,qtblend qtblend 0 false false true false 2 3 #ff0000 0 diff --git a/src/monitor/recmanager.cpp b/src/monitor/recmanager.cpp index f6de4ae7d..566ec8c42 100644 --- a/src/monitor/recmanager.cpp +++ b/src/monitor/recmanager.cpp @@ -1,476 +1,453 @@ /*************************************************************************** * Copyright (C) 2015 by Jean-Baptiste Mardelle (jb@kdenlive.org) * * This file is part of Kdenlive. See www.kdenlive.org. * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "recmanager.h" #include "capture/managecapturesdialog.h" #include "capture/mltdevicecapture.h" #include "core.h" #include "dialogs/profilesdialog.h" #include "kdenlivesettings.h" #include "monitor.h" #include "klocalizedstring.h" #include #include #include #include #include #include #include #include #include #include RecManager::RecManager(Monitor *parent) : QObject(parent) , m_monitor(parent) , m_recToolbar(new QToolBar(parent)) , m_checkAudio(false) , m_checkVideo(false) { m_playAction = m_recToolbar->addAction(QIcon::fromTheme(QStringLiteral("media-playback-start")), i18n("Preview")); m_playAction->setCheckable(true); connect(m_playAction, &QAction::toggled, this, &RecManager::slotPreview); m_recAction = m_recToolbar->addAction(QIcon::fromTheme(QStringLiteral("media-record")), i18n("Record")); m_recAction->setCheckable(true); connect(m_recAction, &QAction::toggled, this, &RecManager::slotRecord); m_showLogAction = new QAction(i18n("Show log"), this); connect(m_showLogAction, &QAction::triggered, this, &RecManager::slotShowLog); m_recVideo = new QCheckBox(i18n("Video")); m_recAudio = new QCheckBox(i18n("Audio")); m_recToolbar->addWidget(m_recVideo); m_recToolbar->addWidget(m_recAudio); m_recAudio->setChecked(KdenliveSettings::v4l_captureaudio()); m_recVideo->setChecked(KdenliveSettings::v4l_capturevideo()); // Check number of monitors for FFmpeg screen capture int screens = QApplication::desktop()->screenCount(); if (screens > 1) { m_screenCombo = new QComboBox(parent); for (int ix = 0; ix < screens; ix++) { m_screenCombo->addItem(i18n("Monitor %1", ix)); } m_recToolbar->addWidget(m_screenCombo); // Update screen grab monitor choice in case we changed from fullscreen m_screenCombo->setEnabled(KdenliveSettings::grab_capture_type() == 0); } QWidget *spacer = new QWidget(parent); spacer->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); m_recToolbar->addWidget(spacer); m_audio_device = new QComboBox(parent); QStringList audioDevices = pCore->getAudioCaptureDevices(); for (int ix = 0; ix < audioDevices.count(); ix++) { m_audio_device->addItem(audioDevices.at(ix), ix); } connect(m_audio_device, static_cast(&QComboBox::currentIndexChanged), this, &RecManager::slotAudioDeviceChanged); - int selectedCapture = m_audio_device->findData(KdenliveSettings::defaultaudiocapture()); - if (selectedCapture > -1) { - m_audio_device->setCurrentIndex(selectedCapture); + QString selectedDevice = KdenliveSettings::defaultaudiocapture(); + int selectedIndex = m_audio_device->findText(selectedDevice); + if (!selectedDevice.isNull() && selectedIndex > -1) { + m_audio_device->setCurrentIndex(selectedIndex); } m_recToolbar->addWidget(m_audio_device); m_audioCaptureSlider = new QSlider(Qt::Vertical); m_audioCaptureSlider->setRange(0, 100); m_audioCaptureSlider->setValue(KdenliveSettings::audiocapturevolume()); connect(m_audioCaptureSlider, &QSlider::valueChanged, this, &RecManager::slotSetVolume); auto *widgetslider = new QWidgetAction(parent); widgetslider->setText(i18n("Audio Capture Volume")); widgetslider->setDefaultWidget(m_audioCaptureSlider); auto *menu = new QMenu(parent); menu->addAction(widgetslider); m_audioCaptureButton = new QToolButton(parent); m_audioCaptureButton->setMenu(menu); m_audioCaptureButton->setToolTip(i18n("Audio Capture Volume")); m_audioCaptureButton->setPopupMode(QToolButton::InstantPopup); QIcon icon; if (KdenliveSettings::audiocapturevolume() == 0) { icon = QIcon::fromTheme(QStringLiteral("audio-volume-muted")); } else { icon = QIcon::fromTheme(QStringLiteral("audio-volume-medium")); } m_audioCaptureButton->setIcon(icon); m_recToolbar->addWidget(m_audioCaptureButton); m_recToolbar->addSeparator(); m_device_selector = new QComboBox(parent); // TODO: re-implement firewire / decklink capture // m_device_selector->addItems(QStringList() << i18n("Firewire") << i18n("Webcam") << i18n("Screen Grab") << i18n("Blackmagic Decklink")); m_device_selector->addItem(i18n("Webcam"), Video4Linux); m_device_selector->addItem(i18n("Screen Grab"), ScreenGrab); - selectedCapture = m_device_selector->findData(KdenliveSettings::defaultcapture()); + selectedIndex = m_device_selector->findData(KdenliveSettings::defaultcapture()); - if (selectedCapture > -1) { - m_device_selector->setCurrentIndex(selectedCapture); + if (selectedIndex > -1) { + m_device_selector->setCurrentIndex(selectedIndex); } connect(m_device_selector, static_cast(&QComboBox::currentIndexChanged), this, &RecManager::slotVideoDeviceChanged); m_recToolbar->addWidget(m_device_selector); QAction *configureRec = m_recToolbar->addAction(QIcon::fromTheme(QStringLiteral("configure")), i18n("Configure Recording")); connect(configureRec, &QAction::triggered, this, &RecManager::showRecConfig); m_recToolbar->addSeparator(); m_switchRec = m_recToolbar->addAction(QIcon::fromTheme(QStringLiteral("list-add")), i18n("Show Record Control")); m_switchRec->setCheckable(true); connect(m_switchRec, &QAction::toggled, m_monitor, &Monitor::slotSwitchRec); m_recToolbar->setVisible(false); slotVideoDeviceChanged(); } RecManager::~RecManager() = default; void RecManager::showRecConfig() { m_monitor->showConfigDialog(4, m_device_selector->currentData().toInt()); } QToolBar *RecManager::toolbar() const { return m_recToolbar; } QAction *RecManager::switchAction() const { return m_switchRec; } void RecManager::stopCapture() { if (m_captureProcess) { slotRecord(false); } else if (pCore->getMediaCaptureState() == 1 && (m_checkAudio || m_checkVideo)) { // QMediaRecorder::RecordingState value is 1 pCore->stopMediaCapture(m_checkAudio, m_checkVideo); m_monitor->slotOpenClip(nullptr); - emit addClipToProject(m_captureFile); } } void RecManager::stop() { if (m_captureProcess) { // Don't stop screen rec when hiding rec toolbar } else { stopCapture(); m_switchRec->setChecked(false); } toolbar()->setVisible(false); } void RecManager::slotRecord(bool record) { if (m_device_selector->currentData().toInt() == Video4Linux) { if (record) { - QDir captureFolder; - if (KdenliveSettings::capturetoprojectfolder()) { - captureFolder = QDir(m_monitor->projectFolder()); - } else { - captureFolder = QDir(KdenliveSettings::capturefolder()); - } - QString extension; - if (!m_recVideo->isChecked()) { - extension = QStringLiteral("wav"); - } else { - extension = QStringLiteral("mpeg"); - } - - QString path = captureFolder.absoluteFilePath("capture0000." + extension); - int i = 1; - while (QFile::exists(path)) { - QString num = QString::number(i).rightJustified(4, '0', false); - path = captureFolder.absoluteFilePath("capture" + num + QLatin1Char('.') + extension); - ++i; - } - QString audioDevice = m_audio_device->currentText(); - m_captureFile = QUrl::fromLocalFile(path); m_checkAudio = m_recAudio->isChecked(); m_checkVideo = m_recVideo->isChecked(); - pCore->startMediaCapture(m_checkAudio, m_checkVideo, m_captureFile, audioDevice); + pCore->startMediaCapture(m_checkAudio, m_checkVideo); } else { stopCapture(); } return; } if (!record) { if (!m_captureProcess) { return; } m_captureProcess->write("q"); if (!m_captureProcess->waitForFinished()) { m_captureProcess->terminate(); QTimer::singleShot(1500, m_captureProcess, &QProcess::kill); } return; } if (m_captureProcess) { return; } m_recError.clear(); QString extension = KdenliveSettings::grab_extension(); QDir captureFolder; if (KdenliveSettings::capturetoprojectfolder()) { captureFolder = QDir(m_monitor->projectFolder()); } else { captureFolder = QDir(KdenliveSettings::capturefolder()); } QFileInfo checkCaptureFolder(captureFolder.absolutePath()); if (!checkCaptureFolder.isWritable()) { emit warningMessage(i18n("The directory %1, could not be created.\nPlease " "make sure you have the required permissions.", captureFolder.absolutePath())); m_recAction->blockSignals(true); m_recAction->setChecked(false); m_recAction->blockSignals(false); return; } m_captureProcess = new QProcess; connect(m_captureProcess, QOverload::of(&QProcess::finished), this, &RecManager::slotProcessStatus); connect(m_captureProcess, &QProcess::readyReadStandardError, this, &RecManager::slotReadProcessInfo); QString path = captureFolder.absoluteFilePath("capture0000." + extension); int i = 1; while (QFile::exists(path)) { QString num = QString::number(i).rightJustified(4, '0', false); path = captureFolder.absoluteFilePath("capture" + num + QLatin1Char('.') + extension); ++i; } m_captureFile = QUrl::fromLocalFile(path); QString captureSize; int screen = -1; if (m_screenCombo) { // Multi monitor setup, capture monitor selected by user screen = m_screenCombo->currentIndex(); } QRect screenSize = QApplication::desktop()->screenGeometry(screen); QStringList captureArgs; #ifdef Q_OS_WIN captureArgs << QStringLiteral("-f") << QStringLiteral("gdigrab"); // fps captureArgs << QStringLiteral("-framerate") << QString::number(KdenliveSettings::grab_fps()); captureSize = QStringLiteral("desktop"); #else captureArgs << QStringLiteral("-f") << QStringLiteral("x11grab"); if (KdenliveSettings::grab_follow_mouse()) { captureArgs << QStringLiteral("-follow_mouse") << QStringLiteral("centered"); } if (!KdenliveSettings::grab_hide_frame()) { captureArgs << QStringLiteral("-show_region") << QStringLiteral("1"); } captureSize = QStringLiteral(":0.0"); if (KdenliveSettings::grab_capture_type() == 0) { // Full screen capture captureArgs << QStringLiteral("-s") << QString::number(screenSize.width()) + QLatin1Char('x') + QString::number(screenSize.height()); captureSize.append(QLatin1Char('+') + QString::number(screenSize.left()) + QLatin1Char('.') + QString::number(screenSize.top())); } else { // Region capture captureArgs << QStringLiteral("-s") << QString::number(KdenliveSettings::grab_width()) + QLatin1Char('x') + QString::number(KdenliveSettings::grab_height()); captureSize.append(QLatin1Char('+') + QString::number(KdenliveSettings::grab_offsetx()) + QLatin1Char(',') + QString::number(KdenliveSettings::grab_offsety())); } if (KdenliveSettings::grab_hide_mouse()) { captureSize.append(QStringLiteral("+nomouse")); } // fps captureArgs << QStringLiteral("-r") << QString::number(KdenliveSettings::grab_fps()); #endif captureArgs << QStringLiteral("-i") << captureSize; if (!KdenliveSettings::grab_parameters().simplified().isEmpty()) { captureArgs << KdenliveSettings::grab_parameters().simplified().split(QLatin1Char(' ')); } captureArgs << path; m_captureProcess->start(KdenliveSettings::ffmpegpath(), captureArgs); if (!m_captureProcess->waitForStarted()) { // Problem launching capture app emit warningMessage(i18n("Failed to start the capture application:\n%1", KdenliveSettings::ffmpegpath())); // delete m_captureProcess; } } void RecManager::slotProcessStatus(int exitCode, QProcess::ExitStatus exitStatus) { m_recAction->setEnabled(true); m_recAction->setChecked(false); m_device_selector->setEnabled(true); if (exitStatus == QProcess::CrashExit) { emit warningMessage(i18n("Capture crashed, please check your parameters"), -1, QList() << m_showLogAction); } else { if (exitCode != 0 && exitCode != 255) { emit warningMessage(i18n("Capture crashed, please check your parameters"), -1, QList() << m_showLogAction); } else { // Capture successful, add clip to project emit addClipToProject(m_captureFile); } } if (m_captureProcess) { delete m_captureProcess; m_captureProcess = nullptr; } } void RecManager::slotReadProcessInfo() { QString data = m_captureProcess->readAllStandardError().simplified(); m_recError.append(data + QLatin1Char('\n')); } void RecManager::slotAudioDeviceChanged(int) { - int currentItem = m_audio_device->currentData().toInt(); - KdenliveSettings::setDefaultaudiocapture(currentItem); + QString currentDevice = m_audio_device->currentText(); + KdenliveSettings::setDefaultaudiocapture(currentDevice); } void RecManager::slotSetVolume(int volume) { KdenliveSettings::setAudiocapturevolume(volume); - pCore->setAudioCaptureVolume(volume); QIcon icon; if (volume == 0) { icon = QIcon::fromTheme(QStringLiteral("audio-volume-muted")); } else { icon = QIcon::fromTheme(QStringLiteral("audio-volume-medium")); } m_audioCaptureButton->setIcon(icon); } void RecManager::slotVideoDeviceChanged(int) { int currentItem = m_device_selector->currentData().toInt(); KdenliveSettings::setDefaultcapture(currentItem); switch (currentItem) { case Video4Linux: m_playAction->setEnabled(true); break; case BlackMagic: m_playAction->setEnabled(false); break; default: m_playAction->setEnabled(false); break; } /* m_previewSettings->setEnabled(ix == Video4Linux || ix == BlackMagic); control_frame->setVisible(ix == Video4Linux); monitor_box->setVisible(ix == ScreenBag && monitor_box->count() > 0); m_playAction->setVisible(ix != ScreenBag); m_fwdAction->setVisible(ix == Firewire); m_discAction->setVisible(ix == Firewire); m_rewAction->setVisible(ix == Firewire); m_recAction->setEnabled(ix != Firewire); m_logger.setVisible(ix == BlackMagic); if (m_captureDevice) { // MLT capture still running, abort m_monitorManager->clearScopeSource(); m_captureDevice->stop(); delete m_captureDevice; m_captureDevice = nullptr; } // The m_videoBox container has to be shown once before the MLT consumer is build, or preview will fail switch (ix) { case ScreenBag: } */ } Mlt::Producer *RecManager::createV4lProducer() { QString profilePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/profiles/video4linux"); Mlt::Profile *vidProfile = new Mlt::Profile(profilePath.toUtf8().constData()); Mlt::Producer *prod = nullptr; if (m_recVideo->isChecked()) { prod = new Mlt::Producer(*vidProfile, QStringLiteral("video4linux2:%1").arg(KdenliveSettings::video4vdevice()).toUtf8().constData()); if ((prod == nullptr) || !prod->is_valid()) { return nullptr; } prod->set("width", vidProfile->width()); prod->set("height", vidProfile->height()); prod->set("framerate", vidProfile->fps()); /*p->set("standard", ui->v4lStandardCombo->currentText().toLatin1().constData()); p->set("channel", ui->v4lChannelSpinBox->value()); p->set("audio_ix", ui->v4lAudioComboBox->currentIndex());*/ prod->set("force_seekable", 0); } if (m_recAudio->isChecked() && (prod != nullptr) && prod->is_valid()) { // Add audio track Mlt::Producer *audio = new Mlt::Producer( *vidProfile, QStringLiteral("alsa:%1?channels=%2").arg(KdenliveSettings::v4l_alsadevicename()).arg(KdenliveSettings::alsachannels()).toUtf8().constData()); audio->set("mlt_service", "avformat-novalidate"); audio->set("audio_index", 0); audio->set("video_index", -1); auto *tractor = new Mlt::Tractor(*vidProfile); tractor->set_track(*prod, 0); delete prod; tractor->set_track(*audio, 1); delete audio; prod = new Mlt::Producer(tractor->get_producer()); delete tractor; } return prod; } void RecManager::slotPreview(bool preview) { if (m_device_selector->currentData().toInt() == Video4Linux) { if (preview) { std::shared_ptr prod(createV4lProducer()); if (prod && prod->is_valid()) { m_monitor->updateClipProducer(prod); } else { emit warningMessage(i18n("Capture crashed, please check your parameters")); } } else { m_monitor->slotOpenClip(nullptr); } } /* buildMltDevice(path); bool isXml; producer = getV4lXmlPlaylist(profile, &isXml); //producer = QString("avformat-novalidate:video4linux2:%1?width:%2&height:%3&frame_rate:%4").arg(KdenliveSettings::video4vdevice()).arg(profile.width).arg(profile.height).arg((double) profile.frame_rate_num / profile.frame_rate_den); if (!m_captureDevice->slotStartPreview(producer, isXml)) { // v4l capture failed to start video_frame->setText(i18n("Failed to start Video4Linux,\ncheck your parameters...")); } else { m_playAction->setEnabled(false); m_stopAction->setEnabled(true); m_isPlaying = true; } }*/ } void RecManager::slotShowLog() { KMessageBox::information(QApplication::activeWindow(), m_recError); }