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 #include <mlt++/Mlt.h>
35 DocumentValidator::DocumentValidator(QDomDocument doc):
40 bool DocumentValidator::validate(const double currentVersion)
42 QDomElement mlt = m_doc.firstChildElement("mlt");
43 // At least the root element must be there
47 QDomElement kdenliveDoc = mlt.firstChildElement("kdenlivedoc");
48 // Check if we're validating a Kdenlive project
49 if (kdenliveDoc.isNull())
51 // Upgrade the document to the latest version
52 if (!upgrade(kdenliveDoc.attribute("version").toDouble(), currentVersion))
56 * Check the syntax (this will be replaced by XSD validation with Qt 4.6)
57 * and correct some errors
60 // Return (or create) the tractor
61 QDomElement tractor = mlt.firstChildElement("tractor");
62 if (tractor.isNull()) {
64 tractor = m_doc.createElement("tractor");
65 tractor.setAttribute("global_feed", "1");
66 tractor.setAttribute("in", "0");
67 tractor.setAttribute("out", "-1");
68 tractor.setAttribute("id", "maintractor");
69 mlt.appendChild(tractor);
73 * Make sure at least one track exists, and they're equal in number to
74 * to the maximum between MLT and Kdenlive playlists and tracks
76 QDomNodeList playlists = m_doc.elementsByTagName("playlist");
77 int tracksMax = playlists.count() - 1; // Remove the black track
78 QDomNodeList tracks = tractor.elementsByTagName("track");
79 tracksMax = qMax(tracks.count() - 1, tracksMax);
80 QDomNodeList tracksinfo = kdenliveDoc.elementsByTagName("trackinfo");
81 tracksMax = qMax(tracksinfo.count(), tracksMax);
82 tracksMax = qMax(1, tracksMax); // Force existance of one track
83 if (playlists.count() - 1 < tracksMax ||
84 tracks.count() - 1 < tracksMax ||
85 tracksinfo.count() < tracksMax) {
86 kDebug() << "//// WARNING, PROJECT IS CORRUPTED, MISSING TRACK";
89 // use the MLT tracks as reference
90 if (tracks.count() - 1 < tracksMax) {
91 // Looks like one MLT track is missing, remove the extra Kdenlive track if there is one.
92 if (tracksinfo.count() != tracks.count() - 1) {
93 // The Kdenlive tracks are not ok, clear and rebuild them
94 QDomNode tinfo = kdenliveDoc.elementsByTagName("tracksinfo").at(0);
95 QDomNode tnode = tinfo.firstChild();
96 while (!tnode.isNull()) {
97 tinfo.removeChild(tnode);
98 tnode = tinfo.firstChild();
101 for (int i = 1; i < tracks.count(); i++) {
102 QString hide = tracks.at(i).toElement().attribute("hide");
103 QDomElement newTrack = m_doc.createElement("trackinfo");
104 if (hide == "video") {
106 newTrack.setAttribute("type", "audio");
107 newTrack.setAttribute("blind", 1);
108 newTrack.setAttribute("mute", 0);
109 newTrack.setAttribute("lock", 0);
111 newTrack.setAttribute("blind", 0);
112 newTrack.setAttribute("mute", 0);
113 newTrack.setAttribute("lock", 0);
115 tinfo.appendChild(newTrack);
120 if (playlists.count() - 1 < tracksMax) {
121 difference = tracksMax - (playlists.count() - 1);
122 for (int i = 0; i < difference; ++i) {
123 QDomElement playlist = m_doc.createElement("playlist");
124 mlt.appendChild(playlist);
127 if (tracks.count() - 1 < tracksMax) {
128 difference = tracksMax - (tracks.count() - 1);
129 for (int i = 0; i < difference; ++i) {
130 QDomElement track = m_doc.createElement("track");
131 tractor.appendChild(track);
134 if (tracksinfo.count() < tracksMax) {
135 QDomElement tracksinfoElm = kdenliveDoc.firstChildElement("tracksinfo");
136 if (tracksinfoElm.isNull()) {
137 tracksinfoElm = m_doc.createElement("tracksinfo");
138 kdenliveDoc.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
158 bool DocumentValidator::upgrade(double version, const double currentVersion)
160 kDebug() << "Opening a document with version " << version;
162 // No conversion needed
163 if (version == currentVersion) {
167 // The document is too new
168 if (version > currentVersion) {
169 kDebug() << "Unable to open document with version " << version;
170 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"));
174 // Unsupported document versions
175 if (version == 0.5 || version == 0.7) {
176 kDebug() << "Unable to open document with version " << version;
177 KMessageBox::sorry(kapp->activeWindow(), i18n("This project type is unsupported (version %1) and can't be loaded.", version), i18n("Unable to open project"));
182 QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
183 QDomElement infoXml = infoXmlNode.toElement();
184 infoXml.setAttribute("upgraded", "1");
186 if (version <= 0.6) {
187 QDomElement infoXml_old = infoXmlNode.cloneNode(true).toElement(); // Needed for folders
188 QDomNode westley = m_doc.elementsByTagName("westley").at(1);
189 QDomNode tractor = m_doc.elementsByTagName("tractor").at(0);
190 QDomNode multitrack = m_doc.elementsByTagName("multitrack").at(0);
191 QDomNodeList playlists = m_doc.elementsByTagName("playlist");
193 QDomNode props = m_doc.elementsByTagName("properties").at(0).toElement();
194 QString profile = props.toElement().attribute("videoprofile");
195 int startPos = props.toElement().attribute("timeline_position").toInt();
196 if (profile == "dv_wide")
197 profile = "dv_pal_wide";
199 // move playlists outside of tractor and add the tracks instead
200 int max = playlists.count();
201 if (westley.isNull()) {
202 westley = m_doc.createElement("westley");
203 m_doc.documentElement().appendChild(westley);
205 if (tractor.isNull()) {
206 kDebug() << "// NO MLT PLAYLIST, building empty one";
207 QDomElement blank_tractor = m_doc.createElement("tractor");
208 westley.appendChild(blank_tractor);
209 QDomElement blank_playlist = m_doc.createElement("playlist");
210 blank_playlist.setAttribute("id", "black_track");
211 westley.insertBefore(blank_playlist, QDomNode());
212 QDomElement blank_track = m_doc.createElement("track");
213 blank_track.setAttribute("producer", "black_track");
214 blank_tractor.appendChild(blank_track);
216 QDomNodeList kdenlivetracks = m_doc.elementsByTagName("kdenlivetrack");
217 for (int i = 0; i < kdenlivetracks.count(); i++) {
218 blank_playlist = m_doc.createElement("playlist");
219 blank_playlist.setAttribute("id", "playlist" + QString::number(i));
220 westley.insertBefore(blank_playlist, QDomNode());
221 blank_track = m_doc.createElement("track");
222 blank_track.setAttribute("producer", "playlist" + QString::number(i));
223 blank_tractor.appendChild(blank_track);
224 if (kdenlivetracks.at(i).toElement().attribute("cliptype") == "Sound") {
225 blank_playlist.setAttribute("hide", "video");
226 blank_track.setAttribute("hide", "video");
229 } else for (int i = 0; i < max; i++) {
230 QDomNode n = playlists.at(i);
231 westley.insertBefore(n, QDomNode());
232 QDomElement pl = n.toElement();
233 QDomElement track = m_doc.createElement("track");
234 QString trackType = pl.attribute("hide");
235 if (!trackType.isEmpty())
236 track.setAttribute("hide", trackType);
237 QString playlist_id = pl.attribute("id");
238 if (playlist_id.isEmpty()) {
239 playlist_id = "black_track";
240 pl.setAttribute("id", playlist_id);
242 track.setAttribute("producer", playlist_id);
243 //tractor.appendChild(track);
244 #define KEEP_TRACK_ORDER 1
245 #ifdef KEEP_TRACK_ORDER
246 tractor.insertAfter(track, QDomNode());
248 // Insert the new track in an order that hopefully matches the 3 video, then 2 audio tracks of Kdenlive 0.7.0
249 // insertion sort - O( tracks*tracks )
250 // Note, this breaks _all_ transitions - but you can move them up and down afterwards.
251 QDomElement tractor_elem = tractor.toElement();
252 if (! tractor_elem.isNull()) {
253 QDomNodeList tracks = tractor_elem.elementsByTagName("track");
254 int size = tracks.size();
256 tractor.insertAfter(track, QDomNode());
258 bool inserted = false;
259 for (int i = 0; i < size; ++i) {
260 QDomElement track_elem = tracks.at(i).toElement();
261 if (track_elem.isNull()) {
262 tractor.insertAfter(track, QDomNode());
266 kDebug() << "playlist_id: " << playlist_id << " producer:" << track_elem.attribute("producer");
267 if (playlist_id < track_elem.attribute("producer")) {
268 tractor.insertBefore(track, track_elem);
274 // Reach here, no insertion, insert last
276 tractor.insertAfter(track, QDomNode());
280 kWarning() << "tractor was not a QDomElement";
281 tractor.insertAfter(track, QDomNode());
285 tractor.removeChild(multitrack);
287 // audio track mixing transitions should not be added to track view, so add required attribute
288 QDomNodeList transitions = m_doc.elementsByTagName("transition");
289 max = transitions.count();
290 for (int i = 0; i < max; i++) {
291 QDomElement tr = transitions.at(i).toElement();
292 if (tr.attribute("combine") == "1" && tr.attribute("mlt_service") == "mix") {
293 QDomElement property = m_doc.createElement("property");
294 property.setAttribute("name", "internal_added");
295 QDomText value = m_doc.createTextNode("237");
296 property.appendChild(value);
297 tr.appendChild(property);
298 property = m_doc.createElement("property");
299 property.setAttribute("name", "mlt_service");
300 value = m_doc.createTextNode("mix");
301 property.appendChild(value);
302 tr.appendChild(property);
304 // convert transition
305 QDomNamedNodeMap attrs = tr.attributes();
306 for (int j = 0; j < attrs.count(); j++) {
307 QString attrName = attrs.item(j).nodeName();
308 if (attrName != "in" && attrName != "out" && attrName != "id") {
309 QDomElement property = m_doc.createElement("property");
310 property.setAttribute("name", attrName);
311 QDomText value = m_doc.createTextNode(attrs.item(j).nodeValue());
312 property.appendChild(value);
313 tr.appendChild(property);
319 // move transitions after tracks
320 for (int i = 0; i < max; i++) {
321 tractor.insertAfter(transitions.at(0), QDomNode());
324 // Fix filters format
325 QDomNodeList entries = m_doc.elementsByTagName("entry");
326 max = entries.count();
327 for (int i = 0; i < max; i++) {
330 QDomNode m = entries.at(i).firstChild();
331 while (!m.isNull()) {
332 if (m.toElement().tagName() == "filter") {
333 QDomElement filt = m.toElement();
334 QDomNamedNodeMap attrs = filt.attributes();
335 QString current_id = filt.attribute("kdenlive_id");
336 if (current_id != last_id) {
338 last_id = current_id;
340 QDomElement e = m_doc.createElement("property");
341 e.setAttribute("name", "kdenlive_ix");
342 QDomText value = m_doc.createTextNode(QString::number(effectix));
343 e.appendChild(value);
345 for (int j = 0; j < attrs.count(); j++) {
346 QDomAttr a = attrs.item(j).toAttr();
348 kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
349 QDomElement e = m_doc.createElement("property");
350 e.setAttribute("name", a.name());
351 QDomText value = m_doc.createTextNode(a.value());
352 e.appendChild(value);
363 QDomNodeList filters = m_doc.elementsByTagName("filter");
364 max = filters.count();
367 for (int i = 0; i < max; i++) {
368 QDomElement filt = filters.at(i).toElement();
369 QDomNamedNodeMap attrs = filt.attributes();
370 QString current_id = filt.attribute("kdenlive_id");
371 if (current_id != last_id) {
373 last_id = current_id;
375 QDomElement e = m_doc.createElement("property");
376 e.setAttribute("name", "kdenlive_ix");
377 QDomText value = m_doc.createTextNode(QString::number(1));
378 e.appendChild(value);
380 for (int j = 0; j < attrs.count(); j++) {
381 QDomAttr a = attrs.item(j).toAttr();
383 kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
384 QDomElement e = m_doc.createElement("property");
385 e.setAttribute("name", a.name());
386 QDomText value = m_doc.createTextNode(a.value());
387 e.appendChild(value);
394 QDomNodeList producers = westley.toElement().elementsByTagName("producer");
395 max = producers.count();
396 for (int i = 0; i < max; i++) {
397 QDomElement prod = producers.at(i).toElement();
398 if (prod.attribute("mlt_service") == "framebuffer") {
399 QString slowmotionprod = prod.attribute("resource");
400 slowmotionprod.replace(':', '?');
401 kDebug() << "// FOUND WRONG SLOWMO, new: " << slowmotionprod;
402 prod.setAttribute("resource", slowmotionprod);
405 // move producers to correct place, markers to a global list, fix clip descriptions
406 QDomElement markers = m_doc.createElement("markers");
407 // This will get the xml producers:
408 producers = m_doc.elementsByTagName("producer");
409 max = producers.count();
410 for (int i = 0; i < max; i++) {
411 QDomElement prod = producers.at(0).toElement();
412 // add resource also as a property (to allow path correction in setNewResource())
413 // TODO: will it work with slowmotion? needs testing
414 /*if (!prod.attribute("resource").isEmpty()) {
415 QDomElement prop_resource = m_doc.createElement("property");
416 prop_resource.setAttribute("name", "resource");
417 QDomText resource = m_doc.createTextNode(prod.attribute("resource"));
418 prop_resource.appendChild(resource);
419 prod.appendChild(prop_resource);
421 QDomNode m = prod.firstChild();
423 if (m.toElement().tagName() == "markers") {
424 QDomNodeList prodchilds = m.childNodes();
425 int maxchild = prodchilds.count();
426 for (int k = 0; k < maxchild; k++) {
427 QDomElement mark = prodchilds.at(0).toElement();
428 mark.setAttribute("id", prod.attribute("id"));
429 markers.insertAfter(mark, QDomNode());
432 } else if (prod.attribute("type").toInt() == TEXT) {
433 // convert title clip
434 if (m.toElement().tagName() == "textclip") {
436 QDomElement titleclip = m.toElement();
437 QDomElement title = tdoc.createElement("kdenlivetitle");
438 tdoc.appendChild(title);
439 QDomNodeList objects = titleclip.childNodes();
440 int maxchild = objects.count();
441 for (int k = 0; k < maxchild; k++) {
443 QDomElement ob = objects.at(k).toElement();
444 if (ob.attribute("type") == "3") {
445 // text object - all of this goes into "xmldata"...
446 QDomElement item = tdoc.createElement("item");
447 item.setAttribute("z-index", ob.attribute("z"));
448 item.setAttribute("type", "QGraphicsTextItem");
449 QDomElement position = tdoc.createElement("position");
450 position.setAttribute("x", ob.attribute("x"));
451 position.setAttribute("y", ob.attribute("y"));
452 QDomElement content = tdoc.createElement("content");
453 content.setAttribute("font", ob.attribute("font_family"));
454 content.setAttribute("font-size", ob.attribute("font_size"));
455 content.setAttribute("font-bold", ob.attribute("bold"));
456 content.setAttribute("font-italic", ob.attribute("italic"));
457 content.setAttribute("font-underline", ob.attribute("underline"));
458 QString col = ob.attribute("color");
460 content.setAttribute("font-color", colorToString(c));
461 // todo: These fields are missing from the newly generated xmldata:
462 // transform, startviewport, endviewport, background
464 QDomText conttxt = tdoc.createTextNode(ob.attribute("text"));
465 content.appendChild(conttxt);
466 item.appendChild(position);
467 item.appendChild(content);
468 title.appendChild(item);
469 } else if (ob.attribute("type") == "5") {
471 QDomElement item = tdoc.createElement("item");
472 item.setAttribute("z-index", ob.attribute("z"));
473 item.setAttribute("type", "QGraphicsRectItem");
474 QDomElement position = tdoc.createElement("position");
475 position.setAttribute("x", ob.attribute("x"));
476 position.setAttribute("y", ob.attribute("y"));
477 QDomElement content = tdoc.createElement("content");
478 QString col = ob.attribute("color");
480 content.setAttribute("brushcolor", colorToString(c));
481 QString rect = "0,0,";
482 rect.append(ob.attribute("width"));
484 rect.append(ob.attribute("height"));
485 content.setAttribute("rect", rect);
486 item.appendChild(position);
487 item.appendChild(content);
488 title.appendChild(item);
491 prod.setAttribute("xmldata", tdoc.toString());
492 // mbd todo: This clearly does not work, as every title gets the same name - trying to leave it empty
493 // QStringList titleInfo = TitleWidget::getFreeTitleInfo(projectFolder());
494 // prod.setAttribute("titlename", titleInfo.at(0));
495 // prod.setAttribute("resource", titleInfo.at(1));
496 //kDebug()<<"TITLE DATA:\n"<<tdoc.toString();
498 } // End conversion of title clips.
500 } else if (m.isText()) {
501 QString comment = m.nodeValue();
502 if (!comment.isEmpty()) {
503 prod.setAttribute("description", comment);
508 int duration = prod.attribute("duration").toInt();
509 if (duration > 0) prod.setAttribute("out", QString::number(duration));
510 // The clip goes back in, but text clips should not go back in, at least not modified
511 westley.insertBefore(prod, QDomNode());
514 QDomNode westley0 = m_doc.elementsByTagName("westley").at(0);
515 if (!markers.firstChild().isNull()) westley0.appendChild(markers);
518 * Convert as much of the kdenlivedoc as possible. Use the producer in
519 * westley. First, remove the old stuff from westley, and add a new
520 * empty one. Also, track the max id in order to use it for the adding
523 int max_kproducer_id = 0;
524 westley0.removeChild(infoXmlNode);
525 QDomElement infoXml_new = m_doc.createElement("kdenlivedoc");
526 infoXml_new.setAttribute("profile", profile);
527 infoXml.setAttribute("position", startPos);
529 // Add all the producers that has a resource in westley
530 QDomElement westley_element = westley0.toElement();
531 if (westley_element.isNull()) {
532 kWarning() << "westley0 element in document was not a QDomElement - unable to add producers to new kdenlivedoc";
534 QDomNodeList wproducers = westley_element.elementsByTagName("producer");
535 int kmax = wproducers.count();
536 for (int i = 0; i < kmax; i++) {
537 QDomElement wproducer = wproducers.at(i).toElement();
538 if (wproducer.isNull()) {
539 kWarning() << "Found producer in westley0, that was not a QDomElement";
542 if (wproducer.attribute("id") == "black") continue;
543 // We have to do slightly different things, depending on the type
544 kDebug() << "Converting producer element with type" << wproducer.attribute("type");
545 if (wproducer.attribute("type").toInt() == TEXT) {
546 kDebug() << "Found TEXT element in producer" << endl;
547 QDomElement kproducer = wproducer.cloneNode(true).toElement();
548 kproducer.setTagName("kdenlive_producer");
549 infoXml_new.appendChild(kproducer);
551 * TODO: Perhaps needs some more changes here to
552 * "frequency", aspect ratio as a float, frame_size,
553 * channels, and later, resource and title name
556 QDomElement kproducer = m_doc.createElement("kdenlive_producer");
557 kproducer.setAttribute("id", wproducer.attribute("id"));
558 if (!wproducer.attribute("description").isEmpty())
559 kproducer.setAttribute("description", wproducer.attribute("description"));
560 kproducer.setAttribute("resource", wproducer.attribute("resource"));
561 kproducer.setAttribute("type", wproducer.attribute("type"));
562 // Testing fix for 358
563 if (!wproducer.attribute("aspect_ratio").isEmpty()) {
564 kproducer.setAttribute("aspect_ratio", wproducer.attribute("aspect_ratio"));
566 if (!wproducer.attribute("source_fps").isEmpty()) {
567 kproducer.setAttribute("fps", wproducer.attribute("source_fps"));
569 if (!wproducer.attribute("length").isEmpty()) {
570 kproducer.setAttribute("duration", wproducer.attribute("length"));
572 infoXml_new.appendChild(kproducer);
574 if (wproducer.attribute("id").toInt() > max_kproducer_id) {
575 max_kproducer_id = wproducer.attribute("id").toInt();
579 #define LOOKUP_FOLDER 1
582 * Look through all the folder elements of the old doc, for each folder,
583 * for each producer, get the id, look it up in the new doc, set the
584 * groupname and groupid. Note, this does not work at the moment - at
585 * least one folder shows up missing, and clips with no folder does not
588 //QDomElement infoXml_old = infoXmlNode.toElement();
589 if (!infoXml_old.isNull()) {
590 QDomNodeList folders = infoXml_old.elementsByTagName("folder");
591 int fsize = folders.size();
592 int groupId = max_kproducer_id + 1; // Start at +1 of max id of the kdenlive_producers
593 for (int i = 0; i < fsize; ++i) {
594 QDomElement folder = folders.at(i).toElement();
595 if (!folder.isNull()) {
596 QString groupName = folder.attribute("name");
597 kDebug() << "groupName: " << groupName << " with groupId: " << groupId;
598 QDomNodeList fproducers = folder.elementsByTagName("producer");
599 int psize = fproducers.size();
600 for (int j = 0; j < psize; ++j) {
601 QDomElement fproducer = fproducers.at(j).toElement();
602 if (!fproducer.isNull()) {
603 QString id = fproducer.attribute("id");
604 // This is not very effective, but compared to loading the clips, its a breeze
605 QDomNodeList kdenlive_producers = infoXml_new.elementsByTagName("kdenlive_producer");
606 int kpsize = kdenlive_producers.size();
607 for (int k = 0; k < kpsize; ++k) {
608 QDomElement kproducer = kdenlive_producers.at(k).toElement(); // Its an element for sure
609 if (id == kproducer.attribute("id")) {
610 // We do not check that it already is part of a folder
611 kproducer.setAttribute("groupid", groupId);
612 kproducer.setAttribute("groupname", groupName);
623 QDomNodeList elements = westley.childNodes();
624 max = elements.count();
625 for (int i = 0; i < max; i++) {
626 QDomElement prod = elements.at(0).toElement();
627 westley0.insertAfter(prod, QDomNode());
630 westley0.appendChild(infoXml_new);
632 westley0.removeChild(westley);
634 // adds <avfile /> information to <kdenlive_producer />
635 QDomNodeList kproducers = m_doc.elementsByTagName("kdenlive_producer");
636 QDomNodeList avfiles = infoXml_old.elementsByTagName("avfile");
637 kDebug() << "found" << avfiles.count() << "<avfile />s and" << kproducers.count() << "<kdenlive_producer />s";
638 for (int i = 0; i < avfiles.count(); ++i) {
639 QDomElement avfile = avfiles.at(i).toElement();
640 QDomElement kproducer;
642 kWarning() << "found an <avfile /> that is not a QDomElement";
644 QString id = avfile.attribute("id");
645 // this is horrible, must be rewritten, it's just for test
646 for (int j = 0; j < kproducers.count(); ++j) {
647 //kDebug() << "checking <kdenlive_producer /> with id" << kproducers.at(j).toElement().attribute("id");
648 if (kproducers.at(j).toElement().attribute("id") == id) {
649 kproducer = kproducers.at(j).toElement();
653 if (kproducer == QDomElement())
654 kWarning() << "no match for <avfile /> with id =" << id;
656 //kDebug() << "ready to set additional <avfile />'s attributes (id =" << id << ")";
657 kproducer.setAttribute("channels", avfile.attribute("channels"));
658 kproducer.setAttribute("duration", avfile.attribute("duration"));
659 kproducer.setAttribute("frame_size", avfile.attribute("width") + 'x' + avfile.attribute("height"));
660 kproducer.setAttribute("frequency", avfile.attribute("frequency"));
661 if (kproducer.attribute("description").isEmpty() && !avfile.attribute("description").isEmpty())
662 kproducer.setAttribute("description", avfile.attribute("description"));
666 infoXml = infoXml_new;
669 if (version <= 0.81) {
670 // Add the tracks information
671 QString tracksOrder = infoXml.attribute("tracks");
672 if (tracksOrder.isEmpty()) {
673 QDomNodeList tracks = m_doc.elementsByTagName("track");
674 for (int i = 0; i < tracks.count(); i++) {
675 QDomElement track = tracks.at(i).toElement();
676 if (track.attribute("producer") != "black_track") {
677 if (track.attribute("hide") == "video")
678 tracksOrder.append('a');
680 tracksOrder.append('v');
684 QDomElement tracksinfo = m_doc.createElement("tracksinfo");
685 for (int i = 0; i < tracksOrder.size(); i++) {
686 QDomElement trackinfo = m_doc.createElement("trackinfo");
687 if (tracksOrder.data()[i] == 'a') {
688 trackinfo.setAttribute("type", "audio");
689 trackinfo.setAttribute("blind", true);
691 trackinfo.setAttribute("blind", false);
692 trackinfo.setAttribute("mute", false);
693 trackinfo.setAttribute("locked", false);
694 tracksinfo.appendChild(trackinfo);
696 infoXml.appendChild(tracksinfo);
699 if (version <= 0.82) {
700 // Convert <westley />s in <mlt />s (MLT extreme makeover)
701 QDomNodeList westleyNodes = m_doc.elementsByTagName("westley");
702 for (int i = 0; i < westleyNodes.count(); i++) {
703 QDomElement westley = westleyNodes.at(i).toElement();
704 westley.setTagName("mlt");
708 if (version <= 0.83) {
709 // Replace point size with pixel size in text titles
710 if (m_doc.toString().contains("font-size")) {
711 KMessageBox::ButtonCode convert = KMessageBox::Continue;
712 QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
713 for (int i = 0; i < kproducerNodes.count() && convert != KMessageBox::No; ++i) {
714 QDomElement kproducer = kproducerNodes.at(i).toElement();
715 if (kproducer.attribute("type").toInt() == TEXT) {
717 data.setContent(kproducer.attribute("xmldata"));
718 QDomNodeList items = data.firstChild().childNodes();
719 for (int j = 0; j < items.count() && convert != KMessageBox::No; ++j) {
720 if (items.at(j).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
721 QDomNamedNodeMap textProperties = items.at(j).namedItem("content").attributes();
722 if (textProperties.namedItem("font-pixel-size").isNull() && !textProperties.namedItem("font-size").isNull()) {
723 // Ask the user if he wants to convert
724 if (convert != KMessageBox::Yes && convert != KMessageBox::No)
725 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"));
726 if (convert == KMessageBox::Yes) {
728 font.setPointSize(textProperties.namedItem("font-size").nodeValue().toInt());
729 QDomElement content = items.at(j).namedItem("content").toElement();
730 content.setAttribute("font-pixel-size", QFontInfo(font).pixelSize());
731 content.removeAttribute("font-size");
732 kproducer.setAttribute("xmldata", data.toString());
734 * You may be tempted to delete the preview file
735 * to force its recreation: bad idea (see
736 * http://www.kdenlive.org/mantis/view.php?id=749)
746 // Fill the <documentproperties /> element
747 QDomElement docProperties = infoXml.firstChildElement("documentproperties");
748 if (docProperties.isNull()) {
749 docProperties = m_doc.createElement("documentproperties");
750 docProperties.setAttribute("zonein", infoXml.attribute("zonein"));
751 docProperties.setAttribute("zoneout", infoXml.attribute("zoneout"));
752 docProperties.setAttribute("zoom", infoXml.attribute("zoom"));
753 docProperties.setAttribute("position", infoXml.attribute("position"));
754 infoXml.appendChild(docProperties);
758 if (version <= 0.84) {
759 // update the title clips to use the new MLT kdenlivetitle producer
760 QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
761 for (int i = 0; i < kproducerNodes.count(); ++i) {
762 QDomElement kproducer = kproducerNodes.at(i).toElement();
763 if (kproducer.attribute("type").toInt() == TEXT) {
764 QString data = kproducer.attribute("xmldata");
765 QString datafile = kproducer.attribute("resource");
766 if (!datafile.endsWith(".kdenlivetitle")) {
767 datafile = QString();
768 kproducer.setAttribute("resource", QString());
770 QString id = kproducer.attribute("id");
771 QDomNodeList mltproducers = m_doc.elementsByTagName("producer");
772 bool foundData = false;
773 bool foundResource = false;
774 bool foundService = false;
775 for (int j = 0; j < mltproducers.count(); j++) {
776 QDomElement wproducer = mltproducers.at(j).toElement();
777 if (wproducer.attribute("id") == id) {
778 QDomNodeList props = wproducer.childNodes();
779 for (int k = 0; k < props.count(); k++) {
780 if (props.at(k).toElement().attribute("name") == "xmldata") {
781 props.at(k).firstChild().setNodeValue(data);
783 } else if (props.at(k).toElement().attribute("name") == "mlt_service") {
784 props.at(k).firstChild().setNodeValue("kdenlivetitle");
786 } else if (props.at(k).toElement().attribute("name") == "resource") {
787 props.at(k).firstChild().setNodeValue(datafile);
788 foundResource = true;
792 QDomElement e = m_doc.createElement("property");
793 e.setAttribute("name", "xmldata");
794 QDomText value = m_doc.createTextNode(data);
795 e.appendChild(value);
796 wproducer.appendChild(e);
799 QDomElement e = m_doc.createElement("property");
800 e.setAttribute("name", "mlt_service");
801 QDomText value = m_doc.createTextNode("kdenlivetitle");
802 e.appendChild(value);
803 wproducer.appendChild(e);
805 if (!foundResource) {
806 QDomElement e = m_doc.createElement("property");
807 e.setAttribute("name", "resource");
808 QDomText value = m_doc.createTextNode(datafile);
809 e.appendChild(value);
810 wproducer.appendChild(e);
818 if (version <= 0.85) {
819 // update the LADSPA effects to use the new ladspa.id format instead of external xml file
820 QDomNodeList effectNodes = m_doc.elementsByTagName("filter");
821 for (int i = 0; i < effectNodes.count(); ++i) {
822 QDomElement effect = effectNodes.at(i).toElement();
823 if (EffectsList::property(effect, "mlt_service") == "ladspa") {
824 // Needs to be converted
825 QStringList info = getInfoFromEffectName(EffectsList::property(effect, "kdenlive_id"));
826 if (info.isEmpty()) continue;
827 // info contains the correct ladspa.id from kdenlive effect name, and a list of parameter's old and new names
828 EffectsList::setProperty(effect, "kdenlive_id", info.at(0));
829 EffectsList::setProperty(effect, "tag", info.at(0));
830 EffectsList::setProperty(effect, "mlt_service", info.at(0));
831 EffectsList::removeProperty(effect, "src");
832 for (int j = 1; j < info.size(); j++) {
833 QString value = EffectsList::property(effect, info.at(j).section('=', 0, 0));
834 if (!value.isEmpty()) {
835 // update parameter name
836 EffectsList::renameProperty(effect, info.at(j).section('=', 0, 0), info.at(j).section('=', 1, 1));
843 if (version <= 0.86) {
844 // Make sure we don't have avformat-novalidate producers, since it caused crashes
845 QDomNodeList producers = m_doc.elementsByTagName("producer");
846 int max = producers.count();
847 for (int i = 0; i < max; i++) {
848 QDomElement prod = producers.at(i).toElement();
849 if (EffectsList::property(prod, "mlt_service") == "avformat-novalidate")
850 EffectsList::setProperty(prod, "mlt_service", "avformat");
853 // There was a mistake in Geometry transitions where the last keyframe was created one frame after the end of transition, so fix it and move last keyframe to real end of transition
855 // Get profile info (width / height)
858 QDomElement profile = m_doc.firstChildElement("profile");
859 if (profile.isNull()) profile = infoXml.firstChildElement("profileinfo");
860 if (profile.isNull()) {
861 // could not find profile info, set PAL
866 profileWidth = profile.attribute("width").toInt();
867 profileHeight = profile.attribute("height").toInt();
869 QDomNodeList transitions = m_doc.elementsByTagName("transition");
870 max = transitions.count();
872 for (int i = 0; i < max; i++) {
873 QDomElement trans = transitions.at(i).toElement();
874 out = trans.attribute("out").toInt() - trans.attribute("in").toInt();
875 QString geom = EffectsList::property(trans, "geometry");
876 Mlt::Geometry *g = new Mlt::Geometry(geom.toUtf8().data(), out, profileWidth, profileHeight);
877 Mlt::GeometryItem item;
878 if (g->next_key(&item, out) == 0) {
879 // We have a keyframe just after last frame, try to move it to last frame
880 if (item.frame() == out + 1) {
884 EffectsList::setProperty(trans, "geometry", g->serialise());
891 // The document has been converted: mark it as modified
892 infoXml.setAttribute("version", currentVersion);
897 QStringList DocumentValidator::getInfoFromEffectName(const QString oldName)
900 // Returns a list to convert old Kdenlive ladspa effects
901 if (oldName == "pitch_shift") {
902 info << "ladspa.1433";
905 else if (oldName == "vinyl") {
906 info << "ladspa.1905";
913 else if (oldName == "room_reverb") {
914 info << "ladspa.1216";
919 else if (oldName == "reverb") {
920 info << "ladspa.1423";
924 else if (oldName == "rate_scale") {
925 info << "ladspa.1417";
928 else if (oldName == "pitch_scale") {
929 info << "ladspa.1193";
932 else if (oldName == "phaser") {
933 info << "ladspa.1217";
936 info << "feedback=2";
939 else if (oldName == "limiter") {
940 info << "ladspa.1913";
945 else if (oldName == "equalizer_15") {
946 info << "ladspa.1197";
963 else if (oldName == "equalizer") {
964 info << "ladspa.1901";
969 else if (oldName == "declipper") {
970 info << "ladspa.1195";
975 QString DocumentValidator::colorToString(const QColor& c)
977 QString ret = "%1,%2,%3,%4";
978 ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
982 bool DocumentValidator::isProject() const
984 QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
985 return !infoXmlNode.isNull();
988 bool DocumentValidator::isModified() const