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>
33 DocumentValidator::DocumentValidator(QDomDocument doc):
38 bool DocumentValidator::validate(const double currentVersion)
40 QDomElement mlt = m_doc.firstChildElement("mlt");
41 // At least the root element must be there
45 QDomElement kdenliveDoc = mlt.firstChildElement("kdenlivedoc");
46 // Check if we're validating a Kdenlive project
47 if (kdenliveDoc.isNull())
49 // Upgrade the document to the latest version
50 if (!upgrade(kdenliveDoc.attribute("version").toDouble(), currentVersion))
54 * Check the syntax (this will be replaced by XSD validation with Qt 4.6)
55 * and correct some errors
58 // Return (or create) the tractor
59 QDomElement tractor = mlt.firstChildElement("tractor");
60 if (tractor.isNull()) {
62 tractor = m_doc.createElement("tractor");
63 tractor.setAttribute("global_feed", "1");
64 tractor.setAttribute("in", "0");
65 tractor.setAttribute("out", "-1");
66 tractor.setAttribute("id", "maintractor");
67 mlt.appendChild(tractor);
71 * Make sure at least one track exists, and they're equal in number to
72 * to the maximum between MLT and Kdenlive playlists and tracks
74 QDomNodeList playlists = m_doc.elementsByTagName("playlist");
75 int tracksMax = playlists.count() - 1; // Remove the black track
76 QDomNodeList tracks = tractor.elementsByTagName("track");
77 tracksMax = qMax(tracks.count() - 1, tracksMax);
78 QDomNodeList tracksinfo = kdenliveDoc.elementsByTagName("trackinfo");
79 tracksMax = qMax(tracksinfo.count(), tracksMax);
80 tracksMax = qMax(1, tracksMax); // Force existance of one track
81 if (playlists.count() - 1 < tracksMax ||
82 tracks.count() - 1 < tracksMax ||
83 tracksinfo.count() < tracksMax) {
84 kDebug() << "//// WARNING, PROJECT IS CORRUPTED, MISSING TRACK";
87 // use the MLT tracks as reference
88 if (tracks.count() - 1 < tracksMax) {
89 // Looks like one MLT track is missing, remove the extra Kdenlive track if there is one.
90 if (tracksinfo.count() != tracks.count() - 1) {
91 // The Kdenlive tracks are not ok, clear and rebuild them
92 QDomNode tinfo = kdenliveDoc.elementsByTagName("tracksinfo").at(0);
93 QDomNode tnode = tinfo.firstChild();
94 while (!tnode.isNull()) {
95 tinfo.removeChild(tnode);
96 tnode = tinfo.firstChild();
99 for (int i = 1; i < tracks.count(); i++) {
100 QString hide = tracks.at(i).toElement().attribute("hide");
101 QDomElement newTrack = m_doc.createElement("trackinfo");
102 if (hide == "video") {
104 newTrack.setAttribute("type", "audio");
105 newTrack.setAttribute("blind", 1);
106 newTrack.setAttribute("mute", 0);
107 newTrack.setAttribute("lock", 0);
109 newTrack.setAttribute("blind", 0);
110 newTrack.setAttribute("mute", 0);
111 newTrack.setAttribute("lock", 0);
113 tinfo.appendChild(newTrack);
118 if (playlists.count() - 1 < tracksMax) {
119 difference = tracksMax - (playlists.count() - 1);
120 for (int i = 0; i < difference; ++i) {
121 QDomElement playlist = m_doc.createElement("playlist");
122 mlt.appendChild(playlist);
125 if (tracks.count() - 1 < tracksMax) {
126 difference = tracksMax - (tracks.count() - 1);
127 for (int i = 0; i < difference; ++i) {
128 QDomElement track = m_doc.createElement("track");
129 tractor.appendChild(track);
132 if (tracksinfo.count() < tracksMax) {
133 QDomElement tracksinfoElm = kdenliveDoc.firstChildElement("tracksinfo");
134 if (tracksinfoElm.isNull()) {
135 tracksinfoElm = m_doc.createElement("tracksinfo");
136 kdenliveDoc.appendChild(tracksinfoElm);
138 difference = tracksMax - tracksinfo.count();
139 for (int i = 0; i < difference; ++i) {
140 QDomElement trackinfo = m_doc.createElement("trackinfo");
141 trackinfo.setAttribute("mute", "0");
142 trackinfo.setAttribute("locked", "0");
143 tracksinfoElm.appendChild(trackinfo);
148 // TODO: check the tracks references
149 // TODO: check internal mix transitions
156 bool DocumentValidator::upgrade(double version, const double currentVersion)
158 kDebug() << "Opening a document with version " << version;
160 // No conversion needed
161 if (version == currentVersion) {
165 // The document is too new
166 if (version > currentVersion) {
167 kDebug() << "Unable to open document with version " << version;
168 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"));
172 // Unsupported document versions
173 if (version == 0.5 || version == 0.7) {
174 kDebug() << "Unable to open document with version " << version;
175 KMessageBox::sorry(kapp->activeWindow(), i18n("This project type is unsupported (version %1) and can't be loaded.", version), i18n("Unable to open project"));
180 QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
181 QDomElement infoXml = infoXmlNode.toElement();
182 infoXml.setAttribute("upgraded", "1");
184 if (version <= 0.6) {
185 QDomElement infoXml_old = infoXmlNode.cloneNode(true).toElement(); // Needed for folders
186 QDomNode westley = m_doc.elementsByTagName("westley").at(1);
187 QDomNode tractor = m_doc.elementsByTagName("tractor").at(0);
188 QDomNode multitrack = m_doc.elementsByTagName("multitrack").at(0);
189 QDomNodeList playlists = m_doc.elementsByTagName("playlist");
191 QDomNode props = m_doc.elementsByTagName("properties").at(0).toElement();
192 QString profile = props.toElement().attribute("videoprofile");
193 int startPos = props.toElement().attribute("timeline_position").toInt();
194 if (profile == "dv_wide")
195 profile = "dv_pal_wide";
197 // move playlists outside of tractor and add the tracks instead
198 int max = playlists.count();
199 if (westley.isNull()) {
200 westley = m_doc.createElement("westley");
201 m_doc.documentElement().appendChild(westley);
203 if (tractor.isNull()) {
204 kDebug() << "// NO MLT PLAYLIST, building empty one";
205 QDomElement blank_tractor = m_doc.createElement("tractor");
206 westley.appendChild(blank_tractor);
207 QDomElement blank_playlist = m_doc.createElement("playlist");
208 blank_playlist.setAttribute("id", "black_track");
209 westley.insertBefore(blank_playlist, QDomNode());
210 QDomElement blank_track = m_doc.createElement("track");
211 blank_track.setAttribute("producer", "black_track");
212 blank_tractor.appendChild(blank_track);
214 QDomNodeList kdenlivetracks = m_doc.elementsByTagName("kdenlivetrack");
215 for (int i = 0; i < kdenlivetracks.count(); i++) {
216 blank_playlist = m_doc.createElement("playlist");
217 blank_playlist.setAttribute("id", "playlist" + QString::number(i));
218 westley.insertBefore(blank_playlist, QDomNode());
219 blank_track = m_doc.createElement("track");
220 blank_track.setAttribute("producer", "playlist" + QString::number(i));
221 blank_tractor.appendChild(blank_track);
222 if (kdenlivetracks.at(i).toElement().attribute("cliptype") == "Sound") {
223 blank_playlist.setAttribute("hide", "video");
224 blank_track.setAttribute("hide", "video");
227 } else for (int i = 0; i < max; i++) {
228 QDomNode n = playlists.at(i);
229 westley.insertBefore(n, QDomNode());
230 QDomElement pl = n.toElement();
231 QDomElement track = m_doc.createElement("track");
232 QString trackType = pl.attribute("hide");
233 if (!trackType.isEmpty())
234 track.setAttribute("hide", trackType);
235 QString playlist_id = pl.attribute("id");
236 if (playlist_id.isEmpty()) {
237 playlist_id = "black_track";
238 pl.setAttribute("id", playlist_id);
240 track.setAttribute("producer", playlist_id);
241 //tractor.appendChild(track);
242 #define KEEP_TRACK_ORDER 1
243 #ifdef KEEP_TRACK_ORDER
244 tractor.insertAfter(track, QDomNode());
246 // Insert the new track in an order that hopefully matches the 3 video, then 2 audio tracks of Kdenlive 0.7.0
247 // insertion sort - O( tracks*tracks )
248 // Note, this breaks _all_ transitions - but you can move them up and down afterwards.
249 QDomElement tractor_elem = tractor.toElement();
250 if (! tractor_elem.isNull()) {
251 QDomNodeList tracks = tractor_elem.elementsByTagName("track");
252 int size = tracks.size();
254 tractor.insertAfter(track, QDomNode());
256 bool inserted = false;
257 for (int i = 0; i < size; ++i) {
258 QDomElement track_elem = tracks.at(i).toElement();
259 if (track_elem.isNull()) {
260 tractor.insertAfter(track, QDomNode());
264 kDebug() << "playlist_id: " << playlist_id << " producer:" << track_elem.attribute("producer");
265 if (playlist_id < track_elem.attribute("producer")) {
266 tractor.insertBefore(track, track_elem);
272 // Reach here, no insertion, insert last
274 tractor.insertAfter(track, QDomNode());
278 kWarning() << "tractor was not a QDomElement";
279 tractor.insertAfter(track, QDomNode());
283 tractor.removeChild(multitrack);
285 // audio track mixing transitions should not be added to track view, so add required attribute
286 QDomNodeList transitions = m_doc.elementsByTagName("transition");
287 max = transitions.count();
288 for (int i = 0; i < max; i++) {
289 QDomElement tr = transitions.at(i).toElement();
290 if (tr.attribute("combine") == "1" && tr.attribute("mlt_service") == "mix") {
291 QDomElement property = m_doc.createElement("property");
292 property.setAttribute("name", "internal_added");
293 QDomText value = m_doc.createTextNode("237");
294 property.appendChild(value);
295 tr.appendChild(property);
296 property = m_doc.createElement("property");
297 property.setAttribute("name", "mlt_service");
298 value = m_doc.createTextNode("mix");
299 property.appendChild(value);
300 tr.appendChild(property);
302 // convert transition
303 QDomNamedNodeMap attrs = tr.attributes();
304 for (int j = 0; j < attrs.count(); j++) {
305 QString attrName = attrs.item(j).nodeName();
306 if (attrName != "in" && attrName != "out" && attrName != "id") {
307 QDomElement property = m_doc.createElement("property");
308 property.setAttribute("name", attrName);
309 QDomText value = m_doc.createTextNode(attrs.item(j).nodeValue());
310 property.appendChild(value);
311 tr.appendChild(property);
317 // move transitions after tracks
318 for (int i = 0; i < max; i++) {
319 tractor.insertAfter(transitions.at(0), QDomNode());
322 // Fix filters format
323 QDomNodeList entries = m_doc.elementsByTagName("entry");
324 max = entries.count();
325 for (int i = 0; i < max; i++) {
328 QDomNode m = entries.at(i).firstChild();
329 while (!m.isNull()) {
330 if (m.toElement().tagName() == "filter") {
331 QDomElement filt = m.toElement();
332 QDomNamedNodeMap attrs = filt.attributes();
333 QString current_id = filt.attribute("kdenlive_id");
334 if (current_id != last_id) {
336 last_id = current_id;
338 QDomElement e = m_doc.createElement("property");
339 e.setAttribute("name", "kdenlive_ix");
340 QDomText value = m_doc.createTextNode(QString::number(effectix));
341 e.appendChild(value);
343 for (int j = 0; j < attrs.count(); j++) {
344 QDomAttr a = attrs.item(j).toAttr();
346 kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
347 QDomElement e = m_doc.createElement("property");
348 e.setAttribute("name", a.name());
349 QDomText value = m_doc.createTextNode(a.value());
350 e.appendChild(value);
361 QDomNodeList filters = m_doc.elementsByTagName("filter");
362 max = filters.count();
365 for (int i = 0; i < max; i++) {
366 QDomElement filt = filters.at(i).toElement();
367 QDomNamedNodeMap attrs = filt.attributes();
368 QString current_id = filt.attribute("kdenlive_id");
369 if (current_id != last_id) {
371 last_id = current_id;
373 QDomElement e = m_doc.createElement("property");
374 e.setAttribute("name", "kdenlive_ix");
375 QDomText value = m_doc.createTextNode(QString::number(1));
376 e.appendChild(value);
378 for (int j = 0; j < attrs.count(); j++) {
379 QDomAttr a = attrs.item(j).toAttr();
381 kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
382 QDomElement e = m_doc.createElement("property");
383 e.setAttribute("name", a.name());
384 QDomText value = m_doc.createTextNode(a.value());
385 e.appendChild(value);
392 QDomNodeList producers = westley.toElement().elementsByTagName("producer");
393 max = producers.count();
394 for (int i = 0; i < max; i++) {
395 QDomElement prod = producers.at(i).toElement();
396 if (prod.attribute("mlt_service") == "framebuffer") {
397 QString slowmotionprod = prod.attribute("resource");
398 slowmotionprod.replace(':', '?');
399 kDebug() << "// FOUND WRONG SLOWMO, new: " << slowmotionprod;
400 prod.setAttribute("resource", slowmotionprod);
403 // move producers to correct place, markers to a global list, fix clip descriptions
404 QDomElement markers = m_doc.createElement("markers");
405 // This will get the xml producers:
406 producers = m_doc.elementsByTagName("producer");
407 max = producers.count();
408 for (int i = 0; i < max; i++) {
409 QDomElement prod = producers.at(0).toElement();
410 // add resource also as a property (to allow path correction in setNewResource())
411 // TODO: will it work with slowmotion? needs testing
412 /*if (!prod.attribute("resource").isEmpty()) {
413 QDomElement prop_resource = m_doc.createElement("property");
414 prop_resource.setAttribute("name", "resource");
415 QDomText resource = m_doc.createTextNode(prod.attribute("resource"));
416 prop_resource.appendChild(resource);
417 prod.appendChild(prop_resource);
419 QDomNode m = prod.firstChild();
421 if (m.toElement().tagName() == "markers") {
422 QDomNodeList prodchilds = m.childNodes();
423 int maxchild = prodchilds.count();
424 for (int k = 0; k < maxchild; k++) {
425 QDomElement mark = prodchilds.at(0).toElement();
426 mark.setAttribute("id", prod.attribute("id"));
427 markers.insertAfter(mark, QDomNode());
430 } else if (prod.attribute("type").toInt() == TEXT) {
431 // convert title clip
432 if (m.toElement().tagName() == "textclip") {
434 QDomElement titleclip = m.toElement();
435 QDomElement title = tdoc.createElement("kdenlivetitle");
436 tdoc.appendChild(title);
437 QDomNodeList objects = titleclip.childNodes();
438 int maxchild = objects.count();
439 for (int k = 0; k < maxchild; k++) {
441 QDomElement ob = objects.at(k).toElement();
442 if (ob.attribute("type") == "3") {
443 // text object - all of this goes into "xmldata"...
444 QDomElement item = tdoc.createElement("item");
445 item.setAttribute("z-index", ob.attribute("z"));
446 item.setAttribute("type", "QGraphicsTextItem");
447 QDomElement position = tdoc.createElement("position");
448 position.setAttribute("x", ob.attribute("x"));
449 position.setAttribute("y", ob.attribute("y"));
450 QDomElement content = tdoc.createElement("content");
451 content.setAttribute("font", ob.attribute("font_family"));
452 content.setAttribute("font-size", ob.attribute("font_size"));
453 content.setAttribute("font-bold", ob.attribute("bold"));
454 content.setAttribute("font-italic", ob.attribute("italic"));
455 content.setAttribute("font-underline", ob.attribute("underline"));
456 QString col = ob.attribute("color");
458 content.setAttribute("font-color", colorToString(c));
459 // todo: These fields are missing from the newly generated xmldata:
460 // transform, startviewport, endviewport, background
462 QDomText conttxt = tdoc.createTextNode(ob.attribute("text"));
463 content.appendChild(conttxt);
464 item.appendChild(position);
465 item.appendChild(content);
466 title.appendChild(item);
467 } else if (ob.attribute("type") == "5") {
469 QDomElement item = tdoc.createElement("item");
470 item.setAttribute("z-index", ob.attribute("z"));
471 item.setAttribute("type", "QGraphicsRectItem");
472 QDomElement position = tdoc.createElement("position");
473 position.setAttribute("x", ob.attribute("x"));
474 position.setAttribute("y", ob.attribute("y"));
475 QDomElement content = tdoc.createElement("content");
476 QString col = ob.attribute("color");
478 content.setAttribute("brushcolor", colorToString(c));
479 QString rect = "0,0,";
480 rect.append(ob.attribute("width"));
482 rect.append(ob.attribute("height"));
483 content.setAttribute("rect", rect);
484 item.appendChild(position);
485 item.appendChild(content);
486 title.appendChild(item);
489 prod.setAttribute("xmldata", tdoc.toString());
490 // mbd todo: This clearly does not work, as every title gets the same name - trying to leave it empty
491 // QStringList titleInfo = TitleWidget::getFreeTitleInfo(projectFolder());
492 // prod.setAttribute("titlename", titleInfo.at(0));
493 // prod.setAttribute("resource", titleInfo.at(1));
494 //kDebug()<<"TITLE DATA:\n"<<tdoc.toString();
496 } // End conversion of title clips.
498 } else if (m.isText()) {
499 QString comment = m.nodeValue();
500 if (!comment.isEmpty()) {
501 prod.setAttribute("description", comment);
506 int duration = prod.attribute("duration").toInt();
507 if (duration > 0) prod.setAttribute("out", QString::number(duration));
508 // The clip goes back in, but text clips should not go back in, at least not modified
509 westley.insertBefore(prod, QDomNode());
512 QDomNode westley0 = m_doc.elementsByTagName("westley").at(0);
513 if (!markers.firstChild().isNull()) westley0.appendChild(markers);
516 * Convert as much of the kdenlivedoc as possible. Use the producer in
517 * westley. First, remove the old stuff from westley, and add a new
518 * empty one. Also, track the max id in order to use it for the adding
521 int max_kproducer_id = 0;
522 westley0.removeChild(infoXmlNode);
523 QDomElement infoXml_new = m_doc.createElement("kdenlivedoc");
524 infoXml_new.setAttribute("profile", profile);
525 infoXml.setAttribute("position", startPos);
527 // Add all the producers that has a resource in westley
528 QDomElement westley_element = westley0.toElement();
529 if (westley_element.isNull()) {
530 kWarning() << "westley0 element in document was not a QDomElement - unable to add producers to new kdenlivedoc";
532 QDomNodeList wproducers = westley_element.elementsByTagName("producer");
533 int kmax = wproducers.count();
534 for (int i = 0; i < kmax; i++) {
535 QDomElement wproducer = wproducers.at(i).toElement();
536 if (wproducer.isNull()) {
537 kWarning() << "Found producer in westley0, that was not a QDomElement";
540 if (wproducer.attribute("id") == "black") continue;
541 // We have to do slightly different things, depending on the type
542 kDebug() << "Converting producer element with type" << wproducer.attribute("type");
543 if (wproducer.attribute("type").toInt() == TEXT) {
544 kDebug() << "Found TEXT element in producer" << endl;
545 QDomElement kproducer = wproducer.cloneNode(true).toElement();
546 kproducer.setTagName("kdenlive_producer");
547 infoXml_new.appendChild(kproducer);
549 * TODO: Perhaps needs some more changes here to
550 * "frequency", aspect ratio as a float, frame_size,
551 * channels, and later, resource and title name
554 QDomElement kproducer = m_doc.createElement("kdenlive_producer");
555 kproducer.setAttribute("id", wproducer.attribute("id"));
556 if (!wproducer.attribute("description").isEmpty())
557 kproducer.setAttribute("description", wproducer.attribute("description"));
558 kproducer.setAttribute("resource", wproducer.attribute("resource"));
559 kproducer.setAttribute("type", wproducer.attribute("type"));
560 // Testing fix for 358
561 if (!wproducer.attribute("aspect_ratio").isEmpty()) {
562 kproducer.setAttribute("aspect_ratio", wproducer.attribute("aspect_ratio"));
564 if (!wproducer.attribute("source_fps").isEmpty()) {
565 kproducer.setAttribute("fps", wproducer.attribute("source_fps"));
567 if (!wproducer.attribute("length").isEmpty()) {
568 kproducer.setAttribute("duration", wproducer.attribute("length"));
570 infoXml_new.appendChild(kproducer);
572 if (wproducer.attribute("id").toInt() > max_kproducer_id) {
573 max_kproducer_id = wproducer.attribute("id").toInt();
577 #define LOOKUP_FOLDER 1
580 * Look through all the folder elements of the old doc, for each folder,
581 * for each producer, get the id, look it up in the new doc, set the
582 * groupname and groupid. Note, this does not work at the moment - at
583 * least one folder shows up missing, and clips with no folder does not
586 //QDomElement infoXml_old = infoXmlNode.toElement();
587 if (!infoXml_old.isNull()) {
588 QDomNodeList folders = infoXml_old.elementsByTagName("folder");
589 int fsize = folders.size();
590 int groupId = max_kproducer_id + 1; // Start at +1 of max id of the kdenlive_producers
591 for (int i = 0; i < fsize; ++i) {
592 QDomElement folder = folders.at(i).toElement();
593 if (!folder.isNull()) {
594 QString groupName = folder.attribute("name");
595 kDebug() << "groupName: " << groupName << " with groupId: " << groupId;
596 QDomNodeList fproducers = folder.elementsByTagName("producer");
597 int psize = fproducers.size();
598 for (int j = 0; j < psize; ++j) {
599 QDomElement fproducer = fproducers.at(j).toElement();
600 if (!fproducer.isNull()) {
601 QString id = fproducer.attribute("id");
602 // This is not very effective, but compared to loading the clips, its a breeze
603 QDomNodeList kdenlive_producers = infoXml_new.elementsByTagName("kdenlive_producer");
604 int kpsize = kdenlive_producers.size();
605 for (int k = 0; k < kpsize; ++k) {
606 QDomElement kproducer = kdenlive_producers.at(k).toElement(); // Its an element for sure
607 if (id == kproducer.attribute("id")) {
608 // We do not check that it already is part of a folder
609 kproducer.setAttribute("groupid", groupId);
610 kproducer.setAttribute("groupname", groupName);
621 QDomNodeList elements = westley.childNodes();
622 max = elements.count();
623 for (int i = 0; i < max; i++) {
624 QDomElement prod = elements.at(0).toElement();
625 westley0.insertAfter(prod, QDomNode());
628 westley0.appendChild(infoXml_new);
630 westley0.removeChild(westley);
632 // adds <avfile /> information to <kdenlive_producer />
633 QDomNodeList kproducers = m_doc.elementsByTagName("kdenlive_producer");
634 QDomNodeList avfiles = infoXml_old.elementsByTagName("avfile");
635 kDebug() << "found" << avfiles.count() << "<avfile />s and" << kproducers.count() << "<kdenlive_producer />s";
636 for (int i = 0; i < avfiles.count(); ++i) {
637 QDomElement avfile = avfiles.at(i).toElement();
638 QDomElement kproducer;
640 kWarning() << "found an <avfile /> that is not a QDomElement";
642 QString id = avfile.attribute("id");
643 // this is horrible, must be rewritten, it's just for test
644 for (int j = 0; j < kproducers.count(); ++j) {
645 //kDebug() << "checking <kdenlive_producer /> with id" << kproducers.at(j).toElement().attribute("id");
646 if (kproducers.at(j).toElement().attribute("id") == id) {
647 kproducer = kproducers.at(j).toElement();
651 if (kproducer == QDomElement())
652 kWarning() << "no match for <avfile /> with id =" << id;
654 //kDebug() << "ready to set additional <avfile />'s attributes (id =" << id << ")";
655 kproducer.setAttribute("channels", avfile.attribute("channels"));
656 kproducer.setAttribute("duration", avfile.attribute("duration"));
657 kproducer.setAttribute("frame_size", avfile.attribute("width") + 'x' + avfile.attribute("height"));
658 kproducer.setAttribute("frequency", avfile.attribute("frequency"));
659 if (kproducer.attribute("description").isEmpty() && !avfile.attribute("description").isEmpty())
660 kproducer.setAttribute("description", avfile.attribute("description"));
664 infoXml = infoXml_new;
667 if (version <= 0.81) {
668 // Add the tracks information
669 QString tracksOrder = infoXml.attribute("tracks");
670 if (tracksOrder.isEmpty()) {
671 QDomNodeList tracks = m_doc.elementsByTagName("track");
672 for (int i = 0; i < tracks.count(); i++) {
673 QDomElement track = tracks.at(i).toElement();
674 if (track.attribute("producer") != "black_track") {
675 if (track.attribute("hide") == "video")
676 tracksOrder.append('a');
678 tracksOrder.append('v');
682 QDomElement tracksinfo = m_doc.createElement("tracksinfo");
683 for (int i = 0; i < tracksOrder.size(); i++) {
684 QDomElement trackinfo = m_doc.createElement("trackinfo");
685 if (tracksOrder.data()[i] == 'a') {
686 trackinfo.setAttribute("type", "audio");
687 trackinfo.setAttribute("blind", true);
689 trackinfo.setAttribute("blind", false);
690 trackinfo.setAttribute("mute", false);
691 trackinfo.setAttribute("locked", false);
692 tracksinfo.appendChild(trackinfo);
694 infoXml.appendChild(tracksinfo);
697 if (version <= 0.82) {
698 // Convert <westley />s in <mlt />s (MLT extreme makeover)
699 QDomNodeList westleyNodes = m_doc.elementsByTagName("westley");
700 for (int i = 0; i < westleyNodes.count(); i++) {
701 QDomElement westley = westleyNodes.at(i).toElement();
702 westley.setTagName("mlt");
706 if (version <= 0.83) {
707 // Replace point size with pixel size in text titles
708 if (m_doc.toString().contains("font-size")) {
709 KMessageBox::ButtonCode convert = KMessageBox::Continue;
710 QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
711 for (int i = 0; i < kproducerNodes.count() && convert != KMessageBox::No; ++i) {
712 QDomElement kproducer = kproducerNodes.at(i).toElement();
713 if (kproducer.attribute("type").toInt() == TEXT) {
715 data.setContent(kproducer.attribute("xmldata"));
716 QDomNodeList items = data.firstChild().childNodes();
717 for (int j = 0; j < items.count() && convert != KMessageBox::No; ++j) {
718 if (items.at(j).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
719 QDomNamedNodeMap textProperties = items.at(j).namedItem("content").attributes();
720 if (textProperties.namedItem("font-pixel-size").isNull() && !textProperties.namedItem("font-size").isNull()) {
721 // Ask the user if he wants to convert
722 if (convert != KMessageBox::Yes && convert != KMessageBox::No)
723 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"));
724 if (convert == KMessageBox::Yes) {
726 font.setPointSize(textProperties.namedItem("font-size").nodeValue().toInt());
727 QDomElement content = items.at(j).namedItem("content").toElement();
728 content.setAttribute("font-pixel-size", QFontInfo(font).pixelSize());
729 content.removeAttribute("font-size");
730 kproducer.setAttribute("xmldata", data.toString());
732 * You may be tempted to delete the preview file
733 * to force its recreation: bad idea (see
734 * http://www.kdenlive.org/mantis/view.php?id=749)
744 // Fill the <documentproperties /> element
745 QDomElement docProperties = infoXml.firstChildElement("documentproperties");
746 if (docProperties.isNull()) {
747 docProperties = m_doc.createElement("documentproperties");
748 docProperties.setAttribute("zonein", infoXml.attribute("zonein"));
749 docProperties.setAttribute("zoneout", infoXml.attribute("zoneout"));
750 docProperties.setAttribute("zoom", infoXml.attribute("zoom"));
751 docProperties.setAttribute("position", infoXml.attribute("position"));
752 infoXml.appendChild(docProperties);
756 if (version <= 0.84) {
757 // update the title clips to use the new MLT kdenlivetitle producer
758 QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
759 for (int i = 0; i < kproducerNodes.count(); ++i) {
760 QDomElement kproducer = kproducerNodes.at(i).toElement();
761 if (kproducer.attribute("type").toInt() == TEXT) {
762 QString data = kproducer.attribute("xmldata");
763 QString datafile = kproducer.attribute("resource");
764 if (!datafile.endsWith(".kdenlivetitle")) {
765 datafile = QString();
766 kproducer.setAttribute("resource", QString());
768 QString id = kproducer.attribute("id");
769 QDomNodeList mltproducers = m_doc.elementsByTagName("producer");
770 bool foundData = false;
771 bool foundResource = false;
772 bool foundService = false;
773 for (int j = 0; j < mltproducers.count(); j++) {
774 QDomElement wproducer = mltproducers.at(j).toElement();
775 if (wproducer.attribute("id") == id) {
776 QDomNodeList props = wproducer.childNodes();
777 for (int k = 0; k < props.count(); k++) {
778 if (props.at(k).toElement().attribute("name") == "xmldata") {
779 props.at(k).firstChild().setNodeValue(data);
781 } else if (props.at(k).toElement().attribute("name") == "mlt_service") {
782 props.at(k).firstChild().setNodeValue("kdenlivetitle");
784 } else if (props.at(k).toElement().attribute("name") == "resource") {
785 props.at(k).firstChild().setNodeValue(datafile);
786 foundResource = true;
790 QDomElement e = m_doc.createElement("property");
791 e.setAttribute("name", "xmldata");
792 QDomText value = m_doc.createTextNode(data);
793 e.appendChild(value);
794 wproducer.appendChild(e);
797 QDomElement e = m_doc.createElement("property");
798 e.setAttribute("name", "mlt_service");
799 QDomText value = m_doc.createTextNode("kdenlivetitle");
800 e.appendChild(value);
801 wproducer.appendChild(e);
803 if (!foundResource) {
804 QDomElement e = m_doc.createElement("property");
805 e.setAttribute("name", "resource");
806 QDomText value = m_doc.createTextNode(datafile);
807 e.appendChild(value);
808 wproducer.appendChild(e);
816 if (version <= 0.85) {
817 // update the LADSPA effects to use the new ladspa.id format instead of external xml file
818 QDomNodeList effectNodes = m_doc.elementsByTagName("filter");
819 for (int i = 0; i < effectNodes.count(); ++i) {
820 QDomElement effect = effectNodes.at(i).toElement();
821 if (EffectsList::property(effect, "mlt_service") == "ladspa") {
822 // Needs to be converted
823 QStringList info = getInfoFromEffectName(EffectsList::property(effect, "kdenlive_id"));
824 if (info.isEmpty()) continue;
825 // info contains the correct ladspa.id from kdenlive effect name, and a list of parameter's old and new names
826 EffectsList::setProperty(effect, "kdenlive_id", info.at(0));
827 EffectsList::setProperty(effect, "tag", info.at(0));
828 EffectsList::setProperty(effect, "mlt_service", info.at(0));
829 EffectsList::removeProperty(effect, "src");
830 for (int j = 1; j < info.size(); j++) {
831 QString value = EffectsList::property(effect, info.at(j).section('=', 0, 0));
832 if (!value.isEmpty()) {
833 // update parameter name
834 EffectsList::renameProperty(effect, info.at(j).section('=', 0, 0), info.at(j).section('=', 1, 1));
841 if (version <= 0.86) {
842 // Make sure we don't have avformat-novalidate producers, since it caused crashes
843 QDomNodeList producers = m_doc.elementsByTagName("producer");
844 int max = producers.count();
845 for (int i = 0; i < max; i++) {
846 QDomElement prod = producers.at(i).toElement();
847 if (EffectsList::property(prod, "mlt_service") == "avformat-novalidate")
848 EffectsList::setProperty(prod, "mlt_service", "avformat");
852 // The document has been converted: mark it as modified
853 infoXml.setAttribute("version", currentVersion);
858 QStringList DocumentValidator::getInfoFromEffectName(const QString oldName)
861 // Returns a list to convert old Kdenlive ladspa effects
862 if (oldName == "pitch_shift") {
863 info << "ladspa.1433";
866 else if (oldName == "vinyl") {
867 info << "ladspa.1905";
874 else if (oldName == "room_reverb") {
875 info << "ladspa.1216";
880 else if (oldName == "reverb") {
881 info << "ladspa.1423";
885 else if (oldName == "rate_scale") {
886 info << "ladspa.1417";
889 else if (oldName == "pitch_scale") {
890 info << "ladspa.1193";
893 else if (oldName == "phaser") {
894 info << "ladspa.1217";
897 info << "feedback=2";
900 else if (oldName == "limiter") {
901 info << "ladspa.1913";
906 else if (oldName == "equalizer_15") {
907 info << "ladspa.1197";
924 else if (oldName == "equalizer") {
925 info << "ladspa.1901";
930 else if (oldName == "declipper") {
931 info << "ladspa.1195";
936 QString DocumentValidator::colorToString(const QColor& c)
938 QString ret = "%1,%2,%3,%4";
939 ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
943 bool DocumentValidator::isProject() const
945 QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
946 return !infoXmlNode.isNull();
949 bool DocumentValidator::isModified() const