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>
33 #include <mlt++/Mlt.h>
36 DocumentValidator::DocumentValidator(QDomDocument doc):
41 bool DocumentValidator::validate(const double currentVersion)
43 QDomElement mlt = m_doc.firstChildElement("mlt");
44 // At least the root element must be there
48 QDomElement kdenliveDoc = mlt.firstChildElement("kdenlivedoc");
49 // Check if we're validating a Kdenlive project
50 if (kdenliveDoc.isNull())
53 // Previous MLT / Kdenlive versions used C locale by default
54 QLocale documentLocale("C");
56 if (mlt.hasAttribute("LC_NUMERIC")) {
57 // Set locale for the document
58 documentLocale = QLocale(mlt.attribute("LC_NUMERIC"));
61 if (documentLocale != QLocale()) {
62 QLocale::setDefault(documentLocale);
63 // locale conversion might need to be redone
64 initEffects::parseEffectFiles();
67 // TODO: remove after string freeze
69 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"));
71 // Upgrade the document to the latest version
72 if (!upgrade(documentLocale.toDouble(kdenliveDoc.attribute("version")), currentVersion))
76 * Check the syntax (this will be replaced by XSD validation with Qt 4.6)
77 * and correct some errors
80 // Return (or create) the tractor
81 QDomElement tractor = mlt.firstChildElement("tractor");
82 if (tractor.isNull()) {
84 tractor = m_doc.createElement("tractor");
85 tractor.setAttribute("global_feed", "1");
86 tractor.setAttribute("in", "0");
87 tractor.setAttribute("out", "-1");
88 tractor.setAttribute("id", "maintractor");
89 mlt.appendChild(tractor);
93 * Make sure at least one track exists, and they're equal in number to
94 * to the maximum between MLT and Kdenlive playlists and tracks
96 QDomNodeList playlists = m_doc.elementsByTagName("playlist");
97 int tracksMax = playlists.count() - 1; // Remove the black track
98 QDomNodeList tracks = tractor.elementsByTagName("track");
99 tracksMax = qMax(tracks.count() - 1, tracksMax);
100 QDomNodeList tracksinfo = kdenliveDoc.elementsByTagName("trackinfo");
101 tracksMax = qMax(tracksinfo.count(), tracksMax);
102 tracksMax = qMax(1, tracksMax); // Force existance of one track
103 if (playlists.count() - 1 < tracksMax ||
104 tracks.count() - 1 < tracksMax ||
105 tracksinfo.count() < tracksMax) {
106 kDebug() << "//// WARNING, PROJECT IS CORRUPTED, MISSING TRACK";
109 // use the MLT tracks as reference
110 if (tracks.count() - 1 < tracksMax) {
111 // Looks like one MLT track is missing, remove the extra Kdenlive track if there is one.
112 if (tracksinfo.count() != tracks.count() - 1) {
113 // The Kdenlive tracks are not ok, clear and rebuild them
114 QDomNode tinfo = kdenliveDoc.elementsByTagName("tracksinfo").at(0);
115 QDomNode tnode = tinfo.firstChild();
116 while (!tnode.isNull()) {
117 tinfo.removeChild(tnode);
118 tnode = tinfo.firstChild();
121 for (int i = 1; i < tracks.count(); i++) {
122 QString hide = tracks.at(i).toElement().attribute("hide");
123 QDomElement newTrack = m_doc.createElement("trackinfo");
124 if (hide == "video") {
126 newTrack.setAttribute("type", "audio");
127 newTrack.setAttribute("blind", 1);
128 newTrack.setAttribute("mute", 0);
129 newTrack.setAttribute("lock", 0);
131 newTrack.setAttribute("blind", 0);
132 newTrack.setAttribute("mute", 0);
133 newTrack.setAttribute("lock", 0);
135 tinfo.appendChild(newTrack);
140 if (playlists.count() - 1 < tracksMax) {
141 difference = tracksMax - (playlists.count() - 1);
142 for (int i = 0; i < difference; ++i) {
143 QDomElement playlist = m_doc.createElement("playlist");
144 mlt.appendChild(playlist);
147 if (tracks.count() - 1 < tracksMax) {
148 difference = tracksMax - (tracks.count() - 1);
149 for (int i = 0; i < difference; ++i) {
150 QDomElement track = m_doc.createElement("track");
151 tractor.appendChild(track);
154 if (tracksinfo.count() < tracksMax) {
155 QDomElement tracksinfoElm = kdenliveDoc.firstChildElement("tracksinfo");
156 if (tracksinfoElm.isNull()) {
157 tracksinfoElm = m_doc.createElement("tracksinfo");
158 kdenliveDoc.appendChild(tracksinfoElm);
160 difference = tracksMax - tracksinfo.count();
161 for (int i = 0; i < difference; ++i) {
162 QDomElement trackinfo = m_doc.createElement("trackinfo");
163 trackinfo.setAttribute("mute", "0");
164 trackinfo.setAttribute("locked", "0");
165 tracksinfoElm.appendChild(trackinfo);
170 // TODO: check the tracks references
171 // TODO: check internal mix transitions
178 bool DocumentValidator::upgrade(double version, const double currentVersion)
180 kDebug() << "Opening a document with version " << version;
182 // No conversion needed
183 if (version == currentVersion) {
187 // The document is too new
188 if (version > currentVersion) {
189 kDebug() << "Unable to open document with version " << version;
190 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"));
194 // Unsupported document versions
195 if (version == 0.5 || version == 0.7) {
196 kDebug() << "Unable to open document with version " << version;
197 KMessageBox::sorry(kapp->activeWindow(), i18n("This project type is unsupported (version %1) and can't be loaded.", version), i18n("Unable to open project"));
202 QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
203 QDomElement infoXml = infoXmlNode.toElement();
204 infoXml.setAttribute("upgraded", "1");
206 if (version <= 0.6) {
207 QDomElement infoXml_old = infoXmlNode.cloneNode(true).toElement(); // Needed for folders
208 QDomNode westley = m_doc.elementsByTagName("westley").at(1);
209 QDomNode tractor = m_doc.elementsByTagName("tractor").at(0);
210 QDomNode multitrack = m_doc.elementsByTagName("multitrack").at(0);
211 QDomNodeList playlists = m_doc.elementsByTagName("playlist");
213 QDomNode props = m_doc.elementsByTagName("properties").at(0).toElement();
214 QString profile = props.toElement().attribute("videoprofile");
215 int startPos = props.toElement().attribute("timeline_position").toInt();
216 if (profile == "dv_wide")
217 profile = "dv_pal_wide";
219 // move playlists outside of tractor and add the tracks instead
220 int max = playlists.count();
221 if (westley.isNull()) {
222 westley = m_doc.createElement("westley");
223 m_doc.documentElement().appendChild(westley);
225 if (tractor.isNull()) {
226 kDebug() << "// NO MLT PLAYLIST, building empty one";
227 QDomElement blank_tractor = m_doc.createElement("tractor");
228 westley.appendChild(blank_tractor);
229 QDomElement blank_playlist = m_doc.createElement("playlist");
230 blank_playlist.setAttribute("id", "black_track");
231 westley.insertBefore(blank_playlist, QDomNode());
232 QDomElement blank_track = m_doc.createElement("track");
233 blank_track.setAttribute("producer", "black_track");
234 blank_tractor.appendChild(blank_track);
236 QDomNodeList kdenlivetracks = m_doc.elementsByTagName("kdenlivetrack");
237 for (int i = 0; i < kdenlivetracks.count(); i++) {
238 blank_playlist = m_doc.createElement("playlist");
239 blank_playlist.setAttribute("id", "playlist" + QString::number(i));
240 westley.insertBefore(blank_playlist, QDomNode());
241 blank_track = m_doc.createElement("track");
242 blank_track.setAttribute("producer", "playlist" + QString::number(i));
243 blank_tractor.appendChild(blank_track);
244 if (kdenlivetracks.at(i).toElement().attribute("cliptype") == "Sound") {
245 blank_playlist.setAttribute("hide", "video");
246 blank_track.setAttribute("hide", "video");
249 } else for (int i = 0; i < max; i++) {
250 QDomNode n = playlists.at(i);
251 westley.insertBefore(n, QDomNode());
252 QDomElement pl = n.toElement();
253 QDomElement track = m_doc.createElement("track");
254 QString trackType = pl.attribute("hide");
255 if (!trackType.isEmpty())
256 track.setAttribute("hide", trackType);
257 QString playlist_id = pl.attribute("id");
258 if (playlist_id.isEmpty()) {
259 playlist_id = "black_track";
260 pl.setAttribute("id", playlist_id);
262 track.setAttribute("producer", playlist_id);
263 //tractor.appendChild(track);
264 #define KEEP_TRACK_ORDER 1
265 #ifdef KEEP_TRACK_ORDER
266 tractor.insertAfter(track, QDomNode());
268 // Insert the new track in an order that hopefully matches the 3 video, then 2 audio tracks of Kdenlive 0.7.0
269 // insertion sort - O( tracks*tracks )
270 // Note, this breaks _all_ transitions - but you can move them up and down afterwards.
271 QDomElement tractor_elem = tractor.toElement();
272 if (! tractor_elem.isNull()) {
273 QDomNodeList tracks = tractor_elem.elementsByTagName("track");
274 int size = tracks.size();
276 tractor.insertAfter(track, QDomNode());
278 bool inserted = false;
279 for (int i = 0; i < size; ++i) {
280 QDomElement track_elem = tracks.at(i).toElement();
281 if (track_elem.isNull()) {
282 tractor.insertAfter(track, QDomNode());
286 kDebug() << "playlist_id: " << playlist_id << " producer:" << track_elem.attribute("producer");
287 if (playlist_id < track_elem.attribute("producer")) {
288 tractor.insertBefore(track, track_elem);
294 // Reach here, no insertion, insert last
296 tractor.insertAfter(track, QDomNode());
300 kWarning() << "tractor was not a QDomElement";
301 tractor.insertAfter(track, QDomNode());
305 tractor.removeChild(multitrack);
307 // audio track mixing transitions should not be added to track view, so add required attribute
308 QDomNodeList transitions = m_doc.elementsByTagName("transition");
309 max = transitions.count();
310 for (int i = 0; i < max; i++) {
311 QDomElement tr = transitions.at(i).toElement();
312 if (tr.attribute("combine") == "1" && tr.attribute("mlt_service") == "mix") {
313 QDomElement property = m_doc.createElement("property");
314 property.setAttribute("name", "internal_added");
315 QDomText value = m_doc.createTextNode("237");
316 property.appendChild(value);
317 tr.appendChild(property);
318 property = m_doc.createElement("property");
319 property.setAttribute("name", "mlt_service");
320 value = m_doc.createTextNode("mix");
321 property.appendChild(value);
322 tr.appendChild(property);
324 // convert transition
325 QDomNamedNodeMap attrs = tr.attributes();
326 for (int j = 0; j < attrs.count(); j++) {
327 QString attrName = attrs.item(j).nodeName();
328 if (attrName != "in" && attrName != "out" && attrName != "id") {
329 QDomElement property = m_doc.createElement("property");
330 property.setAttribute("name", attrName);
331 QDomText value = m_doc.createTextNode(attrs.item(j).nodeValue());
332 property.appendChild(value);
333 tr.appendChild(property);
339 // move transitions after tracks
340 for (int i = 0; i < max; i++) {
341 tractor.insertAfter(transitions.at(0), QDomNode());
344 // Fix filters format
345 QDomNodeList entries = m_doc.elementsByTagName("entry");
346 max = entries.count();
347 for (int i = 0; i < max; i++) {
350 QDomNode m = entries.at(i).firstChild();
351 while (!m.isNull()) {
352 if (m.toElement().tagName() == "filter") {
353 QDomElement filt = m.toElement();
354 QDomNamedNodeMap attrs = filt.attributes();
355 QString current_id = filt.attribute("kdenlive_id");
356 if (current_id != last_id) {
358 last_id = current_id;
360 QDomElement e = m_doc.createElement("property");
361 e.setAttribute("name", "kdenlive_ix");
362 QDomText value = m_doc.createTextNode(QString::number(effectix));
363 e.appendChild(value);
365 for (int j = 0; j < attrs.count(); j++) {
366 QDomAttr a = attrs.item(j).toAttr();
368 kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
369 QDomElement e = m_doc.createElement("property");
370 e.setAttribute("name", a.name());
371 QDomText value = m_doc.createTextNode(a.value());
372 e.appendChild(value);
383 QDomNodeList filters = m_doc.elementsByTagName("filter");
384 max = filters.count();
387 for (int i = 0; i < max; i++) {
388 QDomElement filt = filters.at(i).toElement();
389 QDomNamedNodeMap attrs = filt.attributes();
390 QString current_id = filt.attribute("kdenlive_id");
391 if (current_id != last_id) {
393 last_id = current_id;
395 QDomElement e = m_doc.createElement("property");
396 e.setAttribute("name", "kdenlive_ix");
397 QDomText value = m_doc.createTextNode(QString::number(1));
398 e.appendChild(value);
400 for (int j = 0; j < attrs.count(); j++) {
401 QDomAttr a = attrs.item(j).toAttr();
403 kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
404 QDomElement e = m_doc.createElement("property");
405 e.setAttribute("name", a.name());
406 QDomText value = m_doc.createTextNode(a.value());
407 e.appendChild(value);
414 QDomNodeList producers = westley.toElement().elementsByTagName("producer");
415 max = producers.count();
416 for (int i = 0; i < max; i++) {
417 QDomElement prod = producers.at(i).toElement();
418 if (prod.attribute("mlt_service") == "framebuffer") {
419 QString slowmotionprod = prod.attribute("resource");
420 slowmotionprod.replace(':', '?');
421 kDebug() << "// FOUND WRONG SLOWMO, new: " << slowmotionprod;
422 prod.setAttribute("resource", slowmotionprod);
425 // move producers to correct place, markers to a global list, fix clip descriptions
426 QDomElement markers = m_doc.createElement("markers");
427 // This will get the xml producers:
428 producers = m_doc.elementsByTagName("producer");
429 max = producers.count();
430 for (int i = 0; i < max; i++) {
431 QDomElement prod = producers.at(0).toElement();
432 // add resource also as a property (to allow path correction in setNewResource())
433 // TODO: will it work with slowmotion? needs testing
434 /*if (!prod.attribute("resource").isEmpty()) {
435 QDomElement prop_resource = m_doc.createElement("property");
436 prop_resource.setAttribute("name", "resource");
437 QDomText resource = m_doc.createTextNode(prod.attribute("resource"));
438 prop_resource.appendChild(resource);
439 prod.appendChild(prop_resource);
441 QDomNode m = prod.firstChild();
443 if (m.toElement().tagName() == "markers") {
444 QDomNodeList prodchilds = m.childNodes();
445 int maxchild = prodchilds.count();
446 for (int k = 0; k < maxchild; k++) {
447 QDomElement mark = prodchilds.at(0).toElement();
448 mark.setAttribute("id", prod.attribute("id"));
449 markers.insertAfter(mark, QDomNode());
452 } else if (prod.attribute("type").toInt() == TEXT) {
453 // convert title clip
454 if (m.toElement().tagName() == "textclip") {
456 QDomElement titleclip = m.toElement();
457 QDomElement title = tdoc.createElement("kdenlivetitle");
458 tdoc.appendChild(title);
459 QDomNodeList objects = titleclip.childNodes();
460 int maxchild = objects.count();
461 for (int k = 0; k < maxchild; k++) {
463 QDomElement ob = objects.at(k).toElement();
464 if (ob.attribute("type") == "3") {
465 // text object - all of this goes into "xmldata"...
466 QDomElement item = tdoc.createElement("item");
467 item.setAttribute("z-index", ob.attribute("z"));
468 item.setAttribute("type", "QGraphicsTextItem");
469 QDomElement position = tdoc.createElement("position");
470 position.setAttribute("x", ob.attribute("x"));
471 position.setAttribute("y", ob.attribute("y"));
472 QDomElement content = tdoc.createElement("content");
473 content.setAttribute("font", ob.attribute("font_family"));
474 content.setAttribute("font-size", ob.attribute("font_size"));
475 content.setAttribute("font-bold", ob.attribute("bold"));
476 content.setAttribute("font-italic", ob.attribute("italic"));
477 content.setAttribute("font-underline", ob.attribute("underline"));
478 QString col = ob.attribute("color");
480 content.setAttribute("font-color", colorToString(c));
481 // todo: These fields are missing from the newly generated xmldata:
482 // transform, startviewport, endviewport, background
484 QDomText conttxt = tdoc.createTextNode(ob.attribute("text"));
485 content.appendChild(conttxt);
486 item.appendChild(position);
487 item.appendChild(content);
488 title.appendChild(item);
489 } else if (ob.attribute("type") == "5") {
491 QDomElement item = tdoc.createElement("item");
492 item.setAttribute("z-index", ob.attribute("z"));
493 item.setAttribute("type", "QGraphicsRectItem");
494 QDomElement position = tdoc.createElement("position");
495 position.setAttribute("x", ob.attribute("x"));
496 position.setAttribute("y", ob.attribute("y"));
497 QDomElement content = tdoc.createElement("content");
498 QString col = ob.attribute("color");
500 content.setAttribute("brushcolor", colorToString(c));
501 QString rect = "0,0,";
502 rect.append(ob.attribute("width"));
504 rect.append(ob.attribute("height"));
505 content.setAttribute("rect", rect);
506 item.appendChild(position);
507 item.appendChild(content);
508 title.appendChild(item);
511 prod.setAttribute("xmldata", tdoc.toString());
512 // mbd todo: This clearly does not work, as every title gets the same name - trying to leave it empty
513 // QStringList titleInfo = TitleWidget::getFreeTitleInfo(projectFolder());
514 // prod.setAttribute("titlename", titleInfo.at(0));
515 // prod.setAttribute("resource", titleInfo.at(1));
516 //kDebug()<<"TITLE DATA:\n"<<tdoc.toString();
518 } // End conversion of title clips.
520 } else if (m.isText()) {
521 QString comment = m.nodeValue();
522 if (!comment.isEmpty()) {
523 prod.setAttribute("description", comment);
528 int duration = prod.attribute("duration").toInt();
529 if (duration > 0) prod.setAttribute("out", QString::number(duration));
530 // The clip goes back in, but text clips should not go back in, at least not modified
531 westley.insertBefore(prod, QDomNode());
534 QDomNode westley0 = m_doc.elementsByTagName("westley").at(0);
535 if (!markers.firstChild().isNull()) westley0.appendChild(markers);
538 * Convert as much of the kdenlivedoc as possible. Use the producer in
539 * westley. First, remove the old stuff from westley, and add a new
540 * empty one. Also, track the max id in order to use it for the adding
543 int max_kproducer_id = 0;
544 westley0.removeChild(infoXmlNode);
545 QDomElement infoXml_new = m_doc.createElement("kdenlivedoc");
546 infoXml_new.setAttribute("profile", profile);
547 infoXml.setAttribute("position", startPos);
549 // Add all the producers that has a resource in westley
550 QDomElement westley_element = westley0.toElement();
551 if (westley_element.isNull()) {
552 kWarning() << "westley0 element in document was not a QDomElement - unable to add producers to new kdenlivedoc";
554 QDomNodeList wproducers = westley_element.elementsByTagName("producer");
555 int kmax = wproducers.count();
556 for (int i = 0; i < kmax; i++) {
557 QDomElement wproducer = wproducers.at(i).toElement();
558 if (wproducer.isNull()) {
559 kWarning() << "Found producer in westley0, that was not a QDomElement";
562 if (wproducer.attribute("id") == "black") continue;
563 // We have to do slightly different things, depending on the type
564 kDebug() << "Converting producer element with type" << wproducer.attribute("type");
565 if (wproducer.attribute("type").toInt() == TEXT) {
566 kDebug() << "Found TEXT element in producer" << endl;
567 QDomElement kproducer = wproducer.cloneNode(true).toElement();
568 kproducer.setTagName("kdenlive_producer");
569 infoXml_new.appendChild(kproducer);
571 * TODO: Perhaps needs some more changes here to
572 * "frequency", aspect ratio as a float, frame_size,
573 * channels, and later, resource and title name
576 QDomElement kproducer = m_doc.createElement("kdenlive_producer");
577 kproducer.setAttribute("id", wproducer.attribute("id"));
578 if (!wproducer.attribute("description").isEmpty())
579 kproducer.setAttribute("description", wproducer.attribute("description"));
580 kproducer.setAttribute("resource", wproducer.attribute("resource"));
581 kproducer.setAttribute("type", wproducer.attribute("type"));
582 // Testing fix for 358
583 if (!wproducer.attribute("aspect_ratio").isEmpty()) {
584 kproducer.setAttribute("aspect_ratio", wproducer.attribute("aspect_ratio"));
586 if (!wproducer.attribute("source_fps").isEmpty()) {
587 kproducer.setAttribute("fps", wproducer.attribute("source_fps"));
589 if (!wproducer.attribute("length").isEmpty()) {
590 kproducer.setAttribute("duration", wproducer.attribute("length"));
592 infoXml_new.appendChild(kproducer);
594 if (wproducer.attribute("id").toInt() > max_kproducer_id) {
595 max_kproducer_id = wproducer.attribute("id").toInt();
599 #define LOOKUP_FOLDER 1
602 * Look through all the folder elements of the old doc, for each folder,
603 * for each producer, get the id, look it up in the new doc, set the
604 * groupname and groupid. Note, this does not work at the moment - at
605 * least one folder shows up missing, and clips with no folder does not
608 //QDomElement infoXml_old = infoXmlNode.toElement();
609 if (!infoXml_old.isNull()) {
610 QDomNodeList folders = infoXml_old.elementsByTagName("folder");
611 int fsize = folders.size();
612 int groupId = max_kproducer_id + 1; // Start at +1 of max id of the kdenlive_producers
613 for (int i = 0; i < fsize; ++i) {
614 QDomElement folder = folders.at(i).toElement();
615 if (!folder.isNull()) {
616 QString groupName = folder.attribute("name");
617 kDebug() << "groupName: " << groupName << " with groupId: " << groupId;
618 QDomNodeList fproducers = folder.elementsByTagName("producer");
619 int psize = fproducers.size();
620 for (int j = 0; j < psize; ++j) {
621 QDomElement fproducer = fproducers.at(j).toElement();
622 if (!fproducer.isNull()) {
623 QString id = fproducer.attribute("id");
624 // This is not very effective, but compared to loading the clips, its a breeze
625 QDomNodeList kdenlive_producers = infoXml_new.elementsByTagName("kdenlive_producer");
626 int kpsize = kdenlive_producers.size();
627 for (int k = 0; k < kpsize; ++k) {
628 QDomElement kproducer = kdenlive_producers.at(k).toElement(); // Its an element for sure
629 if (id == kproducer.attribute("id")) {
630 // We do not check that it already is part of a folder
631 kproducer.setAttribute("groupid", groupId);
632 kproducer.setAttribute("groupname", groupName);
643 QDomNodeList elements = westley.childNodes();
644 max = elements.count();
645 for (int i = 0; i < max; i++) {
646 QDomElement prod = elements.at(0).toElement();
647 westley0.insertAfter(prod, QDomNode());
650 westley0.appendChild(infoXml_new);
652 westley0.removeChild(westley);
654 // adds <avfile /> information to <kdenlive_producer />
655 QDomNodeList kproducers = m_doc.elementsByTagName("kdenlive_producer");
656 QDomNodeList avfiles = infoXml_old.elementsByTagName("avfile");
657 kDebug() << "found" << avfiles.count() << "<avfile />s and" << kproducers.count() << "<kdenlive_producer />s";
658 for (int i = 0; i < avfiles.count(); ++i) {
659 QDomElement avfile = avfiles.at(i).toElement();
660 QDomElement kproducer;
662 kWarning() << "found an <avfile /> that is not a QDomElement";
664 QString id = avfile.attribute("id");
665 // this is horrible, must be rewritten, it's just for test
666 for (int j = 0; j < kproducers.count(); ++j) {
667 //kDebug() << "checking <kdenlive_producer /> with id" << kproducers.at(j).toElement().attribute("id");
668 if (kproducers.at(j).toElement().attribute("id") == id) {
669 kproducer = kproducers.at(j).toElement();
673 if (kproducer == QDomElement())
674 kWarning() << "no match for <avfile /> with id =" << id;
676 //kDebug() << "ready to set additional <avfile />'s attributes (id =" << id << ")";
677 kproducer.setAttribute("channels", avfile.attribute("channels"));
678 kproducer.setAttribute("duration", avfile.attribute("duration"));
679 kproducer.setAttribute("frame_size", avfile.attribute("width") + 'x' + avfile.attribute("height"));
680 kproducer.setAttribute("frequency", avfile.attribute("frequency"));
681 if (kproducer.attribute("description").isEmpty() && !avfile.attribute("description").isEmpty())
682 kproducer.setAttribute("description", avfile.attribute("description"));
686 infoXml = infoXml_new;
689 if (version <= 0.81) {
690 // Add the tracks information
691 QString tracksOrder = infoXml.attribute("tracks");
692 if (tracksOrder.isEmpty()) {
693 QDomNodeList tracks = m_doc.elementsByTagName("track");
694 for (int i = 0; i < tracks.count(); i++) {
695 QDomElement track = tracks.at(i).toElement();
696 if (track.attribute("producer") != "black_track") {
697 if (track.attribute("hide") == "video")
698 tracksOrder.append('a');
700 tracksOrder.append('v');
704 QDomElement tracksinfo = m_doc.createElement("tracksinfo");
705 for (int i = 0; i < tracksOrder.size(); i++) {
706 QDomElement trackinfo = m_doc.createElement("trackinfo");
707 if (tracksOrder.data()[i] == 'a') {
708 trackinfo.setAttribute("type", "audio");
709 trackinfo.setAttribute("blind", true);
711 trackinfo.setAttribute("blind", false);
712 trackinfo.setAttribute("mute", false);
713 trackinfo.setAttribute("locked", false);
714 tracksinfo.appendChild(trackinfo);
716 infoXml.appendChild(tracksinfo);
719 if (version <= 0.82) {
720 // Convert <westley />s in <mlt />s (MLT extreme makeover)
721 QDomNodeList westleyNodes = m_doc.elementsByTagName("westley");
722 for (int i = 0; i < westleyNodes.count(); i++) {
723 QDomElement westley = westleyNodes.at(i).toElement();
724 westley.setTagName("mlt");
728 if (version <= 0.83) {
729 // Replace point size with pixel size in text titles
730 if (m_doc.toString().contains("font-size")) {
731 KMessageBox::ButtonCode convert = KMessageBox::Continue;
732 QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
733 for (int i = 0; i < kproducerNodes.count() && convert != KMessageBox::No; ++i) {
734 QDomElement kproducer = kproducerNodes.at(i).toElement();
735 if (kproducer.attribute("type").toInt() == TEXT) {
737 data.setContent(kproducer.attribute("xmldata"));
738 QDomNodeList items = data.firstChild().childNodes();
739 for (int j = 0; j < items.count() && convert != KMessageBox::No; ++j) {
740 if (items.at(j).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
741 QDomNamedNodeMap textProperties = items.at(j).namedItem("content").attributes();
742 if (textProperties.namedItem("font-pixel-size").isNull() && !textProperties.namedItem("font-size").isNull()) {
743 // Ask the user if he wants to convert
744 if (convert != KMessageBox::Yes && convert != KMessageBox::No)
745 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"));
746 if (convert == KMessageBox::Yes) {
748 font.setPointSize(textProperties.namedItem("font-size").nodeValue().toInt());
749 QDomElement content = items.at(j).namedItem("content").toElement();
750 content.setAttribute("font-pixel-size", QFontInfo(font).pixelSize());
751 content.removeAttribute("font-size");
752 kproducer.setAttribute("xmldata", data.toString());
754 * You may be tempted to delete the preview file
755 * to force its recreation: bad idea (see
756 * http://www.kdenlive.org/mantis/view.php?id=749)
766 // Fill the <documentproperties /> element
767 QDomElement docProperties = infoXml.firstChildElement("documentproperties");
768 if (docProperties.isNull()) {
769 docProperties = m_doc.createElement("documentproperties");
770 docProperties.setAttribute("zonein", infoXml.attribute("zonein"));
771 docProperties.setAttribute("zoneout", infoXml.attribute("zoneout"));
772 docProperties.setAttribute("zoom", infoXml.attribute("zoom"));
773 docProperties.setAttribute("position", infoXml.attribute("position"));
774 infoXml.appendChild(docProperties);
778 if (version <= 0.84) {
779 // update the title clips to use the new MLT kdenlivetitle producer
780 QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
781 for (int i = 0; i < kproducerNodes.count(); ++i) {
782 QDomElement kproducer = kproducerNodes.at(i).toElement();
783 if (kproducer.attribute("type").toInt() == TEXT) {
784 QString data = kproducer.attribute("xmldata");
785 QString datafile = kproducer.attribute("resource");
786 if (!datafile.endsWith(".kdenlivetitle")) {
787 datafile = QString();
788 kproducer.setAttribute("resource", QString());
790 QString id = kproducer.attribute("id");
791 QDomNodeList mltproducers = m_doc.elementsByTagName("producer");
792 bool foundData = false;
793 bool foundResource = false;
794 bool foundService = false;
795 for (int j = 0; j < mltproducers.count(); j++) {
796 QDomElement wproducer = mltproducers.at(j).toElement();
797 if (wproducer.attribute("id") == id) {
798 QDomNodeList props = wproducer.childNodes();
799 for (int k = 0; k < props.count(); k++) {
800 if (props.at(k).toElement().attribute("name") == "xmldata") {
801 props.at(k).firstChild().setNodeValue(data);
803 } else if (props.at(k).toElement().attribute("name") == "mlt_service") {
804 props.at(k).firstChild().setNodeValue("kdenlivetitle");
806 } else if (props.at(k).toElement().attribute("name") == "resource") {
807 props.at(k).firstChild().setNodeValue(datafile);
808 foundResource = true;
812 QDomElement e = m_doc.createElement("property");
813 e.setAttribute("name", "xmldata");
814 QDomText value = m_doc.createTextNode(data);
815 e.appendChild(value);
816 wproducer.appendChild(e);
819 QDomElement e = m_doc.createElement("property");
820 e.setAttribute("name", "mlt_service");
821 QDomText value = m_doc.createTextNode("kdenlivetitle");
822 e.appendChild(value);
823 wproducer.appendChild(e);
825 if (!foundResource) {
826 QDomElement e = m_doc.createElement("property");
827 e.setAttribute("name", "resource");
828 QDomText value = m_doc.createTextNode(datafile);
829 e.appendChild(value);
830 wproducer.appendChild(e);
838 if (version <= 0.85) {
839 // update the LADSPA effects to use the new ladspa.id format instead of external xml file
840 QDomNodeList effectNodes = m_doc.elementsByTagName("filter");
841 for (int i = 0; i < effectNodes.count(); ++i) {
842 QDomElement effect = effectNodes.at(i).toElement();
843 if (EffectsList::property(effect, "mlt_service") == "ladspa") {
844 // Needs to be converted
845 QStringList info = getInfoFromEffectName(EffectsList::property(effect, "kdenlive_id"));
846 if (info.isEmpty()) continue;
847 // info contains the correct ladspa.id from kdenlive effect name, and a list of parameter's old and new names
848 EffectsList::setProperty(effect, "kdenlive_id", info.at(0));
849 EffectsList::setProperty(effect, "tag", info.at(0));
850 EffectsList::setProperty(effect, "mlt_service", info.at(0));
851 EffectsList::removeProperty(effect, "src");
852 for (int j = 1; j < info.size(); j++) {
853 QString value = EffectsList::property(effect, info.at(j).section('=', 0, 0));
854 if (!value.isEmpty()) {
855 // update parameter name
856 EffectsList::renameProperty(effect, info.at(j).section('=', 0, 0), info.at(j).section('=', 1, 1));
863 if (version <= 0.86) {
864 // Make sure we don't have avformat-novalidate producers, since it caused crashes
865 QDomNodeList producers = m_doc.elementsByTagName("producer");
866 int max = producers.count();
867 for (int i = 0; i < max; i++) {
868 QDomElement prod = producers.at(i).toElement();
869 if (EffectsList::property(prod, "mlt_service") == "avformat-novalidate")
870 EffectsList::setProperty(prod, "mlt_service", "avformat");
873 // 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
875 // Get profile info (width / height)
878 QDomElement profile = m_doc.firstChildElement("profile");
879 if (profile.isNull()) profile = infoXml.firstChildElement("profileinfo");
880 if (profile.isNull()) {
881 // could not find profile info, set PAL
886 profileWidth = profile.attribute("width").toInt();
887 profileHeight = profile.attribute("height").toInt();
889 QDomNodeList transitions = m_doc.elementsByTagName("transition");
890 max = transitions.count();
892 for (int i = 0; i < max; i++) {
893 QDomElement trans = transitions.at(i).toElement();
894 out = trans.attribute("out").toInt() - trans.attribute("in").toInt();
895 QString geom = EffectsList::property(trans, "geometry");
896 Mlt::Geometry *g = new Mlt::Geometry(geom.toUtf8().data(), out, profileWidth, profileHeight);
897 Mlt::GeometryItem item;
898 if (g->next_key(&item, out) == 0) {
899 // We have a keyframe just after last frame, try to move it to last frame
900 if (item.frame() == out + 1) {
904 EffectsList::setProperty(trans, "geometry", g->serialise());
911 if (version <= 0.87) {
912 if (!m_doc.firstChildElement("mlt").hasAttribute("LC_NUMERIC")) {
913 m_doc.firstChildElement("mlt").setAttribute("LC_NUMERIC", "C");
917 // The document has been converted: mark it as modified
918 infoXml.setAttribute("version", currentVersion);
923 QStringList DocumentValidator::getInfoFromEffectName(const QString oldName)
926 // Returns a list to convert old Kdenlive ladspa effects
927 if (oldName == "pitch_shift") {
928 info << "ladspa.1433";
931 else if (oldName == "vinyl") {
932 info << "ladspa.1905";
939 else if (oldName == "room_reverb") {
940 info << "ladspa.1216";
945 else if (oldName == "reverb") {
946 info << "ladspa.1423";
950 else if (oldName == "rate_scale") {
951 info << "ladspa.1417";
954 else if (oldName == "pitch_scale") {
955 info << "ladspa.1193";
958 else if (oldName == "phaser") {
959 info << "ladspa.1217";
962 info << "feedback=2";
965 else if (oldName == "limiter") {
966 info << "ladspa.1913";
971 else if (oldName == "equalizer_15") {
972 info << "ladspa.1197";
989 else if (oldName == "equalizer") {
990 info << "ladspa.1901";
995 else if (oldName == "declipper") {
996 info << "ladspa.1195";
1001 QString DocumentValidator::colorToString(const QColor& c)
1003 QString ret = "%1,%2,%3,%4";
1004 ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
1008 bool DocumentValidator::isProject() const
1010 QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
1011 return !infoXmlNode.isNull();
1014 bool DocumentValidator::isModified() const