File indexing completed on 2024-04-14 04:46:29

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