1 /***************************************************************************
2 * Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@kdenlive.org) *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
18 ***************************************************************************/
21 #include "documentvalidator.h"
22 #include "definitions.h"
25 #include <KMessageBox>
26 #include <KApplication>
32 #include <mlt++/Mlt.h>
35 DocumentValidator::DocumentValidator(QDomDocument doc):
40 bool DocumentValidator::validate(const double currentVersion)
42 QDomElement mlt = m_doc.firstChildElement("mlt");
43 // At least the root element must be there
47 QDomElement kdenliveDoc = mlt.firstChildElement("kdenlivedoc");
48 // Check if we're validating a Kdenlive project
49 if (kdenliveDoc.isNull())
52 QLocale documentLocale;
54 if (mlt.hasAttribute("LC_NUMERIC")) {
55 // Set locale for the document
56 documentLocale = QLocale(mlt.attribute("LC_NUMERIC"));
57 if (documentLocale.decimalPoint() != QLocale().decimalPoint()) {
58 QDomElement docProperties = kdenliveDoc.firstChildElement("documentproperties");
59 if (docProperties.isNull()) {
60 docProperties = m_doc.createElement("documentproperties");
61 kdenliveDoc.appendChild(docProperties);
63 docProperties.setAttribute("readonly", 1);
64 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"));
68 // Upgrade the document to the latest version
69 if (!upgrade(documentLocale.toDouble(kdenliveDoc.attribute("version")), currentVersion))
73 * Check the syntax (this will be replaced by XSD validation with Qt 4.6)
74 * and correct some errors
77 // Return (or create) the tractor
78 QDomElement tractor = mlt.firstChildElement("tractor");
79 if (tractor.isNull()) {
81 tractor = m_doc.createElement("tractor");
82 tractor.setAttribute("global_feed", "1");
83 tractor.setAttribute("in", "0");
84 tractor.setAttribute("out", "-1");
85 tractor.setAttribute("id", "maintractor");
86 mlt.appendChild(tractor);
90 * Make sure at least one track exists, and they're equal in number to
91 * to the maximum between MLT and Kdenlive playlists and tracks
93 QDomNodeList playlists = m_doc.elementsByTagName("playlist");
94 int tracksMax = playlists.count() - 1; // Remove the black track
95 QDomNodeList tracks = tractor.elementsByTagName("track");
96 tracksMax = qMax(tracks.count() - 1, tracksMax);
97 QDomNodeList tracksinfo = kdenliveDoc.elementsByTagName("trackinfo");
98 tracksMax = qMax(tracksinfo.count(), tracksMax);
99 tracksMax = qMax(1, tracksMax); // Force existance of one track
100 if (playlists.count() - 1 < tracksMax ||
101 tracks.count() - 1 < tracksMax ||
102 tracksinfo.count() < tracksMax) {
103 kDebug() << "//// WARNING, PROJECT IS CORRUPTED, MISSING TRACK";
106 // use the MLT tracks as reference
107 if (tracks.count() - 1 < tracksMax) {
108 // Looks like one MLT track is missing, remove the extra Kdenlive track if there is one.
109 if (tracksinfo.count() != tracks.count() - 1) {
110 // The Kdenlive tracks are not ok, clear and rebuild them
111 QDomNode tinfo = kdenliveDoc.elementsByTagName("tracksinfo").at(0);
112 QDomNode tnode = tinfo.firstChild();
113 while (!tnode.isNull()) {
114 tinfo.removeChild(tnode);
115 tnode = tinfo.firstChild();
118 for (int i = 1; i < tracks.count(); i++) {
119 QString hide = tracks.at(i).toElement().attribute("hide");
120 QDomElement newTrack = m_doc.createElement("trackinfo");
121 if (hide == "video") {
123 newTrack.setAttribute("type", "audio");
124 newTrack.setAttribute("blind", 1);
125 newTrack.setAttribute("mute", 0);
126 newTrack.setAttribute("lock", 0);
128 newTrack.setAttribute("blind", 0);
129 newTrack.setAttribute("mute", 0);
130 newTrack.setAttribute("lock", 0);
132 tinfo.appendChild(newTrack);
137 if (playlists.count() - 1 < tracksMax) {
138 difference = tracksMax - (playlists.count() - 1);
139 for (int i = 0; i < difference; ++i) {
140 QDomElement playlist = m_doc.createElement("playlist");
141 mlt.appendChild(playlist);
144 if (tracks.count() - 1 < tracksMax) {
145 difference = tracksMax - (tracks.count() - 1);
146 for (int i = 0; i < difference; ++i) {
147 QDomElement track = m_doc.createElement("track");
148 tractor.appendChild(track);
151 if (tracksinfo.count() < tracksMax) {
152 QDomElement tracksinfoElm = kdenliveDoc.firstChildElement("tracksinfo");
153 if (tracksinfoElm.isNull()) {
154 tracksinfoElm = m_doc.createElement("tracksinfo");
155 kdenliveDoc.appendChild(tracksinfoElm);
157 difference = tracksMax - tracksinfo.count();
158 for (int i = 0; i < difference; ++i) {
159 QDomElement trackinfo = m_doc.createElement("trackinfo");
160 trackinfo.setAttribute("mute", "0");
161 trackinfo.setAttribute("locked", "0");
162 tracksinfoElm.appendChild(trackinfo);
167 // TODO: check the tracks references
168 // TODO: check internal mix transitions
175 bool DocumentValidator::upgrade(double version, const double currentVersion)
177 kDebug() << "Opening a document with version " << version;
179 // No conversion needed
180 if (version == currentVersion) {
184 // The document is too new
185 if (version > currentVersion) {
186 kDebug() << "Unable to open document with version " << version;
187 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"));
191 // Unsupported document versions
192 if (version == 0.5 || version == 0.7) {
193 kDebug() << "Unable to open document with version " << version;
194 KMessageBox::sorry(kapp->activeWindow(), i18n("This project type is unsupported (version %1) and can't be loaded.", version), i18n("Unable to open project"));
199 QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
200 QDomElement infoXml = infoXmlNode.toElement();
201 infoXml.setAttribute("upgraded", "1");
203 if (version <= 0.6) {
204 QDomElement infoXml_old = infoXmlNode.cloneNode(true).toElement(); // Needed for folders
205 QDomNode westley = m_doc.elementsByTagName("westley").at(1);
206 QDomNode tractor = m_doc.elementsByTagName("tractor").at(0);
207 QDomNode multitrack = m_doc.elementsByTagName("multitrack").at(0);
208 QDomNodeList playlists = m_doc.elementsByTagName("playlist");
210 QDomNode props = m_doc.elementsByTagName("properties").at(0).toElement();
211 QString profile = props.toElement().attribute("videoprofile");
212 int startPos = props.toElement().attribute("timeline_position").toInt();
213 if (profile == "dv_wide")
214 profile = "dv_pal_wide";
216 // move playlists outside of tractor and add the tracks instead
217 int max = playlists.count();
218 if (westley.isNull()) {
219 westley = m_doc.createElement("westley");
220 m_doc.documentElement().appendChild(westley);
222 if (tractor.isNull()) {
223 kDebug() << "// NO MLT PLAYLIST, building empty one";
224 QDomElement blank_tractor = m_doc.createElement("tractor");
225 westley.appendChild(blank_tractor);
226 QDomElement blank_playlist = m_doc.createElement("playlist");
227 blank_playlist.setAttribute("id", "black_track");
228 westley.insertBefore(blank_playlist, QDomNode());
229 QDomElement blank_track = m_doc.createElement("track");
230 blank_track.setAttribute("producer", "black_track");
231 blank_tractor.appendChild(blank_track);
233 QDomNodeList kdenlivetracks = m_doc.elementsByTagName("kdenlivetrack");
234 for (int i = 0; i < kdenlivetracks.count(); i++) {
235 blank_playlist = m_doc.createElement("playlist");
236 blank_playlist.setAttribute("id", "playlist" + QString::number(i));
237 westley.insertBefore(blank_playlist, QDomNode());
238 blank_track = m_doc.createElement("track");
239 blank_track.setAttribute("producer", "playlist" + QString::number(i));
240 blank_tractor.appendChild(blank_track);
241 if (kdenlivetracks.at(i).toElement().attribute("cliptype") == "Sound") {
242 blank_playlist.setAttribute("hide", "video");
243 blank_track.setAttribute("hide", "video");
246 } else for (int i = 0; i < max; i++) {
247 QDomNode n = playlists.at(i);
248 westley.insertBefore(n, QDomNode());
249 QDomElement pl = n.toElement();
250 QDomElement track = m_doc.createElement("track");
251 QString trackType = pl.attribute("hide");
252 if (!trackType.isEmpty())
253 track.setAttribute("hide", trackType);
254 QString playlist_id = pl.attribute("id");
255 if (playlist_id.isEmpty()) {
256 playlist_id = "black_track";
257 pl.setAttribute("id", playlist_id);
259 track.setAttribute("producer", playlist_id);
260 //tractor.appendChild(track);
261 #define KEEP_TRACK_ORDER 1
262 #ifdef KEEP_TRACK_ORDER
263 tractor.insertAfter(track, QDomNode());
265 // Insert the new track in an order that hopefully matches the 3 video, then 2 audio tracks of Kdenlive 0.7.0
266 // insertion sort - O( tracks*tracks )
267 // Note, this breaks _all_ transitions - but you can move them up and down afterwards.
268 QDomElement tractor_elem = tractor.toElement();
269 if (! tractor_elem.isNull()) {
270 QDomNodeList tracks = tractor_elem.elementsByTagName("track");
271 int size = tracks.size();
273 tractor.insertAfter(track, QDomNode());
275 bool inserted = false;
276 for (int i = 0; i < size; ++i) {
277 QDomElement track_elem = tracks.at(i).toElement();
278 if (track_elem.isNull()) {
279 tractor.insertAfter(track, QDomNode());
283 kDebug() << "playlist_id: " << playlist_id << " producer:" << track_elem.attribute("producer");
284 if (playlist_id < track_elem.attribute("producer")) {
285 tractor.insertBefore(track, track_elem);
291 // Reach here, no insertion, insert last
293 tractor.insertAfter(track, QDomNode());
297 kWarning() << "tractor was not a QDomElement";
298 tractor.insertAfter(track, QDomNode());
302 tractor.removeChild(multitrack);
304 // audio track mixing transitions should not be added to track view, so add required attribute
305 QDomNodeList transitions = m_doc.elementsByTagName("transition");
306 max = transitions.count();
307 for (int i = 0; i < max; i++) {
308 QDomElement tr = transitions.at(i).toElement();
309 if (tr.attribute("combine") == "1" && tr.attribute("mlt_service") == "mix") {
310 QDomElement property = m_doc.createElement("property");
311 property.setAttribute("name", "internal_added");
312 QDomText value = m_doc.createTextNode("237");
313 property.appendChild(value);
314 tr.appendChild(property);
315 property = m_doc.createElement("property");
316 property.setAttribute("name", "mlt_service");
317 value = m_doc.createTextNode("mix");
318 property.appendChild(value);
319 tr.appendChild(property);
321 // convert transition
322 QDomNamedNodeMap attrs = tr.attributes();
323 for (int j = 0; j < attrs.count(); j++) {
324 QString attrName = attrs.item(j).nodeName();
325 if (attrName != "in" && attrName != "out" && attrName != "id") {
326 QDomElement property = m_doc.createElement("property");
327 property.setAttribute("name", attrName);
328 QDomText value = m_doc.createTextNode(attrs.item(j).nodeValue());
329 property.appendChild(value);
330 tr.appendChild(property);
336 // move transitions after tracks
337 for (int i = 0; i < max; i++) {
338 tractor.insertAfter(transitions.at(0), QDomNode());
341 // Fix filters format
342 QDomNodeList entries = m_doc.elementsByTagName("entry");
343 max = entries.count();
344 for (int i = 0; i < max; i++) {
347 QDomNode m = entries.at(i).firstChild();
348 while (!m.isNull()) {
349 if (m.toElement().tagName() == "filter") {
350 QDomElement filt = m.toElement();
351 QDomNamedNodeMap attrs = filt.attributes();
352 QString current_id = filt.attribute("kdenlive_id");
353 if (current_id != last_id) {
355 last_id = current_id;
357 QDomElement e = m_doc.createElement("property");
358 e.setAttribute("name", "kdenlive_ix");
359 QDomText value = m_doc.createTextNode(QString::number(effectix));
360 e.appendChild(value);
362 for (int j = 0; j < attrs.count(); j++) {
363 QDomAttr a = attrs.item(j).toAttr();
365 kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
366 QDomElement e = m_doc.createElement("property");
367 e.setAttribute("name", a.name());
368 QDomText value = m_doc.createTextNode(a.value());
369 e.appendChild(value);
380 QDomNodeList filters = m_doc.elementsByTagName("filter");
381 max = filters.count();
384 for (int i = 0; i < max; i++) {
385 QDomElement filt = filters.at(i).toElement();
386 QDomNamedNodeMap attrs = filt.attributes();
387 QString current_id = filt.attribute("kdenlive_id");
388 if (current_id != last_id) {
390 last_id = current_id;
392 QDomElement e = m_doc.createElement("property");
393 e.setAttribute("name", "kdenlive_ix");
394 QDomText value = m_doc.createTextNode(QString::number(1));
395 e.appendChild(value);
397 for (int j = 0; j < attrs.count(); j++) {
398 QDomAttr a = attrs.item(j).toAttr();
400 kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
401 QDomElement e = m_doc.createElement("property");
402 e.setAttribute("name", a.name());
403 QDomText value = m_doc.createTextNode(a.value());
404 e.appendChild(value);
411 QDomNodeList producers = westley.toElement().elementsByTagName("producer");
412 max = producers.count();
413 for (int i = 0; i < max; i++) {
414 QDomElement prod = producers.at(i).toElement();
415 if (prod.attribute("mlt_service") == "framebuffer") {
416 QString slowmotionprod = prod.attribute("resource");
417 slowmotionprod.replace(':', '?');
418 kDebug() << "// FOUND WRONG SLOWMO, new: " << slowmotionprod;
419 prod.setAttribute("resource", slowmotionprod);
422 // move producers to correct place, markers to a global list, fix clip descriptions
423 QDomElement markers = m_doc.createElement("markers");
424 // This will get the xml producers:
425 producers = m_doc.elementsByTagName("producer");
426 max = producers.count();
427 for (int i = 0; i < max; i++) {
428 QDomElement prod = producers.at(0).toElement();
429 // add resource also as a property (to allow path correction in setNewResource())
430 // TODO: will it work with slowmotion? needs testing
431 /*if (!prod.attribute("resource").isEmpty()) {
432 QDomElement prop_resource = m_doc.createElement("property");
433 prop_resource.setAttribute("name", "resource");
434 QDomText resource = m_doc.createTextNode(prod.attribute("resource"));
435 prop_resource.appendChild(resource);
436 prod.appendChild(prop_resource);
438 QDomNode m = prod.firstChild();
440 if (m.toElement().tagName() == "markers") {
441 QDomNodeList prodchilds = m.childNodes();
442 int maxchild = prodchilds.count();
443 for (int k = 0; k < maxchild; k++) {
444 QDomElement mark = prodchilds.at(0).toElement();
445 mark.setAttribute("id", prod.attribute("id"));
446 markers.insertAfter(mark, QDomNode());
449 } else if (prod.attribute("type").toInt() == TEXT) {
450 // convert title clip
451 if (m.toElement().tagName() == "textclip") {
453 QDomElement titleclip = m.toElement();
454 QDomElement title = tdoc.createElement("kdenlivetitle");
455 tdoc.appendChild(title);
456 QDomNodeList objects = titleclip.childNodes();
457 int maxchild = objects.count();
458 for (int k = 0; k < maxchild; k++) {
460 QDomElement ob = objects.at(k).toElement();
461 if (ob.attribute("type") == "3") {
462 // text object - all of this goes into "xmldata"...
463 QDomElement item = tdoc.createElement("item");
464 item.setAttribute("z-index", ob.attribute("z"));
465 item.setAttribute("type", "QGraphicsTextItem");
466 QDomElement position = tdoc.createElement("position");
467 position.setAttribute("x", ob.attribute("x"));
468 position.setAttribute("y", ob.attribute("y"));
469 QDomElement content = tdoc.createElement("content");
470 content.setAttribute("font", ob.attribute("font_family"));
471 content.setAttribute("font-size", ob.attribute("font_size"));
472 content.setAttribute("font-bold", ob.attribute("bold"));
473 content.setAttribute("font-italic", ob.attribute("italic"));
474 content.setAttribute("font-underline", ob.attribute("underline"));
475 QString col = ob.attribute("color");
477 content.setAttribute("font-color", colorToString(c));
478 // todo: These fields are missing from the newly generated xmldata:
479 // transform, startviewport, endviewport, background
481 QDomText conttxt = tdoc.createTextNode(ob.attribute("text"));
482 content.appendChild(conttxt);
483 item.appendChild(position);
484 item.appendChild(content);
485 title.appendChild(item);
486 } else if (ob.attribute("type") == "5") {
488 QDomElement item = tdoc.createElement("item");
489 item.setAttribute("z-index", ob.attribute("z"));
490 item.setAttribute("type", "QGraphicsRectItem");
491 QDomElement position = tdoc.createElement("position");
492 position.setAttribute("x", ob.attribute("x"));
493 position.setAttribute("y", ob.attribute("y"));
494 QDomElement content = tdoc.createElement("content");
495 QString col = ob.attribute("color");
497 content.setAttribute("brushcolor", colorToString(c));
498 QString rect = "0,0,";
499 rect.append(ob.attribute("width"));
501 rect.append(ob.attribute("height"));
502 content.setAttribute("rect", rect);
503 item.appendChild(position);
504 item.appendChild(content);
505 title.appendChild(item);
508 prod.setAttribute("xmldata", tdoc.toString());
509 // mbd todo: This clearly does not work, as every title gets the same name - trying to leave it empty
510 // QStringList titleInfo = TitleWidget::getFreeTitleInfo(projectFolder());
511 // prod.setAttribute("titlename", titleInfo.at(0));
512 // prod.setAttribute("resource", titleInfo.at(1));
513 //kDebug()<<"TITLE DATA:\n"<<tdoc.toString();
515 } // End conversion of title clips.
517 } else if (m.isText()) {
518 QString comment = m.nodeValue();
519 if (!comment.isEmpty()) {
520 prod.setAttribute("description", comment);
525 int duration = prod.attribute("duration").toInt();
526 if (duration > 0) prod.setAttribute("out", QString::number(duration));
527 // The clip goes back in, but text clips should not go back in, at least not modified
528 westley.insertBefore(prod, QDomNode());
531 QDomNode westley0 = m_doc.elementsByTagName("westley").at(0);
532 if (!markers.firstChild().isNull()) westley0.appendChild(markers);
535 * Convert as much of the kdenlivedoc as possible. Use the producer in
536 * westley. First, remove the old stuff from westley, and add a new
537 * empty one. Also, track the max id in order to use it for the adding
540 int max_kproducer_id = 0;
541 westley0.removeChild(infoXmlNode);
542 QDomElement infoXml_new = m_doc.createElement("kdenlivedoc");
543 infoXml_new.setAttribute("profile", profile);
544 infoXml.setAttribute("position", startPos);
546 // Add all the producers that has a resource in westley
547 QDomElement westley_element = westley0.toElement();
548 if (westley_element.isNull()) {
549 kWarning() << "westley0 element in document was not a QDomElement - unable to add producers to new kdenlivedoc";
551 QDomNodeList wproducers = westley_element.elementsByTagName("producer");
552 int kmax = wproducers.count();
553 for (int i = 0; i < kmax; i++) {
554 QDomElement wproducer = wproducers.at(i).toElement();
555 if (wproducer.isNull()) {
556 kWarning() << "Found producer in westley0, that was not a QDomElement";
559 if (wproducer.attribute("id") == "black") continue;
560 // We have to do slightly different things, depending on the type
561 kDebug() << "Converting producer element with type" << wproducer.attribute("type");
562 if (wproducer.attribute("type").toInt() == TEXT) {
563 kDebug() << "Found TEXT element in producer" << endl;
564 QDomElement kproducer = wproducer.cloneNode(true).toElement();
565 kproducer.setTagName("kdenlive_producer");
566 infoXml_new.appendChild(kproducer);
568 * TODO: Perhaps needs some more changes here to
569 * "frequency", aspect ratio as a float, frame_size,
570 * channels, and later, resource and title name
573 QDomElement kproducer = m_doc.createElement("kdenlive_producer");
574 kproducer.setAttribute("id", wproducer.attribute("id"));
575 if (!wproducer.attribute("description").isEmpty())
576 kproducer.setAttribute("description", wproducer.attribute("description"));
577 kproducer.setAttribute("resource", wproducer.attribute("resource"));
578 kproducer.setAttribute("type", wproducer.attribute("type"));
579 // Testing fix for 358
580 if (!wproducer.attribute("aspect_ratio").isEmpty()) {
581 kproducer.setAttribute("aspect_ratio", wproducer.attribute("aspect_ratio"));
583 if (!wproducer.attribute("source_fps").isEmpty()) {
584 kproducer.setAttribute("fps", wproducer.attribute("source_fps"));
586 if (!wproducer.attribute("length").isEmpty()) {
587 kproducer.setAttribute("duration", wproducer.attribute("length"));
589 infoXml_new.appendChild(kproducer);
591 if (wproducer.attribute("id").toInt() > max_kproducer_id) {
592 max_kproducer_id = wproducer.attribute("id").toInt();
596 #define LOOKUP_FOLDER 1
599 * Look through all the folder elements of the old doc, for each folder,
600 * for each producer, get the id, look it up in the new doc, set the
601 * groupname and groupid. Note, this does not work at the moment - at
602 * least one folder shows up missing, and clips with no folder does not
605 //QDomElement infoXml_old = infoXmlNode.toElement();
606 if (!infoXml_old.isNull()) {
607 QDomNodeList folders = infoXml_old.elementsByTagName("folder");
608 int fsize = folders.size();
609 int groupId = max_kproducer_id + 1; // Start at +1 of max id of the kdenlive_producers
610 for (int i = 0; i < fsize; ++i) {
611 QDomElement folder = folders.at(i).toElement();
612 if (!folder.isNull()) {
613 QString groupName = folder.attribute("name");
614 kDebug() << "groupName: " << groupName << " with groupId: " << groupId;
615 QDomNodeList fproducers = folder.elementsByTagName("producer");
616 int psize = fproducers.size();
617 for (int j = 0; j < psize; ++j) {
618 QDomElement fproducer = fproducers.at(j).toElement();
619 if (!fproducer.isNull()) {
620 QString id = fproducer.attribute("id");
621 // This is not very effective, but compared to loading the clips, its a breeze
622 QDomNodeList kdenlive_producers = infoXml_new.elementsByTagName("kdenlive_producer");
623 int kpsize = kdenlive_producers.size();
624 for (int k = 0; k < kpsize; ++k) {
625 QDomElement kproducer = kdenlive_producers.at(k).toElement(); // Its an element for sure
626 if (id == kproducer.attribute("id")) {
627 // We do not check that it already is part of a folder
628 kproducer.setAttribute("groupid", groupId);
629 kproducer.setAttribute("groupname", groupName);
640 QDomNodeList elements = westley.childNodes();
641 max = elements.count();
642 for (int i = 0; i < max; i++) {
643 QDomElement prod = elements.at(0).toElement();
644 westley0.insertAfter(prod, QDomNode());
647 westley0.appendChild(infoXml_new);
649 westley0.removeChild(westley);
651 // adds <avfile /> information to <kdenlive_producer />
652 QDomNodeList kproducers = m_doc.elementsByTagName("kdenlive_producer");
653 QDomNodeList avfiles = infoXml_old.elementsByTagName("avfile");
654 kDebug() << "found" << avfiles.count() << "<avfile />s and" << kproducers.count() << "<kdenlive_producer />s";
655 for (int i = 0; i < avfiles.count(); ++i) {
656 QDomElement avfile = avfiles.at(i).toElement();
657 QDomElement kproducer;
659 kWarning() << "found an <avfile /> that is not a QDomElement";
661 QString id = avfile.attribute("id");
662 // this is horrible, must be rewritten, it's just for test
663 for (int j = 0; j < kproducers.count(); ++j) {
664 //kDebug() << "checking <kdenlive_producer /> with id" << kproducers.at(j).toElement().attribute("id");
665 if (kproducers.at(j).toElement().attribute("id") == id) {
666 kproducer = kproducers.at(j).toElement();
670 if (kproducer == QDomElement())
671 kWarning() << "no match for <avfile /> with id =" << id;
673 //kDebug() << "ready to set additional <avfile />'s attributes (id =" << id << ")";
674 kproducer.setAttribute("channels", avfile.attribute("channels"));
675 kproducer.setAttribute("duration", avfile.attribute("duration"));
676 kproducer.setAttribute("frame_size", avfile.attribute("width") + 'x' + avfile.attribute("height"));
677 kproducer.setAttribute("frequency", avfile.attribute("frequency"));
678 if (kproducer.attribute("description").isEmpty() && !avfile.attribute("description").isEmpty())
679 kproducer.setAttribute("description", avfile.attribute("description"));
683 infoXml = infoXml_new;
686 if (version <= 0.81) {
687 // Add the tracks information
688 QString tracksOrder = infoXml.attribute("tracks");
689 if (tracksOrder.isEmpty()) {
690 QDomNodeList tracks = m_doc.elementsByTagName("track");
691 for (int i = 0; i < tracks.count(); i++) {
692 QDomElement track = tracks.at(i).toElement();
693 if (track.attribute("producer") != "black_track") {
694 if (track.attribute("hide") == "video")
695 tracksOrder.append('a');
697 tracksOrder.append('v');
701 QDomElement tracksinfo = m_doc.createElement("tracksinfo");
702 for (int i = 0; i < tracksOrder.size(); i++) {
703 QDomElement trackinfo = m_doc.createElement("trackinfo");
704 if (tracksOrder.data()[i] == 'a') {
705 trackinfo.setAttribute("type", "audio");
706 trackinfo.setAttribute("blind", true);
708 trackinfo.setAttribute("blind", false);
709 trackinfo.setAttribute("mute", false);
710 trackinfo.setAttribute("locked", false);
711 tracksinfo.appendChild(trackinfo);
713 infoXml.appendChild(tracksinfo);
716 if (version <= 0.82) {
717 // Convert <westley />s in <mlt />s (MLT extreme makeover)
718 QDomNodeList westleyNodes = m_doc.elementsByTagName("westley");
719 for (int i = 0; i < westleyNodes.count(); i++) {
720 QDomElement westley = westleyNodes.at(i).toElement();
721 westley.setTagName("mlt");
725 if (version <= 0.83) {
726 // Replace point size with pixel size in text titles
727 if (m_doc.toString().contains("font-size")) {
728 KMessageBox::ButtonCode convert = KMessageBox::Continue;
729 QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
730 for (int i = 0; i < kproducerNodes.count() && convert != KMessageBox::No; ++i) {
731 QDomElement kproducer = kproducerNodes.at(i).toElement();
732 if (kproducer.attribute("type").toInt() == TEXT) {
734 data.setContent(kproducer.attribute("xmldata"));
735 QDomNodeList items = data.firstChild().childNodes();
736 for (int j = 0; j < items.count() && convert != KMessageBox::No; ++j) {
737 if (items.at(j).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
738 QDomNamedNodeMap textProperties = items.at(j).namedItem("content").attributes();
739 if (textProperties.namedItem("font-pixel-size").isNull() && !textProperties.namedItem("font-size").isNull()) {
740 // Ask the user if he wants to convert
741 if (convert != KMessageBox::Yes && convert != KMessageBox::No)
742 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"));
743 if (convert == KMessageBox::Yes) {
745 font.setPointSize(textProperties.namedItem("font-size").nodeValue().toInt());
746 QDomElement content = items.at(j).namedItem("content").toElement();
747 content.setAttribute("font-pixel-size", QFontInfo(font).pixelSize());
748 content.removeAttribute("font-size");
749 kproducer.setAttribute("xmldata", data.toString());
751 * You may be tempted to delete the preview file
752 * to force its recreation: bad idea (see
753 * http://www.kdenlive.org/mantis/view.php?id=749)
763 // Fill the <documentproperties /> element
764 QDomElement docProperties = infoXml.firstChildElement("documentproperties");
765 if (docProperties.isNull()) {
766 docProperties = m_doc.createElement("documentproperties");
767 docProperties.setAttribute("zonein", infoXml.attribute("zonein"));
768 docProperties.setAttribute("zoneout", infoXml.attribute("zoneout"));
769 docProperties.setAttribute("zoom", infoXml.attribute("zoom"));
770 docProperties.setAttribute("position", infoXml.attribute("position"));
771 infoXml.appendChild(docProperties);
775 if (version <= 0.84) {
776 // update the title clips to use the new MLT kdenlivetitle producer
777 QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
778 for (int i = 0; i < kproducerNodes.count(); ++i) {
779 QDomElement kproducer = kproducerNodes.at(i).toElement();
780 if (kproducer.attribute("type").toInt() == TEXT) {
781 QString data = kproducer.attribute("xmldata");
782 QString datafile = kproducer.attribute("resource");
783 if (!datafile.endsWith(".kdenlivetitle")) {
784 datafile = QString();
785 kproducer.setAttribute("resource", QString());
787 QString id = kproducer.attribute("id");
788 QDomNodeList mltproducers = m_doc.elementsByTagName("producer");
789 bool foundData = false;
790 bool foundResource = false;
791 bool foundService = false;
792 for (int j = 0; j < mltproducers.count(); j++) {
793 QDomElement wproducer = mltproducers.at(j).toElement();
794 if (wproducer.attribute("id") == id) {
795 QDomNodeList props = wproducer.childNodes();
796 for (int k = 0; k < props.count(); k++) {
797 if (props.at(k).toElement().attribute("name") == "xmldata") {
798 props.at(k).firstChild().setNodeValue(data);
800 } else if (props.at(k).toElement().attribute("name") == "mlt_service") {
801 props.at(k).firstChild().setNodeValue("kdenlivetitle");
803 } else if (props.at(k).toElement().attribute("name") == "resource") {
804 props.at(k).firstChild().setNodeValue(datafile);
805 foundResource = true;
809 QDomElement e = m_doc.createElement("property");
810 e.setAttribute("name", "xmldata");
811 QDomText value = m_doc.createTextNode(data);
812 e.appendChild(value);
813 wproducer.appendChild(e);
816 QDomElement e = m_doc.createElement("property");
817 e.setAttribute("name", "mlt_service");
818 QDomText value = m_doc.createTextNode("kdenlivetitle");
819 e.appendChild(value);
820 wproducer.appendChild(e);
822 if (!foundResource) {
823 QDomElement e = m_doc.createElement("property");
824 e.setAttribute("name", "resource");
825 QDomText value = m_doc.createTextNode(datafile);
826 e.appendChild(value);
827 wproducer.appendChild(e);
835 if (version <= 0.85) {
836 // update the LADSPA effects to use the new ladspa.id format instead of external xml file
837 QDomNodeList effectNodes = m_doc.elementsByTagName("filter");
838 for (int i = 0; i < effectNodes.count(); ++i) {
839 QDomElement effect = effectNodes.at(i).toElement();
840 if (EffectsList::property(effect, "mlt_service") == "ladspa") {
841 // Needs to be converted
842 QStringList info = getInfoFromEffectName(EffectsList::property(effect, "kdenlive_id"));
843 if (info.isEmpty()) continue;
844 // info contains the correct ladspa.id from kdenlive effect name, and a list of parameter's old and new names
845 EffectsList::setProperty(effect, "kdenlive_id", info.at(0));
846 EffectsList::setProperty(effect, "tag", info.at(0));
847 EffectsList::setProperty(effect, "mlt_service", info.at(0));
848 EffectsList::removeProperty(effect, "src");
849 for (int j = 1; j < info.size(); j++) {
850 QString value = EffectsList::property(effect, info.at(j).section('=', 0, 0));
851 if (!value.isEmpty()) {
852 // update parameter name
853 EffectsList::renameProperty(effect, info.at(j).section('=', 0, 0), info.at(j).section('=', 1, 1));
860 if (version <= 0.86) {
861 // Make sure we don't have avformat-novalidate producers, since it caused crashes
862 QDomNodeList producers = m_doc.elementsByTagName("producer");
863 int max = producers.count();
864 for (int i = 0; i < max; i++) {
865 QDomElement prod = producers.at(i).toElement();
866 if (EffectsList::property(prod, "mlt_service") == "avformat-novalidate")
867 EffectsList::setProperty(prod, "mlt_service", "avformat");
870 // 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
872 // Get profile info (width / height)
875 QDomElement profile = m_doc.firstChildElement("profile");
876 if (profile.isNull()) profile = infoXml.firstChildElement("profileinfo");
877 if (profile.isNull()) {
878 // could not find profile info, set PAL
883 profileWidth = profile.attribute("width").toInt();
884 profileHeight = profile.attribute("height").toInt();
886 QDomNodeList transitions = m_doc.elementsByTagName("transition");
887 max = transitions.count();
889 for (int i = 0; i < max; i++) {
890 QDomElement trans = transitions.at(i).toElement();
891 out = trans.attribute("out").toInt() - trans.attribute("in").toInt();
892 QString geom = EffectsList::property(trans, "geometry");
893 Mlt::Geometry *g = new Mlt::Geometry(geom.toUtf8().data(), out, profileWidth, profileHeight);
894 Mlt::GeometryItem item;
895 if (g->next_key(&item, out) == 0) {
896 // We have a keyframe just after last frame, try to move it to last frame
897 if (item.frame() == out + 1) {
901 EffectsList::setProperty(trans, "geometry", g->serialise());
908 // The document has been converted: mark it as modified
909 infoXml.setAttribute("version", currentVersion);
914 QStringList DocumentValidator::getInfoFromEffectName(const QString oldName)
917 // Returns a list to convert old Kdenlive ladspa effects
918 if (oldName == "pitch_shift") {
919 info << "ladspa.1433";
922 else if (oldName == "vinyl") {
923 info << "ladspa.1905";
930 else if (oldName == "room_reverb") {
931 info << "ladspa.1216";
936 else if (oldName == "reverb") {
937 info << "ladspa.1423";
941 else if (oldName == "rate_scale") {
942 info << "ladspa.1417";
945 else if (oldName == "pitch_scale") {
946 info << "ladspa.1193";
949 else if (oldName == "phaser") {
950 info << "ladspa.1217";
953 info << "feedback=2";
956 else if (oldName == "limiter") {
957 info << "ladspa.1913";
962 else if (oldName == "equalizer_15") {
963 info << "ladspa.1197";
980 else if (oldName == "equalizer") {
981 info << "ladspa.1901";
986 else if (oldName == "declipper") {
987 info << "ladspa.1195";
992 QString DocumentValidator::colorToString(const QColor& c)
994 QString ret = "%1,%2,%3,%4";
995 ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
999 bool DocumentValidator::isProject() const
1001 QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
1002 return !infoXmlNode.isNull();
1005 bool DocumentValidator::isModified() const