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>
34 DocumentValidator::DocumentValidator(QDomDocument doc):
39 bool DocumentValidator::validate(const double currentVersion)
41 QDomElement mlt = m_doc.firstChildElement("mlt");
42 // At least the root element must be there
46 QDomElement kdenliveDoc = mlt.firstChildElement("kdenlivedoc");
47 // Check if we're validating a Kdenlive project
48 if (kdenliveDoc.isNull())
50 // Upgrade the document to the latest version
51 if (!upgrade(kdenliveDoc.attribute("version").toDouble(), currentVersion))
55 * Check the syntax (this will be replaced by XSD validation with Qt 4.6)
56 * and correct some errors
59 // Return (or create) the tractor
60 QDomElement tractor = mlt.firstChildElement("tractor");
61 if (tractor.isNull()) {
63 tractor = m_doc.createElement("tractor");
64 tractor.setAttribute("global_feed", "1");
65 tractor.setAttribute("in", "0");
66 tractor.setAttribute("out", "-1");
67 tractor.setAttribute("id", "maintractor");
68 mlt.appendChild(tractor);
72 * Make sure at least one track exists, and they're equal in number to
73 * to the maximum between MLT and Kdenlive playlists and tracks
75 QDomNodeList playlists = m_doc.elementsByTagName("playlist");
76 int tracksMax = playlists.count() - 1; // Remove the black track
77 QDomNodeList tracks = tractor.elementsByTagName("track");
78 tracksMax = qMax(tracks.count() - 1, tracksMax);
79 QDomNodeList tracksinfo = kdenliveDoc.elementsByTagName("trackinfo");
80 tracksMax = qMax(tracksinfo.count(), tracksMax);
81 tracksMax = qMax(1, tracksMax); // Force existance of one track
82 if (playlists.count() - 1 < tracksMax ||
83 tracks.count() - 1 < tracksMax ||
84 tracksinfo.count() < tracksMax) {
85 kDebug() << "//// WARNING, PROJECT IS CORRUPTED, MISSING TRACK";
88 // use the MLT tracks as reference
89 if (tracks.count() - 1 < tracksMax) {
90 // Looks like one MLT track is missing, remove the extra Kdenlive track if there is one.
91 if (tracksinfo.count() != tracks.count() - 1) {
92 // The Kdenlive tracks are not ok, clear and rebuild them
93 QDomNode tinfo = kdenliveDoc.elementsByTagName("tracksinfo").at(0);
94 QDomNode tnode = tinfo.firstChild();
95 while (!tnode.isNull()) {
96 tinfo.removeChild(tnode);
97 tnode = tinfo.firstChild();
100 for (int i = 1; i < tracks.count(); i++) {
101 QString hide = tracks.at(i).toElement().attribute("hide");
102 QDomElement newTrack = m_doc.createElement("trackinfo");
103 if (hide == "video") {
105 newTrack.setAttribute("type", "audio");
106 newTrack.setAttribute("blind", 1);
107 newTrack.setAttribute("mute", 0);
108 newTrack.setAttribute("lock", 0);
110 newTrack.setAttribute("blind", 0);
111 newTrack.setAttribute("mute", 0);
112 newTrack.setAttribute("lock", 0);
114 tinfo.appendChild(newTrack);
119 if (playlists.count() - 1 < tracksMax) {
120 difference = tracksMax - (playlists.count() - 1);
121 for (int i = 0; i < difference; ++i) {
122 QDomElement playlist = m_doc.createElement("playlist");
123 mlt.appendChild(playlist);
126 if (tracks.count() - 1 < tracksMax) {
127 difference = tracksMax - (tracks.count() - 1);
128 for (int i = 0; i < difference; ++i) {
129 QDomElement track = m_doc.createElement("track");
130 tractor.appendChild(track);
133 if (tracksinfo.count() < tracksMax) {
134 QDomElement tracksinfoElm = kdenliveDoc.firstChildElement("tracksinfo");
135 if (tracksinfoElm.isNull()) {
136 tracksinfoElm = m_doc.createElement("tracksinfo");
137 kdenliveDoc.appendChild(tracksinfoElm);
139 difference = tracksMax - tracksinfo.count();
140 for (int i = 0; i < difference; ++i) {
141 QDomElement trackinfo = m_doc.createElement("trackinfo");
142 trackinfo.setAttribute("mute", "0");
143 trackinfo.setAttribute("locked", "0");
144 tracksinfoElm.appendChild(trackinfo);
149 // Make sure transitions do not overlap
150 QDomNodeList transitions = m_doc.elementsByTagName("transition");
152 QStringList overlappingTransitions;
153 for (int i = 0; i < transitions.count(); i++) {
154 QDomElement t = transitions.at(i).toElement();
155 QDomNodeList props = t.elementsByTagName("property");
156 bool testTransition = true;
158 for (int k = 0; k < props.count(); k++) {
159 QDomElement p = props.at(k).toElement();
160 QString name = p.attribute("name");
161 if (name == "mlt_service" && p.firstChild().nodeValue() == "mix") testTransition = false;
162 else if (name == "b_track") track = p.firstChild().nodeValue().toInt();
164 if (testTransition) {
165 QRect r(t.attribute("in").toInt(), 3 * track, t.attribute("out").toInt() - t.attribute("in").toInt(), 1);
167 if (scenelist.intersected(p).isEmpty()) {
168 scenelist = scenelist.united(p);
171 // Transition is overlapping, should be removed
172 overlappingTransitions << t.attribute("id");
173 tractor.removeChild(t);
178 if (!overlappingTransitions.isEmpty()) {
179 KMessageBox::informationList(kapp->activeWindow(), i18n("The following transitions were corrupted (overlapping)\n and removed from project."), overlappingTransitions, i18n("Invalid Transitions"));
183 // TODO: check the tracks references
184 // TODO: check internal mix transitions
191 bool DocumentValidator::upgrade(double version, const double currentVersion)
193 kDebug() << "Opening a document with version " << version;
195 // No conversion needed
196 if (version == currentVersion) {
200 // The document is too new
201 if (version > currentVersion) {
202 kDebug() << "Unable to open document with version " << version;
203 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"));
207 // Unsupported document versions
208 if (version == 0.5 || version == 0.7) {
209 kDebug() << "Unable to open document with version " << version;
210 KMessageBox::sorry(kapp->activeWindow(), i18n("This project type is unsupported (version %1) and can't be loaded.", version), i18n("Unable to open project"));
215 QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
216 QDomElement infoXml = infoXmlNode.toElement();
217 infoXml.setAttribute("upgraded", "1");
219 if (version <= 0.6) {
220 QDomElement infoXml_old = infoXmlNode.cloneNode(true).toElement(); // Needed for folders
221 QDomNode westley = m_doc.elementsByTagName("westley").at(1);
222 QDomNode tractor = m_doc.elementsByTagName("tractor").at(0);
223 QDomNode multitrack = m_doc.elementsByTagName("multitrack").at(0);
224 QDomNodeList playlists = m_doc.elementsByTagName("playlist");
226 QDomNode props = m_doc.elementsByTagName("properties").at(0).toElement();
227 QString profile = props.toElement().attribute("videoprofile");
228 int startPos = props.toElement().attribute("timeline_position").toInt();
229 if (profile == "dv_wide")
230 profile = "dv_pal_wide";
232 // move playlists outside of tractor and add the tracks instead
233 int max = playlists.count();
234 if (westley.isNull()) {
235 westley = m_doc.createElement("westley");
236 m_doc.documentElement().appendChild(westley);
238 if (tractor.isNull()) {
239 kDebug() << "// NO MLT PLAYLIST, building empty one";
240 QDomElement blank_tractor = m_doc.createElement("tractor");
241 westley.appendChild(blank_tractor);
242 QDomElement blank_playlist = m_doc.createElement("playlist");
243 blank_playlist.setAttribute("id", "black_track");
244 westley.insertBefore(blank_playlist, QDomNode());
245 QDomElement blank_track = m_doc.createElement("track");
246 blank_track.setAttribute("producer", "black_track");
247 blank_tractor.appendChild(blank_track);
249 QDomNodeList kdenlivetracks = m_doc.elementsByTagName("kdenlivetrack");
250 for (int i = 0; i < kdenlivetracks.count(); i++) {
251 blank_playlist = m_doc.createElement("playlist");
252 blank_playlist.setAttribute("id", "playlist" + QString::number(i));
253 westley.insertBefore(blank_playlist, QDomNode());
254 blank_track = m_doc.createElement("track");
255 blank_track.setAttribute("producer", "playlist" + QString::number(i));
256 blank_tractor.appendChild(blank_track);
257 if (kdenlivetracks.at(i).toElement().attribute("cliptype") == "Sound") {
258 blank_playlist.setAttribute("hide", "video");
259 blank_track.setAttribute("hide", "video");
262 } else for (int i = 0; i < max; i++) {
263 QDomNode n = playlists.at(i);
264 westley.insertBefore(n, QDomNode());
265 QDomElement pl = n.toElement();
266 QDomElement track = m_doc.createElement("track");
267 QString trackType = pl.attribute("hide");
268 if (!trackType.isEmpty())
269 track.setAttribute("hide", trackType);
270 QString playlist_id = pl.attribute("id");
271 if (playlist_id.isEmpty()) {
272 playlist_id = "black_track";
273 pl.setAttribute("id", playlist_id);
275 track.setAttribute("producer", playlist_id);
276 //tractor.appendChild(track);
277 #define KEEP_TRACK_ORDER 1
278 #ifdef KEEP_TRACK_ORDER
279 tractor.insertAfter(track, QDomNode());
281 // Insert the new track in an order that hopefully matches the 3 video, then 2 audio tracks of Kdenlive 0.7.0
282 // insertion sort - O( tracks*tracks )
283 // Note, this breaks _all_ transitions - but you can move them up and down afterwards.
284 QDomElement tractor_elem = tractor.toElement();
285 if (! tractor_elem.isNull()) {
286 QDomNodeList tracks = tractor_elem.elementsByTagName("track");
287 int size = tracks.size();
289 tractor.insertAfter(track, QDomNode());
291 bool inserted = false;
292 for (int i = 0; i < size; ++i) {
293 QDomElement track_elem = tracks.at(i).toElement();
294 if (track_elem.isNull()) {
295 tractor.insertAfter(track, QDomNode());
299 kDebug() << "playlist_id: " << playlist_id << " producer:" << track_elem.attribute("producer");
300 if (playlist_id < track_elem.attribute("producer")) {
301 tractor.insertBefore(track, track_elem);
307 // Reach here, no insertion, insert last
309 tractor.insertAfter(track, QDomNode());
313 kWarning() << "tractor was not a QDomElement";
314 tractor.insertAfter(track, QDomNode());
318 tractor.removeChild(multitrack);
320 // audio track mixing transitions should not be added to track view, so add required attribute
321 QDomNodeList transitions = m_doc.elementsByTagName("transition");
322 max = transitions.count();
323 for (int i = 0; i < max; i++) {
324 QDomElement tr = transitions.at(i).toElement();
325 if (tr.attribute("combine") == "1" && tr.attribute("mlt_service") == "mix") {
326 QDomElement property = m_doc.createElement("property");
327 property.setAttribute("name", "internal_added");
328 QDomText value = m_doc.createTextNode("237");
329 property.appendChild(value);
330 tr.appendChild(property);
331 property = m_doc.createElement("property");
332 property.setAttribute("name", "mlt_service");
333 value = m_doc.createTextNode("mix");
334 property.appendChild(value);
335 tr.appendChild(property);
337 // convert transition
338 QDomNamedNodeMap attrs = tr.attributes();
339 for (int j = 0; j < attrs.count(); j++) {
340 QString attrName = attrs.item(j).nodeName();
341 if (attrName != "in" && attrName != "out" && attrName != "id") {
342 QDomElement property = m_doc.createElement("property");
343 property.setAttribute("name", attrName);
344 QDomText value = m_doc.createTextNode(attrs.item(j).nodeValue());
345 property.appendChild(value);
346 tr.appendChild(property);
352 // move transitions after tracks
353 for (int i = 0; i < max; i++) {
354 tractor.insertAfter(transitions.at(0), QDomNode());
357 // Fix filters format
358 QDomNodeList entries = m_doc.elementsByTagName("entry");
359 max = entries.count();
360 for (int i = 0; i < max; i++) {
363 QDomNode m = entries.at(i).firstChild();
364 while (!m.isNull()) {
365 if (m.toElement().tagName() == "filter") {
366 QDomElement filt = m.toElement();
367 QDomNamedNodeMap attrs = filt.attributes();
368 QString current_id = filt.attribute("kdenlive_id");
369 if (current_id != last_id) {
371 last_id = current_id;
373 QDomElement e = m_doc.createElement("property");
374 e.setAttribute("name", "kdenlive_ix");
375 QDomText value = m_doc.createTextNode(QString::number(effectix));
376 e.appendChild(value);
378 for (int j = 0; j < attrs.count(); j++) {
379 QDomAttr a = attrs.item(j).toAttr();
381 kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
382 QDomElement e = m_doc.createElement("property");
383 e.setAttribute("name", a.name());
384 QDomText value = m_doc.createTextNode(a.value());
385 e.appendChild(value);
396 QDomNodeList filters = m_doc.elementsByTagName("filter");
397 max = filters.count();
400 for (int i = 0; i < max; i++) {
401 QDomElement filt = filters.at(i).toElement();
402 QDomNamedNodeMap attrs = filt.attributes();
403 QString current_id = filt.attribute("kdenlive_id");
404 if (current_id != last_id) {
406 last_id = current_id;
408 QDomElement e = m_doc.createElement("property");
409 e.setAttribute("name", "kdenlive_ix");
410 QDomText value = m_doc.createTextNode(QString::number(1));
411 e.appendChild(value);
413 for (int j = 0; j < attrs.count(); j++) {
414 QDomAttr a = attrs.item(j).toAttr();
416 kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
417 QDomElement e = m_doc.createElement("property");
418 e.setAttribute("name", a.name());
419 QDomText value = m_doc.createTextNode(a.value());
420 e.appendChild(value);
427 QDomNodeList producers = westley.toElement().elementsByTagName("producer");
428 max = producers.count();
429 for (int i = 0; i < max; i++) {
430 QDomElement prod = producers.at(i).toElement();
431 if (prod.attribute("mlt_service") == "framebuffer") {
432 QString slowmotionprod = prod.attribute("resource");
433 slowmotionprod.replace(':', '?');
434 kDebug() << "// FOUND WRONG SLOWMO, new: " << slowmotionprod;
435 prod.setAttribute("resource", slowmotionprod);
438 // move producers to correct place, markers to a global list, fix clip descriptions
439 QDomElement markers = m_doc.createElement("markers");
440 // This will get the xml producers:
441 producers = m_doc.elementsByTagName("producer");
442 max = producers.count();
443 for (int i = 0; i < max; i++) {
444 QDomElement prod = producers.at(0).toElement();
445 // add resource also as a property (to allow path correction in setNewResource())
446 // TODO: will it work with slowmotion? needs testing
447 /*if (!prod.attribute("resource").isEmpty()) {
448 QDomElement prop_resource = m_doc.createElement("property");
449 prop_resource.setAttribute("name", "resource");
450 QDomText resource = m_doc.createTextNode(prod.attribute("resource"));
451 prop_resource.appendChild(resource);
452 prod.appendChild(prop_resource);
454 QDomNode m = prod.firstChild();
456 if (m.toElement().tagName() == "markers") {
457 QDomNodeList prodchilds = m.childNodes();
458 int maxchild = prodchilds.count();
459 for (int k = 0; k < maxchild; k++) {
460 QDomElement mark = prodchilds.at(0).toElement();
461 mark.setAttribute("id", prod.attribute("id"));
462 markers.insertAfter(mark, QDomNode());
465 } else if (prod.attribute("type").toInt() == TEXT) {
466 // convert title clip
467 if (m.toElement().tagName() == "textclip") {
469 QDomElement titleclip = m.toElement();
470 QDomElement title = tdoc.createElement("kdenlivetitle");
471 tdoc.appendChild(title);
472 QDomNodeList objects = titleclip.childNodes();
473 int maxchild = objects.count();
474 for (int k = 0; k < maxchild; k++) {
476 QDomElement ob = objects.at(k).toElement();
477 if (ob.attribute("type") == "3") {
478 // text object - all of this goes into "xmldata"...
479 QDomElement item = tdoc.createElement("item");
480 item.setAttribute("z-index", ob.attribute("z"));
481 item.setAttribute("type", "QGraphicsTextItem");
482 QDomElement position = tdoc.createElement("position");
483 position.setAttribute("x", ob.attribute("x"));
484 position.setAttribute("y", ob.attribute("y"));
485 QDomElement content = tdoc.createElement("content");
486 content.setAttribute("font", ob.attribute("font_family"));
487 content.setAttribute("font-size", ob.attribute("font_size"));
488 content.setAttribute("font-bold", ob.attribute("bold"));
489 content.setAttribute("font-italic", ob.attribute("italic"));
490 content.setAttribute("font-underline", ob.attribute("underline"));
491 QString col = ob.attribute("color");
493 content.setAttribute("font-color", colorToString(c));
494 // todo: These fields are missing from the newly generated xmldata:
495 // transform, startviewport, endviewport, background
497 QDomText conttxt = tdoc.createTextNode(ob.attribute("text"));
498 content.appendChild(conttxt);
499 item.appendChild(position);
500 item.appendChild(content);
501 title.appendChild(item);
502 } else if (ob.attribute("type") == "5") {
504 QDomElement item = tdoc.createElement("item");
505 item.setAttribute("z-index", ob.attribute("z"));
506 item.setAttribute("type", "QGraphicsRectItem");
507 QDomElement position = tdoc.createElement("position");
508 position.setAttribute("x", ob.attribute("x"));
509 position.setAttribute("y", ob.attribute("y"));
510 QDomElement content = tdoc.createElement("content");
511 QString col = ob.attribute("color");
513 content.setAttribute("brushcolor", colorToString(c));
514 QString rect = "0,0,";
515 rect.append(ob.attribute("width"));
517 rect.append(ob.attribute("height"));
518 content.setAttribute("rect", rect);
519 item.appendChild(position);
520 item.appendChild(content);
521 title.appendChild(item);
524 prod.setAttribute("xmldata", tdoc.toString());
525 // mbd todo: This clearly does not work, as every title gets the same name - trying to leave it empty
526 // QStringList titleInfo = TitleWidget::getFreeTitleInfo(projectFolder());
527 // prod.setAttribute("titlename", titleInfo.at(0));
528 // prod.setAttribute("resource", titleInfo.at(1));
529 //kDebug()<<"TITLE DATA:\n"<<tdoc.toString();
531 } // End conversion of title clips.
533 } else if (m.isText()) {
534 QString comment = m.nodeValue();
535 if (!comment.isEmpty()) {
536 prod.setAttribute("description", comment);
541 int duration = prod.attribute("duration").toInt();
542 if (duration > 0) prod.setAttribute("out", QString::number(duration));
543 // The clip goes back in, but text clips should not go back in, at least not modified
544 westley.insertBefore(prod, QDomNode());
547 QDomNode westley0 = m_doc.elementsByTagName("westley").at(0);
548 if (!markers.firstChild().isNull()) westley0.appendChild(markers);
551 * Convert as much of the kdenlivedoc as possible. Use the producer in
552 * westley. First, remove the old stuff from westley, and add a new
553 * empty one. Also, track the max id in order to use it for the adding
556 int max_kproducer_id = 0;
557 westley0.removeChild(infoXmlNode);
558 QDomElement infoXml_new = m_doc.createElement("kdenlivedoc");
559 infoXml_new.setAttribute("profile", profile);
560 infoXml.setAttribute("position", startPos);
562 // Add all the producers that has a resource in westley
563 QDomElement westley_element = westley0.toElement();
564 if (westley_element.isNull()) {
565 kWarning() << "westley0 element in document was not a QDomElement - unable to add producers to new kdenlivedoc";
567 QDomNodeList wproducers = westley_element.elementsByTagName("producer");
568 int kmax = wproducers.count();
569 for (int i = 0; i < kmax; i++) {
570 QDomElement wproducer = wproducers.at(i).toElement();
571 if (wproducer.isNull()) {
572 kWarning() << "Found producer in westley0, that was not a QDomElement";
575 if (wproducer.attribute("id") == "black") continue;
576 // We have to do slightly different things, depending on the type
577 kDebug() << "Converting producer element with type" << wproducer.attribute("type");
578 if (wproducer.attribute("type").toInt() == TEXT) {
579 kDebug() << "Found TEXT element in producer" << endl;
580 QDomElement kproducer = wproducer.cloneNode(true).toElement();
581 kproducer.setTagName("kdenlive_producer");
582 infoXml_new.appendChild(kproducer);
584 * TODO: Perhaps needs some more changes here to
585 * "frequency", aspect ratio as a float, frame_size,
586 * channels, and later, resource and title name
589 QDomElement kproducer = m_doc.createElement("kdenlive_producer");
590 kproducer.setAttribute("id", wproducer.attribute("id"));
591 if (!wproducer.attribute("description").isEmpty())
592 kproducer.setAttribute("description", wproducer.attribute("description"));
593 kproducer.setAttribute("resource", wproducer.attribute("resource"));
594 kproducer.setAttribute("type", wproducer.attribute("type"));
595 // Testing fix for 358
596 if (!wproducer.attribute("aspect_ratio").isEmpty()) {
597 kproducer.setAttribute("aspect_ratio", wproducer.attribute("aspect_ratio"));
599 if (!wproducer.attribute("source_fps").isEmpty()) {
600 kproducer.setAttribute("fps", wproducer.attribute("source_fps"));
602 if (!wproducer.attribute("length").isEmpty()) {
603 kproducer.setAttribute("duration", wproducer.attribute("length"));
605 infoXml_new.appendChild(kproducer);
607 if (wproducer.attribute("id").toInt() > max_kproducer_id) {
608 max_kproducer_id = wproducer.attribute("id").toInt();
612 #define LOOKUP_FOLDER 1
615 * Look through all the folder elements of the old doc, for each folder,
616 * for each producer, get the id, look it up in the new doc, set the
617 * groupname and groupid. Note, this does not work at the moment - at
618 * least one folder shows up missing, and clips with no folder does not
621 //QDomElement infoXml_old = infoXmlNode.toElement();
622 if (!infoXml_old.isNull()) {
623 QDomNodeList folders = infoXml_old.elementsByTagName("folder");
624 int fsize = folders.size();
625 int groupId = max_kproducer_id + 1; // Start at +1 of max id of the kdenlive_producers
626 for (int i = 0; i < fsize; ++i) {
627 QDomElement folder = folders.at(i).toElement();
628 if (!folder.isNull()) {
629 QString groupName = folder.attribute("name");
630 kDebug() << "groupName: " << groupName << " with groupId: " << groupId;
631 QDomNodeList fproducers = folder.elementsByTagName("producer");
632 int psize = fproducers.size();
633 for (int j = 0; j < psize; ++j) {
634 QDomElement fproducer = fproducers.at(j).toElement();
635 if (!fproducer.isNull()) {
636 QString id = fproducer.attribute("id");
637 // This is not very effective, but compared to loading the clips, its a breeze
638 QDomNodeList kdenlive_producers = infoXml_new.elementsByTagName("kdenlive_producer");
639 int kpsize = kdenlive_producers.size();
640 for (int k = 0; k < kpsize; ++k) {
641 QDomElement kproducer = kdenlive_producers.at(k).toElement(); // Its an element for sure
642 if (id == kproducer.attribute("id")) {
643 // We do not check that it already is part of a folder
644 kproducer.setAttribute("groupid", groupId);
645 kproducer.setAttribute("groupname", groupName);
656 QDomNodeList elements = westley.childNodes();
657 max = elements.count();
658 for (int i = 0; i < max; i++) {
659 QDomElement prod = elements.at(0).toElement();
660 westley0.insertAfter(prod, QDomNode());
663 westley0.appendChild(infoXml_new);
665 westley0.removeChild(westley);
667 // adds <avfile /> information to <kdenlive_producer />
668 QDomNodeList kproducers = m_doc.elementsByTagName("kdenlive_producer");
669 QDomNodeList avfiles = infoXml_old.elementsByTagName("avfile");
670 kDebug() << "found" << avfiles.count() << "<avfile />s and" << kproducers.count() << "<kdenlive_producer />s";
671 for (int i = 0; i < avfiles.count(); ++i) {
672 QDomElement avfile = avfiles.at(i).toElement();
673 QDomElement kproducer;
675 kWarning() << "found an <avfile /> that is not a QDomElement";
677 QString id = avfile.attribute("id");
678 // this is horrible, must be rewritten, it's just for test
679 for (int j = 0; j < kproducers.count(); ++j) {
680 //kDebug() << "checking <kdenlive_producer /> with id" << kproducers.at(j).toElement().attribute("id");
681 if (kproducers.at(j).toElement().attribute("id") == id) {
682 kproducer = kproducers.at(j).toElement();
686 if (kproducer == QDomElement())
687 kWarning() << "no match for <avfile /> with id =" << id;
689 //kDebug() << "ready to set additional <avfile />'s attributes (id =" << id << ")";
690 kproducer.setAttribute("channels", avfile.attribute("channels"));
691 kproducer.setAttribute("duration", avfile.attribute("duration"));
692 kproducer.setAttribute("frame_size", avfile.attribute("width") + 'x' + avfile.attribute("height"));
693 kproducer.setAttribute("frequency", avfile.attribute("frequency"));
694 if (kproducer.attribute("description").isEmpty() && !avfile.attribute("description").isEmpty())
695 kproducer.setAttribute("description", avfile.attribute("description"));
699 infoXml = infoXml_new;
702 if (version <= 0.81) {
703 // Add the tracks information
704 QString tracksOrder = infoXml.attribute("tracks");
705 if (tracksOrder.isEmpty()) {
706 QDomNodeList tracks = m_doc.elementsByTagName("track");
707 for (int i = 0; i < tracks.count(); i++) {
708 QDomElement track = tracks.at(i).toElement();
709 if (track.attribute("producer") != "black_track") {
710 if (track.attribute("hide") == "video")
711 tracksOrder.append('a');
713 tracksOrder.append('v');
717 QDomElement tracksinfo = m_doc.createElement("tracksinfo");
718 for (int i = 0; i < tracksOrder.size(); i++) {
719 QDomElement trackinfo = m_doc.createElement("trackinfo");
720 if (tracksOrder.data()[i] == 'a') {
721 trackinfo.setAttribute("type", "audio");
722 trackinfo.setAttribute("blind", true);
724 trackinfo.setAttribute("blind", false);
725 trackinfo.setAttribute("mute", false);
726 trackinfo.setAttribute("locked", false);
727 tracksinfo.appendChild(trackinfo);
729 infoXml.appendChild(tracksinfo);
732 if (version <= 0.82) {
733 // Convert <westley />s in <mlt />s (MLT extreme makeover)
734 QDomNodeList westleyNodes = m_doc.elementsByTagName("westley");
735 for (int i = 0; i < westleyNodes.count(); i++) {
736 QDomElement westley = westleyNodes.at(i).toElement();
737 westley.setTagName("mlt");
741 if (version <= 0.83) {
742 // Replace point size with pixel size in text titles
743 if (m_doc.toString().contains("font-size")) {
744 KMessageBox::ButtonCode convert = KMessageBox::Continue;
745 QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
746 for (int i = 0; i < kproducerNodes.count() && convert != KMessageBox::No; ++i) {
747 QDomElement kproducer = kproducerNodes.at(i).toElement();
748 if (kproducer.attribute("type").toInt() == TEXT) {
750 data.setContent(kproducer.attribute("xmldata"));
751 QDomNodeList items = data.firstChild().childNodes();
752 for (int j = 0; j < items.count() && convert != KMessageBox::No; ++j) {
753 if (items.at(j).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
754 QDomNamedNodeMap textProperties = items.at(j).namedItem("content").attributes();
755 if (textProperties.namedItem("font-pixel-size").isNull() && !textProperties.namedItem("font-size").isNull()) {
756 // Ask the user if he wants to convert
757 if (convert != KMessageBox::Yes && convert != KMessageBox::No)
758 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"));
759 if (convert == KMessageBox::Yes) {
761 font.setPointSize(textProperties.namedItem("font-size").nodeValue().toInt());
762 QDomElement content = items.at(j).namedItem("content").toElement();
763 content.setAttribute("font-pixel-size", QFontInfo(font).pixelSize());
764 content.removeAttribute("font-size");
765 kproducer.setAttribute("xmldata", data.toString());
767 * You may be tempted to delete the preview file
768 * to force its recreation: bad idea (see
769 * http://www.kdenlive.org/mantis/view.php?id=749)
779 // Fill the <documentproperties /> element
780 QDomElement docProperties = infoXml.firstChildElement("documentproperties");
781 if (docProperties.isNull()) {
782 docProperties = m_doc.createElement("documentproperties");
783 docProperties.setAttribute("zonein", infoXml.attribute("zonein"));
784 docProperties.setAttribute("zoneout", infoXml.attribute("zoneout"));
785 docProperties.setAttribute("zoom", infoXml.attribute("zoom"));
786 docProperties.setAttribute("position", infoXml.attribute("position"));
787 infoXml.appendChild(docProperties);
791 if (version <= 0.84) {
792 // update the title clips to use the new MLT kdenlivetitle producer
793 QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
794 for (int i = 0; i < kproducerNodes.count(); ++i) {
795 QDomElement kproducer = kproducerNodes.at(i).toElement();
796 if (kproducer.attribute("type").toInt() == TEXT) {
797 QString data = kproducer.attribute("xmldata");
798 QString datafile = kproducer.attribute("resource");
799 if (!datafile.endsWith(".kdenlivetitle")) {
800 datafile = QString();
801 kproducer.setAttribute("resource", QString());
803 QString id = kproducer.attribute("id");
804 QDomNodeList mltproducers = m_doc.elementsByTagName("producer");
805 bool foundData = false;
806 bool foundResource = false;
807 bool foundService = false;
808 for (int j = 0; j < mltproducers.count(); j++) {
809 QDomElement wproducer = mltproducers.at(j).toElement();
810 if (wproducer.attribute("id") == id) {
811 QDomNodeList props = wproducer.childNodes();
812 for (int k = 0; k < props.count(); k++) {
813 if (props.at(k).toElement().attribute("name") == "xmldata") {
814 props.at(k).firstChild().setNodeValue(data);
816 } else if (props.at(k).toElement().attribute("name") == "mlt_service") {
817 props.at(k).firstChild().setNodeValue("kdenlivetitle");
819 } else if (props.at(k).toElement().attribute("name") == "resource") {
820 props.at(k).firstChild().setNodeValue(datafile);
821 foundResource = true;
825 QDomElement e = m_doc.createElement("property");
826 e.setAttribute("name", "xmldata");
827 QDomText value = m_doc.createTextNode(data);
828 e.appendChild(value);
829 wproducer.appendChild(e);
832 QDomElement e = m_doc.createElement("property");
833 e.setAttribute("name", "mlt_service");
834 QDomText value = m_doc.createTextNode("kdenlivetitle");
835 e.appendChild(value);
836 wproducer.appendChild(e);
838 if (!foundResource) {
839 QDomElement e = m_doc.createElement("property");
840 e.setAttribute("name", "resource");
841 QDomText value = m_doc.createTextNode(datafile);
842 e.appendChild(value);
843 wproducer.appendChild(e);
851 if (version <= 0.85) {
852 // update the LADSPA effects to use the new ladspa.id format instead of external xml file
853 QDomNodeList effectNodes = m_doc.elementsByTagName("filter");
854 for (int i = 0; i < effectNodes.count(); ++i) {
855 QDomElement effect = effectNodes.at(i).toElement();
856 if (EffectsList::property(effect, "mlt_service") == "ladspa") {
857 // Needs to be converted
858 QStringList info = getInfoFromEffectName(EffectsList::property(effect, "kdenlive_id"));
859 if (info.isEmpty()) continue;
860 // info contains the correct ladspa.id from kdenlive effect name, and a list of parameter's old and new names
861 EffectsList::setProperty(effect, "kdenlive_id", info.at(0));
862 EffectsList::setProperty(effect, "tag", info.at(0));
863 EffectsList::setProperty(effect, "mlt_service", info.at(0));
864 EffectsList::removeProperty(effect, "src");
865 for (int j = 1; j < info.size(); j++) {
866 QString value = EffectsList::property(effect, info.at(j).section('=', 0, 0));
867 if (!value.isEmpty()) {
868 // update parameter name
869 EffectsList::renameProperty(effect, info.at(j).section('=', 0, 0), info.at(j).section('=', 1, 1));
877 // The document has been converted: mark it as modified
878 infoXml.setAttribute("version", currentVersion);
883 QStringList DocumentValidator::getInfoFromEffectName(const QString oldName)
886 // Returns a list to convert old Kdenlive ladspa effects
887 if (oldName == "pitch_shift") {
888 info << "ladspa.1433";
891 else if (oldName == "vinyl") {
892 info << "ladspa.1905";
899 else if (oldName == "room_reverb") {
900 info << "ladspa.1216";
905 else if (oldName == "reverb") {
906 info << "ladspa.1423";
910 else if (oldName == "rate_scale") {
911 info << "ladspa.1417";
914 else if (oldName == "pitch_scale") {
915 info << "ladspa.1193";
918 else if (oldName == "phaser") {
919 info << "ladspa.1217";
922 info << "feedback=2";
925 else if (oldName == "limiter") {
926 info << "ladspa.1913";
931 else if (oldName == "equalizer_15") {
932 info << "ladspa.1197";
949 else if (oldName == "equalizer") {
950 info << "ladspa.1901";
955 else if (oldName == "declipper") {
956 info << "ladspa.1195";
961 QString DocumentValidator::colorToString(const QColor& c)
963 QString ret = "%1,%2,%3,%4";
964 ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
968 bool DocumentValidator::isProject() const
970 QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
971 return !infoXmlNode.isNull();
974 bool DocumentValidator::isModified() const