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

Automatically offer to transcode variable frame rate clips

parent 6bf88b18
Pipeline #116371 passed with stage
in 5 minutes and 7 seconds
......@@ -36,6 +36,6 @@ DVD NTSC 16:9=-f dvd -r 23.976 -vf scale=720:480 -aspect 16:9 -minrate 0 -maxrat
[intermediate]
Lossy x264 I frame only=-f mp4 -codec:v libx264 -g 1 -bf 0 -crf 15 -preset medium -codec:a aac -ab 256k %1.mp4
Lossy x264 I frame only (NVidia GPU)=-f mp4 -vsync 0 -c:v %nvcodec -i -codec:v h264_nvenc -g 1 -bf 0 -codec:a aac -ab 256k %1.mp4
Lossy x264 I frame only (NVidia GPU)=-f mp4 -vsync 0 -codec:v h264_nvenc -vb 0 -rc cbr -g 1 -bf 0 -codec:a aac -ab 256k %1.mp4
Intermediate DNxHR HQ (Large files)=-f mov -codec:a alac -codec:v dnxhd -profile:v dnxhr_hq -pix_fmt yuv422p %1.mov
Lossless (Huge files)=-f matroska -codec:a pcm_f32le -codec:v utvideo -pix_fmt yuv422p %1.mkv
......@@ -324,9 +324,8 @@ public:
QVariant v = index.data(AbstractProjectItem::IconOverlay);
if (!v.isNull()) {
QIcon reload = QIcon::fromTheme(v.toString());
r.setTop(int(r.bottom() - bounding.height()));
r.setWidth(int(bounding.height()));
reload.paint(painter, r);
int size = style->pixelMetric(QStyle::PM_SmallIconSize);
reload.paint(painter, QRect(r.left() + 2, r.bottom() - size - 2, size, size));
}
int jobProgress = index.data(AbstractProjectItem::JobProgress).toInt();
auto status = index.data(AbstractProjectItem::JobStatus).value<TaskManagerStatus>();
......@@ -4830,24 +4829,27 @@ void Bin::savePlaylist(const QString &binId, QString savePath, QVector<QPoint> z
}
}
void Bin::requestTranscoding(const QString &url, const QString &id)
void Bin::requestTranscoding(const QString &url, const QString &id, bool checkProfile)
{
if (m_transcodingDialog == nullptr) {
m_transcodingDialog = new TranscodeSeek(this);
connect(m_transcodingDialog, &QDialog::accepted, this, [=] () {
qDebug()<<"==== STARTING TCODE JOB: "<<m_transcodingDialog->ids().front()<<" = "<<m_transcodingDialog->params();
//pCore->jobManager()->startJob<TranscodeJob>(m_transcodingDialog->ids(), -1, QString(), m_transcodingDialog->params(), true);
QString firstId = m_transcodingDialog->ids().front();
std::vector<QString> ids = m_transcodingDialog->ids();
for (const QString &id : ids) {
std::shared_ptr<ProjectClip> clip = m_itemModel->getClipByBinID(id);
TranscodeTask::start({ObjectType::BinClip,id.toInt()}, m_transcodingDialog->params(), -1, -1, true, clip.get());
TranscodeTask::start({ObjectType::BinClip,id.toInt()}, m_transcodingDialog->params(), -1, -1, true, clip.get(), false, id == firstId ? checkProfile : false);
}
delete m_transcodingDialog;
m_transcodingDialog = nullptr;
});
connect(m_transcodingDialog, &QDialog::rejected, this, [=] () {
QString firstId = m_transcodingDialog->ids().front();
delete m_transcodingDialog;
m_transcodingDialog = nullptr;
if (checkProfile) {
pCore->bin()->slotCheckProfile(firstId);
}
});
}
m_transcodingDialog->addUrl(url, id);
......
......@@ -337,8 +337,6 @@ public:
* @param properties some extra properties that will be set on the producer
* @param createNew if true, the playlist will be added as a new clip in project binId */
void savePlaylist(const QString &binId, QString savePath, QVector<QPoint> zones, QMap<QString, QString> properties, bool createNew);
/** @brief A non seekable clip was added to project, propose transcoding */
void requestTranscoding(const QString &url, const QString &id);
// Do some checks on the profile
static void checkProfile(const std::shared_ptr<Mlt::Producer> &producer);
......@@ -457,6 +455,8 @@ public slots:
bool addProjectClipInFolder(const QString &path, const QString &parentFolder, const QString &folderName);
/** @brief Check if a clip profile matches project, propose switch otherwise */
void slotCheckProfile(const QString binId);
/** @brief A non seekable clip was added to project, propose transcoding */
void requestTranscoding(const QString &url, const QString &id, bool checkProfile);
protected:
/* This function is called whenever an item is selected to propagate signals
......
......@@ -1524,6 +1524,9 @@ QVariant ProjectClip::getData(DataType type) const
if (m_clipStatus == FileStatus::StatusWaiting) {
return QVariant("view-refresh");
}
if (m_properties && m_properties->get_int("meta.media.variable_frame_rate")) {
return QVariant("emblem-warning");
}
return m_effectStack && m_effectStack->rowCount() > 0 ? QVariant("kdenlive-track_has_effect") : QVariant();
default:
return AbstractProjectItem::getData(type);
......
......@@ -1511,7 +1511,7 @@ void KdenliveDoc::switchProfile(ProfileParam* pf)
if (qFuzzyCompare(double(profile->m_frame_rate_num) / profile->m_frame_rate_den, fps)) {
adjustMessage = i18n("\nProfile fps adjusted from original %1", QString::number(fps, 'f', 4));
}
if (KMessageBox::warningContinueCancel(QApplication::activeWindow(),
if (KMessageBox::warningContinueCancel(pCore->window(),
i18n("No profile found for your clip.\nCreate and switch to new profile (%1x%2, %3fps)?%4", profile->m_width,
profile->m_height, QString::number(double(profile->m_frame_rate_num) / profile->m_frame_rate_den, 'f', 2),
adjustMessage)) == KMessageBox::Continue) {
......
......@@ -557,26 +557,6 @@ void ClipLoadTask::run()
producer->set("out", fixedLength - 1);
}
} else if (mltService == QLatin1String("avformat")) {
// Check if file is seekable
seekable = producer->get_int("seekable");
bool checkProfile = false;
if (m_xml.hasAttribute(QStringLiteral("_checkProfile")) && producer->get_int("video_index") > -1) {
checkProfile = true;
}
if (!seekable) {
QAction *ac = new QAction(i18n("Transcode to edit friendly format"));
QAction *ac2 = new QAction(i18n("Discard"));
QObject::connect(ac, &QAction::triggered, [id = m_owner.second, resource, checkProfile]() {
QMetaObject::invokeMethod(pCore.get(), "transcodeFriendlyFile", Qt::QueuedConnection, Q_ARG(QString, QString::number(id)), Q_ARG(bool, checkProfile));
});
if (checkProfile) {
QObject::connect(ac2, &QAction::triggered, [id = m_owner.second]() {
QMetaObject::invokeMethod(pCore->bin(), "slotCheckProfile", Qt::QueuedConnection, Q_ARG(QString, QString::number(id)));
});
}
QList<QAction*>actions = {ac,ac2};
QMetaObject::invokeMethod(pCore.get(), "displayBinMessage", Qt::QueuedConnection, Q_ARG(QString, i18n("File <b>%1</b> is not seekable, not recommended for editing.", QFileInfo(resource).fileName())), Q_ARG(int, int(KMessageWidget::Warning)), Q_ARG(QList<QAction*>, actions));
}
// Get a frame to init properties
mlt_image_format format = mlt_image_none;
QSize frameSize = pCore->getCurrentFrameSize();
......@@ -585,24 +565,22 @@ void ClipLoadTask::run()
std::unique_ptr<Mlt::Frame> frame(producer->get_frame());
frame->get_image(format, w, h);
frame.reset();
// Check if file is seekable
seekable = producer->get_int("seekable");
vindex = producer->get_int("video_index");
bool checkProfile = false;
if (m_xml.hasAttribute(QStringLiteral("_checkProfile")) && vindex > -1) {
checkProfile = true;
}
if (!seekable) {
QMetaObject::invokeMethod(pCore->bin(), "requestTranscoding", Qt::QueuedConnection, Q_ARG(QString, resource), Q_ARG(QString, QString::number(m_owner.second)), Q_ARG(bool, checkProfile));
}
// Check for variable frame rate
isVariableFrameRate = producer->get_int("meta.media.variable_frame_rate");
if (isVariableFrameRate) {
QAction *ac = new QAction(i18n("Transcode to edit friendly format"));
QAction *ac2 = new QAction(i18n("Discard"));
QObject::connect(ac, &QAction::triggered, [id = m_owner.second, resource, checkProfile]() {
QMetaObject::invokeMethod(pCore.get(), "transcodeFriendlyFile", Qt::QueuedConnection, Q_ARG(QString, QString::number(id)), Q_ARG(bool, checkProfile));
});
if (checkProfile) {
QObject::connect(ac2, &QAction::triggered, [id = m_owner.second]() {
QMetaObject::invokeMethod(pCore->bin(), "slotCheckProfile", Qt::QueuedConnection, Q_ARG(QString, QString::number(id)));
});
}
QList<QAction*>actions = {ac,ac2};
QMetaObject::invokeMethod(pCore.get(), "displayBinMessage", Qt::QueuedConnection, Q_ARG(QString, i18n("File <b>%1</b> uses a variable framerate and is not recommended for editing.", QFileInfo(resource).fileName())), Q_ARG(int, int(KMessageWidget::Warning)), Q_ARG(QList<QAction*>, actions));
if (isVariableFrameRate && seekable) {
QMetaObject::invokeMethod(pCore->bin(), "requestTranscoding", Qt::QueuedConnection, Q_ARG(QString, resource), Q_ARG(QString, QString::number(m_owner.second)), Q_ARG(bool, checkProfile));
}
// check if there are multiple streams
vindex = producer->get_int("video_index");
// List streams
int streams = producer->get_int("meta.media.nb_streams");
QList<int> audio_list, video_list;
......
......@@ -23,7 +23,7 @@ SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include <klocalizedstring.h>
TranscodeTask::TranscodeTask(const ObjectId &owner, QString params, int in, int out, bool replaceProducer, QObject* object)
TranscodeTask::TranscodeTask(const ObjectId &owner, QString params, int in, int out, bool replaceProducer, QObject* object, bool checkProfile)
: AbstractTask(owner, AbstractTask::TRANSCODEJOB, object)
, m_jobDuration(0)
, m_isFfmpegJob(true)
......@@ -31,13 +31,14 @@ TranscodeTask::TranscodeTask(const ObjectId &owner, QString params, int in, int
, m_replaceProducer(replaceProducer)
, m_inPoint(in)
, m_outPoint(out)
, m_checkProfile(checkProfile)
, m_jobProcess(nullptr)
{
}
void TranscodeTask::start(const ObjectId &owner, QString params, int in, int out, bool replaceProducer, QObject* object, bool force)
void TranscodeTask::start(const ObjectId &owner, QString params, int in, int out, bool replaceProducer, QObject* object, bool force, bool checkProfile)
{
TranscodeTask* task = new TranscodeTask(owner, params, in, out, replaceProducer, object);
TranscodeTask* task = new TranscodeTask(owner, params, in, out, replaceProducer, object, checkProfile);
// See if there is already a task for this MLT service and resource.
if (pCore->taskManager.hasPendingJob(owner, AbstractTask::TRANSCODEJOB)) {
delete task;
......@@ -221,10 +222,15 @@ void TranscodeTask::run()
QMap <QString, QString> newProps;
sourceProps.insert(QStringLiteral("resource"), binClip->url());
sourceProps.insert(QStringLiteral("kdenlive:clipname"), binClip->clipName());
sourceProps.insert(QStringLiteral("_fullreload"), QStringLiteral("1"));
newProps.insert(QStringLiteral("resource"), destUrl);
newProps.insert(QStringLiteral("kdenlive:clipname"), QFileInfo(destUrl).fileName());
newProps.insert(QStringLiteral("_fullreload"), QStringLiteral("1"));
QString id = QString::number(m_owner.second);
pCore->bin()->slotEditClipCommand(binClip->clipId(), sourceProps, newProps);
if (m_checkProfile) {
QMetaObject::invokeMethod(pCore->bin(), "slotCheckProfile", Qt::QueuedConnection, Q_ARG(QString, QString::number(m_owner.second)));
}
} else {
QString folder = QStringLiteral("-1");
if (binClip) {
......
......@@ -14,8 +14,8 @@ class QProcess;
class TranscodeTask : public AbstractTask
{
public:
TranscodeTask(const ObjectId &owner, QString params, int in, int out, bool replaceProducer, QObject* object);
static void start(const ObjectId &owner, QString params, int in, int out, bool replaceProducer, QObject* object, bool force = false);
TranscodeTask(const ObjectId &owner, QString params, int in, int out, bool replaceProducer, QObject* object, bool checkProfile);
static void start(const ObjectId &owner, QString params, int in, int out, bool replaceProducer, QObject* object, bool force = false, bool checkProfile = false);
protected:
void run() override;
......@@ -30,6 +30,7 @@ private:
bool m_replaceProducer;
int m_inPoint;
int m_outPoint;
bool m_checkProfile;
std::unique_ptr<QProcess> m_jobProcess;
QString m_errorMessage;
QString m_logDetails;
......
......@@ -3773,6 +3773,7 @@ void MainWindow::slotFriendlyTranscode(const QString binId, bool checkProfile)
std::shared_ptr<ProjectClip> clip = pCore->projectItemModel()->getClipByBinID(binId);
if (clip == nullptr) {
qDebug()<<"// NO CLIP FOUND FOR BIN ID: "<<binId;
return;
}
QStringList urls = {clip->url()};
// Prepare clip properties
......
......@@ -218,6 +218,10 @@ ClipPropertiesController::ClipPropertiesController(ClipController *controller, Q
KIO::highlightInFileManager({QUrl::fromLocalFile(link)});
});
lay->addWidget(m_clipLabel);
lay->addWidget(&m_warningMessage);
m_warningMessage.setCloseButtonVisible(false);
m_warningMessage.setWordWrap(true);
m_warningMessage.hide();
m_tabWidget = new QTabWidget(this);
lay->addWidget(m_tabWidget);
setLayout(lay);
......@@ -967,6 +971,20 @@ ClipPropertiesController::ClipPropertiesController(ClipController *controller, Q
hlay->addWidget(box);
fpBox->addLayout(hlay);
hlay->addStretch(10);
// Check for variable frame rate
if (m_properties->get_int("meta.media.variable_frame_rate")) {
m_warningMessage.setText(i18n("File uses a variable frame rate, not recommanded"));
QAction *ac = new QAction(i18n("Transcode"));
QObject::connect(ac, &QAction::triggered, [id = m_id, resource = controller->clipUrl()]() {
QMetaObject::invokeMethod(pCore->bin(), "requestTranscoding", Qt::QueuedConnection, Q_ARG(QString, resource), Q_ARG(QString, id), Q_ARG(bool, false));
});
m_warningMessage.setMessageType(KMessageWidget::Warning);
m_warningMessage.addAction(ac);
m_warningMessage.show();
} else {
m_warningMessage.hide();
}
}
// Force properties page
QWidget *forceProp = new QWidget(this);
......
......@@ -11,6 +11,8 @@ SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "definitions.h"
#include "timecode.h"
#include <KMessageWidget>
#include <QString>
#include <QTreeWidget>
#include <QLabel>
......@@ -133,6 +135,7 @@ private:
QCheckBox *m_copyChannel1;
QCheckBox *m_copyChannel2;
QSpinBox *m_gain;
KMessageWidget m_warningMessage;
/** @brief The selected audio stream. */
int m_activeAudioStreams;
void fillProperties();
......
......@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>302</width>
<height>328</height>
<width>498</width>
<height>270</height>
</rect>
</property>
<property name="windowTitle">
......@@ -17,7 +17,7 @@
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label">
<property name="text">
<string>The following clips are not usable for editing. Do you want to transcode them in an edit friendly format ? The converted clips will replace the original ones in your project once created.</string>
<string>The following clips are not usable for editing. This will transcode them in an edit friendly format and replace the original ones in your project.</string>
</property>
<property name="alignment">
<set>Qt::AlignJustify|Qt::AlignVCenter</set>
......
Supports Markdown
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