1 /***************************************************************************
2 * Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@kdenlive.org) *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
18 ***************************************************************************/
21 #include "documentvalidator.h"
22 #include "definitions.h"
23 #include "initeffects.h"
26 #include <KMessageBox>
27 #include <KApplication>
34 #include <mlt++/Mlt.h>
39 DocumentValidator::DocumentValidator(QDomDocument doc):
44 bool DocumentValidator::validate(const double currentVersion)
46 QDomElement mlt = m_doc.firstChildElement("mlt");
47 // At least the root element must be there
51 QDomElement kdenliveDoc = mlt.firstChildElement("kdenlivedoc");
52 // Check if we're validating a Kdenlive project
53 if (kdenliveDoc.isNull())
56 // Previous MLT / Kdenlive versions used C locale by default
57 QLocale documentLocale = QLocale::c();
59 if (mlt.hasAttribute("LC_NUMERIC")) {
60 // Set locale for the document
61 // WARNING: what should be done in case the locale does not exist on the system?
62 setlocale(LC_NUMERIC, mlt.attribute("LC_NUMERIC").toUtf8().constData());
63 documentLocale = QLocale(mlt.attribute("LC_NUMERIC"));
66 documentLocale.setNumberOptions(QLocale::OmitGroupSeparator);
67 if (documentLocale != QLocale()) {
68 QLocale::setDefault(documentLocale);
69 // locale conversion might need to be redone
70 initEffects::parseEffectFiles();
73 // TODO: remove after string freeze
75 KMessageBox::sorry(kapp->activeWindow(), i18n("The document you are opening uses a different locale (%1) than your system. You can only open and render it, no editing is supported unless you change your system's locale.", mlt.attribute("LC_NUMERIC")), i18n("Read only project"));
77 // Upgrade the document to the latest version
78 if (!upgrade(documentLocale.toDouble(kdenliveDoc.attribute("version")), currentVersion))
82 * Check the syntax (this will be replaced by XSD validation with Qt 4.6)
83 * and correct some errors
86 // Return (or create) the tractor
87 QDomElement tractor = mlt.firstChildElement("tractor");
88 if (tractor.isNull()) {
90 tractor = m_doc.createElement("tractor");
91 tractor.setAttribute("global_feed", "1");
92 tractor.setAttribute("in", "0");
93 tractor.setAttribute("out", "-1");
94 tractor.setAttribute("id", "maintractor");
95 mlt.appendChild(tractor);
99 * Make sure at least one track exists, and they're equal in number to
100 * to the maximum between MLT and Kdenlive playlists and tracks
102 QDomNodeList playlists = m_doc.elementsByTagName("playlist");
103 int tracksMax = playlists.count() - 1; // Remove the black track
104 QDomNodeList tracks = tractor.elementsByTagName("track");
105 tracksMax = qMax(tracks.count() - 1, tracksMax);
106 QDomNodeList tracksinfo = kdenliveDoc.elementsByTagName("trackinfo");
107 tracksMax = qMax(tracksinfo.count(), tracksMax);
108 tracksMax = qMax(1, tracksMax); // Force existance of one track
109 if (playlists.count() - 1 < tracksMax ||
110 tracks.count() - 1 < tracksMax ||
111 tracksinfo.count() < tracksMax) {
112 kDebug() << "//// WARNING, PROJECT IS CORRUPTED, MISSING TRACK";
115 // use the MLT tracks as reference
116 if (tracks.count() - 1 < tracksMax) {
117 // Looks like one MLT track is missing, remove the extra Kdenlive track if there is one.
118 if (tracksinfo.count() != tracks.count() - 1) {
119 // The Kdenlive tracks are not ok, clear and rebuild them
120 QDomNode tinfo = kdenliveDoc.firstChildElement("tracksinfo");
121 QDomNode tnode = tinfo.firstChild();
122 while (!tnode.isNull()) {
123 tinfo.removeChild(tnode);
124 tnode = tinfo.firstChild();
127 for (int i = 1; i < tracks.count(); i++) {
128 QString hide = tracks.at(i).toElement().attribute("hide");
129 QDomElement newTrack = m_doc.createElement("trackinfo");
130 if (hide == "video") {
132 newTrack.setAttribute("type", "audio");
133 newTrack.setAttribute("blind", 1);
134 newTrack.setAttribute("mute", 0);
135 newTrack.setAttribute("lock", 0);
137 newTrack.setAttribute("blind", 0);
138 newTrack.setAttribute("mute", 0);
139 newTrack.setAttribute("lock", 0);
141 tinfo.appendChild(newTrack);
146 if (playlists.count() - 1 < tracksMax) {
147 difference = tracksMax - (playlists.count() - 1);
148 for (int i = 0; i < difference; ++i) {
149 QDomElement playlist = m_doc.createElement("playlist");
150 mlt.appendChild(playlist);
153 if (tracks.count() - 1 < tracksMax) {
154 difference = tracksMax - (tracks.count() - 1);
155 for (int i = 0; i < difference; ++i) {
156 QDomElement track = m_doc.createElement("track");
157 tractor.appendChild(track);
160 if (tracksinfo.count() < tracksMax) {
161 QDomElement tracksinfoElm = kdenliveDoc.firstChildElement("tracksinfo");
162 if (tracksinfoElm.isNull()) {
163 tracksinfoElm = m_doc.createElement("tracksinfo");
164 kdenliveDoc.appendChild(tracksinfoElm);
166 difference = tracksMax - tracksinfo.count();
167 for (int i = 0; i < difference; ++i) {
168 QDomElement trackinfo = m_doc.createElement("trackinfo");
169 trackinfo.setAttribute("mute", "0");
170 trackinfo.setAttribute("locked", "0");
171 tracksinfoElm.appendChild(trackinfo);
176 // TODO: check the tracks references
177 // TODO: check internal mix transitions
184 bool DocumentValidator::upgrade(double version, const double currentVersion)
186 kDebug() << "Opening a document with version " << version;
188 // No conversion needed
189 if (version == currentVersion) {
193 // The document is too new
194 if (version > currentVersion) {
195 kDebug() << "Unable to open document with version " << version;
196 KMessageBox::sorry(kapp->activeWindow(), i18n("This project type is unsupported (version %1) and can't be loaded.\nPlease consider upgrading your Kdenlive version.", version), i18n("Unable to open project"));
200 // Unsupported document versions
201 if (version == 0.5 || version == 0.7) {
202 kDebug() << "Unable to open document with version " << version;
203 KMessageBox::sorry(kapp->activeWindow(), i18n("This project type is unsupported (version %1) and can't be loaded.", version), i18n("Unable to open project"));
208 QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
209 QDomElement infoXml = infoXmlNode.toElement();
210 infoXml.setAttribute("upgraded", "1");
212 if (version <= 0.6) {
213 QDomElement infoXml_old = infoXmlNode.cloneNode(true).toElement(); // Needed for folders
214 QDomNode westley = m_doc.elementsByTagName("westley").at(1);
215 QDomNode tractor = m_doc.elementsByTagName("tractor").at(0);
216 QDomNode multitrack = m_doc.elementsByTagName("multitrack").at(0);
217 QDomNodeList playlists = m_doc.elementsByTagName("playlist");
219 QDomNode props = m_doc.elementsByTagName("properties").at(0).toElement();
220 QString profile = props.toElement().attribute("videoprofile");
221 int startPos = props.toElement().attribute("timeline_position").toInt();
222 if (profile == "dv_wide")
223 profile = "dv_pal_wide";
225 // move playlists outside of tractor and add the tracks instead
226 int max = playlists.count();
227 if (westley.isNull()) {
228 westley = m_doc.createElement("westley");
229 m_doc.documentElement().appendChild(westley);
231 if (tractor.isNull()) {
232 kDebug() << "// NO MLT PLAYLIST, building empty one";
233 QDomElement blank_tractor = m_doc.createElement("tractor");
234 westley.appendChild(blank_tractor);
235 QDomElement blank_playlist = m_doc.createElement("playlist");
236 blank_playlist.setAttribute("id", "black_track");
237 westley.insertBefore(blank_playlist, QDomNode());
238 QDomElement blank_track = m_doc.createElement("track");
239 blank_track.setAttribute("producer", "black_track");
240 blank_tractor.appendChild(blank_track);
242 QDomNodeList kdenlivetracks = m_doc.elementsByTagName("kdenlivetrack");
243 for (int i = 0; i < kdenlivetracks.count(); i++) {
244 blank_playlist = m_doc.createElement("playlist");
245 blank_playlist.setAttribute("id", "playlist" + QString::number(i));
246 westley.insertBefore(blank_playlist, QDomNode());
247 blank_track = m_doc.createElement("track");
248 blank_track.setAttribute("producer", "playlist" + QString::number(i));
249 blank_tractor.appendChild(blank_track);
250 if (kdenlivetracks.at(i).toElement().attribute("cliptype") == "Sound") {
251 blank_playlist.setAttribute("hide", "video");
252 blank_track.setAttribute("hide", "video");
255 } else for (int i = 0; i < max; i++) {
256 QDomNode n = playlists.at(i);
257 westley.insertBefore(n, QDomNode());
258 QDomElement pl = n.toElement();
259 QDomElement track = m_doc.createElement("track");
260 QString trackType = pl.attribute("hide");
261 if (!trackType.isEmpty())
262 track.setAttribute("hide", trackType);
263 QString playlist_id = pl.attribute("id");
264 if (playlist_id.isEmpty()) {
265 playlist_id = "black_track";
266 pl.setAttribute("id", playlist_id);
268 track.setAttribute("producer", playlist_id);
269 //tractor.appendChild(track);
270 #define KEEP_TRACK_ORDER 1
271 #ifdef KEEP_TRACK_ORDER
272 tractor.insertAfter(track, QDomNode());
274 // Insert the new track in an order that hopefully matches the 3 video, then 2 audio tracks of Kdenlive 0.7.0
275 // insertion sort - O( tracks*tracks )
276 // Note, this breaks _all_ transitions - but you can move them up and down afterwards.
277 QDomElement tractor_elem = tractor.toElement();
278 if (! tractor_elem.isNull()) {
279 QDomNodeList tracks = tractor_elem.elementsByTagName("track");
280 int size = tracks.size();
282 tractor.insertAfter(track, QDomNode());
284 bool inserted = false;
285 for (int i = 0; i < size; ++i) {
286 QDomElement track_elem = tracks.at(i).toElement();
287 if (track_elem.isNull()) {
288 tractor.insertAfter(track, QDomNode());
292 kDebug() << "playlist_id: " << playlist_id << " producer:" << track_elem.attribute("producer");
293 if (playlist_id < track_elem.attribute("producer")) {
294 tractor.insertBefore(track, track_elem);
300 // Reach here, no insertion, insert last
302 tractor.insertAfter(track, QDomNode());
306 kWarning() << "tractor was not a QDomElement";
307 tractor.insertAfter(track, QDomNode());
311 tractor.removeChild(multitrack);
313 // audio track mixing transitions should not be added to track view, so add required attribute
314 QDomNodeList transitions = m_doc.elementsByTagName("transition");
315 max = transitions.count();
316 for (int i = 0; i < max; i++) {
317 QDomElement tr = transitions.at(i).toElement();
318 if (tr.attribute("combine") == "1" && tr.attribute("mlt_service") == "mix") {
319 QDomElement property = m_doc.createElement("property");
320 property.setAttribute("name", "internal_added");
321 QDomText value = m_doc.createTextNode("237");
322 property.appendChild(value);
323 tr.appendChild(property);
324 property = m_doc.createElement("property");
325 property.setAttribute("name", "mlt_service");
326 value = m_doc.createTextNode("mix");
327 property.appendChild(value);
328 tr.appendChild(property);
330 // convert transition
331 QDomNamedNodeMap attrs = tr.attributes();
332 for (int j = 0; j < attrs.count(); j++) {
333 QString attrName = attrs.item(j).nodeName();
334 if (attrName != "in" && attrName != "out" && attrName != "id") {
335 QDomElement property = m_doc.createElement("property");
336 property.setAttribute("name", attrName);
337 QDomText value = m_doc.createTextNode(attrs.item(j).nodeValue());
338 property.appendChild(value);
339 tr.appendChild(property);
345 // move transitions after tracks
346 for (int i = 0; i < max; i++) {
347 tractor.insertAfter(transitions.at(0), QDomNode());
350 // Fix filters format
351 QDomNodeList entries = m_doc.elementsByTagName("entry");
352 max = entries.count();
353 for (int i = 0; i < max; i++) {
356 QDomNode m = entries.at(i).firstChild();
357 while (!m.isNull()) {
358 if (m.toElement().tagName() == "filter") {
359 QDomElement filt = m.toElement();
360 QDomNamedNodeMap attrs = filt.attributes();
361 QString current_id = filt.attribute("kdenlive_id");
362 if (current_id != last_id) {
364 last_id = current_id;
366 QDomElement e = m_doc.createElement("property");
367 e.setAttribute("name", "kdenlive_ix");
368 QDomText value = m_doc.createTextNode(QString::number(effectix));
369 e.appendChild(value);
371 for (int j = 0; j < attrs.count(); j++) {
372 QDomAttr a = attrs.item(j).toAttr();
374 kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
375 QDomElement e = m_doc.createElement("property");
376 e.setAttribute("name", a.name());
377 QDomText value = m_doc.createTextNode(a.value());
378 e.appendChild(value);
389 QDomNodeList filters = m_doc.elementsByTagName("filter");
390 max = filters.count();
393 for (int i = 0; i < max; i++) {
394 QDomElement filt = filters.at(i).toElement();
395 QDomNamedNodeMap attrs = filt.attributes();
396 QString current_id = filt.attribute("kdenlive_id");
397 if (current_id != last_id) {
399 last_id = current_id;
401 QDomElement e = m_doc.createElement("property");
402 e.setAttribute("name", "kdenlive_ix");
403 QDomText value = m_doc.createTextNode(QString::number(1));
404 e.appendChild(value);
406 for (int j = 0; j < attrs.count(); j++) {
407 QDomAttr a = attrs.item(j).toAttr();
409 kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
410 QDomElement e = m_doc.createElement("property");
411 e.setAttribute("name", a.name());
412 QDomText value = m_doc.createTextNode(a.value());
413 e.appendChild(value);
420 QDomNodeList producers = westley.toElement().elementsByTagName("producer");
421 max = producers.count();
422 for (int i = 0; i < max; i++) {
423 QDomElement prod = producers.at(i).toElement();
424 if (prod.attribute("mlt_service") == "framebuffer") {
425 QString slowmotionprod = prod.attribute("resource");
426 slowmotionprod.replace(':', '?');
427 kDebug() << "// FOUND WRONG SLOWMO, new: " << slowmotionprod;
428 prod.setAttribute("resource", slowmotionprod);
431 // move producers to correct place, markers to a global list, fix clip descriptions
432 QDomElement markers = m_doc.createElement("markers");
433 // This will get the xml producers:
434 producers = m_doc.elementsByTagName("producer");
435 max = producers.count();
436 for (int i = 0; i < max; i++) {
437 QDomElement prod = producers.at(0).toElement();
438 // add resource also as a property (to allow path correction in setNewResource())
439 // TODO: will it work with slowmotion? needs testing
440 /*if (!prod.attribute("resource").isEmpty()) {
441 QDomElement prop_resource = m_doc.createElement("property");
442 prop_resource.setAttribute("name", "resource");
443 QDomText resource = m_doc.createTextNode(prod.attribute("resource"));
444 prop_resource.appendChild(resource);
445 prod.appendChild(prop_resource);
447 QDomNode m = prod.firstChild();
449 if (m.toElement().tagName() == "markers") {
450 QDomNodeList prodchilds = m.childNodes();
451 int maxchild = prodchilds.count();
452 for (int k = 0; k < maxchild; k++) {
453 QDomElement mark = prodchilds.at(0).toElement();
454 mark.setAttribute("id", prod.attribute("id"));
455 markers.insertAfter(mark, QDomNode());
458 } else if (prod.attribute("type").toInt() == TEXT) {
459 // convert title clip
460 if (m.toElement().tagName() == "textclip") {
462 QDomElement titleclip = m.toElement();
463 QDomElement title = tdoc.createElement("kdenlivetitle");
464 tdoc.appendChild(title);
465 QDomNodeList objects = titleclip.childNodes();
466 int maxchild = objects.count();
467 for (int k = 0; k < maxchild; k++) {
469 QDomElement ob = objects.at(k).toElement();
470 if (ob.attribute("type") == "3") {
471 // text object - all of this goes into "xmldata"...
472 QDomElement item = tdoc.createElement("item");
473 item.setAttribute("z-index", ob.attribute("z"));
474 item.setAttribute("type", "QGraphicsTextItem");
475 QDomElement position = tdoc.createElement("position");
476 position.setAttribute("x", ob.attribute("x"));
477 position.setAttribute("y", ob.attribute("y"));
478 QDomElement content = tdoc.createElement("content");
479 content.setAttribute("font", ob.attribute("font_family"));
480 content.setAttribute("font-size", ob.attribute("font_size"));
481 content.setAttribute("font-bold", ob.attribute("bold"));
482 content.setAttribute("font-italic", ob.attribute("italic"));
483 content.setAttribute("font-underline", ob.attribute("underline"));
484 QString col = ob.attribute("color");
486 content.setAttribute("font-color", colorToString(c));
487 // todo: These fields are missing from the newly generated xmldata:
488 // transform, startviewport, endviewport, background
490 QDomText conttxt = tdoc.createTextNode(ob.attribute("text"));
491 content.appendChild(conttxt);
492 item.appendChild(position);
493 item.appendChild(content);
494 title.appendChild(item);
495 } else if (ob.attribute("type") == "5") {
497 QDomElement item = tdoc.createElement("item");
498 item.setAttribute("z-index", ob.attribute("z"));
499 item.setAttribute("type", "QGraphicsRectItem");
500 QDomElement position = tdoc.createElement("position");
501 position.setAttribute("x", ob.attribute("x"));
502 position.setAttribute("y", ob.attribute("y"));
503 QDomElement content = tdoc.createElement("content");
504 QString col = ob.attribute("color");
506 content.setAttribute("brushcolor", colorToString(c));
507 QString rect = "0,0,";
508 rect.append(ob.attribute("width"));
510 rect.append(ob.attribute("height"));
511 content.setAttribute("rect", rect);
512 item.appendChild(position);
513 item.appendChild(content);
514 title.appendChild(item);
517 prod.setAttribute("xmldata", tdoc.toString());
518 // mbd todo: This clearly does not work, as every title gets the same name - trying to leave it empty
519 // QStringList titleInfo = TitleWidget::getFreeTitleInfo(projectFolder());
520 // prod.setAttribute("titlename", titleInfo.at(0));
521 // prod.setAttribute("resource", titleInfo.at(1));
522 //kDebug()<<"TITLE DATA:\n"<<tdoc.toString();
524 } // End conversion of title clips.
526 } else if (m.isText()) {
527 QString comment = m.nodeValue();
528 if (!comment.isEmpty()) {
529 prod.setAttribute("description", comment);
534 int duration = prod.attribute("duration").toInt();
535 if (duration > 0) prod.setAttribute("out", QString::number(duration));
536 // The clip goes back in, but text clips should not go back in, at least not modified
537 westley.insertBefore(prod, QDomNode());
540 QDomNode westley0 = m_doc.elementsByTagName("westley").at(0);
541 if (!markers.firstChild().isNull()) westley0.appendChild(markers);
544 * Convert as much of the kdenlivedoc as possible. Use the producer in
545 * westley. First, remove the old stuff from westley, and add a new
546 * empty one. Also, track the max id in order to use it for the adding
549 int max_kproducer_id = 0;
550 westley0.removeChild(infoXmlNode);
551 QDomElement infoXml_new = m_doc.createElement("kdenlivedoc");
552 infoXml_new.setAttribute("profile", profile);
553 infoXml.setAttribute("position", startPos);
555 // Add all the producers that has a resource in westley
556 QDomElement westley_element = westley0.toElement();
557 if (westley_element.isNull()) {
558 kWarning() << "westley0 element in document was not a QDomElement - unable to add producers to new kdenlivedoc";
560 QDomNodeList wproducers = westley_element.elementsByTagName("producer");
561 int kmax = wproducers.count();
562 for (int i = 0; i < kmax; i++) {
563 QDomElement wproducer = wproducers.at(i).toElement();
564 if (wproducer.isNull()) {
565 kWarning() << "Found producer in westley0, that was not a QDomElement";
568 if (wproducer.attribute("id") == "black") continue;
569 // We have to do slightly different things, depending on the type
570 kDebug() << "Converting producer element with type" << wproducer.attribute("type");
571 if (wproducer.attribute("type").toInt() == TEXT) {
572 kDebug() << "Found TEXT element in producer" << endl;
573 QDomElement kproducer = wproducer.cloneNode(true).toElement();
574 kproducer.setTagName("kdenlive_producer");
575 infoXml_new.appendChild(kproducer);
577 * TODO: Perhaps needs some more changes here to
578 * "frequency", aspect ratio as a float, frame_size,
579 * channels, and later, resource and title name
582 QDomElement kproducer = m_doc.createElement("kdenlive_producer");
583 kproducer.setAttribute("id", wproducer.attribute("id"));
584 if (!wproducer.attribute("description").isEmpty())
585 kproducer.setAttribute("description", wproducer.attribute("description"));
586 kproducer.setAttribute("resource", wproducer.attribute("resource"));
587 kproducer.setAttribute("type", wproducer.attribute("type"));
588 // Testing fix for 358
589 if (!wproducer.attribute("aspect_ratio").isEmpty()) {
590 kproducer.setAttribute("aspect_ratio", wproducer.attribute("aspect_ratio"));
592 if (!wproducer.attribute("source_fps").isEmpty()) {
593 kproducer.setAttribute("fps", wproducer.attribute("source_fps"));
595 if (!wproducer.attribute("length").isEmpty()) {
596 kproducer.setAttribute("duration", wproducer.attribute("length"));
598 infoXml_new.appendChild(kproducer);
600 if (wproducer.attribute("id").toInt() > max_kproducer_id) {
601 max_kproducer_id = wproducer.attribute("id").toInt();
605 #define LOOKUP_FOLDER 1
608 * Look through all the folder elements of the old doc, for each folder,
609 * for each producer, get the id, look it up in the new doc, set the
610 * groupname and groupid. Note, this does not work at the moment - at
611 * least one folder shows up missing, and clips with no folder does not
614 //QDomElement infoXml_old = infoXmlNode.toElement();
615 if (!infoXml_old.isNull()) {
616 QDomNodeList folders = infoXml_old.elementsByTagName("folder");
617 int fsize = folders.size();
618 int groupId = max_kproducer_id + 1; // Start at +1 of max id of the kdenlive_producers
619 for (int i = 0; i < fsize; ++i) {
620 QDomElement folder = folders.at(i).toElement();
621 if (!folder.isNull()) {
622 QString groupName = folder.attribute("name");
623 kDebug() << "groupName: " << groupName << " with groupId: " << groupId;
624 QDomNodeList fproducers = folder.elementsByTagName("producer");
625 int psize = fproducers.size();
626 for (int j = 0; j < psize; ++j) {
627 QDomElement fproducer = fproducers.at(j).toElement();
628 if (!fproducer.isNull()) {
629 QString id = fproducer.attribute("id");
630 // This is not very effective, but compared to loading the clips, its a breeze
631 QDomNodeList kdenlive_producers = infoXml_new.elementsByTagName("kdenlive_producer");
632 int kpsize = kdenlive_producers.size();
633 for (int k = 0; k < kpsize; ++k) {
634 QDomElement kproducer = kdenlive_producers.at(k).toElement(); // Its an element for sure
635 if (id == kproducer.attribute("id")) {
636 // We do not check that it already is part of a folder
637 kproducer.setAttribute("groupid", groupId);
638 kproducer.setAttribute("groupname", groupName);
649 QDomNodeList elements = westley.childNodes();
650 max = elements.count();
651 for (int i = 0; i < max; i++) {
652 QDomElement prod = elements.at(0).toElement();
653 westley0.insertAfter(prod, QDomNode());
656 westley0.appendChild(infoXml_new);
658 westley0.removeChild(westley);
660 // adds <avfile /> information to <kdenlive_producer />
661 QDomNodeList kproducers = m_doc.elementsByTagName("kdenlive_producer");
662 QDomNodeList avfiles = infoXml_old.elementsByTagName("avfile");
663 kDebug() << "found" << avfiles.count() << "<avfile />s and" << kproducers.count() << "<kdenlive_producer />s";
664 for (int i = 0; i < avfiles.count(); ++i) {
665 QDomElement avfile = avfiles.at(i).toElement();
666 QDomElement kproducer;
668 kWarning() << "found an <avfile /> that is not a QDomElement";
670 QString id = avfile.attribute("id");
671 // this is horrible, must be rewritten, it's just for test
672 for (int j = 0; j < kproducers.count(); ++j) {
673 //kDebug() << "checking <kdenlive_producer /> with id" << kproducers.at(j).toElement().attribute("id");
674 if (kproducers.at(j).toElement().attribute("id") == id) {
675 kproducer = kproducers.at(j).toElement();
679 if (kproducer == QDomElement())
680 kWarning() << "no match for <avfile /> with id =" << id;
682 //kDebug() << "ready to set additional <avfile />'s attributes (id =" << id << ")";
683 kproducer.setAttribute("channels", avfile.attribute("channels"));
684 kproducer.setAttribute("duration", avfile.attribute("duration"));
685 kproducer.setAttribute("frame_size", avfile.attribute("width") + 'x' + avfile.attribute("height"));
686 kproducer.setAttribute("frequency", avfile.attribute("frequency"));
687 if (kproducer.attribute("description").isEmpty() && !avfile.attribute("description").isEmpty())
688 kproducer.setAttribute("description", avfile.attribute("description"));
692 infoXml = infoXml_new;
695 if (version <= 0.81) {
696 // Add the tracks information
697 QString tracksOrder = infoXml.attribute("tracks");
698 if (tracksOrder.isEmpty()) {
699 QDomNodeList tracks = m_doc.elementsByTagName("track");
700 for (int i = 0; i < tracks.count(); i++) {
701 QDomElement track = tracks.at(i).toElement();
702 if (track.attribute("producer") != "black_track") {
703 if (track.attribute("hide") == "video")
704 tracksOrder.append('a');
706 tracksOrder.append('v');
710 QDomElement tracksinfo = m_doc.createElement("tracksinfo");
711 for (int i = 0; i < tracksOrder.size(); i++) {
712 QDomElement trackinfo = m_doc.createElement("trackinfo");
713 if (tracksOrder.data()[i] == 'a') {
714 trackinfo.setAttribute("type", "audio");
715 trackinfo.setAttribute("blind", true);
717 trackinfo.setAttribute("blind", false);
718 trackinfo.setAttribute("mute", false);
719 trackinfo.setAttribute("locked", false);
720 tracksinfo.appendChild(trackinfo);
722 infoXml.appendChild(tracksinfo);
725 if (version <= 0.82) {
726 // Convert <westley />s in <mlt />s (MLT extreme makeover)
727 QDomNodeList westleyNodes = m_doc.elementsByTagName("westley");
728 for (int i = 0; i < westleyNodes.count(); i++) {
729 QDomElement westley = westleyNodes.at(i).toElement();
730 westley.setTagName("mlt");
734 if (version <= 0.83) {
735 // Replace point size with pixel size in text titles
736 if (m_doc.toString().contains("font-size")) {
737 KMessageBox::ButtonCode convert = KMessageBox::Continue;
738 QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
739 for (int i = 0; i < kproducerNodes.count() && convert != KMessageBox::No; ++i) {
740 QDomElement kproducer = kproducerNodes.at(i).toElement();
741 if (kproducer.attribute("type").toInt() == TEXT) {
743 data.setContent(kproducer.attribute("xmldata"));
744 QDomNodeList items = data.firstChild().childNodes();
745 for (int j = 0; j < items.count() && convert != KMessageBox::No; ++j) {
746 if (items.at(j).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
747 QDomNamedNodeMap textProperties = items.at(j).namedItem("content").attributes();
748 if (textProperties.namedItem("font-pixel-size").isNull() && !textProperties.namedItem("font-size").isNull()) {
749 // Ask the user if he wants to convert
750 if (convert != KMessageBox::Yes && convert != KMessageBox::No)
751 convert = (KMessageBox::ButtonCode)KMessageBox::warningYesNo(kapp->activeWindow(), i18n("Some of your text clips were saved with size in points, which means different sizes on different displays. Do you want to convert them to pixel size, making them portable? It is recommended you do this on the computer they were first created on, or you could have to adjust their size."), i18n("Update Text Clips"));
752 if (convert == KMessageBox::Yes) {
754 font.setPointSize(textProperties.namedItem("font-size").nodeValue().toInt());
755 QDomElement content = items.at(j).namedItem("content").toElement();
756 content.setAttribute("font-pixel-size", QFontInfo(font).pixelSize());
757 content.removeAttribute("font-size");
758 kproducer.setAttribute("xmldata", data.toString());
760 * You may be tempted to delete the preview file
761 * to force its recreation: bad idea (see
762 * http://www.kdenlive.org/mantis/view.php?id=749)
772 // Fill the <documentproperties /> element
773 QDomElement docProperties = infoXml.firstChildElement("documentproperties");
774 if (docProperties.isNull()) {
775 docProperties = m_doc.createElement("documentproperties");
776 docProperties.setAttribute("zonein", infoXml.attribute("zonein"));
777 docProperties.setAttribute("zoneout", infoXml.attribute("zoneout"));
778 docProperties.setAttribute("zoom", infoXml.attribute("zoom"));
779 docProperties.setAttribute("position", infoXml.attribute("position"));
780 infoXml.appendChild(docProperties);
784 if (version <= 0.84) {
785 // update the title clips to use the new MLT kdenlivetitle producer
786 QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
787 for (int i = 0; i < kproducerNodes.count(); ++i) {
788 QDomElement kproducer = kproducerNodes.at(i).toElement();
789 if (kproducer.attribute("type").toInt() == TEXT) {
790 QString data = kproducer.attribute("xmldata");
791 QString datafile = kproducer.attribute("resource");
792 if (!datafile.endsWith(".kdenlivetitle")) {
793 datafile = QString();
794 kproducer.setAttribute("resource", QString());
796 QString id = kproducer.attribute("id");
797 QDomNodeList mltproducers = m_doc.elementsByTagName("producer");
798 bool foundData = false;
799 bool foundResource = false;
800 bool foundService = false;
801 for (int j = 0; j < mltproducers.count(); j++) {
802 QDomElement wproducer = mltproducers.at(j).toElement();
803 if (wproducer.attribute("id") == id) {
804 QDomNodeList props = wproducer.childNodes();
805 for (int k = 0; k < props.count(); k++) {
806 if (props.at(k).toElement().attribute("name") == "xmldata") {
807 props.at(k).firstChild().setNodeValue(data);
809 } else if (props.at(k).toElement().attribute("name") == "mlt_service") {
810 props.at(k).firstChild().setNodeValue("kdenlivetitle");
812 } else if (props.at(k).toElement().attribute("name") == "resource") {
813 props.at(k).firstChild().setNodeValue(datafile);
814 foundResource = true;
818 QDomElement e = m_doc.createElement("property");
819 e.setAttribute("name", "xmldata");
820 QDomText value = m_doc.createTextNode(data);
821 e.appendChild(value);
822 wproducer.appendChild(e);
825 QDomElement e = m_doc.createElement("property");
826 e.setAttribute("name", "mlt_service");
827 QDomText value = m_doc.createTextNode("kdenlivetitle");
828 e.appendChild(value);
829 wproducer.appendChild(e);
831 if (!foundResource) {
832 QDomElement e = m_doc.createElement("property");
833 e.setAttribute("name", "resource");
834 QDomText value = m_doc.createTextNode(datafile);
835 e.appendChild(value);
836 wproducer.appendChild(e);
844 if (version <= 0.85) {
845 // update the LADSPA effects to use the new ladspa.id format instead of external xml file
846 QDomNodeList effectNodes = m_doc.elementsByTagName("filter");
847 for (int i = 0; i < effectNodes.count(); ++i) {
848 QDomElement effect = effectNodes.at(i).toElement();
849 if (EffectsList::property(effect, "mlt_service") == "ladspa") {
850 // Needs to be converted
851 QStringList info = getInfoFromEffectName(EffectsList::property(effect, "kdenlive_id"));
852 if (info.isEmpty()) continue;
853 // info contains the correct ladspa.id from kdenlive effect name, and a list of parameter's old and new names
854 EffectsList::setProperty(effect, "kdenlive_id", info.at(0));
855 EffectsList::setProperty(effect, "tag", info.at(0));
856 EffectsList::setProperty(effect, "mlt_service", info.at(0));
857 EffectsList::removeProperty(effect, "src");
858 for (int j = 1; j < info.size(); j++) {
859 QString value = EffectsList::property(effect, info.at(j).section('=', 0, 0));
860 if (!value.isEmpty()) {
861 // update parameter name
862 EffectsList::renameProperty(effect, info.at(j).section('=', 0, 0), info.at(j).section('=', 1, 1));
869 if (version <= 0.86) {
870 // Make sure we don't have avformat-novalidate producers, since it caused crashes
871 QDomNodeList producers = m_doc.elementsByTagName("producer");
872 int max = producers.count();
873 for (int i = 0; i < max; i++) {
874 QDomElement prod = producers.at(i).toElement();
875 if (EffectsList::property(prod, "mlt_service") == "avformat-novalidate")
876 EffectsList::setProperty(prod, "mlt_service", "avformat");
879 // There was a mistake in Geometry transitions where the last keyframe was created one frame after the end of transition, so fix it and move last keyframe to real end of transition
881 // Get profile info (width / height)
884 QDomElement profile = m_doc.firstChildElement("profile");
885 if (profile.isNull()) profile = infoXml.firstChildElement("profileinfo");
886 if (profile.isNull()) {
887 // could not find profile info, set PAL
892 profileWidth = profile.attribute("width").toInt();
893 profileHeight = profile.attribute("height").toInt();
895 QDomNodeList transitions = m_doc.elementsByTagName("transition");
896 max = transitions.count();
898 for (int i = 0; i < max; i++) {
899 QDomElement trans = transitions.at(i).toElement();
900 out = trans.attribute("out").toInt() - trans.attribute("in").toInt();
901 QString geom = EffectsList::property(trans, "geometry");
902 Mlt::Geometry *g = new Mlt::Geometry(geom.toUtf8().data(), out, profileWidth, profileHeight);
903 Mlt::GeometryItem item;
904 if (g->next_key(&item, out) == 0) {
905 // We have a keyframe just after last frame, try to move it to last frame
906 if (item.frame() == out + 1) {
910 EffectsList::setProperty(trans, "geometry", g->serialise());
917 if (version <= 0.87) {
918 if (!m_doc.firstChildElement("mlt").hasAttribute("LC_NUMERIC")) {
919 m_doc.firstChildElement("mlt").setAttribute("LC_NUMERIC", "C");
923 // The document has been converted: mark it as modified
924 infoXml.setAttribute("version", currentVersion);
929 QStringList DocumentValidator::getInfoFromEffectName(const QString oldName)
932 // Returns a list to convert old Kdenlive ladspa effects
933 if (oldName == "pitch_shift") {
934 info << "ladspa.1433";
937 else if (oldName == "vinyl") {
938 info << "ladspa.1905";
945 else if (oldName == "room_reverb") {
946 info << "ladspa.1216";
951 else if (oldName == "reverb") {
952 info << "ladspa.1423";
956 else if (oldName == "rate_scale") {
957 info << "ladspa.1417";
960 else if (oldName == "pitch_scale") {
961 info << "ladspa.1193";
964 else if (oldName == "phaser") {
965 info << "ladspa.1217";
968 info << "feedback=2";
971 else if (oldName == "limiter") {
972 info << "ladspa.1913";
977 else if (oldName == "equalizer_15") {
978 info << "ladspa.1197";
995 else if (oldName == "equalizer") {
996 info << "ladspa.1901";
1001 else if (oldName == "declipper") {
1002 info << "ladspa.1195";
1007 QString DocumentValidator::colorToString(const QColor& c)
1009 QString ret = "%1,%2,%3,%4";
1010 ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
1014 bool DocumentValidator::isProject() const
1016 QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
1017 return !infoXmlNode.isNull();
1020 bool DocumentValidator::isModified() const