]> git.sesse.net Git - kdenlive/blobdiff - src/clipitem.cpp
Cache timeline thumbnails for faster project opening, should solve:
[kdenlive] / src / clipitem.cpp
index 52e80ccd01bc9fe2dd3a38133a1d0593bba00866..b270a8c7c0f878b8e671708437baceab76586142 100644 (file)
 
 
 ClipItem::ClipItem(DocClipBase *clip, ItemInfo info, double fps, bool generateThumbs)
-        : AbstractClipItem(info, QRectF(), fps), m_clip(clip), m_resizeMode(NONE), m_grabPoint(0), m_maxTrack(0), m_hasThumbs(false), startThumbTimer(NULL), endThumbTimer(NULL), m_effectsCounter(1), audioThumbWasDrawn(false), m_opacity(1.0), m_timeLine(0), m_startThumbRequested(false), m_endThumbRequested(false), m_startFade(0), m_endFade(0), m_hover(false), m_selectedEffect(-1), m_speed(1.0), framePixelWidth(0), m_startPix(QPixmap()), m_endPix(QPixmap()) {
-    setRect(0, 0, (info.endPos - info.startPos).frames(fps) - 0.02, (qreal)(KdenliveSettings::trackheight() - 2));
-    setPos((qreal) info.startPos.frames(fps), (qreal)(info.track * KdenliveSettings::trackheight()) + 1);
+        : AbstractClipItem(info, QRectF(), fps), m_clip(clip), m_resizeMode(NONE), m_grabPoint(0), m_maxTrack(0), m_hasThumbs(false), startThumbTimer(NULL), endThumbTimer(NULL), audioThumbWasDrawn(false), m_opacity(1.0), m_timeLine(0), m_startThumbRequested(false), m_endThumbRequested(false), m_startFade(0), m_endFade(0), m_hover(false), m_selectedEffect(-1), m_speed(1.0), framePixelWidth(0), m_startPix(QPixmap()), m_endPix(QPixmap()) {
+    setZValue(1);
+    setRect(0, 0, (info.endPos - info.startPos).frames(fps) - 0.02, (double)(KdenliveSettings::trackheight() - 2));
+    setPos(info.startPos.frames(fps), (double)(info.track * KdenliveSettings::trackheight()) + 1);
 
     m_clipName = clip->name();
     m_producer = clip->getId();
@@ -148,6 +149,78 @@ void ClipItem::initEffect(QDomElement effect) {
             }
         }
     }
+
+    if (effect.attribute("tag") == "volume") {
+        if (effect.attribute("id") == "fadeout") {
+            int end = (duration() + cropStart()).frames(m_fps);
+            int start = end - EffectsList::parameter(effect, "in").toInt();
+            EffectsList::setParameter(effect, "in", QString::number(start));
+            EffectsList::setParameter(effect, "out", QString::number(end));
+        } else if (effect.attribute("id") == "fadein") {
+            int start = cropStart().frames(m_fps);
+            int end = start + EffectsList::parameter(effect, "out").toInt();
+            EffectsList::setParameter(effect, "in", QString::number(start));
+            EffectsList::setParameter(effect, "out", QString::number(end));
+        }
+    }
+}
+
+bool ClipItem::checkKeyFrames() {
+    bool clipEffectsModified = false;
+    for (int ix = 0; ix < m_effectList.count(); ix ++) {
+        QString kfr = keyframes(ix);
+        if (!kfr.isEmpty()) {
+            const QStringList keyframes = kfr.split(";", QString::SkipEmptyParts);
+            QStringList newKeyFrames;
+            bool cutKeyFrame = false;
+            bool modified = false;
+            int lastPos = -1;
+            double lastValue = -1;
+            int start = m_cropStart.frames(m_fps);
+            int end = (m_cropStart + m_cropDuration).frames(m_fps);
+            foreach(const QString str, keyframes) {
+                int pos = str.section(":", 0, 0).toInt();
+                double val = str.section(":", 1, 1).toDouble();
+                if (pos - start < 0) {
+                    // a keyframe is defined before the start of the clip
+                    cutKeyFrame = true;
+                } else if (cutKeyFrame) {
+                    // create new keyframe at clip start, calculate interpolated value
+                    if (pos > start) {
+                        int diff = pos - lastPos;
+                        double ratio = (double)(start - lastPos) / diff;
+                        double newValue = lastValue + (val - lastValue) * ratio;
+                        newKeyFrames.append(QString::number(start) + ":" + QString::number(newValue));
+                        modified = true;
+                    }
+                    cutKeyFrame = false;
+                }
+                if (!cutKeyFrame) {
+                    if (pos > end) {
+                        // create new keyframe at clip end, calculate interpolated value
+                        int diff = pos - lastPos;
+                        if (diff != 0) {
+                            double ratio = (double)(end - lastPos) / diff;
+                            double newValue = lastValue + (val - lastValue) * ratio;
+                            newKeyFrames.append(QString::number(end) + ":" + QString::number(newValue));
+                            modified = true;
+                        }
+                        break;
+                    } else {
+                        newKeyFrames.append(QString::number(pos) + ":" + QString::number(val));
+                    }
+                }
+                lastPos = pos;
+                lastValue = val;
+            }
+            if (modified) {
+                // update KeyFrames
+                setKeyframes(ix, newKeyFrames.join(";"));
+                clipEffectsModified = true;
+            }
+        }
+    }
+    return clipEffectsModified;
 }
 
 void ClipItem::setKeyframes(const int ix, const QString keyframes) {
@@ -278,7 +351,7 @@ void ClipItem::slotFetchThumbs() {
     if (m_endPix.isNull() && m_startPix.isNull()) {
         m_startThumbRequested = true;
         m_endThumbRequested = true;
-        emit getThumb((int)m_cropStart.frames(m_fps), (int)(m_cropStart + m_cropDuration).frames(m_fps) - 1);
+        emit getThumb((int)cropStart().frames(m_fps), (int)(cropStart() + cropDuration()).frames(m_fps) - 1);
     } else {
         if (m_endPix.isNull()) {
             slotGetEndThumb();
@@ -304,14 +377,14 @@ void ClipItem::slotFetchThumbs() {
 
 void ClipItem::slotGetStartThumb() {
     m_startThumbRequested = true;
-    emit getThumb((int)m_cropStart.frames(m_fps), -1);
+    emit getThumb((int)cropStart().frames(m_fps), -1);
     //videoThumbProducer.setThumbFrames(m_clip->producer(), (int)m_cropStart.frames(m_fps),  - 1);
     //videoThumbProducer.start(QThread::LowestPriority);
 }
 
 void ClipItem::slotGetEndThumb() {
     m_endThumbRequested = true;
-    emit getThumb(-1, (int)(m_cropStart + m_cropDuration).frames(m_fps) - 1);
+    emit getThumb(-1, (int)(cropStart() + cropDuration()).frames(m_fps) - 1);
     //videoThumbProducer.setThumbFrames(m_clip->producer(), -1, (int)(m_cropStart + m_cropDuration).frames(m_fps) - 1);
     //videoThumbProducer.start(QThread::LowestPriority);
 }
@@ -341,12 +414,12 @@ void ClipItem::slotThumbReady(int frame, QPixmap pix) {
     if (scene() == NULL) return;
     QRectF r = sceneBoundingRect();
     double width = m_startPix.width() / projectScene()->scale();
-    if (m_startThumbRequested && frame == m_cropStart.frames(m_fps)) {
+    if (m_startThumbRequested && frame == cropStart().frames(m_fps)) {
         m_startPix = pix;
         m_startThumbRequested = false;
         double height = r.height();
         update(r.x(), r.y(), width, height);
-    } else if (m_endThumbRequested && frame == (m_cropStart + m_cropDuration).frames(m_fps) - 1) {
+    } else if (m_endThumbRequested && frame == (cropStart() + cropDuration()).frames(m_fps) - 1) {
         m_endPix = pix;
         m_endThumbRequested = false;
         double height = r.height();
@@ -354,14 +427,22 @@ void ClipItem::slotThumbReady(int frame, QPixmap pix) {
     }
 }
 
-void ClipItem::slotSetStartThumb(QPixmap pix) {
+void ClipItem::slotSetStartThumb(const QPixmap pix) {
     m_startPix = pix;
 }
 
-void ClipItem::slotSetEndThumb(QPixmap pix) {
+void ClipItem::slotSetEndThumb(const QPixmap pix) {
     m_endPix = pix;
 }
 
+QPixmap ClipItem::startThumb() const {
+    return m_startPix;
+}
+
+QPixmap ClipItem::endThumb() const {
+    return m_endPix;
+}
+
 void ClipItem::slotGotAudioData() {
     audioThumbReady = true;
     if (m_clipType == AV) {
@@ -426,13 +507,12 @@ void ClipItem::paint(QPainter *painter,
     const double scale = option->matrix.m11();
 
 
-
     //painter->setRenderHints(QPainter::Antialiasing);
 
     //QPainterPath roundRectPathUpper = upperRectPart(br), roundRectPathLower = lowerRectPart(br);
     painter->setClipRect(exposed);
 
-    // build path around clip
+    //build path around clip
     //QPainterPath resultClipPath = roundRectPathUpper.united(roundRectPathLower);
     //painter->fillPath(resultClipPath, paintColor);
     painter->fillRect(br, paintColor);
@@ -471,7 +551,7 @@ void ClipItem::paint(QPainter *painter,
     }
 
     // draw audio thumbnails
-    if (KdenliveSettings::audiothumbnails() && ((m_clipType == AV && exposed.bottom() > (itemHeight / 2)) || m_clipType == AUDIO) && audioThumbReady) {
+    if (KdenliveSettings::audiothumbnails() && m_speed == 1.0 && ((m_clipType == AV && exposed.bottom() > (itemHeight / 2)) || m_clipType == AUDIO) && audioThumbReady) {
 
         double startpixel = exposed.left();
         if (startpixel < 0)
@@ -499,7 +579,9 @@ void ClipItem::paint(QPainter *painter,
         const int mappedEndPixel =  painter->matrix().map(QPointF(endpixel + cropLeft, 0)).x() - clipStart;
         cropLeft = cropLeft * scale;
 
-        emit prepareAudioThumb(scale, mappedStartPixel, mappedEndPixel, channels);
+        if (channels >= 1) {
+            emit prepareAudioThumb(scale, mappedStartPixel, mappedEndPixel, channels);
+        }
 
         for (int startCache = mappedStartPixel - (mappedStartPixel) % 100; startCache < mappedEndPixel; startCache += 100) {
             if (audioThumbCachePic.contains(startCache) && !audioThumbCachePic[startCache].isNull())
@@ -634,11 +716,10 @@ void ClipItem::paint(QPainter *painter,
 
     //kDebug()<<"/// ITEM PAINTING:: exposed="<<exposed<<", RECT = "<<rect();
 
-    exposed.setRight(exposed.right() + 1);
+    // expand clip rect to allow correct painting of clip border
+    exposed.setRight(exposed.right() + 1 / scale + 0.5);
     exposed.setBottom(exposed.bottom() + 1);
     painter->setClipRect(exposed);
-    //painter->setClipping(false);
-    //painter->fillRect(exposed, Qt::blue);
     painter->setPen(pen);
     painter->drawRect(br);
 }
@@ -651,12 +732,7 @@ OPERATIONTYPE ClipItem::operationMode(QPointF pos) {
     }
     QRectF rect = sceneBoundingRect();
     const double scale = projectScene()->scale();
-    double maximumOffset;
-    if (scale > 3) maximumOffset = 25 / scale;
-    else maximumOffset = 6 / scale;
-    QMatrix matrix;
-    matrix.scale(scale, 0);
-    //kDebug()<<"// Item rect: "<<rect.x()<<". pos. "<<pos.x()<<", scale: "<<scale<<", ratio: "<<qAbs((int)(pos.x() - rect.x())) / scale;
+    double maximumOffset = 6 / scale;
 
     if (qAbs((int)(pos.x() - (rect.x() + m_startFade))) < maximumOffset  && qAbs((int)(pos.y() - rect.y())) < 6) {
         if (m_startFade == 0) setToolTip(i18n("Add audio fade"));
@@ -740,6 +816,12 @@ void ClipItem::slotPrepareAudioThumb(double pixelForOneFrame, int startpixel, in
         pixpainter.setPen(audiopen);
         //pixpainter.setRenderHint(QPainter::Antialiasing,true);
         //pixpainter.drawLine(0,0,100,re.height());
+        // Bail out, if caller provided invalid data
+        if (channels <= 0) {
+            kWarning() << "Unable to draw image with " << channels << "number of channels";
+            return;
+        }
+
         int channelHeight = audioThumbCachePic[startCache].height() / channels;
 
         for (int i = 0;i < channels;i++) {
@@ -805,21 +887,23 @@ uint ClipItem::fadeOut() const {
 
 
 void ClipItem::setFadeIn(int pos) {
+    if (pos == m_startFade) return;
     int oldIn = m_startFade;
     if (pos < 0) pos = 0;
-    if (pos > m_cropDuration.frames(m_fps)) pos = (int)(m_cropDuration.frames(m_fps) / 2);
+    if (pos > m_cropDuration.frames(m_fps)) pos = (int)(m_cropDuration.frames(m_fps));
     m_startFade = pos;
     QRectF rect = boundingRect();
     update(rect.x(), rect.y(), qMax(oldIn, pos), rect.height());
 }
 
 void ClipItem::setFadeOut(int pos) {
+    if (pos == m_endFade) return;
     int oldOut = m_endFade;
     if (pos < 0) pos = 0;
-    if (pos > m_cropDuration.frames(m_fps)) pos = (int)(m_cropDuration.frames(m_fps) / 2);
+    if (pos > m_cropDuration.frames(m_fps)) pos = (int)(m_cropDuration.frames(m_fps));
     m_endFade = pos;
     QRectF rect = boundingRect();
-    update(rect.x() + rect.width() - qMax(oldOut, pos), rect.y(), pos, rect.height());
+    update(rect.x() + rect.width() - qMax(oldOut, pos), rect.y(), qMax(oldOut, pos), rect.height());
 
 }
 
@@ -862,12 +946,12 @@ void ClipItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *) {
     update(r.right() - width, r.y() + height, width, height);
 }
 
-void ClipItem::resizeStart(int posx) {
+void ClipItem::resizeStart(int posx, double speed) {
     const int min = (startPos() - cropStart()).frames(m_fps);
     if (posx < min) posx = min;
     if (posx == startPos().frames(m_fps)) return;
     const int previous = cropStart().frames(m_fps);
-    AbstractClipItem::resizeStart(posx);
+    AbstractClipItem::resizeStart(posx, m_speed);
     checkEffectsKeyframesPos(previous, cropStart().frames(m_fps), true);
     if (m_hasThumbs && KdenliveSettings::videothumbnails()) {
         /*connect(m_clip->thumbProducer(), SIGNAL(thumbReady(int, QPixmap)), this, SLOT(slotThumbReady(int, QPixmap)));*/
@@ -875,13 +959,14 @@ void ClipItem::resizeStart(int posx) {
     }
 }
 
-void ClipItem::resizeEnd(int posx) {
+void ClipItem::resizeEnd(int posx, double speed, bool updateKeyFrames) {
     const int max = (startPos() - cropStart() + maxDuration()).frames(m_fps) + 1;
     if (posx > max) posx = max;
     if (posx == endPos().frames(m_fps)) return;
+    //kDebug() << "// NEW POS: " << posx << ", OLD END: " << endPos().frames(m_fps);
     const int previous = (cropStart() + duration()).frames(m_fps);
-    AbstractClipItem::resizeEnd(posx);
-    checkEffectsKeyframesPos(previous, (cropStart() + duration()).frames(m_fps), false);
+    AbstractClipItem::resizeEnd(posx, m_speed);
+    if (updateKeyFrames) checkEffectsKeyframesPos(previous, (cropStart() + duration()).frames(m_fps), false);
     if (m_hasThumbs && KdenliveSettings::videothumbnails()) {
         /*connect(m_clip->thumbProducer(), SIGNAL(thumbReady(int, QPixmap)), this, SLOT(slotThumbReady(int, QPixmap)));*/
         endThumbTimer->start(100);
@@ -899,9 +984,11 @@ void ClipItem::checkEffectsKeyframesPos(const int previous, const int current, b
                 // parse keyframes and adjust values
                 const QStringList keyframes = e.attribute("keyframes").split(";", QString::SkipEmptyParts);
                 QMap <int, double> kfr;
+                int pos;
+                double val;
                 foreach(const QString str, keyframes) {
-                    int pos = str.section(":", 0, 0).toInt();
-                    double val = str.section(":", 1, 1).toDouble();
+                    pos = str.section(":", 0, 0).toInt();
+                    val = str.section(":", 1, 1).toDouble();
                     if (pos == previous) kfr[current] = val;
                     else {
                         if (fromStart && pos >= current) kfr[pos] = val;
@@ -922,18 +1009,17 @@ void ClipItem::checkEffectsKeyframesPos(const int previous, const int current, b
     if (m_selectedEffect >= 0) setSelectedEffect(m_selectedEffect);
 }
 
-
 //virtual
 QVariant ClipItem::itemChange(GraphicsItemChange change, const QVariant &value) {
     if (change == ItemPositionChange && scene()) {
         // calculate new position.
-        if (group() != 0) return pos();
+        if (group()) return pos();
         QPointF newPos = value.toPointF();
-        //kDebug() << "/// MOVING CLIP ITEM.------------";
+        kDebug() << "/// MOVING CLIP ITEM.------------\n++++++++++";
         int xpos = projectScene()->getSnapPointForPos((int) newPos.x(), KdenliveSettings::snaptopoints());
         xpos = qMax(xpos, 0);
         newPos.setX(xpos);
-        int newTrack = (newPos.y() + KdenliveSettings::trackheight() / 2) / KdenliveSettings::trackheight();
+        int newTrack = newPos.y() / KdenliveSettings::trackheight();
         newTrack = qMin(newTrack, projectScene()->tracksCount() - 1);
         newTrack = qMax(newTrack, 0);
         newPos.setY((int)(newTrack  * KdenliveSettings::trackheight() + 1));
@@ -993,7 +1079,7 @@ QVariant ClipItem::itemChange(GraphicsItemChange change, const QVariant &value)
 }*/
 
 int ClipItem::effectsCounter() {
-    return m_effectsCounter++;
+    return effectsCount() + 1;
 }
 
 int ClipItem::effectsCount() {
@@ -1014,7 +1100,8 @@ QDomElement ClipItem::effectAt(int ix) {
 }
 
 void ClipItem::setEffectAt(int ix, QDomElement effect) {
-    kDebug() << "CHange EFFECT AT: " << ix << ", CURR: " << m_effectList.at(ix).attribute("tag") << ", NEW: " << effect.attribute("tag");
+    kDebug() << "CHange EFFECT AT: " << ix << ", CURR: " << m_effectList.at(ix).attribute("tag") << ", NEW: " << effect.attribute("tag");
+    effect.setAttribute("kdenlive_ix", ix + 1);
     m_effectList.insert(ix, effect);
     m_effectList.removeAt(ix + 1);
     m_effectNames = m_effectList.effectNames().join(" / ");
@@ -1031,7 +1118,7 @@ QHash <QString, QString> ClipItem::addEffect(QDomElement effect, bool animate) {
     bool needRepaint = false;
     /*QDomDocument doc;
     doc.appendChild(doc.importNode(effect, true));
-    kDebug() << "///////  CLIP ADD EFFECT: "<< doc.toString();*/
+    kDebug() << "///////  CLIP ADD EFFECT: " << doc.toString();*/
     m_effectList.append(effect);
     effectParams["tag"] = effect.attribute("tag");
     QString effectId = effect.attribute("id");
@@ -1069,7 +1156,7 @@ QHash <QString, QString> ClipItem::addEffect(QDomElement effect, bool animate) {
                     else if (e.attribute("name") == "in") fade += e.attribute("value").toInt();
                 }
             } else {
-                effectParams[e.attribute("name")] =  QString::number(effectParams[e.attribute("name")].toDouble() / f);
+                effectParams[e.attribute("name")] =  QString::number(e.attribute("value").toDouble() / f);
             }
         }
     }
@@ -1134,8 +1221,10 @@ QHash <QString, QString> ClipItem::getEffectArgs(QDomElement effect) {
 
 void ClipItem::deleteEffect(QString index) {
     bool needRepaint = false;
+    QString ix;
     for (int i = 0; i < m_effectList.size(); ++i) {
-        if (m_effectList.at(i).attribute("kdenlive_ix") == index) {
+        ix = m_effectList.at(i).attribute("kdenlive_ix");
+        if (ix == index) {
             if (m_effectList.at(i).attribute("id") == "fadein") {
                 m_startFade = 0;
                 needRepaint = true;
@@ -1144,8 +1233,7 @@ void ClipItem::deleteEffect(QString index) {
                 needRepaint = true;
             }
             m_effectList.removeAt(i);
-            break;
-        }
+        } else if (ix.toInt() > index.toInt()) m_effectList[i].setAttribute("kdenlive_ix", ix.toInt() - 1);
     }
     m_effectNames = m_effectList.effectNames().join(" / ");
     if (needRepaint) update(boundingRect());
@@ -1160,13 +1248,25 @@ void ClipItem::setSpeed(const double speed) {
     m_speed = speed;
     if (m_speed == 1.0) m_clipName = baseClip()->name();
     else m_clipName = baseClip()->name() + " - " + QString::number(speed * 100, 'f', 0) + "%";
-    update();
+    //update();
 }
 
 GenTime ClipItem::maxDuration() const {
     return m_maxDuration / m_speed;
 }
 
+GenTime ClipItem::cropStart() const {
+    return m_cropStart / m_speed;
+}
+
+GenTime ClipItem::cropDuration() const {
+    return m_cropDuration / m_speed;
+}
+
+GenTime ClipItem::endPos() const {
+    return m_startPos + cropDuration();
+}
+
 //virtual
 void ClipItem::dropEvent(QGraphicsSceneDragDropEvent * event) {
     QString effects = QString(event->mimeData()->data("kdenlive/effectslist"));