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

* Check for variable frame rate clips and propose transcoding (WIP)

* Do not refresh video thumbs on audio thumb change
parent e0becd80
......@@ -4066,10 +4066,11 @@ void Bin::slotResetInfoMessage()
{
m_errorLog.clear();
m_currentMessage = BinMessage::BinCategory::NoMessage;
QList<QAction *> actions = m_infoMessage->actions();
// We cannot delete actions here because of concurrency, it might delete actions meant for the upcoming message
/*QList<QAction *> actions = m_infoMessage->actions();
for (int i = 0; i < actions.count(); ++i) {
m_infoMessage->removeAction(actions.at(i));
}
}*/
}
......@@ -4519,7 +4520,61 @@ void Bin::adjustProjectProfileToItem()
std::shared_ptr<AbstractProjectItem> item = m_itemModel->getBinItemByIndex(m_proxyModel->mapToSource(current));
auto clip = std::static_pointer_cast<ProjectClip>(item);
if (clip) {
ClipLoadTask::checkProfile(clip->originalProducer());
checkProfile(clip->originalProducer());
}
}
}
void Bin::slotCheckProfile(const QString binId)
{
std::shared_ptr<ProjectClip> clip = m_itemModel->getClipByBinID(binId);
if (clip) {
checkProfile(clip->originalProducer());
}
}
// static
void Bin::checkProfile(const std::shared_ptr<Mlt::Producer> &producer)
{
// Check if clip profile matches
QString service = producer->get("mlt_service");
// Check for image producer
if (service == QLatin1String("qimage") || service == QLatin1String("pixbuf")) {
// This is an image, create profile from image size
int width = producer->get_int("meta.media.width");
if (width % 2 > 0) {
width += width % 2;
}
int height = producer->get_int("meta.media.height");
height += height % 2;
if (width > 100 && height > 100) {
std::unique_ptr<ProfileParam> projectProfile(new ProfileParam(pCore->getCurrentProfile().get()));
projectProfile->m_width = width;
projectProfile->m_height = height;
projectProfile->m_sample_aspect_num = 1;
projectProfile->m_sample_aspect_den = 1;
projectProfile->m_display_aspect_num = width;
projectProfile->m_display_aspect_den = height;
projectProfile->m_description.clear();
QMetaObject::invokeMethod(pCore->currentDoc(), "switchProfile", Q_ARG(ProfileParam*,new ProfileParam(projectProfile.get())));
} else {
// Very small image, we probably don't want to use this as profile
}
} else if (service.contains(QStringLiteral("avformat"))) {
std::unique_ptr<Mlt::Profile> blankProfile(new Mlt::Profile());
blankProfile->set_explicit(0);
blankProfile->from_producer(*producer);
std::unique_ptr<ProfileParam> clipProfile(new ProfileParam(blankProfile.get()));
std::unique_ptr<ProfileParam> projectProfile(new ProfileParam(pCore->getCurrentProfile().get()));
clipProfile->adjustDimensions();
if (*clipProfile.get() == *projectProfile.get()) {
if (KdenliveSettings::default_profile().isEmpty()) {
// Confirm default project format
KdenliveSettings::setDefault_profile(pCore->getCurrentProfile()->path());
}
} else {
// Profiles do not match, propose profile adjustment
QMetaObject::invokeMethod(pCore->currentDoc(), "switchProfile", Q_ARG(ProfileParam*,new ProfileParam(clipProfile.get())));
}
}
}
......
......@@ -339,6 +339,8 @@ public:
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);
private slots:
void slotAddClip();
......@@ -453,6 +455,8 @@ public slots:
void showTitleWidget(const std::shared_ptr<ProjectClip> &clip);
/** @brief Add a clip in a specially named folder */
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);
protected:
/* This function is called whenever an item is selected to propagate signals
......
......@@ -209,7 +209,7 @@ QString ProjectClip::getXmlProperty(const QDomElement &producer, const QString &
return value;
}
void ProjectClip::updateAudioThumbnail()
void ProjectClip::updateAudioThumbnail(bool cachedThumb)
{
emit audioThumbReady();
if (m_clipType == ClipType::Audio) {
......@@ -263,7 +263,10 @@ void ProjectClip::updateAudioThumbnail()
return;
}
m_audioThumbCreated = true;
updateTimelineClips({TimelineModel::ReloadThumbRole});
if (!cachedThumb) {
// Audio was just created
updateTimelineClips({TimelineModel::ReloadAudioThumbRole});
}
}
bool ProjectClip::audioThumbCreated() const
......@@ -436,7 +439,7 @@ QDomElement ProjectClip::toXml(QDomDocument &document, bool includeMeta, bool in
return prod;
}
void ProjectClip::setThumbnail(const QImage &img, int in, int out)
void ProjectClip::setThumbnail(const QImage &img, int in, int out, bool inCache)
{
if (img.isNull()) {
return;
......@@ -467,6 +470,10 @@ void ProjectClip::setThumbnail(const QImage &img, int in, int out)
std::static_pointer_cast<ProjectItemModel>(ptr)->onItemUpdated(std::static_pointer_cast<ProjectClip>(shared_from_this()),
AbstractProjectItem::DataThumbnail);
}
if (!inCache && (m_clipType == ClipType::Text || m_clipType == ClipType::TextTemplate)) {
// Title clips always use the same thumb as bin, refresh
updateTimelineClips({TimelineModel::ReloadThumbRole});
}
}
bool ProjectClip::hasAudioAndVideo() const
......@@ -498,10 +505,6 @@ bool ProjectClip::setProducer(std::shared_ptr<Mlt::Producer> producer)
FileStatus::ClipStatus currentStatus = m_clipStatus;
updateProducer(producer);
emit producerChanged(m_binId, producer);
if (producer->get_int("kdenlive:transcodingrequired") == 1) {
pCore->bin()->requestTranscoding(clipUrl(), clipId());
producer->set("kdenlive:transcodingrequired", nullptr);
}
m_thumbsProducer.reset();
connectEffectStack();
......
......@@ -259,14 +259,14 @@ protected:
public slots:
/** @brief Store the audio thumbnails once computed. Note that the parameter is a value and not a reference, fill free to use it as a sink (use std::move to
* avoid copy). */
void updateAudioThumbnail();
void updateAudioThumbnail(bool cachedThumb);
/** @brief Delete the proxy file */
void deleteProxy();
/** @brief A clip job progressed, update display */
void updateJobProgress();
/** @brief Sets thumbnail for this clip. */
void setThumbnail(const QImage &, int in, int out);
void setThumbnail(const QImage &, int in, int out, bool inCache = false);
void setThumbProducer(std::shared_ptr<Mlt::Producer>prod);
/** @brief A proxy clip is available or disabled, update path and reload */
......
......@@ -1134,6 +1134,11 @@ void Core::transcodeFile(const QString url)
window()->slotTranscode({url});
}
void Core::transcodeFriendlyFile(const QString binId, bool checkProfile)
{
window()->slotFriendlyTranscode(binId, checkProfile);
}
void Core::setWidgetKeyBinding(const QString &mess)
{
window()->setWidgetKeyBinding(mess);
......
......@@ -333,6 +333,8 @@ public slots:
void updateMonitorProfile();
/** @brief Add a new Bin Widget. */
void addBin(const QString &id = QString());
/** @brief Transcode a bin clip video. */
void transcodeFriendlyFile(const QString binId, bool checkProfile);
signals:
void coreIsReady();
......
......@@ -1500,12 +1500,11 @@ void KdenliveDoc::switchProfile(ProfileParam* pf)
}
// Build actions for the info message (switch / cancel)
QList<QAction *> list;
const QString profilePath = profile->path();
QAction *ac = new QAction(QIcon::fromTheme(QStringLiteral("dialog-ok")), i18n("Switch"), this);
connect(ac, &QAction::triggered, this, [this, profilePath]() { this->slotSwitchProfile(profilePath, true); });
QAction *ac2 = new QAction(QIcon::fromTheme(QStringLiteral("dialog-cancel")), i18n("Cancel"), this);
list << ac << ac2;
QList<QAction *> list = {ac,ac2};
pCore->displayBinMessage(i18n("Switch to clip profile %1?", profile->descriptiveString()), KMessageWidget::Information, list, false, BinMessage::BinCategory::ProfileMessage);
} else {
// No known profile, ask user if he wants to use clip profile anyway
......@@ -1827,4 +1826,3 @@ void KdenliveDoc::initializeSubtitles(std::shared_ptr<SubtitleModel> m_subtitle)
{
m_subtitleModel = m_subtitle;
}
......@@ -188,7 +188,7 @@ void AudioLevelsTask::run()
QString key = QString("_kdenlive:audio%1").arg(stream);
producer->set(key.toUtf8().constData(), levelsCopy, 0, (mlt_destructor) deleteQVariantList);
producer->unlock();
QMetaObject::invokeMethod(m_object, "updateAudioThumbnail");
QMetaObject::invokeMethod(m_object, "updateAudioThumbnail", Q_ARG(bool, false));
}
}
......@@ -232,12 +232,12 @@ void AudioLevelsTask::run()
}
image.save(cachePath);
audioCreated = true;
QMetaObject::invokeMethod(m_object, "updateAudioThumbnail");
QMetaObject::invokeMethod(m_object, "updateAudioThumbnail", Q_ARG(bool, false));
}
}
if (!audioCreated) {
// Audio was cached, ensure the bin thumbnail is loaded
QMetaObject::invokeMethod(m_object, "updateAudioThumbnail");
QMetaObject::invokeMethod(m_object, "updateAudioThumbnail", Q_ARG(bool, true));
}
pCore->taskManager.taskDone(m_owner.second, this);
QMetaObject::invokeMethod(m_object, "updateJobProgress");
......
......@@ -230,7 +230,7 @@ void ClipLoadTask::generateThumbnail(std::shared_ptr<ProjectClip>binClip, std::s
// Thumbnail found in cache
QImage result = ThumbnailCache::get()->getThumbnail(QString::number(m_owner.second), frameNumber);
qDebug()<<"=== FOUND THUMB IN CACHe";
QMetaObject::invokeMethod(binClip.get(), "setThumbnail", Qt::QueuedConnection, Q_ARG(QImage,result), Q_ARG(int,m_in), Q_ARG(int,m_out));
QMetaObject::invokeMethod(binClip.get(), "setThumbnail", Qt::QueuedConnection, Q_ARG(QImage,result), Q_ARG(int,m_in), Q_ARG(int,m_out), Q_ARG(bool,true));
} else {
QString mltService = producer->get("mlt_service");
const QString mltResource = producer->get("resource");
......@@ -272,10 +272,10 @@ void ClipLoadTask::generateThumbnail(std::shared_ptr<ProjectClip>binClip, std::s
QPainter p(&result);
p.setPen(Qt::white);
p.drawText(0, 0, fullWidth, imageHeight, Qt::AlignCenter, i18n("Invalid"));
QMetaObject::invokeMethod(binClip.get(), "setThumbnail", Qt::QueuedConnection, Q_ARG(QImage,result), Q_ARG(int,m_in), Q_ARG(int,m_out));
QMetaObject::invokeMethod(binClip.get(), "setThumbnail", Qt::QueuedConnection, Q_ARG(QImage,result), Q_ARG(int,m_in), Q_ARG(int,m_out), Q_ARG(bool,false));
} else if (!m_isCanceled) {
qDebug()<<"=== GOT THUMB FOR: "<<m_in<<"x"<<m_out;
QMetaObject::invokeMethod(binClip.get(), "setThumbnail", Qt::QueuedConnection, Q_ARG(QImage,result), Q_ARG(int,m_in), Q_ARG(int,m_out));
QMetaObject::invokeMethod(binClip.get(), "setThumbnail", Qt::QueuedConnection, Q_ARG(QImage,result), Q_ARG(int,m_in), Q_ARG(int,m_out), Q_ARG(bool,false));
ThumbnailCache::get()->storeThumbnail(QString::number(m_owner.second), frameNumber, result, true);
}
}
......@@ -284,51 +284,6 @@ void ClipLoadTask::generateThumbnail(std::shared_ptr<ProjectClip>binClip, std::s
}
}
void ClipLoadTask::checkProfile(const std::shared_ptr<Mlt::Producer> &producer)
{
// Check if clip profile matches
QString service = producer->get("mlt_service");
// Check for image producer
if (service == QLatin1String("qimage") || service == QLatin1String("pixbuf")) {
// This is an image, create profile from image size
int width = producer->get_int("meta.media.width");
if (width % 2 > 0) {
width += width % 2;
}
int height = producer->get_int("meta.media.height");
height += height % 2;
if (width > 100 && height > 100) {
std::unique_ptr<ProfileParam> projectProfile(new ProfileParam(pCore->getCurrentProfile().get()));
projectProfile->m_width = width;
projectProfile->m_height = height;
projectProfile->m_sample_aspect_num = 1;
projectProfile->m_sample_aspect_den = 1;
projectProfile->m_display_aspect_num = width;
projectProfile->m_display_aspect_den = height;
projectProfile->m_description.clear();
QMetaObject::invokeMethod(pCore->currentDoc(), "switchProfile", Q_ARG(ProfileParam*,new ProfileParam(projectProfile.get())));
} else {
// Very small image, we probably don't want to use this as profile
}
} else if (service.contains(QStringLiteral("avformat"))) {
std::unique_ptr<Mlt::Profile> blankProfile(new Mlt::Profile());
blankProfile->set_explicit(0);
blankProfile->from_producer(*producer);
std::unique_ptr<ProfileParam> clipProfile(new ProfileParam(blankProfile.get()));
std::unique_ptr<ProfileParam> projectProfile(new ProfileParam(pCore->getCurrentProfile().get()));
clipProfile->adjustDimensions();
if (*clipProfile.get() == *projectProfile.get()) {
if (KdenliveSettings::default_profile().isEmpty()) {
// Confirm default project format
KdenliveSettings::setDefault_profile(pCore->getCurrentProfile()->path());
}
} else {
// Profiles do not match, propose profile adjustment
QMetaObject::invokeMethod(pCore->currentDoc(), "switchProfile", Q_ARG(ProfileParam*,new ProfileParam(clipProfile.get())));
}
}
}
void ClipLoadTask::run()
{
// 2 channels interleaved of uchar values
......@@ -580,9 +535,10 @@ void ClipLoadTask::run()
if (type == ClipType::SlideShow) {
processSlideShow(producer);
}
int vindex = -1;
double fps = -1;
bool isVariableFrameRate = false;
bool seekable = true;
if (mltService == QLatin1String("xml") || mltService == QLatin1String("consumer")) {
// MLT playlist, create producer with blank profile to get real profile info
QString tmpPath = resource;
......@@ -602,10 +558,48 @@ void ClipLoadTask::run()
}
} else if (mltService == QLatin1String("avformat")) {
// Check if file is seekable
bool seekable = producer->get_int("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) {
producer->set("kdenlive:transcodingrequired", 1);
qDebug()<<"================0\n\nFOUND UNSEEKABLE FILE: "<<producer->get("resource")<<"\n\n===================";
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();
int w = frameSize.width();
int h = frameSize.height();
std::unique_ptr<Mlt::Frame> frame(producer->get_frame());
frame->get_image(format, w, h);
frame.reset();
// 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));
}
// check if there are multiple streams
vindex = producer->get_int("video_index");
......@@ -647,10 +641,9 @@ void ClipLoadTask::run()
auto binClip = pCore->projectItemModel()->getClipByBinID(QString::number(m_owner.second));
if (binClip) {
QMetaObject::invokeMethod(binClip.get(), "setProducer", Qt::QueuedConnection, Q_ARG(std::shared_ptr<Mlt::Producer>,producer));
if (m_xml.hasAttribute(QStringLiteral("_checkProfile")) && producer->get_int("video_index") > -1) {
checkProfile(producer);
if (m_xml.hasAttribute(QStringLiteral("_checkProfile")) && producer->get_int("video_index") > -1 && !isVariableFrameRate && seekable) {
QMetaObject::invokeMethod(pCore->bin(), "slotCheckProfile", Qt::QueuedConnection, Q_ARG(QString, QString::number(m_owner.second)));
}
}
generateThumbnail(binClip, producer);
emit taskDone();
......
......@@ -32,8 +32,6 @@ public:
std::shared_ptr<Mlt::Producer> loadPlaylist(QString &resource);
void processProducerProperties(const std::shared_ptr<Mlt::Producer> &prod, const QDomElement &xml);
void processSlideShow(std::shared_ptr<Mlt::Producer> producer);
// Do some checks on the profile
static void checkProfile(const std::shared_ptr<Mlt::Producer> &producer);
protected:
void run() override;
......
......@@ -3766,6 +3766,39 @@ void MainWindow::slotTranscode(const QStringList &urls)
d->show();
}
void MainWindow::slotFriendlyTranscode(const QString binId, bool checkProfile)
{
QString params;
QString desc;
std::shared_ptr<ProjectClip> clip = pCore->projectItemModel()->getClipByBinID(binId);
if (clip == nullptr) {
qDebug()<<"// NO CLIP FOUND FOR BIN ID: "<<binId;
}
QStringList urls = {clip->url()};
// Prepare clip properties
QMap <QString, QString> sourceProps;
sourceProps.insert(QStringLiteral("resource"), clip->url());
sourceProps.insert(QStringLiteral("kdenlive:originalurl"), clip->url());
sourceProps.insert(QStringLiteral("kdenlive:clipname"), clip->clipName());
sourceProps.insert(QStringLiteral("kdenlive:proxy"), clip->getProducerProperty(QStringLiteral("kdenlive:proxy")));
sourceProps.insert(QStringLiteral("_fullreload"), QStringLiteral("1"));
ClipTranscode *d = new ClipTranscode(urls, params, QStringList(), desc, pCore->bin()->getCurrentFolder());
connect(d, &ClipTranscode::addClip, [&, binId, sourceProps](const QUrl &url, const QString&/*folderInfo*/) {
QMap <QString, QString> newProps;
newProps.insert(QStringLiteral("resource"), url.toLocalFile());
newProps.insert(QStringLiteral("kdenlive:originalurl"), url.toLocalFile());
newProps.insert(QStringLiteral("kdenlive:clipname"), url.fileName());
newProps.insert(QStringLiteral("kdenlive:proxy"), QStringLiteral("-"));
newProps.insert(QStringLiteral("_fullreload"), QStringLiteral("1"));
QMetaObject::invokeMethod(pCore->bin(), "slotEditClipCommand", Qt::QueuedConnection, Q_ARG(QString, binId), Q_ARG(stringMap, sourceProps), Q_ARG(stringMap, newProps));
});
d->exec();
if (checkProfile) {
pCore->bin()->slotCheckProfile(binId);
}
}
void MainWindow::slotTranscodeClip()
{
const QString dialogFilter = ClipCreationDialog::getExtensionsFilter(QStringList() << i18n("All Files") + QStringLiteral(" (*)"));
......
......@@ -326,6 +326,7 @@ public slots:
void slotDownloadResources();
void slotEditSubtitle(QMap<QString, QString> subProperties = {});
void slotTranscode(const QStringList &urls = QStringList());
void slotFriendlyTranscode(const QString binId, bool checkProfile);
/** @brief Add subtitle clip to timeline */
void slotAddSubtitle(const QString &text = QString());
/** @brief Ensure subtitle track is displayed */
......
......@@ -229,6 +229,7 @@ QHash<int, QByteArray> TimelineItemModel::roleNames() const
roles[CanBeAudioRole] = "canBeAudio";
roles[CanBeVideoRole] = "canBeVideo";
roles[ReloadThumbRole] = "reloadThumb";
roles[ReloadAudioThumbRole] = "reloadAudioThumb";
roles[PositionOffsetRole] = "positionOffset";
roles[ThumbsFormatRole] = "thumbsFormat";
roles[AudioRecordRole] = "audioRecord";
......@@ -341,6 +342,8 @@ QVariant TimelineItemModel::data(const QModelIndex &index, int role) const
return clip->getMixCutPosition();
case ReloadThumbRole:
return clip->forceThumbReload;
case ReloadAudioThumbRole:
return clip->forceThumbReload;
case PositionOffsetRole:
return clip->getOffset();
case SpeedRole:
......
......@@ -5431,7 +5431,7 @@ void TimelineModel::replugClip(int clipId)
void TimelineModel::requestClipUpdate(int clipId, const QVector<int> &roles)
{
QModelIndex modelIndex = makeClipIndexFromID(clipId);
if (roles.contains(TimelineModel::ReloadThumbRole)) {
if (roles.contains(TimelineModel::ReloadThumbRole) || roles.contains(TimelineModel::ReloadAudioThumbRole)) {
m_allClips[clipId]->forceThumbReload = !m_allClips[clipId]->forceThumbReload;
}
notifyChange(modelIndex, modelIndex, roles);
......
......@@ -146,6 +146,7 @@ public:
FileHashRole, /// clip only
SpeedRole, /// clip only
ReloadThumbRole, /// clip only
ReloadAudioThumbRole, /// clip only
PositionOffsetRole, /// clip only
TimeRemapRole, /// clip only
ItemATrack, /// composition only
......
......@@ -66,6 +66,7 @@ Rectangle {
property double speed: 1.0
property color borderColor: 'black'
property bool forceReloadThumb
property bool forceReloadAudioThumb
property bool isComposition: false
property bool hideClipViews: false
property int slipOffset: boundValue(outPoint - maxDuration + 1, trimmingOffset, inPoint)
......@@ -157,6 +158,19 @@ Rectangle {
onForceReloadThumbChanged: {
// TODO: find a way to force reload of clip thumbs
if (isAudio) {
return;
}
if (thumbsLoader.item) {
thumbsLoader.item.reload(0)
}
}
onForceReloadAudioThumbChanged: {
// TODO: find a way to force reload of clip thumbs
if (!isAudio) {
return;
}
if (thumbsLoader.item) {
thumbsLoader.item.reload(0)
}
......@@ -253,6 +267,8 @@ Rectangle {
dropSource = ''
dropRow = -1
drag.acceptProposedAction
updateDrag()
//console.log('KFR VIEW VISIBLE: ', effectRow.visible, ', SOURCE: ', effectRow.source, '\n HIDEVIEW:', clipRoot.hideClipViews<<', UNDEFINED: ', (clipRoot.keyframeModel == undefined))
}
}
MouseArea {
......@@ -991,36 +1007,48 @@ Rectangle {
property: "kfrModel"
value: clipRoot.hideClipViews ? undefined : clipRoot.keyframeModel
when: effectRow.status == Loader.Ready && effectRow.item
// TODO: use restoreMode for Qt >= 5.15
// restoreMode: Binding.RestoreBindingOrValue
}
Binding {
target: effectRow.item
property: "selected"
value: clipRoot.selected
when: effectRow.status == Loader.Ready && effectRow.item
// TODO: use restoreMode for Qt >= 5.15
// restoreMode: Binding.RestoreBindingOrValue
}
Binding {
target: effectRow.item
property: "inPoint"
value: clipRoot.inPoint
when: effectRow.status == Loader.Ready && effectRow.item
// TODO: use restoreMode for Qt >= 5.15
// restoreMode: Binding.RestoreBindingOrValue
}
Binding {
target: effectRow.item
property: "outPoint"
value: clipRoot.outPoint
when: effectRow.status == Loader.Ready && effectRow.item
// TODO: use restoreMode for Qt >= 5.15
// restoreMode: Binding.RestoreBindingOrValue
}
Binding {
target: effectRow.item
property: "modelStart"
value: clipRoot.modelStart
when: effectRow.status == Loader.Ready && effectRow.item
// TODO: use restoreMode for Qt >= 5.15
// restoreMode: Binding.RestoreBindingOrValue
}
Binding {
target: effectRow.item
property: "clipId"
value: clipRoot.clipId
when: effectRow.status == Loader.Ready && effectRow.item
// TODO: use restoreMode for Qt >= 5.15
// restoreMode: Binding.RestoreBindingOrValue
}
}
Connections {
......
......@@ -235,6 +235,12 @@ Item{
value: model.reloadThumb
when: loader.status == Loader.Ready && clipItem
}
Binding {
target: loader.item
property: "forceReloadAudioThumb"
value: model.reloadAudioThumb
when: loader.status == Loader.Ready && clipItem
}
Binding {
target: loader.item
property: "binId"
......
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