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"));
148 QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
149 QDomElement infoXml = infoXmlNode.toElement();
151 if (version <= 0.6) {
152 QDomElement infoXml_old = infoXmlNode.cloneNode(true).toElement(); // Needed for folders
153 QDomNode westley = m_doc.elementsByTagName("westley").at(1);
154 QDomNode tractor = m_doc.elementsByTagName("tractor").at(0);
155 QDomNode multitrack = m_doc.elementsByTagName("multitrack").at(0);
156 QDomNodeList playlists = m_doc.elementsByTagName("playlist");
158 QDomNode props = m_doc.elementsByTagName("properties").at(0).toElement();
159 QString profile = props.toElement().attribute("videoprofile");
160 int startPos = props.toElement().attribute("timeline_position").toInt();
161 if (profile == "dv_wide")
162 profile = "dv_pal_wide";
164 // move playlists outside of tractor and add the tracks instead
165 int max = playlists.count();
166 if (westley.isNull()) {
167 westley = m_doc.createElement("westley");
168 m_doc.documentElement().appendChild(westley);
170 if (tractor.isNull()) {
171 kDebug() << "// NO MLT PLAYLIST, building empty one";
172 QDomElement blank_tractor = m_doc.createElement("tractor");
173 westley.appendChild(blank_tractor);
174 QDomElement blank_playlist = m_doc.createElement("playlist");
175 blank_playlist.setAttribute("id", "black_track");
176 westley.insertBefore(blank_playlist, QDomNode());
177 QDomElement blank_track = m_doc.createElement("track");
178 blank_track.setAttribute("producer", "black_track");
179 blank_tractor.appendChild(blank_track);
181 QDomNodeList kdenlivetracks = m_doc.elementsByTagName("kdenlivetrack");
182 for (int i = 0; i < kdenlivetracks.count(); i++) {
183 blank_playlist = m_doc.createElement("playlist");
184 blank_playlist.setAttribute("id", "playlist" + QString::number(i));
185 westley.insertBefore(blank_playlist, QDomNode());
186 blank_track = m_doc.createElement("track");
187 blank_track.setAttribute("producer", "playlist" + QString::number(i));
188 blank_tractor.appendChild(blank_track);
189 if (kdenlivetracks.at(i).toElement().attribute("cliptype") == "Sound") {
190 blank_playlist.setAttribute("hide", "video");
191 blank_track.setAttribute("hide", "video");
194 } else for (int i = 0; i < max; i++) {
195 QDomNode n = playlists.at(i);
196 westley.insertBefore(n, QDomNode());
197 QDomElement pl = n.toElement();
198 QDomElement track = m_doc.createElement("track");
199 QString trackType = pl.attribute("hide");
200 if (!trackType.isEmpty())
201 track.setAttribute("hide", trackType);
202 QString playlist_id = pl.attribute("id");
203 if (playlist_id.isEmpty()) {
204 playlist_id = "black_track";
205 pl.setAttribute("id", playlist_id);
207 track.setAttribute("producer", playlist_id);
208 //tractor.appendChild(track);
209 #define KEEP_TRACK_ORDER 1
210 #ifdef KEEP_TRACK_ORDER
211 tractor.insertAfter(track, QDomNode());
213 // Insert the new track in an order that hopefully matches the 3 video, then 2 audio tracks of Kdenlive 0.7.0
214 // insertion sort - O( tracks*tracks )
215 // Note, this breaks _all_ transitions - but you can move them up and down afterwards.
216 QDomElement tractor_elem = tractor.toElement();
217 if (! tractor_elem.isNull()) {
218 QDomNodeList tracks = tractor_elem.elementsByTagName("track");
219 int size = tracks.size();
221 tractor.insertAfter(track, QDomNode());
223 bool inserted = false;
224 for (int i = 0; i < size; ++i) {
225 QDomElement track_elem = tracks.at(i).toElement();
226 if (track_elem.isNull()) {
227 tractor.insertAfter(track, QDomNode());
231 kDebug() << "playlist_id: " << playlist_id << " producer:" << track_elem.attribute("producer");
232 if (playlist_id < track_elem.attribute("producer")) {
233 tractor.insertBefore(track, track_elem);
239 // Reach here, no insertion, insert last
241 tractor.insertAfter(track, QDomNode());
245 kWarning() << "tractor was not a QDomElement";
246 tractor.insertAfter(track, QDomNode());
250 tractor.removeChild(multitrack);
252 // audio track mixing transitions should not be added to track view, so add required attribute
253 QDomNodeList transitions = m_doc.elementsByTagName("transition");
254 max = transitions.count();
255 for (int i = 0; i < max; i++) {
256 QDomElement tr = transitions.at(i).toElement();
257 if (tr.attribute("combine") == "1" && tr.attribute("mlt_service") == "mix") {
258 QDomElement property = m_doc.createElement("property");
259 property.setAttribute("name", "internal_added");
260 QDomText value = m_doc.createTextNode("237");
261 property.appendChild(value);
262 tr.appendChild(property);
263 property = m_doc.createElement("property");
264 property.setAttribute("name", "mlt_service");
265 value = m_doc.createTextNode("mix");
266 property.appendChild(value);
267 tr.appendChild(property);
269 // convert transition
270 QDomNamedNodeMap attrs = tr.attributes();
271 for (int j = 0; j < attrs.count(); j++) {
272 QString attrName = attrs.item(j).nodeName();
273 if (attrName != "in" && attrName != "out" && attrName != "id") {
274 QDomElement property = m_doc.createElement("property");
275 property.setAttribute("name", attrName);
276 QDomText value = m_doc.createTextNode(attrs.item(j).nodeValue());
277 property.appendChild(value);
278 tr.appendChild(property);
284 // move transitions after tracks
285 for (int i = 0; i < max; i++) {
286 tractor.insertAfter(transitions.at(0), QDomNode());
289 // Fix filters format
290 QDomNodeList entries = m_doc.elementsByTagName("entry");
291 max = entries.count();
292 for (int i = 0; i < max; i++) {
295 QDomNode m = entries.at(i).firstChild();
296 while (!m.isNull()) {
297 if (m.toElement().tagName() == "filter") {
298 QDomElement filt = m.toElement();
299 QDomNamedNodeMap attrs = filt.attributes();
300 QString current_id = filt.attribute("kdenlive_id");
301 if (current_id != last_id) {
303 last_id = current_id;
305 QDomElement e = m_doc.createElement("property");
306 e.setAttribute("name", "kdenlive_ix");
307 QDomText value = m_doc.createTextNode(QString::number(effectix));
308 e.appendChild(value);
310 for (int j = 0; j < attrs.count(); j++) {
311 QDomAttr a = attrs.item(j).toAttr();
313 kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
314 QDomElement e = m_doc.createElement("property");
315 e.setAttribute("name", a.name());
316 QDomText value = m_doc.createTextNode(a.value());
317 e.appendChild(value);
328 QDomNodeList filters = m_doc.elementsByTagName("filter");
329 max = filters.count();
332 for (int i = 0; i < max; i++) {
333 QDomElement filt = filters.at(i).toElement();
334 QDomNamedNodeMap attrs = filt.attributes();
335 QString current_id = filt.attribute("kdenlive_id");
336 if (current_id != last_id) {
338 last_id = current_id;
340 QDomElement e = m_doc.createElement("property");
341 e.setAttribute("name", "kdenlive_ix");
342 QDomText value = m_doc.createTextNode(QString::number(1));
343 e.appendChild(value);
345 for (int j = 0; j < attrs.count(); j++) {
346 QDomAttr a = attrs.item(j).toAttr();
348 kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
349 QDomElement e = m_doc.createElement("property");
350 e.setAttribute("name", a.name());
351 QDomText value = m_doc.createTextNode(a.value());
352 e.appendChild(value);
359 QDomNodeList producers = westley.toElement().elementsByTagName("producer");
360 max = producers.count();
361 for (int i = 0; i < max; i++) {
362 QDomElement prod = producers.at(i).toElement();
363 if (prod.attribute("mlt_service") == "framebuffer") {
364 QString slowmotionprod = prod.attribute("resource");
365 slowmotionprod.replace(':', '?');
366 kDebug() << "// FOUND WRONG SLOWMO, new: " << slowmotionprod;
367 prod.setAttribute("resource", slowmotionprod);
370 // move producers to correct place, markers to a global list, fix clip descriptions
371 QDomElement markers = m_doc.createElement("markers");
372 // This will get the xml producers:
373 producers = m_doc.elementsByTagName("producer");
374 max = producers.count();
375 for (int i = 0; i < max; i++) {
376 QDomElement prod = producers.at(0).toElement();
377 // add resource also as a property (to allow path correction in setNewResource())
378 // TODO: will it work with slowmotion? needs testing
379 /*if (!prod.attribute("resource").isEmpty()) {
380 QDomElement prop_resource = m_doc.createElement("property");
381 prop_resource.setAttribute("name", "resource");
382 QDomText resource = m_doc.createTextNode(prod.attribute("resource"));
383 prop_resource.appendChild(resource);
384 prod.appendChild(prop_resource);
386 QDomNode m = prod.firstChild();
388 if (m.toElement().tagName() == "markers") {
389 QDomNodeList prodchilds = m.childNodes();
390 int maxchild = prodchilds.count();
391 for (int k = 0; k < maxchild; k++) {
392 QDomElement mark = prodchilds.at(0).toElement();
393 mark.setAttribute("id", prod.attribute("id"));
394 markers.insertAfter(mark, QDomNode());
397 } else if (prod.attribute("type").toInt() == TEXT) {
398 // convert title clip
399 if (m.toElement().tagName() == "textclip") {
401 QDomElement titleclip = m.toElement();
402 QDomElement title = tdoc.createElement("kdenlivetitle");
403 tdoc.appendChild(title);
404 QDomNodeList objects = titleclip.childNodes();
405 int maxchild = objects.count();
406 for (int k = 0; k < maxchild; k++) {
408 QDomElement ob = objects.at(k).toElement();
409 if (ob.attribute("type") == "3") {
410 // text object - all of this goes into "xmldata"...
411 QDomElement item = tdoc.createElement("item");
412 item.setAttribute("z-index", ob.attribute("z"));
413 item.setAttribute("type", "QGraphicsTextItem");
414 QDomElement position = tdoc.createElement("position");
415 position.setAttribute("x", ob.attribute("x"));
416 position.setAttribute("y", ob.attribute("y"));
417 QDomElement content = tdoc.createElement("content");
418 content.setAttribute("font", ob.attribute("font_family"));
419 content.setAttribute("font-size", ob.attribute("font_size"));
420 content.setAttribute("font-bold", ob.attribute("bold"));
421 content.setAttribute("font-italic", ob.attribute("italic"));
422 content.setAttribute("font-underline", ob.attribute("underline"));
423 QString col = ob.attribute("color");
425 content.setAttribute("font-color", colorToString(c));
426 // todo: These fields are missing from the newly generated xmldata:
427 // transform, startviewport, endviewport, background
429 QDomText conttxt = tdoc.createTextNode(ob.attribute("text"));
430 content.appendChild(conttxt);
431 item.appendChild(position);
432 item.appendChild(content);
433 title.appendChild(item);
434 } else if (ob.attribute("type") == "5") {
436 QDomElement item = tdoc.createElement("item");
437 item.setAttribute("z-index", ob.attribute("z"));
438 item.setAttribute("type", "QGraphicsRectItem");
439 QDomElement position = tdoc.createElement("position");
440 position.setAttribute("x", ob.attribute("x"));
441 position.setAttribute("y", ob.attribute("y"));
442 QDomElement content = tdoc.createElement("content");
443 QString col = ob.attribute("color");
445 content.setAttribute("brushcolor", colorToString(c));
446 QString rect = "0,0,";
447 rect.append(ob.attribute("width"));
449 rect.append(ob.attribute("height"));
450 content.setAttribute("rect", rect);
451 item.appendChild(position);
452 item.appendChild(content);
453 title.appendChild(item);
456 prod.setAttribute("xmldata", tdoc.toString());
457 // mbd todo: This clearly does not work, as every title gets the same name - trying to leave it empty
458 // QStringList titleInfo = TitleWidget::getFreeTitleInfo(projectFolder());
459 // prod.setAttribute("titlename", titleInfo.at(0));
460 // prod.setAttribute("resource", titleInfo.at(1));
461 //kDebug()<<"TITLE DATA:\n"<<tdoc.toString();
463 } // End conversion of title clips.
465 } else if (m.isText()) {
466 QString comment = m.nodeValue();
467 if (!comment.isEmpty()) {
468 prod.setAttribute("description", comment);
473 int duration = prod.attribute("duration").toInt();
474 if (duration > 0) prod.setAttribute("out", QString::number(duration));
475 // The clip goes back in, but text clips should not go back in, at least not modified
476 westley.insertBefore(prod, QDomNode());
479 QDomNode westley0 = m_doc.elementsByTagName("westley").at(0);
480 if (!markers.firstChild().isNull()) westley0.appendChild(markers);
483 * Convert as much of the kdenlivedoc as possible. Use the producer in
484 * westley. First, remove the old stuff from westley, and add a new
485 * empty one. Also, track the max id in order to use it for the adding
488 int max_kproducer_id = 0;
489 westley0.removeChild(infoXmlNode);
490 QDomElement infoXml_new = m_doc.createElement("kdenlivedoc");
491 infoXml_new.setAttribute("profile", profile);
492 infoXml.setAttribute("position", startPos);
494 // Add all the producers that has a resource in westley
495 QDomElement westley_element = westley0.toElement();
496 if (westley_element.isNull()) {
497 kWarning() << "westley0 element in document was not a QDomElement - unable to add producers to new kdenlivedoc";
499 QDomNodeList wproducers = westley_element.elementsByTagName("producer");
500 int kmax = wproducers.count();
501 for (int i = 0; i < kmax; i++) {
502 QDomElement wproducer = wproducers.at(i).toElement();
503 if (wproducer.isNull()) {
504 kWarning() << "Found producer in westley0, that was not a QDomElement";
507 if (wproducer.attribute("id") == "black") continue;
508 // We have to do slightly different things, depending on the type
509 kDebug() << "Converting producer element with type" << wproducer.attribute("type");
510 if (wproducer.attribute("type").toInt() == TEXT) {
511 kDebug() << "Found TEXT element in producer" << endl;
512 QDomElement kproducer = wproducer.cloneNode(true).toElement();
513 kproducer.setTagName("kdenlive_producer");
514 infoXml_new.appendChild(kproducer);
516 * TODO: Perhaps needs some more changes here to
517 * "frequency", aspect ratio as a float, frame_size,
518 * channels, and later, resource and title name
521 QDomElement kproducer = m_doc.createElement("kdenlive_producer");
522 kproducer.setAttribute("id", wproducer.attribute("id"));
523 if (!wproducer.attribute("description").isEmpty())
524 kproducer.setAttribute("description", wproducer.attribute("description"));
525 kproducer.setAttribute("resource", wproducer.attribute("resource"));
526 kproducer.setAttribute("type", wproducer.attribute("type"));
527 // Testing fix for 358
528 if (!wproducer.attribute("aspect_ratio").isEmpty()) {
529 kproducer.setAttribute("aspect_ratio", wproducer.attribute("aspect_ratio"));
531 if (!wproducer.attribute("source_fps").isEmpty()) {
532 kproducer.setAttribute("fps", wproducer.attribute("source_fps"));
534 if (!wproducer.attribute("length").isEmpty()) {
535 kproducer.setAttribute("duration", wproducer.attribute("length"));
537 infoXml_new.appendChild(kproducer);
539 if (wproducer.attribute("id").toInt() > max_kproducer_id) {
540 max_kproducer_id = wproducer.attribute("id").toInt();
544 #define LOOKUP_FOLDER 1
547 * Look through all the folder elements of the old doc, for each folder,
548 * for each producer, get the id, look it up in the new doc, set the
549 * groupname and groupid. Note, this does not work at the moment - at
550 * least one folder shows up missing, and clips with no folder does not
553 //QDomElement infoXml_old = infoXmlNode.toElement();
554 if (!infoXml_old.isNull()) {
555 QDomNodeList folders = infoXml_old.elementsByTagName("folder");
556 int fsize = folders.size();
557 int groupId = max_kproducer_id + 1; // Start at +1 of max id of the kdenlive_producers
558 for (int i = 0; i < fsize; ++i) {
559 QDomElement folder = folders.at(i).toElement();
560 if (!folder.isNull()) {
561 QString groupName = folder.attribute("name");
562 kDebug() << "groupName: " << groupName << " with groupId: " << groupId;
563 QDomNodeList fproducers = folder.elementsByTagName("producer");
564 int psize = fproducers.size();
565 for (int j = 0; j < psize; ++j) {
566 QDomElement fproducer = fproducers.at(j).toElement();
567 if (!fproducer.isNull()) {
568 QString id = fproducer.attribute("id");
569 // This is not very effective, but compared to loading the clips, its a breeze
570 QDomNodeList kdenlive_producers = infoXml_new.elementsByTagName("kdenlive_producer");
571 int kpsize = kdenlive_producers.size();
572 for (int k = 0; k < kpsize; ++k) {
573 QDomElement kproducer = kdenlive_producers.at(k).toElement(); // Its an element for sure
574 if (id == kproducer.attribute("id")) {
575 // We do not check that it already is part of a folder
576 kproducer.setAttribute("groupid", groupId);
577 kproducer.setAttribute("groupname", groupName);
588 QDomNodeList elements = westley.childNodes();
589 max = elements.count();
590 for (int i = 0; i < max; i++) {
591 QDomElement prod = elements.at(0).toElement();
592 westley0.insertAfter(prod, QDomNode());
595 westley0.appendChild(infoXml_new);
597 westley0.removeChild(westley);
599 // adds <avfile /> information to <kdenlive_producer />
600 QDomNodeList kproducers = m_doc.elementsByTagName("kdenlive_producer");
601 QDomNodeList avfiles = infoXml_old.elementsByTagName("avfile");
602 kDebug() << "found" << avfiles.count() << "<avfile />s and" << kproducers.count() << "<kdenlive_producer />s";
603 for (int i = 0; i < avfiles.count(); ++i) {
604 QDomElement avfile = avfiles.at(i).toElement();
605 QDomElement kproducer;
607 kWarning() << "found an <avfile /> that is not a QDomElement";
609 QString id = avfile.attribute("id");
610 // this is horrible, must be rewritten, it's just for test
611 for (int j = 0; j < kproducers.count(); ++j) {
612 //kDebug() << "checking <kdenlive_producer /> with id" << kproducers.at(j).toElement().attribute("id");
613 if (kproducers.at(j).toElement().attribute("id") == id) {
614 kproducer = kproducers.at(j).toElement();
618 if (kproducer == QDomElement())
619 kWarning() << "no match for <avfile /> with id =" << id;
621 //kDebug() << "ready to set additional <avfile />'s attributes (id =" << id << ")";
622 kproducer.setAttribute("channels", avfile.attribute("channels"));
623 kproducer.setAttribute("duration", avfile.attribute("duration"));
624 kproducer.setAttribute("frame_size", avfile.attribute("width") + 'x' + avfile.attribute("height"));
625 kproducer.setAttribute("frequency", avfile.attribute("frequency"));
626 if (kproducer.attribute("description").isEmpty() && !avfile.attribute("description").isEmpty())
627 kproducer.setAttribute("description", avfile.attribute("description"));
631 infoXml = infoXml_new;
634 if (version <= 0.81) {
635 // Add the tracks information
636 QString tracksOrder = infoXml.attribute("tracks");
637 if (tracksOrder.isEmpty()) {
638 QDomNodeList tracks = m_doc.elementsByTagName("track");
639 for (int i = 0; i < tracks.count(); i++) {
640 QDomElement track = tracks.at(i).toElement();
641 if (track.attribute("producer") != "black_track") {
642 if (track.attribute("hide") == "video")
643 tracksOrder.append('a');
645 tracksOrder.append('v');
649 QDomElement tracksinfo = m_doc.createElement("tracksinfo");
650 for (int i = 0; i < tracksOrder.size(); i++) {
651 QDomElement trackinfo = m_doc.createElement("trackinfo");
652 if (tracksOrder.data()[i] == 'a') {
653 trackinfo.setAttribute("type", "audio");
654 trackinfo.setAttribute("blind", true);
656 trackinfo.setAttribute("blind", false);
657 trackinfo.setAttribute("mute", false);
658 trackinfo.setAttribute("locked", false);
659 tracksinfo.appendChild(trackinfo);
661 infoXml.appendChild(tracksinfo);
664 if (version <= 0.82) {
665 // Convert <westley />s in <mlt />s (MLT extreme makeover)
666 QDomNodeList westleyNodes = m_doc.elementsByTagName("westley");
667 for (int i = 0; i < westleyNodes.count(); i++) {
668 QDomElement westley = westleyNodes.at(i).toElement();
669 westley.setTagName("mlt");
673 if (version <= 0.83) {
674 // Replace point size with pixel size in text titles
675 if (m_doc.toString().contains("font-size")) {
676 KMessageBox::ButtonCode convert = KMessageBox::Continue;
677 QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
678 for (int i = 0; i < kproducerNodes.count() && convert != KMessageBox::No; ++i) {
679 QDomElement kproducer = kproducerNodes.at(i).toElement();
680 if (kproducer.attribute("type").toInt() == TEXT) {
682 data.setContent(kproducer.attribute("xmldata"));
683 QDomNodeList items = data.firstChild().childNodes();
684 for (int j = 0; j < items.count() && convert != KMessageBox::No; ++j) {
685 if (items.at(j).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
686 QDomNamedNodeMap textProperties = items.at(j).namedItem("content").attributes();
687 if (textProperties.namedItem("font-pixel-size").isNull() && !textProperties.namedItem("font-size").isNull()) {
688 // Ask the user if he wants to convert
689 if (convert != KMessageBox::Yes && convert != KMessageBox::No)
690 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"));
691 if (convert == KMessageBox::Yes) {
693 font.setPointSize(textProperties.namedItem("font-size").nodeValue().toInt());
694 QDomElement content = items.at(j).namedItem("content").toElement();
695 content.setAttribute("font-pixel-size", QFontInfo(font).pixelSize());
696 content.removeAttribute("font-size");
697 kproducer.setAttribute("xmldata", data.toString());
699 * You may be tempted to delete the preview file
700 * to force its recreation: bad idea (see
701 * http://www.kdenlive.org/mantis/view.php?id=749)
711 // Fill the <documentproperties /> element
712 QDomElement docProperties = infoXml.firstChildElement("documentproperties");
713 if (docProperties.isNull()) {
714 docProperties = m_doc.createElement("documentproperties");
715 docProperties.setAttribute("zonein", infoXml.attribute("zonein"));
716 docProperties.setAttribute("zoneout", infoXml.attribute("zoneout"));
717 docProperties.setAttribute("zoom", infoXml.attribute("zoom"));
718 docProperties.setAttribute("position", infoXml.attribute("position"));
719 infoXml.appendChild(docProperties);
723 if (version <= 0.84) {
724 // update the title clips to use the new MLT kdenlivetitle producer
725 QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
726 for (int i = 0; i < kproducerNodes.count(); ++i) {
727 QDomElement kproducer = kproducerNodes.at(i).toElement();
728 if (kproducer.attribute("type").toInt() == TEXT) {
729 QString data = kproducer.attribute("xmldata");
730 QString datafile = kproducer.attribute("resource");
731 if (!datafile.endsWith(".kdenlivetitle")) {
732 datafile = QString();
733 kproducer.setAttribute("resource", QString());
735 QString id = kproducer.attribute("id");
736 QDomNodeList mltproducers = m_doc.elementsByTagName("producer");
737 bool foundData = false;
738 bool foundResource = false;
739 bool foundService = false;
740 for (int j = 0; j < mltproducers.count(); j++) {
741 QDomElement wproducer = mltproducers.at(j).toElement();
742 if (wproducer.attribute("id") == id) {
743 QDomNodeList props = wproducer.childNodes();
744 for (int k = 0; k < props.count(); k++) {
745 if (props.at(k).toElement().attribute("name") == "xmldata") {
746 props.at(k).firstChild().setNodeValue(data);
749 else if (props.at(k).toElement().attribute("name") == "mlt_service") {
750 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