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 documentLocale.setNumberOptions(QLocale::OmitGroupSeparator);
68 if (documentLocale != QLocale()) {
69 QLocale::setDefault(documentLocale);
70 // locale conversion might need to be redone
71 initEffects::parseEffectFiles();
74 // TODO: remove after string freeze
76 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"));
78 // Upgrade the document to the latest version
79 if (!upgrade(documentLocale.toDouble(kdenliveDoc.attribute("version")), currentVersion))
83 * Check the syntax (this will be replaced by XSD validation with Qt 4.6)
84 * and correct some errors
87 // Return (or create) the tractor
88 QDomElement tractor = mlt.firstChildElement("tractor");
89 if (tractor.isNull()) {
91 tractor = m_doc.createElement("tractor");
92 tractor.setAttribute("global_feed", "1");
93 tractor.setAttribute("in", "0");
94 tractor.setAttribute("out", "-1");
95 tractor.setAttribute("id", "maintractor");
96 mlt.appendChild(tractor);
100 * Make sure at least one track exists, and they're equal in number to
101 * to the maximum between MLT and Kdenlive playlists and tracks
103 QDomNodeList playlists = m_doc.elementsByTagName("playlist");
104 int tracksMax = playlists.count() - 1; // Remove the black track
105 QDomNodeList tracks = tractor.elementsByTagName("track");
106 tracksMax = qMax(tracks.count() - 1, tracksMax);
107 QDomNodeList tracksinfo = kdenliveDoc.elementsByTagName("trackinfo");
108 tracksMax = qMax(tracksinfo.count(), tracksMax);
109 tracksMax = qMax(1, tracksMax); // Force existance of one track
110 if (playlists.count() - 1 < tracksMax ||
111 tracks.count() - 1 < tracksMax ||
112 tracksinfo.count() < tracksMax) {
113 kDebug() << "//// WARNING, PROJECT IS CORRUPTED, MISSING TRACK";
116 // use the MLT tracks as reference
117 if (tracks.count() - 1 < tracksMax) {
118 // Looks like one MLT track is missing, remove the extra Kdenlive track if there is one.
119 if (tracksinfo.count() != tracks.count() - 1) {
120 // The Kdenlive tracks are not ok, clear and rebuild them
121 QDomNode tinfo = kdenliveDoc.elementsByTagName("tracksinfo").at(0);
122 QDomNode tnode = tinfo.firstChild();
123 while (!tnode.isNull()) {
124 tinfo.removeChild(tnode);
125 tnode = tinfo.firstChild();
128 for (int i = 1; i < tracks.count(); i++) {
129 QString hide = tracks.at(i).toElement().attribute("hide");
130 QDomElement newTrack = m_doc.createElement("trackinfo");
131 if (hide == "video") {
133 newTrack.setAttribute("type", "audio");
134 newTrack.setAttribute("blind", 1);
135 newTrack.setAttribute("mute", 0);
136 newTrack.setAttribute("lock", 0);
138 newTrack.setAttribute("blind", 0);
139 newTrack.setAttribute("mute", 0);
140 newTrack.setAttribute("lock", 0);
142 tinfo.appendChild(newTrack);
147 if (playlists.count() - 1 < tracksMax) {
148 difference = tracksMax - (playlists.count() - 1);
149 for (int i = 0; i < difference; ++i) {
150 QDomElement playlist = m_doc.createElement("playlist");
151 mlt.appendChild(playlist);
154 if (tracks.count() - 1 < tracksMax) {
155 difference = tracksMax - (tracks.count() - 1);
156 for (int i = 0; i < difference; ++i) {
157 QDomElement track = m_doc.createElement("track");
158 tractor.appendChild(track);
161 if (tracksinfo.count() < tracksMax) {
162 QDomElement tracksinfoElm = kdenliveDoc.firstChildElement("tracksinfo");
163 if (tracksinfoElm.isNull()) {
164 tracksinfoElm = m_doc.createElement("tracksinfo");
165 kdenliveDoc.appendChild(tracksinfoElm);
167 difference = tracksMax - tracksinfo.count();
168 for (int i = 0; i < difference; ++i) {
169 QDomElement trackinfo = m_doc.createElement("trackinfo");
170 trackinfo.setAttribute("mute", "0");
171 trackinfo.setAttribute("locked", "0");
172 tracksinfoElm.appendChild(trackinfo);
177 // TODO: check the tracks references
178 // TODO: check internal mix transitions
185 bool DocumentValidator::upgrade(double version, const double currentVersion)
187 kDebug() << "Opening a document with version " << version;
189 // No conversion needed
190 if (version == currentVersion) {
194 // The document is too new
195 if (version > currentVersion) {
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.\nPlease consider upgrading your Kdenlive version.", version), i18n("Unable to open project"));
201 // Unsupported document versions
202 if (version == 0.5 || version == 0.7) {
203 kDebug() << "Unable to open document with version " << version;
204 KMessageBox::sorry(kapp->activeWindow(), i18n("This project type is unsupported (version %1) and can't be loaded.", version), i18n("Unable to open project"));
209 QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
210 QDomElement infoXml = infoXmlNode.toElement();
211 infoXml.setAttribute("upgraded", "1");
213 if (version <= 0.6) {
214 QDomElement infoXml_old = infoXmlNode.cloneNode(true).toElement(); // Needed for folders
215 QDomNode westley = m_doc.elementsByTagName("westley").at(1);
216 QDomNode tractor = m_doc.elementsByTagName("tractor").at(0);
217 QDomNode multitrack = m_doc.elementsByTagName("multitrack").at(0);
218 QDomNodeList playlists = m_doc.elementsByTagName("playlist");
220 QDomNode props = m_doc.elementsByTagName("properties").at(0).toElement();
221 QString profile = props.toElement().attribute("videoprofile");
222 int startPos = props.toElement().attribute("timeline_position").toInt();
223 if (profile == "dv_wide")
224 profile = "dv_pal_wide";
226 // move playlists outside of tractor and add the tracks instead
227 int max = playlists.count();
228 if (westley.isNull()) {
229 westley = m_doc.createElement("westley");
230 m_doc.documentElement().appendChild(westley);
232 if (tractor.isNull()) {
233 kDebug() << "// NO MLT PLAYLIST, building empty one";
234 QDomElement blank_tractor = m_doc.createElement("tractor");
235 westley.appendChild(blank_tractor);
236 QDomElement blank_playlist = m_doc.createElement("playlist");
237 blank_playlist.setAttribute("id", "black_track");
238 westley.insertBefore(blank_playlist, QDomNode());
239 QDomElement blank_track = m_doc.createElement("track");
240 blank_track.setAttribute("producer", "black_track");
241 blank_tractor.appendChild(blank_track);
243 QDomNodeList kdenlivetracks = m_doc.elementsByTagName("kdenlivetrack");
244 for (int i = 0; i < kdenlivetracks.count(); i++) {
245 blank_playlist = m_doc.createElement("playlist");
246 blank_playlist.setAttribute("id", "playlist" + QString::number(i));
247 westley.insertBefore(blank_playlist, QDomNode());
248 blank_track = m_doc.createElement("track");
249 blank_track.setAttribute("producer", "playlist" + QString::number(i));
250 blank_tractor.appendChild(blank_track);
251 if (kdenlivetracks.at(i).toElement().attribute("cliptype") == "Sound") {
252 blank_playlist.setAttribute("hide", "video");
253 blank_track.setAttribute("hide", "video");
256 } else for (int i = 0; i < max; i++) {
257 QDomNode n = playlists.at(i);
258 westley.insertBefore(n, QDomNode());
259 QDomElement pl = n.toElement();
260 QDomElement track = m_doc.createElement("track");
261 QString trackType = pl.attribute("hide");
262 if (!trackType.isEmpty())
263 track.setAttribute("hide", trackType);
264 QString playlist_id = pl.attribute("id");
265 if (playlist_id.isEmpty()) {
266 playlist_id = "black_track";
267 pl.setAttribute("id", playlist_id);
269 track.setAttribute("producer", playlist_id);
270 //tractor.appendChild(track);
271 #define KEEP_TRACK_ORDER 1
272 #ifdef KEEP_TRACK_ORDER
273 tractor.insertAfter(track, QDomNode());
275 // Insert the new track in an order that hopefully matches the 3 video, then 2 audio tracks of Kdenlive 0.7.0
276 // insertion sort - O( tracks*tracks )
277 // Note, this breaks _all_ transitions - but you can move them up and down afterwards.
278 QDomElement tractor_elem = tractor.toElement();
279 if (! tractor_elem.isNull()) {
280 QDomNodeList tracks = tractor_elem.elementsByTagName("track");
281 int size = tracks.size();
283 tractor.insertAfter(track, QDomNode());
285 bool inserted = false;
286 for (int i = 0; i < size; ++i) {
287 QDomElement track_elem = tracks.at(i).toElement();
288 if (track_elem.isNull()) {
289 tractor.insertAfter(track, QDomNode());
293 kDebug() << "playlist_id: " << playlist_id << " producer:" << track_elem.attribute("producer");
294 if (playlist_id < track_elem.attribute("producer")) {
295 tractor.insertBefore(track, track_elem);
301 // Reach here, no insertion, insert last
303 tractor.insertAfter(track, QDomNode());
307 kWarning() << "tractor was not a QDomElement";
308 tractor.insertAfter(track, QDomNode());
312 tractor.removeChild(multitrack);
314 // audio track mixing transitions should not be added to track view, so add required attribute
315 QDomNodeList transitions = m_doc.elementsByTagName("transition");
316 max = transitions.count();
317 for (int i = 0; i < max; i++) {
318 QDomElement tr = transitions.at(i).toElement();
319 if (tr.attribute("combine") == "1" && tr.attribute("mlt_service") == "mix") {
320 QDomElement property = m_doc.createElement("property");
321 property.setAttribute("name", "internal_added");
322 QDomText value = m_doc.createTextNode("237");
323 property.appendChild(value);
324 tr.appendChild(property);
325 property = m_doc.createElement("property");
326 property.setAttribute("name", "mlt_service");
327 value = m_doc.createTextNode("mix");
328 property.appendChild(value);
329 tr.appendChild(property);
331 // convert transition
332 QDomNamedNodeMap attrs = tr.attributes();
333 for (int j = 0; j < attrs.count(); j++) {
334 QString attrName = attrs.item(j).nodeName();
335 if (attrName != "in" && attrName != "out" && attrName != "id") {
336 QDomElement property = m_doc.createElement("property");
337 property.setAttribute("name", attrName);
338 QDomText value = m_doc.createTextNode(attrs.item(j).nodeValue());
339 property.appendChild(value);
340 tr.appendChild(property);
346 // move transitions after tracks
347 for (int i = 0; i < max; i++) {
348 tractor.insertAfter(transitions.at(0), QDomNode());
351 // Fix filters format
352 QDomNodeList entries = m_doc.elementsByTagName("entry");
353 max = entries.count();
354 for (int i = 0; i < max; i++) {
357 QDomNode m = entries.at(i).firstChild();
358 while (!m.isNull()) {
359 if (m.toElement().tagName() == "filter") {
360 QDomElement filt = m.toElement();
361 QDomNamedNodeMap attrs = filt.attributes();
362 QString current_id = filt.attribute("kdenlive_id");
363 if (current_id != last_id) {
365 last_id = current_id;
367 QDomElement e = m_doc.createElement("property");
368 e.setAttribute("name", "kdenlive_ix");
369 QDomText value = m_doc.createTextNode(QString::number(effectix));
370 e.appendChild(value);
372 for (int j = 0; j < attrs.count(); j++) {
373 QDomAttr a = attrs.item(j).toAttr();
375 kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
376 QDomElement e = m_doc.createElement("property");
377 e.setAttribute("name", a.name());
378 QDomText value = m_doc.createTextNode(a.value());
379 e.appendChild(value);
390 QDomNodeList filters = m_doc.elementsByTagName("filter");
391 max = filters.count();
394 for (int i = 0; i < max; i++) {
395 QDomElement filt = filters.at(i).toElement();
396 QDomNamedNodeMap attrs = filt.attributes();
397 QString current_id = filt.attribute("kdenlive_id");
398 if (current_id != last_id) {
400 last_id = current_id;
402 QDomElement e = m_doc.createElement("property");
403 e.setAttribute("name", "kdenlive_ix");
404 QDomText value = m_doc.createTextNode(QString::number(1));
405 e.appendChild(value);
407 for (int j = 0; j < attrs.count(); j++) {
408 QDomAttr a = attrs.item(j).toAttr();
410 kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
411 QDomElement e = m_doc.createElement("property");
412 e.setAttribute("name", a.name());
413 QDomText value = m_doc.createTextNode(a.value());
414 e.appendChild(value);
421 QDomNodeList producers = westley.toElement().elementsByTagName("producer");
422 max = producers.count();
423 for (int i = 0; i < max; i++) {
424 QDomElement prod = producers.at(i).toElement();
425 if (prod.attribute("mlt_service") == "framebuffer") {
426 QString slowmotionprod = prod.attribute("resource");
427 slowmotionprod.replace(':', '?');
428 kDebug() << "// FOUND WRONG SLOWMO, new: " << slowmotionprod;
429 prod.setAttribute("resource", slowmotionprod);
432 // move producers to correct place, markers to a global list, fix clip descriptions
433 QDomElement markers = m_doc.createElement("markers");
434 // This will get the xml producers:
435 producers = m_doc.elementsByTagName("producer");
436 max = producers.count();
437 for (int i = 0; i < max; i++) {
438 QDomElement prod = producers.at(0).toElement();
439 // add resource also as a property (to allow path correction in setNewResource())
440 // TODO: will it work with slowmotion? needs testing
441 /*if (!prod.attribute("resource").isEmpty()) {
442 QDomElement prop_resource = m_doc.createElement("property");
443 prop_resource.setAttribute("name", "resource");
444 QDomText resource = m_doc.createTextNode(prod.attribute("resource"));
445 prop_resource.appendChild(resource);
446 prod.appendChild(prop_resource);
448 QDomNode m = prod.firstChild();
450 if (m.toElement().tagName() == "markers") {
451 QDomNodeList prodchilds = m.childNodes();
452 int maxchild = prodchilds.count();
453 for (int k = 0; k < maxchild; k++) {
454 QDomElement mark = prodchilds.at(0).toElement();
455 mark.setAttribute("id", prod.attribute("id"));
456 markers.insertAfter(mark, QDomNode());
459 } else if (prod.attribute("type").toInt() == TEXT) {
460 // convert title clip
461 if (m.toElement().tagName() == "textclip") {
463 QDomElement titleclip = m.toElement();
464 QDomElement title = tdoc.createElement("kdenlivetitle");
465 tdoc.appendChild(title);
466 QDomNodeList objects = titleclip.childNodes();
467 int maxchild = objects.count();
468 for (int k = 0; k < maxchild; k++) {
470 QDomElement ob = objects.at(k).toElement();
471 if (ob.attribute("type") == "3") {
472 // text object - all of this goes into "xmldata"...
473 QDomElement item = tdoc.createElement("item");
474 item.setAttribute("z-index", ob.attribute("z"));
475 item.setAttribute("type", "QGraphicsTextItem");
476 QDomElement position = tdoc.createElement("position");
477 position.setAttribute("x", ob.attribute("x"));
478 position.setAttribute("y", ob.attribute("y"));
479 QDomElement content = tdoc.createElement("content");
480 content.setAttribute("font", ob.attribute("font_family"));
481 content.setAttribute("font-size", ob.attribute("font_size"));
482 content.setAttribute("font-bold", ob.attribute("bold"));
483 content.setAttribute("font-italic", ob.attribute("italic"));
484 content.setAttribute("font-underline", ob.attribute("underline"));
485 QString col = ob.attribute("color");
487 content.setAttribute("font-color", colorToString(c));
488 // todo: These fields are missing from the newly generated xmldata:
489 // transform, startviewport, endviewport, background
491 QDomText conttxt = tdoc.createTextNode(ob.attribute("text"));
492 content.appendChild(conttxt);
493 item.appendChild(position);
494 item.appendChild(content);
495 title.appendChild(item);
496 } else if (ob.attribute("type") == "5") {
498 QDomElement item = tdoc.createElement("item");
499 item.setAttribute("z-index", ob.attribute("z"));
500 item.setAttribute("type", "QGraphicsRectItem");
501 QDomElement position = tdoc.createElement("position");
502 position.setAttribute("x", ob.attribute("x"));
503 position.setAttribute("y", ob.attribute("y"));
504 QDomElement content = tdoc.createElement("content");
505 QString col = ob.attribute("color");
507 content.setAttribute("brushcolor", colorToString(c));
508 QString rect = "0,0,";
509 rect.append(ob.attribute("width"));
511 rect.append(ob.attribute("height"));
512 content.setAttribute("rect", rect);
513 item.appendChild(position);
514 item.appendChild(content);
515 title.appendChild(item);
518 prod.setAttribute("xmldata", tdoc.toString());
519 // mbd todo: This clearly does not work, as every title gets the same name - trying to leave it empty
520 // QStringList titleInfo = TitleWidget::getFreeTitleInfo(projectFolder());
521 // prod.setAttribute("titlename", titleInfo.at(0));
522 // prod.setAttribute("resource", titleInfo.at(1));
523 //kDebug()<<"TITLE DATA:\n"<<tdoc.toString();
525 } // End conversion of title clips.
527 } else if (m.isText()) {
528 QString comment = m.nodeValue();
529 if (!comment.isEmpty()) {
530 prod.setAttribute("description", comment);
535 int duration = prod.attribute("duration").toInt();
536 if (duration > 0) prod.setAttribute("out", QString::number(duration));
537 // The clip goes back in, but text clips should not go back in, at least not modified
538 westley.insertBefore(prod, QDomNode());
541 QDomNode westley0 = m_doc.elementsByTagName("westley").at(0);
542 if (!markers.firstChild().isNull()) westley0.appendChild(markers);
545 * Convert as much of the kdenlivedoc as possible. Use the producer in
546 * westley. First, remove the old stuff from westley, and add a new
547 * empty one. Also, track the max id in order to use it for the adding
550 int max_kproducer_id = 0;
551 westley0.removeChild(infoXmlNode);
552 QDomElement infoXml_new = m_doc.createElement("kdenlivedoc");
553 infoXml_new.setAttribute("profile", profile);
554 infoXml.setAttribute("position", startPos);
556 // Add all the producers that has a resource in westley
557 QDomElement westley_element = westley0.toElement();
558 if (westley_element.isNull()) {
559 kWarning() << "westley0 element in document was not a QDomElement - unable to add producers to new kdenlivedoc";
561 QDomNodeList wproducers = westley_element.elementsByTagName("producer");
562 int kmax = wproducers.count();
563 for (int i = 0; i < kmax; i++) {
564 QDomElement wproducer = wproducers.at(i).toElement();
565 if (wproducer.isNull()) {
566 kWarning() << "Found producer in westley0, that was not a QDomElement";
569 if (wproducer.attribute("id") == "black") continue;
570 // We have to do slightly different things, depending on the type
571 kDebug() << "Converting producer element with type" << wproducer.attribute("type");
572 if (wproducer.attribute("type").toInt() == TEXT) {
573 kDebug() << "Found TEXT element in producer" << endl;
574 QDomElement kproducer = wproducer.cloneNode(true).toElement();
575 kproducer.setTagName("kdenlive_producer");
576 infoXml_new.appendChild(kproducer);
578 * TODO: Perhaps needs some more changes here to
579 * "frequency", aspect ratio as a float, frame_size,
580 * channels, and later, resource and title name
583 QDomElement kproducer = m_doc.createElement("kdenlive_producer");
584 kproducer.setAttribute("id", wproducer.attribute("id"));
585 if (!wproducer.attribute("description").isEmpty())
586 kproducer.setAttribute("description", wproducer.attribute("description"));
587 kproducer.setAttribute("resource", wproducer.attribute("resource"));
588 kproducer.setAttribute("type", wproducer.attribute("type"));
589 // Testing fix for 358
590 if (!wproducer.attribute("aspect_ratio").isEmpty()) {
591 kproducer.setAttribute("aspect_ratio", wproducer.attribute("aspect_ratio"));
593 if (!wproducer.attribute("source_fps").isEmpty()) {
594 kproducer.setAttribute("fps", wproducer.attribute("source_fps"));
596 if (!wproducer.attribute("length").isEmpty()) {
597 kproducer.setAttribute("duration", wproducer.attribute("length"));
599 infoXml_new.appendChild(kproducer);
601 if (wproducer.attribute("id").toInt() > max_kproducer_id) {
602 max_kproducer_id = wproducer.attribute("id").toInt();
606 #define LOOKUP_FOLDER 1
609 * Look through all the folder elements of the old doc, for each folder,
610 * for each producer, get the id, look it up in the new doc, set the
611 * groupname and groupid. Note, this does not work at the moment - at
612 * least one folder shows up missing, and clips with no folder does not
615 //QDomElement infoXml_old = infoXmlNode.toElement();
616 if (!infoXml_old.isNull()) {
617 QDomNodeList folders = infoXml_old.elementsByTagName("folder");
618 int fsize = folders.size();
619 int groupId = max_kproducer_id + 1; // Start at +1 of max id of the kdenlive_producers
620 for (int i = 0; i < fsize; ++i) {
621 QDomElement folder = folders.at(i).toElement();
622 if (!folder.isNull()) {
623 QString groupName = folder.attribute("name");
624 kDebug() << "groupName: " << groupName << " with groupId: " << groupId;
625 QDomNodeList fproducers = folder.elementsByTagName("producer");
626 int psize = fproducers.size();
627 for (int j = 0; j < psize; ++j) {
628 QDomElement fproducer = fproducers.at(j).toElement();
629 if (!fproducer.isNull()) {
630 QString id = fproducer.attribute("id");
631 // This is not very effective, but compared to loading the clips, its a breeze
632 QDomNodeList kdenlive_producers = infoXml_new.elementsByTagName("kdenlive_producer");
633 int kpsize = kdenlive_producers.size();
634 for (int k = 0; k < kpsize; ++k) {
635 QDomElement kproducer = kdenlive_producers.at(k).toElement(); // Its an element for sure
636 if (id == kproducer.attribute("id")) {
637 // We do not check that it already is part of a folder
638 kproducer.setAttribute("groupid", groupId);
639 kproducer.setAttribute("groupname", groupName);
650 QDomNodeList elements = westley.childNodes();
651 max = elements.count();
652 for (int i = 0; i < max; i++) {
653 QDomElement prod = elements.at(0).toElement();
654 westley0.insertAfter(prod, QDomNode());
657 westley0.appendChild(infoXml_new);
659 westley0.removeChild(westley);
661 // adds <avfile /> information to <kdenlive_producer />
662 QDomNodeList kproducers = m_doc.elementsByTagName("kdenlive_producer");
663 QDomNodeList avfiles = infoXml_old.elementsByTagName("avfile");
664 kDebug() << "found" << avfiles.count() << "<avfile />s and" << kproducers.count() << "<kdenlive_producer />s";
665 for (int i = 0; i < avfiles.count(); ++i) {
666 QDomElement avfile = avfiles.at(i).toElement();
667 QDomElement kproducer;
669 kWarning() << "found an <avfile /> that is not a QDomElement";
671 QString id = avfile.attribute("id");
672 // this is horrible, must be rewritten, it's just for test
673 for (int j = 0; j < kproducers.count(); ++j) {
674 //kDebug() << "checking <kdenlive_producer /> with id" << kproducers.at(j).toElement().attribute("id");
675 if (kproducers.at(j).toElement().attribute("id") == id) {
676 kproducer = kproducers.at(j).toElement();
680 if (kproducer == QDomElement())
681 kWarning() << "no match for <avfile /> with id =" << id;
683 //kDebug() << "ready to set additional <avfile />'s attributes (id =" << id << ")";
684 kproducer.setAttribute("channels", avfile.attribute("channels"));
685 kproducer.setAttribute("duration", avfile.attribute("duration"));
686 kproducer.setAttribute("frame_size", avfile.attribute("width") + 'x' + avfile.attribute("height"));
687 kproducer.setAttribute("frequency", avfile.attribute("frequency"));
688 if (kproducer.attribute("description").isEmpty() && !avfile.attribute("description").isEmpty())
689 kproducer.setAttribute("description", avfile.attribute("description"));
693 infoXml = infoXml_new;
696 if (version <= 0.81) {
697 // Add the tracks information
698 QString tracksOrder = infoXml.attribute("tracks");
699 if (tracksOrder.isEmpty()) {
700 QDomNodeList tracks = m_doc.elementsByTagName("track");
701 for (int i = 0; i < tracks.count(); i++) {
702 QDomElement track = tracks.at(i).toElement();
703 if (track.attribute("producer") != "black_track") {
704 if (track.attribute("hide") == "video")
705 tracksOrder.append('a');
707 tracksOrder.append('v');
711 QDomElement tracksinfo = m_doc.createElement("tracksinfo");
712 for (int i = 0; i < tracksOrder.size(); i++) {
713 QDomElement trackinfo = m_doc.createElement("trackinfo");
714 if (tracksOrder.data()[i] == 'a') {
715 trackinfo.setAttribute("type", "audio");
716 trackinfo.setAttribute("blind", true);
718 trackinfo.setAttribute("blind", false);
719 trackinfo.setAttribute("mute", false);
720 trackinfo.setAttribute("locked", false);
721 tracksinfo.appendChild(trackinfo);
723 infoXml.appendChild(tracksinfo);
726 if (version <= 0.82) {
727 // Convert <westley />s in <mlt />s (MLT extreme makeover)
728 QDomNodeList westleyNodes = m_doc.elementsByTagName("westley");
729 for (int i = 0; i < westleyNodes.count(); i++) {
730 QDomElement westley = westleyNodes.at(i).toElement();
731 westley.setTagName("mlt");
735 if (version <= 0.83) {
736 // Replace point size with pixel size in text titles
737 if (m_doc.toString().contains("font-size")) {
738 KMessageBox::ButtonCode convert = KMessageBox::Continue;
739 QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
740 for (int i = 0; i < kproducerNodes.count() && convert != KMessageBox::No; ++i) {
741 QDomElement kproducer = kproducerNodes.at(i).toElement();
742 if (kproducer.attribute("type").toInt() == TEXT) {
744 data.setContent(kproducer.attribute("xmldata"));
745 QDomNodeList items = data.firstChild().childNodes();
746 for (int j = 0; j < items.count() && convert != KMessageBox::No; ++j) {
747 if (items.at(j).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
748 QDomNamedNodeMap textProperties = items.at(j).namedItem("content").attributes();
749 if (textProperties.namedItem("font-pixel-size").isNull() && !textProperties.namedItem("font-size").isNull()) {
750 // Ask the user if he wants to convert
751 if (convert != KMessageBox::Yes && convert != KMessageBox::No)
752 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"));
753 if (convert == KMessageBox::Yes) {
755 font.setPointSize(textProperties.namedItem("font-size").nodeValue().toInt());
756 QDomElement content = items.at(j).namedItem("content").toElement();
757 content.setAttribute("font-pixel-size", QFontInfo(font).pixelSize());
758 content.removeAttribute("font-size");
759 kproducer.setAttribute("xmldata", data.toString());
761 * You may be tempted to delete the preview file
762 * to force its recreation: bad idea (see
763 * http://www.kdenlive.org/mantis/view.php?id=749)
773 // Fill the <documentproperties /> element
774 QDomElement docProperties = infoXml.firstChildElement("documentproperties");
775 if (docProperties.isNull()) {
776 docProperties = m_doc.createElement("documentproperties");
777 docProperties.setAttribute("zonein", infoXml.attribute("zonein"));
778 docProperties.setAttribute("zoneout", infoXml.attribute("zoneout"));
779 docProperties.setAttribute("zoom", infoXml.attribute("zoom"));
780 docProperties.setAttribute("position", infoXml.attribute("position"));
781 infoXml.appendChild(docProperties);
785 if (version <= 0.84) {
786 // update the title clips to use the new MLT kdenlivetitle producer
787 QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
788 for (int i = 0; i < kproducerNodes.count(); ++i) {
789 QDomElement kproducer = kproducerNodes.at(i).toElement();
790 if (kproducer.attribute("type").toInt() == TEXT) {
791 QString data = kproducer.attribute("xmldata");
792 QString datafile = kproducer.attribute("resource");
793 if (!datafile.endsWith(".kdenlivetitle")) {
794 datafile = QString();
795 kproducer.setAttribute("resource", QString());
797 QString id = kproducer.attribute("id");
798 QDomNodeList mltproducers = m_doc.elementsByTagName("producer");
799 bool foundData = false;
800 bool foundResource = false;
801 bool foundService = false;
802 for (int j = 0; j < mltproducers.count(); j++) {
803 QDomElement wproducer = mltproducers.at(j).toElement();
804 if (wproducer.attribute("id") == id) {
805 QDomNodeList props = wproducer.childNodes();
806 for (int k = 0; k < props.count(); k++) {
807 if (props.at(k).toElement().attribute("name") == "xmldata") {
808 props.at(k).firstChild().setNodeValue(data);
810 } else if (props.at(k).toElement().attribute("name") == "mlt_service") {
811 props.at(k).firstChild().setNodeValue("kdenlivetitle");
813 } else if (props.at(k).toElement().attribute("name") == "resource") {
814 props.at(k).firstChild().setNodeValue(datafile);
815 foundResource = true;
819 QDomElement e = m_doc.createElement("property");
820 e.setAttribute("name", "xmldata");
821 QDomText value = m_doc.createTextNode(data);
822 e.appendChild(value);
823 wproducer.appendChild(e);
826 QDomElement e = m_doc.createElement("property");
827 e.setAttribute("name", "mlt_service");
828 QDomText value = m_doc.createTextNode("kdenlivetitle");
829 e.appendChild(value);
830 wproducer.appendChild(e);
832 if (!foundResource) {
833 QDomElement e = m_doc.createElement("property");
834 e.setAttribute("name", "resource");
835 QDomText value = m_doc.createTextNode(datafile);
836 e.appendChild(value);
837 wproducer.appendChild(e);
845 if (version <= 0.85) {
846 // update the LADSPA effects to use the new ladspa.id format instead of external xml file
847 QDomNodeList effectNodes = m_doc.elementsByTagName("filter");
848 for (int i = 0; i < effectNodes.count(); ++i) {
849 QDomElement effect = effectNodes.at(i).toElement();
850 if (EffectsList::property(effect, "mlt_service") == "ladspa") {
851 // Needs to be converted
852 QStringList info = getInfoFromEffectName(EffectsList::property(effect, "kdenlive_id"));
853 if (info.isEmpty()) continue;
854 // info contains the correct ladspa.id from kdenlive effect name, and a list of parameter's old and new names
855 EffectsList::setProperty(effect, "kdenlive_id", info.at(0));
856 EffectsList::setProperty(effect, "tag", info.at(0));
857 EffectsList::setProperty(effect, "mlt_service", info.at(0));
858 EffectsList::removeProperty(effect, "src");
859 for (int j = 1; j < info.size(); j++) {
860 QString value = EffectsList::property(effect, info.at(j).section('=', 0, 0));
861 if (!value.isEmpty()) {
862 // update parameter name
863 EffectsList::renameProperty(effect, info.at(j).section('=', 0, 0), info.at(j).section('=', 1, 1));
870 if (version <= 0.86) {
871 // Make sure we don't have avformat-novalidate producers, since it caused crashes
872 QDomNodeList producers = m_doc.elementsByTagName("producer");
873 int max = producers.count();
874 for (int i = 0; i < max; i++) {
875 QDomElement prod = producers.at(i).toElement();
876 if (EffectsList::property(prod, "mlt_service") == "avformat-novalidate")
877 EffectsList::setProperty(prod, "mlt_service", "avformat");
880 // 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
882 // Get profile info (width / height)
885 QDomElement profile = m_doc.firstChildElement("profile");
886 if (profile.isNull()) profile = infoXml.firstChildElement("profileinfo");
887 if (profile.isNull()) {
888 // could not find profile info, set PAL
893 profileWidth = profile.attribute("width").toInt();
894 profileHeight = profile.attribute("height").toInt();
896 QDomNodeList transitions = m_doc.elementsByTagName("transition");
897 max = transitions.count();
899 for (int i = 0; i < max; i++) {
900 QDomElement trans = transitions.at(i).toElement();
901 out = trans.attribute("out").toInt() - trans.attribute("in").toInt();
902 QString geom = EffectsList::property(trans, "geometry");
903 Mlt::Geometry *g = new Mlt::Geometry(geom.toUtf8().data(), out, profileWidth, profileHeight);
904 Mlt::GeometryItem item;
905 if (g->next_key(&item, out) == 0) {
906 // We have a keyframe just after last frame, try to move it to last frame
907 if (item.frame() == out + 1) {
911 EffectsList::setProperty(trans, "geometry", g->serialise());
918 if (version <= 0.87) {
919 if (!m_doc.firstChildElement("mlt").hasAttribute("LC_NUMERIC")) {
920 m_doc.firstChildElement("mlt").setAttribute("LC_NUMERIC", "C");
924 // The document has been converted: mark it as modified
925 infoXml.setAttribute("version", currentVersion);
930 QStringList DocumentValidator::getInfoFromEffectName(const QString oldName)
933 // Returns a list to convert old Kdenlive ladspa effects
934 if (oldName == "pitch_shift") {
935 info << "ladspa.1433";
938 else if (oldName == "vinyl") {
939 info << "ladspa.1905";
946 else if (oldName == "room_reverb") {
947 info << "ladspa.1216";
952 else if (oldName == "reverb") {
953 info << "ladspa.1423";
957 else if (oldName == "rate_scale") {
958 info << "ladspa.1417";
961 else if (oldName == "pitch_scale") {
962 info << "ladspa.1193";
965 else if (oldName == "phaser") {
966 info << "ladspa.1217";
969 info << "feedback=2";
972 else if (oldName == "limiter") {
973 info << "ladspa.1913";
978 else if (oldName == "equalizer_15") {
979 info << "ladspa.1197";
996 else if (oldName == "equalizer") {
997 info << "ladspa.1901";
1002 else if (oldName == "declipper") {
1003 info << "ladspa.1195";
1008 QString DocumentValidator::colorToString(const QColor& c)
1010 QString ret = "%1,%2,%3,%4";
1011 ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
1015 bool DocumentValidator::isProject() const
1017 QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
1018 return !infoXmlNode.isNull();
1021 bool DocumentValidator::isModified() const