]> git.sesse.net Git - kdenlive/blobdiff - src/clipitem.cpp
* Several fixes to clip resizing & cutting
[kdenlive] / src / clipitem.cpp
index 138414d561d64d871650af279fd229b2967a66a3..01ddd6e0d63cc17d8a4a51e74ea7a148688f595c 100644 (file)
 #include "kdenlivesettings.h"
 #include "kthumb.h"
 
-ClipItem::ClipItem(DocClipBase *clip, ItemInfo info, GenTime cropStart, double scale, double fps)
-        : 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_thumbsRequested(0), m_startFade(0), m_endFade(0), m_hover(false), m_selectedEffect(-1) {
+ClipItem::ClipItem(DocClipBase *clip, ItemInfo info, double scale, double fps)
+        : 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_thumbsRequested(0), m_startFade(0), m_endFade(0), m_hover(false), m_selectedEffect(-1), m_speed(1.0) {
     QRectF rect((double) info.startPos.frames(fps) * scale, (double)(info.track * KdenliveSettings::trackheight() + 1), (double)(info.endPos - info.startPos).frames(fps) * scale, (double)(KdenliveSettings::trackheight() - 1));
     setRect(rect);
 
     m_clipName = clip->name();
     m_producer = clip->getId();
     m_clipType = clip->clipType();
-    m_cropStart = cropStart;
+    m_cropStart = info.cropStart;
     m_maxDuration = clip->maxDuration();
     setAcceptDrops(true);
     audioThumbReady = clip->audioThumbCreated();
-    /*m_keyframes[0] = 50;
-    m_keyframes[30] = 20;
-    m_keyframes[70] = 90;
-    m_keyframes[100] = 10;*/
+
     /*
       m_cropStart = xml.attribute("in", 0).toInt();
       m_maxDuration = xml.attribute("duration", 0).toInt();
@@ -66,7 +63,7 @@ ClipItem::ClipItem(DocClipBase *clip, ItemInfo info, GenTime cropStart, double s
 
     setFlags(QGraphicsItem::ItemClipsToShape | QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
     setAcceptsHoverEvents(true);
-    connect(this , SIGNAL(prepareAudioThumb(double, QPainterPath, int, int)) , this, SLOT(slotPrepareAudioThumb(double, QPainterPath, int, int)));
+    connect(this , SIGNAL(prepareAudioThumb(double, QPainterPath, int, int, int)) , this, SLOT(slotPrepareAudioThumb(double, QPainterPath, int, int, int)));
 
     setBrush(QColor(141, 166, 215));
     if (m_clipType == VIDEO || m_clipType == AV || m_clipType == SLIDESHOW) {
@@ -98,83 +95,146 @@ ClipItem::ClipItem(DocClipBase *clip, ItemInfo info, GenTime cropStart, double s
 ClipItem::~ClipItem() {
     if (startThumbTimer) delete startThumbTimer;
     if (endThumbTimer) delete endThumbTimer;
+    if (m_timeLine) m_timeLine;
+}
+
+ClipItem *ClipItem::clone(double scale, ItemInfo info) const {
+    ClipItem *duplicate = new ClipItem(m_clip, info, scale, m_fps);
+    duplicate->setEffectList(m_effectList);
+    duplicate->setSpeed(m_speed);
+    return duplicate;
+}
+
+void ClipItem::setEffectList(const EffectsList effectList) {
+    m_effectList = effectList;
+    m_effectNames = m_effectList.effectNames().join(" / ");
+}
+
+const EffectsList ClipItem::effectList() {
+    return m_effectList;
 }
 
 int ClipItem::selectedEffectIndex() const {
     return m_selectedEffect;
 }
 
-void ClipItem::setSelectedEffect(int ix) {
-    //if (ix == m_selectedEffect) return;
-    m_selectedEffect = ix;
-    QDomElement effect = effectAt(m_selectedEffect);
+void ClipItem::initEffect(QDomElement effect) {
+    // the kdenlive_ix int is used to identify an effect in mlt's playlist, should
+    // not be changed
+    if (effect.attribute("kdenlive_ix").toInt() == 0)
+        effect.setAttribute("kdenlive_ix", QString::number(effectsCounter()));
+    // init keyframes if required
     QDomNodeList params = effect.elementsByTagName("parameter");
-
     for (int i = 0; i < params.count(); i++) {
         QDomElement e = params.item(i).toElement();
         if (!e.isNull() && e.attribute("type") == "keyframe") {
-            int max = e.attribute("max").toInt();
-            int min = e.attribute("min").toInt();
-            int def = e.attribute("default").toInt();
-            int factor = e.attribute("factor").toInt();
-            if (factor == 0) factor = 1;
-
+            QString def = e.attribute("default");
             // Effect has a keyframe type parameter, we need to set the values
             if (e.attribute("keyframes").isEmpty()) {
-                // no keyframes defined, set up 2 keyframes (start and end) with default value.
-                m_keyframes[0] = 100 * def / (max - min);
-                m_keyframes[100] = 100 * def / (max - min);
-            } else {
+                e.setAttribute("keyframes", QString::number(m_cropStart.frames(m_fps)) + ":" + def + ";" + QString::number((m_cropStart + m_cropDuration).frames(m_fps)) + ":" + def);
+                //kDebug() << "///// EFFECT KEYFRAMES INITED: " << e.attribute("keyframes");
+                break;
+            }
+        }
+    }
+}
+
+void ClipItem::setKeyframes(const int ix, const QString keyframes) {
+    QDomElement effect = effectAt(ix);
+    if (effect.attribute("disabled") == "1") return;
+    QDomNodeList params = effect.elementsByTagName("parameter");
+    for (int i = 0; i < params.count(); i++) {
+        QDomElement e = params.item(i).toElement();
+        if (!e.isNull() && e.attribute("type") == "keyframe") {
+            e.setAttribute("keyframes", keyframes);
+            if (ix == m_selectedEffect) {
+                m_keyframes.clear();
+                double max = e.attribute("max").toDouble();
+                double min = e.attribute("min").toDouble();
+                m_keyframeFactor = 100.0 / (max - min);
+                m_keyframeDefault = e.attribute("default").toDouble();
                 // parse keyframes
-                QStringList keyframes = e.attribute("keyframes").split(";");
-                foreach(QString str, keyframes) {
-                    if (!str.isEmpty()) {
-                        int pos = str.section(":", 0, 0).toInt();
-                        int val = str.section(":", 1, 1).toInt();
-                        /*int frame = (int) (pos * 100 / m_cropDuration.frames(m_fps));
-                        int value = (int) (((val * factor) - min) * 100 * factor / (max - min));*/
-                        m_keyframes[pos] = val;
-                    }
+                const QStringList keyframes = e.attribute("keyframes").split(";", QString::SkipEmptyParts);
+                foreach(const QString str, keyframes) {
+                    int pos = str.section(":", 0, 0).toInt();
+                    double val = str.section(":", 1, 1).toDouble();
+                    m_keyframes[pos] = val;
                 }
+                update();
+                return;
             }
-            update();
-            return;
+            break;
         }
     }
+}
+
+
+void ClipItem::setSelectedEffect(const int ix) {
+    m_selectedEffect = ix;
+    QDomElement effect = effectAt(m_selectedEffect);
+    QDomNodeList params = effect.elementsByTagName("parameter");
+    if (effect.attribute("disabled") != "1")
+        for (int i = 0; i < params.count(); i++) {
+            QDomElement e = params.item(i).toElement();
+            if (!e.isNull() && e.attribute("type") == "keyframe") {
+                m_keyframes.clear();
+                double max = e.attribute("max").toDouble();
+                double min = e.attribute("min").toDouble();
+                m_keyframeFactor = 100.0 / (max - min);
+                m_keyframeDefault = e.attribute("default").toDouble();
+                // parse keyframes
+                const QStringList keyframes = e.attribute("keyframes").split(";", QString::SkipEmptyParts);
+                foreach(const QString str, keyframes) {
+                    int pos = str.section(":", 0, 0).toInt();
+                    double val = str.section(":", 1, 1).toDouble();
+                    m_keyframes[pos] = val;
+                }
+                update();
+                return;
+            }
+        }
     if (!m_keyframes.isEmpty()) {
         m_keyframes.clear();
         update();
     }
 }
 
+QString ClipItem::keyframes(const int index) {
+    QString result;
+    QDomElement effect = effectAt(index);
+    QDomNodeList params = effect.elementsByTagName("parameter");
+
+    for (int i = 0; i < params.count(); i++) {
+        QDomElement e = params.item(i).toElement();
+        if (!e.isNull() && e.attribute("type") == "keyframe") {
+            result = e.attribute("keyframes");
+            break;
+        }
+    }
+    return result;
+}
+
 void ClipItem::updateKeyframeEffect() {
+    // regenerate xml parameter from the clip keyframes
     QDomElement effect = effectAt(m_selectedEffect);
+    if (effect.attribute("disabled") == "1") return;
     QDomNodeList params = effect.elementsByTagName("parameter");
 
     for (int i = 0; i < params.count(); i++) {
         QDomElement e = params.item(i).toElement();
         if (!e.isNull() && e.attribute("type") == "keyframe") {
-            int max = e.attribute("max").toInt();
-            int min = e.attribute("min").toInt();
-            int def = e.attribute("default").toInt();
-            int factor = e.attribute("factor").toInt();
-            if (factor == 0) factor = 1;
             QString keyframes;
-
             if (m_keyframes.count() > 1) {
-                QMap<int, int>::const_iterator i = m_keyframes.constBegin();
+                QMap<int, double>::const_iterator i = m_keyframes.constBegin();
                 double x1;
                 double y1;
                 while (i != m_keyframes.constEnd()) {
                     keyframes.append(QString::number(i.key()) + ":" + QString::number(i.value()) + ";");
-                    /*x1 = m_cropDuration.frames(m_fps) * i.key() / 100.0;
-                    y1 = (min + i.value() * (max - min) / 100.0) / factor;
-                    keyframes.append(QString::number(x1) + ":" + QString::number(y1) + ";");*/
                     ++i;
                 }
             }
             // Effect has a keyframe type parameter, we need to set the values
-            kDebug() << ":::::::::::::::   SETTING EFFECT KEYFRAMES: " << keyframes;
+            //kDebug() << ":::::::::::::::   SETTING EFFECT KEYFRAMES: " << keyframes;
             e.setAttribute("keyframes", keyframes);
             break;
         }
@@ -206,7 +266,7 @@ void ClipItem::refreshClip() {
 }
 
 void ClipItem::slotFetchThumbs() {
-    m_thumbsRequested += 2;
+    m_thumbsRequested = 2;
     emit getThumb((int)m_cropStart.frames(m_fps), (int)(m_cropStart + m_cropDuration).frames(m_fps));
 }
 
@@ -221,19 +281,20 @@ void ClipItem::slotGetEndThumb() {
 }
 
 void ClipItem::slotThumbReady(int frame, QPixmap pix) {
-    if (m_thumbsRequested == 0) return;
+    //if (m_thumbsRequested == 0) return;
     if (frame == m_cropStart.frames(m_fps)) {
         m_startPix = pix;
         QRectF r = boundingRect();
         r.setRight(pix.width() + 2);
         update(r);
-    } else {
+        m_thumbsRequested--;
+    } else if (frame == (m_cropStart + m_cropDuration).frames(m_fps)) {
         m_endPix = pix;
         QRectF r = boundingRect();
         r.setLeft(r.right() - pix.width() - 2);
         update(r);
+        m_thumbsRequested--;
     }
-    m_thumbsRequested--;
 }
 
 void ClipItem::slotGotAudioData() {
@@ -300,7 +361,7 @@ void ClipItem::paint(QPainter *painter,
 
     if (startpixel < 0)
         startpixel = 0;
-    int endpixel = (int)option->exposedRect.right() - rect().x();
+    int endpixel = (int)option->exposedRect.right();
     if (endpixel < 0)
         endpixel = 0;
 
@@ -332,15 +393,15 @@ void ClipItem::paint(QPainter *painter,
     }
 
     // draw audio thumbnails
-    if (KdenliveSettings::audiothumbnails() && ((m_clipType == AV && option->exposedRect.bottom() > br.height() / 2) || m_clipType == AUDIO) && audioThumbReady) {
+    if (KdenliveSettings::audiothumbnails() && ((m_clipType == AV && option->exposedRect.bottom() > (br.y() + br.height() / 2)) || m_clipType == AUDIO) && audioThumbReady) {
 
         QPainterPath path = m_clipType == AV ? roundRectPathLower : resultClipPath;
         if (m_clipType == AV) painter->fillPath(path, QBrush(QColor(200, 200, 200, 140)));
 
-        int channels = 2;
+        int channels = baseClip()->getProperty("channels").toInt();
         if (scale != framePixelWidth)
             audioThumbCachePic.clear();
-        emit prepareAudioThumb(scale, path, startpixel, endpixel + 200);//200 more for less missing parts before repaint after scrolling
+        emit prepareAudioThumb(scale, path, startpixel, endpixel + 200, channels);//200 more for less missing parts before repaint after scrolling
         int cropLeft = (int)((m_cropStart).frames(m_fps) * scale);
         for (int startCache = startpixel - startpixel % 100; startCache < endpixel + 300;startCache += 100) {
             if (audioThumbCachePic.contains(startCache) && !audioThumbCachePic[startCache].isNull())
@@ -550,11 +611,26 @@ QList <GenTime> ClipItem::snapMarkers() const {
     return snaps;
 }
 
-void ClipItem::slotPrepareAudioThumb(double pixelForOneFrame, QPainterPath path, int startpixel, int endpixel) {
-    int channels = 2;
+QList <CommentedTime> ClipItem::commentedSnapMarkers() const {
+    QList < CommentedTime > snaps;
+    QList < CommentedTime > markers = baseClip()->commentedSnapMarkers();
+    GenTime pos;
+    double framepos;
 
-    QRectF re = path.boundingRect();
+    for (int i = 0; i < markers.size(); i++) {
+        pos = markers.at(i).time() - cropStart();
+        if (pos > GenTime()) {
+            if (pos > duration()) break;
+            else snaps.append(CommentedTime(pos + startPos(), markers.at(i).comment()));
+        }
+    }
+    return snaps;
+}
+
+void ClipItem::slotPrepareAudioThumb(double pixelForOneFrame, QPainterPath path, int startpixel, int endpixel, int channels) {
 
+    QRectF re = path.boundingRect();
+    //kDebug() << "// PREP AUDIO THMB FRMO : " << startpixel << ", to: " << endpixel;
     //if ( (!audioThumbWasDrawn || framePixelWidth!=pixelForOneFrame ) && !baseClip()->audioFrameChache.isEmpty()){
 
     for (int startCache = startpixel - startpixel % 100;startCache + 100 < endpixel ;startCache += 100) {
@@ -691,15 +767,54 @@ void ClipItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *) {
 }
 
 void ClipItem::resizeStart(int posx, double scale) {
+    const int previous = cropStart().frames(m_fps);
     AbstractClipItem::resizeStart(posx, scale);
-    if (m_hasThumbs) startThumbTimer->start(100);
+    checkEffectsKeyframesPos(previous, cropStart().frames(m_fps), true);
+    if (m_hasThumbs && KdenliveSettings::videothumbnails()) startThumbTimer->start(100);
 }
 
 void ClipItem::resizeEnd(int posx, double scale) {
+    const int previous = (cropStart() + duration()).frames(m_fps);
     AbstractClipItem::resizeEnd(posx, scale);
-    if (m_hasThumbs) endThumbTimer->start(100);
+    checkEffectsKeyframesPos(previous, (cropStart() + duration()).frames(m_fps), false);
+    if (m_hasThumbs && KdenliveSettings::videothumbnails()) endThumbTimer->start(100);
+}
+
+
+void ClipItem::checkEffectsKeyframesPos(const int previous, const int current, bool fromStart) {
+    for (int i = 0; i < m_effectList.size(); i++) {
+        QDomElement effect = m_effectList.at(i);
+        QDomNodeList params = effect.elementsByTagName("parameter");
+        for (int j = 0; j < params.count(); j++) {
+            QDomElement e = params.item(i).toElement();
+            if (e.attribute("type") == "keyframe") {
+                // parse keyframes and adjust values
+                const QStringList keyframes = e.attribute("keyframes").split(";", QString::SkipEmptyParts);
+                QMap <int, double> kfr;
+                foreach(const QString str, keyframes) {
+                    int pos = str.section(":", 0, 0).toInt();
+                    double val = str.section(":", 1, 1).toDouble();
+                    if (pos == previous) kfr[current] = val;
+                    else {
+                        if (fromStart && pos >= current) kfr[pos] = val;
+                        else if (!fromStart && pos <= current) kfr[pos] = val;
+                    }
+                }
+                QString newkfr;
+                QMap<int, double>::const_iterator k = kfr.constBegin();
+                while (k != kfr.constEnd()) {
+                    newkfr.append(QString::number(k.key()) + ":" + QString::number(k.value()) + ";");
+                    ++k;
+                }
+                e.setAttribute("keyframes", newkfr);
+                break;
+            }
+        }
+    }
+    if (m_selectedEffect >= 0) setSelectedEffect(m_selectedEffect);
 }
 
+
 // virtual
 /*void ClipItem::mouseMoveEvent(QGraphicsSceneMouseEvent * event) {
 }*/
@@ -753,7 +868,18 @@ QMap <QString, QString> ClipItem::addEffect(QDomElement effect, bool animate) {
     for (int i = 0; i < params.count(); i++) {
         QDomElement e = params.item(i).toElement();
         if (!e.isNull()) {
-            if (e.attribute("factor").isEmpty()) {
+            if (e.attribute("type") == "keyframe") {
+                effectParams["keyframes"] = e.attribute("keyframes");
+                effectParams["min"] = e.attribute("min");
+                effectParams["max"] = e.attribute("max");
+                effectParams["factor"] = e.attribute("factor", "1");
+                effectParams["starttag"] = e.attribute("starttag", "start");
+                effectParams["endtag"] = e.attribute("endtag", "end");
+            }
+
+            double f = e.attribute("factor", "1").toDouble();
+
+            if (f == 1) {
                 effectParams[e.attribute("name")] = e.attribute("value");
                 // check if it is a fade effect
                 if (effectId == "fadein") {
@@ -766,7 +892,7 @@ QMap <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() / e.attribute("factor").toDouble());
+                effectParams[e.attribute("name")] =  QString::number(effectParams[e.attribute("name")].toDouble() / f);
             }
         }
     }
@@ -781,6 +907,10 @@ QMap <QString, QString> ClipItem::addEffect(QDomElement effect, bool animate) {
         r.setHeight(20);
         update(r);
     }
+    if (m_selectedEffect == -1) {
+        m_selectedEffect = 0;
+        setSelectedEffect(m_selectedEffect);
+    }
     return effectParams;
 }
 
@@ -800,9 +930,9 @@ QMap <QString, QString> ClipItem::getEffectArgs(QDomElement effect) {
             effectParams["keyframes"] = e.attribute("keyframes");
             effectParams["max"] = e.attribute("max");
             effectParams["min"] = e.attribute("min");
-            effectParams["factor"] = e.attribute("factor");
-            effectParams["starttag"] = e.attribute("starttag");
-            effectParams["endtag"] = e.attribute("endtag");
+            effectParams["factor"] = e.attribute("factor", "1");
+            effectParams["starttag"] = e.attribute("starttag", "start");
+            effectParams["endtag"] = e.attribute("endtag", "end");
         } else if (e.attribute("namedesc").contains(";")) {
             QString format = e.attribute("format");
             QStringList separators = format.split("%d", QString::SkipEmptyParts);
@@ -816,9 +946,9 @@ QMap <QString, QString> ClipItem::getEffectArgs(QDomElement effect) {
                 txtNeu << (int)(values[i+1].toDouble());
             }
             effectParams["start"] = neu;
-        } else if (!e.isNull()) {
-            if (!e.attribute("factor").isEmpty())
-                effectParams[e.attribute("name")] =  QString::number(effectParams[e.attribute("value")].toDouble() / e.attribute("factor").toDouble());
+        } else {
+            if (e.attribute("factor", "1") != "1")
+                effectParams[e.attribute("name")] =  QString::number(e.attribute("value").toDouble() / e.attribute("factor").toDouble());
             else effectParams[e.attribute("name")] = e.attribute("value");
         }
     }
@@ -845,6 +975,21 @@ void ClipItem::deleteEffect(QString index) {
     flashClip();
 }
 
+double ClipItem::speed() const {
+    return m_speed;
+}
+
+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();
+}
+
+GenTime ClipItem::maxDuration() const {
+    return m_maxDuration / m_speed;
+}
+
 //virtual
 void ClipItem::dropEvent(QGraphicsSceneDragDropEvent * event) {
     QString effects = QString(event->mimeData()->data("kdenlive/effectslist"));