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 QDomElement mlt = m_doc.firstChildElement("mlt");
40 // At least the root element must be there
44 QDomElement kdenliveDoc = mlt.firstChildElement("kdenlivedoc");
45 // Check if we're validating a Kdenlive project
46 if (kdenliveDoc.isNull())
48 // Upgrade the document to the latest version
49 if (!upgrade(kdenliveDoc.attribute("version").toDouble(), currentVersion))
53 * Check the syntax (this will be replaced by XSD validation with Qt 4.6)
54 * and correct some errors
57 // Return (or create) the tractor
58 QDomElement tractor = mlt.firstChildElement("tractor");
59 if (tractor.isNull()) {
61 tractor = m_doc.createElement("tractor");
62 tractor.setAttribute("global_feed", "1");
63 tractor.setAttribute("in", "0");
64 tractor.setAttribute("out", "-1");
65 tractor.setAttribute("id", "maintractor");
66 mlt.appendChild(tractor);
70 * Make sure at least one track exists, and they're equal in number to
71 * to the maximum between MLT and Kdenlive playlists and tracks
73 QDomNodeList playlists = m_doc.elementsByTagName("playlist");
74 int tracksMax = playlists.count() - 1; // Remove the black track
75 QDomNodeList tracks = tractor.elementsByTagName("track");
76 tracksMax = qMax(tracks.count() - 1, tracksMax);
77 QDomNodeList tracksinfo = kdenliveDoc.elementsByTagName("trackinfo");
78 tracksMax = qMax(tracksinfo.count(), tracksMax);
79 tracksMax = qMax(1, tracksMax); // Force existance of one track
80 if (playlists.count() - 1 < tracksMax ||
81 tracks.count() - 1 < tracksMax ||
82 tracksinfo.count() < tracksMax) {
83 kDebug() << "//// WARNING, PROJECT IS CORRUPTED, MISSING TRACK";
86 // use the MLT tracks as reference
87 if (tracks.count() - 1 < tracksMax) {
88 // Looks like one MLT track is missing, remove the extra Kdenlive track if there is one.
89 if (tracksinfo.count() != tracks.count() - 1) {
90 // The Kdenlive tracks are not ok, clear and rebuild them
91 QDomNode tinfo = kdenliveDoc.elementsByTagName("tracksinfo").at(0);
92 QDomNode tnode = tinfo.firstChild();
93 while (!tnode.isNull()) {
94 tinfo.removeChild(tnode);
95 tnode = tinfo.firstChild();
98 for (int i = 1; i < tracks.count(); i++) {
99 QString hide = tracks.at(i).toElement().attribute("hide");
100 QDomElement newTrack = m_doc.createElement("trackinfo");
101 if (hide == "video") {
103 newTrack.setAttribute("type", "audio");
104 newTrack.setAttribute("blind", 1);
105 newTrack.setAttribute("mute", 0);
106 newTrack.setAttribute("lock", 0);
108 newTrack.setAttribute("blind", 0);
109 newTrack.setAttribute("mute", 0);
110 newTrack.setAttribute("lock", 0);
112 tinfo.appendChild(newTrack);
117 if (playlists.count() - 1 < tracksMax) {
118 difference = tracksMax - (playlists.count() - 1);
119 for (int i = 0; i < difference; ++i) {
120 QDomElement playlist = m_doc.createElement("playlist");
121 mlt.appendChild(playlist);
124 if (tracks.count() - 1 < tracksMax) {
125 difference = tracksMax - (tracks.count() - 1);
126 for (int i = 0; i < difference; ++i) {
127 QDomElement track = m_doc.createElement("track");
128 tractor.appendChild(track);
131 if (tracksinfo.count() < tracksMax) {
132 QDomElement tracksinfoElm = kdenliveDoc.firstChildElement("tracksinfo");
133 if (tracksinfoElm.isNull()) {
134 tracksinfoElm = m_doc.createElement("tracksinfo");
135 kdenliveDoc.appendChild(tracksinfoElm);
137 difference = tracksMax - tracksinfo.count();
138 for (int i = 0; i < difference; ++i) {
139 QDomElement trackinfo = m_doc.createElement("trackinfo");
140 trackinfo.setAttribute("mute", "0");
141 trackinfo.setAttribute("locked", "0");
142 tracksinfoElm.appendChild(trackinfo);
147 // TODO: check the tracks references
148 // TODO: check internal mix transitions
154 bool DocumentValidator::upgrade(double version, const double currentVersion)
156 kDebug() << "Opening a document with version " << version;
158 // No conversion needed
159 if (version == currentVersion) {
163 // The document is too new
164 if (version > currentVersion) {
165 kDebug() << "Unable to open document with version " << version;
166 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"));
170 // Unsupported document versions
171 if (version == 0.5 || version == 0.7) {
172 kDebug() << "Unable to open document with version " << version;
173 KMessageBox::sorry(kapp->activeWindow(), i18n("This project type is unsupported (version %1) and can't be loaded.", version), i18n("Unable to open project"));
178 QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
179 QDomElement infoXml = infoXmlNode.toElement();
180 infoXml.setAttribute("upgraded", "1");
182 if (version <= 0.6) {
183 QDomElement infoXml_old = infoXmlNode.cloneNode(true).toElement(); // Needed for folders
184 QDomNode westley = m_doc.elementsByTagName("westley").at(1);
185 QDomNode tractor = m_doc.elementsByTagName("tractor").at(0);
186 QDomNode multitrack = m_doc.elementsByTagName("multitrack").at(0);
187 QDomNodeList playlists = m_doc.elementsByTagName("playlist");
189 QDomNode props = m_doc.elementsByTagName("properties").at(0).toElement();
190 QString profile = props.toElement().attribute("videoprofile");
191 int startPos = props.toElement().attribute("timeline_position").toInt();
192 if (profile == "dv_wide")
193 profile = "dv_pal_wide";
195 // move playlists outside of tractor and add the tracks instead
196 int max = playlists.count();
197 if (westley.isNull()) {
198 westley = m_doc.createElement("westley");
199 m_doc.documentElement().appendChild(westley);
201 if (tractor.isNull()) {
202 kDebug() << "// NO MLT PLAYLIST, building empty one";
203 QDomElement blank_tractor = m_doc.createElement("tractor");
204 westley.appendChild(blank_tractor);
205 QDomElement blank_playlist = m_doc.createElement("playlist");
206 blank_playlist.setAttribute("id", "black_track");
207 westley.insertBefore(blank_playlist, QDomNode());
208 QDomElement blank_track = m_doc.createElement("track");
209 blank_track.setAttribute("producer", "black_track");
210 blank_tractor.appendChild(blank_track);
212 QDomNodeList kdenlivetracks = m_doc.elementsByTagName("kdenlivetrack");
213 for (int i = 0; i < kdenlivetracks.count(); i++) {
214 blank_playlist = m_doc.createElement("playlist");
215 blank_playlist.setAttribute("id", "playlist" + QString::number(i));
216 westley.insertBefore(blank_playlist, QDomNode());
217 blank_track = m_doc.createElement("track");
218 blank_track.setAttribute("producer", "playlist" + QString::number(i));
219 blank_tractor.appendChild(blank_track);
220 if (kdenlivetracks.at(i).toElement().attribute("cliptype") == "Sound") {
221 blank_playlist.setAttribute("hide", "video");
222 blank_track.setAttribute("hide", "video");
225 } else for (int i = 0; i < max; i++) {
226 QDomNode n = playlists.at(i);
227 westley.insertBefore(n, QDomNode());
228 QDomElement pl = n.toElement();
229 QDomElement track = m_doc.createElement("track");
230 QString trackType = pl.attribute("hide");
231 if (!trackType.isEmpty())
232 track.setAttribute("hide", trackType);
233 QString playlist_id = pl.attribute("id");
234 if (playlist_id.isEmpty()) {
235 playlist_id = "black_track";
236 pl.setAttribute("id", playlist_id);
238 track.setAttribute("producer", playlist_id);
239 //tractor.appendChild(track);
240 #define KEEP_TRACK_ORDER 1
241 #ifdef KEEP_TRACK_ORDER
242 tractor.insertAfter(track, QDomNode());
244 // Insert the new track in an order that hopefully matches the 3 video, then 2 audio tracks of Kdenlive 0.7.0
245 // insertion sort - O( tracks*tracks )
246 // Note, this breaks _all_ transitions - but you can move them up and down afterwards.
247 QDomElement tractor_elem = tractor.toElement();
248 if (! tractor_elem.isNull()) {
249 QDomNodeList tracks = tractor_elem.elementsByTagName("track");
250 int size = tracks.size();
252 tractor.insertAfter(track, QDomNode());
254 bool inserted = false;
255 for (int i = 0; i < size; ++i) {
256 QDomElement track_elem = tracks.at(i).toElement();
257 if (track_elem.isNull()) {
258 tractor.insertAfter(track, QDomNode());
262 kDebug() << "playlist_id: " << playlist_id << " producer:" << track_elem.attribute("producer");
263 if (playlist_id < track_elem.attribute("producer")) {
264 tractor.insertBefore(track, track_elem);
270 // Reach here, no insertion, insert last
272 tractor.insertAfter(track, QDomNode());
276 kWarning() << "tractor was not a QDomElement";
277 tractor.insertAfter(track, QDomNode());
281 tractor.removeChild(multitrack);
283 // audio track mixing transitions should not be added to track view, so add required attribute
284 QDomNodeList transitions = m_doc.elementsByTagName("transition");
285 max = transitions.count();
286 for (int i = 0; i < max; i++) {
287 QDomElement tr = transitions.at(i).toElement();
288 if (tr.attribute("combine") == "1" && tr.attribute("mlt_service") == "mix") {
289 QDomElement property = m_doc.createElement("property");
290 property.setAttribute("name", "internal_added");
291 QDomText value = m_doc.createTextNode("237");
292 property.appendChild(value);
293 tr.appendChild(property);
294 property = m_doc.createElement("property");
295 property.setAttribute("name", "mlt_service");
296 value = m_doc.createTextNode("mix");
297 property.appendChild(value);
298 tr.appendChild(property);
300 // convert transition
301 QDomNamedNodeMap attrs = tr.attributes();
302 for (int j = 0; j < attrs.count(); j++) {
303 QString attrName = attrs.item(j).nodeName();
304 if (attrName != "in" && attrName != "out" && attrName != "id") {
305 QDomElement property = m_doc.createElement("property");
306 property.setAttribute("name", attrName);
307 QDomText value = m_doc.createTextNode(attrs.item(j).nodeValue());
308 property.appendChild(value);
309 tr.appendChild(property);
315 // move transitions after tracks
316 for (int i = 0; i < max; i++) {
317 tractor.insertAfter(transitions.at(0), QDomNode());
320 // Fix filters format
321 QDomNodeList entries = m_doc.elementsByTagName("entry");
322 max = entries.count();
323 for (int i = 0; i < max; i++) {
326 QDomNode m = entries.at(i).firstChild();
327 while (!m.isNull()) {
328 if (m.toElement().tagName() == "filter") {
329 QDomElement filt = m.toElement();
330 QDomNamedNodeMap attrs = filt.attributes();
331 QString current_id = filt.attribute("kdenlive_id");
332 if (current_id != last_id) {
334 last_id = current_id;
336 QDomElement e = m_doc.createElement("property");
337 e.setAttribute("name", "kdenlive_ix");
338 QDomText value = m_doc.createTextNode(QString::number(effectix));
339 e.appendChild(value);
341 for (int j = 0; j < attrs.count(); j++) {
342 QDomAttr a = attrs.item(j).toAttr();
344 kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
345 QDomElement e = m_doc.createElement("property");
346 e.setAttribute("name", a.name());
347 QDomText value = m_doc.createTextNode(a.value());
348 e.appendChild(value);
359 QDomNodeList filters = m_doc.elementsByTagName("filter");
360 max = filters.count();
363 for (int i = 0; i < max; i++) {
364 QDomElement filt = filters.at(i).toElement();
365 QDomNamedNodeMap attrs = filt.attributes();
366 QString current_id = filt.attribute("kdenlive_id");
367 if (current_id != last_id) {
369 last_id = current_id;
371 QDomElement e = m_doc.createElement("property");
372 e.setAttribute("name", "kdenlive_ix");
373 QDomText value = m_doc.createTextNode(QString::number(1));
374 e.appendChild(value);
376 for (int j = 0; j < attrs.count(); j++) {
377 QDomAttr a = attrs.item(j).toAttr();
379 kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
380 QDomElement e = m_doc.createElement("property");
381 e.setAttribute("name", a.name());
382 QDomText value = m_doc.createTextNode(a.value());
383 e.appendChild(value);
390 QDomNodeList producers = westley.toElement().elementsByTagName("producer");
391 max = producers.count();
392 for (int i = 0; i < max; i++) {
393 QDomElement prod = producers.at(i).toElement();
394 if (prod.attribute("mlt_service") == "framebuffer") {
395 QString slowmotionprod = prod.attribute("resource");
396 slowmotionprod.replace(':', '?');
397 kDebug() << "// FOUND WRONG SLOWMO, new: " << slowmotionprod;
398 prod.setAttribute("resource", slowmotionprod);
401 // move producers to correct place, markers to a global list, fix clip descriptions
402 QDomElement markers = m_doc.createElement("markers");
403 // This will get the xml producers:
404 producers = m_doc.elementsByTagName("producer");
405 max = producers.count();
406 for (int i = 0; i < max; i++) {
407 QDomElement prod = producers.at(0).toElement();
408 // add resource also as a property (to allow path correction in setNewResource())
409 // TODO: will it work with slowmotion? needs testing
410 /*if (!prod.attribute("resource").isEmpty()) {
411 QDomElement prop_resource = m_doc.createElement("property");
412 prop_resource.setAttribute("name", "resource");
413 QDomText resource = m_doc.createTextNode(prod.attribute("resource"));
414 prop_resource.appendChild(resource);
415 prod.appendChild(prop_resource);
417 QDomNode m = prod.firstChild();
419 if (m.toElement().tagName() == "markers") {
420 QDomNodeList prodchilds = m.childNodes();
421 int maxchild = prodchilds.count();
422 for (int k = 0; k < maxchild; k++) {
423 QDomElement mark = prodchilds.at(0).toElement();
424 mark.setAttribute("id", prod.attribute("id"));
425 markers.insertAfter(mark, QDomNode());
428 } else if (prod.attribute("type").toInt() == TEXT) {
429 // convert title clip
430 if (m.toElement().tagName() == "textclip") {
432 QDomElement titleclip = m.toElement();
433 QDomElement title = tdoc.createElement("kdenlivetitle");
434 tdoc.appendChild(title);
435 QDomNodeList objects = titleclip.childNodes();
436 int maxchild = objects.count();
437 for (int k = 0; k < maxchild; k++) {
439 QDomElement ob = objects.at(k).toElement();
440 if (ob.attribute("type") == "3") {
441 // text object - all of this goes into "xmldata"...
442 QDomElement item = tdoc.createElement("item");
443 item.setAttribute("z-index", ob.attribute("z"));
444 item.setAttribute("type", "QGraphicsTextItem");
445 QDomElement position = tdoc.createElement("position");
446 position.setAttribute("x", ob.attribute("x"));
447 position.setAttribute("y", ob.attribute("y"));
448 QDomElement content = tdoc.createElement("content");
449 content.setAttribute("font", ob.attribute("font_family"));
450 content.setAttribute("font-size", ob.attribute("font_size"));
451 content.setAttribute("font-bold", ob.attribute("bold"));
452 content.setAttribute("font-italic", ob.attribute("italic"));
453 content.setAttribute("font-underline", ob.attribute("underline"));
454 QString col = ob.attribute("color");
456 content.setAttribute("font-color", colorToString(c));
457 // todo: These fields are missing from the newly generated xmldata:
458 // transform, startviewport, endviewport, background
460 QDomText conttxt = tdoc.createTextNode(ob.attribute("text"));
461 content.appendChild(conttxt);
462 item.appendChild(position);
463 item.appendChild(content);
464 title.appendChild(item);
465 } else if (ob.attribute("type") == "5") {
467 QDomElement item = tdoc.createElement("item");
468 item.setAttribute("z-index", ob.attribute("z"));
469 item.setAttribute("type", "QGraphicsRectItem");
470 QDomElement position = tdoc.createElement("position");
471 position.setAttribute("x", ob.attribute("x"));
472 position.setAttribute("y", ob.attribute("y"));
473 QDomElement content = tdoc.createElement("content");
474 QString col = ob.attribute("color");
476 content.setAttribute("brushcolor", colorToString(c));
477 QString rect = "0,0,";
478 rect.append(ob.attribute("width"));
480 rect.append(ob.attribute("height"));
481 content.setAttribute("rect", rect);
482 item.appendChild(position);
483 item.appendChild(content);
484 title.appendChild(item);
487 prod.setAttribute("xmldata", tdoc.toString());
488 // mbd todo: This clearly does not work, as every title gets the same name - trying to leave it empty
489 // QStringList titleInfo = TitleWidget::getFreeTitleInfo(projectFolder());
490 // prod.setAttribute("titlename", titleInfo.at(0));
491 // prod.setAttribute("resource", titleInfo.at(1));
492 //kDebug()<<"TITLE DATA:\n"<<tdoc.toString();
494 } // End conversion of title clips.
496 } else if (m.isText()) {
497 QString comment = m.nodeValue();
498 if (!comment.isEmpty()) {
499 prod.setAttribute("description", comment);
504 int duration = prod.attribute("duration").toInt();
505 if (duration > 0) prod.setAttribute("out", QString::number(duration));
506 // The clip goes back in, but text clips should not go back in, at least not modified
507 westley.insertBefore(prod, QDomNode());
510 QDomNode westley0 = m_doc.elementsByTagName("westley").at(0);
511 if (!markers.firstChild().isNull()) westley0.appendChild(markers);
514 * Convert as much of the kdenlivedoc as possible. Use the producer in
515 * westley. First, remove the old stuff from westley, and add a new
516 * empty one. Also, track the max id in order to use it for the adding
519 int max_kproducer_id = 0;
520 westley0.removeChild(infoXmlNode);
521 QDomElement infoXml_new = m_doc.createElement("kdenlivedoc");
522 infoXml_new.setAttribute("profile", profile);
523 infoXml.setAttribute("position", startPos);
525 // Add all the producers that has a resource in westley
526 QDomElement westley_element = westley0.toElement();
527 if (westley_element.isNull()) {
528 kWarning() << "westley0 element in document was not a QDomElement - unable to add producers to new kdenlivedoc";
530 QDomNodeList wproducers = westley_element.elementsByTagName("producer");
531 int kmax = wproducers.count();
532 for (int i = 0; i < kmax; i++) {
533 QDomElement wproducer = wproducers.at(i).toElement();
534 if (wproducer.isNull()) {
535 kWarning() << "Found producer in westley0, that was not a QDomElement";
538 if (wproducer.attribute("id") == "black") continue;
539 // We have to do slightly different things, depending on the type
540 kDebug() << "Converting producer element with type" << wproducer.attribute("type");
541 if (wproducer.attribute("type").toInt() == TEXT) {
542 kDebug() << "Found TEXT element in producer" << endl;
543 QDomElement kproducer = wproducer.cloneNode(true).toElement();
544 kproducer.setTagName("kdenlive_producer");
545 infoXml_new.appendChild(kproducer);
547 * TODO: Perhaps needs some more changes here to
548 * "frequency", aspect ratio as a float, frame_size,
549 * channels, and later, resource and title name
552 QDomElement kproducer = m_doc.createElement("kdenlive_producer");
553 kproducer.setAttribute("id", wproducer.attribute("id"));
554 if (!wproducer.attribute("description").isEmpty())
555 kproducer.setAttribute("description", wproducer.attribute("description"));
556 kproducer.setAttribute("resource", wproducer.attribute("resource"));
557 kproducer.setAttribute("type", wproducer.attribute("type"));
558 // Testing fix for 358
559 if (!wproducer.attribute("aspect_ratio").isEmpty()) {
560 kproducer.setAttribute("aspect_ratio", wproducer.attribute("aspect_ratio"));
562 if (!wproducer.attribute("source_fps").isEmpty()) {
563 kproducer.setAttribute("fps", wproducer.attribute("source_fps"));
565 if (!wproducer.attribute("length").isEmpty()) {
566 kproducer.setAttribute("duration", wproducer.attribute("length"));
568 infoXml_new.appendChild(kproducer);
570 if (wproducer.attribute("id").toInt() > max_kproducer_id) {
571 max_kproducer_id = wproducer.attribute("id").toInt();
575 #define LOOKUP_FOLDER 1
578 * Look through all the folder elements of the old doc, for each folder,
579 * for each producer, get the id, look it up in the new doc, set the
580 * groupname and groupid. Note, this does not work at the moment - at
581 * least one folder shows up missing, and clips with no folder does not
584 //QDomElement infoXml_old = infoXmlNode.toElement();
585 if (!infoXml_old.isNull()) {
586 QDomNodeList folders = infoXml_old.elementsByTagName("folder");
587 int fsize = folders.size();
588 int groupId = max_kproducer_id + 1; // Start at +1 of max id of the kdenlive_producers
589 for (int i = 0; i < fsize; ++i) {
590 QDomElement folder = folders.at(i).toElement();
591 if (!folder.isNull()) {
592 QString groupName = folder.attribute("name");
593 kDebug() << "groupName: " << groupName << " with groupId: " << groupId;
594 QDomNodeList fproducers = folder.elementsByTagName("producer");
595 int psize = fproducers.size();
596 for (int j = 0; j < psize; ++j) {
597 QDomElement fproducer = fproducers.at(j).toElement();
598 if (!fproducer.isNull()) {
599 QString id = fproducer.attribute("id");
600 // This is not very effective, but compared to loading the clips, its a breeze
601 QDomNodeList kdenlive_producers = infoXml_new.elementsByTagName("kdenlive_producer");
602 int kpsize = kdenlive_producers.size();
603 for (int k = 0; k < kpsize; ++k) {
604 QDomElement kproducer = kdenlive_producers.at(k).toElement(); // Its an element for sure
605 if (id == kproducer.attribute("id")) {
606 // We do not check that it already is part of a folder
607 kproducer.setAttribute("groupid", groupId);
608 kproducer.setAttribute("groupname", groupName);
619 QDomNodeList elements = westley.childNodes();
620 max = elements.count();
621 for (int i = 0; i < max; i++) {
622 QDomElement prod = elements.at(0).toElement();
623 westley0.insertAfter(prod, QDomNode());
626 westley0.appendChild(infoXml_new);
628 westley0.removeChild(westley);
630 // adds <avfile /> information to <kdenlive_producer />
631 QDomNodeList kproducers = m_doc.elementsByTagName("kdenlive_producer");
632 QDomNodeList avfiles = infoXml_old.elementsByTagName("avfile");
633 kDebug() << "found" << avfiles.count() << "<avfile />s and" << kproducers.count() << "<kdenlive_producer />s";
634 for (int i = 0; i < avfiles.count(); ++i) {
635 QDomElement avfile = avfiles.at(i).toElement();
636 QDomElement kproducer;
638 kWarning() << "found an <avfile /> that is not a QDomElement";
640 QString id = avfile.attribute("id");
641 // this is horrible, must be rewritten, it's just for test
642 for (int j = 0; j < kproducers.count(); ++j) {
643 //kDebug() << "checking <kdenlive_producer /> with id" << kproducers.at(j).toElement().attribute("id");
644 if (kproducers.at(j).toElement().attribute("id") == id) {
645 kproducer = kproducers.at(j).toElement();
649 if (kproducer == QDomElement())
650 kWarning() << "no match for <avfile /> with id =" << id;
652 //kDebug() << "ready to set additional <avfile />'s attributes (id =" << id << ")";
653 kproducer.setAttribute("channels", avfile.attribute("channels"));
654 kproducer.setAttribute("duration", avfile.attribute("duration"));
655 kproducer.setAttribute("frame_size", avfile.attribute("width") + 'x' + avfile.attribute("height"));
656 kproducer.setAttribute("frequency", avfile.attribute("frequency"));
657 if (kproducer.attribute("description").isEmpty() && !avfile.attribute("description").isEmpty())
658 kproducer.setAttribute("description", avfile.attribute("description"));
662 infoXml = infoXml_new;
665 if (version <= 0.81) {
666 // Add the tracks information
667 QString tracksOrder = infoXml.attribute("tracks");
668 if (tracksOrder.isEmpty()) {
669 QDomNodeList tracks = m_doc.elementsByTagName("track");
670 for (int i = 0; i < tracks.count(); i++) {
671 QDomElement track = tracks.at(i).toElement();
672 if (track.attribute("producer") != "black_track") {
673 if (track.attribute("hide") == "video")
674 tracksOrder.append('a');
676 tracksOrder.append('v');
680 QDomElement tracksinfo = m_doc.createElement("tracksinfo");
681 for (int i = 0; i < tracksOrder.size(); i++) {
682 QDomElement trackinfo = m_doc.createElement("trackinfo");
683 if (tracksOrder.data()[i] == 'a') {
684 trackinfo.setAttribute("type", "audio");
685 trackinfo.setAttribute("blind", true);
687 trackinfo.setAttribute("blind", false);
688 trackinfo.setAttribute("mute", false);
689 trackinfo.setAttribute("locked", false);
690 tracksinfo.appendChild(trackinfo);
692 infoXml.appendChild(tracksinfo);
695 if (version <= 0.82) {
696 // Convert <westley />s in <mlt />s (MLT extreme makeover)
697 QDomNodeList westleyNodes = m_doc.elementsByTagName("westley");
698 for (int i = 0; i < westleyNodes.count(); i++) {
699 QDomElement westley = westleyNodes.at(i).toElement();
700 westley.setTagName("mlt");
704 if (version <= 0.83) {
705 // Replace point size with pixel size in text titles
706 if (m_doc.toString().contains("font-size")) {
707 KMessageBox::ButtonCode convert = KMessageBox::Continue;
708 QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
709 for (int i = 0; i < kproducerNodes.count() && convert != KMessageBox::No; ++i) {
710 QDomElement kproducer = kproducerNodes.at(i).toElement();
711 if (kproducer.attribute("type").toInt() == TEXT) {
713 data.setContent(kproducer.attribute("xmldata"));
714 QDomNodeList items = data.firstChild().childNodes();
715 for (int j = 0; j < items.count() && convert != KMessageBox::No; ++j) {
716 if (items.at(j).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
717 QDomNamedNodeMap textProperties = items.at(j).namedItem("content").attributes();
718 if (textProperties.namedItem("font-pixel-size").isNull() && !textProperties.namedItem("font-size").isNull()) {
719 // Ask the user if he wants to convert
720 if (convert != KMessageBox::Yes && convert != KMessageBox::No)
721 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"));
722 if (convert == KMessageBox::Yes) {
724 font.setPointSize(textProperties.namedItem("font-size").nodeValue().toInt());
725 QDomElement content = items.at(j).namedItem("content").toElement();
726 content.setAttribute("font-pixel-size", QFontInfo(font).pixelSize());
727 content.removeAttribute("font-size");
728 kproducer.setAttribute("xmldata", data.toString());
730 * You may be tempted to delete the preview file
731 * to force its recreation: bad idea (see
732 * http://www.kdenlive.org/mantis/view.php?id=749)
742 // Fill the <documentproperties /> element
743 QDomElement docProperties = infoXml.firstChildElement("documentproperties");
744 if (docProperties.isNull()) {
745 docProperties = m_doc.createElement("documentproperties");
746 docProperties.setAttribute("zonein", infoXml.attribute("zonein"));
747 docProperties.setAttribute("zoneout", infoXml.attribute("zoneout"));
748 docProperties.setAttribute("zoom", infoXml.attribute("zoom"));
749 docProperties.setAttribute("position", infoXml.attribute("position"));
750 infoXml.appendChild(docProperties);
754 if (version <= 0.84) {
755 // update the title clips to use the new MLT kdenlivetitle producer
756 QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
757 for (int i = 0; i < kproducerNodes.count(); ++i) {
758 QDomElement kproducer = kproducerNodes.at(i).toElement();
759 if (kproducer.attribute("type").toInt() == TEXT) {
760 QString data = kproducer.attribute("xmldata");
761 QString datafile = kproducer.attribute("resource");
762 if (!datafile.endsWith(".kdenlivetitle")) {
763 datafile = QString();
764 kproducer.setAttribute("resource", QString());
766 QString id = kproducer.attribute("id");
767 QDomNodeList mltproducers = m_doc.elementsByTagName("producer");
768 bool foundData = false;
769 bool foundResource = false;
770 bool foundService = false;
771 for (int j = 0; j < mltproducers.count(); j++) {
772 QDomElement wproducer = mltproducers.at(j).toElement();
773 if (wproducer.attribute("id") == id) {
774 QDomNodeList props = wproducer.childNodes();
775 for (int k = 0; k < props.count(); k++) {
776 if (props.at(k).toElement().attribute("name") == "xmldata") {
777 props.at(k).firstChild().setNodeValue(data);
779 } else if (props.at(k).toElement().attribute("name") == "mlt_service") {
780 props.at(k).firstChild().setNodeValue("kdenlivetitle");
782 } else if (props.at(k).toElement().attribute("name") == "resource") {
783 props.at(k).firstChild().setNodeValue(datafile);
784 foundResource = true;
788 QDomElement e = m_doc.createElement("property");
789 e.setAttribute("name", "xmldata");
790 QDomText value = m_doc.createTextNode(data);
791 e.appendChild(value);
792 wproducer.appendChild(e);
795 QDomElement e = m_doc.createElement("property");
796 e.setAttribute("name", "mlt_service");
797 QDomText value = m_doc.createTextNode("kdenlivetitle");
798 e.appendChild(value);
799 wproducer.appendChild(e);
801 if (!foundResource) {
802 QDomElement e = m_doc.createElement("property");
803 e.setAttribute("name", "resource");
804 QDomText value = m_doc.createTextNode(datafile);
805 e.appendChild(value);
806 wproducer.appendChild(e);
816 // The document has been converted: mark it as modified
817 infoXml.setAttribute("version", currentVersion);
822 QString DocumentValidator::colorToString(const QColor& c)
824 QString ret = "%1,%2,%3,%4";
825 ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
829 bool DocumentValidator::isProject() const
831 QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
832 return !infoXmlNode.isNull();
835 bool DocumentValidator::isModified() const