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 ¶ms, 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 }