]> git.sesse.net Git - kdenlive/blobdiff - src/docclipbase.cpp
Fix indent
[kdenlive] / src / docclipbase.cpp
index 740628ef4e9bedd865e12e11b112446dca4ae3ff..5065936daee52f234d2b83d873fded9350392833 100644 (file)
 
 #include <KIO/NetAccess>
 #include <KStandardDirs>
+#include <KApplication>
 #include <KDebug>
 
 #include <QCryptographicHash>
 #include <QtConcurrentRun>
 
 #include <cstdio>
+#include <kmessagebox.h>
 
 DocClipBase::DocClipBase(ClipManager *clipManager, QDomElement xml, const QString &id) :
-        QObject(),
-        m_audioFrameCache(),
-        m_refcount(0),
-        m_baseTrackProducers(),
-        m_audioTrackProducers(),
-        m_videoOnlyProducer(NULL),
-        m_snapMarkers(QList < CommentedTime >()),
-        m_duration(),
-        m_audioTimer(NULL),
-        m_thumbProd(NULL),
-        m_audioThumbCreated(false),
-        m_id(id),
-        m_placeHolder(xml.hasAttribute("placeholder")),
-        m_properties(),
-        m_abortProxy(false)
+    QObject(),
+    lastSeekPosition(0),
+    audioFrameCache(),
+    m_refcount(0),
+    m_baseTrackProducers(),
+    m_videoTrackProducers(),
+    m_audioTrackProducers(),
+    m_snapMarkers(QList < CommentedTime >()),
+    m_duration(),
+    m_thumbProd(NULL),
+    m_audioThumbCreated(false),
+    m_id(id),
+    m_placeHolder(xml.hasAttribute("placeholder")),
+    m_properties()
 {
     int type = xml.attribute("type").toInt();
     m_clipType = (CLIPTYPE) type;
     if (m_placeHolder) xml.removeAttribute("placeholder");
     QDomNamedNodeMap attributes = xml.attributes();
-    for (int i = 0; i < attributes.count(); i++) {
+    for (int i = 0; i < attributes.count(); ++i) {
         QString name = attributes.item(i).nodeName();
         if (name.startsWith("meta.attr.")) {
-            m_metadata.insert(name.section('.', 2, 3), attributes.item(i).nodeValue());
+            m_metadata.insert(name.section('.', 2), QStringList() << attributes.item(i).nodeValue());
         } else m_properties.insert(name, attributes.item(i).nodeValue());
     }
-
+    QDomNodeList metas = xml.elementsByTagName("metaproperty");
+    for (int i = 0; i < metas.count(); ++i) {
+        QDomElement e = metas.item(i).toElement();
+        if (!e.isNull()) {
+            m_metadata.insert(e.attribute("name").section('.', 2), QStringList() << e.firstChild().nodeValue() << e.attribute("tool"));
+        }
+    }
     if (xml.hasAttribute("cutzones")) {
-        QStringList cuts = xml.attribute("cutzones").split(";", QString::SkipEmptyParts);
-        for (int i = 0; i < cuts.count(); i++) {
+        QStringList cuts = xml.attribute("cutzones").split(';', QString::SkipEmptyParts);
+        for (int i = 0; i < cuts.count(); ++i) {
             QString z = cuts.at(i);
             addCutZone(z.section('-', 0, 0).toInt(), z.section('-', 1, 1).toInt(), z.section('-', 2, 2));
         }
     }
 
+    if (xml.hasAttribute("analysisdata")) {
+        QStringList adata = xml.attribute("analysisdata").split('#', QString::SkipEmptyParts);
+        for (int i = 0; i < adata.count(); ++i)
+            m_analysisdata.insert(adata.at(i).section('?', 0, 0), adata.at(i).section('?', 1, 1));
+    }
+
     KUrl url = KUrl(xml.attribute("resource"));
     if (!m_properties.contains("file_hash") && !url.isEmpty()) getFileHash(url.path());
 
@@ -83,49 +96,36 @@ DocClipBase::DocClipBase(ClipManager *clipManager, QDomElement xml, const QStrin
     } else {
         int out = xml.attribute("out").toInt();
         int in = xml.attribute("in").toInt();
-        setDuration(GenTime(out - in, KdenliveSettings::project_fps()));
+        if (out > in) setDuration(GenTime(out - in + 1, KdenliveSettings::project_fps()));
     }
 
     if (!m_properties.contains("name")) m_properties.insert("name", url.fileName());
 
-    //if (!url.isEmpty() && QFile::exists(url.path()))
-    {
-        m_thumbProd = new KThumb(clipManager, url, m_id, m_properties.value("file_hash"));
-        if (m_clipType == AV || m_clipType == AUDIO || m_clipType == PLAYLIST) slotCreateAudioTimer();
-    }
-    //kDebug() << "type is video" << (m_clipType == AV) << " " << m_clipType;
+    m_thumbProd = new KThumb(clipManager, url, m_id, m_properties.value("file_hash"));
+    
+    // Setup timer to trigger audio thumbs creation
+    m_audioTimer.setSingleShot(true);
+    m_audioTimer.setInterval(800);
+    connect(&m_audioTimer, SIGNAL(timeout()), m_thumbProd, SLOT(slotCreateAudioThumbs()));
+    
 }
 
-/*DocClipBase & DocClipBase::operator=(const DocClipBase & clip) {
-    DocClipBase::operator=(clip);
-    m_id = clip.getId();
-    m_clipType = clip.clipType();
-    m_name = clip.name();
-    m_duration = clip.duration();
-    m_audioThumbCreated = clip.audioThumbCreated();
-    m_properties = clip.properties();
-    return *this;
-}*/
-
 DocClipBase::~DocClipBase()
 {
-    kDebug() << "CLIP " << m_id << " DELETED******************************";
+    m_audioTimer.stop();
     delete m_thumbProd;
-    if (m_audioTimer) {
-        m_audioTimer->stop();
-        delete m_audioTimer;
-    }
-    /*kDebug() <<" * * *CNT "<<m_baseTrackProducers.count();
-    if (m_baseTrackProducers.count() > 0) kDebug()<<"YOYO: "<<m_baseTrackProducers.at(0)->get_out()<<", CUT: "<<m_baseTrackProducers.at(0)->is_cut();*/
+    m_thumbProd = NULL;
+    qDeleteAll(m_toDeleteProducers);
+    m_toDeleteProducers.clear();
     qDeleteAll(m_baseTrackProducers);
     m_baseTrackProducers.clear();
     qDeleteAll(m_audioTrackProducers);
     m_audioTrackProducers.clear();
-    delete m_videoOnlyProducer;
-    m_videoOnlyProducer = NULL;
+    qDeleteAll(m_videoTrackProducers);
+    m_videoTrackProducers.clear();
 }
 
-void DocClipBase::setZone(QPoint zone)
+void DocClipBase::setZone(const QPoint &zone)
 {
     m_properties.insert("zone_in", QString::number(zone.x()));
     m_properties.insert("zone_out", QString::number(zone.y()));
@@ -137,23 +137,16 @@ QPoint DocClipBase::zone() const
     return zone;
 }
 
-void DocClipBase::slotCreateAudioTimer()
-{
-    connect(m_thumbProd, SIGNAL(audioThumbReady(QMap <int, QMap <int, QByteArray> >)), this , SLOT(updateAudioThumbnail(QMap <int, QMap <int, QByteArray> >)));
-    m_audioTimer = new QTimer(this);
-    connect(m_audioTimer, SIGNAL(timeout()), this, SLOT(slotGetAudioThumbs()));
-}
 
-void DocClipBase::askForAudioThumbs()
+bool DocClipBase::hasAudioThumb() const
 {
-    if (m_thumbProd && m_audioTimer) m_thumbProd->askForAudioThumbs(getId());
+    if (m_clipType == AUDIO || m_clipType == AV || m_clipType == PLAYLIST) return true;
+    return false;
 }
 
 void DocClipBase::slotClearAudioCache()
 {
-    if (m_thumbProd) m_thumbProd->stopAudioThumbs();
-    if (m_audioTimer != NULL) m_audioTimer->stop();
-    m_audioFrameCache.clear();
+    audioFrameCache.clear();
     m_audioThumbCreated = false;
 }
 
@@ -182,11 +175,6 @@ const QString &DocClipBase::getId() const
     return m_id;
 }
 
-void DocClipBase::setId(const QString &newId)
-{
-    m_id = newId;
-}
-
 const CLIPTYPE & DocClipBase::clipType() const
 {
     return m_clipType;
@@ -195,10 +183,7 @@ const CLIPTYPE & DocClipBase::clipType() const
 void DocClipBase::setClipType(CLIPTYPE type)
 {
     m_clipType = type;
-
     m_properties.insert("type", QString::number((int) type));
-    if (m_thumbProd && m_audioTimer == NULL && (m_clipType == AV || m_clipType == AUDIO || m_clipType == PLAYLIST))
-        slotCreateAudioTimer();
 }
 
 KUrl DocClipBase::fileURL() const
@@ -228,12 +213,12 @@ bool DocClipBase::isTransparent() const
     return (m_properties.value("transparency") == "1");
 }
 
-const QString DocClipBase::getProperty(const QString prop) const
+const QString DocClipBase::getProperty(const QString &prop) const
 {
     return m_properties.value(prop);
 }
 
-void DocClipBase::setDuration(GenTime dur)
+void DocClipBase::setDuration(const GenTime &dur)
 {
     m_duration = dur;
     m_properties.insert("duration", QString::number((int) dur.frames(KdenliveSettings::project_fps())));
@@ -265,7 +250,7 @@ qulonglong DocClipBase::fileSize() const
 }
 
 // virtual
-QDomElement DocClipBase::toXML() const
+QDomElement DocClipBase::toXML(bool hideTemporaryProperties) const
 {
     QDomDocument doc;
     QDomElement clip = doc.createElement("producer");
@@ -273,42 +258,108 @@ QDomElement DocClipBase::toXML() const
     QMapIterator<QString, QString> i(m_properties);
     while (i.hasNext()) {
         i.next();
+        if (hideTemporaryProperties && i.key().startsWith('_')) continue;
         if (!i.value().isEmpty()) clip.setAttribute(i.key(), i.value());
     }
+
+    QMapIterator<QString, QStringList> j(m_metadata);
+    // Metadata name can have special chars so we cannot pass it as simple attribute
+    while (j.hasNext()) {
+        j.next();
+        if (!j.value().isEmpty()) {
+            QDomElement property = doc.createElement("metaproperty");
+            property.setAttribute("name", "meta.attr." + j.key());
+            QStringList values = j.value();
+            QDomText value = doc.createTextNode(values.at(0));
+            if (values.count() > 1) property.setAttribute("tool", values.at(1));
+            property.appendChild(value);
+            clip.appendChild(property);
+        }
+    }
     doc.appendChild(clip);
     if (!m_cutZones.isEmpty()) {
         QStringList cuts;
-        for (int i = 0; i < m_cutZones.size(); i++) {
+        for (int i = 0; i < m_cutZones.size(); ++i) {
             CutZoneInfo info = m_cutZones.at(i);
             cuts << QString::number(info.zone.x()) + "-" + QString::number(info.zone.y()) + "-" + info.description;
         }
         clip.setAttribute("cutzones", cuts.join(";"));
     }
+    QString adata;
+    if (!m_analysisdata.isEmpty()) {
+        QMapIterator<QString, QString> i(m_analysisdata);
+        while (i.hasNext()) {
+            i.next();
+            //WARNING: a ? and # separator is not a good idea
+            adata.append(i.key() + "?" + i.value() + "#");
+        }
+    }
+    clip.setAttribute("analysisdata", adata);
     //kDebug() << "/// CLIP XML: " << doc.toString();
     return doc.documentElement();
 }
 
-
-void DocClipBase::setAudioThumbCreated(bool isDone)
+const QString DocClipBase::shortInfo() const
 {
-    m_audioThumbCreated = isDone;
-}
 
-
-void DocClipBase::setThumbnail(const QPixmap & pixmap)
-{
-    m_thumbnail = pixmap;
+    QString info;
+    if (m_clipType == AV || m_clipType == VIDEO || m_clipType == IMAGE || m_clipType == PLAYLIST) {
+        info = m_properties.value("frame_size") + " ";
+        if (m_properties.contains("fps")) {
+            info.append(i18n("%1 fps", m_properties.value("fps").left(5)));
+        }
+        if (!info.simplified().isEmpty()) info.prepend(" - ");
+    }
+    else if (m_clipType == AUDIO) {
+        info = " - " + m_properties.value("frequency") + i18n("Hz");
+    }
+    QString tip = "<b>";
+    switch (m_clipType) {
+    case AUDIO:
+        tip.append(i18n("Audio clip") + "</b>" + info + "<br />" + fileURL().path());
+        break;
+    case VIDEO:
+        tip.append(i18n("Mute video clip") + "</b>" + info + "<br />" + fileURL().path());
+        break;
+    case AV:
+        tip.append(i18n("Video clip") + "</b>" + info + "<br />" + fileURL().path());
+        break;
+    case COLOR:
+        tip.append(i18n("Color clip"));
+        break;
+    case IMAGE:
+        tip.append(i18n("Image clip") + "</b>" + info + "<br />" + fileURL().path());
+        break;
+    case TEXT:
+        if (!fileURL().isEmpty() && getProperty("xmldata").isEmpty()) tip.append(i18n("Template text clip") + "</b><br />" + fileURL().path());
+        else tip.append(i18n("Text clip") + "</b><br />" + fileURL().path());
+        break;
+    case SLIDESHOW:
+        tip.append(i18n("Slideshow clip") + "</b><br />" + fileURL().directory());
+        break;
+    case VIRTUAL:
+        tip.append(i18n("Virtual clip"));
+        break;
+    case PLAYLIST:
+        tip.append(i18n("Playlist clip") + "</b>" + info + "<br />" + fileURL().path());
+        break;
+    default:
+        tip.append(i18n("Unknown clip"));
+        break;
+    }
+    return tip;
 }
 
-const QPixmap & DocClipBase::thumbnail() const
+
+void DocClipBase::setAudioThumbCreated(bool isDone)
 {
-    return m_thumbnail;
+    m_audioThumbCreated = isDone;
 }
 
-void DocClipBase::updateAudioThumbnail(QMap<int, QMap<int, QByteArray> > data)
+void DocClipBase::updateAudioThumbnail(const audioByteArray& data)
 {
     //kDebug() << "CLIPBASE RECIEDVED AUDIO DATA*********************************************";
-    m_audioFrameCache = data;
+    audioFrameCache = data;
     m_audioThumbCreated = true;
     emit gotAudioData();
 }
@@ -316,7 +367,6 @@ void DocClipBase::updateAudioThumbnail(QMap<int, QMap<int, QByteArray> > data)
 QList < GenTime > DocClipBase::snapMarkers() const
 {
     QList < GenTime > markers;
-
     for (int count = 0; count < m_snapMarkers.count(); ++count) {
         markers.append(m_snapMarkers.at(count).time());
     }
@@ -330,25 +380,24 @@ QList < CommentedTime > DocClipBase::commentedSnapMarkers() const
 }
 
 
-void DocClipBase::addSnapMarker(const GenTime & time, QString comment)
+void DocClipBase::addSnapMarker(const CommentedTime &marker)
 {
     QList < CommentedTime >::Iterator it = m_snapMarkers.begin();
     for (it = m_snapMarkers.begin(); it != m_snapMarkers.end(); ++it) {
-        if ((*it).time() >= time)
+        if ((*it).time() >= marker.time())
             break;
     }
 
-    if ((it != m_snapMarkers.end()) && ((*it).time() == time)) {
-        (*it).setComment(comment);
+    if ((it != m_snapMarkers.end()) && ((*it).time() == marker.time())) {
+        (*it).setComment(marker.comment());
+        (*it).setMarkerType(marker.markerType());
         //kError() << "trying to add Snap Marker that already exists, this will cause inconsistancies with undo/redo";
     } else {
-        CommentedTime t(time, comment);
-        m_snapMarkers.insert(it, t);
+        m_snapMarkers.insert(it, marker);
     }
-
 }
 
-void DocClipBase::editSnapMarker(const GenTime & time, QString comment)
+void DocClipBase::editSnapMarker(const GenTime & time, const QString &comment)
 {
     QList < CommentedTime >::Iterator it;
     for (it = m_snapMarkers.begin(); it != m_snapMarkers.end(); ++it) {
@@ -418,10 +467,9 @@ GenTime DocClipBase::findNextSnapMarker(const GenTime & currTime)
     return duration();
 }
 
-QString DocClipBase::markerComment(GenTime t)
+QString DocClipBase::markerComment(const GenTime &t) const
 {
-    QList < CommentedTime >::Iterator itt = m_snapMarkers.begin();
-
+    QList < CommentedTime >::ConstIterator itt = m_snapMarkers.begin();
     while (itt != m_snapMarkers.end()) {
         if ((*itt).time() == t)
             return (*itt).comment();
@@ -430,51 +478,113 @@ QString DocClipBase::markerComment(GenTime t)
     return QString();
 }
 
-void DocClipBase::clearProducers()
+CommentedTime DocClipBase::markerAt(const GenTime &t) const
 {
-    m_baseTrackProducers.clear();
+    QList < CommentedTime >::ConstIterator itt = m_snapMarkers.begin();
+    while (itt != m_snapMarkers.end()) {
+        if ((*itt).time() == t)
+            return (*itt);
+        ++itt;
+    }
+    return CommentedTime();
 }
 
-void DocClipBase::deleteProducers(bool clearThumbCreator)
+void DocClipBase::clearThumbProducer()
 {
-    kDebug() << "// CLIP KILL PRODS ct: " << m_baseTrackProducers.count();
-    if (clearThumbCreator && m_thumbProd) m_thumbProd->clearProducer();
-    /*kDebug()<<"// CLIP KILL PRODS ct: "<<m_baseTrackProducers.count();
-    int max = m_baseTrackProducers.count();
-    for (int i = 0; i < max; i++) {
-        kDebug()<<"// CLIP KILL PROD "<<i;
-    Mlt::Producer *p = m_baseTrackProducers.takeAt(i);
-    if (p != NULL) {
-     delete p;
-     p = NULL;
-    }
-    m_baseTrackProducers.insert(i, NULL);
-    }*/
+    if (m_thumbProd) m_thumbProd->clearProducer();
+}
 
-    delete m_videoOnlyProducer;
-    m_videoOnlyProducer = NULL;
+void DocClipBase::reloadThumbProducer()
+{
+    if (m_thumbProd && !m_thumbProd->hasProducer())
+        m_thumbProd->setProducer(getProducer());
+}
 
-    qDeleteAll(m_baseTrackProducers);
+void DocClipBase::deleteProducers()
+{
+    if (m_thumbProd) m_thumbProd->clearProducer();
+    
+    if (numReferences() > 0 && (!m_baseTrackProducers.isEmpty() || !m_videoTrackProducers.isEmpty() || !m_audioTrackProducers.isEmpty())) {
+        // Clip is used in timeline, delay producers deletion
+        for (int i = 0; i < m_baseTrackProducers.count(); ++i) {
+            m_toDeleteProducers.append(m_baseTrackProducers.at(i));
+        }
+        for (int i = 0; i < m_videoTrackProducers.count(); ++i) {
+            m_toDeleteProducers.append(m_videoTrackProducers.at(i));
+        }
+        for (int i = 0; i < m_audioTrackProducers.count(); ++i) {
+            m_toDeleteProducers.append(m_audioTrackProducers.at(i));
+        }
+    }
+    else {
+        qDeleteAll(m_baseTrackProducers);
+        qDeleteAll(m_videoTrackProducers);
+        qDeleteAll(m_audioTrackProducers);
+        m_replaceMutex.unlock();
+    }
     m_baseTrackProducers.clear();
-    qDeleteAll(m_audioTrackProducers);
+    m_videoTrackProducers.clear();
     m_audioTrackProducers.clear();
 }
 
+void DocClipBase::cleanupProducers()
+{
+    /*
+    int ct = 0;
+    kDebug()<<"----------------------------------------------------------------------------------";
+    for (int i = 0; i < m_toDeleteProducers.count(); ++i) {
+        if (m_toDeleteProducers.at(i) != NULL) {
+            Mlt::Properties props(m_toDeleteProducers.at(i)->get_properties());
+            if (props.ref_count() > 2) {
+                kDebug()<<"PRODUCER: "<<i<<", COUNTS: "<<props.ref_count();
+                //exit(1);
+            }
+            ct++;
+        }
+    }*/
+
+    if (!isClean()) {
+        qDeleteAll(m_toDeleteProducers);
+        m_toDeleteProducers.clear();
+        m_replaceMutex.unlock();
+    }
+}
+
+bool DocClipBase::isClean() const
+{
+    return m_toDeleteProducers.isEmpty();
+}
+
 void DocClipBase::setValid()
 {
     m_placeHolder = false;
 }
 
-void DocClipBase::setProducer(Mlt::Producer *producer, bool reset)
+void DocClipBase::setProducer(Mlt::Producer *producer, bool reset, bool readPropertiesFromProducer)
 {
-    if (producer == NULL || (m_placeHolder && !reset)) return;
-    if (m_thumbProd && (reset || !m_thumbProd->hasProducer())) m_thumbProd->setProducer(producer);
+    if (producer == NULL) return;
     if (reset) {
-        // Clear all previous producers
-        kDebug() << "/+++++++++++++++   DELETE ALL PRODS " << producer->get("id");
-        deleteProducers(false);
+        QMutexLocker locker(&m_producerMutex);
+        m_replaceMutex.lock();
+        deleteProducers();
     }
     QString id = producer->get("id");
+    if (m_placeHolder || !producer->is_valid()) {
+        char *tmp = qstrdup(i18n("Missing clip").toUtf8().constData());
+        producer->set("markup", tmp);
+        producer->set("bgcolour", "0xff0000ff");
+        producer->set("pad", "10");
+        delete[] tmp;
+    }
+    else if (m_thumbProd && !m_thumbProd->hasProducer()) {
+        if (m_clipType != AUDIO) {
+            if (!id.endsWith("_audio"))
+                m_thumbProd->setProducer(producer);
+        }
+        else m_thumbProd->setProducer(producer);
+        getAudioThumbs();
+    }
+    bool updated = false;
     if (id.contains('_')) {
         // this is a subtrack producer, insert it at correct place
         id = id.section('_', 1);
@@ -485,10 +595,26 @@ void DocClipBase::setProducer(Mlt::Producer *producer, bool reset)
                     m_audioTrackProducers.append(NULL);
                 }
             }
-            if (m_audioTrackProducers.at(pos) == NULL) m_audioTrackProducers[pos] = producer;
+            if (m_audioTrackProducers.at(pos) == NULL) {
+                m_audioTrackProducers[pos] = producer;
+                updated = true;
+            }
+            else delete producer;
             return;
         } else if (id.endsWith("video")) {
-            m_videoOnlyProducer = producer;
+            int pos = 0;
+            // Keep compatibility with older projects where video only producers were not track specific
+            if (id.contains('_')) pos = id.section('_', 0, 0).toInt();
+            if (pos >= m_videoTrackProducers.count()) {
+                while (m_videoTrackProducers.count() - 1 < pos) {
+                    m_videoTrackProducers.append(NULL);
+                }
+            }
+            if (m_videoTrackProducers.at(pos) == NULL) {
+                m_videoTrackProducers[pos] = producer;
+                updated = true;
+            }
+            else delete producer;
             return;
         }
         int pos = id.toInt();
@@ -497,13 +623,24 @@ void DocClipBase::setProducer(Mlt::Producer *producer, bool reset)
                 m_baseTrackProducers.append(NULL);
             }
         }
-        if (m_baseTrackProducers.at(pos) == NULL) m_baseTrackProducers[pos] = producer;
+        if (m_baseTrackProducers.at(pos) == NULL) {
+            m_baseTrackProducers[pos] = producer;
+            updated = true;
+        }
+        else delete producer;
     } else {
-        if (m_baseTrackProducers.isEmpty()) m_baseTrackProducers.append(producer);
-        else if (m_baseTrackProducers.at(0) == NULL) m_baseTrackProducers[0] = producer;
+        if (m_baseTrackProducers.isEmpty()) {
+            m_baseTrackProducers.append(producer);
+            updated = true;
+        }
+        else if (m_baseTrackProducers.at(0) == NULL) {
+            m_baseTrackProducers[0] = producer;
+            updated = true;
+        }
+        else delete producer;
     }
-    //m_clipProducer = producer;
-    //m_clipProducer->set("transparency", m_properties.value("transparency").toInt());
+    if (updated && readPropertiesFromProducer && (m_clipType != COLOR && m_clipType != IMAGE && m_clipType != TEXT))
+        setDuration(GenTime(producer->get_length(), KdenliveSettings::project_fps()));
 }
 
 static double getPixelAspect(QMap<QString, QString>& props) {
@@ -512,69 +649,147 @@ static double getPixelAspect(QMap<QString, QString>& props) {
     int aspectNumerator = props.value("force_aspect_num").toInt();
     int aspectDenominator = props.value("force_aspect_den").toInt();
     if (aspectDenominator != 0 && width != 0)
-        return double(height) * aspectNumerator / aspectDenominator / width;    
+        return double(height) * aspectNumerator / aspectDenominator / width;
     else
         return 1.0;
 }
 
 Mlt::Producer *DocClipBase::audioProducer(int track)
 {
+    QMutexLocker locker(&m_producerMutex);
     if (m_audioTrackProducers.count() <= track) {
         while (m_audioTrackProducers.count() - 1 < track) {
             m_audioTrackProducers.append(NULL);
         }
     }
     if (m_audioTrackProducers.at(track) == NULL) {
-        Mlt::Producer *base = producer();
-        m_audioTrackProducers[track] = new Mlt::Producer(*(base->profile()), base->get("resource"));
-        if (m_properties.contains("force_aspect_num") && m_properties.contains("force_aspect_den") && m_properties.contains("frame_size"))
-            m_audioTrackProducers.at(track)->set("force_aspect_ratio", getPixelAspect(m_properties));
-        if (m_properties.contains("force_fps")) m_audioTrackProducers.at(track)->set("force_fps", m_properties.value("force_fps").toDouble());
-        if (m_properties.contains("force_progressive")) m_audioTrackProducers.at(track)->set("force_progressive", m_properties.value("force_progressive").toInt());
-        if (m_properties.contains("force_tff")) m_audioTrackProducers.at(track)->set("force_tff", m_properties.value("force_tff").toInt());
-        if (m_properties.contains("threads")) m_audioTrackProducers.at(track)->set("threads", m_properties.value("threads").toInt());
-        m_audioTrackProducers.at(track)->set("video_index", -1);
-        if (m_properties.contains("audio_index")) m_audioTrackProducers.at(track)->set("audio_index", m_properties.value("audio_index").toInt());
-        m_audioTrackProducers.at(track)->set("id", QString(getId() + '_' + QString::number(track) + "_audio").toUtf8().data());
-        if (m_properties.contains("force_colorspace")) m_audioTrackProducers.at(track)->set("force_colorspace", m_properties.value("force_colorspace").toInt());
-        if (m_properties.contains("full_luma")) m_audioTrackProducers.at(track)->set("set.force_full_luma", m_properties.value("full_luma").toInt());
+        int i;
+        for (i = 0; i < m_audioTrackProducers.count(); ++i)
+            if (m_audioTrackProducers.at(i) != NULL) break;
+        Mlt::Producer *base;
+        if (i >= m_audioTrackProducers.count()) {
+            // Could not find a valid producer for that clip
+            locker.unlock();
+            base = getProducer();
+            if (base == NULL) {
+                return NULL;
+            }
+            locker.relock();
+        }
+        else base = m_audioTrackProducers.at(i);
+        m_audioTrackProducers[track] = cloneProducer(base);
+        adjustProducerProperties(m_audioTrackProducers.at(track), QString(getId() + '_' + QString::number(track) + "_audio"), false, true);
     }
     return m_audioTrackProducers.at(track);
 }
 
-Mlt::Producer *DocClipBase::videoProducer()
+
+void DocClipBase::adjustProducerProperties(Mlt::Producer *prod, const QString &id, bool mute, bool blind)
+{
+    if (m_properties.contains("force_aspect_num") && m_properties.contains("force_aspect_den") && m_properties.contains("frame_size"))
+        prod->set("force_aspect_ratio", getPixelAspect(m_properties));
+    if (m_properties.contains("force_fps")) prod->set("force_fps", m_properties.value("force_fps").toDouble());
+    if (m_properties.contains("force_progressive")) prod->set("force_progressive", m_properties.value("force_progressive").toInt());
+    if (m_properties.contains("force_tff")) prod->set("force_tff", m_properties.value("force_tff").toInt());
+    if (m_properties.contains("threads")) prod->set("threads", m_properties.value("threads").toInt());
+    if (mute) prod->set("audio_index", -1);
+    else if (m_properties.contains("audio_index")) prod->set("audio_index", m_properties.value("audio_index").toInt());
+    if (blind) prod->set("video_index", -1);
+    else if (m_properties.contains("video_index")) prod->set("video_index", m_properties.value("video_index").toInt());
+    prod->set("id", id.toUtf8().constData());
+    if (m_properties.contains("force_colorspace")) prod->set("force_colorspace", m_properties.value("force_colorspace").toInt());
+    if (m_properties.contains("full_luma")) prod->set("set.force_full_luma", m_properties.value("full_luma").toInt());
+    if (m_properties.contains("proxy_out")) {
+        // We have a proxy clip, make sure the proxy has same duration as original
+        prod->set("length", m_properties.value("duration").toInt());
+        prod->set("out", m_properties.value("proxy_out").toInt());
+    }
+
+}
+
+Mlt::Producer *DocClipBase::videoProducer(int track)
 {
-    if (m_videoOnlyProducer == NULL) {
+    QMutexLocker locker(&m_producerMutex);
+    if (m_videoTrackProducers.count() <= track) {
+        while (m_videoTrackProducers.count() - 1 < track) {
+            m_videoTrackProducers.append(NULL);
+        }
+    }
+    if (m_videoTrackProducers.at(track) == NULL) {
         int i;
-        for (i = 0; i < m_baseTrackProducers.count(); i++)
-            if (m_baseTrackProducers.at(i) != NULL) break;
-        if (i >= m_baseTrackProducers.count()) return NULL;
-        m_videoOnlyProducer = new Mlt::Producer(*m_baseTrackProducers.at(i)->profile(), m_baseTrackProducers.at(i)->get("resource"));
-        if (m_properties.contains("force_aspect_num") && m_properties.contains("force_aspect_den") && m_properties.contains("frame_size"))
-            m_videoOnlyProducer->set("force_aspect_ratio", getPixelAspect(m_properties));
-        if (m_properties.contains("force_fps")) m_videoOnlyProducer->set("force_fps", m_properties.value("force_fps").toDouble());
-        if (m_properties.contains("force_progressive")) m_videoOnlyProducer->set("force_progressive", m_properties.value("force_progressive").toInt());
-        if (m_properties.contains("force_tff")) m_videoOnlyProducer->set("force_tff", m_properties.value("force_tff").toInt());
-        if (m_properties.contains("threads")) m_videoOnlyProducer->set("threads", m_properties.value("threads").toInt());
-        m_videoOnlyProducer->set("audio_index", -1);
-        if (m_properties.contains("video_index")) m_videoOnlyProducer->set("video_index", m_properties.value("video_index").toInt());
-        m_videoOnlyProducer->set("id", QString(getId() + "_video").toUtf8().data());
-        if (m_properties.contains("force_colorspace")) m_videoOnlyProducer->set("force_colorspace", m_properties.value("force_colorspace").toInt());
-        if (m_properties.contains("full_luma")) m_videoOnlyProducer->set("set.force_full_luma", m_properties.value("full_luma").toInt());
-    }
-    return m_videoOnlyProducer;
-}
-
-Mlt::Producer *DocClipBase::producer(int track)
-{
-    /*for (int i = 0; i < m_baseTrackProducers.count(); i++) {
-        if (m_baseTrackProducers.at(i)) kDebug() << "// PROD: " << i << ", ID: " << m_baseTrackProducers.at(i)->get("id");
-    }*/
-    if (track == -1 || (m_clipType != AUDIO && m_clipType != AV)) {
-        if (m_baseTrackProducers.count() == 0) return NULL;
-        for (int i = 0; i < m_baseTrackProducers.count(); i++) {
-            if (m_baseTrackProducers.at(i) != NULL)
+        for (i = 0; i < m_videoTrackProducers.count(); ++i)
+            if (m_videoTrackProducers.at(i) != NULL) break;
+        Mlt::Producer *base;
+        if (i >= m_videoTrackProducers.count()) {
+            // Could not find a valid producer for that clip
+            locker.unlock();
+            base = getProducer();
+            if (base == NULL) {
+                return NULL;
+            }
+            locker.relock();
+        }
+        else base = m_videoTrackProducers.at(i);
+        m_videoTrackProducers[track] = cloneProducer(base);
+        adjustProducerProperties(m_videoTrackProducers.at(track), QString(getId() + '_' + QString::number(track) + "_video"), true, false);
+    }
+    return m_videoTrackProducers.at(track);
+}
+
+Mlt::Producer *DocClipBase::getCloneProducer()
+{
+    Mlt::Producer *source = NULL;
+    Mlt::Producer *prod = NULL;
+    if (m_clipType != AUDIO && m_clipType != AV && m_clipType != PLAYLIST) {
+        source = getProducer();
+        if (!source) return NULL;
+    }
+    if (m_clipType == COLOR) {
+        prod = new Mlt::Producer(*(source->profile()), 0, QString("colour:" + QString(source->get("resource"))).toUtf8().constData());
+    } else if (m_clipType == TEXT) {
+        prod = new Mlt::Producer(*(source->profile()), 0, QString("kdenlivetitle:" + QString(source->get("resource"))).toUtf8().constData());
+        if (prod && prod->is_valid() && m_properties.contains("xmldata"))
+            prod->set("xmldata", m_properties.value("xmldata").toUtf8().constData());
+    }
+    if (!prod) {
+        if (!source) {
+            QMutexLocker locker(&m_producerMutex);
+            for (int i = 0; i < m_baseTrackProducers.count(); ++i) {
+                if (m_baseTrackProducers.at(i) != NULL) {
+                    source = m_baseTrackProducers.at(i);
+                    break;
+                }
+            }
+            if (!source) return NULL;
+        }
+        prod = cloneProducer(source);
+    }
+    if (prod) {
+        adjustProducerProperties(prod, getId() + "_", false, false);
+        if (!m_properties.contains("proxy_out")) {
+            // Adjust length in case...
+            if (m_properties.contains("duration")) prod->set("length", m_properties.value("duration").toInt());
+            if (m_properties.contains("out"))prod->set("out", m_properties.value("out").toInt());
+        }
+        if (m_clipType == AUDIO) {
+            prod->set("_audioclip", 1);
+        }
+    }
+    return prod;
+}
+
+
+Mlt::Producer *DocClipBase::getProducer(int track)
+{
+    QMutexLocker locker(&m_producerMutex);
+    if (track == -1 || (m_clipType != AUDIO && m_clipType != AV && m_clipType != PLAYLIST)) {
+        if (m_baseTrackProducers.count() == 0) {
+            return NULL;
+        }
+        for (int i = 0; i < m_baseTrackProducers.count(); ++i) {
+            if (m_baseTrackProducers.at(i) != NULL) {
                 return m_baseTrackProducers.at(i);
+            }
         }
         return NULL;
     }
@@ -585,36 +800,56 @@ Mlt::Producer *DocClipBase::producer(int track)
     }
     if (m_baseTrackProducers.at(track) == NULL) {
         int i;
-        for (i = 0; i < m_baseTrackProducers.count(); i++)
+        for (i = 0; i < m_baseTrackProducers.count(); ++i)
             if (m_baseTrackProducers.at(i) != NULL) break;
 
-        if (i >= m_baseTrackProducers.count()) return NULL;
-
-        if (KIO::NetAccess::exists(KUrl(m_baseTrackProducers.at(i)->get("resource")), KIO::NetAccess::SourceSide, 0))
-            m_baseTrackProducers[track] = new Mlt::Producer(*m_baseTrackProducers.at(i)->profile(), m_baseTrackProducers.at(i)->get("resource"));
-        else { // special case for placeholder clips
-            m_baseTrackProducers[track] = NULL;
+        if (i >= m_baseTrackProducers.count()) {
+            // Could not find a valid producer for that clip, check in
             return NULL;
         }
-        if (m_properties.contains("force_aspect_num") && m_properties.contains("force_aspect_den") && m_properties.contains("frame_size"))
-            m_baseTrackProducers[track]->set("force_aspect_ratio", getPixelAspect(m_properties));
-        if (m_properties.contains("force_fps")) m_baseTrackProducers[track]->set("force_fps", m_properties.value("force_fps").toDouble());
-        if (m_properties.contains("force_progressive")) m_baseTrackProducers[track]->set("force_progressive", m_properties.value("force_progressive").toInt());
-        if (m_properties.contains("force_tff")) m_baseTrackProducers[track]->set("force_tff", m_properties.value("force_tff").toInt());
-        if (m_properties.contains("threads")) m_baseTrackProducers[track]->set("threads", m_properties.value("threads").toInt());
-        if (m_properties.contains("video_index")) m_baseTrackProducers[track]->set("video_index", m_properties.value("video_index").toInt());
-        if (m_properties.contains("audio_index")) m_baseTrackProducers[track]->set("audio_index", m_properties.value("audio_index").toInt());
-        m_baseTrackProducers[track]->set("id", QString(getId() + '_' + QString::number(track)).toUtf8().data());
-
-        if (m_properties.contains("force_colorspace")) m_baseTrackProducers[track]->set("force_colorspace", m_properties.value("force_colorspace").toInt());
-        if (m_properties.contains("full_luma")) m_baseTrackProducers[track]->set("set.force_full_luma", m_properties.value("full_luma").toInt());
+        Mlt::Producer *prod = cloneProducer(m_baseTrackProducers.at(i));
+        adjustProducerProperties(prod, QString(getId() + '_' + QString::number(track)), false, false);
+        m_baseTrackProducers[track] = prod;
     }
     return m_baseTrackProducers.at(track);
 }
 
+
+Mlt::Producer *DocClipBase::cloneProducer(Mlt::Producer *source)
+{
+    Mlt::Producer *result = NULL;
+    QString url = QString::fromUtf8(source->get("resource"));
+    if (url == "<playlist>" || url == "<tractor>" || url == "<producer>") {
+        // Xml producer sometimes loses the correct url
+        url = m_properties.value("resource");
+    }
+    if (m_clipType == SLIDESHOW || KIO::NetAccess::exists(KUrl(url), KIO::NetAccess::SourceSide, 0)) {
+        result = new Mlt::Producer(*(source->profile()), url.toUtf8().constData());
+    }
+    if (result == NULL || !result->is_valid()) {
+        // placeholder clip
+        QString txt = "+" + i18n("Missing clip") + ".txt";
+        char *tmp = qstrdup(txt.toUtf8().constData());
+        result = new Mlt::Producer(*source->profile(), tmp);
+        delete[] tmp;
+        if (result == NULL || !result->is_valid())
+            result = new Mlt::Producer(*(source->profile()), "colour:red");
+        else {
+            result->set("bgcolour", "0xff0000ff");
+            result->set("pad", "10");
+        }
+        return result;
+    }
+    /*Mlt::Properties src_props(source->get_properties());
+    Mlt::Properties props(result->get_properties());
+    props.inherit(src_props);*/
+    return result;
+}
+
 void DocClipBase::setProducerProperty(const char *name, int data)
 {
-    for (int i = 0; i < m_baseTrackProducers.count(); i++) {
+    QMutexLocker locker(&m_producerMutex);
+    for (int i = 0; i < m_baseTrackProducers.count(); ++i) {
         if (m_baseTrackProducers.at(i) != NULL)
             m_baseTrackProducers[i]->set(name, data);
     }
@@ -622,7 +857,8 @@ void DocClipBase::setProducerProperty(const char *name, int data)
 
 void DocClipBase::setProducerProperty(const char *name, double data)
 {
-    for (int i = 0; i < m_baseTrackProducers.count(); i++) {
+    QMutexLocker locker(&m_producerMutex);
+    for (int i = 0; i < m_baseTrackProducers.count(); ++i) {
         if (m_baseTrackProducers.at(i) != NULL)
             m_baseTrackProducers[i]->set(name, data);
     }
@@ -630,7 +866,8 @@ void DocClipBase::setProducerProperty(const char *name, double data)
 
 void DocClipBase::setProducerProperty(const char *name, const char *data)
 {
-    for (int i = 0; i < m_baseTrackProducers.count(); i++) {
+    QMutexLocker locker(&m_producerMutex);
+    for (int i = 0; i < m_baseTrackProducers.count(); ++i) {
         if (m_baseTrackProducers.at(i) != NULL)
             m_baseTrackProducers[i]->set(name, data);
     }
@@ -638,7 +875,8 @@ void DocClipBase::setProducerProperty(const char *name, const char *data)
 
 void DocClipBase::resetProducerProperty(const char *name)
 {
-    for (int i = 0; i < m_baseTrackProducers.count(); i++) {
+    QMutexLocker locker(&m_producerMutex);
+    for (int i = 0; i < m_baseTrackProducers.count(); ++i) {
         if (m_baseTrackProducers.at(i) != NULL)
             m_baseTrackProducers[i]->set(name, (const char*) NULL);
     }
@@ -646,7 +884,7 @@ void DocClipBase::resetProducerProperty(const char *name)
 
 const char *DocClipBase::producerProperty(const char *name) const
 {
-    for (int i = 0; i < m_baseTrackProducers.count(); i++) {
+    for (int i = 0; i < m_baseTrackProducers.count(); ++i) {
         if (m_baseTrackProducers.at(i) != NULL) {
             return m_baseTrackProducers.at(i)->get(name);
         }
@@ -658,12 +896,7 @@ const char *DocClipBase::producerProperty(const char *name) const
 void DocClipBase::slotRefreshProducer()
 {
     if (m_baseTrackProducers.count() == 0) return;
-    kDebug() << "////////////   REFRESH CLIP !!!!!!!!!!!!!!!!";
     if (m_clipType == SLIDESHOW) {
-        /*Mlt::Producer producer(*(m_clipProducer->profile()), getProperty("resource").toUtf8().data());
-        delete m_clipProducer;
-        m_clipProducer = new Mlt::Producer(producer.get_producer());
-        if (!getProperty("out").isEmpty()) m_clipProducer->set_in_and_out(getProperty("in").toInt(), getProperty("out").toInt());*/
         setProducerProperty("ttl", getProperty("ttl").toInt());
         //m_clipProducer->set("id", getProperty("id"));
         if (!getProperty("animation").isEmpty()) {
@@ -795,15 +1028,27 @@ void DocClipBase::setProperties(QMap <QString, QString> properties)
     bool refreshProducer = false;
     QStringList keys;
     keys << "luma_duration" << "luma_file" << "fade" << "ttl" << "softness" << "crop" << "animation";
+    QString oldProxy = m_properties.value("proxy");
     while (i.hasNext()) {
         i.next();
         setProperty(i.key(), i.value());
         if (m_clipType == SLIDESHOW && keys.contains(i.key())) refreshProducer = true;
     }
+    if (properties.contains("proxy")) {
+        QString value = properties.value("proxy");
+        // If value is "-", that means user manually disabled proxy on this clip
+        if (value.isEmpty() || value == "-") {
+            // reset proxy
+            emit abortProxy(m_id, oldProxy);
+        }
+        else {
+            emit createProxy(m_id);
+        }
+    }
     if (refreshProducer) slotRefreshProducer();
 }
 
-void DocClipBase::setMetadata(QMap <QString, QString> properties)
+void DocClipBase::setMetadata(const QMap <QString, QString> &properties, const QString &tool)
 {
     QMapIterator<QString, QString> i(properties);
     while (i.hasNext()) {
@@ -811,12 +1056,12 @@ void DocClipBase::setMetadata(QMap <QString, QString> properties)
         if (i.value().isEmpty() && m_metadata.contains(i.key())) {
             m_metadata.remove(i.key());
         } else {
-            m_metadata.insert(i.key(), i.value());
+            m_metadata.insert(i.key(), QStringList() << i.value() << tool);
         }
     }
 }
 
-QMap <QString, QString> DocClipBase::metadata() const
+QMap <QString, QStringList> DocClipBase::metadata() const
 {
     return m_metadata;
 }
@@ -826,7 +1071,7 @@ void DocClipBase::clearProperty(const QString &key)
     m_properties.remove(key);
 }
 
-void DocClipBase::getFileHash(const QString url)
+void DocClipBase::getFileHash(const QString &url)
 {
     if (m_clipType == SLIDESHOW) return;
     QFile file(url);
@@ -864,7 +1109,11 @@ QString DocClipBase::getClipHash() const
     if (m_clipType == SLIDESHOW) hash = QCryptographicHash::hash(m_properties.value("resource").toAscii().data(), QCryptographicHash::Md5).toHex();
     else if (m_clipType == COLOR) hash = QCryptographicHash::hash(m_properties.value("colour").toAscii().data(), QCryptographicHash::Md5).toHex();
     else if (m_clipType == TEXT) hash = QCryptographicHash::hash(QString("title" + getId() + m_properties.value("xmldata")).toUtf8().data(), QCryptographicHash::Md5).toHex();
-    else hash = m_properties.value("file_hash");
+    else {
+        if (m_properties.contains("file_hash")) hash = m_properties.value("file_hash");
+        if (hash.isEmpty()) hash = getHash(fileURL().path());
+        
+    }
     return hash;
 }
 
@@ -907,8 +1156,10 @@ void DocClipBase::setProperty(const QString &key, const QString &value)
     if (key == "resource") {
         getFileHash(value);
         if (m_thumbProd) m_thumbProd->updateClipUrl(KUrl(value), m_properties.value("file_hash"));
-    } else if (key == "out") setDuration(GenTime(value.toInt(), KdenliveSettings::project_fps()));
-    //else if (key == "transparency") m_clipProducer->set("transparency", value.toInt());
+        //else if (key == "transparency") m_clipProducer->set("transparency", value.toInt());
+    } else if (key == "out") {
+        setDuration(GenTime(value.toInt() + 1, KdenliveSettings::project_fps()));
+    }
     else if (key == "colour") {
         setProducerProperty("colour", value.toUtf8().data());
     } else if (key == "templatetext") {
@@ -975,20 +1226,24 @@ QMap <QString, QString> DocClipBase::properties() const
     return m_properties;
 }
 
-bool DocClipBase::slotGetAudioThumbs()
+QMap <QString, QString> DocClipBase::currentProperties(const QMap <QString, QString> &props)
 {
-    if (m_thumbProd == NULL || isPlaceHolder()) return false;
-    if (!KdenliveSettings::audiothumbnails() || m_audioTimer == NULL) {
-        if (m_audioTimer != NULL) m_audioTimer->stop();
-        return false;
+    QMap <QString, QString> currentProps;
+    QMap<QString, QString>::const_iterator i = props.constBegin();
+    while (i != props.constEnd()) {
+        currentProps.insert(i.key(), m_properties.value(i.key()));
+        ++i;
     }
+    return currentProps;
+}
+
+bool DocClipBase::getAudioThumbs()
+{
+    if (m_thumbProd == NULL || isPlaceHolder() || !KdenliveSettings::audiothumbnails()) return false;
     if (m_audioThumbCreated) {
-        m_audioTimer->stop();
         return false;
     }
-    m_audioTimer->start(1500);
-    double lengthInFrames = duration().frames(KdenliveSettings::project_fps());
-    m_thumbProd->getAudioThumbs(2, 0, lengthInFrames /*must be number of frames*/, 20);
+    m_audioTimer.start();
     return true;
 }
 
@@ -997,22 +1252,24 @@ bool DocClipBase::isPlaceHolder() const
     return m_placeHolder;
 }
 
-void DocClipBase::addCutZone(int in, int out, QString desc)
+void DocClipBase::addCutZone(int in, int out, const QString &desc)
 {
     CutZoneInfo info;
     info.zone = QPoint(in, out);
     info.description = desc;
-    for (int i = 0; i < m_cutZones.count(); i++)
+    for (int i = 0; i < m_cutZones.count(); ++i) {
         if (m_cutZones.at(i).zone == info.zone) {
             return;
         }
+    }
     m_cutZones.append(info);
 }
 
-bool DocClipBase::hasCutZone(QPoint p) const
+bool DocClipBase::hasCutZone(const QPoint &p) const
 {
-    for (int i = 0; i < m_cutZones.count(); i++)
-        if (m_cutZones.at(i).zone == p) return true;
+    for (int i = 0; i < m_cutZones.count(); ++i)
+        if (m_cutZones.at(i).zone == p)
+            return true;
     return false;
 }
 
@@ -1020,15 +1277,15 @@ bool DocClipBase::hasCutZone(QPoint p) const
 void DocClipBase::removeCutZone(int in, int out)
 {
     QPoint p(in, out);
-    for (int i = 0; i < m_cutZones.count(); i++) {
+    for (int i = 0; i < m_cutZones.count(); ++i) {
         if (m_cutZones.at(i).zone == p) {
             m_cutZones.removeAt(i);
-            i--;
+            --i;
         }
     }
 }
 
-void DocClipBase::updateCutZone(int oldin, int oldout, int in, int out, QString desc)
+void DocClipBase::updateCutZone(int oldin, int oldout, int in, int out, const QString &desc)
 {
     QPoint old(oldin, oldout);
     for (int i = 0; i < m_cutZones.size(); ++i) {
@@ -1051,7 +1308,7 @@ bool DocClipBase::hasVideoCodec(const QString &codec) const
 {
     Mlt::Producer *prod = NULL;
     if (m_baseTrackProducers.count() == 0) return false;
-    for (int i = 0; i < m_baseTrackProducers.count(); i++) {
+    for (int i = 0; i < m_baseTrackProducers.count(); ++i) {
         if (m_baseTrackProducers.at(i) != NULL) {
             prod = m_baseTrackProducers.at(i);
             break;
@@ -1069,7 +1326,7 @@ bool DocClipBase::hasAudioCodec(const QString &codec) const
 {
     Mlt::Producer *prod = NULL;
     if (m_baseTrackProducers.count() == 0) return false;
-    for (int i = 0; i < m_baseTrackProducers.count(); i++) {
+    for (int i = 0; i < m_baseTrackProducers.count(); ++i) {
         if (m_baseTrackProducers.at(i) != NULL) {
             prod = m_baseTrackProducers.at(i);
             break;
@@ -1082,64 +1339,76 @@ bool DocClipBase::hasAudioCodec(const QString &codec) const
     return prod->get(property) == codec;
 }
 
-void DocClipBase::generateProxy(KUrl proxyFolder, QString params)
-{
-    if (m_proxyThread.isRunning()) return;
-    QStringList parameters;
-    parameters << "-i" << m_properties.value("resource");
-    foreach(QString s, params.split(' '))
-    parameters << s;
-    // Make sure we don't block when proxy file already exists
-    parameters << "-y";
-    if (m_properties.value("file_hash").isEmpty()) getFileHash(m_properties.value("resource"));
-    QString proxydir=proxyFolder.path( KUrl::AddTrailingSlash) + "proxy/";
-    KStandardDirs::makeDir(proxydir);
-    QString path = proxydir + m_properties.value("file_hash") + ".avi";
-    setProperty("proxy", path.toUtf8().data());
-    if (QFile::exists(path)) {
-        emit proxyReady(m_id, true);
-        return;
-    }
-    parameters << path;
-    m_proxyThread = QtConcurrent::run(this, &DocClipBase::slotGenerateProxy, parameters);
-}
-
-void DocClipBase::slotGenerateProxy(QStringList parameters)
-{
-    QProcess myProcess;
-    myProcess.start("ffmpeg", parameters);
-    myProcess.waitForStarted();
-    int result = -1;
-    while (myProcess.state() != QProcess::NotRunning) {
-        // building proxy file
-        if (m_abortProxy) {
-            QString path = getProperty("proxy");
-            myProcess.close();
-            myProcess.waitForFinished();
-            QFile::remove(path);
-            m_abortProxy = false;
-            result = 1;
-        }
-        myProcess.waitForFinished(500);
-    }
-    myProcess.waitForFinished();
-    if (result == -1) result = myProcess.exitStatus();
-    if (result == 0) emit proxyReady(m_id, true);
+
+void DocClipBase::slotExtractImage(const QList <int> &frames)
+{
+    if (m_thumbProd == NULL) return;
+    m_thumbProd->extractImage(frames);
+}
+
+QImage DocClipBase::extractImage(int frame, int width, int height)
+{
+    if (m_thumbProd == NULL) return QImage();
+    QMutexLocker locker(&m_producerMutex);
+    return m_thumbProd->extractImage(frame, width, height);
+}
+
+void DocClipBase::setAnalysisData(const QString &name, const QString &data, int offset)
+{
+    if (data.isEmpty()) m_analysisdata.remove(name);
     else {
-        clearProperty("proxy");
-        emit proxyReady(m_id, false);
+        if (m_analysisdata.contains(name)) {
+            if (KMessageBox::questionYesNo(kapp->activeWindow(), i18n("Clip already contains analysis data %1", name), QString(), KGuiItem(i18n("Merge")), KGuiItem(i18n("Add"))) == KMessageBox::Yes) {
+                // Merge data
+                Mlt::Profile *profile = m_baseTrackProducers.at(0)->profile();
+                Mlt::Geometry geometry(m_analysisdata.value(name).toUtf8().data(), m_properties.value("duration").toInt(), profile->width(), profile->height());
+                Mlt::Geometry newGeometry(data.toUtf8().data(), m_properties.value("duration").toInt(), profile->width(), profile->height());
+                Mlt::GeometryItem item;
+                int pos = 0;
+                while (!newGeometry.next_key(&item, pos)) {
+                    pos = item.frame();
+                    item.frame(pos + offset);
+                    pos++;
+                    geometry.insert(item);
+                }
+                m_analysisdata.insert(name, geometry.serialise());
+            }
+            else {
+                // Add data with another name
+                int i = 1;
+                QString newname = name + " " + QString::number(i);
+                while (m_analysisdata.contains(newname)) {
+                    ++i;
+                    newname = name + " " + QString::number(i);
+                }
+                m_analysisdata.insert(newname, geometryWithOffset(data, offset));
+            }
+        }
+        else m_analysisdata.insert(name, geometryWithOffset(data, offset));
     }
 }
 
-void DocClipBase::abortProxy()
+const QString DocClipBase::geometryWithOffset(const QString &data, int offset)
 {
-    if (m_proxyThread.isRunning()) {
-        // Proxy is being created, abort
-        m_abortProxy = true;
-    }
-    else {
-        clearProperty("proxy");
-        emit proxyReady(m_id, false);
+    if (offset == 0) return data;
+    Mlt::Profile *profile = m_baseTrackProducers.at(0)->profile();
+    Mlt::Geometry geometry(data.toUtf8().data(), m_properties.value("duration").toInt(), profile->width(), profile->height());
+    Mlt::Geometry newgeometry(NULL, m_properties.value("duration").toInt(), profile->width(), profile->height());
+    Mlt::GeometryItem item;
+    int pos = 0;
+    while (!geometry.next_key(&item, pos)) {
+        pos = item.frame();
+        item.frame(pos + offset);
+        pos++;
+        newgeometry.insert(item);
     }
+    return newgeometry.serialise();
 }
 
+QMap <QString, QString> DocClipBase::analysisData() const
+{
+    return m_analysisdata;
+}
+
+
+#include "docclipbase.moc"