]> git.sesse.net Git - kdenlive/blobdiff - src/documentvalidator.cpp
Cleaning code style of Definitions.
[kdenlive] / src / documentvalidator.cpp
index 62c46352ff8d57b3a228474637bfa70b17ca48d6..ef8e069489f8f1bc934f83c9b6eb511f833027ee 100644 (file)
 
 #include "documentvalidator.h"
 #include "definitions.h"
+#include "initeffects.h"
+#include "mainwindow.h"
 
 #include <KDebug>
 #include <KMessageBox>
 #include <KApplication>
-#include <KLocale>
+#include <KLocalizedString>
+#include <KStandardDirs>
 
 #include <QFile>
 #include <QColor>
+#include <QString>
+#include <QDir>
+#include <QScriptEngine>
 
-DocumentValidator::DocumentValidator(QDomDocument doc):
+#include <mlt++/Mlt.h>
+
+#include <locale>
+
+
+DocumentValidator::DocumentValidator(const QDomDocument &doc, const KUrl &documentUrl):
         m_doc(doc),
+        m_url(documentUrl),
         m_modified(false)
 {}
 
 bool DocumentValidator::validate(const double currentVersion)
 {
+    QDomElement mlt = m_doc.firstChildElement("mlt");
+    // At least the root element must be there
+    if (mlt.isNull())
+        return false;
+
+    QDomElement kdenliveDoc = mlt.firstChildElement("kdenlivedoc");
     // Check if we're validating a Kdenlive project
-    if (!isProject())
+    if (kdenliveDoc.isNull())
         return false;
+    
+    QString rootDir = mlt.attribute("root");
+    if (rootDir == "$CURRENTPATH") {
+        // The document was extracted from a Kdenlive archived project, fix root directory
+        QString playlist = m_doc.toString();
+        playlist.replace("$CURRENTPATH", m_url.directory(KUrl::IgnoreTrailingSlash));
+        m_doc.setContent(playlist);
+        mlt = m_doc.firstChildElement("mlt");
+        kdenliveDoc = mlt.firstChildElement("kdenlivedoc");
+    }
+
+    // Previous MLT / Kdenlive versions used C locale by default
+    QLocale documentLocale = QLocale::c();
+    
+    if (mlt.hasAttribute("LC_NUMERIC")) {
+        // Set locale for the document        
+        const QString newLocale = setlocale(LC_NUMERIC, mlt.attribute("LC_NUMERIC").toUtf8().constData());
+        documentLocale = QLocale(mlt.attribute("LC_NUMERIC"));
 
+        // Make sure Qt locale and C++ locale have the same numeric separator, might not be the case
+        // With some locales since C++ and Qt use a different database for locales
+        char *separator = localeconv()->decimal_point;
+       if (newLocale.isEmpty()) {
+            // Requested locale not available, ask for install
+            KMessageBox::sorry(kapp->activeWindow(), i18n("The document was created in \"%1\" locale, which is not installed on your system. Please install that language pack. Until then, Kdenlive might not be able to correctly open the document.", mlt.attribute("LC_NUMERIC")));
+        }
+       
+        if (separator != documentLocale.decimalPoint()) {
+           KMessageBox::sorry(kapp->activeWindow(), i18n("There is a locale conflict on your system. The document uses locale %1 which uses a \"%2\" as numeric separator (in system libraries) but Qt expects \"%3\". You might not be able to correctly open the project.", mlt.attribute("LC_NUMERIC"), separator, documentLocale.decimalPoint()));
+            kDebug()<<"------\n!!! system locale is not similar to Qt's locale... be prepared for bugs!!!\n------";
+            // HACK: There is a locale conflict, so set locale to at least have correct decimal point
+            if (strncmp(separator, ".", 1) == 0) documentLocale = QLocale::c();
+            else if (strncmp(separator, ",", 1) == 0) documentLocale = QLocale("fr_FR.UTF-8");
+        }
+    }
+    
+    documentLocale.setNumberOptions(QLocale::OmitGroupSeparator);
+    if (documentLocale.decimalPoint() != QLocale().decimalPoint()) {
+        // If loading an older MLT file without LC_NUMERIC, set locale to C which was previously the default
+        if (!mlt.hasAttribute("LC_NUMERIC")) {
+           setlocale(LC_NUMERIC, "C");
+       }
+
+        QLocale::setDefault(documentLocale);
+        // locale conversion might need to be redone
+        initEffects::parseEffectFiles(setlocale(LC_NUMERIC, NULL));
+    }
+
+    bool ok;
+    double version = documentLocale.toDouble(kdenliveDoc.attribute("version"), &ok);
+    if (!ok) {
+       // Could not parse version number, there is probably a conflict in decimal separator
+       QLocale tempLocale = QLocale(mlt.attribute("LC_NUMERIC"));
+       version = tempLocale.toDouble(kdenliveDoc.attribute("version"), &ok);
+       if (!ok) version = kdenliveDoc.attribute("version").toDouble(&ok);
+       if (!ok) {
+           // Last try: replace comma with a dot
+           QString versionString = kdenliveDoc.attribute("version");
+           if (versionString.contains(',')) versionString.replace(',', '.');
+           version = versionString.toDouble(&ok);
+           if (!ok) kDebug()<<"// CANNOT PARSE VERSION NUMBER, ERROR!";
+       }
+    }
+    
     // Upgrade the document to the latest version
-    QDomNode kdenlivedocNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
-    QDomElement kdenlivedocElm = kdenlivedocNode.toElement();
-    if (!upgrade(kdenlivedocElm.attribute("version").toDouble(), currentVersion))
+    if (!upgrade(version, currentVersion))
         return false;
 
     /*
      * Check the syntax (this will be replaced by XSD validation with Qt 4.6)
      * and correct some errors
      */
-    QDomNode mltNode = m_doc.elementsByTagName("mlt").at(0);
-    QDomElement mltElm = mltNode.toElement();
-    if (mltElm.isNull()) // At least the root element must be there
-        return false;
-    else {
+    {
         // Return (or create) the tractor
-        QDomNode tractorNode = m_doc.elementsByTagName("tractor").at(0);
-        QDomElement tractorElm = tractorNode.toElement();
-        if (tractorElm.isNull()) {
+        QDomElement tractor = mlt.firstChildElement("tractor");
+        if (tractor.isNull()) {
             m_modified = true;
-            tractorElm = m_doc.createElement("tractor");
-            tractorElm.setAttribute("global_feed", "1");
-            tractorElm.setAttribute("in", "0");
-            tractorElm.setAttribute("out", "-1");
-            tractorElm.setAttribute("id", "maintractor");
-            mltElm.appendChild(tractorElm);
+            tractor = m_doc.createElement("tractor");
+            tractor.setAttribute("global_feed", "1");
+            tractor.setAttribute("in", "0");
+            tractor.setAttribute("out", "-1");
+            tractor.setAttribute("id", "maintractor");
+            mlt.appendChild(tractor);
         }
 
         /*
@@ -74,36 +148,67 @@ bool DocumentValidator::validate(const double currentVersion)
          */
         QDomNodeList playlists = m_doc.elementsByTagName("playlist");
         int tracksMax = playlists.count() - 1; // Remove the black track
-        QDomNodeList tracks = m_doc.elementsByTagName("track");
+        QDomNodeList tracks = tractor.elementsByTagName("track");
         tracksMax = qMax(tracks.count() - 1, tracksMax);
-        QDomNodeList tracksinfo = m_doc.elementsByTagName("trackinfo");
+        QDomNodeList tracksinfo = kdenliveDoc.elementsByTagName("trackinfo");
         tracksMax = qMax(tracksinfo.count(), tracksMax);
         tracksMax = qMax(1, tracksMax); // Force existance of one track
         if (playlists.count() - 1 < tracksMax ||
-            tracks.count() - 1 < tracksMax ||
-            tracksinfo.count() < tracksMax) {
+                tracks.count() - 1 < tracksMax ||
+                tracksinfo.count() < tracksMax) {
+            kDebug() << "//// WARNING, PROJECT IS CORRUPTED, MISSING TRACK";
             m_modified = true;
             int difference;
+            // use the MLT tracks as reference
+            if (tracks.count() - 1 < tracksMax) {
+                // Looks like one MLT track is missing, remove the extra Kdenlive track if there is one.
+                if (tracksinfo.count() != tracks.count() - 1) {
+                    // The Kdenlive tracks are not ok, clear and rebuild them
+                    QDomNode tinfo = kdenliveDoc.firstChildElement("tracksinfo");
+                    QDomNode tnode = tinfo.firstChild();
+                    while (!tnode.isNull()) {
+                        tinfo.removeChild(tnode);
+                        tnode = tinfo.firstChild();
+                    }
+
+                    for (int i = 1; i < tracks.count(); ++i) {
+                        QString hide = tracks.at(i).toElement().attribute("hide");
+                        QDomElement newTrack = m_doc.createElement("trackinfo");
+                        if (hide == "video") {
+                            // audio track;
+                            newTrack.setAttribute("type", "audio");
+                            newTrack.setAttribute("blind", 1);
+                            newTrack.setAttribute("mute", 0);
+                            newTrack.setAttribute("lock", 0);
+                        } else {
+                            newTrack.setAttribute("blind", 0);
+                            newTrack.setAttribute("mute", 0);
+                            newTrack.setAttribute("lock", 0);
+                        }
+                        tinfo.appendChild(newTrack);
+                    }
+                }
+            }
+
             if (playlists.count() - 1 < tracksMax) {
                 difference = tracksMax - (playlists.count() - 1);
                 for (int i = 0; i < difference; ++i) {
                     QDomElement playlist = m_doc.createElement("playlist");
-                    mltElm.appendChild(playlist);
+                    mlt.appendChild(playlist);
                 }
             }
             if (tracks.count() - 1 < tracksMax) {
                 difference = tracksMax - (tracks.count() - 1);
                 for (int i = 0; i < difference; ++i) {
                     QDomElement track = m_doc.createElement("track");
-                    tractorElm.appendChild(track);
+                    tractor.appendChild(track);
                 }
             }
             if (tracksinfo.count() < tracksMax) {
-                QDomNode tracksinfoNode = m_doc.elementsByTagName("tracksinfo").at(0);
-                QDomElement tracksinfoElm = tracksinfoNode.toElement();
+                QDomElement tracksinfoElm = kdenliveDoc.firstChildElement("tracksinfo");
                 if (tracksinfoElm.isNull()) {
                     tracksinfoElm = m_doc.createElement("tracksinfo");
-                    kdenlivedocElm.appendChild(tracksinfoElm);
+                    kdenliveDoc.appendChild(tracksinfoElm);
                 }
                 difference = tracksMax - tracksinfo.count();
                 for (int i = 0; i < difference; ++i) {
@@ -113,12 +218,15 @@ bool DocumentValidator::validate(const double currentVersion)
                     tracksinfoElm.appendChild(trackinfo);
                 }
             }
-        }
+        }        
 
         // TODO: check the tracks references
         // TODO: check internal mix transitions
+        
     }
 
+    updateEffects();
+
     return true;
 }
 
@@ -128,14 +236,13 @@ bool DocumentValidator::upgrade(double version, const double currentVersion)
 
     // No conversion needed
     if (version == currentVersion) {
-        if (!m_doc.toString().contains("font-size")) // TODO: remove when currentVersion == 0.84
-            return true;
+        return true;
     }
 
     // The document is too new
     if (version > currentVersion) {
         kDebug() << "Unable to open document with version " << version;
-        KMessageBox::sorry(kapp->activeWindow(), i18n("This project type is unsupported (version %1) and can't be loaded.\nPlease consider upgrading you Kdenlive version.", version), i18n("Unable to open project"));
+        KMessageBox::sorry(kapp->activeWindow(), i18n("This project type is unsupported (version %1) and can't be loaded.\nPlease consider upgrading your Kdenlive version.", version), i18n("Unable to open project"));
         return false;
     }
 
@@ -149,6 +256,7 @@ bool DocumentValidator::upgrade(double version, const double currentVersion)
     // <kdenlivedoc />
     QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
     QDomElement infoXml = infoXmlNode.toElement();
+    infoXml.setAttribute("upgraded", "1");
 
     if (version <= 0.6) {
         QDomElement infoXml_old = infoXmlNode.cloneNode(true).toElement(); // Needed for folders
@@ -181,7 +289,7 @@ bool DocumentValidator::upgrade(double version, const double currentVersion)
             blank_tractor.appendChild(blank_track);
 
             QDomNodeList kdenlivetracks = m_doc.elementsByTagName("kdenlivetrack");
-            for (int i = 0; i < kdenlivetracks.count(); i++) {
+            for (int i = 0; i < kdenlivetracks.count(); ++i) {
                 blank_playlist = m_doc.createElement("playlist");
                 blank_playlist.setAttribute("id", "playlist" + QString::number(i));
                 westley.insertBefore(blank_playlist, QDomNode());
@@ -193,68 +301,68 @@ bool DocumentValidator::upgrade(double version, const double currentVersion)
                     blank_track.setAttribute("hide", "video");
                 }
             }
-        } else for (int i = 0; i < max; i++) {
-            QDomNode n = playlists.at(i);
-            westley.insertBefore(n, QDomNode());
-            QDomElement pl = n.toElement();
-            QDomElement track = m_doc.createElement("track");
-            QString trackType = pl.attribute("hide");
-            if (!trackType.isEmpty())
-                track.setAttribute("hide", trackType);
-            QString playlist_id =  pl.attribute("id");
-            if (playlist_id.isEmpty()) {
-                playlist_id = "black_track";
-                pl.setAttribute("id", playlist_id);
-            }
-            track.setAttribute("producer", playlist_id);
-            //tractor.appendChild(track);
+        } else for (int i = 0; i < max; ++i) {
+                QDomNode n = playlists.at(i);
+                westley.insertBefore(n, QDomNode());
+                QDomElement pl = n.toElement();
+                QDomElement track = m_doc.createElement("track");
+                QString trackType = pl.attribute("hide");
+                if (!trackType.isEmpty())
+                    track.setAttribute("hide", trackType);
+                QString playlist_id =  pl.attribute("id");
+                if (playlist_id.isEmpty()) {
+                    playlist_id = "black_track";
+                    pl.setAttribute("id", playlist_id);
+                }
+                track.setAttribute("producer", playlist_id);
+                //tractor.appendChild(track);
 #define KEEP_TRACK_ORDER 1
 #ifdef KEEP_TRACK_ORDER
-            tractor.insertAfter(track, QDomNode());
+                tractor.insertAfter(track, QDomNode());
 #else
-            // Insert the new track in an order that hopefully matches the 3 video, then 2 audio tracks of Kdenlive 0.7.0
-            // insertion sort - O( tracks*tracks )
-            // Note, this breaks _all_ transitions - but you can move them up and down afterwards.
-            QDomElement tractor_elem = tractor.toElement();
-            if (! tractor_elem.isNull()) {
-                QDomNodeList tracks = tractor_elem.elementsByTagName("track");
-                int size = tracks.size();
-                if (size == 0) {
-                    tractor.insertAfter(track, QDomNode());
-                } else {
-                    bool inserted = false;
-                    for (int i = 0; i < size; ++i) {
-                        QDomElement track_elem = tracks.at(i).toElement();
-                        if (track_elem.isNull()) {
-                            tractor.insertAfter(track, QDomNode());
-                            inserted = true;
-                            break;
-                        } else {
-                            kDebug() << "playlist_id: " << playlist_id << " producer:" << track_elem.attribute("producer");
-                            if (playlist_id < track_elem.attribute("producer")) {
-                                tractor.insertBefore(track, track_elem);
+                // Insert the new track in an order that hopefully matches the 3 video, then 2 audio tracks of Kdenlive 0.7.0
+                // insertion sort - O( tracks*tracks )
+                // Note, this breaks _all_ transitions - but you can move them up and down afterwards.
+                QDomElement tractor_elem = tractor.toElement();
+                if (! tractor_elem.isNull()) {
+                    QDomNodeList tracks = tractor_elem.elementsByTagName("track");
+                    int size = tracks.size();
+                    if (size == 0) {
+                        tractor.insertAfter(track, QDomNode());
+                    } else {
+                        bool inserted = false;
+                        for (int i = 0; i < size; ++i) {
+                            QDomElement track_elem = tracks.at(i).toElement();
+                            if (track_elem.isNull()) {
+                                tractor.insertAfter(track, QDomNode());
                                 inserted = true;
                                 break;
+                            } else {
+                                kDebug() << "playlist_id: " << playlist_id << " producer:" << track_elem.attribute("producer");
+                                if (playlist_id < track_elem.attribute("producer")) {
+                                    tractor.insertBefore(track, track_elem);
+                                    inserted = true;
+                                    break;
+                                }
                             }
                         }
+                        // Reach here, no insertion, insert last
+                        if (!inserted) {
+                            tractor.insertAfter(track, QDomNode());
+                        }
                     }
-                    // Reach here, no insertion, insert last
-                    if (!inserted) {
-                        tractor.insertAfter(track, QDomNode());
-                    }
+                } else {
+                    kWarning() << "tractor was not a QDomElement";
+                    tractor.insertAfter(track, QDomNode());
                 }
-            } else {
-                kWarning() << "tractor was not a QDomElement";
-                tractor.insertAfter(track, QDomNode());
-            }
 #endif
-        }
+            }
         tractor.removeChild(multitrack);
 
         // audio track mixing transitions should not be added to track view, so add required attribute
         QDomNodeList transitions = m_doc.elementsByTagName("transition");
         max = transitions.count();
-        for (int i = 0; i < max; i++) {
+        for (int i = 0; i < max; ++i) {
             QDomElement tr = transitions.at(i).toElement();
             if (tr.attribute("combine") == "1" && tr.attribute("mlt_service") == "mix") {
                 QDomElement property = m_doc.createElement("property");
@@ -284,14 +392,14 @@ bool DocumentValidator::upgrade(double version, const double currentVersion)
         }
 
         // move transitions after tracks
-        for (int i = 0; i < max; i++) {
+        for (int i = 0; i < max; ++i) {
             tractor.insertAfter(transitions.at(0), QDomNode());
         }
 
         // Fix filters format
         QDomNodeList entries = m_doc.elementsByTagName("entry");
         max = entries.count();
-        for (int i = 0; i < max; i++) {
+        for (int i = 0; i < max; ++i) {
             QString last_id;
             int effectix = 0;
             QDomNode m = entries.at(i).firstChild();
@@ -331,7 +439,7 @@ bool DocumentValidator::upgrade(double version, const double currentVersion)
             max = filters.count();
             QString last_id;
             int effectix = 0;
-            for (int i = 0; i < max; i++) {
+            for (int i = 0; i < max; ++i) {
                 QDomElement filt = filters.at(i).toElement();
                 QDomNamedNodeMap attrs = filt.attributes();
         QString current_id = filt.attribute("kdenlive_id");
@@ -360,7 +468,7 @@ bool DocumentValidator::upgrade(double version, const double currentVersion)
         // fix slowmotion
         QDomNodeList producers = westley.toElement().elementsByTagName("producer");
         max = producers.count();
-        for (int i = 0; i < max; i++) {
+        for (int i = 0; i < max; ++i) {
             QDomElement prod = producers.at(i).toElement();
             if (prod.attribute("mlt_service") == "framebuffer") {
                 QString slowmotionprod = prod.attribute("resource");
@@ -374,7 +482,7 @@ bool DocumentValidator::upgrade(double version, const double currentVersion)
         // This will get the xml producers:
         producers = m_doc.elementsByTagName("producer");
         max = producers.count();
-        for (int i = 0; i < max; i++) {
+        for (int i = 0; i < max; ++i) {
             QDomElement prod = producers.at(0).toElement();
             // add resource also as a property (to allow path correction in setNewResource())
             // TODO: will it work with slowmotion? needs testing
@@ -396,7 +504,7 @@ bool DocumentValidator::upgrade(double version, const double currentVersion)
                         markers.insertAfter(mark, QDomNode());
                     }
                     prod.removeChild(m);
-                } else if (prod.attribute("type").toInt() == TEXT) {
+                } else if (prod.attribute("type").toInt() == Text) {
                     // convert title clip
                     if (m.toElement().tagName() == "textclip") {
                         QDomDocument tdoc;
@@ -500,7 +608,7 @@ bool DocumentValidator::upgrade(double version, const double currentVersion)
         } else {
             QDomNodeList wproducers = westley_element.elementsByTagName("producer");
             int kmax = wproducers.count();
-            for (int i = 0; i < kmax; i++) {
+            for (int i = 0; i < kmax; ++i) {
                 QDomElement wproducer = wproducers.at(i).toElement();
                 if (wproducer.isNull()) {
                     kWarning() << "Found producer in westley0, that was not a QDomElement";
@@ -509,7 +617,7 @@ bool DocumentValidator::upgrade(double version, const double currentVersion)
                 if (wproducer.attribute("id") == "black") continue;
                 // We have to do slightly different things, depending on the type
                 kDebug() << "Converting producer element with type" << wproducer.attribute("type");
-                if (wproducer.attribute("type").toInt() == TEXT) {
+                if (wproducer.attribute("type").toInt() == Text) {
                     kDebug() << "Found TEXT element in producer" << endl;
                     QDomElement kproducer = wproducer.cloneNode(true).toElement();
                     kproducer.setTagName("kdenlive_producer");
@@ -589,7 +697,7 @@ bool DocumentValidator::upgrade(double version, const double currentVersion)
 #endif
         QDomNodeList elements = westley.childNodes();
         max = elements.count();
-        for (int i = 0; i < max; i++) {
+        for (int i = 0; i < max; ++i) {
             QDomElement prod = elements.at(0).toElement();
             westley0.insertAfter(prod, QDomNode());
         }
@@ -638,7 +746,7 @@ bool DocumentValidator::upgrade(double version, const double currentVersion)
         QString tracksOrder = infoXml.attribute("tracks");
         if (tracksOrder.isEmpty()) {
             QDomNodeList tracks = m_doc.elementsByTagName("track");
-            for (int i = 0; i < tracks.count(); i++) {
+            for (int i = 0; i < tracks.count(); ++i) {
                 QDomElement track = tracks.at(i).toElement();
                 if (track.attribute("producer") != "black_track") {
                     if (track.attribute("hide") == "video")
@@ -649,7 +757,7 @@ bool DocumentValidator::upgrade(double version, const double currentVersion)
             }
         }
         QDomElement tracksinfo = m_doc.createElement("tracksinfo");
-        for (int i = 0; i < tracksOrder.size(); i++) {
+        for (int i = 0; i < tracksOrder.size(); ++i) {
             QDomElement trackinfo = m_doc.createElement("trackinfo");
             if (tracksOrder.data()[i] == 'a') {
                 trackinfo.setAttribute("type", "audio");
@@ -666,7 +774,7 @@ bool DocumentValidator::upgrade(double version, const double currentVersion)
     if (version <= 0.82) {
         // Convert <westley />s in <mlt />s (MLT extreme makeover)
         QDomNodeList westleyNodes = m_doc.elementsByTagName("westley");
-        for (int i = 0; i < westleyNodes.count(); i++) {
+        for (int i = 0; i < westleyNodes.count(); ++i) {
             QDomElement westley = westleyNodes.at(i).toElement();
             westley.setTagName("mlt");
         }
@@ -675,43 +783,276 @@ bool DocumentValidator::upgrade(double version, const double currentVersion)
     if (version <= 0.83) {
         // Replace point size with pixel size in text titles
         if (m_doc.toString().contains("font-size")) {
+            KMessageBox::ButtonCode convert = KMessageBox::Continue;
             QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
-            for (int i = 0; i < kproducerNodes.count(); ++i) {
+            for (int i = 0; i < kproducerNodes.count() && convert != KMessageBox::No; ++i) {
                 QDomElement kproducer = kproducerNodes.at(i).toElement();
-                if (kproducer.attribute("type").toInt() == TEXT) {
+                if (kproducer.attribute("type").toInt() == Text) {
                     QDomDocument data;
                     data.setContent(kproducer.attribute("xmldata"));
                     QDomNodeList items = data.firstChild().childNodes();
-                    for (int j = 0; j < items.count(); ++j) {
+                    for (int j = 0; j < items.count() && convert != KMessageBox::No; ++j) {
                         if (items.at(j).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
                             QDomNamedNodeMap textProperties = items.at(j).namedItem("content").attributes();
-                            if (textProperties.namedItem("font-pixel-size").isNull()) {
-                                QFont font;
-                                font.setPointSize(textProperties.namedItem("font-size").nodeValue().toInt());
-                                QDomElement content = items.at(j).namedItem("content").toElement();
-                                content.setAttribute("font-pixel-size", QFontInfo(font).pixelSize());
-                                content.removeAttribute("font-size");
-                                kproducer.setAttribute("xmldata", data.toString());
-                                /*
-                                 * You may be tempted to delete the preview file
-                                 * to force its recreation: bad idea (see
-                                 * http://www.kdenlive.org/mantis/view.php?id=749)
-                                 */
+                            if (textProperties.namedItem("font-pixel-size").isNull() && !textProperties.namedItem("font-size").isNull()) {
+                                // Ask the user if he wants to convert
+                                if (convert != KMessageBox::Yes && convert != KMessageBox::No)
+                                    convert = (KMessageBox::ButtonCode)KMessageBox::warningYesNo(kapp->activeWindow(), i18n("Some of your text clips were saved with size in points, which means different sizes on different displays. Do you want to convert them to pixel size, making them portable? It is recommended you do this on the computer they were first created on, or you could have to adjust their size."), i18n("Update Text Clips"));
+                                if (convert == KMessageBox::Yes) {
+                                    QFont font;
+                                    font.setPointSize(textProperties.namedItem("font-size").nodeValue().toInt());
+                                    QDomElement content = items.at(j).namedItem("content").toElement();
+                                    content.setAttribute("font-pixel-size", QFontInfo(font).pixelSize());
+                                    content.removeAttribute("font-size");
+                                    kproducer.setAttribute("xmldata", data.toString());
+                                    /*
+                                     * You may be tempted to delete the preview file
+                                     * to force its recreation: bad idea (see
+                                     * http://www.kdenlive.org/mantis/view.php?id=749)
+                                     */
+                                }
                             }
                         }
                     }
                 }
             }
         }
+
+        // Fill the <documentproperties /> element
+        QDomElement docProperties = infoXml.firstChildElement("documentproperties");
+        if (docProperties.isNull()) {
+            docProperties = m_doc.createElement("documentproperties");
+            docProperties.setAttribute("zonein", infoXml.attribute("zonein"));
+            docProperties.setAttribute("zoneout", infoXml.attribute("zoneout"));
+            docProperties.setAttribute("zoom", infoXml.attribute("zoom"));
+            docProperties.setAttribute("position", infoXml.attribute("position"));
+            infoXml.appendChild(docProperties);
+        }
+    }
+
+    if (version <= 0.84) {
+        // update the title clips to use the new MLT kdenlivetitle producer
+        QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
+        for (int i = 0; i < kproducerNodes.count(); ++i) {
+            QDomElement kproducer = kproducerNodes.at(i).toElement();
+            if (kproducer.attribute("type").toInt() == Text) {
+                QString data = kproducer.attribute("xmldata");
+                QString datafile = kproducer.attribute("resource");
+                if (!datafile.endsWith(".kdenlivetitle")) {
+                    datafile = QString();
+                    kproducer.setAttribute("resource", QString());
+                }
+                QString id = kproducer.attribute("id");
+                QDomNodeList mltproducers = m_doc.elementsByTagName("producer");
+                bool foundData = false;
+                bool foundResource = false;
+                bool foundService = false;
+                for (int j = 0; j < mltproducers.count(); j++) {
+                    QDomElement wproducer = mltproducers.at(j).toElement();
+                    if (wproducer.attribute("id") == id) {
+                        QDomNodeList props = wproducer.childNodes();
+                        for (int k = 0; k < props.count(); k++) {
+                            if (props.at(k).toElement().attribute("name") == "xmldata") {
+                                props.at(k).firstChild().setNodeValue(data);
+                                foundData = true;
+                            } else if (props.at(k).toElement().attribute("name") == "mlt_service") {
+                                props.at(k).firstChild().setNodeValue("kdenlivetitle");
+                                foundService = true;
+                            } else if (props.at(k).toElement().attribute("name") == "resource") {
+                                props.at(k).firstChild().setNodeValue(datafile);
+                                foundResource = true;
+                            }
+                        }
+                        if (!foundData) {
+                            QDomElement e = m_doc.createElement("property");
+                            e.setAttribute("name", "xmldata");
+                            QDomText value = m_doc.createTextNode(data);
+                            e.appendChild(value);
+                            wproducer.appendChild(e);
+                        }
+                        if (!foundService) {
+                            QDomElement e = m_doc.createElement("property");
+                            e.setAttribute("name", "mlt_service");
+                            QDomText value = m_doc.createTextNode("kdenlivetitle");
+                            e.appendChild(value);
+                            wproducer.appendChild(e);
+                        }
+                        if (!foundResource) {
+                            QDomElement e = m_doc.createElement("property");
+                            e.setAttribute("name", "resource");
+                            QDomText value = m_doc.createTextNode(datafile);
+                            e.appendChild(value);
+                            wproducer.appendChild(e);
+                        }
+                        break;
+                    }
+                }
+            }
+        }
+    }
+    if (version <= 0.85) {
+        // update the LADSPA effects to use the new ladspa.id format instead of external xml file
+        QDomNodeList effectNodes = m_doc.elementsByTagName("filter");
+        for (int i = 0; i < effectNodes.count(); ++i) {
+            QDomElement effect = effectNodes.at(i).toElement();
+            if (EffectsList::property(effect, "mlt_service") == "ladspa") {
+                // Needs to be converted
+                QStringList info = getInfoFromEffectName(EffectsList::property(effect, "kdenlive_id"));
+                if (info.isEmpty()) continue;
+                // info contains the correct ladspa.id from kdenlive effect name, and a list of parameter's old and new names
+                EffectsList::setProperty(effect, "kdenlive_id", info.at(0));
+                EffectsList::setProperty(effect, "tag", info.at(0));
+                EffectsList::setProperty(effect, "mlt_service", info.at(0));
+                EffectsList::removeProperty(effect, "src");
+                for (int j = 1; j < info.size(); j++) {
+                    QString value = EffectsList::property(effect, info.at(j).section('=', 0, 0));
+                    if (!value.isEmpty()) {
+                        // update parameter name
+                        EffectsList::renameProperty(effect, info.at(j).section('=', 0, 0), info.at(j).section('=', 1, 1));
+                    }
+                }
+            }
+        }
+    }
+
+    if (version <= 0.86) {
+        // Make sure we don't have avformat-novalidate producers, since it caused crashes
+        QDomNodeList producers = m_doc.elementsByTagName("producer");
+        int max = producers.count();
+        for (int i = 0; i < max; ++i) {
+            QDomElement prod = producers.at(i).toElement();
+            if (EffectsList::property(prod, "mlt_service") == "avformat-novalidate")
+                EffectsList::setProperty(prod, "mlt_service", "avformat");
+        }
+
+        // There was a mistake in Geometry transitions where the last keyframe was created one frame after the end of transition, so fix it and move last keyframe to real end of transition
+
+        // Get profile info (width / height)
+        int profileWidth;
+        int profileHeight;
+        QDomElement profile = m_doc.firstChildElement("profile");
+        if (profile.isNull()) profile = infoXml.firstChildElement("profileinfo");
+        if (profile.isNull()) {
+            // could not find profile info, set PAL
+            profileWidth = 720;
+            profileHeight = 576;
+        }
+        else {
+            profileWidth = profile.attribute("width").toInt();
+            profileHeight = profile.attribute("height").toInt();
+        }
+        QDomNodeList transitions = m_doc.elementsByTagName("transition");
+        max = transitions.count();
+        int out;
+        for (int i = 0; i < max; ++i) {
+            QDomElement trans = transitions.at(i).toElement();
+            out = trans.attribute("out").toInt() - trans.attribute("in").toInt();
+            QString geom = EffectsList::property(trans, "geometry");
+            Mlt::Geometry *g = new Mlt::Geometry(geom.toUtf8().data(), out, profileWidth, profileHeight);
+            Mlt::GeometryItem item;
+            if (g->next_key(&item, out) == 0) {
+                // We have a keyframe just after last frame, try to move it to last frame
+                if (item.frame() == out + 1) {
+                    item.frame(out);
+                    g->insert(item);
+                    g->remove(out + 1);
+                    EffectsList::setProperty(trans, "geometry", g->serialise());
+                }
+            }
+            delete g;
+        }
+    }
+
+    if (version <= 0.87) {
+        if (!m_doc.firstChildElement("mlt").hasAttribute("LC_NUMERIC")) {
+            m_doc.firstChildElement("mlt").setAttribute("LC_NUMERIC", "C");
+        }
     }
 
     // The document has been converted: mark it as modified
     infoXml.setAttribute("version", currentVersion);
     m_modified = true;
-
     return true;
 }
 
+QStringList DocumentValidator::getInfoFromEffectName(const QString &oldName)
+{
+    QStringList info;
+    // Returns a list to convert old Kdenlive ladspa effects
+    if (oldName == "pitch_shift") {
+        info << "ladspa.1433";
+        info << "pitch=0";
+    }
+    else if (oldName == "vinyl") {
+        info << "ladspa.1905";
+        info << "year=0";
+        info << "rpm=1";
+        info << "warping=2";
+        info << "crackle=3";
+        info << "wear=4";
+    }
+    else if (oldName == "room_reverb") {
+        info << "ladspa.1216";
+        info << "room=0";
+        info << "delay=1";
+        info << "damp=2";
+    }
+    else if (oldName == "reverb") {
+        info << "ladspa.1423";
+        info << "room=0";
+        info << "damp=1";
+    }
+    else if (oldName == "rate_scale") {
+        info << "ladspa.1417";
+        info << "rate=0";
+    }
+    else if (oldName == "pitch_scale") {
+        info << "ladspa.1193";
+        info << "coef=0";
+    }
+    else if (oldName == "phaser") {
+        info << "ladspa.1217";
+        info << "rate=0";
+        info << "depth=1";
+        info << "feedback=2";
+        info << "spread=3";
+    }
+    else if (oldName == "limiter") {
+        info << "ladspa.1913";
+        info << "gain=0";
+        info << "limit=1";
+        info << "release=2";
+    }
+    else if (oldName == "equalizer_15") {
+        info << "ladspa.1197";
+        info << "1=0";
+        info << "2=1";
+        info << "3=2";
+        info << "4=3";
+        info << "5=4";
+        info << "6=5";
+        info << "7=6";
+        info << "8=7";
+        info << "9=8";
+        info << "10=9";
+        info << "11=10";
+        info << "12=11";
+        info << "13=12";
+        info << "14=13";
+        info << "15=14";
+    }
+    else if (oldName == "equalizer") {
+        info << "ladspa.1901";
+        info << "logain=0";
+        info << "midgain=1";
+        info << "higain=2";
+    }
+    else if (oldName == "declipper") {
+        info << "ladspa.1195";
+    }
+    return info;
+}
+
 QString DocumentValidator::colorToString(const QColor& c)
 {
     QString ret = "%1,%2,%3,%4";
@@ -729,3 +1070,138 @@ bool DocumentValidator::isModified() const
 {
     return m_modified;
 }
+
+void DocumentValidator::updateEffects()
+{
+    // WARNING: order by findDirs will determine which js file to use (in case multiple scripts for the same filter exist)
+    QMap <QString, KUrl> paths;
+#if QT_VERSION >= 0x040700
+    QMap <QString, QScriptProgram> scripts;
+#else
+    QMap <QString, QString> scripts;
+#endif
+    QStringList directories = KGlobal::dirs()->findDirs("appdata", "effects/update");
+    foreach (const QString &directoryName, directories) {
+        QDir directory(directoryName);
+        QStringList fileList = directory.entryList(QStringList() << "*.js", QDir::Files);
+        foreach (const QString &fileName, fileList) {
+            QString identifier = fileName;
+            // remove extension (".js")
+            identifier.chop(3);
+            paths.insert(identifier, KUrl(directoryName + fileName));
+        }
+    }
+
+    QDomNodeList effects = m_doc.elementsByTagName("filter");
+    int max = effects.count();
+    QStringList safeEffects;
+    for(int i = 0; i < max; ++i) {
+        QDomElement effect = effects.at(i).toElement();
+        QString effectId = EffectsList::property(effect, "kdenlive_id");
+        if (safeEffects.contains(effectId)) {
+            // Do not check the same effect twice if it is at the correct version
+            // (assume we don't have different versions of the same effect in a project file)
+            continue;
+        }
+        QString effectTag = EffectsList::property(effect, "tag");
+        QString effectVersionStr = EffectsList::property(effect, "version");
+        double effectVersion = effectVersionStr.isNull() ? -1 : effectVersionStr.toDouble();
+
+        QDomElement effectDescr = MainWindow::customEffects.getEffectByTag(QString(), effectId);
+        if (effectDescr.isNull()) {
+            effectDescr = MainWindow::videoEffects.getEffectByTag(effectTag, effectId);
+        }
+        if (effectDescr.isNull()) {
+            effectDescr = MainWindow::audioEffects.getEffectByTag(effectTag, effectId);
+        }
+        if (!effectDescr.isNull()) {
+            double serviceVersion = -1;
+            QDomElement serviceVersionElem = effectDescr.firstChildElement("version");
+            if (!serviceVersionElem.isNull()) {
+                serviceVersion = serviceVersionElem.text().toDouble();
+            }
+            if (serviceVersion != effectVersion && paths.contains(effectId)) {
+                if (!scripts.contains(effectId)) {
+                    QFile scriptFile(paths.value(effectId).path());
+                    if (!scriptFile.open(QIODevice::ReadOnly)) {
+                        continue;
+                    }
+#if QT_VERSION >= 0x040700
+                    QScriptProgram scriptProgram(scriptFile.readAll());
+#else
+                    QString scriptProgram = scriptFile.readAll();
+#endif
+                    scriptFile.close();
+                    scripts.insert(effectId, scriptProgram);
+                }
+
+                QScriptEngine scriptEngine;
+                scriptEngine.importExtension("qt.core");
+                scriptEngine.importExtension("qt.xml");
+                scriptEngine.evaluate(scripts.value(effectId));
+                QScriptValue updateRules = scriptEngine.globalObject().property("update");
+                if (!updateRules.isValid())
+                    continue;
+                if (updateRules.isFunction()) {
+                    QDomDocument scriptDoc;
+                    scriptDoc.appendChild(scriptDoc.importNode(effect, true));
+
+                    QString effectString = updateRules.call(QScriptValue(), QScriptValueList()  << serviceVersion << effectVersion << scriptDoc.toString()).toString();
+
+                    if (!effectString.isEmpty() && !scriptEngine.hasUncaughtException()) {
+                        scriptDoc.setContent(effectString);
+                        QDomNode updatedEffect = effect.ownerDocument().importNode(scriptDoc.documentElement(), true);
+                        effect.parentNode().replaceChild(updatedEffect, effect);
+                        m_modified = true;
+                    }
+                } else {
+                    m_modified = updateEffectParameters(effect.childNodes(), &updateRules, serviceVersion, effectVersion);
+                }
+
+                // set version number since MLT won't change it (only initially set it)
+                QDomElement versionElem = effect.firstChildElement("version");
+                if (EffectsList::property(effect, "version").isNull()) {
+                    versionElem = effect.ownerDocument().createTextNode(QLocale().toString(serviceVersion)).toElement();
+                    versionElem.setTagName("property");
+                    versionElem.setAttribute("name", "version");
+                    effect.appendChild(versionElem);
+                } else {
+                    EffectsList::setProperty(effect, "version", QLocale().toString(serviceVersion));
+                }
+            }
+            else safeEffects.append(effectId);
+        }
+    }
+}
+
+bool DocumentValidator::updateEffectParameters(const QDomNodeList &parameters, const QScriptValue* updateRules, const double serviceVersion, const double effectVersion)
+{
+    bool updated = false;
+    bool isDowngrade = serviceVersion < effectVersion;
+    for (int i = 0; i < parameters.count(); ++i) {
+        QDomElement parameter = parameters.at(i).toElement();
+        QScriptValue rules = updateRules->property(parameter.attribute("name"));
+        if (rules.isValid() && rules.isArray()) {
+            int rulesCount = rules.property("length").toInt32();
+            if (isDowngrade) {
+                // start with the highest version and downgrade step by step
+                for (int j = rulesCount - 1; j >= 0; --j) {
+                    double version = rules.property(j).property(0).toNumber();
+                    if (version <= effectVersion && version > serviceVersion) {
+                        parameter.firstChild().setNodeValue(rules.property(j).property(1).call(QScriptValue(), QScriptValueList() << parameter.text() << isDowngrade).toString());
+                        updated = true;
+                    }
+                }
+            } else {
+                for (int j = 0; j < rulesCount; ++j) {
+                    double version = rules.property(j).property(0).toNumber();
+                    if (version > effectVersion && version <= serviceVersion) {
+                        parameter.firstChild().setNodeValue(rules.property(j).property(1).call(QScriptValue(), QScriptValueList() << parameter.text() << isDowngrade).toString());
+                        updated = true;
+                    }
+                }
+            }
+        }
+    }
+    return updated;
+}