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) {
85 kDebug() << "//// WARNING, PROJECT IS CORRUPTED, MISSING TRACK";
88 // use the MLT tracks as reference
89 if (tracks.count() - 1 < tracksMax) {
90 // Looks like one MLT track is missing, remove the extra Kdenlive track if there is one.
91 if (tracksinfo.count() != tracks.count() - 1) {
92 // The Kdenlive tracks are not ok, clear and rebuild them
93 QDomNode tinfo = m_doc.elementsByTagName("tracksinfo").at(0);
94 QDomNode tnode = tinfo.firstChild();
95 while (!tnode.isNull()) {
96 tinfo.removeChild(tnode);
97 tnode = tinfo.firstChild();
100 for (int i = 1; i < tracks.count(); i++) {
101 QString hide = tracks.at(i).toElement().attribute("hide");
102 QDomElement newTrack = m_doc.createElement("trackinfo");
103 if (hide == "video") {
105 newTrack.setAttribute("type", "audio");
106 newTrack.setAttribute("blind", 1);
107 newTrack.setAttribute("mute", 0);
108 newTrack.setAttribute("lock", 0);
110 newTrack.setAttribute("blind", 0);
111 newTrack.setAttribute("mute", 0);
112 newTrack.setAttribute("lock", 0);
114 tinfo.appendChild(newTrack);
119 if (playlists.count() - 1 < tracksMax) {
120 difference = tracksMax - (playlists.count() - 1);
121 for (int i = 0; i < difference; ++i) {
122 QDomElement playlist = m_doc.createElement("playlist");
123 mltElm.appendChild(playlist);
126 if (tracks.count() - 1 < tracksMax) {
127 difference = tracksMax - (tracks.count() - 1);
128 for (int i = 0; i < difference; ++i) {
129 QDomElement track = m_doc.createElement("track");
130 tractorElm.appendChild(track);
133 if (tracksinfo.count() < tracksMax) {
134 QDomNode tracksinfoNode = m_doc.elementsByTagName("tracksinfo").at(0);
135 QDomElement tracksinfoElm = tracksinfoNode.toElement();
136 if (tracksinfoElm.isNull()) {
137 tracksinfoElm = m_doc.createElement("tracksinfo");
138 kdenlivedocElm.appendChild(tracksinfoElm);
140 difference = tracksMax - tracksinfo.count();
141 for (int i = 0; i < difference; ++i) {
142 QDomElement trackinfo = m_doc.createElement("trackinfo");
143 trackinfo.setAttribute("mute", "0");
144 trackinfo.setAttribute("locked", "0");
145 tracksinfoElm.appendChild(trackinfo);
150 // TODO: check the tracks references
151 // TODO: check internal mix transitions
157 bool DocumentValidator::upgrade(double version, const double currentVersion)
159 kDebug() << "Opening a document with version " << version;
161 // No conversion needed
162 if (version == currentVersion) {
166 // The document is too new
167 if (version > currentVersion) {
168 kDebug() << "Unable to open document with version " << version;
169 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"));
173 // Unsupported document versions
174 if (version == 0.5 || version == 0.7) {
175 kDebug() << "Unable to open document with version " << version;
176 KMessageBox::sorry(kapp->activeWindow(), i18n("This project type is unsupported (version %1) and can't be loaded.", version), i18n("Unable to open project"));
181 QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
182 QDomElement infoXml = infoXmlNode.toElement();
183 infoXml.setAttribute("upgraded", "1");
185 if (version <= 0.6) {
186 QDomElement infoXml_old = infoXmlNode.cloneNode(true).toElement(); // Needed for folders
187 QDomNode westley = m_doc.elementsByTagName("westley").at(1);
188 QDomNode tractor = m_doc.elementsByTagName("tractor").at(0);
189 QDomNode multitrack = m_doc.elementsByTagName("multitrack").at(0);
190 QDomNodeList playlists = m_doc.elementsByTagName("playlist");
192 QDomNode props = m_doc.elementsByTagName("properties").at(0).toElement();
193 QString profile = props.toElement().attribute("videoprofile");
194 int startPos = props.toElement().attribute("timeline_position").toInt();
195 if (profile == "dv_wide")
196 profile = "dv_pal_wide";
198 // move playlists outside of tractor and add the tracks instead
199 int max = playlists.count();
200 if (westley.isNull()) {
201 westley = m_doc.createElement("westley");
202 m_doc.documentElement().appendChild(westley);
204 if (tractor.isNull()) {
205 kDebug() << "// NO MLT PLAYLIST, building empty one";
206 QDomElement blank_tractor = m_doc.createElement("tractor");
207 westley.appendChild(blank_tractor);
208 QDomElement blank_playlist = m_doc.createElement("playlist");
209 blank_playlist.setAttribute("id", "black_track");
210 westley.insertBefore(blank_playlist, QDomNode());
211 QDomElement blank_track = m_doc.createElement("track");
212 blank_track.setAttribute("producer", "black_track");
213 blank_tractor.appendChild(blank_track);
215 QDomNodeList kdenlivetracks = m_doc.elementsByTagName("kdenlivetrack");
216 for (int i = 0; i < kdenlivetracks.count(); i++) {
217 blank_playlist = m_doc.createElement("playlist");
218 blank_playlist.setAttribute("id", "playlist" + QString::number(i));
219 westley.insertBefore(blank_playlist, QDomNode());
220 blank_track = m_doc.createElement("track");
221 blank_track.setAttribute("producer", "playlist" + QString::number(i));
222 blank_tractor.appendChild(blank_track);
223 if (kdenlivetracks.at(i).toElement().attribute("cliptype") == "Sound") {
224 blank_playlist.setAttribute("hide", "video");
225 blank_track.setAttribute("hide", "video");
228 } else for (int i = 0; i < max; i++) {
229 QDomNode n = playlists.at(i);
230 westley.insertBefore(n, QDomNode());
231 QDomElement pl = n.toElement();
232 QDomElement track = m_doc.createElement("track");
233 QString trackType = pl.attribute("hide");
234 if (!trackType.isEmpty())
235 track.setAttribute("hide", trackType);
236 QString playlist_id = pl.attribute("id");
237 if (playlist_id.isEmpty()) {
238 playlist_id = "black_track";
239 pl.setAttribute("id", playlist_id);
241 track.setAttribute("producer", playlist_id);
242 //tractor.appendChild(track);
243 #define KEEP_TRACK_ORDER 1
244 #ifdef KEEP_TRACK_ORDER
245 tractor.insertAfter(track, QDomNode());
247 // Insert the new track in an order that hopefully matches the 3 video, then 2 audio tracks of Kdenlive 0.7.0
248 // insertion sort - O( tracks*tracks )
249 // Note, this breaks _all_ transitions - but you can move them up and down afterwards.
250 QDomElement tractor_elem = tractor.toElement();
251 if (! tractor_elem.isNull()) {
252 QDomNodeList tracks = tractor_elem.elementsByTagName("track");
253 int size = tracks.size();
255 tractor.insertAfter(track, QDomNode());
257 bool inserted = false;
258 for (int i = 0; i < size; ++i) {
259 QDomElement track_elem = tracks.at(i).toElement();
260 if (track_elem.isNull()) {
261 tractor.insertAfter(track, QDomNode());
265 kDebug() << "playlist_id: " << playlist_id << " producer:" << track_elem.attribute("producer");
266 if (playlist_id < track_elem.attribute("producer")) {
267 tractor.insertBefore(track, track_elem);
273 // Reach here, no insertion, insert last
275 tractor.insertAfter(track, QDomNode());
279 kWarning() << "tractor was not a QDomElement";
280 tractor.insertAfter(track, QDomNode());
284 tractor.removeChild(multitrack);
286 // audio track mixing transitions should not be added to track view, so add required attribute
287 QDomNodeList transitions = m_doc.elementsByTagName("transition");
288 max = transitions.count();
289 for (int i = 0; i < max; i++) {
290 QDomElement tr = transitions.at(i).toElement();
291 if (tr.attribute("combine") == "1" && tr.attribute("mlt_service") == "mix") {
292 QDomElement property = m_doc.createElement("property");
293 property.setAttribute("name", "internal_added");
294 QDomText value = m_doc.createTextNode("237");
295 property.appendChild(value);
296 tr.appendChild(property);
297 property = m_doc.createElement("property");
298 property.setAttribute("name", "mlt_service");
299 value = m_doc.createTextNode("mix");
300 property.appendChild(value);
301 tr.appendChild(property);
303 // convert transition
304 QDomNamedNodeMap attrs = tr.attributes();
305 for (int j = 0; j < attrs.count(); j++) {
306 QString attrName = attrs.item(j).nodeName();
307 if (attrName != "in" && attrName != "out" && attrName != "id") {
308 QDomElement property = m_doc.createElement("property");
309 property.setAttribute("name", attrName);
310 QDomText value = m_doc.createTextNode(attrs.item(j).nodeValue());
311 property.appendChild(value);
312 tr.appendChild(property);
318 // move transitions after tracks
319 for (int i = 0; i < max; i++) {
320 tractor.insertAfter(transitions.at(0), QDomNode());
323 // Fix filters format
324 QDomNodeList entries = m_doc.elementsByTagName("entry");
325 max = entries.count();
326 for (int i = 0; i < max; i++) {
329 QDomNode m = entries.at(i).firstChild();
330 while (!m.isNull()) {
331 if (m.toElement().tagName() == "filter") {
332 QDomElement filt = m.toElement();
333 QDomNamedNodeMap attrs = filt.attributes();
334 QString current_id = filt.attribute("kdenlive_id");
335 if (current_id != last_id) {
337 last_id = current_id;
339 QDomElement e = m_doc.createElement("property");
340 e.setAttribute("name", "kdenlive_ix");
341 QDomText value = m_doc.createTextNode(QString::number(effectix));
342 e.appendChild(value);
344 for (int j = 0; j < attrs.count(); j++) {
345 QDomAttr a = attrs.item(j).toAttr();
347 kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
348 QDomElement e = m_doc.createElement("property");
349 e.setAttribute("name", a.name());
350 QDomText value = m_doc.createTextNode(a.value());
351 e.appendChild(value);
362 QDomNodeList filters = m_doc.elementsByTagName("filter");
363 max = filters.count();
366 for (int i = 0; i < max; i++) {
367 QDomElement filt = filters.at(i).toElement();
368 QDomNamedNodeMap attrs = filt.attributes();
369 QString current_id = filt.attribute("kdenlive_id");
370 if (current_id != last_id) {
372 last_id = current_id;
374 QDomElement e = m_doc.createElement("property");
375 e.setAttribute("name", "kdenlive_ix");
376 QDomText value = m_doc.createTextNode(QString::number(1));
377 e.appendChild(value);
379 for (int j = 0; j < attrs.count(); j++) {
380 QDomAttr a = attrs.item(j).toAttr();
382 kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
383 QDomElement e = m_doc.createElement("property");
384 e.setAttribute("name", a.name());
385 QDomText value = m_doc.createTextNode(a.value());
386 e.appendChild(value);
393 QDomNodeList producers = westley.toElement().elementsByTagName("producer");
394 max = producers.count();
395 for (int i = 0; i < max; i++) {
396 QDomElement prod = producers.at(i).toElement();
397 if (prod.attribute("mlt_service") == "framebuffer") {
398 QString slowmotionprod = prod.attribute("resource");
399 slowmotionprod.replace(':', '?');
400 kDebug() << "// FOUND WRONG SLOWMO, new: " << slowmotionprod;
401 prod.setAttribute("resource", slowmotionprod);
404 // move producers to correct place, markers to a global list, fix clip descriptions
405 QDomElement markers = m_doc.createElement("markers");
406 // This will get the xml producers:
407 producers = m_doc.elementsByTagName("producer");
408 max = producers.count();
409 for (int i = 0; i < max; i++) {
410 QDomElement prod = producers.at(0).toElement();
411 // add resource also as a property (to allow path correction in setNewResource())
412 // TODO: will it work with slowmotion? needs testing
413 /*if (!prod.attribute("resource").isEmpty()) {
414 QDomElement prop_resource = m_doc.createElement("property");
415 prop_resource.setAttribute("name", "resource");
416 QDomText resource = m_doc.createTextNode(prod.attribute("resource"));
417 prop_resource.appendChild(resource);
418 prod.appendChild(prop_resource);
420 QDomNode m = prod.firstChild();
422 if (m.toElement().tagName() == "markers") {
423 QDomNodeList prodchilds = m.childNodes();
424 int maxchild = prodchilds.count();
425 for (int k = 0; k < maxchild; k++) {
426 QDomElement mark = prodchilds.at(0).toElement();
427 mark.setAttribute("id", prod.attribute("id"));
428 markers.insertAfter(mark, QDomNode());
431 } else if (prod.attribute("type").toInt() == TEXT) {
432 // convert title clip
433 if (m.toElement().tagName() == "textclip") {
435 QDomElement titleclip = m.toElement();
436 QDomElement title = tdoc.createElement("kdenlivetitle");
437 tdoc.appendChild(title);
438 QDomNodeList objects = titleclip.childNodes();
439 int maxchild = objects.count();
440 for (int k = 0; k < maxchild; k++) {
442 QDomElement ob = objects.at(k).toElement();
443 if (ob.attribute("type") == "3") {
444 // text object - all of this goes into "xmldata"...
445 QDomElement item = tdoc.createElement("item");
446 item.setAttribute("z-index", ob.attribute("z"));
447 item.setAttribute("type", "QGraphicsTextItem");
448 QDomElement position = tdoc.createElement("position");
449 position.setAttribute("x", ob.attribute("x"));
450 position.setAttribute("y", ob.attribute("y"));
451 QDomElement content = tdoc.createElement("content");
452 content.setAttribute("font", ob.attribute("font_family"));
453 content.setAttribute("font-size", ob.attribute("font_size"));
454 content.setAttribute("font-bold", ob.attribute("bold"));
455 content.setAttribute("font-italic", ob.attribute("italic"));
456 content.setAttribute("font-underline", ob.attribute("underline"));
457 QString col = ob.attribute("color");
459 content.setAttribute("font-color", colorToString(c));
460 // todo: These fields are missing from the newly generated xmldata:
461 // transform, startviewport, endviewport, background
463 QDomText conttxt = tdoc.createTextNode(ob.attribute("text"));
464 content.appendChild(conttxt);
465 item.appendChild(position);
466 item.appendChild(content);
467 title.appendChild(item);
468 } else if (ob.attribute("type") == "5") {
470 QDomElement item = tdoc.createElement("item");
471 item.setAttribute("z-index", ob.attribute("z"));
472 item.setAttribute("type", "QGraphicsRectItem");
473 QDomElement position = tdoc.createElement("position");
474 position.setAttribute("x", ob.attribute("x"));
475 position.setAttribute("y", ob.attribute("y"));
476 QDomElement content = tdoc.createElement("content");
477 QString col = ob.attribute("color");
479 content.setAttribute("brushcolor", colorToString(c));
480 QString rect = "0,0,";
481 rect.append(ob.attribute("width"));
483 rect.append(ob.attribute("height"));
484 content.setAttribute("rect", rect);
485 item.appendChild(position);
486 item.appendChild(content);
487 title.appendChild(item);
490 prod.setAttribute("xmldata", tdoc.toString());
491 // mbd todo: This clearly does not work, as every title gets the same name - trying to leave it empty
492 // QStringList titleInfo = TitleWidget::getFreeTitleInfo(projectFolder());
493 // prod.setAttribute("titlename", titleInfo.at(0));
494 // prod.setAttribute("resource", titleInfo.at(1));
495 //kDebug()<<"TITLE DATA:\n"<<tdoc.toString();
497 } // End conversion of title clips.
499 } else if (m.isText()) {
500 QString comment = m.nodeValue();
501 if (!comment.isEmpty()) {
502 prod.setAttribute("description", comment);
507 int duration = prod.attribute("duration").toInt();
508 if (duration > 0) prod.setAttribute("out", QString::number(duration));
509 // The clip goes back in, but text clips should not go back in, at least not modified
510 westley.insertBefore(prod, QDomNode());
513 QDomNode westley0 = m_doc.elementsByTagName("westley").at(0);
514 if (!markers.firstChild().isNull()) westley0.appendChild(markers);
517 * Convert as much of the kdenlivedoc as possible. Use the producer in
518 * westley. First, remove the old stuff from westley, and add a new
519 * empty one. Also, track the max id in order to use it for the adding
522 int max_kproducer_id = 0;
523 westley0.removeChild(infoXmlNode);
524 QDomElement infoXml_new = m_doc.createElement("kdenlivedoc");
525 infoXml_new.setAttribute("profile", profile);
526 infoXml.setAttribute("position", startPos);
528 // Add all the producers that has a resource in westley
529 QDomElement westley_element = westley0.toElement();
530 if (westley_element.isNull()) {
531 kWarning() << "westley0 element in document was not a QDomElement - unable to add producers to new kdenlivedoc";
533 QDomNodeList wproducers = westley_element.elementsByTagName("producer");
534 int kmax = wproducers.count();
535 for (int i = 0; i < kmax; i++) {
536 QDomElement wproducer = wproducers.at(i).toElement();
537 if (wproducer.isNull()) {
538 kWarning() << "Found producer in westley0, that was not a QDomElement";
541 if (wproducer.attribute("id") == "black") continue;
542 // We have to do slightly different things, depending on the type
543 kDebug() << "Converting producer element with type" << wproducer.attribute("type");
544 if (wproducer.attribute("type").toInt() == TEXT) {
545 kDebug() << "Found TEXT element in producer" << endl;
546 QDomElement kproducer = wproducer.cloneNode(true).toElement();
547 kproducer.setTagName("kdenlive_producer");
548 infoXml_new.appendChild(kproducer);
550 * TODO: Perhaps needs some more changes here to
551 * "frequency", aspect ratio as a float, frame_size,
552 * channels, and later, resource and title name
555 QDomElement kproducer = m_doc.createElement("kdenlive_producer");
556 kproducer.setAttribute("id", wproducer.attribute("id"));
557 if (!wproducer.attribute("description").isEmpty())
558 kproducer.setAttribute("description", wproducer.attribute("description"));
559 kproducer.setAttribute("resource", wproducer.attribute("resource"));
560 kproducer.setAttribute("type", wproducer.attribute("type"));
561 // Testing fix for 358
562 if (!wproducer.attribute("aspect_ratio").isEmpty()) {
563 kproducer.setAttribute("aspect_ratio", wproducer.attribute("aspect_ratio"));
565 if (!wproducer.attribute("source_fps").isEmpty()) {
566 kproducer.setAttribute("fps", wproducer.attribute("source_fps"));
568 if (!wproducer.attribute("length").isEmpty()) {
569 kproducer.setAttribute("duration", wproducer.attribute("length"));
571 infoXml_new.appendChild(kproducer);
573 if (wproducer.attribute("id").toInt() > max_kproducer_id) {
574 max_kproducer_id = wproducer.attribute("id").toInt();
578 #define LOOKUP_FOLDER 1
581 * Look through all the folder elements of the old doc, for each folder,
582 * for each producer, get the id, look it up in the new doc, set the
583 * groupname and groupid. Note, this does not work at the moment - at
584 * least one folder shows up missing, and clips with no folder does not
587 //QDomElement infoXml_old = infoXmlNode.toElement();
588 if (!infoXml_old.isNull()) {
589 QDomNodeList folders = infoXml_old.elementsByTagName("folder");
590 int fsize = folders.size();
591 int groupId = max_kproducer_id + 1; // Start at +1 of max id of the kdenlive_producers
592 for (int i = 0; i < fsize; ++i) {
593 QDomElement folder = folders.at(i).toElement();
594 if (!folder.isNull()) {
595 QString groupName = folder.attribute("name");
596 kDebug() << "groupName: " << groupName << " with groupId: " << groupId;
597 QDomNodeList fproducers = folder.elementsByTagName("producer");
598 int psize = fproducers.size();
599 for (int j = 0; j < psize; ++j) {
600 QDomElement fproducer = fproducers.at(j).toElement();
601 if (!fproducer.isNull()) {
602 QString id = fproducer.attribute("id");
603 // This is not very effective, but compared to loading the clips, its a breeze
604 QDomNodeList kdenlive_producers = infoXml_new.elementsByTagName("kdenlive_producer");
605 int kpsize = kdenlive_producers.size();
606 for (int k = 0; k < kpsize; ++k) {
607 QDomElement kproducer = kdenlive_producers.at(k).toElement(); // Its an element for sure
608 if (id == kproducer.attribute("id")) {
609 // We do not check that it already is part of a folder
610 kproducer.setAttribute("groupid", groupId);
611 kproducer.setAttribute("groupname", groupName);
622 QDomNodeList elements = westley.childNodes();
623 max = elements.count();
624 for (int i = 0; i < max; i++) {
625 QDomElement prod = elements.at(0).toElement();
626 westley0.insertAfter(prod, QDomNode());
629 westley0.appendChild(infoXml_new);
631 westley0.removeChild(westley);
633 // adds <avfile /> information to <kdenlive_producer />
634 QDomNodeList kproducers = m_doc.elementsByTagName("kdenlive_producer");
635 QDomNodeList avfiles = infoXml_old.elementsByTagName("avfile");
636 kDebug() << "found" << avfiles.count() << "<avfile />s and" << kproducers.count() << "<kdenlive_producer />s";
637 for (int i = 0; i < avfiles.count(); ++i) {
638 QDomElement avfile = avfiles.at(i).toElement();
639 QDomElement kproducer;
641 kWarning() << "found an <avfile /> that is not a QDomElement";
643 QString id = avfile.attribute("id");
644 // this is horrible, must be rewritten, it's just for test
645 for (int j = 0; j < kproducers.count(); ++j) {
646 //kDebug() << "checking <kdenlive_producer /> with id" << kproducers.at(j).toElement().attribute("id");
647 if (kproducers.at(j).toElement().attribute("id") == id) {
648 kproducer = kproducers.at(j).toElement();
652 if (kproducer == QDomElement())
653 kWarning() << "no match for <avfile /> with id =" << id;
655 //kDebug() << "ready to set additional <avfile />'s attributes (id =" << id << ")";
656 kproducer.setAttribute("channels", avfile.attribute("channels"));
657 kproducer.setAttribute("duration", avfile.attribute("duration"));
658 kproducer.setAttribute("frame_size", avfile.attribute("width") + 'x' + avfile.attribute("height"));
659 kproducer.setAttribute("frequency", avfile.attribute("frequency"));
660 if (kproducer.attribute("description").isEmpty() && !avfile.attribute("description").isEmpty())
661 kproducer.setAttribute("description", avfile.attribute("description"));
665 infoXml = infoXml_new;
668 if (version <= 0.81) {
669 // Add the tracks information
670 QString tracksOrder = infoXml.attribute("tracks");
671 if (tracksOrder.isEmpty()) {
672 QDomNodeList tracks = m_doc.elementsByTagName("track");
673 for (int i = 0; i < tracks.count(); i++) {
674 QDomElement track = tracks.at(i).toElement();
675 if (track.attribute("producer") != "black_track") {
676 if (track.attribute("hide") == "video")
677 tracksOrder.append('a');
679 tracksOrder.append('v');
683 QDomElement tracksinfo = m_doc.createElement("tracksinfo");
684 for (int i = 0; i < tracksOrder.size(); i++) {
685 QDomElement trackinfo = m_doc.createElement("trackinfo");
686 if (tracksOrder.data()[i] == 'a') {
687 trackinfo.setAttribute("type", "audio");
688 trackinfo.setAttribute("blind", true);
690 trackinfo.setAttribute("blind", false);
691 trackinfo.setAttribute("mute", false);
692 trackinfo.setAttribute("locked", false);
693 tracksinfo.appendChild(trackinfo);
695 infoXml.appendChild(tracksinfo);
698 if (version <= 0.82) {
699 // Convert <westley />s in <mlt />s (MLT extreme makeover)
700 QDomNodeList westleyNodes = m_doc.elementsByTagName("westley");
701 for (int i = 0; i < westleyNodes.count(); i++) {
702 QDomElement westley = westleyNodes.at(i).toElement();
703 westley.setTagName("mlt");
707 if (version <= 0.83) {
708 // Replace point size with pixel size in text titles
709 if (m_doc.toString().contains("font-size")) {
710 KMessageBox::ButtonCode convert = KMessageBox::Continue;
711 QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
712 for (int i = 0; i < kproducerNodes.count() && convert != KMessageBox::No; ++i) {
713 QDomElement kproducer = kproducerNodes.at(i).toElement();
714 if (kproducer.attribute("type").toInt() == TEXT) {
716 data.setContent(kproducer.attribute("xmldata"));
717 QDomNodeList items = data.firstChild().childNodes();
718 for (int j = 0; j < items.count() && convert != KMessageBox::No; ++j) {
719 if (items.at(j).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
720 QDomNamedNodeMap textProperties = items.at(j).namedItem("content").attributes();
721 if (textProperties.namedItem("font-pixel-size").isNull() && !textProperties.namedItem("font-size").isNull()) {
722 // Ask the user if he wants to convert
723 if (convert != KMessageBox::Yes && convert != KMessageBox::No)
724 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"));
725 if (convert == KMessageBox::Yes) {
727 font.setPointSize(textProperties.namedItem("font-size").nodeValue().toInt());
728 QDomElement content = items.at(j).namedItem("content").toElement();
729 content.setAttribute("font-pixel-size", QFontInfo(font).pixelSize());
730 content.removeAttribute("font-size");
731 kproducer.setAttribute("xmldata", data.toString());
733 * You may be tempted to delete the preview file
734 * to force its recreation: bad idea (see
735 * http://www.kdenlive.org/mantis/view.php?id=749)
745 // Fill the <documentproperties /> element
746 QDomElement docProperties = infoXml.firstChildElement("documentproperties");
747 if (docProperties.isNull()) {
748 docProperties = m_doc.createElement("documentproperties");
749 docProperties.setAttribute("zonein", infoXml.attribute("zonein"));
750 docProperties.setAttribute("zoneout", infoXml.attribute("zoneout"));
751 docProperties.setAttribute("zoom", infoXml.attribute("zoom"));
752 docProperties.setAttribute("position", infoXml.attribute("position"));
753 infoXml.appendChild(docProperties);
757 if (version <= 0.84) {
758 // update the title clips to use the new MLT kdenlivetitle producer
759 QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
760 for (int i = 0; i < kproducerNodes.count(); ++i) {
761 QDomElement kproducer = kproducerNodes.at(i).toElement();
762 if (kproducer.attribute("type").toInt() == TEXT) {
763 QString data = kproducer.attribute("xmldata");
764 QString datafile = kproducer.attribute("resource");
765 if (!datafile.endsWith(".kdenlivetitle")) {
766 datafile = QString();
767 kproducer.setAttribute("resource", QString());
769 QString id = kproducer.attribute("id");
770 QDomNodeList mltproducers = m_doc.elementsByTagName("producer");
771 bool foundData = false;
772 bool foundResource = false;
773 bool foundService = false;
774 for (int j = 0; j < mltproducers.count(); j++) {
775 QDomElement wproducer = mltproducers.at(j).toElement();
776 if (wproducer.attribute("id") == id) {
777 QDomNodeList props = wproducer.childNodes();
778 for (int k = 0; k < props.count(); k++) {
779 if (props.at(k).toElement().attribute("name") == "xmldata") {
780 props.at(k).firstChild().setNodeValue(data);
782 } else if (props.at(k).toElement().attribute("name") == "mlt_service") {
783 props.at(k).firstChild().setNodeValue("kdenlivetitle");
785 } else if (props.at(k).toElement().attribute("name") == "resource") {
786 props.at(k).firstChild().setNodeValue(datafile);
787 foundResource = true;
791 QDomElement e = m_doc.createElement("property");
792 e.setAttribute("name", "xmldata");
793 QDomText value = m_doc.createTextNode(data);
794 e.appendChild(value);
795 wproducer.appendChild(e);
798 QDomElement e = m_doc.createElement("property");
799 e.setAttribute("name", "mlt_service");
800 QDomText value = m_doc.createTextNode("kdenlivetitle");
801 e.appendChild(value);
802 wproducer.appendChild(e);
804 if (!foundResource) {
805 QDomElement e = m_doc.createElement("property");
806 e.setAttribute("name", "resource");
807 QDomText value = m_doc.createTextNode(datafile);
808 e.appendChild(value);
809 wproducer.appendChild(e);
819 // The document has been converted: mark it as modified
820 infoXml.setAttribute("version", currentVersion);
825 QString DocumentValidator::colorToString(const QColor& c)
827 QString ret = "%1,%2,%3,%4";
828 ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
832 bool DocumentValidator::isProject() const
834 QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
835 return !infoXmlNode.isNull();
838 bool DocumentValidator::isModified() const