File indexing completed on 2024-04-28 08:43:51

0001 /*
0002     SPDX-FileCopyrightText: 2007 Jean-Baptiste Mardelle <jb@kdenlive.org>
0003 
0004 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 #include "documentvalidator.h"
0008 
0009 #include "bin/binplaylist.hpp"
0010 #include "core.h"
0011 #include "definitions.h"
0012 #include "effects/effectsrepository.hpp"
0013 #include "mainwindow.h"
0014 #include "transitions/transitionsrepository.hpp"
0015 #include "xml/xml.hpp"
0016 
0017 #include "kdenlive_debug.h"
0018 #include "utils/KMessageBox_KdenliveCompat.h"
0019 #include <KLocalizedString>
0020 #include <KMessageBox>
0021 
0022 #include <QApplication>
0023 #include <QJsonArray>
0024 #include <QJsonDocument>
0025 #include <QJsonObject>
0026 
0027 #include <mlt++/Mlt.h>
0028 
0029 #include <locale>
0030 #ifdef Q_OS_MAC
0031 #include <xlocale.h>
0032 #endif
0033 
0034 #include <QStandardPaths>
0035 #include <lib/localeHandling.h>
0036 #include <utility>
0037 
0038 DocumentValidator::DocumentValidator(const QDomDocument &doc, QUrl documentUrl)
0039     : m_doc(doc)
0040     , m_url(std::move(documentUrl))
0041     , m_modified(false)
0042 {
0043 }
0044 
0045 QPair<bool, QString> DocumentValidator::validate(const double currentVersion)
0046 {
0047     QDomElement mlt = m_doc.firstChildElement(QStringLiteral("mlt"));
0048     // At least the root element must be there
0049     if (mlt.isNull()) {
0050         return QPair<bool, QString>(false, QString());
0051     }
0052     QDomElement kdenliveDoc = mlt.firstChildElement(QStringLiteral("kdenlivedoc"));
0053     QString rootDir = mlt.attribute(QStringLiteral("root"));
0054     if (rootDir == QLatin1String("$CURRENTPATH")) {
0055         // The document was extracted from a Kdenlive archived project, fix root directory
0056         QString playlist = m_doc.toString();
0057         playlist.replace(QLatin1String("$CURRENTPATH"), m_url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toLocalFile());
0058         m_doc.setContent(playlist);
0059         mlt = m_doc.firstChildElement(QStringLiteral("mlt"));
0060         kdenliveDoc = mlt.firstChildElement(QStringLiteral("kdenlivedoc"));
0061     } else if (rootDir.isEmpty()) {
0062         mlt.setAttribute(QStringLiteral("root"), m_url.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).toLocalFile());
0063     }
0064 
0065     QLocale documentLocale = QLocale::c(); // Document locale for conversion. Previous MLT / Kdenlive versions used C locale by default
0066     QDomElement main_playlist;
0067     QDomNodeList playlists = m_doc.elementsByTagName(QStringLiteral("playlist"));
0068     for (int i = 0; i < playlists.count(); i++) {
0069         if (playlists.at(i).toElement().attribute(QStringLiteral("id")) == QLatin1String("main bin") ||
0070             playlists.at(i).toElement().attribute(QStringLiteral("id")) == QLatin1String("main_bin")) {
0071             main_playlist = playlists.at(i).toElement();
0072             break;
0073         }
0074     }
0075 
0076     if (mlt.hasAttribute(QStringLiteral("LC_NUMERIC"))) { // Backwards compatibility
0077         // Check document numeric separator (added in Kdenlive 16.12.1 and removed in Kdenlive 20.08)
0078         QString sep = Xml::getXmlProperty(main_playlist, "kdenlive:docproperties.decimalPoint", QString("."));
0079         QString mltLocale = mlt.attribute(QStringLiteral("LC_NUMERIC"), "C"); // Backwards compatibility
0080         qDebug() << "LOCALE: Document uses " << sep << " as decimal point and " << mltLocale << " as locale";
0081 
0082         auto localeMatch = LocaleHandling::getQLocaleForDecimalPoint(mltLocale, sep);
0083         qDebug() << "Searching for locale: Found " << localeMatch.first << " with match type " << int(localeMatch.second);
0084 
0085         if (localeMatch.second == LocaleHandling::MatchType::NoMatch) {
0086             // Requested locale not available, ask for install
0087             KMessageBox::error(QApplication::activeWindow(),
0088                                i18n("The document was created in \"%1\" locale, which is not installed on your system. Please install that language pack. "
0089                                     "Until then, Kdenlive might not be able to correctly open the document.",
0090                                     mltLocale));
0091         }
0092 
0093         documentLocale.setNumberOptions(QLocale::OmitGroupSeparator);
0094         documentLocale = localeMatch.first;
0095     }
0096 
0097     double version = -1;
0098     if (kdenliveDoc.isNull() || !kdenliveDoc.hasAttribute(QStringLiteral("version"))) {
0099         // Newer Kdenlive document version
0100         version = Xml::getXmlProperty(main_playlist, QStringLiteral("kdenlive:docproperties.version")).toDouble();
0101     } else {
0102         bool ok;
0103         version = documentLocale.toDouble(kdenliveDoc.attribute(QStringLiteral("version")), &ok);
0104         if (!ok) {
0105             // Could not parse version number, there is probably a conflict in decimal separator
0106             QLocale tempLocale = QLocale(mlt.attribute(QStringLiteral("LC_NUMERIC"))); // Try parsing version number with document locale
0107             version = tempLocale.toDouble(kdenliveDoc.attribute(QStringLiteral("version")), &ok);
0108             if (!ok) {
0109                 version = kdenliveDoc.attribute(QStringLiteral("version")).toDouble(&ok);
0110             }
0111             if (!ok) {
0112                 // Last try: replace comma with a dot
0113                 QString versionString = kdenliveDoc.attribute(QStringLiteral("version"));
0114                 if (versionString.contains(QLatin1Char(','))) {
0115                     versionString.replace(QLatin1Char(','), QLatin1Char('.'));
0116                 }
0117                 version = versionString.toDouble(&ok);
0118                 if (!ok) {
0119                     qCDebug(KDENLIVE_LOG) << "// CANNOT PARSE VERSION NUMBER, ERROR!";
0120                 }
0121             }
0122         }
0123     }
0124     if (qFuzzyIsNull(version)) {
0125         // version missing, try with latest
0126         KMessageBox::error(QApplication::activeWindow(), i18n("Version of the project file cannot be read.\nAttempting to open nonetheless."),
0127                            i18n("Incorrect project file"));
0128         version = currentVersion;
0129     }
0130     int mltMajorVersion = 0;
0131     int mltServiceVersion = 0;
0132     int mltPatchVersion = 0;
0133     const QString mltVersion = mlt.attribute(QStringLiteral("version"));
0134     const QStringList v = mltVersion.split(QLatin1Char('.'));
0135     if (v.size() > 2) {
0136         mltMajorVersion = v.at(0).toInt();
0137         mltServiceVersion = v.at(1).toInt();
0138         mltPatchVersion = v.at(2).toInt();
0139     }
0140     qDebug() << "FOUND MLT PROJECT VERSION: " << mltMajorVersion << " / " << mltServiceVersion << " / " << mltPatchVersion;
0141     if (mltMajorVersion <= 7 && mltServiceVersion <= 15) {
0142         // MLT <= 7.15.0 used the mute_on_pause property that is now deprecated and breaks audio playback so remove it
0143         QDomNodeList producers = m_doc.elementsByTagName(QStringLiteral("producer"));
0144         QDomNodeList chains = m_doc.elementsByTagName(QStringLiteral("chain"));
0145         int max = producers.count();
0146         for (int i = 0; i < max; ++i) {
0147             QDomElement t = producers.at(i).toElement();
0148             Xml::removeXmlProperty(t, QStringLiteral("mute_on_pause"));
0149         }
0150         max = chains.count();
0151         for (int i = 0; i < max; ++i) {
0152             QDomElement t = chains.at(i).toElement();
0153             Xml::removeXmlProperty(t, QStringLiteral("mute_on_pause"));
0154         }
0155     }
0156 
0157     // Upgrade the document to the latest version
0158     if (!upgrade(version, currentVersion)) {
0159         return QPair<bool, QString>(false, QString());
0160     }
0161 
0162     if (version < 0.97) {
0163         checkOrphanedProducers();
0164     }
0165 
0166     QString changedDecimalPoint;
0167     if (version < 1.00) {
0168         changedDecimalPoint = upgradeTo100(documentLocale);
0169     }
0170 
0171     return QPair<bool, QString>(true, changedDecimalPoint);
0172 }
0173 
0174 bool DocumentValidator::upgrade(double version, const double currentVersion)
0175 {
0176     qCDebug(KDENLIVE_LOG) << "Opening a document with version " << version << " / " << currentVersion;
0177 
0178     // No conversion needed
0179     if (qFuzzyCompare(version, currentVersion)) {
0180         return true;
0181     }
0182 
0183     // The document is too new
0184     if (version > currentVersion) {
0185         // qCDebug(KDENLIVE_LOG) << "Unable to open document with version " << version;
0186         KMessageBox::error(
0187             QApplication::activeWindow(),
0188             i18n("This project type is unsupported (version %1) and cannot be loaded.\nPlease consider upgrading your Kdenlive version.", version),
0189             i18n("Unable to open project"));
0190         return false;
0191     }
0192 
0193     // Unsupported document versions
0194     if (qFuzzyCompare(version, 0.5) || qFuzzyCompare(version, 0.7)) {
0195         // 0.7 is unsupported
0196         // qCDebug(KDENLIVE_LOG) << "Unable to open document with version " << version;
0197         KMessageBox::error(QApplication::activeWindow(), i18n("This project type is unsupported (version %1) and cannot be loaded.", version),
0198                            i18n("Unable to open project"));
0199         return false;
0200     }
0201 
0202     // <kdenlivedoc />
0203     QDomNode infoXmlNode;
0204     QDomElement infoXml;
0205     QDomNodeList docs = m_doc.elementsByTagName(QStringLiteral("kdenlivedoc"));
0206     if (!docs.isEmpty()) {
0207         infoXmlNode = m_doc.elementsByTagName(QStringLiteral("kdenlivedoc")).at(0);
0208         infoXml = infoXmlNode.toElement();
0209         infoXml.setAttribute(QStringLiteral("upgraded"), 1);
0210     }
0211     m_doc.documentElement().setAttribute(QStringLiteral("upgraded"), 1);
0212 
0213     if (version <= 0.6) {
0214         QDomElement infoXml_old = infoXmlNode.cloneNode(true).toElement(); // Needed for folders
0215         QDomNode westley = m_doc.elementsByTagName(QStringLiteral("westley")).at(1);
0216         QDomNode tractor = m_doc.elementsByTagName(QStringLiteral("tractor")).at(0);
0217         QDomNode multitrack = m_doc.elementsByTagName(QStringLiteral("multitrack")).at(0);
0218         QDomNodeList playlists = m_doc.elementsByTagName(QStringLiteral("playlist"));
0219 
0220         QDomNode props = m_doc.elementsByTagName(QStringLiteral("properties")).at(0).toElement();
0221         QString profile = props.toElement().attribute(QStringLiteral("videoprofile"));
0222         int startPos = props.toElement().attribute(QStringLiteral("timeline_position")).toInt();
0223         if (profile == QLatin1String("dv_wide")) {
0224             profile = QStringLiteral("dv_pal_wide");
0225         }
0226 
0227         // move playlists outside of tractor and add the tracks instead
0228         int max = playlists.count();
0229         if (westley.isNull()) {
0230             westley = m_doc.createElement(QStringLiteral("westley"));
0231             m_doc.documentElement().appendChild(westley);
0232         }
0233         if (tractor.isNull()) {
0234             // qCDebug(KDENLIVE_LOG) << "// NO MLT PLAYLIST, building empty one";
0235             QDomElement blank_tractor = m_doc.createElement(QStringLiteral("tractor"));
0236             westley.appendChild(blank_tractor);
0237             QDomElement blank_playlist = m_doc.createElement(QStringLiteral("playlist"));
0238             blank_playlist.setAttribute(QStringLiteral("id"), QStringLiteral("black_track"));
0239             westley.insertBefore(blank_playlist, QDomNode());
0240             QDomElement blank_track = m_doc.createElement(QStringLiteral("track"));
0241             blank_track.setAttribute(QStringLiteral("producer"), QStringLiteral("black_track"));
0242             blank_tractor.appendChild(blank_track);
0243 
0244             QDomNodeList kdenlivetracks = m_doc.elementsByTagName(QStringLiteral("kdenlivetrack"));
0245             for (int i = 0; i < kdenlivetracks.count(); ++i) {
0246                 blank_playlist = m_doc.createElement(QStringLiteral("playlist"));
0247                 blank_playlist.setAttribute(QStringLiteral("id"), QStringLiteral("playlist") + QString::number(i));
0248                 westley.insertBefore(blank_playlist, QDomNode());
0249                 blank_track = m_doc.createElement(QStringLiteral("track"));
0250                 blank_track.setAttribute(QStringLiteral("producer"), QStringLiteral("playlist") + QString::number(i));
0251                 blank_tractor.appendChild(blank_track);
0252                 if (kdenlivetracks.at(i).toElement().attribute(QStringLiteral("cliptype")) == QLatin1String("Sound")) {
0253                     blank_playlist.setAttribute(QStringLiteral("hide"), QStringLiteral("video"));
0254                     blank_track.setAttribute(QStringLiteral("hide"), QStringLiteral("video"));
0255                 }
0256             }
0257         } else
0258             for (int i = 0; i < max; ++i) {
0259                 QDomNode n = playlists.at(i);
0260                 westley.insertBefore(n, QDomNode());
0261                 QDomElement pl = n.toElement();
0262                 QDomElement track = m_doc.createElement(QStringLiteral("track"));
0263                 QString trackType = pl.attribute(QStringLiteral("hide"));
0264                 if (!trackType.isEmpty()) {
0265                     track.setAttribute(QStringLiteral("hide"), trackType);
0266                 }
0267                 QString playlist_id = pl.attribute(QStringLiteral("id"));
0268                 if (playlist_id.isEmpty()) {
0269                     playlist_id = QStringLiteral("black_track");
0270                     pl.setAttribute(QStringLiteral("id"), playlist_id);
0271                 }
0272                 track.setAttribute(QStringLiteral("producer"), playlist_id);
0273 // tractor.appendChild(track);
0274 #define KEEP_TRACK_ORDER 1
0275 #ifdef KEEP_TRACK_ORDER
0276                 tractor.insertAfter(track, QDomNode());
0277 #else
0278                 // Insert the new track in an order that hopefully matches the 3 video, then 2 audio tracks of Kdenlive 0.7.0
0279                 // insertion sort - O( tracks*tracks )
0280                 // Note, this breaks _all_ transitions - but you can move them up and down afterwards.
0281                 QDomElement tractor_elem = tractor.toElement();
0282                 if (!tractor_elem.isNull()) {
0283                     QDomNodeList tracks = tractor_elem.elementsByTagName("track");
0284                     int size = tracks.size();
0285                     if (size == 0) {
0286                         tractor.insertAfter(track, QDomNode());
0287                     } else {
0288                         bool inserted = false;
0289                         for (int i = 0; i < size; ++i) {
0290                             QDomElement track_elem = tracks.at(i).toElement();
0291                             if (track_elem.isNull()) {
0292                                 tractor.insertAfter(track, QDomNode());
0293                                 inserted = true;
0294                                 break;
0295                             } else {
0296                                 // qCDebug(KDENLIVE_LOG) << "playlist_id: " << playlist_id << " producer:" << track_elem.attribute("producer");
0297                                 if (playlist_id < track_elem.attribute("producer")) {
0298                                     tractor.insertBefore(track, track_elem);
0299                                     inserted = true;
0300                                     break;
0301                                 }
0302                             }
0303                         }
0304                         // Reach here, no insertion, insert last
0305                         if (!inserted) {
0306                             tractor.insertAfter(track, QDomNode());
0307                         }
0308                     }
0309                 } else {
0310                     qCWarning(KDENLIVE_LOG) << "tractor was not a QDomElement";
0311                     tractor.insertAfter(track, QDomNode());
0312                 }
0313 #endif
0314             }
0315         tractor.removeChild(multitrack);
0316 
0317         // audio track mixing transitions should not be added to track view, so add required attribute
0318         QDomNodeList transitions = m_doc.elementsByTagName(QStringLiteral("transition"));
0319         max = transitions.count();
0320         for (int i = 0; i < max; ++i) {
0321             QDomElement tr = transitions.at(i).toElement();
0322             if (tr.attribute(QStringLiteral("combine")) == QLatin1String("1") && tr.attribute(QStringLiteral("mlt_service")) == QLatin1String("mix")) {
0323                 QDomElement property = m_doc.createElement(QStringLiteral("property"));
0324                 property.setAttribute(QStringLiteral("name"), QStringLiteral("internal_added"));
0325                 QDomText value = m_doc.createTextNode(QStringLiteral("237"));
0326                 property.appendChild(value);
0327                 tr.appendChild(property);
0328                 property = m_doc.createElement(QStringLiteral("property"));
0329                 property.setAttribute(QStringLiteral("name"), QStringLiteral("mlt_service"));
0330                 value = m_doc.createTextNode(QStringLiteral("mix"));
0331                 property.appendChild(value);
0332                 tr.appendChild(property);
0333             } else {
0334                 // convert transition
0335                 QDomNamedNodeMap attrs = tr.attributes();
0336                 for (int j = 0; j < attrs.count(); ++j) {
0337                     QString attrName = attrs.item(j).nodeName();
0338                     if (attrName != QLatin1String("in") && attrName != QLatin1String("out") && attrName != QLatin1String("id")) {
0339                         QDomElement property = m_doc.createElement(QStringLiteral("property"));
0340                         property.setAttribute(QStringLiteral("name"), attrName);
0341                         QDomText value = m_doc.createTextNode(attrs.item(j).nodeValue());
0342                         property.appendChild(value);
0343                         tr.appendChild(property);
0344                     }
0345                 }
0346             }
0347         }
0348 
0349         // move transitions after tracks
0350         for (int i = 0; i < max; ++i) {
0351             tractor.insertAfter(transitions.at(0), QDomNode());
0352         }
0353 
0354         // Fix filters format
0355         QDomNodeList entries = m_doc.elementsByTagName(QStringLiteral("entry"));
0356         max = entries.count();
0357         for (int i = 0; i < max; ++i) {
0358             QString last_id;
0359             int effectix = 0;
0360             QDomNode m = entries.at(i).firstChild();
0361             while (!m.isNull()) {
0362                 if (m.toElement().tagName() == QLatin1String("filter")) {
0363                     QDomElement filt = m.toElement();
0364                     QDomNamedNodeMap attrs = filt.attributes();
0365                     QString current_id = filt.attribute(QStringLiteral("kdenlive_id"));
0366                     if (current_id != last_id) {
0367                         effectix++;
0368                         last_id = current_id;
0369                     }
0370                     QDomElement e = m_doc.createElement(QStringLiteral("property"));
0371                     e.setAttribute(QStringLiteral("name"), QStringLiteral("kdenlive_ix"));
0372                     QDomText value = m_doc.createTextNode(QString::number(effectix));
0373                     e.appendChild(value);
0374                     filt.appendChild(e);
0375                     for (int j = 0; j < attrs.count(); ++j) {
0376                         QDomAttr a = attrs.item(j).toAttr();
0377                         if (!a.isNull()) {
0378                             // qCDebug(KDENLIVE_LOG) << " FILTER; adding :" << a.name() << ':' << a.value();
0379                             auto property = m_doc.createElement(QStringLiteral("property"));
0380                             property.setAttribute(QStringLiteral("name"), a.name());
0381                             auto property_value = m_doc.createTextNode(a.value());
0382                             property.appendChild(property_value);
0383                             filt.appendChild(property);
0384                         }
0385                     }
0386                 }
0387                 m = m.nextSibling();
0388             }
0389         }
0390 
0391         // fix slowmotion
0392         QDomNodeList producers = westley.toElement().elementsByTagName(QStringLiteral("producer"));
0393         max = producers.count();
0394         for (int i = 0; i < max; ++i) {
0395             QDomElement prod = producers.at(i).toElement();
0396             if (prod.attribute(QStringLiteral("mlt_service")) == QLatin1String("framebuffer")) {
0397                 QString slowmotionprod = prod.attribute(QStringLiteral("resource"));
0398                 slowmotionprod.replace(QLatin1Char(':'), QLatin1Char('?'));
0399                 // qCDebug(KDENLIVE_LOG) << "// FOUND WRONG SLOWMO, new: " << slowmotionprod;
0400                 prod.setAttribute(QStringLiteral("resource"), slowmotionprod);
0401             }
0402         }
0403         // move producers to correct place, markers to a global list, fix clip descriptions
0404         QDomElement markers = m_doc.createElement(QStringLiteral("markers"));
0405         // This will get the xml producers:
0406         producers = m_doc.elementsByTagName(QStringLiteral("producer"));
0407         max = producers.count();
0408         for (int i = 0; i < max; ++i) {
0409             QDomElement prod = producers.at(0).toElement();
0410             QDomNode m = prod.firstChild();
0411             if (!m.isNull()) {
0412                 if (m.toElement().tagName() == QLatin1String("markers")) {
0413                     QDomNodeList prodchilds = m.childNodes();
0414                     int maxchild = prodchilds.count();
0415                     for (int k = 0; k < maxchild; ++k) {
0416                         QDomElement mark = prodchilds.at(0).toElement();
0417                         mark.setAttribute(QStringLiteral("id"), prod.attribute(QStringLiteral("id")));
0418                         markers.insertAfter(mark, QDomNode());
0419                     }
0420                     prod.removeChild(m);
0421                 } else if (prod.attribute(QStringLiteral("type")).toInt() == int(ClipType::Text)) {
0422                     // convert title clip
0423                     if (m.toElement().tagName() == QLatin1String("textclip")) {
0424                         QDomDocument tdoc;
0425                         QDomElement titleclip = m.toElement();
0426                         QDomElement title = tdoc.createElement(QStringLiteral("kdenlivetitle"));
0427                         tdoc.appendChild(title);
0428                         QDomNodeList objects = titleclip.childNodes();
0429                         int maxchild = objects.count();
0430                         for (int k = 0; k < maxchild; ++k) {
0431                             QDomElement ob = objects.at(k).toElement();
0432                             if (ob.attribute(QStringLiteral("type")) == QLatin1String("3")) {
0433                                 // text object - all of this goes into "xmldata"...
0434                                 QDomElement item = tdoc.createElement(QStringLiteral("item"));
0435                                 item.setAttribute(QStringLiteral("z-index"), ob.attribute(QStringLiteral("z")));
0436                                 item.setAttribute(QStringLiteral("type"), QStringLiteral("QGraphicsTextItem"));
0437                                 QDomElement position = tdoc.createElement(QStringLiteral("position"));
0438                                 position.setAttribute(QStringLiteral("x"), ob.attribute(QStringLiteral("x")));
0439                                 position.setAttribute(QStringLiteral("y"), ob.attribute(QStringLiteral("y")));
0440                                 QDomElement content = tdoc.createElement(QStringLiteral("content"));
0441                                 content.setAttribute(QStringLiteral("font"), ob.attribute(QStringLiteral("font_family")));
0442                                 content.setAttribute(QStringLiteral("font-size"), ob.attribute(QStringLiteral("font_size")));
0443                                 content.setAttribute(QStringLiteral("font-bold"), ob.attribute(QStringLiteral("bold")));
0444                                 content.setAttribute(QStringLiteral("font-italic"), ob.attribute(QStringLiteral("italic")));
0445                                 content.setAttribute(QStringLiteral("font-underline"), ob.attribute(QStringLiteral("underline")));
0446                                 QString col = ob.attribute(QStringLiteral("color"));
0447                                 QColor c(col);
0448                                 content.setAttribute(QStringLiteral("font-color"), colorToString(c));
0449                                 // todo: These fields are missing from the newly generated xmldata:
0450                                 // transform, startviewport, endviewport, background
0451 
0452                                 QDomText conttxt = tdoc.createTextNode(ob.attribute(QStringLiteral("text")));
0453                                 content.appendChild(conttxt);
0454                                 item.appendChild(position);
0455                                 item.appendChild(content);
0456                                 title.appendChild(item);
0457                             } else if (ob.attribute(QStringLiteral("type")) == QLatin1String("5")) {
0458                                 // rectangle object
0459                                 QDomElement item = tdoc.createElement(QStringLiteral("item"));
0460                                 item.setAttribute(QStringLiteral("z-index"), ob.attribute(QStringLiteral("z")));
0461                                 item.setAttribute(QStringLiteral("type"), QStringLiteral("QGraphicsRectItem"));
0462                                 QDomElement position = tdoc.createElement(QStringLiteral("position"));
0463                                 position.setAttribute(QStringLiteral("x"), ob.attribute(QStringLiteral("x")));
0464                                 position.setAttribute(QStringLiteral("y"), ob.attribute(QStringLiteral("y")));
0465                                 QDomElement content = tdoc.createElement(QStringLiteral("content"));
0466                                 QString col = ob.attribute(QStringLiteral("color"));
0467                                 QColor c(col);
0468                                 content.setAttribute(QStringLiteral("brushcolor"), colorToString(c));
0469                                 QString rect = QStringLiteral("0,0,");
0470                                 rect.append(ob.attribute(QStringLiteral("width")));
0471                                 rect.append(QLatin1String(","));
0472                                 rect.append(ob.attribute(QStringLiteral("height")));
0473                                 content.setAttribute(QStringLiteral("rect"), rect);
0474                                 item.appendChild(position);
0475                                 item.appendChild(content);
0476                                 title.appendChild(item);
0477                             }
0478                         }
0479                         prod.setAttribute(QStringLiteral("xmldata"), tdoc.toString());
0480                         prod.removeChild(m);
0481                     } // End conversion of title clips.
0482 
0483                 } else if (m.isText()) {
0484                     QString comment = m.nodeValue();
0485                     if (!comment.isEmpty()) {
0486                         prod.setAttribute(QStringLiteral("description"), comment);
0487                     }
0488                     prod.removeChild(m);
0489                 }
0490             }
0491             int duration = prod.attribute(QStringLiteral("duration")).toInt();
0492             if (duration > 0) {
0493                 prod.setAttribute(QStringLiteral("out"), QString::number(duration));
0494             }
0495             // The clip goes back in, but text clips should not go back in, at least not modified
0496             westley.insertBefore(prod, QDomNode());
0497         }
0498 
0499         QDomNode westley0 = m_doc.elementsByTagName(QStringLiteral("westley")).at(0);
0500         if (!markers.firstChild().isNull()) {
0501             westley0.appendChild(markers);
0502         }
0503 
0504         /*
0505          * Convert as much of the kdenlivedoc as possible. Use the producer in
0506          * westley. First, remove the old stuff from westley, and add a new
0507          * empty one. Also, track the max id in order to use it for the adding
0508          * of groups/folders
0509          */
0510         int max_kproducer_id = 0;
0511         westley0.removeChild(infoXmlNode);
0512         QDomElement infoXml_new = m_doc.createElement(QStringLiteral("kdenlivedoc"));
0513         infoXml_new.setAttribute(QStringLiteral("profile"), profile);
0514         infoXml.setAttribute(QStringLiteral("position"), startPos);
0515 
0516         // Add all the producers that has a resource in westley
0517         QDomElement westley_element = westley0.toElement();
0518         if (westley_element.isNull()) {
0519             qCWarning(KDENLIVE_LOG) << "westley0 element in document was not a QDomElement - unable to add producers to new kdenlivedoc";
0520         } else {
0521             QDomNodeList wproducers = westley_element.elementsByTagName(QStringLiteral("producer"));
0522             int kmax = wproducers.count();
0523             for (int i = 0; i < kmax; ++i) {
0524                 QDomElement wproducer = wproducers.at(i).toElement();
0525                 if (wproducer.isNull()) {
0526                     qCWarning(KDENLIVE_LOG) << "Found producer in westley0, that was not a QDomElement";
0527                     continue;
0528                 }
0529                 if (wproducer.attribute(QStringLiteral("id")) == QLatin1String("black")) {
0530                     continue;
0531                 }
0532                 // We have to do slightly different things, depending on the type
0533                 // qCDebug(KDENLIVE_LOG) << "Converting producer element with type" << wproducer.attribute("type");
0534                 if (wproducer.attribute(QStringLiteral("type")).toInt() == int(ClipType::Text)) {
0535                     // qCDebug(KDENLIVE_LOG) << "Found TEXT element in producer" << endl;
0536                     QDomElement kproducer = wproducer.cloneNode(true).toElement();
0537                     kproducer.setTagName(QStringLiteral("kdenlive_producer"));
0538                     infoXml_new.appendChild(kproducer);
0539                     /*
0540                      * TODO: Perhaps needs some more changes here to
0541                      * "frequency", aspect ratio as a float, frame_size,
0542                      * channels, and later, resource and title name
0543                      */
0544                 } else {
0545                     QDomElement kproducer = m_doc.createElement(QStringLiteral("kdenlive_producer"));
0546                     kproducer.setAttribute(QStringLiteral("id"), wproducer.attribute(QStringLiteral("id")));
0547                     if (!wproducer.attribute(QStringLiteral("description")).isEmpty()) {
0548                         kproducer.setAttribute(QStringLiteral("description"), wproducer.attribute(QStringLiteral("description")));
0549                     }
0550                     kproducer.setAttribute(QStringLiteral("resource"), wproducer.attribute(QStringLiteral("resource")));
0551                     kproducer.setAttribute(QStringLiteral("type"), wproducer.attribute(QStringLiteral("type")));
0552                     // Testing fix for 358
0553                     if (!wproducer.attribute(QStringLiteral("aspect_ratio")).isEmpty()) {
0554                         kproducer.setAttribute(QStringLiteral("aspect_ratio"), wproducer.attribute(QStringLiteral("aspect_ratio")));
0555                     }
0556                     if (!wproducer.attribute(QStringLiteral("source_fps")).isEmpty()) {
0557                         kproducer.setAttribute(QStringLiteral("fps"), wproducer.attribute(QStringLiteral("source_fps")));
0558                     }
0559                     if (!wproducer.attribute(QStringLiteral("length")).isEmpty()) {
0560                         kproducer.setAttribute(QStringLiteral("duration"), wproducer.attribute(QStringLiteral("length")));
0561                     }
0562                     infoXml_new.appendChild(kproducer);
0563                 }
0564                 if (wproducer.attribute(QStringLiteral("id")).toInt() > max_kproducer_id) {
0565                     max_kproducer_id = wproducer.attribute(QStringLiteral("id")).toInt();
0566                 }
0567             }
0568         }
0569 #define LOOKUP_FOLDER 1
0570 #ifdef LOOKUP_FOLDER
0571         /*
0572          * Look through all the folder elements of the old doc, for each folder,
0573          * for each producer, get the id, look it up in the new doc, set the
0574          * groupname and groupid. Note, this does not work at the moment - at
0575          * least one folder shows up missing, and clips with no folder does not
0576          * show up.
0577          */
0578         // QDomElement infoXml_old = infoXmlNode.toElement();
0579         if (!infoXml_old.isNull()) {
0580             QDomNodeList folders = infoXml_old.elementsByTagName(QStringLiteral("folder"));
0581             int fsize = folders.size();
0582             int groupId = max_kproducer_id + 1; // Start at +1 of max id of the kdenlive_producers
0583             for (int i = 0; i < fsize; ++i) {
0584                 QDomElement folder = folders.at(i).toElement();
0585                 if (!folder.isNull()) {
0586                     QString groupName = folder.attribute(QStringLiteral("name"));
0587                     // qCDebug(KDENLIVE_LOG) << "groupName: " << groupName << " with groupId: " << groupId;
0588                     QDomNodeList fproducers = folder.elementsByTagName(QStringLiteral("producer"));
0589                     int psize = fproducers.size();
0590                     for (int j = 0; j < psize; ++j) {
0591                         QDomElement fproducer = fproducers.at(j).toElement();
0592                         if (!fproducer.isNull()) {
0593                             QString id = fproducer.attribute(QStringLiteral("id"));
0594                             // This is not very effective, but compared to loading the clips, its a breeze
0595                             QDomNodeList kdenlive_producers = infoXml_new.elementsByTagName(QStringLiteral("kdenlive_producer"));
0596                             int kpsize = kdenlive_producers.size();
0597                             for (int k = 0; k < kpsize; ++k) {
0598                                 QDomElement kproducer = kdenlive_producers.at(k).toElement(); // Its an element for sure
0599                                 if (id == kproducer.attribute(QStringLiteral("id"))) {
0600                                     // We do not check that it already is part of a folder
0601                                     kproducer.setAttribute(QStringLiteral("groupid"), groupId);
0602                                     kproducer.setAttribute(QStringLiteral("groupname"), groupName);
0603                                     break;
0604                                 }
0605                             }
0606                         }
0607                     }
0608                     ++groupId;
0609                 }
0610             }
0611         }
0612 #endif
0613         QDomNodeList elements = westley.childNodes();
0614         max = elements.count();
0615         for (int i = 0; i < max; ++i) {
0616             QDomElement prod = elements.at(0).toElement();
0617             westley0.insertAfter(prod, QDomNode());
0618         }
0619 
0620         westley0.appendChild(infoXml_new);
0621 
0622         westley0.removeChild(westley);
0623 
0624         // adds <avfile /> information to <kdenlive_producer />
0625         QDomNodeList kproducers = m_doc.elementsByTagName(QStringLiteral("kdenlive_producer"));
0626         QDomNodeList avfiles = infoXml_old.elementsByTagName(QStringLiteral("avfile"));
0627         // qCDebug(KDENLIVE_LOG) << "found" << avfiles.count() << "<avfile />s and" << kproducers.count() << "<kdenlive_producer />s";
0628         for (int i = 0; i < avfiles.count(); ++i) {
0629             QDomElement avfile = avfiles.at(i).toElement();
0630             QDomElement kproducer;
0631             if (avfile.isNull()) {
0632                 qCWarning(KDENLIVE_LOG) << "found an <avfile /> that is not a QDomElement";
0633             } else {
0634                 QString id = avfile.attribute(QStringLiteral("id"));
0635                 // this is horrible, must be rewritten, it's just for test
0636                 for (int j = 0; j < kproducers.count(); ++j) {
0637                     ////qCDebug(KDENLIVE_LOG) << "checking <kdenlive_producer /> with id" << kproducers.at(j).toElement().attribute("id");
0638                     if (kproducers.at(j).toElement().attribute(QStringLiteral("id")) == id) {
0639                         kproducer = kproducers.at(j).toElement();
0640                         break;
0641                     }
0642                 }
0643                 if (kproducer == QDomElement()) {
0644                     qCWarning(KDENLIVE_LOG) << "no match for <avfile /> with id =" << id;
0645                 } else {
0646                     ////qCDebug(KDENLIVE_LOG) << "ready to set additional <avfile />'s attributes (id =" << id << ')';
0647                     kproducer.setAttribute(QStringLiteral("channels"), avfile.attribute(QStringLiteral("channels")));
0648                     kproducer.setAttribute(QStringLiteral("duration"), avfile.attribute(QStringLiteral("duration")));
0649                     kproducer.setAttribute(QStringLiteral("frame_size"),
0650                                            avfile.attribute(QStringLiteral("width")) + QLatin1Char('x') + avfile.attribute(QStringLiteral("height")));
0651                     kproducer.setAttribute(QStringLiteral("frequency"), avfile.attribute(QStringLiteral("frequency")));
0652                     if (kproducer.attribute(QStringLiteral("description")).isEmpty() && !avfile.attribute(QStringLiteral("description")).isEmpty()) {
0653                         kproducer.setAttribute(QStringLiteral("description"), avfile.attribute(QStringLiteral("description")));
0654                     }
0655                 }
0656             }
0657         }
0658         infoXml = infoXml_new;
0659     }
0660 
0661     if (version <= 0.81) {
0662         // Add the tracks information
0663         QString tracksOrder = infoXml.attribute(QStringLiteral("tracks"));
0664         if (tracksOrder.isEmpty()) {
0665             QDomNodeList tracks = m_doc.elementsByTagName(QStringLiteral("track"));
0666             for (int i = 0; i < tracks.count(); ++i) {
0667                 QDomElement track = tracks.at(i).toElement();
0668                 if (track.attribute(QStringLiteral("producer")) != QLatin1String("black_track")) {
0669                     if (track.attribute(QStringLiteral("hide")) == QLatin1String("video")) {
0670                         tracksOrder.append(QLatin1Char('a'));
0671                     } else {
0672                         tracksOrder.append(QLatin1Char('v'));
0673                     }
0674                 }
0675             }
0676         }
0677         QDomElement tracksinfo = m_doc.createElement(QStringLiteral("tracksinfo"));
0678         for (int i = 0; i < tracksOrder.size(); ++i) {
0679             QDomElement trackinfo = m_doc.createElement(QStringLiteral("trackinfo"));
0680             if (tracksOrder.data()[i] == QLatin1Char('a')) {
0681                 trackinfo.setAttribute(QStringLiteral("type"), QStringLiteral("audio"));
0682                 trackinfo.setAttribute(QStringLiteral("blind"), 1);
0683             } else {
0684                 trackinfo.setAttribute(QStringLiteral("blind"), 0);
0685             }
0686             trackinfo.setAttribute(QStringLiteral("mute"), 0);
0687             trackinfo.setAttribute(QStringLiteral("locked"), 0);
0688             tracksinfo.appendChild(trackinfo);
0689         }
0690         infoXml.appendChild(tracksinfo);
0691     }
0692 
0693     if (version <= 0.82) {
0694         // Convert <westley />s in <mlt />s (MLT extreme makeover)
0695         QDomNodeList westleyNodes = m_doc.elementsByTagName(QStringLiteral("westley"));
0696         for (int i = 0; i < westleyNodes.count(); ++i) {
0697             QDomElement westley = westleyNodes.at(i).toElement();
0698             westley.setTagName(QStringLiteral("mlt"));
0699         }
0700     }
0701 
0702     if (version <= 0.83) {
0703         // Replace point size with pixel size in text titles
0704         if (m_doc.toString().contains(QStringLiteral("font-size"))) {
0705             KMessageBox::ButtonCode convert = KMessageBox::Continue;
0706             QDomNodeList kproducerNodes = m_doc.elementsByTagName(QStringLiteral("kdenlive_producer"));
0707             for (int i = 0; i < kproducerNodes.count() && convert != KMessageBox::SecondaryAction; ++i) {
0708                 QDomElement kproducer = kproducerNodes.at(i).toElement();
0709                 if (kproducer.attribute(QStringLiteral("type")).toInt() == int(ClipType::Text)) {
0710                     QDomDocument data;
0711                     data.setContent(kproducer.attribute(QStringLiteral("xmldata")));
0712                     QDomNodeList items = data.firstChild().childNodes();
0713                     for (int j = 0; j < items.count() && convert != KMessageBox::SecondaryAction; ++j) {
0714                         if (items.at(j).attributes().namedItem(QStringLiteral("type")).nodeValue() == QLatin1String("QGraphicsTextItem")) {
0715                             QDomNamedNodeMap textProperties = items.at(j).namedItem(QStringLiteral("content")).attributes();
0716                             if (textProperties.namedItem(QStringLiteral("font-pixel-size")).isNull() &&
0717                                 !textProperties.namedItem(QStringLiteral("font-size")).isNull()) {
0718                                 // Ask the user if he wants to convert
0719                                 if (convert != KMessageBox::PrimaryAction && convert != KMessageBox::SecondaryAction) {
0720                                     convert = KMessageBox::ButtonCode(KMessageBox::warningTwoActions(
0721                                         QApplication::activeWindow(),
0722                                         i18n("Some of your text clips were saved with size in points, which means different sizes on different displays. Do "
0723                                              "you want to convert them to pixel size, making them portable? It is recommended you do this on the computer they "
0724                                              "were first created on, or you could have to adjust their size."),
0725                                         i18n("Update Text Clips"), KGuiItem(i18n("Convert")), KStandardGuiItem::cancel()));
0726                                 }
0727                                 if (convert == KMessageBox::PrimaryAction) {
0728                                     QFont font;
0729                                     font.setPointSize(textProperties.namedItem(QStringLiteral("font-size")).nodeValue().toInt());
0730                                     QDomElement content = items.at(j).namedItem(QStringLiteral("content")).toElement();
0731                                     content.setAttribute(QStringLiteral("font-pixel-size"), QFontInfo(font).pixelSize());
0732                                     content.removeAttribute(QStringLiteral("font-size"));
0733                                     kproducer.setAttribute(QStringLiteral("xmldata"), data.toString());
0734                                     /*
0735                                      * You may be tempted to delete the preview file
0736                                      * to force its recreation: bad idea (see
0737                                      * http://www.kdenlive.org/mantis/view.php?id=749)
0738                                      */
0739                                 }
0740                             }
0741                         }
0742                     }
0743                 }
0744             }
0745         }
0746 
0747         // Fill the <documentproperties /> element
0748         QDomElement docProperties = infoXml.firstChildElement(QStringLiteral("documentproperties"));
0749         if (docProperties.isNull()) {
0750             docProperties = m_doc.createElement(QStringLiteral("documentproperties"));
0751             docProperties.setAttribute(QStringLiteral("zonein"), infoXml.attribute(QStringLiteral("zonein")));
0752             docProperties.setAttribute(QStringLiteral("zoneout"), infoXml.attribute(QStringLiteral("zoneout")));
0753             docProperties.setAttribute(QStringLiteral("zoom"), infoXml.attribute(QStringLiteral("zoom")));
0754             docProperties.setAttribute(QStringLiteral("position"), infoXml.attribute(QStringLiteral("position")));
0755             infoXml.appendChild(docProperties);
0756         }
0757     }
0758 
0759     if (version <= 0.84) {
0760         // update the title clips to use the new MLT kdenlivetitle producer
0761         QDomNodeList kproducerNodes = m_doc.elementsByTagName(QStringLiteral("kdenlive_producer"));
0762         for (int i = 0; i < kproducerNodes.count(); ++i) {
0763             QDomElement kproducer = kproducerNodes.at(i).toElement();
0764             if (kproducer.attribute(QStringLiteral("type")).toInt() == int(ClipType::Text)) {
0765                 QString data = kproducer.attribute(QStringLiteral("xmldata"));
0766                 QString datafile = kproducer.attribute(QStringLiteral("resource"));
0767                 if (!datafile.endsWith(QLatin1String(".kdenlivetitle"))) {
0768                     datafile = QString();
0769                     kproducer.setAttribute(QStringLiteral("resource"), QString());
0770                 }
0771                 QString id = kproducer.attribute(QStringLiteral("id"));
0772                 QDomNodeList mltproducers = m_doc.elementsByTagName(QStringLiteral("producer"));
0773                 bool foundData = false;
0774                 bool foundResource = false;
0775                 bool foundService = false;
0776                 for (int j = 0; j < mltproducers.count(); ++j) {
0777                     QDomElement wproducer = mltproducers.at(j).toElement();
0778                     if (wproducer.attribute(QStringLiteral("id")) == id) {
0779                         QDomNodeList props = wproducer.childNodes();
0780                         for (int k = 0; k < props.count(); ++k) {
0781                             if (props.at(k).toElement().attribute(QStringLiteral("name")) == QLatin1String("xmldata")) {
0782                                 props.at(k).firstChild().setNodeValue(data);
0783                                 foundData = true;
0784                             } else if (props.at(k).toElement().attribute(QStringLiteral("name")) == QLatin1String("mlt_service")) {
0785                                 props.at(k).firstChild().setNodeValue(QStringLiteral("kdenlivetitle"));
0786                                 foundService = true;
0787                             } else if (props.at(k).toElement().attribute(QStringLiteral("name")) == QLatin1String("resource")) {
0788                                 props.at(k).firstChild().setNodeValue(datafile);
0789                                 foundResource = true;
0790                             }
0791                         }
0792                         if (!foundData) {
0793                             QDomElement e = m_doc.createElement(QStringLiteral("property"));
0794                             e.setAttribute(QStringLiteral("name"), QStringLiteral("xmldata"));
0795                             QDomText value = m_doc.createTextNode(data);
0796                             e.appendChild(value);
0797                             wproducer.appendChild(e);
0798                         }
0799                         if (!foundService) {
0800                             QDomElement e = m_doc.createElement(QStringLiteral("property"));
0801                             e.setAttribute(QStringLiteral("name"), QStringLiteral("mlt_service"));
0802                             QDomText value = m_doc.createTextNode(QStringLiteral("kdenlivetitle"));
0803                             e.appendChild(value);
0804                             wproducer.appendChild(e);
0805                         }
0806                         if (!foundResource) {
0807                             QDomElement e = m_doc.createElement(QStringLiteral("property"));
0808                             e.setAttribute(QStringLiteral("name"), QStringLiteral("resource"));
0809                             QDomText value = m_doc.createTextNode(datafile);
0810                             e.appendChild(value);
0811                             wproducer.appendChild(e);
0812                         }
0813                         break;
0814                     }
0815                 }
0816             }
0817         }
0818     }
0819     if (version <= 0.85) {
0820         // update the LADSPA effects to use the new ladspa.id format instead of external xml file
0821         QDomNodeList effectNodes = m_doc.elementsByTagName(QStringLiteral("filter"));
0822         for (int i = 0; i < effectNodes.count(); ++i) {
0823             QDomElement effect = effectNodes.at(i).toElement();
0824             if (Xml::getXmlProperty(effect, QStringLiteral("mlt_service")) == QLatin1String("ladspa")) {
0825                 // Needs to be converted
0826                 QStringList info = getInfoFromEffectName(Xml::getXmlProperty(effect, QStringLiteral("kdenlive_id")));
0827                 if (info.isEmpty()) {
0828                     continue;
0829                 }
0830                 // info contains the correct ladspa.id from kdenlive effect name, and a list of parameter's old and new names
0831                 Xml::setXmlProperty(effect, QStringLiteral("kdenlive_id"), info.at(0));
0832                 Xml::setXmlProperty(effect, QStringLiteral("tag"), info.at(0));
0833                 Xml::setXmlProperty(effect, QStringLiteral("mlt_service"), info.at(0));
0834                 Xml::removeXmlProperty(effect, QStringLiteral("src"));
0835                 for (int j = 1; j < info.size(); ++j) {
0836                     QString value = Xml::getXmlProperty(effect, info.at(j).section(QLatin1Char('='), 0, 0));
0837                     if (!value.isEmpty()) {
0838                         // update parameter name
0839                         Xml::renameXmlProperty(effect, info.at(j).section(QLatin1Char('='), 0, 0), info.at(j).section(QLatin1Char('='), 1, 1));
0840                     }
0841                 }
0842             }
0843         }
0844     }
0845 
0846     if (version <= 0.86) {
0847         // Make sure we don't have avformat-novalidate producers, since it caused crashes
0848         QDomNodeList producers = m_doc.elementsByTagName(QStringLiteral("producer"));
0849         int max = producers.count();
0850         for (int i = 0; i < max; ++i) {
0851             QDomElement prod = producers.at(i).toElement();
0852             if (Xml::getXmlProperty(prod, QStringLiteral("mlt_service")) == QLatin1String("avformat-novalidate")) {
0853                 Xml::setXmlProperty(prod, QStringLiteral("mlt_service"), QStringLiteral("avformat"));
0854             }
0855         }
0856 
0857         // 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
0858         // keyframe to real end of transition
0859 
0860         // Get profile info (width / height)
0861         QDomElement profile = m_doc.firstChildElement(QStringLiteral("profile"));
0862         if (profile.isNull()) {
0863             profile = infoXml.firstChildElement(QStringLiteral("profileinfo"));
0864             if (!profile.isNull()) {
0865                 // old MLT format, we need to add profile
0866                 QDomNode mlt = m_doc.firstChildElement(QStringLiteral("mlt"));
0867                 QDomNode firstProd = m_doc.firstChildElement(QStringLiteral("producer"));
0868                 QDomElement pr = profile.cloneNode().toElement();
0869                 pr.setTagName(QStringLiteral("profile"));
0870                 mlt.insertBefore(pr, firstProd);
0871             }
0872         }
0873         // TODO MLT7: port?
0874         /*int profileWidth;
0875         int profileHeight;
0876         if (profile.isNull()) {
0877             // could not find profile info, set PAL
0878             profileWidth = 720;
0879             profileHeight = 576;
0880         } else {
0881             profileWidth = profile.attribute(QStringLiteral("width")).toInt();
0882             profileHeight = profile.attribute(QStringLiteral("height")).toInt();
0883         }
0884         QDomNodeList transitions = m_doc.elementsByTagName(QStringLiteral("transition"));
0885         max = transitions.count();
0886         for (int i = 0; i < max; ++i) {
0887 
0888             QDomElement trans = transitions.at(i).toElement();
0889             int out = trans.attribute(QStringLiteral("out")).toInt() - trans.attribute(QStringLiteral("in")).toInt();
0890             QString geom = Xml::getXmlProperty(trans, QStringLiteral("geometry"));
0891             Mlt::Geometry *g = new Mlt::Geometry(geom.toUtf8().data(), out, profileWidth, profileHeight);
0892             Mlt::GeometryItem item;
0893             if (g->next_key(&item, out) == 0) {
0894                 // We have a keyframe just after last frame, try to move it to last frame
0895                 if (item.frame() == out + 1) {
0896                     item.frame(out);
0897                     g->insert(item);
0898                     g->remove(out + 1);
0899                     Xml::setXmlProperty(trans, QStringLiteral("geometry"), QString::fromLatin1(g->serialise()));
0900                 }
0901             }
0902             delete g;
0903 
0904         }*/
0905     }
0906 
0907     if (version <= 0.87) {
0908         if (!m_doc.firstChildElement(QStringLiteral("mlt")).hasAttribute(QStringLiteral("LC_NUMERIC"))) {
0909             m_doc.firstChildElement(QStringLiteral("mlt")).setAttribute(QStringLiteral("LC_NUMERIC"), QStringLiteral("C"));
0910         }
0911     }
0912 
0913     if (version <= 0.88) {
0914         // convert to new MLT-only format
0915         QDomNodeList producers = m_doc.elementsByTagName(QStringLiteral("producer"));
0916         QDomDocumentFragment frag = m_doc.createDocumentFragment();
0917 
0918         // Create Bin Playlist
0919         QDomElement main_playlist = m_doc.createElement(QStringLiteral("playlist"));
0920         QDomElement prop = m_doc.createElement(QStringLiteral("property"));
0921         prop.setAttribute(QStringLiteral("name"), QStringLiteral("xml_retain"));
0922         QDomText val = m_doc.createTextNode(QStringLiteral("1"));
0923         prop.appendChild(val);
0924         main_playlist.appendChild(prop);
0925 
0926         // Move markers
0927         QDomNodeList markers = m_doc.elementsByTagName(QStringLiteral("marker"));
0928         for (int i = 0; i < markers.count(); ++i) {
0929             QDomElement marker = markers.at(i).toElement();
0930             QDomElement property = m_doc.createElement(QStringLiteral("property"));
0931             property.setAttribute(QStringLiteral("name"), QStringLiteral("kdenlive:marker.") + marker.attribute(QStringLiteral("id")) + QLatin1Char(':') +
0932                                                               marker.attribute(QStringLiteral("time")));
0933             QDomText val_node = m_doc.createTextNode(marker.attribute(QStringLiteral("type")) + QLatin1Char(':') + marker.attribute(QStringLiteral("comment")));
0934             property.appendChild(val_node);
0935             main_playlist.appendChild(property);
0936         }
0937 
0938         // Move guides
0939         QDomNodeList guides = m_doc.elementsByTagName(QStringLiteral("guide"));
0940         for (int i = 0; i < guides.count(); ++i) {
0941             QDomElement guide = guides.at(i).toElement();
0942             QDomElement property = m_doc.createElement(QStringLiteral("property"));
0943             property.setAttribute(QStringLiteral("name"), QStringLiteral("kdenlive:guide.") + guide.attribute(QStringLiteral("time")));
0944             QDomText val_node = m_doc.createTextNode(guide.attribute(QStringLiteral("comment")));
0945             property.appendChild(val_node);
0946             main_playlist.appendChild(property);
0947         }
0948 
0949         // Move folders
0950         QDomNodeList folders = m_doc.elementsByTagName(QStringLiteral("folder"));
0951         for (int i = 0; i < folders.count(); ++i) {
0952             QDomElement folder = folders.at(i).toElement();
0953             QDomElement property = m_doc.createElement(QStringLiteral("property"));
0954             property.setAttribute(QStringLiteral("name"), QStringLiteral("kdenlive:folder.-1.") + folder.attribute(QStringLiteral("id")));
0955             QDomText val_node = m_doc.createTextNode(folder.attribute(QStringLiteral("name")));
0956             property.appendChild(val_node);
0957             main_playlist.appendChild(property);
0958         }
0959 
0960         QDomNode mlt = m_doc.firstChildElement(QStringLiteral("mlt"));
0961         main_playlist.setAttribute(QStringLiteral("id"), BinPlaylist::binPlaylistId);
0962         mlt.toElement().setAttribute(QStringLiteral("producer"), BinPlaylist::binPlaylistId);
0963         QStringList ids;
0964         QStringList slowmotionIds;
0965         QDomNode firstProd = m_doc.firstChildElement(QStringLiteral("producer"));
0966 
0967         QDomNodeList kdenlive_producers = m_doc.elementsByTagName(QStringLiteral("kdenlive_producer"));
0968 
0969         // Rename all track producers to correct name: "id_playlistName" instead of "id_trackNumber"
0970         QMap<QString, QString> trackRenaming;
0971         // Create a list of which producers / track on which the producer is
0972         QMap<QString, QString> playlistForId;
0973         QDomNodeList entries = m_doc.elementsByTagName(QStringLiteral("entry"));
0974         for (int i = 0; i < entries.count(); i++) {
0975             QDomElement entry = entries.at(i).toElement();
0976             QString entryId = entry.attribute(QStringLiteral("producer"));
0977             if (entryId == QLatin1String("black")) {
0978                 continue;
0979             }
0980             bool audioOnlyProducer = false;
0981             if (trackRenaming.contains(entryId)) {
0982                 // rename
0983                 entry.setAttribute(QStringLiteral("producer"), trackRenaming.value(entryId));
0984                 continue;
0985             }
0986             if (entryId.endsWith(QLatin1String("_video"))) {
0987                 // Video only producers are not track aware
0988                 continue;
0989             }
0990             if (entryId.endsWith(QLatin1String("_audio"))) {
0991                 // Audio only producer
0992                 audioOnlyProducer = true;
0993                 entryId = entryId.section(QLatin1Char('_'), 0, -2);
0994             }
0995             if (!entryId.contains(QLatin1Char('_'))) {
0996                 // not a track producer
0997                 playlistForId.insert(entryId, entry.parentNode().toElement().attribute(QStringLiteral("id")));
0998                 continue;
0999             }
1000             if (entryId.startsWith(QLatin1String("slowmotion:"))) {
1001                 // Check broken slowmotion producers (they should not be track aware)
1002                 QString newId = QStringLiteral("slowmotion:") + entryId.section(QLatin1Char(':'), 1, 1).section(QLatin1Char('_'), 0, 0) + QLatin1Char(':') +
1003                                 entryId.section(QLatin1Char(':'), 2);
1004                 trackRenaming.insert(entryId, newId);
1005                 entry.setAttribute(QStringLiteral("producer"), newId);
1006                 continue;
1007             }
1008             QString track = entryId.section(QLatin1Char('_'), 1, 1);
1009             QString playlistId = entry.parentNode().toElement().attribute(QStringLiteral("id"));
1010             if (track == playlistId) {
1011                 continue;
1012             }
1013             QString newId = entryId.section(QLatin1Char('_'), 0, 0) + QLatin1Char('_') + playlistId;
1014             if (audioOnlyProducer) {
1015                 newId.append(QStringLiteral("_audio"));
1016                 trackRenaming.insert(entryId + QStringLiteral("_audio"), newId);
1017             } else {
1018                 trackRenaming.insert(entryId, newId);
1019             }
1020             entry.setAttribute(QStringLiteral("producer"), newId);
1021         }
1022         if (!trackRenaming.isEmpty()) {
1023             for (int i = 0; i < producers.count(); ++i) {
1024                 QDomElement prod = producers.at(i).toElement();
1025                 QString id = prod.attribute(QStringLiteral("id"));
1026                 if (trackRenaming.contains(id)) {
1027                     prod.setAttribute(QStringLiteral("id"), trackRenaming.value(id));
1028                 }
1029             }
1030         }
1031 
1032         // Create easily searchable index of original producers
1033         QMap<QString, QDomElement> m_source_producers;
1034         for (int j = 0; j < kdenlive_producers.count(); j++) {
1035             QDomElement prod = kdenlive_producers.at(j).toElement();
1036             QString id = prod.attribute(QStringLiteral("id"));
1037             m_source_producers.insert(id, prod);
1038         }
1039 
1040         for (int i = 0; i < producers.count(); ++i) {
1041             QDomElement prod = producers.at(i).toElement();
1042             QString id = prod.attribute(QStringLiteral("id"));
1043             if (id == QLatin1String("black")) {
1044                 continue;
1045             }
1046             if (id.startsWith(QLatin1String("slowmotion"))) {
1047                 // No need to process slowmotion producers
1048                 QString slowmo = id.section(QLatin1Char(':'), 1, 1).section(QLatin1Char('_'), 0, 0);
1049                 if (!slowmotionIds.contains(slowmo)) {
1050                     slowmotionIds << slowmo;
1051                 }
1052                 continue;
1053             }
1054             QString prodId = id.section(QLatin1Char('_'), 0, 0);
1055             if (ids.contains(prodId)) {
1056                 // Make sure we didn't create a duplicate
1057                 if (ids.contains(id)) {
1058                     // we have a duplicate, check if this needs to be a track producer
1059                     QString service = Xml::getXmlProperty(prod, QStringLiteral("mlt_service"));
1060                     int a_ix = Xml::getXmlProperty(prod, QStringLiteral("audio_index")).toInt();
1061                     if (service == QLatin1String("xml") || service == QLatin1String("consumer") ||
1062                         (service.contains(QStringLiteral("avformat")) && a_ix != -1)) {
1063                         // This should be a track producer, rename
1064                         QString newId = id + QLatin1Char('_') + playlistForId.value(id);
1065                         prod.setAttribute(QStringLiteral("id"), newId);
1066                         for (int j = 0; j < entries.count(); j++) {
1067                             QDomElement entry = entries.at(j).toElement();
1068                             QString entryId = entry.attribute(QStringLiteral("producer"));
1069                             if (entryId == id) {
1070                                 entry.setAttribute(QStringLiteral("producer"), newId);
1071                             }
1072                         }
1073                     } else {
1074                         // This is a duplicate, remove
1075                         mlt.removeChild(prod);
1076                         i--;
1077                     }
1078                 }
1079                 // Already processed, continue
1080                 continue;
1081             }
1082             if (id == prodId) {
1083                 // This is an original producer, move it to the main playlist
1084                 QDomElement entry = m_doc.createElement(QStringLiteral("entry"));
1085                 entry.setAttribute(QStringLiteral("producer"), id);
1086                 main_playlist.appendChild(entry);
1087                 QString service = Xml::getXmlProperty(prod, QStringLiteral("mlt_service"));
1088                 if (service == QLatin1String("kdenlivetitle")) {
1089                     fixTitleProducerLocale(prod);
1090                 }
1091                 QDomElement source = m_source_producers.value(id);
1092                 if (!source.isNull()) {
1093                     updateProducerInfo(prod, source);
1094                     entry.setAttribute(QStringLiteral("in"), QStringLiteral("0"));
1095                     entry.setAttribute(QStringLiteral("out"), QString::number(source.attribute(QStringLiteral("duration")).toInt() - 1));
1096                 }
1097                 frag.appendChild(prod);
1098                 // Changing prod parent removes it from list, so rewind index
1099                 i--;
1100             } else {
1101                 QDomElement originalProd = prod.cloneNode().toElement();
1102                 originalProd.setAttribute(QStringLiteral("id"), prodId);
1103                 if (id.endsWith(QLatin1String("_audio"))) {
1104                     Xml::removeXmlProperty(originalProd, QStringLiteral("video_index"));
1105                 } else if (id.endsWith(QLatin1String("_video"))) {
1106                     Xml::removeXmlProperty(originalProd, QStringLiteral("audio_index"));
1107                 }
1108                 QDomElement source = m_source_producers.value(prodId);
1109                 QDomElement entry = m_doc.createElement(QStringLiteral("entry"));
1110                 if (!source.isNull()) {
1111                     updateProducerInfo(originalProd, source);
1112                     entry.setAttribute(QStringLiteral("in"), QStringLiteral("0"));
1113                     entry.setAttribute(QStringLiteral("out"), QString::number(source.attribute(QStringLiteral("duration")).toInt() - 1));
1114                 }
1115                 frag.appendChild(originalProd);
1116                 entry.setAttribute(QStringLiteral("producer"), prodId);
1117                 main_playlist.appendChild(entry);
1118             }
1119             ids.append(prodId);
1120         }
1121 
1122         // Make sure to include producers that were not in timeline
1123         for (int j = 0; j < kdenlive_producers.count(); j++) {
1124             QDomElement prod = kdenlive_producers.at(j).toElement();
1125             QString id = prod.attribute(QStringLiteral("id"));
1126             if (!ids.contains(id)) {
1127                 // Clip was not in timeline, create it
1128                 QDomElement originalProd = prod.cloneNode().toElement();
1129                 originalProd.setTagName(QStringLiteral("producer"));
1130                 Xml::setXmlProperty(originalProd, QStringLiteral("resource"), originalProd.attribute(QStringLiteral("resource")));
1131                 updateProducerInfo(originalProd, prod);
1132                 originalProd.removeAttribute(QStringLiteral("proxy"));
1133                 originalProd.removeAttribute(QStringLiteral("type"));
1134                 originalProd.removeAttribute(QStringLiteral("file_hash"));
1135                 originalProd.removeAttribute(QStringLiteral("file_size"));
1136                 originalProd.removeAttribute(QStringLiteral("frame_size"));
1137                 originalProd.removeAttribute(QStringLiteral("zone_out"));
1138                 originalProd.removeAttribute(QStringLiteral("zone_in"));
1139                 originalProd.removeAttribute(QStringLiteral("name"));
1140                 originalProd.removeAttribute(QStringLiteral("type"));
1141                 originalProd.removeAttribute(QStringLiteral("duration"));
1142                 originalProd.removeAttribute(QStringLiteral("cutzones"));
1143 
1144                 int type = prod.attribute(QStringLiteral("type")).toInt();
1145                 QString mltService;
1146                 switch (type) {
1147                 case 4:
1148                     mltService = QStringLiteral("colour");
1149                     break;
1150                 case 5:
1151                 case 7:
1152                     mltService = QStringLiteral("qimage");
1153                     break;
1154                 case 6:
1155                     mltService = QStringLiteral("kdenlivetitle");
1156                     break;
1157                 case 9:
1158                     mltService = QStringLiteral("xml");
1159                     break;
1160                 default:
1161                     mltService = QStringLiteral("avformat");
1162                     break;
1163                 }
1164                 Xml::setXmlProperty(originalProd, QStringLiteral("mlt_service"), mltService);
1165                 Xml::setXmlProperty(originalProd, QStringLiteral("mlt_type"), QStringLiteral("producer"));
1166                 QDomElement entry = m_doc.createElement(QStringLiteral("entry"));
1167                 entry.setAttribute(QStringLiteral("in"), QStringLiteral("0"));
1168                 entry.setAttribute(QStringLiteral("out"), QString::number(prod.attribute(QStringLiteral("duration")).toInt() - 1));
1169                 entry.setAttribute(QStringLiteral("producer"), id);
1170                 main_playlist.appendChild(entry);
1171                 if (type == 6) {
1172                     fixTitleProducerLocale(originalProd);
1173                 }
1174                 frag.appendChild(originalProd);
1175                 ids << id;
1176             }
1177         }
1178 
1179         // Set clip folders
1180         for (int j = 0; j < kdenlive_producers.count(); j++) {
1181             QDomElement prod = kdenlive_producers.at(j).toElement();
1182             QString id = prod.attribute(QStringLiteral("id"));
1183             QString folder = prod.attribute(QStringLiteral("groupid"));
1184             QDomNodeList mlt_producers = frag.childNodes();
1185             for (int k = 0; k < mlt_producers.count(); k++) {
1186                 QDomElement mltprod = mlt_producers.at(k).toElement();
1187                 if (mltprod.tagName() != QLatin1String("producer")) {
1188                     continue;
1189                 }
1190                 if (mltprod.attribute(QStringLiteral("id")) == id) {
1191                     if (!folder.isEmpty()) {
1192                         // We have found our producer, set folder info
1193                         QDomElement property = m_doc.createElement(QStringLiteral("property"));
1194                         property.setAttribute(QStringLiteral("name"), QStringLiteral("kdenlive:folderid"));
1195                         QDomText val_node = m_doc.createTextNode(folder);
1196                         property.appendChild(val_node);
1197                         mltprod.appendChild(property);
1198                     }
1199                     break;
1200                 }
1201             }
1202         }
1203 
1204         // Make sure all slowmotion producers have a master clip
1205         for (int i = 0; i < slowmotionIds.count(); i++) {
1206             const QString &slo = slowmotionIds.at(i);
1207             if (!ids.contains(slo)) {
1208                 // rebuild producer from Kdenlive's old xml format
1209                 for (int j = 0; j < kdenlive_producers.count(); j++) {
1210                     QDomElement prod = kdenlive_producers.at(j).toElement();
1211                     QString id = prod.attribute(QStringLiteral("id"));
1212                     if (id == slo) {
1213                         // We found the kdenlive_producer, build MLT producer
1214                         QDomElement original = m_doc.createElement(QStringLiteral("producer"));
1215                         original.setAttribute(QStringLiteral("in"), 0);
1216                         original.setAttribute(QStringLiteral("out"), prod.attribute(QStringLiteral("duration")).toInt() - 1);
1217                         original.setAttribute(QStringLiteral("id"), id);
1218                         QDomElement property = m_doc.createElement(QStringLiteral("property"));
1219                         property.setAttribute(QStringLiteral("name"), QStringLiteral("resource"));
1220                         QDomText val_node = m_doc.createTextNode(prod.attribute(QStringLiteral("resource")));
1221                         property.appendChild(val_node);
1222                         original.appendChild(property);
1223                         QDomElement prop2 = m_doc.createElement(QStringLiteral("property"));
1224                         prop2.setAttribute(QStringLiteral("name"), QStringLiteral("mlt_service"));
1225                         QDomText val2 = m_doc.createTextNode(QStringLiteral("avformat"));
1226                         prop2.appendChild(val2);
1227                         original.appendChild(prop2);
1228                         QDomElement prop3 = m_doc.createElement(QStringLiteral("property"));
1229                         prop3.setAttribute(QStringLiteral("name"), QStringLiteral("length"));
1230                         QDomText val3 = m_doc.createTextNode(prod.attribute(QStringLiteral("duration")));
1231                         prop3.appendChild(val3);
1232                         original.appendChild(prop3);
1233                         QDomElement entry = m_doc.createElement(QStringLiteral("entry"));
1234                         entry.setAttribute(QStringLiteral("in"), original.attribute(QStringLiteral("in")));
1235                         entry.setAttribute(QStringLiteral("out"), original.attribute(QStringLiteral("out")));
1236                         entry.setAttribute(QStringLiteral("producer"), id);
1237                         main_playlist.appendChild(entry);
1238                         frag.appendChild(original);
1239                         ids << slo;
1240                         break;
1241                     }
1242                 }
1243             }
1244         }
1245         frag.appendChild(main_playlist);
1246         mlt.insertBefore(frag, firstProd);
1247     }
1248 
1249     if (version < 0.91) {
1250         // Migrate track properties
1251         QDomNode mlt = m_doc.firstChildElement(QStringLiteral("mlt"));
1252         QDomNodeList old_tracks = m_doc.elementsByTagName(QStringLiteral("trackinfo"));
1253         QDomNodeList tracks = m_doc.elementsByTagName(QStringLiteral("track"));
1254         QDomNodeList playlists = m_doc.elementsByTagName(QStringLiteral("playlist"));
1255         for (int i = 0; i < old_tracks.count(); i++) {
1256             QString playlistName = tracks.at(i + 1).toElement().attribute(QStringLiteral("producer"));
1257             // find playlist for track
1258             QDomElement trackPlaylist;
1259             for (int j = 0; j < playlists.count(); j++) {
1260                 if (playlists.at(j).toElement().attribute(QStringLiteral("id")) == playlistName) {
1261                     trackPlaylist = playlists.at(j).toElement();
1262                     break;
1263                 }
1264             }
1265             if (!trackPlaylist.isNull()) {
1266                 QDomElement kdenliveTrack = old_tracks.at(i).toElement();
1267                 if (kdenliveTrack.attribute(QStringLiteral("type")) == QLatin1String("audio")) {
1268                     Xml::setXmlProperty(trackPlaylist, QStringLiteral("kdenlive:audio_track"), QStringLiteral("1"));
1269                 }
1270                 if (kdenliveTrack.attribute(QStringLiteral("locked")) == QLatin1String("1")) {
1271                     Xml::setXmlProperty(trackPlaylist, QStringLiteral("kdenlive:locked_track"), QStringLiteral("1"));
1272                 }
1273                 Xml::setXmlProperty(trackPlaylist, QStringLiteral("kdenlive:track_name"), kdenliveTrack.attribute(QStringLiteral("trackname")));
1274             }
1275         }
1276         // Find bin playlist
1277         playlists = m_doc.elementsByTagName(QStringLiteral("playlist"));
1278         QDomElement playlist;
1279         for (int i = 0; i < playlists.count(); i++) {
1280             if (playlists.at(i).toElement().attribute(QStringLiteral("id")) == BinPlaylist::binPlaylistId) {
1281                 playlist = playlists.at(i).toElement();
1282                 break;
1283             }
1284         }
1285         if (playlist.isNull()) {
1286             for (int i = 0; i < playlists.count(); i++) {
1287                 if (playlists.at(i).toElement().attribute(QStringLiteral("id")) == QLatin1String("main bin")) {
1288                     playlist = playlists.at(i).toElement();
1289                     playlist.setAttribute("id", BinPlaylist::binPlaylistId);
1290                     break;
1291                 }
1292             }
1293         }
1294         if (playlist.isNull()) {
1295             KMessageBox::error(QApplication::activeWindow(), i18n("Cannot recover this project file"));
1296             return false;
1297         }
1298         // Migrate document notes
1299         QDomNodeList notesList = m_doc.elementsByTagName(QStringLiteral("documentnotes"));
1300         if (!notesList.isEmpty()) {
1301             QDomElement notes_elem = notesList.at(0).toElement();
1302             QString notes = notes_elem.firstChild().nodeValue();
1303             Xml::setXmlProperty(playlist, QStringLiteral("kdenlive:documentnotes"), notes);
1304         }
1305         // Migrate clip groups
1306         QDomNodeList groupElement = m_doc.elementsByTagName(QStringLiteral("groups"));
1307         if (!groupElement.isEmpty()) {
1308             QDomElement groups = groupElement.at(0).toElement();
1309             QDomDocument d2;
1310             d2.importNode(groups, true);
1311             Xml::setXmlProperty(playlist, QStringLiteral("kdenlive:clipgroups"), d2.toString());
1312         }
1313         // Migrate custom effects
1314         QDomNodeList effectsElement = m_doc.elementsByTagName(QStringLiteral("customeffects"));
1315         if (!effectsElement.isEmpty()) {
1316             QDomElement effects = effectsElement.at(0).toElement();
1317             QDomDocument d2;
1318             d2.importNode(effects, true);
1319             Xml::setXmlProperty(playlist, QStringLiteral("kdenlive:customeffects"), d2.toString());
1320         }
1321         Xml::setXmlProperty(playlist, QStringLiteral("kdenlive:docproperties.version"), QString::number(currentVersion));
1322         if (!infoXml.isNull()) {
1323             Xml::setXmlProperty(playlist, QStringLiteral("kdenlive:docproperties.projectfolder"), infoXml.attribute(QStringLiteral("projectfolder")));
1324         }
1325 
1326         // Remove deprecated Kdenlive extra info from xml doc before sending it to MLT
1327         QDomElement docXml = mlt.firstChildElement(QStringLiteral("kdenlivedoc"));
1328         if (!docXml.isNull()) {
1329             mlt.removeChild(docXml);
1330         }
1331     }
1332 
1333     if (version < 0.92) {
1334         // Luma transition used for wipe is deprecated, we now use a composite, convert
1335         QDomNodeList transitionList = m_doc.elementsByTagName(QStringLiteral("transition"));
1336         QDomElement trans;
1337         for (int i = 0; i < transitionList.count(); i++) {
1338             trans = transitionList.at(i).toElement();
1339             QString id = Xml::getXmlProperty(trans, QStringLiteral("kdenlive_id"));
1340             if (id == QLatin1String("luma")) {
1341                 Xml::setXmlProperty(trans, QStringLiteral("kdenlive_id"), QStringLiteral("wipe"));
1342                 Xml::setXmlProperty(trans, QStringLiteral("mlt_service"), QStringLiteral("composite"));
1343                 bool reverse = Xml::getXmlProperty(trans, QStringLiteral("reverse")).toInt() != 0;
1344                 Xml::setXmlProperty(trans, QStringLiteral("luma_invert"), Xml::getXmlProperty(trans, QStringLiteral("invert")));
1345                 Xml::setXmlProperty(trans, QStringLiteral("luma"), Xml::getXmlProperty(trans, QStringLiteral("resource")));
1346                 Xml::removeXmlProperty(trans, QStringLiteral("invert"));
1347                 Xml::removeXmlProperty(trans, QStringLiteral("reverse"));
1348                 Xml::removeXmlProperty(trans, QStringLiteral("resource"));
1349                 if (reverse) {
1350                     Xml::setXmlProperty(trans, QStringLiteral("geometry"), QStringLiteral("0%/0%:100%x100%:100;-1=0%/0%:100%x100%:0"));
1351                 } else {
1352                     Xml::setXmlProperty(trans, QStringLiteral("geometry"), QStringLiteral("0%/0%:100%x100%:0;-1=0%/0%:100%x100%:100"));
1353                 }
1354                 Xml::setXmlProperty(trans, QStringLiteral("aligned"), QStringLiteral("0"));
1355                 Xml::setXmlProperty(trans, QStringLiteral("fill"), QStringLiteral("1"));
1356             }
1357         }
1358     }
1359 
1360     if (version < 0.93) {
1361         // convert old keyframe filters to animated
1362         // these filters were "animated" by adding several instance of the filter, each one having a start and end tag.
1363         // We convert by parsing the start and end tags vor values and adding all to the new animated parameter
1364         QMap<QString, QStringList> keyframeFilterToConvert;
1365         keyframeFilterToConvert.insert(QStringLiteral("volume"), QStringList() << QStringLiteral("gain") << QStringLiteral("end") << QStringLiteral("level"));
1366         keyframeFilterToConvert.insert(QStringLiteral("brightness"), QStringList()
1367                                                                          << QStringLiteral("start") << QStringLiteral("end") << QStringLiteral("level"));
1368 
1369         QDomNodeList entries = m_doc.elementsByTagName(QStringLiteral("entry"));
1370         for (int i = 0; i < entries.count(); i++) {
1371             QDomNode entry = entries.at(i);
1372             QDomNodeList effects = entry.toElement().elementsByTagName(QStringLiteral("filter"));
1373             QStringList parsedIds;
1374             for (int j = 0; j < effects.count(); j++) {
1375                 QDomElement eff = effects.at(j).toElement();
1376                 QString id = Xml::getXmlProperty(eff, QStringLiteral("kdenlive_id"));
1377                 if (keyframeFilterToConvert.contains(id) && !parsedIds.contains(id)) {
1378                     parsedIds << id;
1379                     QMap<int, double> values;
1380                     QStringList conversionParams = keyframeFilterToConvert.value(id);
1381                     int offset = eff.attribute(QStringLiteral("in")).toInt();
1382                     int out = eff.attribute(QStringLiteral("out")).toInt();
1383                     convertKeyframeEffect_093(eff, conversionParams, values, offset);
1384                     Xml::removeXmlProperty(eff, conversionParams.at(0));
1385                     Xml::removeXmlProperty(eff, conversionParams.at(1));
1386                     for (int k = j + 1; k < effects.count(); k++) {
1387                         QDomElement subEffect = effects.at(k).toElement();
1388                         QString subId = Xml::getXmlProperty(subEffect, QStringLiteral("kdenlive_id"));
1389                         if (subId == id) {
1390                             convertKeyframeEffect_093(subEffect, conversionParams, values, offset);
1391                             out = subEffect.attribute(QStringLiteral("out")).toInt();
1392                             entry.removeChild(subEffect);
1393                             k--;
1394                         }
1395                     }
1396                     QStringList parsedValues;
1397                     QLocale locale; // Used for conversion of pre-1.0 version → OK
1398                     locale.setNumberOptions(QLocale::OmitGroupSeparator);
1399                     QMapIterator<int, double> l(values);
1400                     if (id == QLatin1String("volume")) {
1401                         // convert old volume range (0-300) to new dB values (-60-60)
1402                         while (l.hasNext()) {
1403                             l.next();
1404                             double v = l.value();
1405                             if (v <= 0) {
1406                                 v = -60;
1407                             } else {
1408                                 v = log10(v) * 20;
1409                             }
1410                             parsedValues << QString::number(l.key()) + QLatin1Char('=') + locale.toString(v);
1411                         }
1412                     } else {
1413                         while (l.hasNext()) {
1414                             l.next();
1415                             parsedValues << QString::number(l.key()) + QLatin1Char('=') + locale.toString(l.value());
1416                         }
1417                     }
1418                     Xml::setXmlProperty(eff, conversionParams.at(2), parsedValues.join(QLatin1Char(';')));
1419                     eff.setAttribute(QStringLiteral("out"), out);
1420                 }
1421             }
1422         }
1423     }
1424 
1425     if (version < 0.94) {
1426         // convert slowmotion effects/producers
1427         QDomNodeList producers = m_doc.elementsByTagName(QStringLiteral("producer"));
1428         int max = producers.count();
1429         QStringList slowmoIds;
1430         for (int i = 0; i < max; ++i) {
1431             QDomElement prod = producers.at(i).toElement();
1432             QString id = prod.attribute(QStringLiteral("id"));
1433             if (id.startsWith(QLatin1String("slowmotion"))) {
1434                 QString service = Xml::getXmlProperty(prod, QStringLiteral("mlt_service"));
1435                 if (service == QLatin1String("framebuffer")) {
1436                     // convert to new timewarp producer
1437                     prod.setAttribute(QStringLiteral("id"), id + QStringLiteral(":1"));
1438                     slowmoIds << id;
1439                     Xml::setXmlProperty(prod, QStringLiteral("mlt_service"), QStringLiteral("timewarp"));
1440                     QString resource = Xml::getXmlProperty(prod, QStringLiteral("resource"));
1441                     Xml::setXmlProperty(prod, QStringLiteral("warp_resource"), resource.section(QLatin1Char('?'), 0, 0));
1442                     Xml::setXmlProperty(prod, QStringLiteral("warp_speed"), resource.section(QLatin1Char('?'), 1).section(QLatin1Char(':'), 0, 0));
1443                     Xml::setXmlProperty(prod, QStringLiteral("resource"),
1444                                         resource.section(QLatin1Char('?'), 1) + QLatin1Char(':') + resource.section(QLatin1Char('?'), 0, 0));
1445                     Xml::setXmlProperty(prod, QStringLiteral("audio_index"), QStringLiteral("-1"));
1446                 }
1447             }
1448         }
1449         if (!slowmoIds.isEmpty()) {
1450             producers = m_doc.elementsByTagName(QStringLiteral("entry"));
1451             max = producers.count();
1452             for (int i = 0; i < max; ++i) {
1453                 QDomElement prod = producers.at(i).toElement();
1454                 QString entryId = prod.attribute(QStringLiteral("producer"));
1455                 if (slowmoIds.contains(entryId)) {
1456                     prod.setAttribute(QStringLiteral("producer"), entryId + QStringLiteral(":1"));
1457                 }
1458             }
1459         }
1460         // qCDebug(KDENLIVE_LOG)<<"------------------------\n"<<m_doc.toString();
1461     }
1462     if (version < 0.95) {
1463         // convert slowmotion effects/producers
1464         QDomNodeList producers = m_doc.elementsByTagName(QStringLiteral("producer"));
1465         int max = producers.count();
1466         for (int i = 0; i < max; ++i) {
1467             QDomElement prod = producers.at(i).toElement();
1468             if (prod.isNull()) {
1469                 continue;
1470             }
1471             QString id = prod.attribute(QStringLiteral("id")).section(QLatin1Char('_'), 0, 0);
1472             if (id == QLatin1String("black")) {
1473                 Xml::setXmlProperty(prod, QStringLiteral("set.test_audio"), QStringLiteral("0"));
1474                 break;
1475             }
1476         }
1477     }
1478     if (version < 0.97) {
1479         // move guides to new JSON format
1480         QDomElement main_playlist = m_doc.documentElement().firstChildElement(QStringLiteral("playlist"));
1481         QDomNodeList props = main_playlist.elementsByTagName(QStringLiteral("property"));
1482         QJsonArray guidesList;
1483         QMap<QString, QJsonArray> markersList;
1484         QLocale locale; // Used for conversion of pre-1.0 version → OK
1485         for (int i = 0; i < props.count(); ++i) {
1486             QDomNode n = props.at(i);
1487             QString prop = n.toElement().attribute(QStringLiteral("name"));
1488             if (prop.startsWith(QLatin1String("kdenlive:guide."))) {
1489                 // Process guide
1490                 double guidePos = locale.toDouble(prop.section(QLatin1Char('.'), 1));
1491                 QJsonObject currentGuide;
1492                 currentGuide.insert(QStringLiteral("pos"), QJsonValue(GenTime(guidePos).frames(pCore->getCurrentFps())));
1493                 currentGuide.insert(QStringLiteral("comment"), QJsonValue(n.firstChild().nodeValue()));
1494                 currentGuide.insert(QStringLiteral("type"), QJsonValue(0));
1495                 // Clear entry in old format
1496                 n.toElement().setAttribute(QStringLiteral("name"), QStringLiteral("_"));
1497                 guidesList.push_back(currentGuide);
1498             } else if (prop.startsWith(QLatin1String("kdenlive:marker."))) {
1499                 // Process marker
1500                 double markerPos = locale.toDouble(prop.section(QLatin1Char(':'), -1));
1501                 QString markerBinClip = prop.section(QLatin1Char('.'), 1).section(QLatin1Char(':'), 0, 0);
1502                 QString markerData = n.firstChild().nodeValue();
1503                 int markerType = markerData.section(QLatin1Char(':'), 0, 0).toInt();
1504                 QString markerComment = markerData.section(QLatin1Char(':'), 1);
1505                 QJsonObject currentMarker;
1506                 currentMarker.insert(QStringLiteral("pos"), QJsonValue(GenTime(markerPos).frames(pCore->getCurrentFps())));
1507                 currentMarker.insert(QStringLiteral("comment"), QJsonValue(markerComment));
1508                 currentMarker.insert(QStringLiteral("type"), QJsonValue(markerType));
1509                 // Clear entry in old format
1510                 n.toElement().setAttribute(QStringLiteral("name"), QStringLiteral("_"));
1511                 if (markersList.contains(markerBinClip)) {
1512                     // we already have a marker list for this clip
1513                     QJsonArray markerList = markersList.value(markerBinClip);
1514                     markerList.push_back(currentMarker);
1515                     markersList.insert(markerBinClip, markerList);
1516                 } else {
1517                     QJsonArray markerList;
1518                     markerList.push_back(currentMarker);
1519                     markersList.insert(markerBinClip, markerList);
1520                 }
1521             }
1522         }
1523         if (!guidesList.isEmpty()) {
1524             QJsonDocument json(guidesList);
1525             Xml::setXmlProperty(main_playlist, QStringLiteral("kdenlive:docproperties.guides"), json.toJson());
1526         }
1527 
1528         // Update producers
1529         QDomNodeList producers = m_doc.elementsByTagName(QStringLiteral("producer"));
1530         int max = producers.count();
1531         for (int i = 0; i < max; ++i) {
1532             QDomElement prod = producers.at(i).toElement();
1533             if (prod.isNull()) continue;
1534             // Move to new kdenlive:id format
1535             const QString id = prod.attribute(QStringLiteral("id")).section(QLatin1Char('_'), 0, 0);
1536             Xml::setXmlProperty(prod, QStringLiteral("kdenlive:id"), id);
1537             if (markersList.contains(id)) {
1538                 QJsonDocument json(markersList.value(id));
1539                 Xml::setXmlProperty(prod, QStringLiteral("kdenlive:markers"), json.toJson());
1540             }
1541 
1542             // Check image sequences with buggy begin frame number
1543             const QString service = Xml::getXmlProperty(prod, QStringLiteral("mlt_service"));
1544             if (service == QLatin1String("pixbuf") || service == QLatin1String("qimage")) {
1545                 QString resource = Xml::getXmlProperty(prod, QStringLiteral("resource"));
1546                 if (resource.contains(QStringLiteral("?begin:"))) {
1547                     resource.replace(QStringLiteral("?begin:"), QStringLiteral("?begin="));
1548                     Xml::setXmlProperty(prod, QStringLiteral("resource"), resource);
1549                 }
1550             }
1551         }
1552     }
1553     if (version < 0.98) {
1554         // rename main bin playlist, create extra tracks for old type AV clips, port groups to JSon
1555         QJsonArray newGroups;
1556         QDomNodeList playlists = m_doc.elementsByTagName(QStringLiteral("playlist"));
1557         QDomNodeList masterProducers = m_doc.elementsByTagName(QStringLiteral("producer"));
1558         QDomElement playlist;
1559         QDomNode mainplaylist;
1560         QDomNode mlt = m_doc.firstChildElement(QStringLiteral("mlt"));
1561         QDomNode tractor = mlt.firstChildElement(QStringLiteral("tractor"));
1562         // Build start trackIndex
1563         QMap<QString, QString> trackIndex;
1564         QDomNodeList tracks = tractor.toElement().elementsByTagName(QStringLiteral("track"));
1565         for (int i = 0; i < tracks.count(); i++) {
1566             trackIndex.insert(QString::number(i), tracks.at(i).toElement().attribute(QStringLiteral("producer")));
1567         }
1568 
1569         int trackOffset = 0;
1570         // AV clips are not supported anymore. Check if we have some and add extra audio tracks if necessary
1571         // Update the main bin name as well to be xml compliant
1572         for (int i = 0; i < playlists.count(); i++) {
1573             if (playlists.at(i).toElement().attribute(QStringLiteral("id")) == QLatin1String("main bin") ||
1574                 playlists.at(i).toElement().attribute(QStringLiteral("id")) == QLatin1String("main_bin")) {
1575                 playlists.at(i).toElement().setAttribute(QStringLiteral("id"), BinPlaylist::binPlaylistId);
1576                 mainplaylist = playlists.at(i);
1577                 QString oldGroups = Xml::getXmlProperty(mainplaylist.toElement(), QStringLiteral("kdenlive:clipgroups"));
1578                 QDomDocument groupsDoc;
1579                 groupsDoc.setContent(oldGroups);
1580                 QDomNodeList groups = groupsDoc.elementsByTagName(QStringLiteral("group"));
1581                 for (int g = 0; g < groups.count(); g++) {
1582                     QDomNodeList elements = groups.at(g).childNodes();
1583                     QJsonArray array;
1584                     for (int h = 0; h < elements.count(); h++) {
1585                         QJsonObject item;
1586                         item.insert(QLatin1String("type"), QJsonValue(QStringLiteral("Leaf")));
1587                         item.insert(QLatin1String("leaf"), QJsonValue(QLatin1String("clip")));
1588                         QString pos = elements.at(h).toElement().attribute(QStringLiteral("position"));
1589                         QString track = trackIndex.value(elements.at(h).toElement().attribute(QStringLiteral("track")));
1590                         item.insert(QLatin1String("data"), QJsonValue(QString("%1:%2").arg(track, pos)));
1591                         array.push_back(item);
1592                     }
1593                     QJsonObject currentGroup;
1594                     currentGroup.insert(QLatin1String("type"), QJsonValue(QStringLiteral("Normal")));
1595                     currentGroup.insert(QLatin1String("children"), array);
1596                     newGroups.push_back(currentGroup);
1597                 }
1598             } else {
1599                 if (Xml::getXmlProperty(playlists.at(i).toElement(), QStringLiteral("kdenlive:audio_track")) == QLatin1String("1")) {
1600                     // Audio track, no need to process
1601                     continue;
1602                 }
1603                 const QString playlistName = playlists.at(i).toElement().attribute(QStringLiteral("id"));
1604                 QDomElement duplicate_playlist = m_doc.createElement(QStringLiteral("playlist"));
1605                 duplicate_playlist.setAttribute(QStringLiteral("id"), QString("%1_duplicate").arg(playlistName));
1606                 QDomElement pltype = m_doc.createElement(QStringLiteral("property"));
1607                 pltype.setAttribute(QStringLiteral("name"), QStringLiteral("kdenlive:audio_track"));
1608                 pltype.setNodeValue(QStringLiteral("1"));
1609                 QDomText value1 = m_doc.createTextNode(QStringLiteral("1"));
1610                 pltype.appendChild(value1);
1611                 duplicate_playlist.appendChild(pltype);
1612                 QDomElement plname = m_doc.createElement(QStringLiteral("property"));
1613                 plname.setAttribute(QStringLiteral("name"), QStringLiteral("kdenlive:track_name"));
1614                 QDomText value = m_doc.createTextNode(i18n("extra audio"));
1615                 plname.appendChild(value);
1616                 duplicate_playlist.appendChild(plname);
1617                 QDomNodeList producers = playlists.at(i).childNodes();
1618                 bool duplicationRequested = false;
1619                 int pos = 0;
1620                 for (int j = 0; j < producers.count(); j++) {
1621                     if (producers.at(j).nodeName() == QLatin1String("blank")) {
1622                         // blank, duplicate
1623                         duplicate_playlist.appendChild(producers.at(j).cloneNode());
1624                         pos += producers.at(j).toElement().attribute(QStringLiteral("length")).toInt();
1625                     } else if (producers.at(j).nodeName() == QLatin1String("filter")) {
1626                         // effect, duplicate
1627                         duplicate_playlist.appendChild(producers.at(j).cloneNode());
1628                     } else if (producers.at(j).nodeName() != QLatin1String("entry")) {
1629                         // property node, pass
1630                         continue;
1631                     } else if (producers.at(j).toElement().attribute(QStringLiteral("producer")).endsWith(playlistName)) {
1632                         // This is an AV clip
1633                         // Check master properties
1634                         bool hasAudio = true;
1635                         bool hasVideo = true;
1636                         const QString currentId = producers.at(j).toElement().attribute(QStringLiteral("producer"));
1637                         int in = producers.at(j).toElement().attribute(QStringLiteral("in")).toInt();
1638                         int out = producers.at(j).toElement().attribute(QStringLiteral("out")).toInt();
1639                         for (int k = 0; k < masterProducers.count(); k++) {
1640                             if (masterProducers.at(k).toElement().attribute(QStringLiteral("id")) == currentId) {
1641                                 hasVideo = Xml::getXmlProperty(masterProducers.at(k).toElement(), QStringLiteral("video_index")) != QLatin1String("-1");
1642                                 hasAudio = Xml::getXmlProperty(masterProducers.at(k).toElement(), QStringLiteral("audio_index")) != QLatin1String("-1");
1643                                 break;
1644                             }
1645                         }
1646                         if (!hasAudio) {
1647                             // no duplication needed, replace with blank
1648                             QDomElement duplicate = m_doc.createElement(QStringLiteral("blank"));
1649                             duplicate.setAttribute(QStringLiteral("length"), QString::number(out - in + 1));
1650                             duplicate_playlist.appendChild(duplicate);
1651                             pos += out - in + 1;
1652                             continue;
1653                         }
1654                         QDomNode prod = producers.at(j).cloneNode();
1655                         Xml::setXmlProperty(prod.toElement(), QStringLiteral("set.test_video"), QStringLiteral("1"));
1656                         duplicate_playlist.appendChild(prod);
1657                         // Check if that is an audio clip on a video track
1658                         if (!hasVideo) {
1659                             // Audio clip on a video track, replace with blank and duplicate
1660                             producers.at(j).toElement().setTagName("blank");
1661                             producers.at(j).toElement().setAttribute("length", QString::number(out - in + 1));
1662                         } else {
1663                             // group newly created AVSplit group
1664                             // We temporarily store track with their playlist name since track index will change
1665                             // as we insert the duplicate tracks
1666                             QJsonArray array;
1667                             QJsonObject items;
1668                             items.insert(QLatin1String("type"), QJsonValue(QStringLiteral("Leaf")));
1669                             items.insert(QLatin1String("leaf"), QJsonValue(QLatin1String("clip")));
1670                             items.insert(QLatin1String("data"), QJsonValue(QString("%1:%2").arg(playlistName).arg(pos)));
1671                             array.push_back(items);
1672                             QJsonObject itemb;
1673                             itemb.insert(QLatin1String("type"), QJsonValue(QStringLiteral("Leaf")));
1674                             itemb.insert(QLatin1String("leaf"), QJsonValue(QLatin1String("clip")));
1675                             itemb.insert(QLatin1String("data"), QJsonValue(QString("%1:%2").arg(duplicate_playlist.attribute(QStringLiteral("id"))).arg(pos)));
1676                             array.push_back(itemb);
1677                             QJsonObject currentGroup;
1678                             currentGroup.insert(QLatin1String("type"), QJsonValue(QStringLiteral("AVSplit")));
1679                             currentGroup.insert(QLatin1String("children"), array);
1680                             newGroups.push_back(currentGroup);
1681                         }
1682                         duplicationRequested = true;
1683                         pos += out - in + 1;
1684                     } else {
1685                         // no duplication needed, replace with blank
1686                         QDomElement duplicate = m_doc.createElement(QStringLiteral("blank"));
1687                         int in = producers.at(j).toElement().attribute(QStringLiteral("in")).toInt();
1688                         int out = producers.at(j).toElement().attribute(QStringLiteral("out")).toInt();
1689                         duplicate.setAttribute(QStringLiteral("length"), QString::number(out - in + 1));
1690                         duplicate_playlist.appendChild(duplicate);
1691                         pos += out - in + 1;
1692                     }
1693                 }
1694                 if (duplicationRequested) {
1695                     // Plant the playlist at the end
1696                     mlt.insertBefore(duplicate_playlist, tractor);
1697                     QDomNode lastTrack = tractor.firstChildElement(QStringLiteral("track"));
1698                     QDomElement duplicate = m_doc.createElement(QStringLiteral("track"));
1699                     duplicate.setAttribute(QStringLiteral("producer"), QString("%1_duplicate").arg(playlistName));
1700                     duplicate.setAttribute(QStringLiteral("hide"), QStringLiteral("video"));
1701                     tractor.insertAfter(duplicate, lastTrack);
1702                     trackOffset++;
1703                 }
1704             }
1705         }
1706         if (trackOffset > 0) {
1707             // Some tracks were added, adjust compositions
1708             QDomNodeList transitions = m_doc.elementsByTagName(QStringLiteral("transition"));
1709             int max = transitions.count();
1710             for (int i = 0; i < max; ++i) {
1711                 QDomElement t = transitions.at(i).toElement();
1712                 if (Xml::getXmlProperty(t, QStringLiteral("internal_added")).toInt() > 0) {
1713                     // internal transitions will be rebuilt, no need to correct
1714                     continue;
1715                 }
1716                 int a_track = Xml::getXmlProperty(t, QStringLiteral("a_track")).toInt();
1717                 int b_track = Xml::getXmlProperty(t, QStringLiteral("b_track")).toInt();
1718                 if (a_track > 0) {
1719                     Xml::setXmlProperty(t, QStringLiteral("a_track"), QString::number(a_track + trackOffset));
1720                 }
1721                 if (b_track > 0) {
1722                     Xml::setXmlProperty(t, QStringLiteral("b_track"), QString::number(b_track + trackOffset));
1723                 }
1724             }
1725         }
1726         // Process groups data
1727         QJsonDocument json(newGroups);
1728         QString groupsData = QString(json.toJson());
1729         tracks = tractor.toElement().elementsByTagName(QStringLiteral("track"));
1730         for (int i = 0; i < tracks.count(); i++) {
1731             // Replace track names with their current index in our view
1732             const QString trackId = QString("%1:").arg(tracks.at(i).toElement().attribute(QStringLiteral("producer")));
1733             groupsData.replace(trackId, QString("%1:").arg(i - 1));
1734         }
1735         Xml::setXmlProperty(mainplaylist.toElement(), QStringLiteral("kdenlive:docproperties.groups"), groupsData);
1736     }
1737     if (version < 0.99) {
1738         // rename main bin playlist, create extra tracks for old type AV clips, port groups to JSon
1739         QDomNodeList masterProducers = m_doc.elementsByTagName(QStringLiteral("producer"));
1740         for (int i = 0; i < masterProducers.count(); i++) {
1741             QMap<QString, QString> map = Xml::getXmlPropertyByWildcard(masterProducers.at(i).toElement(), QLatin1String("kdenlive:clipzone."));
1742             if (map.isEmpty()) {
1743                 continue;
1744             }
1745             QJsonArray list;
1746             QMapIterator<QString, QString> j(map);
1747             while (j.hasNext()) {
1748                 j.next();
1749                 Xml::removeXmlProperty(masterProducers.at(i).toElement(), j.key());
1750                 QJsonObject currentZone;
1751                 currentZone.insert(QLatin1String("name"), QJsonValue(j.key().section(QLatin1Char('.'), 1)));
1752                 if (!j.value().contains(QLatin1Char(';'))) {
1753                     // invalid zone
1754                     continue;
1755                 }
1756                 currentZone.insert(QLatin1String("in"), QJsonValue(j.value().section(QLatin1Char(';'), 0, 0).toInt()));
1757                 currentZone.insert(QLatin1String("out"), QJsonValue(j.value().section(QLatin1Char(';'), 1, 1).toInt()));
1758                 list.push_back(currentZone);
1759             }
1760             QJsonDocument json(list);
1761             Xml::setXmlProperty(masterProducers.at(i).toElement(), QStringLiteral("kdenlive:clipzones"), QString(json.toJson()));
1762         }
1763     }
1764 
1765     // Doc 1.01: Kdenlive 21.08.0
1766     if (version < 1.01) {
1767         // Upgrade wipe composition replace old mlt geometry with mlt rect
1768         // Upgrade affine effect and transition (geometry parameter renamed to rect)
1769         // Warn about deprecated automask
1770         // Some tracks were added, adjust compositions
1771         QDomNodeList transitions = m_doc.elementsByTagName(QStringLiteral("transition"));
1772         int max = transitions.count();
1773         for (int i = 0; i < max; ++i) {
1774             QDomElement t = transitions.at(i).toElement();
1775             if (Xml::getXmlProperty(t, QStringLiteral("kdenlive_id")) == QLatin1String("wipe")) {
1776                 QString animation = Xml::getXmlProperty(t, QStringLiteral("geometry"));
1777                 if (animation == QLatin1String("0%/0%:100%x100%:100;-1=0%/0%:100%x100%:0")) {
1778                     Xml::setXmlProperty(t, QStringLiteral("geometry"), QStringLiteral("0=0% 0% 100% 100% 100%;-1=0% 0% 100% 100% 0%"));
1779                 } else if (animation == QLatin1String("0%/0%:100%x100%:0;-1=0%/0%:100%x100%:100")) {
1780                     Xml::setXmlProperty(t, QStringLiteral("geometry"), QStringLiteral("0=0% 0% 100% 100% 0%;-1=0% 0% 100% 100% 100%"));
1781                 }
1782             } else if (Xml::getXmlProperty(t, QStringLiteral("kdenlive_id")) == QLatin1String("affine")) {
1783                 Xml::renameXmlProperty(t, QStringLiteral("geometry"), QStringLiteral("rect"));
1784             }
1785         }
1786         QDomNodeList effects = m_doc.elementsByTagName(QStringLiteral("filter"));
1787         max = effects.count();
1788         for (int i = 0; i < max; ++i) {
1789             QDomElement t = effects.at(i).toElement();
1790             if (Xml::getXmlProperty(t, QStringLiteral("kdenlive_id")) == QLatin1String("pan_zoom")) {
1791                 Xml::renameXmlProperty(t, QStringLiteral("transition.geometry"), QStringLiteral("transition.rect"));
1792             }
1793         }
1794     }
1795 
1796     // Doc 1.02: Kdenlive 21.08.1
1797     if (version < 1.02) {
1798         // Custom affine effects: replace old mlt geometry with mlt rect
1799         QDomNodeList effects = m_doc.elementsByTagName(QStringLiteral("filter"));
1800         int max = effects.count();
1801         QStringList changedEffects;
1802         for (int i = 0; i < max; ++i) {
1803             QDomElement t = effects.at(i).toElement();
1804             QString kdenliveId = Xml::getXmlProperty(t, QStringLiteral("kdenlive_id"));
1805             if (Xml::getXmlProperty(t, QStringLiteral("mlt_service")) == QLatin1String("affine") && kdenliveId != QLatin1String("pan_zoom")) {
1806                 QDomElement effect = EffectsRepository::get()->getXml(kdenliveId);
1807 
1808                 // check whether the effect already uses mlt rect
1809                 if (!Xml::hasXmlProperty(t, QStringLiteral("transition.rect"))) {
1810                     QString newId = kdenliveId.append(" mlt7");
1811 
1812                     Xml::renameXmlProperty(t, QStringLiteral("transition.geometry"), QStringLiteral("transition.rect"));
1813                     Xml::setXmlProperty(t, QStringLiteral("kdenlive_id"), newId);
1814 
1815                     QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QStringLiteral("/effects/"));
1816 
1817                     if (!dir.exists(newId + QStringLiteral(".xml"))) {
1818                         // update the custom effect xml too (create a new fixed xml with "(mlt7)" appendix)
1819                         QDomDocument doc;
1820                         doc.appendChild(doc.importNode(effect, true));
1821 
1822                         if (!dir.exists()) {
1823                             dir.mkpath(QStringLiteral("."));
1824                         }
1825                         QFile file(dir.absoluteFilePath(newId + QStringLiteral(".xml")));
1826 
1827                         QDomElement root = doc.documentElement();
1828                         QDomElement nodelist = root.firstChildElement("name");
1829                         QDomElement newNodeTag = doc.createElement(QString("name"));
1830                         QDomText text = doc.createTextNode(newId);
1831                         newNodeTag.appendChild(text);
1832                         root.replaceChild(newNodeTag, nodelist);
1833 
1834                         // QDomElement e = doc.documentElement();
1835                         // e.setAttribute("id", newId);
1836 
1837                         auto params = doc.elementsByTagName(QStringLiteral("parameter"));
1838                         for (int j = 0; j < params.count(); j++) {
1839                             QString paramName = params.at(j).attributes().namedItem("name").nodeValue();
1840                             if (paramName == QStringLiteral("transition.geometry")) {
1841                                 QDomElement e = params.at(j).toElement();
1842                                 e.setAttribute("name", QStringLiteral("transition.rect"));
1843                             }
1844                         }
1845 
1846                         if (file.open(QFile::WriteOnly | QFile::Truncate)) {
1847                             QTextStream out(&file);
1848 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
1849                             out.setCodec("UTF-8");
1850 #endif
1851                             out << doc.toString();
1852                         }
1853                         file.close();
1854 
1855                         changedEffects << dir.absoluteFilePath(newId + QStringLiteral(".xml"));
1856                     }
1857                 }
1858             }
1859         }
1860         if (!changedEffects.isEmpty()) {
1861             KMessageBox::informationList(nullptr, "changedEffects", changedEffects);
1862             pCore->window()->slotReloadEffects(changedEffects);
1863         }
1864     }
1865     // Doc 1.03: Kdenlive 21.08.2
1866     if (version < 1.03) {
1867         // Fades: replace deprecated syntax (using start/end properties and alpha=-1) with level and alpha animated properties
1868         QDomNodeList effects = m_doc.elementsByTagName(QStringLiteral("filter"));
1869         int max = effects.count();
1870         QStringList changedEffects;
1871         for (int i = 0; i < max; ++i) {
1872             QDomElement t = effects.at(i).toElement();
1873             QString kdenliveId = Xml::getXmlProperty(t, QStringLiteral("kdenlive_id"));
1874             if (kdenliveId.startsWith(QLatin1String("fade_"))) {
1875                 bool fadeIn = kdenliveId.startsWith(QLatin1String("fade_from_"));
1876                 bool isAlpha = Xml::getXmlProperty(t, QStringLiteral("alpha")).toInt() == -1;
1877                 // Clear unused properties
1878                 Xml::removeXmlProperty(t, QStringLiteral("start"));
1879                 Xml::removeXmlProperty(t, QStringLiteral("end"));
1880                 Xml::removeXmlProperty(t, QStringLiteral("alpha"));
1881                 QString params;
1882                 if (fadeIn) {
1883                     params = QStringLiteral("0=0;-1=1");
1884                 } else {
1885                     params = QStringLiteral("0=1;-1=0");
1886                 }
1887                 if (isAlpha) {
1888                     Xml::setXmlProperty(t, QStringLiteral("level"), QStringLiteral("1"));
1889                     Xml::setXmlProperty(t, QStringLiteral("alpha"), params);
1890                 } else {
1891                     Xml::setXmlProperty(t, QStringLiteral("level"), params);
1892                     Xml::setXmlProperty(t, QStringLiteral("alpha"), QStringLiteral("1"));
1893                 }
1894             }
1895         }
1896     }
1897     // Doc 1.03: Kdenlive 21.08.2
1898     if (version < 1.04) {
1899         // Slide: replace buggy composite transition with affine
1900         QDomNodeList transitions = m_doc.elementsByTagName(QStringLiteral("transition"));
1901         int max = transitions.count();
1902         QStringList changedEffects;
1903         for (int i = 0; i < max; ++i) {
1904             QDomElement t = transitions.at(i).toElement();
1905             QString kdenliveId = Xml::getXmlProperty(t, QStringLiteral("kdenlive_id"));
1906             if (kdenliveId == QLatin1String("slide")) {
1907                 // Switch to affine and rect instead of composite and geometry
1908                 Xml::renameXmlProperty(t, QStringLiteral("geometry"), QStringLiteral("rect"));
1909                 Xml::setXmlProperty(t, QStringLiteral("mlt_service"), QStringLiteral("affine"));
1910             }
1911         }
1912     }
1913     // Doc 1.1: Kdenlive 21.12.0
1914     /*if (version < 1.1) {
1915         // OpenCV tracker: Fix for older syntax where filter had in/out defined
1916         QDomNodeList effects = m_doc.elementsByTagName(QStringLiteral("filter"));
1917         int max = effects.count();
1918         QStringList changedEffects;
1919         for (int i = 0; i < max; ++i) {
1920             QDomElement t = effects.at(i).toElement();
1921             QString kdenliveId = Xml::getXmlProperty(t, QStringLiteral("kdenlive_id"));
1922             if (kdenliveId == QLatin1String("opencv.tracker") && t.hasAttribute(QLatin1String("in"))) {
1923                 QString filterIn = t.attribute(QLatin1String("in"));
1924                 int inPoint;
1925                 Mlt::Properties props;
1926                 props.set("_profile", pCore->getProjectProfile()->get_profile(), 0);
1927                 if (!filterIn.contains(QLatin1Char(':'))) {
1928                     inPoint = filterIn.toInt();
1929                 } else {
1930                     // Convert from hh:mm:ss.mmm to frames
1931                     inPoint = props.time_to_frames(filterIn.toUtf8().constData());
1932                 }
1933                 qDebug() << "=== FOUND TRACKER WITH IN POINT: " << inPoint;
1934                 QString animation = Xml::getXmlProperty(t, QStringLiteral("results"));
1935                 props.set("key", animation.toUtf8().constData());
1936                 // This is a fake query to force the animation to be parsed
1937                 (void)props.anim_get_double("key", 0, -1);
1938                 Mlt::Animation anim = props.get_animation("key");
1939                 anim.shift_frames(inPoint);
1940                 Xml::setXmlProperty(t, QStringLiteral("results"), qstrdup(anim.serialize_cut()));
1941                 t.removeAttribute("in");
1942                 t.removeAttribute("out");
1943             }
1944         }
1945     }*/
1946 
1947     m_modified = true;
1948     return true;
1949 }
1950 
1951 auto DocumentValidator::upgradeTo100(const QLocale &documentLocale) -> QString
1952 {
1953 
1954     bool modified = false;
1955     auto decimalPoint = documentLocale.decimalPoint();
1956 
1957     if (decimalPoint != '.') {
1958         qDebug() << "Decimal point is NOT OK and needs fixing. Converting to . from " << decimalPoint;
1959 
1960         auto fixTimecode = [decimalPoint](QString &value) {
1961             static const QRegularExpression reTimecode(R"((\d+:\d+:\d+))" + QString(decimalPoint) + "(\\d+)");
1962             static const QRegularExpression reValue("(=\\d+)" + QString(decimalPoint) + "(\\d+)");
1963             value.replace(reTimecode, QString::fromLatin1("\\1.\\2")).replace(reValue, "\\1.\\2");
1964         };
1965 
1966         // List of properties which always need to be fixed
1967         // Example: <property name="aspect_ratio">1,00247</property>
1968         QList<QString> generalPropertiesToFix = {
1969             "warp_speed",
1970             "length",
1971             "aspect_ratio",
1972             "kdenlive:clipanalysis.motion_vector_list",
1973             "kdenlive:original.meta.attr.com.apple.quicktime.rating.user.markup",
1974         };
1975 
1976         // Fix properties just by name, anywhere in the file
1977         auto props = m_doc.elementsByTagName(QStringLiteral("property"));
1978         qDebug() << "Found " << props.count() << " properties.";
1979         for (int i = 0; i < props.count(); i++) {
1980             QString propName = props.at(i).attributes().namedItem("name").nodeValue();
1981             QDomElement element = props.at(i).toElement();
1982             if (element.childNodes().size() == 1) {
1983                 QDomText text = element.firstChild().toText();
1984                 if (!text.isNull()) {
1985 
1986                     bool autoReplace = propName.endsWith("frame_rate") || propName.endsWith("aspect_ratio") || (generalPropertiesToFix.indexOf(propName) >= 0);
1987 
1988                     QString originalValue = text.nodeValue();
1989                     QString value(originalValue);
1990                     if (propName == "resource") {
1991                         // Fix entries like <property name="resource">0,500000:/path/to/video
1992                         value.replace(QRegularExpression("^(\\d+)" + QString(decimalPoint) + "(\\d+:)"), "\\1.\\2");
1993                     } else if (autoReplace) {
1994                         // Just replace decimal point
1995                         value.replace(decimalPoint, QStringLiteral("."));
1996                     } else {
1997                         fixTimecode(value);
1998                     }
1999 
2000                     if (originalValue != value) {
2001                         text.setNodeValue(value);
2002                         qDebug() << "Decimal point: Converted " << propName << " from " << originalValue << " to " << value;
2003                     }
2004                 }
2005             }
2006         }
2007 
2008         QList<QString> filterPropertiesToFix = {
2009             "version",
2010         };
2011 
2012         // Fix filter specific properties.
2013         // The first entry is the filter (MLT service) name, the second entry a list of properties to fix.
2014         // Warning: This list may not be complete!
2015         QMap<QString, QList<QString>> servicePropertiesToFix;
2016         servicePropertiesToFix.insert("panner", {"start"});
2017         servicePropertiesToFix.insert("volume", {"level"});
2018         servicePropertiesToFix.insert("window", {"gain"});
2019         servicePropertiesToFix.insert("lumaliftgaingamma", {"lift", "gain", "gamma"});
2020 
2021         // Fix filter properties.
2022         // Note that effect properties will be fixed when the effect is loaded
2023         // as there is more information available about the parameter type.
2024         auto filters = m_doc.elementsByTagName(QStringLiteral("filter"));
2025         qDebug() << "Found" << filters.count() << "filters.";
2026         for (int i = 0; i < filters.count(); i++) {
2027             QDomElement filter = filters.at(i).toElement();
2028             QString mltService = Xml::getXmlProperty(filter, "mlt_service");
2029 
2030             QList<QString> propertiesToFix;
2031             propertiesToFix.append(filterPropertiesToFix);
2032 
2033             if (servicePropertiesToFix.contains(mltService)) {
2034                 propertiesToFix.append(servicePropertiesToFix.value(mltService));
2035             }
2036 
2037             qDebug() << "Decimal point: Found MLT service" << mltService << "and will fix " << propertiesToFix;
2038 
2039             for (const QString &property : propertiesToFix) {
2040                 QString value = Xml::getXmlProperty(filter, property);
2041                 if (!value.isEmpty()) {
2042                     QString newValue = QString(value).replace(decimalPoint, ".");
2043                     if (value != newValue) {
2044                         Xml::setXmlProperty(filter, property, newValue);
2045                         qDebug() << "Decimal point: Converted service property" << mltService << "from " << value << "to" << newValue;
2046                     }
2047                 }
2048             }
2049         }
2050 
2051         auto fixAttribute = [fixTimecode](QDomElement &el, const QString &attributeName) {
2052             if (el.hasAttribute(attributeName)) {
2053                 QString oldValue = el.attribute(attributeName, "");
2054                 QString newValue(oldValue);
2055                 fixTimecode(newValue);
2056                 if (oldValue != newValue) {
2057                     el.setAttribute(attributeName, newValue);
2058                     qDebug() << "Decimal point: Converted" << el.nodeName() << attributeName << "from" << oldValue << "to" << newValue;
2059                 }
2060             }
2061         };
2062 
2063         // Fix attributes
2064         QList<QString> tagsToFix = {"producer", "filter", "tractor", "entry", "transition", "blank"};
2065         for (const QString &tag : tagsToFix) {
2066             QDomNodeList elements = m_doc.elementsByTagName(tag);
2067             for (int i = 0; i < elements.count(); i++) {
2068                 QDomElement el = elements.at(i).toElement();
2069                 fixAttribute(el, "in");
2070                 fixAttribute(el, "out");
2071                 fixAttribute(el, "length");
2072             }
2073         }
2074 
2075         auto mltElements = m_doc.elementsByTagName("mlt");
2076         for (int i = 0; i < mltElements.count(); i++) {
2077             QDomElement mltElement = mltElements.at(i).toElement();
2078             if (mltElement.hasAttribute("LC_NUMERIC")) {
2079                 qDebug() << "Removing LC_NUMERIC=" << mltElement.attribute("LC_NUMERIC") << "from root node";
2080                 mltElement.removeAttribute("LC_NUMERIC");
2081             }
2082         }
2083 
2084         modified = true;
2085         qDebug() << "Decimal point: New XML: " << m_doc.toString(-1);
2086 
2087     } else {
2088         qDebug() << "Decimal point is OK";
2089     }
2090 
2091     m_modified |= modified;
2092     return modified ? decimalPoint : QString();
2093 }
2094 
2095 void DocumentValidator::convertKeyframeEffect_093(const QDomElement &effect, const QStringList &params, QMap<int, double> &values, int offset)
2096 {
2097     QLocale locale; // Used for upgrading to pre-1.0 version → OK
2098     int in = effect.attribute(QStringLiteral("in")).toInt() - offset;
2099     values.insert(in, locale.toDouble(Xml::getXmlProperty(effect, params.at(0))));
2100     QString endValue = Xml::getXmlProperty(effect, params.at(1));
2101     if (!endValue.isEmpty()) {
2102         int out = effect.attribute(QStringLiteral("out")).toInt() - offset;
2103         values.insert(out, locale.toDouble(endValue));
2104     }
2105 }
2106 
2107 void DocumentValidator::updateProducerInfo(const QDomElement &prod, const QDomElement &source)
2108 {
2109     QString pxy = source.attribute(QStringLiteral("proxy"));
2110     if (pxy.length() > 1) {
2111         Xml::setXmlProperty(prod, QStringLiteral("kdenlive:proxy"), pxy);
2112         Xml::setXmlProperty(prod, QStringLiteral("kdenlive:originalurl"), source.attribute(QStringLiteral("resource")));
2113     }
2114     if (source.hasAttribute(QStringLiteral("file_hash"))) {
2115         Xml::setXmlProperty(prod, QStringLiteral("kdenlive:file_hash"), source.attribute(QStringLiteral("file_hash")));
2116     }
2117     if (source.hasAttribute(QStringLiteral("file_size"))) {
2118         Xml::setXmlProperty(prod, QStringLiteral("kdenlive:file_size"), source.attribute(QStringLiteral("file_size")));
2119     }
2120     if (source.hasAttribute(QStringLiteral("name"))) {
2121         Xml::setXmlProperty(prod, QStringLiteral("kdenlive:clipname"), source.attribute(QStringLiteral("name")));
2122     }
2123     if (source.hasAttribute(QStringLiteral("zone_out"))) {
2124         Xml::setXmlProperty(prod, QStringLiteral("kdenlive:zone_out"), source.attribute(QStringLiteral("zone_out")));
2125     }
2126     if (source.hasAttribute(QStringLiteral("zone_in"))) {
2127         Xml::setXmlProperty(prod, QStringLiteral("kdenlive:zone_in"), source.attribute(QStringLiteral("zone_in")));
2128     }
2129     if (source.hasAttribute(QStringLiteral("cutzones"))) {
2130         QString zoneData = source.attribute(QStringLiteral("cutzones"));
2131         const QStringList zoneList = zoneData.split(QLatin1Char(';'));
2132         int ct = 1;
2133         for (const QString &data : zoneList) {
2134             QString zoneName = data.section(QLatin1Char('-'), 2);
2135             if (zoneName.isEmpty()) {
2136                 zoneName = i18n("Zone %1", ct++);
2137             }
2138             Xml::setXmlProperty(prod, QStringLiteral("kdenlive:clipzone.") + zoneName,
2139                                 data.section(QLatin1Char('-'), 0, 0) + QLatin1Char(';') + data.section(QLatin1Char('-'), 1, 1));
2140         }
2141     }
2142 }
2143 
2144 QStringList DocumentValidator::getInfoFromEffectName(const QString &oldName)
2145 {
2146     QStringList info;
2147     // Returns a list to convert old Kdenlive ladspa effects
2148     if (oldName == QLatin1String("pitch_shift")) {
2149         info << QStringLiteral("ladspa.1433");
2150         info << QStringLiteral("pitch=0");
2151     } else if (oldName == QLatin1String("vinyl")) {
2152         info << QStringLiteral("ladspa.1905");
2153         info << QStringLiteral("year=0");
2154         info << QStringLiteral("rpm=1");
2155         info << QStringLiteral("warping=2");
2156         info << QStringLiteral("crackle=3");
2157         info << QStringLiteral("wear=4");
2158     } else if (oldName == QLatin1String("room_reverb")) {
2159         info << QStringLiteral("ladspa.1216");
2160         info << QStringLiteral("room=0");
2161         info << QStringLiteral("delay=1");
2162         info << QStringLiteral("damp=2");
2163     } else if (oldName == QLatin1String("reverb")) {
2164         info << QStringLiteral("ladspa.1423");
2165         info << QStringLiteral("room=0");
2166         info << QStringLiteral("damp=1");
2167     } else if (oldName == QLatin1String("rate_scale")) {
2168         info << QStringLiteral("ladspa.1417");
2169         info << QStringLiteral("rate=0");
2170     } else if (oldName == QLatin1String("pitch_scale")) {
2171         info << QStringLiteral("ladspa.1193");
2172         info << QStringLiteral("coef=0");
2173     } else if (oldName == QLatin1String("phaser")) {
2174         info << QStringLiteral("ladspa.1217");
2175         info << QStringLiteral("rate=0");
2176         info << QStringLiteral("depth=1");
2177         info << QStringLiteral("feedback=2");
2178         info << QStringLiteral("spread=3");
2179     } else if (oldName == QLatin1String("limiter")) {
2180         info << QStringLiteral("ladspa.1913");
2181         info << QStringLiteral("gain=0");
2182         info << QStringLiteral("limit=1");
2183         info << QStringLiteral("release=2");
2184     } else if (oldName == QLatin1String("equalizer_15")) {
2185         info << QStringLiteral("ladspa.1197");
2186         info << QStringLiteral("1=0");
2187         info << QStringLiteral("2=1");
2188         info << QStringLiteral("3=2");
2189         info << QStringLiteral("4=3");
2190         info << QStringLiteral("5=4");
2191         info << QStringLiteral("6=5");
2192         info << QStringLiteral("7=6");
2193         info << QStringLiteral("8=7");
2194         info << QStringLiteral("9=8");
2195         info << QStringLiteral("10=9");
2196         info << QStringLiteral("11=10");
2197         info << QStringLiteral("12=11");
2198         info << QStringLiteral("13=12");
2199         info << QStringLiteral("14=13");
2200         info << QStringLiteral("15=14");
2201     } else if (oldName == QLatin1String("equalizer")) {
2202         info << QStringLiteral("ladspa.1901");
2203         info << QStringLiteral("logain=0");
2204         info << QStringLiteral("midgain=1");
2205         info << QStringLiteral("higain=2");
2206     } else if (oldName == QLatin1String("declipper")) {
2207         info << QStringLiteral("ladspa.1195");
2208     }
2209     return info;
2210 }
2211 
2212 QString DocumentValidator::colorToString(const QColor &c)
2213 {
2214     QString ret = QStringLiteral("%1,%2,%3,%4");
2215     ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
2216     return ret;
2217 }
2218 
2219 bool DocumentValidator::isProject() const
2220 {
2221     return m_doc.documentElement().tagName() == QLatin1String("mlt");
2222 }
2223 
2224 bool DocumentValidator::isModified() const
2225 {
2226     return m_modified;
2227 }
2228 
2229 bool DocumentValidator::checkMovit()
2230 {
2231     QString playlist = m_doc.toString();
2232     if (!playlist.contains(QStringLiteral("movit."))) {
2233         // Project does not use Movit GLSL effects, we can load it
2234         return true;
2235     }
2236     if (KMessageBox::questionTwoActions(QApplication::activeWindow(),
2237                                         i18n("The project file uses some GPU effects. GPU acceleration is not currently enabled.\nDo you want to convert the "
2238                                              "project to a non-GPU version?\nThis might result in data loss."),
2239                                         i18n("GPU Effects"), KGuiItem(i18n("Convert")), KStandardGuiItem::cancel()) != KMessageBox::PrimaryAction) {
2240         return false;
2241     }
2242     // Try to convert Movit filters to their non GPU equivalent
2243     QStringList convertedFilters;
2244     QStringList discardedFilters;
2245     bool hasWB = EffectsRepository::get()->exists(QStringLiteral("frei0r.colgate"));
2246     bool hasBlur = EffectsRepository::get()->exists(QStringLiteral("frei0r.IIRblur"));
2247     QString compositeTrans;
2248     if (KdenliveSettings::preferredcomposite() != i18n("auto") && TransitionsRepository::get()->exists(KdenliveSettings::preferredcomposite())) {
2249         compositeTrans = KdenliveSettings::preferredcomposite();
2250     } else if (TransitionsRepository::get()->exists(QStringLiteral("frei0r.cairoblend"))) {
2251         compositeTrans = QStringLiteral("frei0r.cairoblend");
2252     } else if (TransitionsRepository::get()->exists(QStringLiteral("qtblend"))) {
2253         compositeTrans = QStringLiteral("qtblend");
2254     }
2255 
2256     // Parse all effects in document
2257     QDomNodeList filters = m_doc.elementsByTagName(QStringLiteral("filter"));
2258     int max = filters.count();
2259     for (int i = 0; i < max; ++i) {
2260         QDomElement filt = filters.at(i).toElement();
2261         QString filterId = filt.attribute(QStringLiteral("id"));
2262         if (!filterId.startsWith(QLatin1String("movit."))) {
2263             continue;
2264         }
2265         if (filterId == QLatin1String("movit.white_balance") && hasWB) {
2266             // Convert to frei0r.colgate
2267             filt.setAttribute(QStringLiteral("id"), QStringLiteral("frei0r.colgate"));
2268             Xml::setXmlProperty(filt, QStringLiteral("kdenlive_id"), QStringLiteral("frei0r.colgate"));
2269             Xml::setXmlProperty(filt, QStringLiteral("tag"), QStringLiteral("frei0r.colgate"));
2270             Xml::setXmlProperty(filt, QStringLiteral("mlt_service"), QStringLiteral("frei0r.colgate"));
2271             Xml::renameXmlProperty(filt, QStringLiteral("neutral_color"), QStringLiteral("Neutral Color"));
2272             QString value = Xml::getXmlProperty(filt, QStringLiteral("color_temperature"));
2273             value = factorizeGeomValue(value, 15000.0);
2274             Xml::setXmlProperty(filt, QStringLiteral("color_temperature"), value);
2275             Xml::renameXmlProperty(filt, QStringLiteral("color_temperature"), QStringLiteral("Color Temperature"));
2276             convertedFilters << filterId;
2277             continue;
2278         }
2279         if (filterId == QLatin1String("movit.blur") && hasBlur) {
2280             // Convert to frei0r.IIRblur
2281             filt.setAttribute(QStringLiteral("id"), QStringLiteral("frei0r.IIRblur"));
2282             Xml::setXmlProperty(filt, QStringLiteral("kdenlive_id"), QStringLiteral("frei0r.IIRblur"));
2283             Xml::setXmlProperty(filt, QStringLiteral("tag"), QStringLiteral("frei0r.IIRblur"));
2284             Xml::setXmlProperty(filt, QStringLiteral("mlt_service"), QStringLiteral("frei0r.IIRblur"));
2285             Xml::renameXmlProperty(filt, QStringLiteral("radius"), QStringLiteral("Amount"));
2286             QString value = Xml::getXmlProperty(filt, QStringLiteral("Amount"));
2287             value = factorizeGeomValue(value, 14.0);
2288             Xml::setXmlProperty(filt, QStringLiteral("Amount"), value);
2289             convertedFilters << filterId;
2290             continue;
2291         }
2292         if (filterId == QLatin1String("movit.mirror")) {
2293             // Convert to MLT's mirror
2294             filt.setAttribute(QStringLiteral("id"), QStringLiteral("mirror"));
2295             Xml::setXmlProperty(filt, QStringLiteral("kdenlive_id"), QStringLiteral("mirror"));
2296             Xml::setXmlProperty(filt, QStringLiteral("tag"), QStringLiteral("mirror"));
2297             Xml::setXmlProperty(filt, QStringLiteral("mlt_service"), QStringLiteral("mirror"));
2298             Xml::setXmlProperty(filt, QStringLiteral("mirror"), QStringLiteral("flip"));
2299             convertedFilters << filterId;
2300             continue;
2301         }
2302         if (filterId.startsWith(QLatin1String("movit."))) {
2303             // TODO: implement conversion for more filters
2304             discardedFilters << filterId;
2305         }
2306     }
2307 
2308     // Parse all transitions in document
2309     QDomNodeList transitions = m_doc.elementsByTagName(QStringLiteral("transition"));
2310     max = transitions.count();
2311     for (int i = 0; i < max; ++i) {
2312         QDomElement t = transitions.at(i).toElement();
2313         QString transId = Xml::getXmlProperty(t, QStringLiteral("mlt_service"));
2314         if (!transId.startsWith(QLatin1String("movit."))) {
2315             continue;
2316         }
2317         if (transId == QLatin1String("movit.overlay") && !compositeTrans.isEmpty()) {
2318             // Convert to frei0r.cairoblend
2319             Xml::setXmlProperty(t, QStringLiteral("mlt_service"), compositeTrans);
2320             convertedFilters << transId;
2321             continue;
2322         }
2323         if (transId.startsWith(QLatin1String("movit."))) {
2324             // TODO: implement conversion for more filters
2325             discardedFilters << transId;
2326         }
2327     }
2328 
2329     convertedFilters.removeDuplicates();
2330     discardedFilters.removeDuplicates();
2331     if (discardedFilters.isEmpty()) {
2332         KMessageBox::informationList(QApplication::activeWindow(), i18n("The following filters/transitions were converted to non GPU versions:"),
2333                                      convertedFilters);
2334     } else {
2335         KMessageBox::informationList(QApplication::activeWindow(), i18n("The following filters/transitions were deleted from the project:"), discardedFilters);
2336     }
2337     m_modified = true;
2338     QString scene = m_doc.toString();
2339     scene.replace(QLatin1String("movit."), QString());
2340     m_doc.setContent(scene);
2341     return true;
2342 }
2343 
2344 QString DocumentValidator::factorizeGeomValue(const QString &value, double factor)
2345 {
2346     const QStringList vals = value.split(QLatin1Char(';'));
2347     QString result;
2348     for (int i = 0; i < vals.count(); i++) {
2349         const QString &s = vals.at(i);
2350         QString key = s.section(QLatin1Char('='), 0, 0);
2351         QString val = s.section(QLatin1Char('='), 1, 1);
2352         double v = val.toDouble() / factor;
2353         result.append(key + QLatin1Char('=') + QString::number(v, 'f'));
2354         if (i + 1 < vals.count()) {
2355             result.append(QLatin1Char(';'));
2356         }
2357     }
2358     return result;
2359 }
2360 
2361 void DocumentValidator::checkOrphanedProducers()
2362 {
2363     QDomElement mlt = m_doc.firstChildElement(QStringLiteral("mlt"));
2364     QDomElement main = mlt.firstChildElement(QStringLiteral("playlist"));
2365     QDomNodeList bin_producers = main.childNodes();
2366     QStringList binProducers;
2367     for (int k = 0; k < bin_producers.count(); k++) {
2368         QDomElement mltprod = bin_producers.at(k).toElement();
2369         if (mltprod.tagName() != QLatin1String("entry")) {
2370             continue;
2371         }
2372         binProducers << mltprod.attribute(QStringLiteral("producer"));
2373     }
2374 
2375     QDomNodeList producers = m_doc.elementsByTagName(QStringLiteral("producer"));
2376     int max = producers.count();
2377     QStringList allProducers;
2378     for (int i = 0; i < max; ++i) {
2379         QDomElement prod = producers.item(i).toElement();
2380         if (prod.isNull()) {
2381             continue;
2382         }
2383         allProducers << prod.attribute(QStringLiteral("id"));
2384     }
2385 
2386     QDomDocumentFragment frag = m_doc.createDocumentFragment();
2387     QDomDocumentFragment trackProds = m_doc.createDocumentFragment();
2388     for (int i = 0; i < producers.count(); ++i) {
2389         QDomElement prod = producers.item(i).toElement();
2390         if (prod.isNull()) {
2391             continue;
2392         }
2393         QString id = prod.attribute(QStringLiteral("id")).section(QLatin1Char('_'), 0, 0);
2394         if (id.startsWith(QLatin1String("slowmotion")) || id == QLatin1String("black")) {
2395             continue;
2396         }
2397         if (!binProducers.contains(id)) {
2398             QString binId = Xml::getXmlProperty(prod, QStringLiteral("kdenlive:binid"));
2399             Xml::setXmlProperty(prod, QStringLiteral("kdenlive:id"), binId);
2400             if (!binId.isEmpty() && binProducers.contains(binId)) {
2401                 continue;
2402             }
2403             qCWarning(KDENLIVE_LOG) << " ///////// WARNING, FOUND UNKNOWN PRODUDER: " << id << " ----------------";
2404             // This producer is unknown to Bin
2405             QString service = Xml::getXmlProperty(prod, QStringLiteral("mlt_service"));
2406             QString distinctiveTag(QStringLiteral("resource"));
2407             if (service == QLatin1String("kdenlivetitle")) {
2408                 distinctiveTag = QStringLiteral("xmldata");
2409             }
2410             QString orphanValue = Xml::getXmlProperty(prod, distinctiveTag);
2411             for (int j = 0; j < producers.count(); j++) {
2412                 // Search for a similar producer
2413                 QDomElement binProd = producers.item(j).toElement();
2414                 binId = binProd.attribute(QStringLiteral("id")).section(QLatin1Char('_'), 0, 0);
2415                 if (service != QLatin1String("timewarp") && (binId.startsWith(QLatin1String("slowmotion")) || !binProducers.contains(binId))) {
2416                     continue;
2417                 }
2418                 QString binService = Xml::getXmlProperty(binProd, QStringLiteral("mlt_service"));
2419                 qCDebug(KDENLIVE_LOG) << " / /LKNG FOR: " << service << " / " << orphanValue << ", checking: " << binProd.attribute(QStringLiteral("id"));
2420                 if (service != binService) {
2421                     continue;
2422                 }
2423                 QString binValue = Xml::getXmlProperty(binProd, distinctiveTag);
2424                 if (binValue == orphanValue) {
2425                     // Found probable source producer, replace
2426                     frag.appendChild(prod);
2427                     if (i > 0) {
2428                         i--;
2429                     }
2430                     QDomNodeList entries = m_doc.elementsByTagName(QStringLiteral("entry"));
2431                     for (int k = 0; k < entries.count(); k++) {
2432                         QDomElement entry = entries.at(k).toElement();
2433                         if (entry.attribute(QStringLiteral("producer")) == id) {
2434                             QString entryId = binId;
2435                             if (service.contains(QStringLiteral("avformat")) || service == QLatin1String("xml") || service == QLatin1String("consumer")) {
2436                                 // We must use track producer, find track for this entry
2437                                 QString trackPlaylist = entry.parentNode().toElement().attribute(QStringLiteral("id"));
2438                                 entryId.append(QLatin1Char('_') + trackPlaylist);
2439                             }
2440                             if (!allProducers.contains(entryId)) {
2441                                 // The track producer does not exist, create a clone for it
2442                                 QDomElement cloned = binProd.cloneNode(true).toElement();
2443                                 cloned.setAttribute(QStringLiteral("id"), entryId);
2444                                 trackProds.appendChild(cloned);
2445                                 allProducers << entryId;
2446                             }
2447                             entry.setAttribute(QStringLiteral("producer"), entryId);
2448                             m_modified = true;
2449                         }
2450                     }
2451                     continue;
2452                 }
2453             }
2454         }
2455     }
2456     if (!trackProds.isNull()) {
2457         QDomNode firstProd = m_doc.firstChildElement(QStringLiteral("producer"));
2458         mlt.insertBefore(trackProds, firstProd);
2459     }
2460 }
2461 
2462 void DocumentValidator::fixTitleProducerLocale(QDomElement &producer)
2463 {
2464     QString data = Xml::getXmlProperty(producer, QStringLiteral("xmldata"));
2465     QDomDocument doc;
2466     doc.setContent(data);
2467     QDomNodeList nodes = doc.elementsByTagName(QStringLiteral("position"));
2468     bool fixed = false;
2469     for (int i = 0; i < nodes.count(); i++) {
2470         QDomElement pos = nodes.at(i).toElement();
2471         QString x = pos.attribute(QStringLiteral("x"));
2472         QString y = pos.attribute(QStringLiteral("y"));
2473         if (x.contains(QLatin1Char(','))) {
2474             // x pos was saved in locale format, fix
2475             x = x.section(QLatin1Char(','), 0, 0);
2476             pos.setAttribute(QStringLiteral("x"), x);
2477             fixed = true;
2478         }
2479         if (y.contains(QLatin1Char(','))) {
2480             // x pos was saved in locale format, fix
2481             y = y.section(QLatin1Char(','), 0, 0);
2482             pos.setAttribute(QStringLiteral("y"), y);
2483             fixed = true;
2484         }
2485     }
2486     nodes = doc.elementsByTagName(QStringLiteral("content"));
2487     for (int i = 0; i < nodes.count(); i++) {
2488         QDomElement pos = nodes.at(i).toElement();
2489         QString x = pos.attribute(QStringLiteral("font-outline"));
2490         QString y = pos.attribute(QStringLiteral("textwidth"));
2491         if (x.contains(QLatin1Char(','))) {
2492             // x pos was saved in locale format, fix
2493             x = x.section(QLatin1Char(','), 0, 0);
2494             pos.setAttribute(QStringLiteral("font-outline"), x);
2495             fixed = true;
2496         }
2497         if (y.contains(QLatin1Char(','))) {
2498             // x pos was saved in locale format, fix
2499             y = y.section(QLatin1Char(','), 0, 0);
2500             pos.setAttribute(QStringLiteral("textwidth"), y);
2501             fixed = true;
2502         }
2503     }
2504     if (fixed) {
2505         Xml::setXmlProperty(producer, QStringLiteral("xmldata"), doc.toString());
2506     }
2507 }