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"
23 #include "initeffects.h"
26 #include <KMessageBox>
27 #include <KApplication>
34 #include <mlt++/Mlt.h>
39 DocumentValidator::DocumentValidator(QDomDocument doc):
44 bool DocumentValidator::validate(const double currentVersion)
46 QDomElement mlt = m_doc.firstChildElement("mlt");
47 // At least the root element must be there
51 QDomElement kdenliveDoc = mlt.firstChildElement("kdenlivedoc");
52 // Check if we're validating a Kdenlive project
53 if (kdenliveDoc.isNull())
56 // Previous MLT / Kdenlive versions used C locale by default
57 QLocale documentLocale = QLocale::c();
59 if (mlt.hasAttribute("LC_NUMERIC")) {
60 // Set locale for the document
61 // WARNING: what should be done in case the locale does not exist on the system?
62 setlocale(LC_NUMERIC, mlt.attribute("LC_NUMERIC").toUtf8().constData());
63 documentLocale = QLocale(mlt.attribute("LC_NUMERIC"));
66 if (documentLocale != QLocale()) {
67 QLocale::setDefault(documentLocale);
68 // locale conversion might need to be redone
69 initEffects::parseEffectFiles();
72 // TODO: remove after string freeze
74 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"));
76 // Upgrade the document to the latest version
77 if (!upgrade(documentLocale.toDouble(kdenliveDoc.attribute("version")), currentVersion))
81 * Check the syntax (this will be replaced by XSD validation with Qt 4.6)
82 * and correct some errors
85 // Return (or create) the tractor
86 QDomElement tractor = mlt.firstChildElement("tractor");
87 if (tractor.isNull()) {
89 tractor = m_doc.createElement("tractor");
90 tractor.setAttribute("global_feed", "1");
91 tractor.setAttribute("in", "0");
92 tractor.setAttribute("out", "-1");
93 tractor.setAttribute("id", "maintractor");
94 mlt.appendChild(tractor);
98 * Make sure at least one track exists, and they're equal in number to
99 * to the maximum between MLT and Kdenlive playlists and tracks
101 QDomNodeList playlists = m_doc.elementsByTagName("playlist");
102 int tracksMax = playlists.count() - 1; // Remove the black track
103 QDomNodeList tracks = tractor.elementsByTagName("track");
104 tracksMax = qMax(tracks.count() - 1, tracksMax);
105 QDomNodeList tracksinfo = kdenliveDoc.elementsByTagName("trackinfo");
106 tracksMax = qMax(tracksinfo.count(), tracksMax);
107 tracksMax = qMax(1, tracksMax); // Force existance of one track
108 if (playlists.count() - 1 < tracksMax ||
109 tracks.count() - 1 < tracksMax ||
110 tracksinfo.count() < tracksMax) {
111 kDebug() << "//// WARNING, PROJECT IS CORRUPTED, MISSING TRACK";
114 // use the MLT tracks as reference
115 if (tracks.count() - 1 < tracksMax) {
116 // Looks like one MLT track is missing, remove the extra Kdenlive track if there is one.
117 if (tracksinfo.count() != tracks.count() - 1) {
118 // The Kdenlive tracks are not ok, clear and rebuild them
119 QDomNode tinfo = kdenliveDoc.elementsByTagName("tracksinfo").at(0);
120 QDomNode tnode = tinfo.firstChild();
121 while (!tnode.isNull()) {
122 tinfo.removeChild(tnode);
123 tnode = tinfo.firstChild();
126 for (int i = 1; i < tracks.count(); i++) {
127 QString hide = tracks.at(i).toElement().attribute("hide");
128 QDomElement newTrack = m_doc.createElement("trackinfo");
129 if (hide == "video") {
131 newTrack.setAttribute("type", "audio");
132 newTrack.setAttribute("blind", 1);
133 newTrack.setAttribute("mute", 0);
134 newTrack.setAttribute("lock", 0);
136 newTrack.setAttribute("blind", 0);
137 newTrack.setAttribute("mute", 0);
138 newTrack.setAttribute("lock", 0);
140 tinfo.appendChild(newTrack);
145 if (playlists.count() - 1 < tracksMax) {
146 difference = tracksMax - (playlists.count() - 1);
147 for (int i = 0; i < difference; ++i) {
148 QDomElement playlist = m_doc.createElement("playlist");
149 mlt.appendChild(playlist);
152 if (tracks.count() - 1 < tracksMax) {
153 difference = tracksMax - (tracks.count() - 1);
154 for (int i = 0; i < difference; ++i) {
155 QDomElement track = m_doc.createElement("track");
156 tractor.appendChild(track);
159 if (tracksinfo.count() < tracksMax) {
160 QDomElement tracksinfoElm = kdenliveDoc.firstChildElement("tracksinfo");
161 if (tracksinfoElm.isNull()) {
162 tracksinfoElm = m_doc.createElement("tracksinfo");
163 kdenliveDoc.appendChild(tracksinfoElm);
165 difference = tracksMax - tracksinfo.count();
166 for (int i = 0; i < difference; ++i) {
167 QDomElement trackinfo = m_doc.createElement("trackinfo");
168 trackinfo.setAttribute("mute", "0");
169 trackinfo.setAttribute("locked", "0");
170 tracksinfoElm.appendChild(trackinfo);
175 // TODO: check the tracks references
176 // TODO: check internal mix transitions
183 bool DocumentValidator::upgrade(double version, const double currentVersion)
185 kDebug() << "Opening a document with version " << version;
187 // No conversion needed
188 if (version == currentVersion) {
192 // The document is too new
193 if (version > currentVersion) {
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.\nPlease consider upgrading your Kdenlive version.", version), i18n("Unable to open project"));
199 // Unsupported document versions
200 if (version == 0.5 || version == 0.7) {
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.", version), i18n("Unable to open project"));
207 QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
208 QDomElement infoXml = infoXmlNode.toElement();
209 infoXml.setAttribute("upgraded", "1");
211 if (version <= 0.6) {
212 QDomElement infoXml_old = infoXmlNode.cloneNode(true).toElement(); // Needed for folders
213 QDomNode westley = m_doc.elementsByTagName("westley").at(1);
214 QDomNode tractor = m_doc.elementsByTagName("tractor").at(0);
215 QDomNode multitrack = m_doc.elementsByTagName("multitrack").at(0);
216 QDomNodeList playlists = m_doc.elementsByTagName("playlist");
218 QDomNode props = m_doc.elementsByTagName("properties").at(0).toElement();
219 QString profile = props.toElement().attribute("videoprofile");
220 int startPos = props.toElement().attribute("timeline_position").toInt();
221 if (profile == "dv_wide")
222 profile = "dv_pal_wide";
224 // move playlists outside of tractor and add the tracks instead
225 int max = playlists.count();
226 if (westley.isNull()) {
227 westley = m_doc.createElement("westley");
228 m_doc.documentElement().appendChild(westley);
230 if (tractor.isNull()) {
231 kDebug() << "// NO MLT PLAYLIST, building empty one";
232 QDomElement blank_tractor = m_doc.createElement("tractor");
233 westley.appendChild(blank_tractor);
234 QDomElement blank_playlist = m_doc.createElement("playlist");
235 blank_playlist.setAttribute("id", "black_track");
236 westley.insertBefore(blank_playlist, QDomNode());
237 QDomElement blank_track = m_doc.createElement("track");
238 blank_track.setAttribute("producer", "black_track");
239 blank_tractor.appendChild(blank_track);
241 QDomNodeList kdenlivetracks = m_doc.elementsByTagName("kdenlivetrack");
242 for (int i = 0; i < kdenlivetracks.count(); i++) {
243 blank_playlist = m_doc.createElement("playlist");
244 blank_playlist.setAttribute("id", "playlist" + QString::number(i));
245 westley.insertBefore(blank_playlist, QDomNode());
246 blank_track = m_doc.createElement("track");
247 blank_track.setAttribute("producer", "playlist" + QString::number(i));
248 blank_tractor.appendChild(blank_track);
249 if (kdenlivetracks.at(i).toElement().attribute("cliptype") == "Sound") {
250 blank_playlist.setAttribute("hide", "video");
251 blank_track.setAttribute("hide", "video");
254 } else for (int i = 0; i < max; i++) {
255 QDomNode n = playlists.at(i);
256 westley.insertBefore(n, QDomNode());
257 QDomElement pl = n.toElement();
258 QDomElement track = m_doc.createElement("track");
259 QString trackType = pl.attribute("hide");
260 if (!trackType.isEmpty())
261 track.setAttribute("hide", trackType);
262 QString playlist_id = pl.attribute("id");
263 if (playlist_id.isEmpty()) {
264 playlist_id = "black_track";
265 pl.setAttribute("id", playlist_id);
267 track.setAttribute("producer", playlist_id);
268 //tractor.appendChild(track);
269 #define KEEP_TRACK_ORDER 1
270 #ifdef KEEP_TRACK_ORDER
271 tractor.insertAfter(track, QDomNode());
273 // Insert the new track in an order that hopefully matches the 3 video, then 2 audio tracks of Kdenlive 0.7.0
274 // insertion sort - O( tracks*tracks )
275 // Note, this breaks _all_ transitions - but you can move them up and down afterwards.
276 QDomElement tractor_elem = tractor.toElement();
277 if (! tractor_elem.isNull()) {
278 QDomNodeList tracks = tractor_elem.elementsByTagName("track");
279 int size = tracks.size();
281 tractor.insertAfter(track, QDomNode());
283 bool inserted = false;
284 for (int i = 0; i < size; ++i) {
285 QDomElement track_elem = tracks.at(i).toElement();
286 if (track_elem.isNull()) {
287 tractor.insertAfter(track, QDomNode());
291 kDebug() << "playlist_id: " << playlist_id << " producer:" << track_elem.attribute("producer");
292 if (playlist_id < track_elem.attribute("producer")) {
293 tractor.insertBefore(track, track_elem);
299 // Reach here, no insertion, insert last
301 tractor.insertAfter(track, QDomNode());
305 kWarning() << "tractor was not a QDomElement";
306 tractor.insertAfter(track, QDomNode());
310 tractor.removeChild(multitrack);
312 // audio track mixing transitions should not be added to track view, so add required attribute
313 QDomNodeList transitions = m_doc.elementsByTagName("transition");
314 max = transitions.count();
315 for (int i = 0; i < max; i++) {
316 QDomElement tr = transitions.at(i).toElement();
317 if (tr.attribute("combine") == "1" && tr.attribute("mlt_service") == "mix") {
318 QDomElement property = m_doc.createElement("property");
319 property.setAttribute("name", "internal_added");
320 QDomText value = m_doc.createTextNode("237");
321 property.appendChild(value);
322 tr.appendChild(property);
323 property = m_doc.createElement("property");
324 property.setAttribute("name", "mlt_service");
325 value = m_doc.createTextNode("mix");
326 property.appendChild(value);
327 tr.appendChild(property);
329 // convert transition
330 QDomNamedNodeMap attrs = tr.attributes();
331 for (int j = 0; j < attrs.count(); j++) {
332 QString attrName = attrs.item(j).nodeName();
333 if (attrName != "in" && attrName != "out" && attrName != "id") {
334 QDomElement property = m_doc.createElement("property");
335 property.setAttribute("name", attrName);
336 QDomText value = m_doc.createTextNode(attrs.item(j).nodeValue());
337 property.appendChild(value);
338 tr.appendChild(property);
344 // move transitions after tracks
345 for (int i = 0; i < max; i++) {
346 tractor.insertAfter(transitions.at(0), QDomNode());
349 // Fix filters format
350 QDomNodeList entries = m_doc.elementsByTagName("entry");
351 max = entries.count();
352 for (int i = 0; i < max; i++) {
355 QDomNode m = entries.at(i).firstChild();
356 while (!m.isNull()) {
357 if (m.toElement().tagName() == "filter") {
358 QDomElement filt = m.toElement();
359 QDomNamedNodeMap attrs = filt.attributes();
360 QString current_id = filt.attribute("kdenlive_id");
361 if (current_id != last_id) {
363 last_id = current_id;
365 QDomElement e = m_doc.createElement("property");
366 e.setAttribute("name", "kdenlive_ix");
367 QDomText value = m_doc.createTextNode(QString::number(effectix));
368 e.appendChild(value);
370 for (int j = 0; j < attrs.count(); j++) {
371 QDomAttr a = attrs.item(j).toAttr();
373 kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
374 QDomElement e = m_doc.createElement("property");
375 e.setAttribute("name", a.name());
376 QDomText value = m_doc.createTextNode(a.value());
377 e.appendChild(value);
388 QDomNodeList filters = m_doc.elementsByTagName("filter");
389 max = filters.count();
392 for (int i = 0; i < max; i++) {
393 QDomElement filt = filters.at(i).toElement();
394 QDomNamedNodeMap attrs = filt.attributes();
395 QString current_id = filt.attribute("kdenlive_id");
396 if (current_id != last_id) {
398 last_id = current_id;
400 QDomElement e = m_doc.createElement("property");
401 e.setAttribute("name", "kdenlive_ix");
402 QDomText value = m_doc.createTextNode(QString::number(1));
403 e.appendChild(value);
405 for (int j = 0; j < attrs.count(); j++) {
406 QDomAttr a = attrs.item(j).toAttr();
408 kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
409 QDomElement e = m_doc.createElement("property");
410 e.setAttribute("name", a.name());
411 QDomText value = m_doc.createTextNode(a.value());
412 e.appendChild(value);
419 QDomNodeList producers = westley.toElement().elementsByTagName("producer");
420 max = producers.count();
421 for (int i = 0; i < max; i++) {
422 QDomElement prod = producers.at(i).toElement();
423 if (prod.attribute("mlt_service") == "framebuffer") {
424 QString slowmotionprod = prod.attribute("resource");
425 slowmotionprod.replace(':', '?');
426 kDebug() << "// FOUND WRONG SLOWMO, new: " << slowmotionprod;
427 prod.setAttribute("resource", slowmotionprod);
430 // move producers to correct place, markers to a global list, fix clip descriptions
431 QDomElement markers = m_doc.createElement("markers");
432 // This will get the xml producers:
433 producers = m_doc.elementsByTagName("producer");
434 max = producers.count();
435 for (int i = 0; i < max; i++) {
436 QDomElement prod = producers.at(0).toElement();
437 // add resource also as a property (to allow path correction in setNewResource())
438 // TODO: will it work with slowmotion? needs testing
439 /*if (!prod.attribute("resource").isEmpty()) {
440 QDomElement prop_resource = m_doc.createElement("property");
441 prop_resource.setAttribute("name", "resource");
442 QDomText resource = m_doc.createTextNode(prod.attribute("resource"));
443 prop_resource.appendChild(resource);
444 prod.appendChild(prop_resource);
446 QDomNode m = prod.firstChild();
448 if (m.toElement().tagName() == "markers") {
449 QDomNodeList prodchilds = m.childNodes();
450 int maxchild = prodchilds.count();
451 for (int k = 0; k < maxchild; k++) {
452 QDomElement mark = prodchilds.at(0).toElement();
453 mark.setAttribute("id", prod.attribute("id"));
454 markers.insertAfter(mark, QDomNode());
457 } else if (prod.attribute("type").toInt() == TEXT) {
458 // convert title clip
459 if (m.toElement().tagName() == "textclip") {
461 QDomElement titleclip = m.toElement();
462 QDomElement title = tdoc.createElement("kdenlivetitle");
463 tdoc.appendChild(title);
464 QDomNodeList objects = titleclip.childNodes();
465 int maxchild = objects.count();
466 for (int k = 0; k < maxchild; k++) {
468 QDomElement ob = objects.at(k).toElement();
469 if (ob.attribute("type") == "3") {
470 // text object - all of this goes into "xmldata"...
471 QDomElement item = tdoc.createElement("item");
472 item.setAttribute("z-index", ob.attribute("z"));
473 item.setAttribute("type", "QGraphicsTextItem");
474 QDomElement position = tdoc.createElement("position");
475 position.setAttribute("x", ob.attribute("x"));
476 position.setAttribute("y", ob.attribute("y"));
477 QDomElement content = tdoc.createElement("content");
478 content.setAttribute("font", ob.attribute("font_family"));
479 content.setAttribute("font-size", ob.attribute("font_size"));
480 content.setAttribute("font-bold", ob.attribute("bold"));
481 content.setAttribute("font-italic", ob.attribute("italic"));
482 content.setAttribute("font-underline", ob.attribute("underline"));
483 QString col = ob.attribute("color");
485 content.setAttribute("font-color", colorToString(c));
486 // todo: These fields are missing from the newly generated xmldata:
487 // transform, startviewport, endviewport, background
489 QDomText conttxt = tdoc.createTextNode(ob.attribute("text"));
490 content.appendChild(conttxt);
491 item.appendChild(position);
492 item.appendChild(content);
493 title.appendChild(item);
494 } else if (ob.attribute("type") == "5") {
496 QDomElement item = tdoc.createElement("item");
497 item.setAttribute("z-index", ob.attribute("z"));
498 item.setAttribute("type", "QGraphicsRectItem");
499 QDomElement position = tdoc.createElement("position");
500 position.setAttribute("x", ob.attribute("x"));
501 position.setAttribute("y", ob.attribute("y"));
502 QDomElement content = tdoc.createElement("content");
503 QString col = ob.attribute("color");
505 content.setAttribute("brushcolor", colorToString(c));
506 QString rect = "0,0,";
507 rect.append(ob.attribute("width"));
509 rect.append(ob.attribute("height"));
510 content.setAttribute("rect", rect);
511 item.appendChild(position);
512 item.appendChild(content);
513 title.appendChild(item);
516 prod.setAttribute("xmldata", tdoc.toString());
517 // mbd todo: This clearly does not work, as every title gets the same name - trying to leave it empty
518 // QStringList titleInfo = TitleWidget::getFreeTitleInfo(projectFolder());
519 // prod.setAttribute("titlename", titleInfo.at(0));
520 // prod.setAttribute("resource", titleInfo.at(1));
521 //kDebug()<<"TITLE DATA:\n"<<tdoc.toString();
523 } // End conversion of title clips.
525 } else if (m.isText()) {
526 QString comment = m.nodeValue();
527 if (!comment.isEmpty()) {
528 prod.setAttribute("description", comment);
533 int duration = prod.attribute("duration").toInt();
534 if (duration > 0) prod.setAttribute("out", QString::number(duration));
535 // The clip goes back in, but text clips should not go back in, at least not modified
536 westley.insertBefore(prod, QDomNode());
539 QDomNode westley0 = m_doc.elementsByTagName("westley").at(0);
540 if (!markers.firstChild().isNull()) westley0.appendChild(markers);
543 * Convert as much of the kdenlivedoc as possible. Use the producer in
544 * westley. First, remove the old stuff from westley, and add a new
545 * empty one. Also, track the max id in order to use it for the adding
548 int max_kproducer_id = 0;
549 westley0.removeChild(infoXmlNode);
550 QDomElement infoXml_new = m_doc.createElement("kdenlivedoc");
551 infoXml_new.setAttribute("profile", profile);
552 infoXml.setAttribute("position", startPos);
554 // Add all the producers that has a resource in westley
555 QDomElement westley_element = westley0.toElement();
556 if (westley_element.isNull()) {
557 kWarning() << "westley0 element in document was not a QDomElement - unable to add producers to new kdenlivedoc";
559 QDomNodeList wproducers = westley_element.elementsByTagName("producer");
560 int kmax = wproducers.count();
561 for (int i = 0; i < kmax; i++) {
562 QDomElement wproducer = wproducers.at(i).toElement();
563 if (wproducer.isNull()) {
564 kWarning() << "Found producer in westley0, that was not a QDomElement";
567 if (wproducer.attribute("id") == "black") continue;
568 // We have to do slightly different things, depending on the type
569 kDebug() << "Converting producer element with type" << wproducer.attribute("type");
570 if (wproducer.attribute("type").toInt() == TEXT) {
571 kDebug() << "Found TEXT element in producer" << endl;
572 QDomElement kproducer = wproducer.cloneNode(true).toElement();
573 kproducer.setTagName("kdenlive_producer");
574 infoXml_new.appendChild(kproducer);
576 * TODO: Perhaps needs some more changes here to
577 * "frequency", aspect ratio as a float, frame_size,
578 * channels, and later, resource and title name
581 QDomElement kproducer = m_doc.createElement("kdenlive_producer");
582 kproducer.setAttribute("id", wproducer.attribute("id"));
583 if (!wproducer.attribute("description").isEmpty())
584 kproducer.setAttribute("description", wproducer.attribute("description"));
585 kproducer.setAttribute("resource", wproducer.attribute("resource"));
586 kproducer.setAttribute("type", wproducer.attribute("type"));
587 // Testing fix for 358
588 if (!wproducer.attribute("aspect_ratio").isEmpty()) {
589 kproducer.setAttribute("aspect_ratio", wproducer.attribute("aspect_ratio"));
591 if (!wproducer.attribute("source_fps").isEmpty()) {
592 kproducer.setAttribute("fps", wproducer.attribute("source_fps"));
594 if (!wproducer.attribute("length").isEmpty()) {
595 kproducer.setAttribute("duration", wproducer.attribute("length"));
597 infoXml_new.appendChild(kproducer);
599 if (wproducer.attribute("id").toInt() > max_kproducer_id) {
600 max_kproducer_id = wproducer.attribute("id").toInt();
604 #define LOOKUP_FOLDER 1
607 * Look through all the folder elements of the old doc, for each folder,
608 * for each producer, get the id, look it up in the new doc, set the
609 * groupname and groupid. Note, this does not work at the moment - at
610 * least one folder shows up missing, and clips with no folder does not
613 //QDomElement infoXml_old = infoXmlNode.toElement();
614 if (!infoXml_old.isNull()) {
615 QDomNodeList folders = infoXml_old.elementsByTagName("folder");
616 int fsize = folders.size();
617 int groupId = max_kproducer_id + 1; // Start at +1 of max id of the kdenlive_producers
618 for (int i = 0; i < fsize; ++i) {
619 QDomElement folder = folders.at(i).toElement();
620 if (!folder.isNull()) {
621 QString groupName = folder.attribute("name");
622 kDebug() << "groupName: " << groupName << " with groupId: " << groupId;
623 QDomNodeList fproducers = folder.elementsByTagName("producer");
624 int psize = fproducers.size();
625 for (int j = 0; j < psize; ++j) {
626 QDomElement fproducer = fproducers.at(j).toElement();
627 if (!fproducer.isNull()) {
628 QString id = fproducer.attribute("id");
629 // This is not very effective, but compared to loading the clips, its a breeze
630 QDomNodeList kdenlive_producers = infoXml_new.elementsByTagName("kdenlive_producer");
631 int kpsize = kdenlive_producers.size();
632 for (int k = 0; k < kpsize; ++k) {
633 QDomElement kproducer = kdenlive_producers.at(k).toElement(); // Its an element for sure
634 if (id == kproducer.attribute("id")) {
635 // We do not check that it already is part of a folder
636 kproducer.setAttribute("groupid", groupId);
637 kproducer.setAttribute("groupname", groupName);
648 QDomNodeList elements = westley.childNodes();
649 max = elements.count();
650 for (int i = 0; i < max; i++) {
651 QDomElement prod = elements.at(0).toElement();
652 westley0.insertAfter(prod, QDomNode());
655 westley0.appendChild(infoXml_new);
657 westley0.removeChild(westley);
659 // adds <avfile /> information to <kdenlive_producer />
660 QDomNodeList kproducers = m_doc.elementsByTagName("kdenlive_producer");
661 QDomNodeList avfiles = infoXml_old.elementsByTagName("avfile");
662 kDebug() << "found" << avfiles.count() << "<avfile />s and" << kproducers.count() << "<kdenlive_producer />s";
663 for (int i = 0; i < avfiles.count(); ++i) {
664 QDomElement avfile = avfiles.at(i).toElement();
665 QDomElement kproducer;
667 kWarning() << "found an <avfile /> that is not a QDomElement";
669 QString id = avfile.attribute("id");
670 // this is horrible, must be rewritten, it's just for test
671 for (int j = 0; j < kproducers.count(); ++j) {
672 //kDebug() << "checking <kdenlive_producer /> with id" << kproducers.at(j).toElement().attribute("id");
673 if (kproducers.at(j).toElement().attribute("id") == id) {
674 kproducer = kproducers.at(j).toElement();
678 if (kproducer == QDomElement())
679 kWarning() << "no match for <avfile /> with id =" << id;
681 //kDebug() << "ready to set additional <avfile />'s attributes (id =" << id << ")";
682 kproducer.setAttribute("channels", avfile.attribute("channels"));
683 kproducer.setAttribute("duration", avfile.attribute("duration"));
684 kproducer.setAttribute("frame_size", avfile.attribute("width") + 'x' + avfile.attribute("height"));
685 kproducer.setAttribute("frequency", avfile.attribute("frequency"));
686 if (kproducer.attribute("description").isEmpty() && !avfile.attribute("description").isEmpty())
687 kproducer.setAttribute("description", avfile.attribute("description"));
691 infoXml = infoXml_new;
694 if (version <= 0.81) {
695 // Add the tracks information
696 QString tracksOrder = infoXml.attribute("tracks");
697 if (tracksOrder.isEmpty()) {
698 QDomNodeList tracks = m_doc.elementsByTagName("track");
699 for (int i = 0; i < tracks.count(); i++) {
700 QDomElement track = tracks.at(i).toElement();
701 if (track.attribute("producer") != "black_track") {
702 if (track.attribute("hide") == "video")
703 tracksOrder.append('a');
705 tracksOrder.append('v');
709 QDomElement tracksinfo = m_doc.createElement("tracksinfo");
710 for (int i = 0; i < tracksOrder.size(); i++) {
711 QDomElement trackinfo = m_doc.createElement("trackinfo");
712 if (tracksOrder.data()[i] == 'a') {
713 trackinfo.setAttribute("type", "audio");
714 trackinfo.setAttribute("blind", true);
716 trackinfo.setAttribute("blind", false);
717 trackinfo.setAttribute("mute", false);
718 trackinfo.setAttribute("locked", false);
719 tracksinfo.appendChild(trackinfo);
721 infoXml.appendChild(tracksinfo);
724 if (version <= 0.82) {
725 // Convert <westley />s in <mlt />s (MLT extreme makeover)
726 QDomNodeList westleyNodes = m_doc.elementsByTagName("westley");
727 for (int i = 0; i < westleyNodes.count(); i++) {
728 QDomElement westley = westleyNodes.at(i).toElement();
729 westley.setTagName("mlt");
733 if (version <= 0.83) {
734 // Replace point size with pixel size in text titles
735 if (m_doc.toString().contains("font-size")) {
736 KMessageBox::ButtonCode convert = KMessageBox::Continue;
737 QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
738 for (int i = 0; i < kproducerNodes.count() && convert != KMessageBox::No; ++i) {
739 QDomElement kproducer = kproducerNodes.at(i).toElement();
740 if (kproducer.attribute("type").toInt() == TEXT) {
742 data.setContent(kproducer.attribute("xmldata"));
743 QDomNodeList items = data.firstChild().childNodes();
744 for (int j = 0; j < items.count() && convert != KMessageBox::No; ++j) {
745 if (items.at(j).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
746 QDomNamedNodeMap textProperties = items.at(j).namedItem("content").attributes();
747 if (textProperties.namedItem("font-pixel-size").isNull() && !textProperties.namedItem("font-size").isNull()) {
748 // Ask the user if he wants to convert
749 if (convert != KMessageBox::Yes && convert != KMessageBox::No)
750 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"));
751 if (convert == KMessageBox::Yes) {
753 font.setPointSize(textProperties.namedItem("font-size").nodeValue().toInt());
754 QDomElement content = items.at(j).namedItem("content").toElement();
755 content.setAttribute("font-pixel-size", QFontInfo(font).pixelSize());
756 content.removeAttribute("font-size");
757 kproducer.setAttribute("xmldata", data.toString());
759 * You may be tempted to delete the preview file
760 * to force its recreation: bad idea (see
761 * http://www.kdenlive.org/mantis/view.php?id=749)
771 // Fill the <documentproperties /> element
772 QDomElement docProperties = infoXml.firstChildElement("documentproperties");
773 if (docProperties.isNull()) {
774 docProperties = m_doc.createElement("documentproperties");
775 docProperties.setAttribute("zonein", infoXml.attribute("zonein"));
776 docProperties.setAttribute("zoneout", infoXml.attribute("zoneout"));
777 docProperties.setAttribute("zoom", infoXml.attribute("zoom"));
778 docProperties.setAttribute("position", infoXml.attribute("position"));
779 infoXml.appendChild(docProperties);
783 if (version <= 0.84) {
784 // update the title clips to use the new MLT kdenlivetitle producer
785 QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
786 for (int i = 0; i < kproducerNodes.count(); ++i) {
787 QDomElement kproducer = kproducerNodes.at(i).toElement();
788 if (kproducer.attribute("type").toInt() == TEXT) {
789 QString data = kproducer.attribute("xmldata");
790 QString datafile = kproducer.attribute("resource");
791 if (!datafile.endsWith(".kdenlivetitle")) {
792 datafile = QString();
793 kproducer.setAttribute("resource", QString());
795 QString id = kproducer.attribute("id");
796 QDomNodeList mltproducers = m_doc.elementsByTagName("producer");
797 bool foundData = false;
798 bool foundResource = false;
799 bool foundService = false;
800 for (int j = 0; j < mltproducers.count(); j++) {
801 QDomElement wproducer = mltproducers.at(j).toElement();
802 if (wproducer.attribute("id") == id) {
803 QDomNodeList props = wproducer.childNodes();
804 for (int k = 0; k < props.count(); k++) {
805 if (props.at(k).toElement().attribute("name") == "xmldata") {
806 props.at(k).firstChild().setNodeValue(data);
808 } else if (props.at(k).toElement().attribute("name") == "mlt_service") {
809 props.at(k).firstChild().setNodeValue("kdenlivetitle");
811 } else if (props.at(k).toElement().attribute("name") == "resource") {
812 props.at(k).firstChild().setNodeValue(datafile);
813 foundResource = true;
817 QDomElement e = m_doc.createElement("property");
818 e.setAttribute("name", "xmldata");
819 QDomText value = m_doc.createTextNode(data);
820 e.appendChild(value);
821 wproducer.appendChild(e);
824 QDomElement e = m_doc.createElement("property");
825 e.setAttribute("name", "mlt_service");
826 QDomText value = m_doc.createTextNode("kdenlivetitle");
827 e.appendChild(value);
828 wproducer.appendChild(e);
830 if (!foundResource) {
831 QDomElement e = m_doc.createElement("property");
832 e.setAttribute("name", "resource");
833 QDomText value = m_doc.createTextNode(datafile);
834 e.appendChild(value);
835 wproducer.appendChild(e);
843 if (version <= 0.85) {
844 // update the LADSPA effects to use the new ladspa.id format instead of external xml file
845 QDomNodeList effectNodes = m_doc.elementsByTagName("filter");
846 for (int i = 0; i < effectNodes.count(); ++i) {
847 QDomElement effect = effectNodes.at(i).toElement();
848 if (EffectsList::property(effect, "mlt_service") == "ladspa") {
849 // Needs to be converted
850 QStringList info = getInfoFromEffectName(EffectsList::property(effect, "kdenlive_id"));
851 if (info.isEmpty()) continue;
852 // info contains the correct ladspa.id from kdenlive effect name, and a list of parameter's old and new names
853 EffectsList::setProperty(effect, "kdenlive_id", info.at(0));
854 EffectsList::setProperty(effect, "tag", info.at(0));
855 EffectsList::setProperty(effect, "mlt_service", info.at(0));
856 EffectsList::removeProperty(effect, "src");
857 for (int j = 1; j < info.size(); j++) {
858 QString value = EffectsList::property(effect, info.at(j).section('=', 0, 0));
859 if (!value.isEmpty()) {
860 // update parameter name
861 EffectsList::renameProperty(effect, info.at(j).section('=', 0, 0), info.at(j).section('=', 1, 1));
868 if (version <= 0.86) {
869 // Make sure we don't have avformat-novalidate producers, since it caused crashes
870 QDomNodeList producers = m_doc.elementsByTagName("producer");
871 int max = producers.count();
872 for (int i = 0; i < max; i++) {
873 QDomElement prod = producers.at(i).toElement();
874 if (EffectsList::property(prod, "mlt_service") == "avformat-novalidate")
875 EffectsList::setProperty(prod, "mlt_service", "avformat");
878 // 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
880 // Get profile info (width / height)
883 QDomElement profile = m_doc.firstChildElement("profile");
884 if (profile.isNull()) profile = infoXml.firstChildElement("profileinfo");
885 if (profile.isNull()) {
886 // could not find profile info, set PAL
891 profileWidth = profile.attribute("width").toInt();
892 profileHeight = profile.attribute("height").toInt();
894 QDomNodeList transitions = m_doc.elementsByTagName("transition");
895 max = transitions.count();
897 for (int i = 0; i < max; i++) {
898 QDomElement trans = transitions.at(i).toElement();
899 out = trans.attribute("out").toInt() - trans.attribute("in").toInt();
900 QString geom = EffectsList::property(trans, "geometry");
901 Mlt::Geometry *g = new Mlt::Geometry(geom.toUtf8().data(), out, profileWidth, profileHeight);
902 Mlt::GeometryItem item;
903 if (g->next_key(&item, out) == 0) {
904 // We have a keyframe just after last frame, try to move it to last frame
905 if (item.frame() == out + 1) {
909 EffectsList::setProperty(trans, "geometry", g->serialise());
916 if (version <= 0.87) {
917 if (!m_doc.firstChildElement("mlt").hasAttribute("LC_NUMERIC")) {
918 m_doc.firstChildElement("mlt").setAttribute("LC_NUMERIC", "C");
922 // The document has been converted: mark it as modified
923 infoXml.setAttribute("version", currentVersion);
928 QStringList DocumentValidator::getInfoFromEffectName(const QString oldName)
931 // Returns a list to convert old Kdenlive ladspa effects
932 if (oldName == "pitch_shift") {
933 info << "ladspa.1433";
936 else if (oldName == "vinyl") {
937 info << "ladspa.1905";
944 else if (oldName == "room_reverb") {
945 info << "ladspa.1216";
950 else if (oldName == "reverb") {
951 info << "ladspa.1423";
955 else if (oldName == "rate_scale") {
956 info << "ladspa.1417";
959 else if (oldName == "pitch_scale") {
960 info << "ladspa.1193";
963 else if (oldName == "phaser") {
964 info << "ladspa.1217";
967 info << "feedback=2";
970 else if (oldName == "limiter") {
971 info << "ladspa.1913";
976 else if (oldName == "equalizer_15") {
977 info << "ladspa.1197";
994 else if (oldName == "equalizer") {
995 info << "ladspa.1901";
1000 else if (oldName == "declipper") {
1001 info << "ladspa.1195";
1006 QString DocumentValidator::colorToString(const QColor& c)
1008 QString ret = "%1,%2,%3,%4";
1009 ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
1013 bool DocumentValidator::isProject() const
1015 QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
1016 return !infoXmlNode.isNull();
1019 bool DocumentValidator::isModified() const