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

Reintroduce open multiple video stream clips

parent 2377fc0f
Pipeline #204241 passed with stage
in 14 minutes and 19 seconds
......@@ -15,6 +15,7 @@ SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "doc/documentchecker.h"
#include "doc/docundostack.hpp"
#include "doc/kdenlivedoc.h"
#include "doc/kthumb.h"
#include "effects/effectstack/model/effectstackmodel.hpp"
#include "jobs/abstracttask.h"
#include "jobs/cliploadtask.h"
......@@ -5279,3 +5280,111 @@ bool Bin::containsId(const QString &clipId) const
}
return true;
}
void Bin::processMultiStream(const QString &clipId, QList<int> videoStreams, QList<int> audioStreams, int aindex, int vindex)
{
auto binClip = m_itemModel->getClipByBinID(clipId);
// We retrieve the folder containing our clip, because we will set the other streams in the same
std::shared_ptr<AbstractProjectItem> baseFolder = binClip->parent();
if (!baseFolder) {
baseFolder = m_itemModel->getRootFolder();
}
const QString parentId = baseFolder->clipId();
std::shared_ptr<Mlt::Producer> producer = binClip->originalProducer();
// This helper lambda request addition of a given stream
auto addStream = [this, parentId, producer](int vindex, int aindex, Fun &undo, Fun &redo) {
auto clone = ProjectClip::cloneProducer(producer);
clone->set("video_index", vindex);
clone->set("audio_index", aindex);
QString id;
m_itemModel->requestAddBinClip(id, clone, parentId, undo, redo);
};
Fun undo = []() { return true; };
Fun redo = []() { return true; };
if (KdenliveSettings::automultistreams()) {
for (int i = 1; i < videoStreams.count(); ++i) {
int vindex = videoStreams.at(i);
int aindex = 0;
if (i <= audioStreams.count() - 1) {
aindex = audioStreams.at(i);
}
addStream(vindex, aindex, undo, redo);
}
pCore->pushUndo(undo, redo, i18np("Add additional stream for clip", "Add additional streams for clip", videoStreams.count() - 1));
return;
}
int width = int(60.0 * pCore->getCurrentDar());
if (width % 2 == 1) {
width++;
}
QScopedPointer<QDialog> dialog(new QDialog(qApp->activeWindow()));
dialog->setWindowTitle(QStringLiteral("Multi Stream Clip"));
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
QWidget *mainWidget = new QWidget(dialog.data());
auto *mainLayout = new QVBoxLayout;
dialog->setLayout(mainLayout);
mainLayout->addWidget(mainWidget);
QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
okButton->setDefault(true);
okButton->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Return));
dialog->connect(buttonBox, &QDialogButtonBox::accepted, dialog.data(), &QDialog::accept);
dialog->connect(buttonBox, &QDialogButtonBox::rejected, dialog.data(), &QDialog::reject);
okButton->setText(i18n("Import selected clips"));
QLabel *lab1 = new QLabel(i18n("Additional streams for clip\n %1", binClip->clipName()), mainWidget);
mainLayout->addWidget(lab1);
QList<QGroupBox *> groupList;
QList<QComboBox *> comboList;
// We start loading the list at 1, video index 0 should already be loaded
for (int j = 1; j < videoStreams.count(); ++j) {
auto clone = ProjectClip::cloneProducer(producer);
clone->set("video_index", videoStreams.at(j));
// TODO this keyframe should be cached
QImage thumb = KThumb::getFrame(clone.get(), 0, width, 60);
QGroupBox *streamFrame = new QGroupBox(i18n("Video stream %1", videoStreams.at(j)), mainWidget);
mainLayout->addWidget(streamFrame);
streamFrame->setProperty("vindex", videoStreams.at(j));
groupList << streamFrame;
streamFrame->setCheckable(true);
streamFrame->setChecked(true);
auto *vh = new QVBoxLayout(streamFrame);
QLabel *iconLabel = new QLabel(mainWidget);
mainLayout->addWidget(iconLabel);
iconLabel->setPixmap(QPixmap::fromImage(thumb));
vh->addWidget(iconLabel);
if (audioStreams.count() > 1) {
auto *cb = new QComboBox(mainWidget);
mainLayout->addWidget(cb);
for (int k = 0; k < audioStreams.count(); ++k) {
cb->addItem(i18n("Audio stream %1", audioStreams.at(k)), audioStreams.at(k));
}
comboList << cb;
cb->setCurrentIndex(qMin(j, audioStreams.count() - 1));
vh->addWidget(cb);
}
mainLayout->addWidget(streamFrame);
}
mainLayout->addStretch(10);
mainLayout->addWidget(buttonBox);
if (dialog->exec() == QDialog::Accepted) {
// import selected streams
int importedStreams = 0;
for (int i = 0; i < groupList.count(); ++i) {
if (groupList.at(i)->isChecked()) {
int vindex = groupList.at(i)->property("vindex").toInt();
int ax = qMin(i, comboList.size() - 1);
int aindex = -1;
if (ax >= 0) {
// only check audio index if we have several audio streams
aindex = comboList.at(ax)->itemData(comboList.at(ax)->currentIndex()).toInt();
}
addStream(vindex, aindex, undo, redo);
importedStreams++;
}
}
pCore->pushUndo(undo, redo, i18np("Add additional stream for clip", "Add additional streams for clip", importedStreams));
}
}
......@@ -472,6 +472,8 @@ public slots:
void requestSelectionTranscoding();
/** @brief Build the project bin audio/video icons according to color theme */
void slotUpdatePalette();
/** @brief Import multiple video streams in a clip */
void processMultiStream(const QString &clipId, QList<int> videoStreams, QList<int> audioStreams, int aindex, int vindex);
protected:
/* This function is called whenever an item is selected to propagate signals
......
......@@ -1242,12 +1242,25 @@ const QString ProjectClip::hash(bool createIfEmpty)
return QString();
}
QString clipHash = getProducerProperty(QStringLiteral("kdenlive:file_hash"));
if (!clipHash.isEmpty() || createIfEmpty) {
if (!clipHash.isEmpty() || !createIfEmpty) {
return clipHash;
}
return getFileHash();
}
const QString ProjectClip::hashForThumbs()
{
if (m_clipStatus == FileStatus::StatusWaiting) {
// Clip is not ready
return QString();
}
QString clipHash = getProducerProperty(QStringLiteral("kdenlive:file_hash"));
if (!clipHash.isEmpty() && m_hasMultipleVideoStreams) {
clipHash.append(m_properties->get("video_index"));
}
return clipHash;
}
const QByteArray ProjectClip::getFolderHash(const QDir &dir, QString fileName)
{
QStringList files = dir.entryList(QDir::Files);
......@@ -1987,7 +2000,7 @@ void ProjectClip::getThumbFromPercent(int percent, bool storeFrame)
if (percent < 0) {
if (hasProducerProperty(QStringLiteral("kdenlive:thumbnailFrame"))) {
int framePos = qMax(0, getProducerIntProperty(QStringLiteral("kdenlive:thumbnailFrame")));
QImage thumb = ThumbnailCache::get()->getThumbnail(hash(), m_binId, framePos);
QImage thumb = ThumbnailCache::get()->getThumbnail(hashForThumbs(), m_binId, framePos);
if (!thumb.isNull()) {
setThumbnail(thumb, -1, -1);
}
......@@ -1998,7 +2011,7 @@ void ProjectClip::getThumbFromPercent(int percent, bool storeFrame)
int steps = qCeil(qMax(pCore->getCurrentFps(), double(duration) / 30));
int framePos = duration * percent / 100;
framePos -= framePos % steps;
QImage thumb = ThumbnailCache::get()->getThumbnail(hash(), m_binId, framePos);
QImage thumb = ThumbnailCache::get()->getThumbnail(hashForThumbs(), m_binId, framePos);
if (!thumb.isNull()) {
setThumbnail(thumb, -1, -1);
} else {
......
......@@ -153,6 +153,8 @@ public:
/** @brief The clip hash created from the clip's resource. */
const QString hash(bool createIfEmpty = true);
/** @brief The clip hash created from the clip's resource, plus the video stream in case of multi-stream clips. */
const QString hashForThumbs();
/** @brief Callculate a file hash from a path. */
static const QPair<QByteArray, qint64> calculateHash(const QString &path);
......
......@@ -227,7 +227,7 @@ void ClipLoadTask::generateThumbnail(std::shared_ptr<ProjectClip> binClip, std::
qDebug() << "===== \nREADY FOR THUMB" << binClip->clipType() << "\n\n=========";
int frameNumber = m_in > -1 ? m_in : qMax(0, binClip->getProducerIntProperty(QStringLiteral("kdenlive:thumbnailFrame")));
if (producer->get_int("video_index") > -1) {
QImage thumb = ThumbnailCache::get()->getThumbnail(binClip->hash(), QString::number(m_owner.second), frameNumber);
QImage thumb = ThumbnailCache::get()->getThumbnail(binClip->hashForThumbs(), QString::number(m_owner.second), frameNumber);
if (!thumb.isNull()) {
// Thumbnail found in cache
qDebug() << "=== FOUND THUMB IN CACHe";
......
......@@ -36,6 +36,7 @@ ClipController::ClipController(const QString &clipId, const std::shared_ptr<Mlt:
, m_videoIndex(0)
, m_clipType(ClipType::Unknown)
, m_hasLimitedDuration(true)
, m_hasMultipleVideoStreams(false)
, m_effectStack(producer ? EffectStackModel::construct(producer, {ObjectType::BinClip, clipId.toInt()}, pCore->undoStack()) : nullptr)
, m_hasAudio(false)
, m_hasVideo(false)
......@@ -47,6 +48,7 @@ ClipController::ClipController(const QString &clipId, const std::shared_ptr<Mlt:
return;
}
if (m_properties) {
m_hasMultipleVideoStreams = m_properties->property_exists("kdenlive:multistreams");
setProducerProperty(QStringLiteral("kdenlive:id"), m_controllerBinId);
getInfoForProducer();
checkAudioVideo();
......@@ -105,6 +107,31 @@ void ClipController::addMasterProducer(const std::shared_ptr<Mlt::Producer> &pro
setProducerProperty(QStringLiteral("kdenlive:id"), m_controllerBinId);
getInfoForProducer();
emitProducerChanged(m_controllerBinId, producer);
if (!m_hasMultipleVideoStreams && m_service.startsWith(QLatin1String("avformat")) && (m_clipType == ClipType::AV || m_clipType == ClipType::Video)) {
// Check if clip has multiple video streams
int vindex = m_properties->get_int("video_index");
int aindex = m_properties->get_int("audio_index");
// Find maximum stream index values
QList<int> videoStreams;
QList<int> audioStreams;
int aStreams = m_properties->get_int("meta.media.nb_streams");
for (int ix = 0; ix < aStreams; ++ix) {
char property[200];
snprintf(property, sizeof(property), "meta.media.%d.stream.type", ix);
QString type = m_properties->get(property);
if (type == QLatin1String("video")) {
videoStreams << ix;
} else if (type == QLatin1String("audio")) {
audioStreams << ix;
}
}
if (videoStreams.count() > 1) {
setProducerProperty(QStringLiteral("kdenlive:multistreams"), 1);
m_hasMultipleVideoStreams = true;
QMetaObject::invokeMethod(pCore->bin(), "processMultiStream", Qt::QueuedConnection, Q_ARG(const QString &, m_controllerBinId),
Q_ARG(QList<int>, videoStreams), Q_ARG(QList<int>, audioStreams), Q_ARG(int, aindex), Q_ARG(int, vindex));
}
}
}
connectEffectStack();
}
......@@ -312,11 +339,13 @@ bool ClipController::isValid()
const char *ClipController::getPassPropertiesList(bool passLength)
{
if (!passLength) {
return "kdenlive:proxy,kdenlive:originalurl,rotate,force_aspect_num,force_aspect_den,force_aspect_ratio,force_fps,force_progressive,force_tff,threads,"
return "kdenlive:proxy,kdenlive:originalurl,kdenlive:multistreams,rotate,force_aspect_num,force_aspect_den,force_aspect_ratio,force_fps,force_"
"progressive,force_tff,threads,"
"force_"
"colorspace,set.force_full_luma,file_hash,autorotate,disable_exif,xmldata,video_index,audio_index,set.test_image,set.test_audio";
}
return "kdenlive:proxy,kdenlive:originalurl,rotate,force_aspect_num,force_aspect_den,force_aspect_ratio,force_fps,force_progressive,force_tff,threads,"
return "kdenlive:proxy,kdenlive:originalurl,kdenlive:multistreams,rotate,force_aspect_num,force_aspect_den,force_aspect_ratio,force_fps,force_progressive,"
"force_tff,threads,"
"force_"
"colorspace,set.force_full_luma,templatetext,file_hash,autorotate,disable_exif,xmldata,length,video_index,audio_index,set.test_image,set.test_audio";
}
......
......@@ -233,6 +233,7 @@ protected:
int m_videoIndex;
ClipType::ProducerType m_clipType;
bool m_hasLimitedDuration;
bool m_hasMultipleVideoStreams;
QMutex m_effectMutex;
void getInfoForProducer();
// void rebuildEffectList(ProfileInfo info);
......
......@@ -34,7 +34,7 @@ QImage ThumbnailProvider::requestImage(const QString &id, QSize *size, const QSi
if (ok) {
std::shared_ptr<ProjectClip> binClip = pCore->projectItemModel()->getClipByBinID(binId);
if (binClip) {
result = ThumbnailCache::get()->getThumbnail(binClip->hash(), binId, frameNumber);
result = ThumbnailCache::get()->getThumbnail(binClip->hashForThumbs(), binId, frameNumber);
if (!result.isNull()) {
*size = result.size();
return result;
......
......@@ -330,7 +330,11 @@ QString ThumbnailCache::getKey(const QString &binId, int pos, bool *ok)
}
auto binClip = pCore->projectItemModel()->getClipByBinID(binId);
*ok = binClip != nullptr && binClip->statusReady();
return *ok ? binClip->hash() + QLatin1Char('#') + QString::number(pos) + QStringLiteral(".jpg") : QString();
QString result;
if (!ok) {
return result;
}
return *ok ? binClip->hashForThumbs() + QLatin1Char('#') + QString::number(pos) + QStringLiteral(".jpg") : QString();
}
// static
......
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