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

reimplement bin clip transcoding

parent eea5da49
......@@ -9,6 +9,7 @@ set(kdenlive_SRCS
jobs/speedjob.cpp
jobs/stabilizejob.cpp
jobs/thumbjob.cpp
jobs/transcodeclipjob.cpp
# jobs/cutclipjob.cpp
# jobs/filterjob.cpp
jobs/proxyclipjob.cpp
......
/***************************************************************************
* *
* Copyright (C) 2019 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 "transcodeclipjob.h"
#include "bin/bin.h"
#include "bin/clipcreator.hpp"
#include "bin/projectclip.h"
#include "bin/projectitemmodel.h"
#include "core.h"
#include "doc/kdenlivedoc.h"
#include "kdenlive_debug.h"
#include "kdenlivesettings.h"
#include "macros.hpp"
#include <QProcess>
#include <QTemporaryFile>
#include <QThread>
#include <klocalizedstring.h>
TranscodeJob::TranscodeJob(const QString &binId, QString params)
: AbstractClipJob(PROXYJOB, binId)
, m_jobDuration(0)
, m_isFfmpegJob(true)
, m_jobProcess(nullptr)
, m_done(false)
, m_transcodeParams(params)
{
}
const QString TranscodeJob::getDescription() const
{
//TODO: add better description after string freeze
return i18n("Transcode");
}
bool TranscodeJob::startJob()
{
auto binClip = pCore->projectItemModel()->getClipByBinID(m_clipId);
const QString source = binClip->url();
ClipType::ProducerType type = binClip->clipType();
QString transcoderExt = m_transcodeParams.section(QLatin1String("%1"), 1).section(QLatin1Char(' '), 0, 0);
if (transcoderExt.isEmpty()) {
qDebug()<<"// INVALID TRANSCODING PROFILE";
return false;
}
QFileInfo finfo(source);
QString fileName = finfo.fileName().section(QLatin1Char('.'), 0, -2);
QDir dir = finfo.absoluteDir();
QString path = fileName + transcoderExt;
int fileCount = 1;
bool updatedPath = false;
while (dir.exists(path)) {
QString num = QString::number(fileCount).rightJustified(4, '0', false);
path = fileName + num + transcoderExt;
++fileCount;
updatedPath = true;
}
m_destUrl = dir.absoluteFilePath(fileName);
if (updatedPath) {
m_destUrl.append(QString::number(fileCount).rightJustified(4, '0', false));
}
bool result;
if (type == ClipType::Playlist || type == ClipType::SlideShow) {
// change FFmpeg params to MLT format
m_isFfmpegJob = false;
// insert transcoded filename
m_transcodeParams.replace(QStringLiteral("%1"), QString("-consumer avformat:%1").arg(m_destUrl));
// Convert param style
QStringList params = m_transcodeParams.split(QLatin1Char('-'), QString::SkipEmptyParts);
bool skipNext = false;
QStringList mltParameters;
for (const QString &s : params) {
QString t = s.simplified();
if (skipNext) {
skipNext = false;
continue;
}
if (t.count(QLatin1Char(' ')) == 0) {
t.append(QLatin1String("=1"));
} else {
t.replace(QLatin1Char(' '), QLatin1String("="));
}
mltParameters << t;
}
int threadCount = QThread::idealThreadCount();
if (threadCount > 2) {
threadCount = qMin(threadCount - 1, 4);
} else {
threadCount = 1;
}
mltParameters.append(QStringLiteral("real_time=-%1").arg(threadCount));
mltParameters.append(QStringLiteral("threads=%1").arg(threadCount));
// Ask for progress reporting
mltParameters << QStringLiteral("progress=1") << QStringLiteral("-consumer")<<QString("avformat:%1").arg(m_destUrl);
m_jobProcess = new QProcess;
// m_jobProcess->setProcessChannelMode(QProcess::MergedChannels);
connect(this, &TranscodeJob::jobCanceled, m_jobProcess, &QProcess::kill, Qt::DirectConnection);
connect(m_jobProcess, &QProcess::readyReadStandardError, this, &TranscodeJob::processLogInfo);
m_jobProcess->start(KdenliveSettings::rendererpath(), mltParameters);
m_jobProcess->waitForFinished(-1);
result = m_jobProcess->exitStatus() == QProcess::NormalExit;
} else {
m_isFfmpegJob = true;
QStringList parameters;
if (KdenliveSettings::ffmpegpath().isEmpty()) {
// FFmpeg not detected, cannot process the Job
m_errorMessage.prepend(i18n("Failed to create proxy. FFmpeg not found, please set path in Kdenlive's settings Environment"));
m_done = true;
return false;
}
m_jobDuration = (int)binClip->duration().seconds();
//parameters << QStringLiteral("-y");
parameters << QStringLiteral("-stats") << QStringLiteral("-i") << source;
// Only output error data
parameters << QStringLiteral("-v") << QStringLiteral("error");
m_transcodeParams.replace(QStringLiteral("%1"), m_destUrl);
QStringList params = m_transcodeParams.split(QLatin1Char(' '));
QStringList finalParams{QStringLiteral("-i"),source};
for (const QString &s : params) {
QString t = s.simplified();
parameters << t;
}
qDebug()<<"/// FULL PROXY PARAMS:\n"<<parameters<<"\n------";
m_jobProcess = new QProcess;
// m_jobProcess->setProcessChannelMode(QProcess::MergedChannels);
connect(m_jobProcess, &QProcess::readyReadStandardError, this, &TranscodeJob::processLogInfo);
connect(this, &TranscodeJob::jobCanceled, m_jobProcess, &QProcess::kill, Qt::DirectConnection);
m_jobProcess->start(KdenliveSettings::ffmpegpath(), parameters, QIODevice::ReadOnly);
m_jobProcess->waitForFinished(-1);
result = m_jobProcess->exitStatus() == QProcess::NormalExit;
}
m_destUrl.append(transcoderExt);
// remove temporary playlist if it exists
if (result) {
if (QFileInfo(m_destUrl).size() == 0) {
QFile::remove(m_destUrl);
// File was not created
m_done = false;
m_errorMessage.append(i18n("Failed to create file."));
} else {
m_done = true;
}
} else {
// Proxy process crashed
QFile::remove(m_destUrl);
m_done = false;
m_errorMessage.append(QString::fromUtf8(m_jobProcess->readAll()));
}
m_jobProcess->deleteLater();
return result;
}
void TranscodeJob::processLogInfo()
{
const QString buffer = QString::fromUtf8(m_jobProcess->readAllStandardError());
m_logDetails.append(buffer);
int progress = 0;
if (m_isFfmpegJob) {
// Parse FFmpeg output
if (m_jobDuration == 0) {
if (buffer.contains(QLatin1String("Duration:"))) {
QString data = buffer.section(QStringLiteral("Duration:"), 1, 1).section(QLatin1Char(','), 0, 0).simplified();
if (!data.isEmpty()) {
QStringList numbers = data.split(QLatin1Char(':'));
if (numbers.size() < 3) {
return;
}
m_jobDuration = (int)(numbers.at(0).toInt() * 3600 + numbers.at(1).toInt() * 60 + numbers.at(2).toDouble());
}
}
} else if (buffer.contains(QLatin1String("time="))) {
QString time = buffer.section(QStringLiteral("time="), 1, 1).simplified().section(QLatin1Char(' '), 0, 0);
if (!time.isEmpty()) {
QStringList numbers = time.split(QLatin1Char(':'));
if (numbers.size() < 3) {
progress = (int)time.toDouble();
if (progress == 0) {
return;
}
} else {
progress = numbers.at(0).toInt() * 3600 + numbers.at(1).toInt() * 60 + numbers.at(2).toDouble();
}
}
emit jobProgress((int)(100.0 * progress / m_jobDuration));
}
} else {
// Parse MLT output
if (buffer.contains(QLatin1String("percentage:"))) {
progress = buffer.section(QStringLiteral("percentage:"), 1).simplified().section(QLatin1Char(' '), 0, 0).toInt();
emit jobProgress(progress);
}
}
}
bool TranscodeJob::commitResult(Fun &undo, Fun &redo)
{
Q_ASSERT(!m_resultConsumed);
if (!m_done) {
qDebug() << "ERROR: Trying to consume invalid results";
return false;
}
m_resultConsumed = true;
QString folderId = QStringLiteral("-1");
auto id = ClipCreator::createClipFromFile(m_destUrl, folderId, pCore->projectItemModel(), undo, redo);
return id != QStringLiteral("-1");
}
/***************************************************************************
* *
* Copyright (C) 2019 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 TRANSCODECLIPJOB
#define TRANSCODECLIPJOB
#include "abstractclipjob.h"
class QTemporaryFile;
class Bin;
class ProjectClip;
class QProcess;
class TranscodeJob : public AbstractClipJob
{
Q_OBJECT
public:
TranscodeJob(const QString &binId, QString params);
const QString getDescription() const override;
bool startJob() override;
/** @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 */
bool commitResult(Fun &undo, Fun &redo) override;
private slots:
void processLogInfo();
private:
int m_jobDuration;
bool m_isFfmpegJob;
QProcess *m_jobProcess;
bool m_done;
QString m_destUrl;
QString m_transcodeParams;
};
#endif
......@@ -38,6 +38,7 @@
#include "jobs/scenesplitjob.hpp"
#include "jobs/speedjob.hpp"
#include "jobs/stabilizejob.hpp"
#include "jobs/transcodeclipjob.h"
#include "kdenlivesettings.h"
#include "layoutmanagement.h"
#include "library/librarywidget.h"
......@@ -3108,8 +3109,7 @@ void MainWindow::buildDynamicActions()
ts = kdenliveCategoryMap.take(QStringLiteral("audiotranscoderslist"));
delete ts;
}
// TODO refac : reimplement transcode
/*
// transcoders
ts = new KActionCategory(i18n("Transcoders"), m_extraFactory->actionCollection());
KActionCategory *ats = new KActionCategory(i18n("Extract Audio"), m_extraFactory->actionCollection());
KSharedConfigPtr config =
......@@ -3121,16 +3121,17 @@ void MainWindow::buildDynamicActions()
while (i.hasNext()) {
i.next();
QStringList transList;
transList << QString::number((int)AbstractClipJob::TRANSCODEJOB);
transList << i.value().split(QLatin1Char(';'));
auto *a = new QAction(i.key(), m_extraFactory->actionCollection());
a->setData(transList);
if (transList.count() > 1) {
a->setToolTip(transList.at(1));
}
// slottranscode
connect(a, &QAction::triggered, pCore->bin(), &Bin::slotStartClipJob);
if (transList.count() > 3 && transList.at(3) == QLatin1String("audio")) {
connect(a, &QAction::triggered, [&, a]() {
QStringList transcodeData = a->data().toStringList();
pCore->jobManager()->startJob<TranscodeJob>(pCore->bin()->selectedClipsIds(), -1, QString(), transcodeData.first());
});
if (transList.count() > 2 && transList.at(2) == QLatin1String("audio")) {
// This is an audio transcoding action
ats->addAction(i.key(), a);
} else {
......@@ -3139,7 +3140,6 @@ void MainWindow::buildDynamicActions()
}
kdenliveCategoryMap.insert(QStringLiteral("transcoderslist"), ts);
kdenliveCategoryMap.insert(QStringLiteral("audiotranscoderslist"), ats);
*/
// Populate View menu with show / hide actions for dock widgets
KActionCategory *guiActions = nullptr;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment