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 // Make sure transitions do not overlap
149 QDomNodeList transitions = m_doc.elementsByTagName("transition");
151 QStringList overlappingTransitions;
152 for (int i = 0; i < transitions.count(); i++) {
153 QDomElement t = transitions.at(i).toElement();
154 QDomNodeList props = t.elementsByTagName("property");
155 bool testTransition = true;
157 for (int k = 0; k < props.count(); k++) {
158 QDomElement p = props.at(k).toElement();
159 QString name = p.attribute("name");
160 if (name == "mlt_service" && p.firstChild().nodeValue() == "mix") testTransition = false;
161 else if (name == "b_track") track = p.firstChild().nodeValue().toInt();
163 if (testTransition) {
164 QRect r(t.attribute("in").toInt(), 3 * track, t.attribute("out").toInt() - t.attribute("in").toInt(), 1);
166 if (scenelist.intersected(p).isEmpty()) {
167 scenelist = scenelist.united(p);
170 // Transition is overlapping, should be removed
171 overlappingTransitions << t.attribute("id");
172 tractor.removeChild(t);
177 if (!overlappingTransitions.isEmpty()) {
178 KMessageBox::informationList(kapp->activeWindow(), i18n("The following transitions were corrupted (overlapping)\n and removed from project."), overlappingTransitions, i18n("Invalid Transitions"));
182 // TODO: check the tracks references
183 // TODO: check internal mix transitions
190 bool DocumentValidator::upgrade(double version, const double currentVersion)
192 kDebug() << "Opening a document with version " << version;
194 // No conversion needed
195 if (version == currentVersion) {
199 // The document is too new
200 if (version > currentVersion) {
201 kDebug() << "Unable to open document with version " << version;
202 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"));
206 // Unsupported document versions
207 if (version == 0.5 || version == 0.7) {
208 kDebug() << "Unable to open document with version " << version;
209 KMessageBox::sorry(kapp->activeWindow(), i18n("This project type is unsupported (version %1) and can't be loaded.", version), i18n("Unable to open project"));
214 QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
215 QDomElement infoXml = infoXmlNode.toElement();
216 infoXml.setAttribute("upgraded", "1");
218 if (version <= 0.6) {
219 QDomElement infoXml_old = infoXmlNode.cloneNode(true).toElement(); // Needed for folders
220 QDomNode westley = m_doc.elementsByTagName("westley").at(1);
221 QDomNode tractor = m_doc.elementsByTagName("tractor").at(0);
222 QDomNode multitrack = m_doc.elementsByTagName("multitrack").at(0);
223 QDomNodeList playlists = m_doc.elementsByTagName("playlist");
225 QDomNode props = m_doc.elementsByTagName("properties").at(0).toElement();
226 QString profile = props.toElement().attribute("videoprofile");
227 int startPos = props.toElement().attribute("timeline_position").toInt();
228 if (profile == "dv_wide")
229 profile = "dv_pal_wide";
231 // move playlists outside of tractor and add the tracks instead
232 int max = playlists.count();
233 if (westley.isNull()) {
234 westley = m_doc.createElement("westley");
235 m_doc.documentElement().appendChild(westley);
237 if (tractor.isNull()) {
238 kDebug() << "// NO MLT PLAYLIST, building empty one";
239 QDomElement blank_tractor = m_doc.createElement("tractor");
240 westley.appendChild(blank_tractor);
241 QDomElement blank_playlist = m_doc.createElement("playlist");
242 blank_playlist.setAttribute("id", "black_track");
243 westley.insertBefore(blank_playlist, QDomNode());
244 QDomElement blank_track = m_doc.createElement("track");
245 blank_track.setAttribute("producer", "black_track");
246 blank_tractor.appendChild(blank_track);
248 QDomNodeList kdenlivetracks = m_doc.elementsByTagName("kdenlivetrack");
249 for (int i = 0; i < kdenlivetracks.count(); i++) {
250 blank_playlist = m_doc.createElement("playlist");
251 blank_playlist.setAttribute("id", "playlist" + QString::number(i));
252 westley.insertBefore(blank_playlist, QDomNode());
253 blank_track = m_doc.createElement("track");
254 blank_track.setAttribute("producer", "playlist" + QString::number(i));
255 blank_tractor.appendChild(blank_track);
256 if (kdenlivetracks.at(i).toElement().attribute("cliptype") == "Sound") {
257 blank_playlist.setAttribute("hide", "video");
258 blank_track.setAttribute("hide", "video");
261 } else for (int i = 0; i < max; i++) {
262 QDomNode n = playlists.at(i);
263 westley.insertBefore(n, QDomNode());
264 QDomElement pl = n.toElement();
265 QDomElement track = m_doc.createElement("track");
266 QString trackType = pl.attribute("hide");
267 if (!trackType.isEmpty())
268 track.setAttribute("hide", trackType);
269 QString playlist_id = pl.attribute("id");
270 if (playlist_id.isEmpty()) {
271 playlist_id = "black_track";
272 pl.setAttribute("id", playlist_id);
274 track.setAttribute("producer", playlist_id);
275 //tractor.appendChild(track);
276 #define KEEP_TRACK_ORDER 1
277 #ifdef KEEP_TRACK_ORDER
278 tractor.insertAfter(track, QDomNode());
280 // Insert the new track in an order that hopefully matches the 3 video, then 2 audio tracks of Kdenlive 0.7.0
281 // insertion sort - O( tracks*tracks )
282 // Note, this breaks _all_ transitions - but you can move them up and down afterwards.
283 QDomElement tractor_elem = tractor.toElement();
284 if (! tractor_elem.isNull()) {
285 QDomNodeList tracks = tractor_elem.elementsByTagName("track");
286 int size = tracks.size();
288 tractor.insertAfter(track, QDomNode());
290 bool inserted = false;
291 for (int i = 0; i < size; ++i) {
292 QDomElement track_elem = tracks.at(i).toElement();
293 if (track_elem.isNull()) {
294 tractor.insertAfter(track, QDomNode());
298 kDebug() << "playlist_id: " << playlist_id << " producer:" << track_elem.attribute("producer");
299 if (playlist_id < track_elem.attribute("producer")) {
300 tractor.insertBefore(track, track_elem);
306 // Reach here, no insertion, insert last
308 tractor.insertAfter(track, QDomNode());
312 kWarning() << "tractor was not a QDomElement";
313 tractor.insertAfter(track, QDomNode());
317 tractor.removeChild(multitrack);
319 // audio track mixing transitions should not be added to track view, so add required attribute
320 QDomNodeList transitions = m_doc.elementsByTagName("transition");
321 max = transitions.count();
322 for (int i = 0; i < max; i++) {
323 QDomElement tr = transitions.at(i).toElement();
324 if (tr.attribute("combine") == "1" && tr.attribute("mlt_service") == "mix") {
325 QDomElement property = m_doc.createElement("property");
326 property.setAttribute("name", "internal_added");
327 QDomText value = m_doc.createTextNode("237");
328 property.appendChild(value);
329 tr.appendChild(property);
330 property = m_doc.createElement("property");
331 property.setAttribute("name", "mlt_service");
332 value = m_doc.createTextNode("mix");
333 property.appendChild(value);
334 tr.appendChild(property);
336 // convert transition
337 QDomNamedNodeMap attrs = tr.attributes();
338 for (int j = 0; j < attrs.count(); j++) {
339 QString attrName = attrs.item(j).nodeName();
340 if (attrName != "in" && attrName != "out" && attrName != "id") {
341 QDomElement property = m_doc.createElement("property");
342 property.setAttribute("name", attrName);
343 QDomText value = m_doc.createTextNode(attrs.item(j).nodeValue());
344 property.appendChild(value);
345 tr.appendChild(property);
351 // move transitions after tracks
352 for (int i = 0; i < max; i++) {
353 tractor.insertAfter(transitions.at(0), QDomNode());
356 // Fix filters format
357 QDomNodeList entries = m_doc.elementsByTagName("entry");
358 max = entries.count();
359 for (int i = 0; i < max; i++) {
362 QDomNode m = entries.at(i).firstChild();
363 while (!m.isNull()) {
364 if (m.toElement().tagName() == "filter") {
365 QDomElement filt = m.toElement();
366 QDomNamedNodeMap attrs = filt.attributes();
367 QString current_id = filt.attribute("kdenlive_id");
368 if (current_id != last_id) {
370 last_id = current_id;
372 QDomElement e = m_doc.createElement("property");
373 e.setAttribute("name", "kdenlive_ix");
374 QDomText value = m_doc.createTextNode(QString::number(effectix));
375 e.appendChild(value);
377 for (int j = 0; j < attrs.count(); j++) {
378 QDomAttr a = attrs.item(j).toAttr();
380 kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
381 QDomElement e = m_doc.createElement("property");
382 e.setAttribute("name", a.name());
383 QDomText value = m_doc.createTextNode(a.value());
384 e.appendChild(value);
395 QDomNodeList filters = m_doc.elementsByTagName("filter");
396 max = filters.count();
399 for (int i = 0; i < max; i++) {
400 QDomElement filt = filters.at(i).toElement();
401 QDomNamedNodeMap attrs = filt.attributes();
402 QString current_id = filt.attribute("kdenlive_id");
403 if (current_id != last_id) {
405 last_id = current_id;
407 QDomElement e = m_doc.createElement("property");
408 e.setAttribute("name", "kdenlive_ix");
409 QDomText value = m_doc.createTextNode(QString::number(1));
410 e.appendChild(value);
412 for (int j = 0; j < attrs.count(); j++) {
413 QDomAttr a = attrs.item(j).toAttr();
415 kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
416 QDomElement e = m_doc.createElement("property");
417 e.setAttribute("name", a.name());
418 QDomText value = m_doc.createTextNode(a.value());
419 e.appendChild(value);
426 QDomNodeList producers = westley.toElement().elementsByTagName("producer");
427 max = producers.count();
428 for (int i = 0; i < max; i++) {
429 QDomElement prod = producers.at(i).toElement();
430 if (prod.attribute("mlt_service") == "framebuffer") {
431 QString slowmotionprod = prod.attribute("resource");
432 slowmotionprod.replace(':', '?');
433 kDebug() << "// FOUND WRONG SLOWMO, new: " << slowmotionprod;
434 prod.setAttribute("resource", slowmotionprod);
437 // move producers to correct place, markers to a global list, fix clip descriptions
438 QDomElement markers = m_doc.createElement("markers");
439 // This will get the xml producers:
440 producers = m_doc.elementsByTagName("producer");
441 max = producers.count();
442 for (int i = 0; i < max; i++) {
443 QDomElement prod = producers.at(0).toElement();
444 // add resource also as a property (to allow path correction in setNewResource())
445 // TODO: will it work with slowmotion? needs testing
446 /*if (!prod.attribute("resource").isEmpty()) {
447 QDomElement prop_resource = m_doc.createElement("property");
448 prop_resource.setAttribute("name", "resource");
449 QDomText resource = m_doc.createTextNode(prod.attribute("resource"));
450 prop_resource.appendChild(resource);
451 prod.appendChild(prop_resource);
453 QDomNode m = prod.firstChild();
455 if (m.toElement().tagName() == "markers") {
456 QDomNodeList prodchilds = m.childNodes();
457 int maxchild = prodchilds.count();
458 for (int k = 0; k < maxchild; k++) {
459 QDomElement mark = prodchilds.at(0).toElement();
460 mark.setAttribute("id", prod.attribute("id"));
461 markers.insertAfter(mark, QDomNode());
464 } else if (prod.attribute("type").toInt() == TEXT) {
465 // convert title clip
466 if (m.toElement().tagName() == "textclip") {
468 QDomElement titleclip = m.toElement();
469 QDomElement title = tdoc.createElement("kdenlivetitle");
470 tdoc.appendChild(title);
471 QDomNodeList objects = titleclip.childNodes();
472 int maxchild = objects.count();
473 for (int k = 0; k < maxchild; k++) {
475 QDomElement ob = objects.at(k).toElement();
476 if (ob.attribute("type") == "3") {
477 // text object - all of this goes into "xmldata"...
478 QDomElement item = tdoc.createElement("item");
479 item.setAttribute("z-index", ob.attribute("z"));
480 item.setAttribute("type", "QGraphicsTextItem");
481 QDomElement position = tdoc.createElement("position");
482 position.setAttribute("x", ob.attribute("x"));
483 position.setAttribute("y", ob.attribute("y"));
484 QDomElement content = tdoc.createElement("content");
485 content.setAttribute("font", ob.attribute("font_family"));
486 content.setAttribute("font-size", ob.attribute("font_size"));
487 content.setAttribute("font-bold", ob.attribute("bold"));
488 content.setAttribute("font-italic", ob.attribute("italic"));
489 content.setAttribute("font-underline", ob.attribute("underline"));
490 QString col = ob.attribute("color");
492 content.setAttribute("font-color", colorToString(c));
493 // todo: These fields are missing from the newly generated xmldata:
494 // transform, startviewport, endviewport, background
496 QDomText conttxt = tdoc.createTextNode(ob.attribute("text"));
497 content.appendChild(conttxt);
498 item.appendChild(position);
499 item.appendChild(content);
500 title.appendChild(item);
501 } else if (ob.attribute("type") == "5") {
503 QDomElement item = tdoc.createElement("item");
504 item.setAttribute("z-index", ob.attribute("z"));
505 item.setAttribute("type", "QGraphicsRectItem");
506 QDomElement position = tdoc.createElement("position");
507 position.setAttribute("x", ob.attribute("x"));
508 position.setAttribute("y", ob.attribute("y"));
509 QDomElement content = tdoc.createElement("content");
510 QString col = ob.attribute("color");
512 content.setAttribute("brushcolor", colorToString(c));
513 QString rect = "0,0,";
514 rect.append(ob.attribute("width"));
516 rect.append(ob.attribute("height"));
517 content.setAttribute("rect", rect);
518 item.appendChild(position);
519 item.appendChild(content);
520 title.appendChild(item);
523 prod.setAttribute("xmldata", tdoc.toString());
524 // mbd todo: This clearly does not work, as every title gets the same name - trying to leave it empty
525 // QStringList titleInfo = TitleWidget::getFreeTitleInfo(projectFolder());
526 // prod.setAttribute("titlename", titleInfo.at(0));
527 // prod.setAttribute("resource", titleInfo.at(1));
528 //kDebug()<<"TITLE DATA:\n"<<tdoc.toString();
530 } // End conversion of title clips.
532 } else if (m.isText()) {
533 QString comment = m.nodeValue();
534 if (!comment.isEmpty()) {
535 prod.setAttribute("description", comment);
540 int duration = prod.attribute("duration").toInt();
541 if (duration > 0) prod.setAttribute("out", QString::number(duration));
542 // The clip goes back in, but text clips should not go back in, at least not modified
543 westley.insertBefore(prod, QDomNode());
546 QDomNode westley0 = m_doc.elementsByTagName("westley").at(0);
547 if (!markers.firstChild().isNull()) westley0.appendChild(markers);
550 * Convert as much of the kdenlivedoc as possible. Use the producer in
551 * westley. First, remove the old stuff from westley, and add a new
552 * empty one. Also, track the max id in order to use it for the adding
555 int max_kproducer_id = 0;
556 westley0.removeChild(infoXmlNode);
557 QDomElement infoXml_new = m_doc.createElement("kdenlivedoc");
558 infoXml_new.setAttribute("profile", profile);
559 infoXml.setAttribute("position", startPos);
561 // Add all the producers that has a resource in westley
562 QDomElement westley_element = westley0.toElement();
563 if (westley_element.isNull()) {
564 kWarning() << "westley0 element in document was not a QDomElement - unable to add producers to new kdenlivedoc";
566 QDomNodeList wproducers = westley_element.elementsByTagName("producer");
567 int kmax = wproducers.count();
568 for (int i = 0; i < kmax; i++) {
569 QDomElement wproducer = wproducers.at(i).toElement();
570 if (wproducer.isNull()) {
571 kWarning() << "Found producer in westley0, that was not a QDomElement";
574 if (wproducer.attribute("id") == "black") continue;
575 // We have to do slightly different things, depending on the type
576 kDebug() << "Converting producer element with type" << wproducer.attribute("type");
577 if (wproducer.attribute("type").toInt() == TEXT) {
578 kDebug() << "Found TEXT element in producer" << endl;
579 QDomElement kproducer = wproducer.cloneNode(true).toElement();
580 kproducer.setTagName("kdenlive_producer");
581 infoXml_new.appendChild(kproducer);
583 * TODO: Perhaps needs some more changes here to
584 * "frequency", aspect ratio as a float, frame_size,
585 * channels, and later, resource and title name
588 QDomElement kproducer = m_doc.createElement("kdenlive_producer");
589 kproducer.setAttribute("id", wproducer.attribute("id"));
590 if (!wproducer.attribute("description").isEmpty())
591 kproducer.setAttribute("description", wproducer.attribute("description"));
592 kproducer.setAttribute("resource", wproducer.attribute("resource"));
593 kproducer.setAttribute("type", wproducer.attribute("type"));
594 // Testing fix for 358
595 if (!wproducer.attribute("aspect_ratio").isEmpty()) {
596 kproducer.setAttribute("aspect_ratio", wproducer.attribute("aspect_ratio"));
598 if (!wproducer.attribute("source_fps").isEmpty()) {
599 kproducer.setAttribute("fps", wproducer.attribute("source_fps"));
601 if (!wproducer.attribute("length").isEmpty()) {
602 kproducer.setAttribute("duration", wproducer.attribute("length"));
604 infoXml_new.appendChild(kproducer);
606 if (wproducer.attribute("id").toInt() > max_kproducer_id) {
607 max_kproducer_id = wproducer.attribute("id").toInt();
611 #define LOOKUP_FOLDER 1
614 * Look through all the folder elements of the old doc, for each folder,
615 * for each producer, get the id, look it up in the new doc, set the
616 * groupname and groupid. Note, this does not work at the moment - at
617 * least one folder shows up missing, and clips with no folder does not
620 //QDomElement infoXml_old = infoXmlNode.toElement();
621 if (!infoXml_old.isNull()) {
622 QDomNodeList folders = infoXml_old.elementsByTagName("folder");
623 int fsize = folders.size();
624 int groupId = max_kproducer_id + 1; // Start at +1 of max id of the kdenlive_producers
625 for (int i = 0; i < fsize; ++i) {
626 QDomElement folder = folders.at(i).toElement();
627 if (!folder.isNull()) {
628 QString groupName = folder.attribute("name");
629 kDebug() << "groupName: " << groupName << " with groupId: " << groupId;
630 QDomNodeList fproducers = folder.elementsByTagName("producer");
631 int psize = fproducers.size();
632 for (int j = 0; j < psize; ++j) {
633 QDomElement fproducer = fproducers.at(j).toElement();
634 if (!fproducer.isNull()) {
635 QString id = fproducer.attribute("id");
636 // This is not very effective, but compared to loading the clips, its a breeze
637 QDomNodeList kdenlive_producers = infoXml_new.elementsByTagName("kdenlive_producer");
638 int kpsize = kdenlive_producers.size();
639 for (int k = 0; k < kpsize; ++k) {
640 QDomElement kproducer = kdenlive_producers.at(k).toElement(); // Its an element for sure
641 if (id == kproducer.attribute("id")) {
642 // We do not check that it already is part of a folder
643 kproducer.setAttribute("groupid", groupId);
644 kproducer.setAttribute("groupname", groupName);
655 QDomNodeList elements = westley.childNodes();
656 max = elements.count();
657 for (int i = 0; i < max; i++) {
658 QDomElement prod = elements.at(0).toElement();
659 westley0.insertAfter(prod, QDomNode());
662 westley0.appendChild(infoXml_new);
664 westley0.removeChild(westley);
666 // adds <avfile /> information to <kdenlive_producer />
667 QDomNodeList kproducers = m_doc.elementsByTagName("kdenlive_producer");
668 QDomNodeList avfiles = infoXml_old.elementsByTagName("avfile");
669 kDebug() << "found" << avfiles.count() << "<avfile />s and" << kproducers.count() << "<kdenlive_producer />s";
670 for (int i = 0; i < avfiles.count(); ++i) {
671 QDomElement avfile = avfiles.at(i).toElement();
672 QDomElement kproducer;
674 kWarning() << "found an <avfile /> that is not a QDomElement";
676 QString id = avfile.attribute("id");
677 // this is horrible, must be rewritten, it's just for test
678 for (int j = 0; j < kproducers.count(); ++j) {
679 //kDebug() << "checking <kdenlive_producer /> with id" << kproducers.at(j).toElement().attribute("id");
680 if (kproducers.at(j).toElement().attribute("id") == id) {
681 kproducer = kproducers.at(j).toElement();
685 if (kproducer == QDomElement())
686 kWarning() << "no match for <avfile /> with id =" << id;
688 //kDebug() << "ready to set additional <avfile />'s attributes (id =" << id << ")";
689 kproducer.setAttribute("channels", avfile.attribute("channels"));
690 kproducer.setAttribute("duration", avfile.attribute("duration"));
691 kproducer.setAttribute("frame_size", avfile.attribute("width") + 'x' + avfile.attribute("height"));
692 kproducer.setAttribute("frequency", avfile.attribute("frequency"));
693 if (kproducer.attribute("description").isEmpty() && !avfile.attribute("description").isEmpty())
694 kproducer.setAttribute("description", avfile.attribute("description"));
698 infoXml = infoXml_new;
701 if (version <= 0.81) {
702 // Add the tracks information
703 QString tracksOrder = infoXml.attribute("tracks");
704 if (tracksOrder.isEmpty()) {
705 QDomNodeList tracks = m_doc.elementsByTagName("track");
706 for (int i = 0; i < tracks.count(); i++) {
707 QDomElement track = tracks.at(i).toElement();
708 if (track.attribute("producer") != "black_track") {
709 if (track.attribute("hide") == "video")
710 tracksOrder.append('a');
712 tracksOrder.append('v');
716 QDomElement tracksinfo = m_doc.createElement("tracksinfo");
717 for (int i = 0; i < tracksOrder.size(); i++) {
718 QDomElement trackinfo = m_doc.createElement("trackinfo");
719 if (tracksOrder.data()[i] == 'a') {
720 trackinfo.setAttribute("type", "audio");
721 trackinfo.setAttribute("blind", true);
723 trackinfo.setAttribute("blind", false);
724 trackinfo.setAttribute("mute", false);
725 trackinfo.setAttribute("locked", false);
726 tracksinfo.appendChild(trackinfo);
728 infoXml.appendChild(tracksinfo);
731 if (version <= 0.82) {
732 // Convert <westley />s in <mlt />s (MLT extreme makeover)
733 QDomNodeList westleyNodes = m_doc.elementsByTagName("westley");
734 for (int i = 0; i < westleyNodes.count(); i++) {
735 QDomElement westley = westleyNodes.at(i).toElement();
736 westley.setTagName("mlt");
740 if (version <= 0.83) {
741 // Replace point size with pixel size in text titles
742 if (m_doc.toString().contains("font-size")) {
743 KMessageBox::ButtonCode convert = KMessageBox::Continue;
744 QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
745 for (int i = 0; i < kproducerNodes.count() && convert != KMessageBox::No; ++i) {
746 QDomElement kproducer = kproducerNodes.at(i).toElement();
747 if (kproducer.attribute("type").toInt() == TEXT) {
749 data.setContent(kproducer.attribute("xmldata"));
750 QDomNodeList items = data.firstChild().childNodes();
751 for (int j = 0; j < items.count() && convert != KMessageBox::No; ++j) {
752 if (items.at(j).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
753 QDomNamedNodeMap textProperties = items.at(j).namedItem("content").attributes();
754 if (textProperties.namedItem("font-pixel-size").isNull() && !textProperties.namedItem("font-size").isNull()) {
755 // Ask the user if he wants to convert
756 if (convert != KMessageBox::Yes && convert != KMessageBox::No)
757 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"));
758 if (convert == KMessageBox::Yes) {
760 font.setPointSize(textProperties.namedItem("font-size").nodeValue().toInt());
761 QDomElement content = items.at(j).namedItem("content").toElement();
762 content.setAttribute("font-pixel-size", QFontInfo(font).pixelSize());
763 content.removeAttribute("font-size");
764 kproducer.setAttribute("xmldata", data.toString());
766 * You may be tempted to delete the preview file
767 * to force its recreation: bad idea (see
768 * http://www.kdenlive.org/mantis/view.php?id=749)
778 // Fill the <documentproperties /> element
779 QDomElement docProperties = infoXml.firstChildElement("documentproperties");
780 if (docProperties.isNull()) {
781 docProperties = m_doc.createElement("documentproperties");
782 docProperties.setAttribute("zonein", infoXml.attribute("zonein"));
783 docProperties.setAttribute("zoneout", infoXml.attribute("zoneout"));
784 docProperties.setAttribute("zoom", infoXml.attribute("zoom"));
785 docProperties.setAttribute("position", infoXml.attribute("position"));
786 infoXml.appendChild(docProperties);
790 if (version <= 0.84) {
791 // update the title clips to use the new MLT kdenlivetitle producer
792 QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
793 for (int i = 0; i < kproducerNodes.count(); ++i) {
794 QDomElement kproducer = kproducerNodes.at(i).toElement();
795 if (kproducer.attribute("type").toInt() == TEXT) {
796 QString data = kproducer.attribute("xmldata");
797 QString datafile = kproducer.attribute("resource");
798 if (!datafile.endsWith(".kdenlivetitle")) {
799 datafile = QString();
800 kproducer.setAttribute("resource", QString());
802 QString id = kproducer.attribute("id");
803 QDomNodeList mltproducers = m_doc.elementsByTagName("producer");
804 bool foundData = false;
805 bool foundResource = false;
806 bool foundService = false;
807 for (int j = 0; j < mltproducers.count(); j++) {
808 QDomElement wproducer = mltproducers.at(j).toElement();
809 if (wproducer.attribute("id") == id) {
810 QDomNodeList props = wproducer.childNodes();
811 for (int k = 0; k < props.count(); k++) {
812 if (props.at(k).toElement().attribute("name") == "xmldata") {
813 props.at(k).firstChild().setNodeValue(data);
815 } else if (props.at(k).toElement().attribute("name") == "mlt_service") {
816 props.at(k).firstChild().setNodeValue("kdenlivetitle");
818 } else if (props.at(k).toElement().attribute("name") == "resource") {
819 props.at(k).firstChild().setNodeValue(datafile);
820 foundResource = true;
824 QDomElement e = m_doc.createElement("property");
825 e.setAttribute("name", "xmldata");
826 QDomText value = m_doc.createTextNode(data);
827 e.appendChild(value);
828 wproducer.appendChild(e);
831 QDomElement e = m_doc.createElement("property");
832 e.setAttribute("name", "mlt_service");
833 QDomText value = m_doc.createTextNode("kdenlivetitle");
834 e.appendChild(value);
835 wproducer.appendChild(e);
837 if (!foundResource) {
838 QDomElement e = m_doc.createElement("property");
839 e.setAttribute("name", "resource");
840 QDomText value = m_doc.createTextNode(datafile);
841 e.appendChild(value);
842 wproducer.appendChild(e);
850 if (version <= 0.85) {
851 // update the LADSPA effects to use the new ladspa.id format instead of external xml file
852 QDomNodeList effectNodes = m_doc.elementsByTagName("filter");
853 for (int i = 0; i < effectNodes.count(); ++i) {
854 QDomElement effect = effectNodes.at(i).toElement();
855 if (EffectsList::property(effect, "mlt_service") == "ladspa") {
856 // Needs to be converted
857 QStringList info = getInfoFromEffectName(EffectsList::property(effect, "kdenlive_id"));
858 if (info.isEmpty()) continue;
859 // info contains the correct ladspa.id from kdenlive effect name, and a list of parameter's old and new names
860 EffectsList::setProperty(effect, "kdenlive_id", info.at(0));
861 EffectsList::setProperty(effect, "tag", info.at(0));
862 EffectsList::setProperty(effect, "mlt_service", info.at(0));
863 EffectsList::removeProperty(effect, "src");
864 for (int j = 1; j < info.size(); j++) {
865 QString value = EffectsList::property(effect, info.at(j).section('=', 0, 0));
866 if (!value.isEmpty()) {
867 // update parameter name
868 EffectsList::renameProperty(effect, info.at(j).section('=', 0, 0), info.at(j).section('=', 1, 1));
876 // The document has been converted: mark it as modified
877 infoXml.setAttribute("version", currentVersion);
882 QStringList DocumentValidator::getInfoFromEffectName(const QString oldName)
885 // Returns a list to convert old Kdenlive ladspa effects
886 if (oldName == "pitch_shift") {
887 info << "ladspa.1433";
890 else if (oldName == "vinyl") {
891 info << "ladspa.1905";
898 else if (oldName == "room_reverb") {
899 info << "ladspa.1216";
904 else if (oldName == "reverb") {
905 info << "ladspa.1423";
909 else if (oldName == "rate_scale") {
910 info << "ladspa.1417";
913 else if (oldName == "pitch_scale") {
914 info << "ladspa.1193";
917 else if (oldName == "phaser") {
918 info << "ladspa.1217";
921 info << "feedback=2";
924 else if (oldName == "limiter") {
925 info << "ladspa.1913";
930 else if (oldName == "equalizer_15") {
931 info << "ladspa.1197";
948 else if (oldName == "equalizer") {
949 info << "ladspa.1901";
954 else if (oldName == "declipper") {
955 info << "ladspa.1195";
960 QString DocumentValidator::colorToString(const QColor& c)
962 QString ret = "%1,%2,%3,%4";
963 ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
967 bool DocumentValidator::isProject() const
969 QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
970 return !infoXmlNode.isNull();
973 bool DocumentValidator::isModified() const