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