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())
52 // Previous MLT / Kdenlive versions used C locale by default
53 QLocale documentLocale("C");
55 if (mlt.hasAttribute("LC_NUMERIC")) {
56 // Set locale for the document
57 documentLocale = QLocale(mlt.attribute("LC_NUMERIC"));
58 if (documentLocale.decimalPoint() != QLocale().decimalPoint()) {
59 QDomElement docProperties = kdenliveDoc.firstChildElement("documentproperties");
60 if (docProperties.isNull()) {
61 docProperties = m_doc.createElement("documentproperties");
62 kdenliveDoc.appendChild(docProperties);
64 docProperties.setAttribute("readonly", 1);
65 KMessageBox::sorry(kapp->activeWindow(), i18n("The document you are opening uses a different locale (%1) than your system. You can only open and render it, no editing is supported unless you change your system's locale.", mlt.attribute("LC_NUMERIC")), i18n("Read only project"));
69 // Upgrade the document to the latest version
70 if (!upgrade(documentLocale.toDouble(kdenliveDoc.attribute("version")), currentVersion))
74 * Check the syntax (this will be replaced by XSD validation with Qt 4.6)
75 * and correct some errors
78 // Return (or create) the tractor
79 QDomElement tractor = mlt.firstChildElement("tractor");
80 if (tractor.isNull()) {
82 tractor = m_doc.createElement("tractor");
83 tractor.setAttribute("global_feed", "1");
84 tractor.setAttribute("in", "0");
85 tractor.setAttribute("out", "-1");
86 tractor.setAttribute("id", "maintractor");
87 mlt.appendChild(tractor);
91 * Make sure at least one track exists, and they're equal in number to
92 * to the maximum between MLT and Kdenlive playlists and tracks
94 QDomNodeList playlists = m_doc.elementsByTagName("playlist");
95 int tracksMax = playlists.count() - 1; // Remove the black track
96 QDomNodeList tracks = tractor.elementsByTagName("track");
97 tracksMax = qMax(tracks.count() - 1, tracksMax);
98 QDomNodeList tracksinfo = kdenliveDoc.elementsByTagName("trackinfo");
99 tracksMax = qMax(tracksinfo.count(), tracksMax);
100 tracksMax = qMax(1, tracksMax); // Force existance of one track
101 if (playlists.count() - 1 < tracksMax ||
102 tracks.count() - 1 < tracksMax ||
103 tracksinfo.count() < tracksMax) {
104 kDebug() << "//// WARNING, PROJECT IS CORRUPTED, MISSING TRACK";
107 // use the MLT tracks as reference
108 if (tracks.count() - 1 < tracksMax) {
109 // Looks like one MLT track is missing, remove the extra Kdenlive track if there is one.
110 if (tracksinfo.count() != tracks.count() - 1) {
111 // The Kdenlive tracks are not ok, clear and rebuild them
112 QDomNode tinfo = kdenliveDoc.elementsByTagName("tracksinfo").at(0);
113 QDomNode tnode = tinfo.firstChild();
114 while (!tnode.isNull()) {
115 tinfo.removeChild(tnode);
116 tnode = tinfo.firstChild();
119 for (int i = 1; i < tracks.count(); i++) {
120 QString hide = tracks.at(i).toElement().attribute("hide");
121 QDomElement newTrack = m_doc.createElement("trackinfo");
122 if (hide == "video") {
124 newTrack.setAttribute("type", "audio");
125 newTrack.setAttribute("blind", 1);
126 newTrack.setAttribute("mute", 0);
127 newTrack.setAttribute("lock", 0);
129 newTrack.setAttribute("blind", 0);
130 newTrack.setAttribute("mute", 0);
131 newTrack.setAttribute("lock", 0);
133 tinfo.appendChild(newTrack);
138 if (playlists.count() - 1 < tracksMax) {
139 difference = tracksMax - (playlists.count() - 1);
140 for (int i = 0; i < difference; ++i) {
141 QDomElement playlist = m_doc.createElement("playlist");
142 mlt.appendChild(playlist);
145 if (tracks.count() - 1 < tracksMax) {
146 difference = tracksMax - (tracks.count() - 1);
147 for (int i = 0; i < difference; ++i) {
148 QDomElement track = m_doc.createElement("track");
149 tractor.appendChild(track);
152 if (tracksinfo.count() < tracksMax) {
153 QDomElement tracksinfoElm = kdenliveDoc.firstChildElement("tracksinfo");
154 if (tracksinfoElm.isNull()) {
155 tracksinfoElm = m_doc.createElement("tracksinfo");
156 kdenliveDoc.appendChild(tracksinfoElm);
158 difference = tracksMax - tracksinfo.count();
159 for (int i = 0; i < difference; ++i) {
160 QDomElement trackinfo = m_doc.createElement("trackinfo");
161 trackinfo.setAttribute("mute", "0");
162 trackinfo.setAttribute("locked", "0");
163 tracksinfoElm.appendChild(trackinfo);
168 // TODO: check the tracks references
169 // TODO: check internal mix transitions
176 bool DocumentValidator::upgrade(double version, const double currentVersion)
178 kDebug() << "Opening a document with version " << version;
180 // No conversion needed
181 if (version == currentVersion) {
185 // The document is too new
186 if (version > currentVersion) {
187 kDebug() << "Unable to open document with version " << version;
188 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"));
192 // Unsupported document versions
193 if (version == 0.5 || version == 0.7) {
194 kDebug() << "Unable to open document with version " << version;
195 KMessageBox::sorry(kapp->activeWindow(), i18n("This project type is unsupported (version %1) and can't be loaded.", version), i18n("Unable to open project"));
200 QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
201 QDomElement infoXml = infoXmlNode.toElement();
202 infoXml.setAttribute("upgraded", "1");
204 if (version <= 0.6) {
205 QDomElement infoXml_old = infoXmlNode.cloneNode(true).toElement(); // Needed for folders
206 QDomNode westley = m_doc.elementsByTagName("westley").at(1);
207 QDomNode tractor = m_doc.elementsByTagName("tractor").at(0);
208 QDomNode multitrack = m_doc.elementsByTagName("multitrack").at(0);
209 QDomNodeList playlists = m_doc.elementsByTagName("playlist");
211 QDomNode props = m_doc.elementsByTagName("properties").at(0).toElement();
212 QString profile = props.toElement().attribute("videoprofile");
213 int startPos = props.toElement().attribute("timeline_position").toInt();
214 if (profile == "dv_wide")
215 profile = "dv_pal_wide";
217 // move playlists outside of tractor and add the tracks instead
218 int max = playlists.count();
219 if (westley.isNull()) {
220 westley = m_doc.createElement("westley");
221 m_doc.documentElement().appendChild(westley);
223 if (tractor.isNull()) {
224 kDebug() << "// NO MLT PLAYLIST, building empty one";
225 QDomElement blank_tractor = m_doc.createElement("tractor");
226 westley.appendChild(blank_tractor);
227 QDomElement blank_playlist = m_doc.createElement("playlist");
228 blank_playlist.setAttribute("id", "black_track");
229 westley.insertBefore(blank_playlist, QDomNode());
230 QDomElement blank_track = m_doc.createElement("track");
231 blank_track.setAttribute("producer", "black_track");
232 blank_tractor.appendChild(blank_track);
234 QDomNodeList kdenlivetracks = m_doc.elementsByTagName("kdenlivetrack");
235 for (int i = 0; i < kdenlivetracks.count(); i++) {
236 blank_playlist = m_doc.createElement("playlist");
237 blank_playlist.setAttribute("id", "playlist" + QString::number(i));
238 westley.insertBefore(blank_playlist, QDomNode());
239 blank_track = m_doc.createElement("track");
240 blank_track.setAttribute("producer", "playlist" + QString::number(i));
241 blank_tractor.appendChild(blank_track);
242 if (kdenlivetracks.at(i).toElement().attribute("cliptype") == "Sound") {
243 blank_playlist.setAttribute("hide", "video");
244 blank_track.setAttribute("hide", "video");
247 } else for (int i = 0; i < max; i++) {
248 QDomNode n = playlists.at(i);
249 westley.insertBefore(n, QDomNode());
250 QDomElement pl = n.toElement();
251 QDomElement track = m_doc.createElement("track");
252 QString trackType = pl.attribute("hide");
253 if (!trackType.isEmpty())
254 track.setAttribute("hide", trackType);
255 QString playlist_id = pl.attribute("id");
256 if (playlist_id.isEmpty()) {
257 playlist_id = "black_track";
258 pl.setAttribute("id", playlist_id);
260 track.setAttribute("producer", playlist_id);
261 //tractor.appendChild(track);
262 #define KEEP_TRACK_ORDER 1
263 #ifdef KEEP_TRACK_ORDER
264 tractor.insertAfter(track, QDomNode());
266 // Insert the new track in an order that hopefully matches the 3 video, then 2 audio tracks of Kdenlive 0.7.0
267 // insertion sort - O( tracks*tracks )
268 // Note, this breaks _all_ transitions - but you can move them up and down afterwards.
269 QDomElement tractor_elem = tractor.toElement();
270 if (! tractor_elem.isNull()) {
271 QDomNodeList tracks = tractor_elem.elementsByTagName("track");
272 int size = tracks.size();
274 tractor.insertAfter(track, QDomNode());
276 bool inserted = false;
277 for (int i = 0; i < size; ++i) {
278 QDomElement track_elem = tracks.at(i).toElement();
279 if (track_elem.isNull()) {
280 tractor.insertAfter(track, QDomNode());
284 kDebug() << "playlist_id: " << playlist_id << " producer:" << track_elem.attribute("producer");
285 if (playlist_id < track_elem.attribute("producer")) {
286 tractor.insertBefore(track, track_elem);
292 // Reach here, no insertion, insert last
294 tractor.insertAfter(track, QDomNode());
298 kWarning() << "tractor was not a QDomElement";
299 tractor.insertAfter(track, QDomNode());
303 tractor.removeChild(multitrack);
305 // audio track mixing transitions should not be added to track view, so add required attribute
306 QDomNodeList transitions = m_doc.elementsByTagName("transition");
307 max = transitions.count();
308 for (int i = 0; i < max; i++) {
309 QDomElement tr = transitions.at(i).toElement();
310 if (tr.attribute("combine") == "1" && tr.attribute("mlt_service") == "mix") {
311 QDomElement property = m_doc.createElement("property");
312 property.setAttribute("name", "internal_added");
313 QDomText value = m_doc.createTextNode("237");
314 property.appendChild(value);
315 tr.appendChild(property);
316 property = m_doc.createElement("property");
317 property.setAttribute("name", "mlt_service");
318 value = m_doc.createTextNode("mix");
319 property.appendChild(value);
320 tr.appendChild(property);
322 // convert transition
323 QDomNamedNodeMap attrs = tr.attributes();
324 for (int j = 0; j < attrs.count(); j++) {
325 QString attrName = attrs.item(j).nodeName();
326 if (attrName != "in" && attrName != "out" && attrName != "id") {
327 QDomElement property = m_doc.createElement("property");
328 property.setAttribute("name", attrName);
329 QDomText value = m_doc.createTextNode(attrs.item(j).nodeValue());
330 property.appendChild(value);
331 tr.appendChild(property);
337 // move transitions after tracks
338 for (int i = 0; i < max; i++) {
339 tractor.insertAfter(transitions.at(0), QDomNode());
342 // Fix filters format
343 QDomNodeList entries = m_doc.elementsByTagName("entry");
344 max = entries.count();
345 for (int i = 0; i < max; i++) {
348 QDomNode m = entries.at(i).firstChild();
349 while (!m.isNull()) {
350 if (m.toElement().tagName() == "filter") {
351 QDomElement filt = m.toElement();
352 QDomNamedNodeMap attrs = filt.attributes();
353 QString current_id = filt.attribute("kdenlive_id");
354 if (current_id != last_id) {
356 last_id = current_id;
358 QDomElement e = m_doc.createElement("property");
359 e.setAttribute("name", "kdenlive_ix");
360 QDomText value = m_doc.createTextNode(QString::number(effectix));
361 e.appendChild(value);
363 for (int j = 0; j < attrs.count(); j++) {
364 QDomAttr a = attrs.item(j).toAttr();
366 kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
367 QDomElement e = m_doc.createElement("property");
368 e.setAttribute("name", a.name());
369 QDomText value = m_doc.createTextNode(a.value());
370 e.appendChild(value);
381 QDomNodeList filters = m_doc.elementsByTagName("filter");
382 max = filters.count();
385 for (int i = 0; i < max; i++) {
386 QDomElement filt = filters.at(i).toElement();
387 QDomNamedNodeMap attrs = filt.attributes();
388 QString current_id = filt.attribute("kdenlive_id");
389 if (current_id != last_id) {
391 last_id = current_id;
393 QDomElement e = m_doc.createElement("property");
394 e.setAttribute("name", "kdenlive_ix");
395 QDomText value = m_doc.createTextNode(QString::number(1));
396 e.appendChild(value);
398 for (int j = 0; j < attrs.count(); j++) {
399 QDomAttr a = attrs.item(j).toAttr();
401 kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
402 QDomElement e = m_doc.createElement("property");
403 e.setAttribute("name", a.name());
404 QDomText value = m_doc.createTextNode(a.value());
405 e.appendChild(value);
412 QDomNodeList producers = westley.toElement().elementsByTagName("producer");
413 max = producers.count();
414 for (int i = 0; i < max; i++) {
415 QDomElement prod = producers.at(i).toElement();
416 if (prod.attribute("mlt_service") == "framebuffer") {
417 QString slowmotionprod = prod.attribute("resource");
418 slowmotionprod.replace(':', '?');
419 kDebug() << "// FOUND WRONG SLOWMO, new: " << slowmotionprod;
420 prod.setAttribute("resource", slowmotionprod);
423 // move producers to correct place, markers to a global list, fix clip descriptions
424 QDomElement markers = m_doc.createElement("markers");
425 // This will get the xml producers:
426 producers = m_doc.elementsByTagName("producer");
427 max = producers.count();
428 for (int i = 0; i < max; i++) {
429 QDomElement prod = producers.at(0).toElement();
430 // add resource also as a property (to allow path correction in setNewResource())
431 // TODO: will it work with slowmotion? needs testing
432 /*if (!prod.attribute("resource").isEmpty()) {
433 QDomElement prop_resource = m_doc.createElement("property");
434 prop_resource.setAttribute("name", "resource");
435 QDomText resource = m_doc.createTextNode(prod.attribute("resource"));
436 prop_resource.appendChild(resource);
437 prod.appendChild(prop_resource);
439 QDomNode m = prod.firstChild();
441 if (m.toElement().tagName() == "markers") {
442 QDomNodeList prodchilds = m.childNodes();
443 int maxchild = prodchilds.count();
444 for (int k = 0; k < maxchild; k++) {
445 QDomElement mark = prodchilds.at(0).toElement();
446 mark.setAttribute("id", prod.attribute("id"));
447 markers.insertAfter(mark, QDomNode());
450 } else if (prod.attribute("type").toInt() == TEXT) {
451 // convert title clip
452 if (m.toElement().tagName() == "textclip") {
454 QDomElement titleclip = m.toElement();
455 QDomElement title = tdoc.createElement("kdenlivetitle");
456 tdoc.appendChild(title);
457 QDomNodeList objects = titleclip.childNodes();
458 int maxchild = objects.count();
459 for (int k = 0; k < maxchild; k++) {
461 QDomElement ob = objects.at(k).toElement();
462 if (ob.attribute("type") == "3") {
463 // text object - all of this goes into "xmldata"...
464 QDomElement item = tdoc.createElement("item");
465 item.setAttribute("z-index", ob.attribute("z"));
466 item.setAttribute("type", "QGraphicsTextItem");
467 QDomElement position = tdoc.createElement("position");
468 position.setAttribute("x", ob.attribute("x"));
469 position.setAttribute("y", ob.attribute("y"));
470 QDomElement content = tdoc.createElement("content");
471 content.setAttribute("font", ob.attribute("font_family"));
472 content.setAttribute("font-size", ob.attribute("font_size"));
473 content.setAttribute("font-bold", ob.attribute("bold"));
474 content.setAttribute("font-italic", ob.attribute("italic"));
475 content.setAttribute("font-underline", ob.attribute("underline"));
476 QString col = ob.attribute("color");
478 content.setAttribute("font-color", colorToString(c));
479 // todo: These fields are missing from the newly generated xmldata:
480 // transform, startviewport, endviewport, background
482 QDomText conttxt = tdoc.createTextNode(ob.attribute("text"));
483 content.appendChild(conttxt);
484 item.appendChild(position);
485 item.appendChild(content);
486 title.appendChild(item);
487 } else if (ob.attribute("type") == "5") {
489 QDomElement item = tdoc.createElement("item");
490 item.setAttribute("z-index", ob.attribute("z"));
491 item.setAttribute("type", "QGraphicsRectItem");
492 QDomElement position = tdoc.createElement("position");
493 position.setAttribute("x", ob.attribute("x"));
494 position.setAttribute("y", ob.attribute("y"));
495 QDomElement content = tdoc.createElement("content");
496 QString col = ob.attribute("color");
498 content.setAttribute("brushcolor", colorToString(c));
499 QString rect = "0,0,";
500 rect.append(ob.attribute("width"));
502 rect.append(ob.attribute("height"));
503 content.setAttribute("rect", rect);
504 item.appendChild(position);
505 item.appendChild(content);
506 title.appendChild(item);
509 prod.setAttribute("xmldata", tdoc.toString());
510 // mbd todo: This clearly does not work, as every title gets the same name - trying to leave it empty
511 // QStringList titleInfo = TitleWidget::getFreeTitleInfo(projectFolder());
512 // prod.setAttribute("titlename", titleInfo.at(0));
513 // prod.setAttribute("resource", titleInfo.at(1));
514 //kDebug()<<"TITLE DATA:\n"<<tdoc.toString();
516 } // End conversion of title clips.
518 } else if (m.isText()) {
519 QString comment = m.nodeValue();
520 if (!comment.isEmpty()) {
521 prod.setAttribute("description", comment);
526 int duration = prod.attribute("duration").toInt();
527 if (duration > 0) prod.setAttribute("out", QString::number(duration));
528 // The clip goes back in, but text clips should not go back in, at least not modified
529 westley.insertBefore(prod, QDomNode());
532 QDomNode westley0 = m_doc.elementsByTagName("westley").at(0);
533 if (!markers.firstChild().isNull()) westley0.appendChild(markers);
536 * Convert as much of the kdenlivedoc as possible. Use the producer in
537 * westley. First, remove the old stuff from westley, and add a new
538 * empty one. Also, track the max id in order to use it for the adding
541 int max_kproducer_id = 0;
542 westley0.removeChild(infoXmlNode);
543 QDomElement infoXml_new = m_doc.createElement("kdenlivedoc");
544 infoXml_new.setAttribute("profile", profile);
545 infoXml.setAttribute("position", startPos);
547 // Add all the producers that has a resource in westley
548 QDomElement westley_element = westley0.toElement();
549 if (westley_element.isNull()) {
550 kWarning() << "westley0 element in document was not a QDomElement - unable to add producers to new kdenlivedoc";
552 QDomNodeList wproducers = westley_element.elementsByTagName("producer");
553 int kmax = wproducers.count();
554 for (int i = 0; i < kmax; i++) {
555 QDomElement wproducer = wproducers.at(i).toElement();
556 if (wproducer.isNull()) {
557 kWarning() << "Found producer in westley0, that was not a QDomElement";
560 if (wproducer.attribute("id") == "black") continue;
561 // We have to do slightly different things, depending on the type
562 kDebug() << "Converting producer element with type" << wproducer.attribute("type");
563 if (wproducer.attribute("type").toInt() == TEXT) {
564 kDebug() << "Found TEXT element in producer" << endl;
565 QDomElement kproducer = wproducer.cloneNode(true).toElement();
566 kproducer.setTagName("kdenlive_producer");
567 infoXml_new.appendChild(kproducer);
569 * TODO: Perhaps needs some more changes here to
570 * "frequency", aspect ratio as a float, frame_size,
571 * channels, and later, resource and title name
574 QDomElement kproducer = m_doc.createElement("kdenlive_producer");
575 kproducer.setAttribute("id", wproducer.attribute("id"));
576 if (!wproducer.attribute("description").isEmpty())
577 kproducer.setAttribute("description", wproducer.attribute("description"));
578 kproducer.setAttribute("resource", wproducer.attribute("resource"));
579 kproducer.setAttribute("type", wproducer.attribute("type"));
580 // Testing fix for 358
581 if (!wproducer.attribute("aspect_ratio").isEmpty()) {
582 kproducer.setAttribute("aspect_ratio", wproducer.attribute("aspect_ratio"));
584 if (!wproducer.attribute("source_fps").isEmpty()) {
585 kproducer.setAttribute("fps", wproducer.attribute("source_fps"));
587 if (!wproducer.attribute("length").isEmpty()) {
588 kproducer.setAttribute("duration", wproducer.attribute("length"));
590 infoXml_new.appendChild(kproducer);
592 if (wproducer.attribute("id").toInt() > max_kproducer_id) {
593 max_kproducer_id = wproducer.attribute("id").toInt();
597 #define LOOKUP_FOLDER 1
600 * Look through all the folder elements of the old doc, for each folder,
601 * for each producer, get the id, look it up in the new doc, set the
602 * groupname and groupid. Note, this does not work at the moment - at
603 * least one folder shows up missing, and clips with no folder does not
606 //QDomElement infoXml_old = infoXmlNode.toElement();
607 if (!infoXml_old.isNull()) {
608 QDomNodeList folders = infoXml_old.elementsByTagName("folder");
609 int fsize = folders.size();
610 int groupId = max_kproducer_id + 1; // Start at +1 of max id of the kdenlive_producers
611 for (int i = 0; i < fsize; ++i) {
612 QDomElement folder = folders.at(i).toElement();
613 if (!folder.isNull()) {
614 QString groupName = folder.attribute("name");
615 kDebug() << "groupName: " << groupName << " with groupId: " << groupId;
616 QDomNodeList fproducers = folder.elementsByTagName("producer");
617 int psize = fproducers.size();
618 for (int j = 0; j < psize; ++j) {
619 QDomElement fproducer = fproducers.at(j).toElement();
620 if (!fproducer.isNull()) {
621 QString id = fproducer.attribute("id");
622 // This is not very effective, but compared to loading the clips, its a breeze
623 QDomNodeList kdenlive_producers = infoXml_new.elementsByTagName("kdenlive_producer");
624 int kpsize = kdenlive_producers.size();
625 for (int k = 0; k < kpsize; ++k) {
626 QDomElement kproducer = kdenlive_producers.at(k).toElement(); // Its an element for sure
627 if (id == kproducer.attribute("id")) {
628 // We do not check that it already is part of a folder
629 kproducer.setAttribute("groupid", groupId);
630 kproducer.setAttribute("groupname", groupName);
641 QDomNodeList elements = westley.childNodes();
642 max = elements.count();
643 for (int i = 0; i < max; i++) {
644 QDomElement prod = elements.at(0).toElement();
645 westley0.insertAfter(prod, QDomNode());
648 westley0.appendChild(infoXml_new);
650 westley0.removeChild(westley);
652 // adds <avfile /> information to <kdenlive_producer />
653 QDomNodeList kproducers = m_doc.elementsByTagName("kdenlive_producer");
654 QDomNodeList avfiles = infoXml_old.elementsByTagName("avfile");
655 kDebug() << "found" << avfiles.count() << "<avfile />s and" << kproducers.count() << "<kdenlive_producer />s";
656 for (int i = 0; i < avfiles.count(); ++i) {
657 QDomElement avfile = avfiles.at(i).toElement();
658 QDomElement kproducer;
660 kWarning() << "found an <avfile /> that is not a QDomElement";
662 QString id = avfile.attribute("id");
663 // this is horrible, must be rewritten, it's just for test
664 for (int j = 0; j < kproducers.count(); ++j) {
665 //kDebug() << "checking <kdenlive_producer /> with id" << kproducers.at(j).toElement().attribute("id");
666 if (kproducers.at(j).toElement().attribute("id") == id) {
667 kproducer = kproducers.at(j).toElement();
671 if (kproducer == QDomElement())
672 kWarning() << "no match for <avfile /> with id =" << id;
674 //kDebug() << "ready to set additional <avfile />'s attributes (id =" << id << ")";
675 kproducer.setAttribute("channels", avfile.attribute("channels"));
676 kproducer.setAttribute("duration", avfile.attribute("duration"));
677 kproducer.setAttribute("frame_size", avfile.attribute("width") + 'x' + avfile.attribute("height"));
678 kproducer.setAttribute("frequency", avfile.attribute("frequency"));
679 if (kproducer.attribute("description").isEmpty() && !avfile.attribute("description").isEmpty())
680 kproducer.setAttribute("description", avfile.attribute("description"));
684 infoXml = infoXml_new;
687 if (version <= 0.81) {
688 // Add the tracks information
689 QString tracksOrder = infoXml.attribute("tracks");
690 if (tracksOrder.isEmpty()) {
691 QDomNodeList tracks = m_doc.elementsByTagName("track");
692 for (int i = 0; i < tracks.count(); i++) {
693 QDomElement track = tracks.at(i).toElement();
694 if (track.attribute("producer") != "black_track") {
695 if (track.attribute("hide") == "video")
696 tracksOrder.append('a');
698 tracksOrder.append('v');
702 QDomElement tracksinfo = m_doc.createElement("tracksinfo");
703 for (int i = 0; i < tracksOrder.size(); i++) {
704 QDomElement trackinfo = m_doc.createElement("trackinfo");
705 if (tracksOrder.data()[i] == 'a') {
706 trackinfo.setAttribute("type", "audio");
707 trackinfo.setAttribute("blind", true);
709 trackinfo.setAttribute("blind", false);
710 trackinfo.setAttribute("mute", false);
711 trackinfo.setAttribute("locked", false);
712 tracksinfo.appendChild(trackinfo);
714 infoXml.appendChild(tracksinfo);
717 if (version <= 0.82) {
718 // Convert <westley />s in <mlt />s (MLT extreme makeover)
719 QDomNodeList westleyNodes = m_doc.elementsByTagName("westley");
720 for (int i = 0; i < westleyNodes.count(); i++) {
721 QDomElement westley = westleyNodes.at(i).toElement();
722 westley.setTagName("mlt");
726 if (version <= 0.83) {
727 // Replace point size with pixel size in text titles
728 if (m_doc.toString().contains("font-size")) {
729 KMessageBox::ButtonCode convert = KMessageBox::Continue;
730 QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
731 for (int i = 0; i < kproducerNodes.count() && convert != KMessageBox::No; ++i) {
732 QDomElement kproducer = kproducerNodes.at(i).toElement();
733 if (kproducer.attribute("type").toInt() == TEXT) {
735 data.setContent(kproducer.attribute("xmldata"));
736 QDomNodeList items = data.firstChild().childNodes();
737 for (int j = 0; j < items.count() && convert != KMessageBox::No; ++j) {
738 if (items.at(j).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
739 QDomNamedNodeMap textProperties = items.at(j).namedItem("content").attributes();
740 if (textProperties.namedItem("font-pixel-size").isNull() && !textProperties.namedItem("font-size").isNull()) {
741 // Ask the user if he wants to convert
742 if (convert != KMessageBox::Yes && convert != KMessageBox::No)
743 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"));
744 if (convert == KMessageBox::Yes) {
746 font.setPointSize(textProperties.namedItem("font-size").nodeValue().toInt());
747 QDomElement content = items.at(j).namedItem("content").toElement();
748 content.setAttribute("font-pixel-size", QFontInfo(font).pixelSize());
749 content.removeAttribute("font-size");
750 kproducer.setAttribute("xmldata", data.toString());
752 * You may be tempted to delete the preview file
753 * to force its recreation: bad idea (see
754 * http://www.kdenlive.org/mantis/view.php?id=749)
764 // Fill the <documentproperties /> element
765 QDomElement docProperties = infoXml.firstChildElement("documentproperties");
766 if (docProperties.isNull()) {
767 docProperties = m_doc.createElement("documentproperties");
768 docProperties.setAttribute("zonein", infoXml.attribute("zonein"));
769 docProperties.setAttribute("zoneout", infoXml.attribute("zoneout"));
770 docProperties.setAttribute("zoom", infoXml.attribute("zoom"));
771 docProperties.setAttribute("position", infoXml.attribute("position"));
772 infoXml.appendChild(docProperties);
776 if (version <= 0.84) {
777 // update the title clips to use the new MLT kdenlivetitle producer
778 QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
779 for (int i = 0; i < kproducerNodes.count(); ++i) {
780 QDomElement kproducer = kproducerNodes.at(i).toElement();
781 if (kproducer.attribute("type").toInt() == TEXT) {
782 QString data = kproducer.attribute("xmldata");
783 QString datafile = kproducer.attribute("resource");
784 if (!datafile.endsWith(".kdenlivetitle")) {
785 datafile = QString();
786 kproducer.setAttribute("resource", QString());
788 QString id = kproducer.attribute("id");
789 QDomNodeList mltproducers = m_doc.elementsByTagName("producer");
790 bool foundData = false;
791 bool foundResource = false;
792 bool foundService = false;
793 for (int j = 0; j < mltproducers.count(); j++) {
794 QDomElement wproducer = mltproducers.at(j).toElement();
795 if (wproducer.attribute("id") == id) {
796 QDomNodeList props = wproducer.childNodes();
797 for (int k = 0; k < props.count(); k++) {
798 if (props.at(k).toElement().attribute("name") == "xmldata") {
799 props.at(k).firstChild().setNodeValue(data);
801 } else if (props.at(k).toElement().attribute("name") == "mlt_service") {
802 props.at(k).firstChild().setNodeValue("kdenlivetitle");
804 } else if (props.at(k).toElement().attribute("name") == "resource") {
805 props.at(k).firstChild().setNodeValue(datafile);
806 foundResource = true;
810 QDomElement e = m_doc.createElement("property");
811 e.setAttribute("name", "xmldata");
812 QDomText value = m_doc.createTextNode(data);
813 e.appendChild(value);
814 wproducer.appendChild(e);
817 QDomElement e = m_doc.createElement("property");
818 e.setAttribute("name", "mlt_service");
819 QDomText value = m_doc.createTextNode("kdenlivetitle");
820 e.appendChild(value);
821 wproducer.appendChild(e);
823 if (!foundResource) {
824 QDomElement e = m_doc.createElement("property");
825 e.setAttribute("name", "resource");
826 QDomText value = m_doc.createTextNode(datafile);
827 e.appendChild(value);
828 wproducer.appendChild(e);
836 if (version <= 0.85) {
837 // update the LADSPA effects to use the new ladspa.id format instead of external xml file
838 QDomNodeList effectNodes = m_doc.elementsByTagName("filter");
839 for (int i = 0; i < effectNodes.count(); ++i) {
840 QDomElement effect = effectNodes.at(i).toElement();
841 if (EffectsList::property(effect, "mlt_service") == "ladspa") {
842 // Needs to be converted
843 QStringList info = getInfoFromEffectName(EffectsList::property(effect, "kdenlive_id"));
844 if (info.isEmpty()) continue;
845 // info contains the correct ladspa.id from kdenlive effect name, and a list of parameter's old and new names
846 EffectsList::setProperty(effect, "kdenlive_id", info.at(0));
847 EffectsList::setProperty(effect, "tag", info.at(0));
848 EffectsList::setProperty(effect, "mlt_service", info.at(0));
849 EffectsList::removeProperty(effect, "src");
850 for (int j = 1; j < info.size(); j++) {
851 QString value = EffectsList::property(effect, info.at(j).section('=', 0, 0));
852 if (!value.isEmpty()) {
853 // update parameter name
854 EffectsList::renameProperty(effect, info.at(j).section('=', 0, 0), info.at(j).section('=', 1, 1));
861 if (version <= 0.86) {
862 // Make sure we don't have avformat-novalidate producers, since it caused crashes
863 QDomNodeList producers = m_doc.elementsByTagName("producer");
864 int max = producers.count();
865 for (int i = 0; i < max; i++) {
866 QDomElement prod = producers.at(i).toElement();
867 if (EffectsList::property(prod, "mlt_service") == "avformat-novalidate")
868 EffectsList::setProperty(prod, "mlt_service", "avformat");
871 // 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
873 // Get profile info (width / height)
876 QDomElement profile = m_doc.firstChildElement("profile");
877 if (profile.isNull()) profile = infoXml.firstChildElement("profileinfo");
878 if (profile.isNull()) {
879 // could not find profile info, set PAL
884 profileWidth = profile.attribute("width").toInt();
885 profileHeight = profile.attribute("height").toInt();
887 QDomNodeList transitions = m_doc.elementsByTagName("transition");
888 max = transitions.count();
890 for (int i = 0; i < max; i++) {
891 QDomElement trans = transitions.at(i).toElement();
892 out = trans.attribute("out").toInt() - trans.attribute("in").toInt();
893 QString geom = EffectsList::property(trans, "geometry");
894 Mlt::Geometry *g = new Mlt::Geometry(geom.toUtf8().data(), out, profileWidth, profileHeight);
895 Mlt::GeometryItem item;
896 if (g->next_key(&item, out) == 0) {
897 // We have a keyframe just after last frame, try to move it to last frame
898 if (item.frame() == out + 1) {
902 EffectsList::setProperty(trans, "geometry", g->serialise());
909 // The document has been converted: mark it as modified
910 infoXml.setAttribute("version", currentVersion);
915 QStringList DocumentValidator::getInfoFromEffectName(const QString oldName)
918 // Returns a list to convert old Kdenlive ladspa effects
919 if (oldName == "pitch_shift") {
920 info << "ladspa.1433";
923 else if (oldName == "vinyl") {
924 info << "ladspa.1905";
931 else if (oldName == "room_reverb") {
932 info << "ladspa.1216";
937 else if (oldName == "reverb") {
938 info << "ladspa.1423";
942 else if (oldName == "rate_scale") {
943 info << "ladspa.1417";
946 else if (oldName == "pitch_scale") {
947 info << "ladspa.1193";
950 else if (oldName == "phaser") {
951 info << "ladspa.1217";
954 info << "feedback=2";
957 else if (oldName == "limiter") {
958 info << "ladspa.1913";
963 else if (oldName == "equalizer_15") {
964 info << "ladspa.1197";
981 else if (oldName == "equalizer") {
982 info << "ladspa.1901";
987 else if (oldName == "declipper") {
988 info << "ladspa.1195";
993 QString DocumentValidator::colorToString(const QColor& c)
995 QString ret = "%1,%2,%3,%4";
996 ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
1000 bool DocumentValidator::isProject() const
1002 QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
1003 return !infoXmlNode.isNull();
1006 bool DocumentValidator::isModified() const