+ if (m_timeLine) m_timeLine;
+}
+
+ClipItem *ClipItem::clone(ItemInfo info) const {
+ ClipItem *duplicate = new ClipItem(m_clip, info, m_fps);
+ if (info.cropStart == cropStart()) duplicate->slotSetStartThumb(m_startPix);
+ if (info.cropStart + (info.endPos - info.startPos) == m_cropStart + m_cropDuration) duplicate->slotSetEndThumb(m_endPix);
+ kDebug() << "// CLoning clip: " << (info.cropStart + (info.endPos - info.startPos)).frames(m_fps) << ", CURRENT end: " << (cropStart() + duration()).frames(m_fps);
+ duplicate->setEffectList(m_effectList.clone());
+ 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::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") {
+ QString def = e.attribute("default");
+ // Effect has a keyframe type parameter, we need to set the values
+ if (e.attribute("keyframes").isEmpty()) {
+ 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;
+ }
+ }
+ }
+
+ 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) {
+ 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
+ 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;
+ }
+ 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") {
+ QString keyframes;
+ if (m_keyframes.count() > 1) {
+ 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()) + ";");
+ ++i;
+ }
+ }
+ // Effect has a keyframe type parameter, we need to set the values
+ //kDebug() << "::::::::::::::: SETTING EFFECT KEYFRAMES: " << keyframes;
+ e.setAttribute("keyframes", keyframes);
+ break;
+ }
+ }
+}
+
+QDomElement ClipItem::selectedEffect() {
+ if (m_selectedEffect == -1 || m_effectList.isEmpty()) return QDomElement();
+ return effectAt(m_selectedEffect);