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"
25 #include <KMessageBox>
26 #include <KApplication>
32 DocumentValidator::DocumentValidator(QDomDocument doc):
37 bool DocumentValidator::validate(const double currentVersion)
39 // Check if we're validating a Kdenlive project
43 // Upgrade the document to the latest version
44 QDomNode kdenlivedocNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
45 QDomElement kdenlivedocElm = kdenlivedocNode.toElement();
46 if (!upgrade(kdenlivedocElm.attribute("version").toDouble(), currentVersion))
50 * Check the syntax (this will be replaced by XSD validation with Qt 4.6)
51 * and correct some errors
53 QDomNode mltNode = m_doc.elementsByTagName("mlt").at(0);
54 QDomElement mltElm = mltNode.toElement();
55 if (mltElm.isNull()) // At least the root element must be there
58 // Return (or create) the tractor
59 QDomNode tractorNode = m_doc.elementsByTagName("tractor").at(0);
60 QDomElement tractorElm = tractorNode.toElement();
61 if (tractorElm.isNull()) {
63 tractorElm = m_doc.createElement("tractor");
64 tractorElm.setAttribute("global_feed", "1");
65 tractorElm.setAttribute("in", "0");
66 tractorElm.setAttribute("out", "-1");
67 tractorElm.setAttribute("id", "maintractor");
68 mltElm.appendChild(tractorElm);
72 * Make sure at least one track exists, and they're equal in number to
73 * to the maximum between MLT and Kdenlive playlists and tracks
75 QDomNodeList playlists = m_doc.elementsByTagName("playlist");
76 int tracksMax = playlists.count() - 1; // Remove the black track
77 QDomNodeList tracks = m_doc.elementsByTagName("track");
78 tracksMax = qMax(tracks.count() - 1, tracksMax);
79 QDomNodeList tracksinfo = m_doc.elementsByTagName("trackinfo");
80 tracksMax = qMax(tracksinfo.count(), tracksMax);
81 tracksMax = qMax(1, tracksMax); // Force existance of one track
82 if (playlists.count() - 1 < tracksMax ||
83 tracks.count() - 1 < tracksMax ||
84 tracksinfo.count() < tracksMax) {
87 if (playlists.count() - 1 < tracksMax) {
88 difference = tracksMax - (playlists.count() - 1);
89 for (int i = 0; i < difference; ++i) {
90 QDomElement playlist = m_doc.createElement("playlist");
91 mltElm.appendChild(playlist);
94 if (tracks.count() - 1 < tracksMax) {
95 difference = tracksMax - (tracks.count() - 1);
96 for (int i = 0; i < difference; ++i) {
97 QDomElement track = m_doc.createElement("track");
98 tractorElm.appendChild(track);
101 if (tracksinfo.count() < tracksMax) {
102 QDomNode tracksinfoNode = m_doc.elementsByTagName("tracksinfo").at(0);
103 QDomElement tracksinfoElm = tracksinfoNode.toElement();
104 if (tracksinfoElm.isNull()) {
105 tracksinfoElm = m_doc.createElement("tracksinfo");
106 kdenlivedocElm.appendChild(tracksinfoElm);
108 difference = tracksMax - tracksinfo.count();
109 for (int i = 0; i < difference; ++i) {
110 QDomElement trackinfo = m_doc.createElement("trackinfo");
111 trackinfo.setAttribute("mute", "0");
112 trackinfo.setAttribute("locked", "0");
113 tracksinfoElm.appendChild(trackinfo);
118 // TODO: check the tracks references
119 // TODO: check internal mix transitions
125 bool DocumentValidator::upgrade(double version, const double currentVersion)
127 kDebug() << "Opening a document with version " << version;
129 // No conversion needed
130 if (version == currentVersion) {
134 // The document is too new
135 if (version > currentVersion) {
136 kDebug() << "Unable to open document with version " << version;
137 KMessageBox::sorry(kapp->activeWindow(), i18n("This project type is unsupported (version %1) and can't be loaded.\nPlease consider upgrading you Kdenlive version.", version), i18n("Unable to open project"));
141 // Unsupported document versions
142 if (version == 0.5 || version == 0.7) {
143 kDebug() << "Unable to open document with version " << version;
144 KMessageBox::sorry(kapp->activeWindow(), i18n("This project type is unsupported (version %1) and can't be loaded.", version), i18n("Unable to open project"));
149 QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
150 QDomElement infoXml = infoXmlNode.toElement();
151 infoXml.setAttribute("upgraded", "1");
153 if (version <= 0.6) {
154 QDomElement infoXml_old = infoXmlNode.cloneNode(true).toElement(); // Needed for folders
155 QDomNode westley = m_doc.elementsByTagName("westley").at(1);
156 QDomNode tractor = m_doc.elementsByTagName("tractor").at(0);
157 QDomNode multitrack = m_doc.elementsByTagName("multitrack").at(0);
158 QDomNodeList playlists = m_doc.elementsByTagName("playlist");
160 QDomNode props = m_doc.elementsByTagName("properties").at(0).toElement();
161 QString profile = props.toElement().attribute("videoprofile");
162 int startPos = props.toElement().attribute("timeline_position").toInt();
163 if (profile == "dv_wide")
164 profile = "dv_pal_wide";
166 // move playlists outside of tractor and add the tracks instead
167 int max = playlists.count();
168 if (westley.isNull()) {
169 westley = m_doc.createElement("westley");
170 m_doc.documentElement().appendChild(westley);
172 if (tractor.isNull()) {
173 kDebug() << "// NO MLT PLAYLIST, building empty one";
174 QDomElement blank_tractor = m_doc.createElement("tractor");
175 westley.appendChild(blank_tractor);
176 QDomElement blank_playlist = m_doc.createElement("playlist");
177 blank_playlist.setAttribute("id", "black_track");
178 westley.insertBefore(blank_playlist, QDomNode());
179 QDomElement blank_track = m_doc.createElement("track");
180 blank_track.setAttribute("producer", "black_track");
181 blank_tractor.appendChild(blank_track);
183 QDomNodeList kdenlivetracks = m_doc.elementsByTagName("kdenlivetrack");
184 for (int i = 0; i < kdenlivetracks.count(); i++) {
185 blank_playlist = m_doc.createElement("playlist");
186 blank_playlist.setAttribute("id", "playlist" + QString::number(i));
187 westley.insertBefore(blank_playlist, QDomNode());
188 blank_track = m_doc.createElement("track");
189 blank_track.setAttribute("producer", "playlist" + QString::number(i));
190 blank_tractor.appendChild(blank_track);
191 if (kdenlivetracks.at(i).toElement().attribute("cliptype") == "Sound") {
192 blank_playlist.setAttribute("hide", "video");
193 blank_track.setAttribute("hide", "video");
196 } else for (int i = 0; i < max; i++) {
197 QDomNode n = playlists.at(i);
198 westley.insertBefore(n, QDomNode());
199 QDomElement pl = n.toElement();
200 QDomElement track = m_doc.createElement("track");
201 QString trackType = pl.attribute("hide");
202 if (!trackType.isEmpty())
203 track.setAttribute("hide", trackType);
204 QString playlist_id = pl.attribute("id");
205 if (playlist_id.isEmpty()) {
206 playlist_id = "black_track";
207 pl.setAttribute("id", playlist_id);
209 track.setAttribute("producer", playlist_id);
210 //tractor.appendChild(track);
211 #define KEEP_TRACK_ORDER 1
212 #ifdef KEEP_TRACK_ORDER
213 tractor.insertAfter(track, QDomNode());
215 // Insert the new track in an order that hopefully matches the 3 video, then 2 audio tracks of Kdenlive 0.7.0
216 // insertion sort - O( tracks*tracks )
217 // Note, this breaks _all_ transitions - but you can move them up and down afterwards.
218 QDomElement tractor_elem = tractor.toElement();
219 if (! tractor_elem.isNull()) {
220 QDomNodeList tracks = tractor_elem.elementsByTagName("track");
221 int size = tracks.size();
223 tractor.insertAfter(track, QDomNode());
225 bool inserted = false;
226 for (int i = 0; i < size; ++i) {
227 QDomElement track_elem = tracks.at(i).toElement();
228 if (track_elem.isNull()) {
229 tractor.insertAfter(track, QDomNode());
233 kDebug() << "playlist_id: " << playlist_id << " producer:" << track_elem.attribute("producer");
234 if (playlist_id < track_elem.attribute("producer")) {
235 tractor.insertBefore(track, track_elem);
241 // Reach here, no insertion, insert last
243 tractor.insertAfter(track, QDomNode());
247 kWarning() << "tractor was not a QDomElement";
248 tractor.insertAfter(track, QDomNode());
252 tractor.removeChild(multitrack);
254 // audio track mixing transitions should not be added to track view, so add required attribute
255 QDomNodeList transitions = m_doc.elementsByTagName("transition");
256 max = transitions.count();
257 for (int i = 0; i < max; i++) {
258 QDomElement tr = transitions.at(i).toElement();
259 if (tr.attribute("combine") == "1" && tr.attribute("mlt_service") == "mix") {
260 QDomElement property = m_doc.createElement("property");
261 property.setAttribute("name", "internal_added");
262 QDomText value = m_doc.createTextNode("237");
263 property.appendChild(value);
264 tr.appendChild(property);
265 property = m_doc.createElement("property");
266 property.setAttribute("name", "mlt_service");
267 value = m_doc.createTextNode("mix");
268 property.appendChild(value);
269 tr.appendChild(property);
271 // convert transition
272 QDomNamedNodeMap attrs = tr.attributes();
273 for (int j = 0; j < attrs.count(); j++) {
274 QString attrName = attrs.item(j).nodeName();
275 if (attrName != "in" && attrName != "out" && attrName != "id") {
276 QDomElement property = m_doc.createElement("property");
277 property.setAttribute("name", attrName);
278 QDomText value = m_doc.createTextNode(attrs.item(j).nodeValue());
279 property.appendChild(value);
280 tr.appendChild(property);
286 // move transitions after tracks
287 for (int i = 0; i < max; i++) {
288 tractor.insertAfter(transitions.at(0), QDomNode());
291 // Fix filters format
292 QDomNodeList entries = m_doc.elementsByTagName("entry");
293 max = entries.count();
294 for (int i = 0; i < max; i++) {
297 QDomNode m = entries.at(i).firstChild();
298 while (!m.isNull()) {
299 if (m.toElement().tagName() == "filter") {
300 QDomElement filt = m.toElement();
301 QDomNamedNodeMap attrs = filt.attributes();
302 QString current_id = filt.attribute("kdenlive_id");
303 if (current_id != last_id) {
305 last_id = current_id;
307 QDomElement e = m_doc.createElement("property");
308 e.setAttribute("name", "kdenlive_ix");
309 QDomText value = m_doc.createTextNode(QString::number(effectix));
310 e.appendChild(value);
312 for (int j = 0; j < attrs.count(); j++) {
313 QDomAttr a = attrs.item(j).toAttr();
315 kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
316 QDomElement e = m_doc.createElement("property");
317 e.setAttribute("name", a.name());
318 QDomText value = m_doc.createTextNode(a.value());
319 e.appendChild(value);
330 QDomNodeList filters = m_doc.elementsByTagName("filter");
331 max = filters.count();
334 for (int i = 0; i < max; i++) {
335 QDomElement filt = filters.at(i).toElement();
336 QDomNamedNodeMap attrs = filt.attributes();
337 QString current_id = filt.attribute("kdenlive_id");
338 if (current_id != last_id) {
340 last_id = current_id;
342 QDomElement e = m_doc.createElement("property");
343 e.setAttribute("name", "kdenlive_ix");
344 QDomText value = m_doc.createTextNode(QString::number(1));
345 e.appendChild(value);
347 for (int j = 0; j < attrs.count(); j++) {
348 QDomAttr a = attrs.item(j).toAttr();
350 kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
351 QDomElement e = m_doc.createElement("property");
352 e.setAttribute("name", a.name());
353 QDomText value = m_doc.createTextNode(a.value());
354 e.appendChild(value);
361 QDomNodeList producers = westley.toElement().elementsByTagName("producer");
362 max = producers.count();
363 for (int i = 0; i < max; i++) {
364 QDomElement prod = producers.at(i).toElement();
365 if (prod.attribute("mlt_service") == "framebuffer") {
366 QString slowmotionprod = prod.attribute("resource");
367 slowmotionprod.replace(':', '?');
368 kDebug() << "// FOUND WRONG SLOWMO, new: " << slowmotionprod;
369 prod.setAttribute("resource", slowmotionprod);
372 // move producers to correct place, markers to a global list, fix clip descriptions
373 QDomElement markers = m_doc.createElement("markers");
374 // This will get the xml producers:
375 producers = m_doc.elementsByTagName("producer");
376 max = producers.count();
377 for (int i = 0; i < max; i++) {
378 QDomElement prod = producers.at(0).toElement();
379 // add resource also as a property (to allow path correction in setNewResource())
380 // TODO: will it work with slowmotion? needs testing
381 /*if (!prod.attribute("resource").isEmpty()) {
382 QDomElement prop_resource = m_doc.createElement("property");
383 prop_resource.setAttribute("name", "resource");
384 QDomText resource = m_doc.createTextNode(prod.attribute("resource"));
385 prop_resource.appendChild(resource);
386 prod.appendChild(prop_resource);
388 QDomNode m = prod.firstChild();
390 if (m.toElement().tagName() == "markers") {
391 QDomNodeList prodchilds = m.childNodes();
392 int maxchild = prodchilds.count();
393 for (int k = 0; k < maxchild; k++) {
394 QDomElement mark = prodchilds.at(0).toElement();
395 mark.setAttribute("id", prod.attribute("id"));
396 markers.insertAfter(mark, QDomNode());
399 } else if (prod.attribute("type").toInt() == TEXT) {
400 // convert title clip
401 if (m.toElement().tagName() == "textclip") {
403 QDomElement titleclip = m.toElement();
404 QDomElement title = tdoc.createElement("kdenlivetitle");
405 tdoc.appendChild(title);
406 QDomNodeList objects = titleclip.childNodes();
407 int maxchild = objects.count();
408 for (int k = 0; k < maxchild; k++) {
410 QDomElement ob = objects.at(k).toElement();
411 if (ob.attribute("type") == "3") {
412 // text object - all of this goes into "xmldata"...
413 QDomElement item = tdoc.createElement("item");
414 item.setAttribute("z-index", ob.attribute("z"));
415 item.setAttribute("type", "QGraphicsTextItem");
416 QDomElement position = tdoc.createElement("position");
417 position.setAttribute("x", ob.attribute("x"));
418 position.setAttribute("y", ob.attribute("y"));
419 QDomElement content = tdoc.createElement("content");
420 content.setAttribute("font", ob.attribute("font_family"));
421 content.setAttribute("font-size", ob.attribute("font_size"));
422 content.setAttribute("font-bold", ob.attribute("bold"));
423 content.setAttribute("font-italic", ob.attribute("italic"));
424 content.setAttribute("font-underline", ob.attribute("underline"));
425 QString col = ob.attribute("color");
427 content.setAttribute("font-color", colorToString(c));
428 // todo: These fields are missing from the newly generated xmldata:
429 // transform, startviewport, endviewport, background
431 QDomText conttxt = tdoc.createTextNode(ob.attribute("text"));
432 content.appendChild(conttxt);
433 item.appendChild(position);
434 item.appendChild(content);
435 title.appendChild(item);
436 } else if (ob.attribute("type") == "5") {
438 QDomElement item = tdoc.createElement("item");
439 item.setAttribute("z-index", ob.attribute("z"));
440 item.setAttribute("type", "QGraphicsRectItem");
441 QDomElement position = tdoc.createElement("position");
442 position.setAttribute("x", ob.attribute("x"));
443 position.setAttribute("y", ob.attribute("y"));
444 QDomElement content = tdoc.createElement("content");
445 QString col = ob.attribute("color");
447 content.setAttribute("brushcolor", colorToString(c));
448 QString rect = "0,0,";
449 rect.append(ob.attribute("width"));
451 rect.append(ob.attribute("height"));
452 content.setAttribute("rect", rect);
453 item.appendChild(position);
454 item.appendChild(content);
455 title.appendChild(item);
458 prod.setAttribute("xmldata", tdoc.toString());
459 // mbd todo: This clearly does not work, as every title gets the same name - trying to leave it empty
460 // QStringList titleInfo = TitleWidget::getFreeTitleInfo(projectFolder());
461 // prod.setAttribute("titlename", titleInfo.at(0));
462 // prod.setAttribute("resource", titleInfo.at(1));
463 //kDebug()<<"TITLE DATA:\n"<<tdoc.toString();
465 } // End conversion of title clips.
467 } else if (m.isText()) {
468 QString comment = m.nodeValue();
469 if (!comment.isEmpty()) {
470 prod.setAttribute("description", comment);
475 int duration = prod.attribute("duration").toInt();
476 if (duration > 0) prod.setAttribute("out", QString::number(duration));
477 // The clip goes back in, but text clips should not go back in, at least not modified
478 westley.insertBefore(prod, QDomNode());
481 QDomNode westley0 = m_doc.elementsByTagName("westley").at(0);
482 if (!markers.firstChild().isNull()) westley0.appendChild(markers);
485 * Convert as much of the kdenlivedoc as possible. Use the producer in
486 * westley. First, remove the old stuff from westley, and add a new
487 * empty one. Also, track the max id in order to use it for the adding
490 int max_kproducer_id = 0;
491 westley0.removeChild(infoXmlNode);
492 QDomElement infoXml_new = m_doc.createElement("kdenlivedoc");
493 infoXml_new.setAttribute("profile", profile);
494 infoXml.setAttribute("position", startPos);
496 // Add all the producers that has a resource in westley
497 QDomElement westley_element = westley0.toElement();
498 if (westley_element.isNull()) {
499 kWarning() << "westley0 element in document was not a QDomElement - unable to add producers to new kdenlivedoc";
501 QDomNodeList wproducers = westley_element.elementsByTagName("producer");
502 int kmax = wproducers.count();
503 for (int i = 0; i < kmax; i++) {
504 QDomElement wproducer = wproducers.at(i).toElement();
505 if (wproducer.isNull()) {
506 kWarning() << "Found producer in westley0, that was not a QDomElement";
509 if (wproducer.attribute("id") == "black") continue;
510 // We have to do slightly different things, depending on the type
511 kDebug() << "Converting producer element with type" << wproducer.attribute("type");
512 if (wproducer.attribute("type").toInt() == TEXT) {
513 kDebug() << "Found TEXT element in producer" << endl;
514 QDomElement kproducer = wproducer.cloneNode(true).toElement();
515 kproducer.setTagName("kdenlive_producer");
516 infoXml_new.appendChild(kproducer);
518 * TODO: Perhaps needs some more changes here to
519 * "frequency", aspect ratio as a float, frame_size,
520 * channels, and later, resource and title name
523 QDomElement kproducer = m_doc.createElement("kdenlive_producer");
524 kproducer.setAttribute("id", wproducer.attribute("id"));
525 if (!wproducer.attribute("description").isEmpty())
526 kproducer.setAttribute("description", wproducer.attribute("description"));
527 kproducer.setAttribute("resource", wproducer.attribute("resource"));
528 kproducer.setAttribute("type", wproducer.attribute("type"));
529 // Testing fix for 358
530 if (!wproducer.attribute("aspect_ratio").isEmpty()) {
531 kproducer.setAttribute("aspect_ratio", wproducer.attribute("aspect_ratio"));
533 if (!wproducer.attribute("source_fps").isEmpty()) {
534 kproducer.setAttribute("fps", wproducer.attribute("source_fps"));
536 if (!wproducer.attribute("length").isEmpty()) {
537 kproducer.setAttribute("duration", wproducer.attribute("length"));
539 infoXml_new.appendChild(kproducer);
541 if (wproducer.attribute("id").toInt() > max_kproducer_id) {
542 max_kproducer_id = wproducer.attribute("id").toInt();
546 #define LOOKUP_FOLDER 1
549 * Look through all the folder elements of the old doc, for each folder,
550 * for each producer, get the id, look it up in the new doc, set the
551 * groupname and groupid. Note, this does not work at the moment - at
552 * least one folder shows up missing, and clips with no folder does not
555 //QDomElement infoXml_old = infoXmlNode.toElement();
556 if (!infoXml_old.isNull()) {
557 QDomNodeList folders = infoXml_old.elementsByTagName("folder");
558 int fsize = folders.size();
559 int groupId = max_kproducer_id + 1; // Start at +1 of max id of the kdenlive_producers
560 for (int i = 0; i < fsize; ++i) {
561 QDomElement folder = folders.at(i).toElement();
562 if (!folder.isNull()) {
563 QString groupName = folder.attribute("name");
564 kDebug() << "groupName: " << groupName << " with groupId: " << groupId;
565 QDomNodeList fproducers = folder.elementsByTagName("producer");
566 int psize = fproducers.size();
567 for (int j = 0; j < psize; ++j) {
568 QDomElement fproducer = fproducers.at(j).toElement();
569 if (!fproducer.isNull()) {
570 QString id = fproducer.attribute("id");
571 // This is not very effective, but compared to loading the clips, its a breeze
572 QDomNodeList kdenlive_producers = infoXml_new.elementsByTagName("kdenlive_producer");
573 int kpsize = kdenlive_producers.size();
574 for (int k = 0; k < kpsize; ++k) {
575 QDomElement kproducer = kdenlive_producers.at(k).toElement(); // Its an element for sure
576 if (id == kproducer.attribute("id")) {
577 // We do not check that it already is part of a folder
578 kproducer.setAttribute("groupid", groupId);
579 kproducer.setAttribute("groupname", groupName);
590 QDomNodeList elements = westley.childNodes();
591 max = elements.count();
592 for (int i = 0; i < max; i++) {
593 QDomElement prod = elements.at(0).toElement();
594 westley0.insertAfter(prod, QDomNode());
597 westley0.appendChild(infoXml_new);
599 westley0.removeChild(westley);
601 // adds <avfile /> information to <kdenlive_producer />
602 QDomNodeList kproducers = m_doc.elementsByTagName("kdenlive_producer");
603 QDomNodeList avfiles = infoXml_old.elementsByTagName("avfile");
604 kDebug() << "found" << avfiles.count() << "<avfile />s and" << kproducers.count() << "<kdenlive_producer />s";
605 for (int i = 0; i < avfiles.count(); ++i) {
606 QDomElement avfile = avfiles.at(i).toElement();
607 QDomElement kproducer;
609 kWarning() << "found an <avfile /> that is not a QDomElement";
611 QString id = avfile.attribute("id");
612 // this is horrible, must be rewritten, it's just for test
613 for (int j = 0; j < kproducers.count(); ++j) {
614 //kDebug() << "checking <kdenlive_producer /> with id" << kproducers.at(j).toElement().attribute("id");
615 if (kproducers.at(j).toElement().attribute("id") == id) {
616 kproducer = kproducers.at(j).toElement();
620 if (kproducer == QDomElement())
621 kWarning() << "no match for <avfile /> with id =" << id;
623 //kDebug() << "ready to set additional <avfile />'s attributes (id =" << id << ")";
624 kproducer.setAttribute("channels", avfile.attribute("channels"));
625 kproducer.setAttribute("duration", avfile.attribute("duration"));
626 kproducer.setAttribute("frame_size", avfile.attribute("width") + 'x' + avfile.attribute("height"));
627 kproducer.setAttribute("frequency", avfile.attribute("frequency"));
628 if (kproducer.attribute("description").isEmpty() && !avfile.attribute("description").isEmpty())
629 kproducer.setAttribute("description", avfile.attribute("description"));
633 infoXml = infoXml_new;
636 if (version <= 0.81) {
637 // Add the tracks information
638 QString tracksOrder = infoXml.attribute("tracks");
639 if (tracksOrder.isEmpty()) {
640 QDomNodeList tracks = m_doc.elementsByTagName("track");
641 for (int i = 0; i < tracks.count(); i++) {
642 QDomElement track = tracks.at(i).toElement();
643 if (track.attribute("producer") != "black_track") {
644 if (track.attribute("hide") == "video")
645 tracksOrder.append('a');
647 tracksOrder.append('v');
651 QDomElement tracksinfo = m_doc.createElement("tracksinfo");
652 for (int i = 0; i < tracksOrder.size(); i++) {
653 QDomElement trackinfo = m_doc.createElement("trackinfo");
654 if (tracksOrder.data()[i] == 'a') {
655 trackinfo.setAttribute("type", "audio");
656 trackinfo.setAttribute("blind", true);
658 trackinfo.setAttribute("blind", false);
659 trackinfo.setAttribute("mute", false);
660 trackinfo.setAttribute("locked", false);
661 tracksinfo.appendChild(trackinfo);
663 infoXml.appendChild(tracksinfo);
666 if (version <= 0.82) {
667 // Convert <westley />s in <mlt />s (MLT extreme makeover)
668 QDomNodeList westleyNodes = m_doc.elementsByTagName("westley");
669 for (int i = 0; i < westleyNodes.count(); i++) {
670 QDomElement westley = westleyNodes.at(i).toElement();
671 westley.setTagName("mlt");
675 if (version <= 0.83) {
676 // Replace point size with pixel size in text titles
677 if (m_doc.toString().contains("font-size")) {
678 KMessageBox::ButtonCode convert = KMessageBox::Continue;
679 QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
680 for (int i = 0; i < kproducerNodes.count() && convert != KMessageBox::No; ++i) {
681 QDomElement kproducer = kproducerNodes.at(i).toElement();
682 if (kproducer.attribute("type").toInt() == TEXT) {
684 data.setContent(kproducer.attribute("xmldata"));
685 QDomNodeList items = data.firstChild().childNodes();
686 for (int j = 0; j < items.count() && convert != KMessageBox::No; ++j) {
687 if (items.at(j).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
688 QDomNamedNodeMap textProperties = items.at(j).namedItem("content").attributes();
689 if (textProperties.namedItem("font-pixel-size").isNull() && !textProperties.namedItem("font-size").isNull()) {
690 // Ask the user if he wants to convert
691 if (convert != KMessageBox::Yes && convert != KMessageBox::No)
692 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"));
693 if (convert == KMessageBox::Yes) {
695 font.setPointSize(textProperties.namedItem("font-size").nodeValue().toInt());
696 QDomElement content = items.at(j).namedItem("content").toElement();
697 content.setAttribute("font-pixel-size", QFontInfo(font).pixelSize());
698 content.removeAttribute("font-size");
699 kproducer.setAttribute("xmldata", data.toString());
701 * You may be tempted to delete the preview file
702 * to force its recreation: bad idea (see
703 * http://www.kdenlive.org/mantis/view.php?id=749)
713 // Fill the <documentproperties /> element
714 QDomElement docProperties = infoXml.firstChildElement("documentproperties");
715 if (docProperties.isNull()) {
716 docProperties = m_doc.createElement("documentproperties");
717 docProperties.setAttribute("zonein", infoXml.attribute("zonein"));
718 docProperties.setAttribute("zoneout", infoXml.attribute("zoneout"));
719 docProperties.setAttribute("zoom", infoXml.attribute("zoom"));
720 docProperties.setAttribute("position", infoXml.attribute("position"));
721 infoXml.appendChild(docProperties);
725 if (version <= 0.84) {
726 // update the title clips to use the new MLT kdenlivetitle producer
727 QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
728 for (int i = 0; i < kproducerNodes.count(); ++i) {
729 QDomElement kproducer = kproducerNodes.at(i).toElement();
730 if (kproducer.attribute("type").toInt() == TEXT) {
731 QString data = kproducer.attribute("xmldata");
732 QString datafile = kproducer.attribute("resource");
733 if (!datafile.endsWith(".kdenlivetitle")) {
734 datafile = QString();
735 kproducer.setAttribute("resource", QString());
737 QString id = kproducer.attribute("id");
738 QDomNodeList mltproducers = m_doc.elementsByTagName("producer");
739 bool foundData = false;
740 bool foundResource = false;
741 bool foundService = false;
742 for (int j = 0; j < mltproducers.count(); j++) {
743 QDomElement wproducer = mltproducers.at(j).toElement();
744 if (wproducer.attribute("id") == id) {
745 QDomNodeList props = wproducer.childNodes();
746 for (int k = 0; k < props.count(); k++) {
747 if (props.at(k).toElement().attribute("name") == "xmldata") {
748 props.at(k).firstChild().setNodeValue(data);
750 } else if (props.at(k).toElement().attribute("name") == "mlt_service") {
751 props.at(k).firstChild().setNodeValue("kdenlivetitle");
753 } else if (props.at(k).toElement().attribute("name") == "resource") {
754 props.at(k).firstChild().setNodeValue(datafile);
755 foundResource = true;
759 QDomElement e = m_doc.createElement("property");
760 e.setAttribute("name", "xmldata");
761 QDomText value = m_doc.createTextNode(data);
762 e.appendChild(value);
763 wproducer.appendChild(e);
766 QDomElement e = m_doc.createElement("property");
767 e.setAttribute("name", "mlt_service");
768 QDomText value = m_doc.createTextNode("kdenlivetitle");
769 e.appendChild(value);
770 wproducer.appendChild(e);
772 if (!foundResource) {
773 QDomElement e = m_doc.createElement("property");
774 e.setAttribute("name", "resource");
775 QDomText value = m_doc.createTextNode(datafile);
776 e.appendChild(value);
777 wproducer.appendChild(e);
787 // The document has been converted: mark it as modified
788 infoXml.setAttribute("version", currentVersion);
793 QString DocumentValidator::colorToString(const QColor& c)
795 QString ret = "%1,%2,%3,%4";
796 ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
800 bool DocumentValidator::isProject() const
802 QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
803 return !infoXmlNode.isNull();
806 bool DocumentValidator::isModified() const