From c6137d8358c60b7ad69171053272299dbdc483c1 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Mardelle Date: Tue, 23 Oct 2012 01:08:37 +0200 Subject: [PATCH] Project tree sub-clips and clip properties dialog thumbnails are now created in another thread, not blocking ui --- src/clipmanager.cpp | 37 ++++++++++++++++++++++++++++++-- src/clipmanager.h | 5 +++++ src/clipproperties.cpp | 32 ++++++++++++++------------- src/clipproperties.h | 1 + src/kthumb.cpp | 31 +++++++++++++++----------- src/kthumb.h | 1 + src/mainwindow.cpp | 7 ++++++ src/projectlist.cpp | 27 +++++++++++++++++------ src/projectlist.h | 1 + src/renderwidget.cpp | 1 + src/subprojectitem.cpp | 15 +++++++++++-- src/subprojectitem.h | 3 ++- src/widgets/clipproperties_ui.ui | 3 --- 13 files changed, 122 insertions(+), 42 deletions(-) diff --git a/src/clipmanager.cpp b/src/clipmanager.cpp index c6552696..a706e5ad 100644 --- a/src/clipmanager.cpp +++ b/src/clipmanager.cpp @@ -26,6 +26,7 @@ #include "abstractclipitem.h" #include "abstractgroupitem.h" #include "titledocument.h" +#include "subprojectitem.h" #include "kthumb.h" #include @@ -167,19 +168,42 @@ void ClipManager::slotGetThumbs() QMap::const_iterator i; int max; int done = 0; + int thumbType = 0; // 0 = timeline thumb, 1 = project clip zone thumb, 2 = clip properties thumb + while (!m_requestedThumbs.isEmpty() && !m_abortThumb) { m_thumbsMutex.lock(); i = m_requestedThumbs.constBegin(); m_processingThumbId = i.key(); QList values = m_requestedThumbs.values(m_processingThumbId); m_requestedThumbs.remove(m_processingThumbId); + if (m_processingThumbId.startsWith("?")) { + // if id starts with ?, it means the request comes from a clip property widget + thumbType = 2; + m_processingThumbId.remove(0, 1); + } + if (m_processingThumbId.startsWith("#")) { + // if id starts with #, it means the request comes from project tree + thumbType = 1; + m_processingThumbId.remove(0, 1); + } m_thumbsMutex.unlock(); qSort(values); DocClipBase *clip = getClipById(m_processingThumbId); if (!clip) continue; max = m_requestedThumbs.size() + values.count(); + int pos; while (!values.isEmpty() && clip->thumbProducer() && !m_abortThumb) { - clip->thumbProducer()->getThumb(values.takeFirst()); + pos = values.takeFirst(); + switch (thumbType) { + case 1: + clip->thumbProducer()->getGenericThumb(pos, SubProjectItem::itemDefaultHeight(), thumbType); + break; + case 2: + clip->thumbProducer()->getGenericThumb(pos, 180, thumbType); + break; + default: + clip->thumbProducer()->getThumb(pos); + } done++; if (max > 3) emit displayMessage(i18n("Loading thumbnails"), 100 * done / max); } @@ -918,5 +942,14 @@ bool ClipManager::isOnRemovableDevice(const KUrl &url) return volumeMatch; } - +void ClipManager::projectTreeThumbReady(const QString &id, int frame, QImage img, int type) +{ + switch (type) { + case 2: + emit gotClipPropertyThumbnail(id, img); + break; + default: + emit thumbReady(id, frame, img); + } +} diff --git a/src/clipmanager.h b/src/clipmanager.h index 512edd20..ca85ab05 100644 --- a/src/clipmanager.h +++ b/src/clipmanager.h @@ -127,6 +127,7 @@ Q_OBJECT public: void requestThumbs(const QString id, QList frames); /** @brief remove a clip id from the queue list. */ void stopThumbs(const QString &id); + void projectTreeThumbReady(const QString &id, int frame, QImage img, int type); #if KDE_IS_VERSION(4,5,0) KImageCache* pixmapCache; @@ -177,6 +178,8 @@ private: // Private attributes QString m_processingAudioThumbId; /** @brief The list of removable drives. */ QList m_removableVolumes; + + QPoint m_projectTreeThumbSize; /** @brief Get a list of drives, to check if we have files on removable media. */ void listRemovableVolumes(); @@ -190,6 +193,8 @@ signals: void availableClip(const QString &); void checkAllClips(bool displayRatioChanged, bool fpsChanged, QStringList brokenClips); void displayMessage(const QString &, int); + void thumbReady(const QString &id, int, QImage); + void gotClipPropertyThumbnail(const QString &id, QImage); }; #endif diff --git a/src/clipproperties.cpp b/src/clipproperties.cpp index 1d5e39ee..22bc7269 100644 --- a/src/clipproperties.cpp +++ b/src/clipproperties.cpp @@ -435,21 +435,8 @@ ClipProperties::ClipProperties(DocClipBase *clip, Timecode tc, double fps, QWidg if (props.contains("colorspace")) new QTreeWidgetItem(m_view.clip_vproperties, QStringList() << i18n("Colorspace") << ProfilesDialog::getColorspaceDescription(props.value("colorspace").toInt())); - - int width = 180.0 * KdenliveSettings::project_display_ratio(); - if (width % 2 == 1) width++; - QPixmap pix = m_clip->thumbProducer()->getImage(url, m_clip->getClipThumbFrame(), width, 180); - QPixmap framedPix(pix.width(), pix.height()); - framedPix.fill(Qt::transparent); - QPainter p(&framedPix); - p.setRenderHint(QPainter::Antialiasing, true); - QPainterPath path; - path.addRoundedRect(0.5, 0.5, framedPix.width() - 1, framedPix.height() - 1, 4, 4); - p.setClipPath(path); - p.drawPixmap(0, 0, pix); - p.end(); - - m_view.clip_thumb->setPixmap(framedPix); + m_view.clip_thumb->setMinimumSize(180 * KdenliveSettings::project_display_ratio(), 180); + if (t == IMAGE || t == VIDEO || t == PLAYLIST) m_view.tabWidget->removeTab(AUDIOTAB); } else { m_view.tabWidget->removeTab(IMAGETAB); @@ -688,6 +675,21 @@ ClipProperties::~ClipProperties() if (del2) delete del2; } +void ClipProperties::slotGotThumbnail(const QString &id, QImage img) +{ + if (id != m_clip->getId()) return; + QPixmap framedPix(img.width(), img.height()); + framedPix.fill(Qt::transparent); + QPainter p(&framedPix); + p.setRenderHint(QPainter::Antialiasing, true); + QPainterPath path; + path.addRoundedRect(0.5, 0.5, framedPix.width() - 1, framedPix.height() - 1, 4, 4); + p.setClipPath(path); + p.drawImage(0, 0, img); + p.end(); + m_view.clip_thumb->setPixmap(framedPix); +} + void ClipProperties::slotApplyProperties() { if (m_clip != NULL) { diff --git a/src/clipproperties.h b/src/clipproperties.h index ec0dee89..18ef15fd 100644 --- a/src/clipproperties.h +++ b/src/clipproperties.h @@ -76,6 +76,7 @@ private slots: void slotSaveMarkers(); void slotLoadMarkers(); void slotDeleteAnalysis(); + void slotGotThumbnail(const QString &id, QImage img); private: Ui::ClipProperties_UI m_view; diff --git a/src/kthumb.cpp b/src/kthumb.cpp index 350f14f4..86dcab93 100644 --- a/src/kthumb.cpp +++ b/src/kthumb.cpp @@ -66,7 +66,7 @@ void KThumb::setProducer(Mlt::Producer *producer) if (m_producer) m_clipManager->stopThumbs(m_id); m_intraFramesQueue.clear(); m_intra.waitForFinished(); - m_mutex.lock(); + QMutexLocker lock(&m_mutex); m_producer = producer; // FIXME: the profile() call leaks an object, but trying to free // it leads to a double-free in Profile::~Profile() @@ -75,7 +75,6 @@ void KThumb::setProducer(Mlt::Producer *producer) m_dar = profile->dar(); m_ratio = (double) profile->width() / profile->height(); } - m_mutex.unlock(); } void KThumb::clearProducer() @@ -118,11 +117,18 @@ void KThumb::getThumb(int frame) const int theight = KdenliveSettings::trackheight(); const int swidth = (int)(theight * m_ratio + 0.5); const int dwidth = (int)(theight * m_dar + 0.5); - QImage img = getProducerFrame(frame, swidth, dwidth, theight); emit thumbReady(frame, img); } +void KThumb::getGenericThumb(int frame, int height, int type) +{ + const int swidth = (int)(height * m_ratio + 0.5); + const int dwidth = (int)(height * m_dar + 0.5); + QImage img = getProducerFrame(frame, swidth, dwidth, height); + m_clipManager->projectTreeThumbReady(m_id, frame, img, type); +} + QImage KThumb::extractImage(int frame, int width, int height) { if (m_producer == NULL) { @@ -139,9 +145,6 @@ QPixmap KThumb::getImage(KUrl url, int frame, int width, int height) Mlt::Profile profile(KdenliveSettings::current_profile().toUtf8().constData()); QPixmap pix(width, height); if (url.isEmpty()) return pix; - - //""); - //Mlt::Producer producer(profile, "xml-string", tmp); Mlt::Producer *producer = new Mlt::Producer(profile, url.path().toUtf8().constData()); double swidth = (double) profile.width() / profile.height(); pix = QPixmap::fromImage(getFrame(producer, frame, (int) (height * swidth + 0.5), width, height)); @@ -162,12 +165,14 @@ QImage KThumb::getProducerFrame(int framepos, int frameWidth, int displayWidth, p.fill(QColor(Qt::black).rgb()); return p; } - m_mutex.lock(); + QMutexLocker lock(&m_mutex); m_producer->seek(framepos); Mlt::Frame *frame = m_producer->get_frame(); + frame->set("rescale.interp", "nearest"); + frame->set("deinterlace_method", "onefield"); + frame->set("top_field_first", -1 ); QImage p = getFrame(frame, frameWidth, displayWidth, height); delete frame; - m_mutex.unlock(); return p; } @@ -187,6 +192,9 @@ QImage KThumb::getFrame(Mlt::Producer *producer, int framepos, int frameWidth, i producer->seek(framepos); Mlt::Frame *frame = producer->get_frame(); + frame->set("rescale.interp", "nearest"); + frame->set("deinterlace_method", "onefield"); + frame->set("top_field_first", -1 ); QImage p = getFrame(frame, frameWidth, displayWidth, height); delete frame; return p; @@ -201,17 +209,14 @@ QImage KThumb::getFrame(Mlt::Frame *frame, int frameWidth, int displayWidth, int p.fill(QColor(Qt::red).rgb()); return p; } - + int ow = frameWidth; int oh = height; mlt_image_format format = mlt_image_rgb24a; - frame->set("rescale.interp", "nearest"); - frame->set("deinterlace_method", "onefield"); - frame->set("top_field_first", -1 ); //frame->set("progressive", "1"); if (ow % 2 == 1) ow++; - const uchar* imagedata = frame->get_image(format, ow, oh); QImage image(ow, oh, QImage::Format_ARGB32_Premultiplied); + const uchar* imagedata = frame->get_image(format, ow, oh); memcpy(image.bits(), imagedata, ow * oh * 4);//.byteCount()); //const uchar* imagedata = frame->get_image(format, ow, oh); diff --git a/src/kthumb.h b/src/kthumb.h index 142bff41..65e1cb8b 100644 --- a/src/kthumb.h +++ b/src/kthumb.h @@ -71,6 +71,7 @@ Q_OBJECT public: QImage findCachedThumb(const QString &path); #endif void getThumb(int frame); + void getGenericThumb(int frame, int height, int type); public slots: void updateClipUrl(KUrl url, const QString &hash); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 31d181f5..6aee6888 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -3323,6 +3323,13 @@ void MainWindow::slotShowClipProperties(DocClipBase *clip) // any type of clip but a title ClipProperties *dia = new ClipProperties(clip, m_activeDocument->timecode(), m_activeDocument->fps(), this); + + if (clip->clipType() == AV || clip->clipType() == VIDEO || clip->clipType() == PLAYLIST) { + // request clip thumbnails + m_activeDocument->clipManager()->requestThumbs(QString('?' + clip->getId()), QList() << clip->getClipThumbFrame()); + connect(m_activeDocument->clipManager(), SIGNAL(gotClipPropertyThumbnail(const QString&,QImage)), dia, SLOT(slotGotThumbnail(const QString&,QImage))); + } + connect(dia, SIGNAL(addMarkers(const QString &, QList )), m_activeTimeline->projectView(), SLOT(slotAddClipMarker(const QString &, QList ))); connect(dia, SIGNAL(deleteAnalysis(QString,QString)), m_activeTimeline->projectView(), SLOT(slotAddClipExtraData(QString,QString))); connect(m_activeTimeline->projectView(), SIGNAL(updateClipMarkers(DocClipBase *)), dia, SLOT(slotFillMarkersList(DocClipBase *))); diff --git a/src/projectlist.cpp b/src/projectlist.cpp index 8bc65c36..70ea5a10 100644 --- a/src/projectlist.cpp +++ b/src/projectlist.cpp @@ -1360,7 +1360,7 @@ void ProjectList::slotAddClip(DocClipBase *clip, bool getProperties) QList cuts = clip->cutZones(); if (!cuts.isEmpty()) { for (int i = 0; i < cuts.count(); i++) { - SubProjectItem *sub = new SubProjectItem(item, cuts.at(i).zone.x(), cuts.at(i).zone.y(), cuts.at(i).description); + SubProjectItem *sub = new SubProjectItem(m_render->dar(), item, cuts.at(i).zone.x(), cuts.at(i).zone.y(), cuts.at(i).description); if (!clip->getClipHash().isEmpty()) { QString cachedPixmap = m_doc->projectFolder().path(KUrl::AddTrailingSlash) + "thumbs/" + clip->getClipHash() + '#' + QString::number(cuts.at(i).zone.x()) + ".png"; if (QFile::exists(cachedPixmap)) { @@ -2001,6 +2001,23 @@ void ProjectList::setDocument(KdenliveDoc *doc) connect(m_doc->clipManager(), SIGNAL(missingClip(const QString &)), this, SLOT(slotMissingClip(const QString &))); connect(m_doc->clipManager(), SIGNAL(availableClip(const QString &)), this, SLOT(slotAvailableClip(const QString &))); connect(m_doc->clipManager(), SIGNAL(checkAllClips(bool, bool, QStringList)), this, SLOT(updateAllClips(bool, bool, QStringList))); + connect(m_doc->clipManager(), SIGNAL(thumbReady(const QString &, int, QImage)), this, SLOT(slotSetThumbnail(const QString &, int, QImage))); +} + +void ProjectList::slotSetThumbnail(const QString &id, int framePos, QImage img) +{ + QString fullid = id + '#' + QString::number(framePos); + ProjectItem *pItem = NULL; + QTreeWidgetItem *item = getAnyItemById(fullid); + if (item && item->parent()) pItem = static_cast (item->parent()); + if (!item && framePos == 0) pItem = getItemById(id); + if (!item && !pItem) return; + if (item) item->setData(0, Qt::DecorationRole, QPixmap::fromImage(img)); + else if (pItem) pItem->setData(0, Qt::DecorationRole, QPixmap::fromImage(img)); + if (pItem) { + QString hash = pItem->getClipHash(); + if (!hash.isEmpty()) m_doc->cacheImage(hash + '#' + QString::number(framePos), img); + } } QList ProjectList::documentClipList() const @@ -2576,17 +2593,14 @@ void ProjectList::addClipCut(const QString &id, int in, int out, const QString d DocClipBase *base = clip->referencedClip(); base->addCutZone(in, out); monitorItemEditing(false); - SubProjectItem *sub = new SubProjectItem(clip, in, out, desc); + SubProjectItem *sub = new SubProjectItem(m_render->dar(), clip, in, out, desc); if (newItem && desc.isEmpty() && !m_listView->isColumnHidden(1)) { if (!clip->isExpanded()) clip->setExpanded(true); m_listView->scrollToItem(sub); m_listView->editItem(sub, 1); } - QImage img = clip->referencedClip()->extractImage(in, (int)(sub->sizeHint(0).height() * m_render->dar()), sub->sizeHint(0).height() - 2); - sub->setData(0, Qt::DecorationRole, QPixmap::fromImage(img)); - QString hash = clip->getClipHash(); - if (!hash.isEmpty()) m_doc->cacheImage(hash + '#' + QString::number(in), img); + m_doc->clipManager()->requestThumbs(QString('#' + id), QList () << in); monitorItemEditing(true); } emit projectModified(); @@ -3668,4 +3682,5 @@ void ProjectList::slotGotFilterJobResults(QString id, int , int , QString filter } } + #include "projectlist.moc" diff --git a/src/projectlist.h b/src/projectlist.h index 7019595c..1f906a5d 100644 --- a/src/projectlist.h +++ b/src/projectlist.h @@ -331,6 +331,7 @@ public slots: void slotTranscodeClipJob(const QString &condition, QString params, QString desc); /** @brief Start an MLT process job. */ void slotStartFilterJob(ItemInfo, const QString&,const QString&,const QString&,const QString&,const QString&,const QString&,const QStringList&); + void slotSetThumbnail(const QString &id, int framePos, QImage img); private: diff --git a/src/renderwidget.cpp b/src/renderwidget.cpp index 35e40814..89887ea1 100644 --- a/src/renderwidget.cpp +++ b/src/renderwidget.cpp @@ -980,6 +980,7 @@ void RenderWidget::slotExport(bool scriptExport, int zoneIn, int zoneOut, const render_process_args << "consumer:" + (scriptExport ? "$SOURCE" : playlistPath); else render_process_args << (scriptExport ? "$SOURCE" : playlistPath); + render_process_args << (scriptExport ? "$TARGET" : KUrl(dest).url()); render_process_args << paramsList; diff --git a/src/subprojectitem.cpp b/src/subprojectitem.cpp index 9c8ad1ef..eba677b1 100644 --- a/src/subprojectitem.cpp +++ b/src/subprojectitem.cpp @@ -24,22 +24,27 @@ #include "kdenlivesettings.h" #include "docclipbase.h" + #include #include #include const int DurationRole = Qt::UserRole + 1; +const int itemHeight = 30; -SubProjectItem::SubProjectItem(QTreeWidgetItem * parent, int in, int out, QString description) : +SubProjectItem::SubProjectItem(double display_ratio, QTreeWidgetItem * parent, int in, int out, QString description) : QTreeWidgetItem(parent, PROJECTSUBCLIPTYPE), m_in(in), m_out(out), m_description(description) { - setSizeHint(0, QSize(65, 30)); + setSizeHint(0, QSize((int) (itemHeight * display_ratio) + 2, itemHeight + 2)); setFlags(Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsDropEnabled); QString name = Timecode::getStringTimecode(in, KdenliveSettings::project_fps()); setText(0, name); setText(1, description); GenTime duration = GenTime(out - in, KdenliveSettings::project_fps()); if (duration != GenTime()) setData(0, DurationRole, Timecode::getEasyTimecode(duration, KdenliveSettings::project_fps())); + QPixmap pix((int) (itemHeight * display_ratio), itemHeight); + pix.fill(Qt::gray); + setData(0, Qt::DecorationRole, pix); //setFlags(Qt::NoItemFlags); //kDebug() << "Constructed with clipId: " << m_clipId; } @@ -54,6 +59,12 @@ int SubProjectItem::numReferences() const return 0; } +//static +int SubProjectItem::itemDefaultHeight() +{ + return itemHeight; +} + QDomElement SubProjectItem::toXml() const { //return m_clip->toXML(); diff --git a/src/subprojectitem.h b/src/subprojectitem.h index 9a5a01cf..d3efd85a 100644 --- a/src/subprojectitem.h +++ b/src/subprojectitem.h @@ -38,7 +38,7 @@ class DocClipBase; class SubProjectItem : public QTreeWidgetItem { public: - SubProjectItem(QTreeWidgetItem * parent, int in, int out, QString description = QString()); + SubProjectItem(double display_ratio, QTreeWidgetItem * parent, int in, int out, QString description = QString()); virtual ~SubProjectItem(); QDomElement toXml() const; int numReferences() const; @@ -47,6 +47,7 @@ public: void setZone(QPoint p); QString description() const; void setDescription(QString desc); + static int itemDefaultHeight(); /** Make sure folders appear on top of the tree widget */ virtual bool operator<(const QTreeWidgetItem &other)const { diff --git a/src/widgets/clipproperties_ui.ui b/src/widgets/clipproperties_ui.ui index 95516d38..c6080d51 100644 --- a/src/widgets/clipproperties_ui.ui +++ b/src/widgets/clipproperties_ui.ui @@ -16,9 +16,6 @@ - - Image preview - Qt::AlignCenter -- 2.39.2