Commit 8ddfc9ef authored by Jean-Baptiste Mardelle's avatar Jean-Baptiste Mardelle
Browse files

Delete remaining jobmanager stuff

parent 83003d02
......@@ -26,10 +26,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "macros.hpp"
#include "projectitemmodel.h"
#include "jobs/audiothumbjob.hpp"
#include "jobs/loadjob.hpp"
#include "jobs/thumbjob.hpp"
#include <QPainter>
#include <QPainterPath>
#include <QVariant>
......
......@@ -29,10 +29,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "doc/docundostack.hpp"
#include "doc/kdenlivedoc.h"
#include "effects/effectstack/model/effectstackmodel.hpp"
#include "jobs/audiothumbjob.hpp"
#include "jobs/transcodetask.h"
#include "jobs/taskmanager.h"
#include "jobs/thumbjob.hpp"
#include "jobs/cliploadtask.h"
#include "kdenlive_debug.h"
#include "kdenlivesettings.h"
......@@ -2864,7 +2862,7 @@ void Bin::setupMenu()
m_proxyAction = new QAction(i18n("Proxy Clip"), pCore->window());
pCore->window()->addAction(QStringLiteral("proxy_clip"), m_proxyAction);
m_proxyAction->setData(QStringList() << QString::number(static_cast<int>(AbstractClipJob::PROXYJOB)));
m_proxyAction->setData(QStringList() << QString::number(static_cast<int>(AbstractTask::PROXYJOB)));
m_proxyAction->setCheckable(true);
m_proxyAction->setChecked(false);
......
......@@ -49,7 +49,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "utils/thumbnailcache.hpp"
#include "xml/xml.hpp"
#include <QPainter>
#include <jobs/proxyclipjob.h>
#include <kimagecache.h>
#include "kdenlive_debug.h"
......
set(kdenlive_SRCS
${kdenlive_SRCS}
jobs/abstractclipjob.cpp
jobs/abstracttask.cpp
jobs/taskmanager.cpp
jobs/audiolevelstask.cpp
......
/***************************************************************************
* *
* Copyright (C) 2011 by Jean-Baptiste Mardelle (jb@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) any later version. *
* *
* 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, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
***************************************************************************/
#include "abstractclipjob.h"
#include "doc/kdenlivedoc.h"
#include "kdenlivesettings.h"
#include <utility>
AbstractClipJob::AbstractClipJob(JOBTYPE type, QString id, ObjectId owner, QObject *parent)
: QObject(parent)
, m_clipId(std::move(id))
, m_jobType(type)
, m_inPoint(-1)
, m_outPoint(-1)
, m_owner(std::move(owner))
{
if (m_clipId.count(QStringLiteral("/")) == 2) {
m_inPoint = m_clipId.section(QLatin1Char('/'), 1, 1).toInt();
m_outPoint = m_clipId.section(QLatin1Char('/'), 2).toInt();
m_clipId = m_clipId.section(QLatin1Char('/'), 0, 0);
}
}
AbstractClipJob::~AbstractClipJob() = default;
const QString AbstractClipJob::clipId() const
{
return m_clipId;
}
const QString AbstractClipJob::getErrorMessage() const
{
return m_errorMessage;
}
const QString AbstractClipJob::getLogDetails() const
{
return m_logDetails;
}
// static
bool AbstractClipJob::execute(const std::shared_ptr<AbstractClipJob> &job)
{
return job->startJob();
}
AbstractClipJob::JOBTYPE AbstractClipJob::jobType() const
{
return m_jobType;
}
const ObjectId AbstractClipJob::owner()
{
return m_owner;
}
/***************************************************************************
* *
* Copyright (C) 2011 by Jean-Baptiste Mardelle (jb@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) any later version. *
* *
* 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, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
***************************************************************************/
#ifndef ABSTRACTCLIPJOB
#define ABSTRACTCLIPJOB
#include <QObject>
#include "definitions.h"
#include "undohelper.hpp"
#include <memory>
struct Job_t;
/** @class AbstractClipJob
@brief This is the base class for all Kdenlive clip jobs.
*/
class AbstractClipJob : public QObject
{
Q_OBJECT
public:
enum JOBTYPE {
NOJOBTYPE = 0,
PROXYJOB = 1,
CUTJOB = 2,
STABILIZEJOB = 3,
TRANSCODEJOB = 4,
FILTERCLIPJOB = 5,
THUMBJOB = 6,
ANALYSECLIPJOB = 7,
LOADJOB = 8,
AUDIOTHUMBJOB = 9,
SPEEDJOB = 10,
CACHEJOB = 11
};
AbstractClipJob(JOBTYPE type, QString id, ObjectId owner, QObject *parent = nullptr);
~AbstractClipJob() override;
template <typename T, typename... Args> static std::shared_ptr<T> make(const QString &binId, Args &&... args)
{
auto m = std::make_shared<T>(binId, std::forward<Args>(args)...);
return m;
}
/** @brief Returns the id of the bin clip that this job is working on. */
const QString clipId() const;
const QString getErrorMessage() const;
const QString getLogDetails() const;
virtual const QString getDescription() const = 0;
virtual bool startJob() = 0;
/** @brief This is to be called after the job finished.
By design, the job should store the result of the computation but not share it with the rest of the code. This happens when we call commitResult
This methods return true on success
*/
virtual bool commitResult(Fun &undo, Fun &redo) = 0;
/** @brief run a given job */
static bool execute(const std::shared_ptr<AbstractClipJob> &job);
/** @brief return the type of this job */
JOBTYPE jobType() const;
/** @brief return the owner of this job */
const ObjectId owner();
protected:
QString m_clipId;
QString m_errorMessage;
QString m_logDetails;
int m_addClipToProject;
JOBTYPE m_jobType;
int m_inPoint;
int m_outPoint;
const ObjectId m_owner;
bool m_resultConsumed{false};
signals:
/** @brief send an int between 0 and 100 to reflect computation progress */
void jobProgress(int);
void jobCanceled();
};
#endif
/***************************************************************************
* Copyright (C) 2017 by Nicolas Carion *
* 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 <http://www.gnu.org/licenses/>. *
***************************************************************************/
#include "audiothumbjob.hpp"
#include "bin/projectclip.h"
#include "bin/projectitemmodel.h"
#include "core.h"
#include "doc/kdenlivedoc.h"
#include "doc/kthumb.h"
#include "kdenlivesettings.h"
#include "klocalizedstring.h"
#include "lib/audio/audioStreamInfo.h"
#include "macros.hpp"
#include "utils/thumbnailcache.hpp"
#include <QScopedPointer>
#include <QTemporaryFile>
#include <QProcess>
#include <memory>
#include <mlt++/MltProducer.h>
AudioThumbJob::AudioThumbJob(const QString &binId)
: AbstractClipJob(AUDIOTHUMBJOB, binId, {ObjectType::BinClip, binId.toInt()})
, m_ffmpegProcess(nullptr)
{
}
const QString AudioThumbJob::getDescription() const
{
return i18n("Extracting audio thumb from clip %1", m_clipId);
}
bool AudioThumbJob::computeWithMlt()
{
m_audioLevels.clear();
m_errorMessage.clear();
// MLT audio thumbs: slower but safer
QString service = m_prod->get("mlt_service");
if (service == QLatin1String("avformat-novalidate")) {
service = QStringLiteral("avformat");
} else if (service.startsWith(QLatin1String("xml"))) {
service = QStringLiteral("xml-nogl");
}
QScopedPointer<Mlt::Producer> audioProducer(new Mlt::Producer(*m_prod->profile(), service.toUtf8().constData(), m_prod->get("resource")));
if (!audioProducer->is_valid()) {
m_errorMessage.append(i18n("Audio thumbs: cannot open file %1", m_prod->get("resource")));
return false;
}
audioProducer->set("video_index", "-1");
Mlt::Filter chans(*m_prod->profile(), "audiochannels");
Mlt::Filter converter(*m_prod->profile(), "audioconvert");
Mlt::Filter levels(*m_prod->profile(), "audiolevel");
audioProducer->attach(chans);
audioProducer->attach(converter);
audioProducer->attach(levels);
int last_val = 0;
double framesPerSecond = audioProducer->get_fps();
mlt_audio_format audioFormat = mlt_audio_s16;
QStringList keys;
keys.reserve(m_channels);
for (int i = 0; i < m_channels; i++) {
keys << "meta.media.audio_level." + QString::number(i);
}
double maxLevel = 1;
QVector <double> mltLevels;
for (int z = 0; z < m_lengthInFrames; ++z) {
int val = int(100.0 * z / m_lengthInFrames);
if (last_val != val) {
emit jobProgress(val);
last_val = val;
}
QScopedPointer<Mlt::Frame> mltFrame(audioProducer->get_frame());
if ((mltFrame != nullptr) && mltFrame->is_valid() && (mltFrame->get_int("test_audio") == 0)) {
int samples = mlt_sample_calculator(float(framesPerSecond), m_frequency, z);
mltFrame->get_audio(audioFormat, m_frequency, m_channels, samples);
for (int channel = 0; channel < m_channels; ++channel) {
double lev = mltFrame->get_double(keys.at(channel).toUtf8().constData());
mltLevels << lev;
maxLevel = qMax(lev, maxLevel);
}
} else if (!mltLevels.isEmpty()) {
for (int channel = 0; channel < m_channels; channel++) {
mltLevels << mltLevels.last();
}
}
}
// Normalize
for (double &v : mltLevels) {
m_audioLevels << uchar(255 * v / maxLevel);
}
m_done = true;
return true;
}
bool AudioThumbJob::computeWithFFMPEG()
{
if (!KdenliveSettings::audiothumbnails()) {
// We only wanted the thumb generation
return m_done;
}
QString filePath = m_binClip->getOriginalUrl();
if (!QFile::exists(filePath)) {
return false;
}
int audioMax = m_binClip->getProducerIntProperty(QStringLiteral("kdenlive:audio_max"));
if (audioMax == 0) {
// Calculate max audio level with ffmpeg
QProcess ffmpeg;
QStringList args = {QStringLiteral("-i"), filePath, QStringLiteral("-vn"), QStringLiteral("-af"), QStringLiteral("volumedetect"), QStringLiteral("-f"), QStringLiteral("null")};
#ifdef Q_OS_WIN
args << QStringLiteral("-");
#else
args << QStringLiteral("/dev/stdout");
#endif
QObject::connect(&ffmpeg, &QProcess::readyReadStandardOutput, [&ffmpeg, this]() {
QString output = ffmpeg.readAllStandardOutput();
if (output.contains(QLatin1String("max_volume"))) {
output = output.section(QLatin1String("max_volume:"), 1).simplified();
output = output.section(QLatin1Char(' '), 0, 0);
bool ok;
double maxVolume = output.toDouble(&ok);
if (ok) {
int aMax = qMax(1, qAbs(qRound(maxVolume)));
m_binClip->setProducerProperty(QStringLiteral("kdenlive:audio_max"), aMax);
QMetaObject::invokeMethod(pCore.get(), "setDocumentModified", Qt::QueuedConnection);
} else {
m_binClip->setProducerProperty(QStringLiteral("kdenlive:audio_max"), -1);
}
}
});
ffmpeg.setProcessChannelMode(QProcess::MergedChannels);
ffmpeg.start(KdenliveSettings::ffmpegpath(), args);
ffmpeg.waitForFinished(-1);
}
int audioStreamIndex = m_binClip->getAudioStreamFfmpegIndex(m_audioStream);
if (!QFile::exists(m_cachePath) && !m_dataInCache) {
// Generate timeline audio thumbnail data
m_audioLevels.clear();
std::vector<std::unique_ptr<QTemporaryFile>> channelFiles;
for (int i = 0; i < m_channels; i++) {
std::unique_ptr<QTemporaryFile> channelTmpfile(new QTemporaryFile());
if (!channelTmpfile->open()) {
m_errorMessage.append(i18n("Audio thumbs: cannot create temporary file, check disk space and permissions\n"));
return false;
}
channelTmpfile->close();
channelFiles.emplace_back(std::move(channelTmpfile));
}
// Always create audio thumbs from the original source file, because proxy
// can have a different audio config (channels / mono/ stereo)
QStringList args {QStringLiteral("-hide_banner"), QStringLiteral("-i"), QUrl::fromLocalFile(filePath).toLocalFile(), QStringLiteral("-progress")};
#ifdef Q_OS_WIN
args << QStringLiteral("-");
#else
args << QStringLiteral("/dev/stdout");
#endif
bool isFFmpeg = KdenliveSettings::ffmpegpath().contains(QLatin1String("ffmpeg"));
args << QStringLiteral("-filter_complex");
if (m_channels == 1) {
if (m_audioStream >= 0) {
args << QStringLiteral("[a:%1]aformat=channel_layouts=mono,%2=100").arg(audioStreamIndex).arg(isFFmpeg ? "aresample=1500:async" : "sample_rates");
} else {
args << QStringLiteral("[a]aformat=channel_layouts=mono,%1=100").arg(isFFmpeg ? "aresample=1500:async" : "sample_rates");
}
/*args << QStringLiteral("-map") << QStringLiteral("0:a%1").arg(m_audioStream > 0 ? ":" + QString::number(audioStreamIndex) : QString())*/
args << QStringLiteral("-c:a") << QStringLiteral("pcm_s16le") << QStringLiteral("-frames:v")
<< QStringLiteral("1") << QStringLiteral("-y") << QStringLiteral("-f") << QStringLiteral("data")
<< channelFiles[0]->fileName();
} else {
QString aformat = QStringLiteral("[a%1]%2=100,channelsplit=channel_layout=%3")
.arg(audioStreamIndex >= 0 ? ":" + QString::number(audioStreamIndex) : QString(),
isFFmpeg ? "aresample=1500:async" : "aformat=sample_rates",
m_channels > 2 ? "5.1" : "stereo");
for (int i = 0; i < m_channels; ++i) {
aformat.append(QStringLiteral("[0:%1]").arg(i));
}
args << aformat;
args << QStringLiteral("-frames:v") << QStringLiteral("1");
for (int i = 0; i < m_channels; i++) {
// Channel 1
args << QStringLiteral("-map") << QStringLiteral("[0:%1]").arg(i) << QStringLiteral("-c:a") << QStringLiteral("pcm_s16le") << QStringLiteral("-y")
<< QStringLiteral("-f") << QStringLiteral("data") << channelFiles[size_t(i)]->fileName();
}
}
m_ffmpegProcess = std::make_unique<QProcess>();
connect(m_ffmpegProcess.get(), &QProcess::readyReadStandardOutput, this, &AudioThumbJob::updateFfmpegProgress, Qt::UniqueConnection);
connect(this, &AudioThumbJob::jobCanceled, [&]() {
if (m_ffmpegProcess) {
disconnect(m_ffmpegProcess.get(), &QProcess::readyReadStandardOutput, this, &AudioThumbJob::updateFfmpegProgress);
m_ffmpegProcess->kill();
}
m_audioLevels.clear();
m_done = true;
m_successful = false;
});
m_ffmpegProcess->start(KdenliveSettings::ffmpegpath(), args);
m_ffmpegProcess->waitForFinished(-1);
disconnect(m_ffmpegProcess.get(), &QProcess::readyReadStandardOutput, this, &AudioThumbJob::updateFfmpegProgress);
if (m_successful && m_ffmpegProcess->exitStatus() != QProcess::CrashExit) {
int dataSize = 0;
std::vector<const qint16 *> rawChannels;
std::vector<QByteArray> sourceChannels;
for (auto &channelFile : channelFiles) {
channelFile->open();
sourceChannels.emplace_back(channelFile->readAll());
QByteArray &res = sourceChannels.back();
channelFile->close();
if (dataSize == 0) {
dataSize = res.size();
}
if (res.isEmpty() || res.size() != dataSize) {
// Something went wrong, abort
m_errorMessage.append(i18n("Audio thumbs: error reading audio thumbnail created with FFmpeg\n"));
return false;
}
rawChannels.emplace_back(reinterpret_cast<const qint16 *>(res.constData()));
}
int progress = 0;
std::vector<long> channelsData;
double offset = double(dataSize) / (2.0 * m_lengthInFrames);
int intraOffset = 1;
if (offset > 1000) {
intraOffset = int(offset / 60);
} else if (offset > 250) {
intraOffset = int(offset / 10);
}
long maxAudioLevel = 1;
if (!m_successful) {
m_done = true;
return true;
}
std::vector <long> ffmpegLevels;
for (int i = 0; i < m_lengthInFrames; i++) {
channelsData.resize(size_t(rawChannels.size()));
std::fill(channelsData.begin(), channelsData.end(), 0);
int pos = int(i * offset);
int steps = 0;
for (int j = 0; j < int(offset) && (pos + j < dataSize); j += intraOffset) {
steps++;
for (size_t k = 0; k < rawChannels.size(); k++) {
channelsData[k] += abs(rawChannels[k][pos + j]);
}
}
steps = qMax(steps, 1);
for (long &k : channelsData) {
if (!m_successful) {
break;
}
k /= steps;
maxAudioLevel = qMax(k, maxAudioLevel);
}
int p = 80 + (i * 20 / m_lengthInFrames);
if (p != progress) {
emit jobProgress(p);
progress = p;
}
ffmpegLevels.insert(ffmpegLevels.end(), channelsData.begin(), channelsData.end());
}
if (!m_successful) {
m_done = true;
return true;
}
for (long &v : ffmpegLevels) {
m_audioLevels << uint8_t(255 * v / maxAudioLevel);
}
m_done = true;
return true;
} else if (m_ffmpegProcess) {
QString err = m_ffmpegProcess->readAllStandardError();
// m_errorMessage += err;
// m_errorMessage.append(i18n("Failed to create FFmpeg audio thumbnails, we now try to use MLT"));
qWarning() << "Failed to create FFmpeg audio thumbs:\n" << err << "\n---------------------";
}
} else {
m_done = true;
}
return m_done;
}
void AudioThumbJob::updateFfmpegProgress()
{
if (m_ffmpegProcess == nullptr) {
return;
}
const QString result = m_ffmpegProcess->readAllStandardOutput();
const QStringList lines = result.split(QLatin1Char('\n'));
for (const QString &data : lines) {
if (data.startsWith(QStringLiteral("out_time_ms"))) {
double ms = data.section(QLatin1Char('='), 1).toDouble();
emit jobProgress(int(ms / m_binClip->duration().ms() / 10));
} else {
m_logDetails += data + QStringLiteral("\n");
}
}
}
bool AudioThumbJob::startJob()
{
if (m_done) {
return true;
}
m_dataInCache = false;
m_binClip = pCore->projectItemModel()->getClipByBinID(m_clipId);
if (m_binClip == nullptr) {
// Clip was deleted
return false;
}
m_successful = true;
if (m_binClip->audioChannels() == 0 || m_binClip->audioThumbCreated()) {
// nothing to do
m_done = true;
return true;
}
m_prod = m_binClip->originalProducer();
if ((m_prod == nullptr) || !m_prod->is_valid()) {
m_errorMessage.append(i18n("Audio thumbs: cannot open project file %1", m_binClip->url()));
m_done = true;
m_successful = false;
return false;
}
m_lengthInFrames = m_prod->get_length(); // Multiply this if we want more than 1 sample per frame
if (m_lengthInFrames == INT_MAX) {
// This is a broken file or live feed, don't attempt to generate audio thumbnails
m_done = true;
m_successful = false;
return false;
}
m_frequency = m_binClip->audioInfo()->samplingRate();
m_frequency = m_frequency <= 0 ? 48000 : m_frequency;
m_channels = m_binClip->audioInfo()->channels();
m_channels = m_channels <= 0 ? 2 : m_channels;
QMap <int, QString> streams = m_binClip->audioInfo()->streams();
QMap <int, int> audioChannels = m_binClip->audioInfo()->streamChannels();
QMapIterator<int, QString> st(streams);
m_done = true;
ClipType::ProducerType type = m_binClip->clipType();
while (st.hasNext()) {
st.next();
int stream = st.key();
if (audioChannels.contains(stream)) {
m_channels = audioChannels.value(stream);
}
// Generate one thumb per stream
m_audioStream = stream;
m_cachePath = m_binClip->getAudioThumbPath(stream);
if (QFile::exists(m_cachePath)) {
// Audio thumb already exists
continue;
}
m_done = false;
bool ok = false;