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"
24 #include "mainwindow.h"
27 #include <KMessageBox>
28 #include <KApplication>
31 #include <KStandardDirs>
37 #include <QScriptEngine>
39 #include <mlt++/Mlt.h>
44 DocumentValidator::DocumentValidator(QDomDocument doc):
49 bool DocumentValidator::validate(const double currentVersion)
51 QDomElement mlt = m_doc.firstChildElement("mlt");
52 // At least the root element must be there
56 QDomElement kdenliveDoc = mlt.firstChildElement("kdenlivedoc");
57 // Check if we're validating a Kdenlive project
58 if (kdenliveDoc.isNull())
61 // Previous MLT / Kdenlive versions used C locale by default
62 QLocale documentLocale = QLocale::c();
64 if (mlt.hasAttribute("LC_NUMERIC")) {
65 // Set locale for the document
66 // WARNING: what should be done in case the locale does not exist on the system?
67 setlocale(LC_NUMERIC, mlt.attribute("LC_NUMERIC").toUtf8().constData());
68 documentLocale = QLocale(mlt.attribute("LC_NUMERIC"));
71 documentLocale.setNumberOptions(QLocale::OmitGroupSeparator);
72 if (documentLocale != QLocale()) {
73 QLocale::setDefault(documentLocale);
74 // locale conversion might need to be redone
75 initEffects::parseEffectFiles();
78 // TODO: remove after string freeze
80 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"));
82 // Upgrade the document to the latest version
83 if (!upgrade(documentLocale.toDouble(kdenliveDoc.attribute("version")), currentVersion))
87 * Check the syntax (this will be replaced by XSD validation with Qt 4.6)
88 * and correct some errors
91 // Return (or create) the tractor
92 QDomElement tractor = mlt.firstChildElement("tractor");
93 if (tractor.isNull()) {
95 tractor = m_doc.createElement("tractor");
96 tractor.setAttribute("global_feed", "1");
97 tractor.setAttribute("in", "0");
98 tractor.setAttribute("out", "-1");
99 tractor.setAttribute("id", "maintractor");
100 mlt.appendChild(tractor);
104 * Make sure at least one track exists, and they're equal in number to
105 * to the maximum between MLT and Kdenlive playlists and tracks
107 QDomNodeList playlists = m_doc.elementsByTagName("playlist");
108 int tracksMax = playlists.count() - 1; // Remove the black track
109 QDomNodeList tracks = tractor.elementsByTagName("track");
110 tracksMax = qMax(tracks.count() - 1, tracksMax);
111 QDomNodeList tracksinfo = kdenliveDoc.elementsByTagName("trackinfo");
112 tracksMax = qMax(tracksinfo.count(), tracksMax);
113 tracksMax = qMax(1, tracksMax); // Force existance of one track
114 if (playlists.count() - 1 < tracksMax ||
115 tracks.count() - 1 < tracksMax ||
116 tracksinfo.count() < tracksMax) {
117 kDebug() << "//// WARNING, PROJECT IS CORRUPTED, MISSING TRACK";
120 // use the MLT tracks as reference
121 if (tracks.count() - 1 < tracksMax) {
122 // Looks like one MLT track is missing, remove the extra Kdenlive track if there is one.
123 if (tracksinfo.count() != tracks.count() - 1) {
124 // The Kdenlive tracks are not ok, clear and rebuild them
125 QDomNode tinfo = kdenliveDoc.firstChildElement("tracksinfo");
126 QDomNode tnode = tinfo.firstChild();
127 while (!tnode.isNull()) {
128 tinfo.removeChild(tnode);
129 tnode = tinfo.firstChild();
132 for (int i = 1; i < tracks.count(); i++) {
133 QString hide = tracks.at(i).toElement().attribute("hide");
134 QDomElement newTrack = m_doc.createElement("trackinfo");
135 if (hide == "video") {
137 newTrack.setAttribute("type", "audio");
138 newTrack.setAttribute("blind", 1);
139 newTrack.setAttribute("mute", 0);
140 newTrack.setAttribute("lock", 0);
142 newTrack.setAttribute("blind", 0);
143 newTrack.setAttribute("mute", 0);
144 newTrack.setAttribute("lock", 0);
146 tinfo.appendChild(newTrack);
151 if (playlists.count() - 1 < tracksMax) {
152 difference = tracksMax - (playlists.count() - 1);
153 for (int i = 0; i < difference; ++i) {
154 QDomElement playlist = m_doc.createElement("playlist");
155 mlt.appendChild(playlist);
158 if (tracks.count() - 1 < tracksMax) {
159 difference = tracksMax - (tracks.count() - 1);
160 for (int i = 0; i < difference; ++i) {
161 QDomElement track = m_doc.createElement("track");
162 tractor.appendChild(track);
165 if (tracksinfo.count() < tracksMax) {
166 QDomElement tracksinfoElm = kdenliveDoc.firstChildElement("tracksinfo");
167 if (tracksinfoElm.isNull()) {
168 tracksinfoElm = m_doc.createElement("tracksinfo");
169 kdenliveDoc.appendChild(tracksinfoElm);
171 difference = tracksMax - tracksinfo.count();
172 for (int i = 0; i < difference; ++i) {
173 QDomElement trackinfo = m_doc.createElement("trackinfo");
174 trackinfo.setAttribute("mute", "0");
175 trackinfo.setAttribute("locked", "0");
176 tracksinfoElm.appendChild(trackinfo);
181 // TODO: check the tracks references
182 // TODO: check internal mix transitions
191 bool DocumentValidator::upgrade(double version, const double currentVersion)
193 kDebug() << "Opening a document with version " << version;
195 // No conversion needed
196 if (version == currentVersion) {
200 // The document is too new
201 if (version > currentVersion) {
202 kDebug() << "Unable to open document with version " << version;
203 KMessageBox::sorry(kapp->activeWindow(), i18n("This project type is unsupported (version %1) and can't be loaded.\nPlease consider upgrading your Kdenlive version.", version), i18n("Unable to open project"));
207 // Unsupported document versions
208 if (version == 0.5 || version == 0.7) {
209 kDebug() << "Unable to open document with version " << version;
210 KMessageBox::sorry(kapp->activeWindow(), i18n("This project type is unsupported (version %1) and can't be loaded.", version), i18n("Unable to open project"));
215 QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
216 QDomElement infoXml = infoXmlNode.toElement();
217 infoXml.setAttribute("upgraded", "1");
219 if (version <= 0.6) {
220 QDomElement infoXml_old = infoXmlNode.cloneNode(true).toElement(); // Needed for folders
221 QDomNode westley = m_doc.elementsByTagName("westley").at(1);
222 QDomNode tractor = m_doc.elementsByTagName("tractor").at(0);
223 QDomNode multitrack = m_doc.elementsByTagName("multitrack").at(0);
224 QDomNodeList playlists = m_doc.elementsByTagName("playlist");
226 QDomNode props = m_doc.elementsByTagName("properties").at(0).toElement();
227 QString profile = props.toElement().attribute("videoprofile");
228 int startPos = props.toElement().attribute("timeline_position").toInt();
229 if (profile == "dv_wide")
230 profile = "dv_pal_wide";
232 // move playlists outside of tractor and add the tracks instead
233 int max = playlists.count();
234 if (westley.isNull()) {
235 westley = m_doc.createElement("westley");
236 m_doc.documentElement().appendChild(westley);
238 if (tractor.isNull()) {
239 kDebug() << "// NO MLT PLAYLIST, building empty one";
240 QDomElement blank_tractor = m_doc.createElement("tractor");
241 westley.appendChild(blank_tractor);
242 QDomElement blank_playlist = m_doc.createElement("playlist");
243 blank_playlist.setAttribute("id", "black_track");
244 westley.insertBefore(blank_playlist, QDomNode());
245 QDomElement blank_track = m_doc.createElement("track");
246 blank_track.setAttribute("producer", "black_track");
247 blank_tractor.appendChild(blank_track);
249 QDomNodeList kdenlivetracks = m_doc.elementsByTagName("kdenlivetrack");
250 for (int i = 0; i < kdenlivetracks.count(); i++) {
251 blank_playlist = m_doc.createElement("playlist");
252 blank_playlist.setAttribute("id", "playlist" + QString::number(i));
253 westley.insertBefore(blank_playlist, QDomNode());
254 blank_track = m_doc.createElement("track");
255 blank_track.setAttribute("producer", "playlist" + QString::number(i));
256 blank_tractor.appendChild(blank_track);
257 if (kdenlivetracks.at(i).toElement().attribute("cliptype") == "Sound") {
258 blank_playlist.setAttribute("hide", "video");
259 blank_track.setAttribute("hide", "video");
262 } else for (int i = 0; i < max; i++) {
263 QDomNode n = playlists.at(i);
264 westley.insertBefore(n, QDomNode());
265 QDomElement pl = n.toElement();
266 QDomElement track = m_doc.createElement("track");
267 QString trackType = pl.attribute("hide");
268 if (!trackType.isEmpty())
269 track.setAttribute("hide", trackType);
270 QString playlist_id = pl.attribute("id");
271 if (playlist_id.isEmpty()) {
272 playlist_id = "black_track";
273 pl.setAttribute("id", playlist_id);
275 track.setAttribute("producer", playlist_id);
276 //tractor.appendChild(track);
277 #define KEEP_TRACK_ORDER 1
278 #ifdef KEEP_TRACK_ORDER
279 tractor.insertAfter(track, QDomNode());
281 // Insert the new track in an order that hopefully matches the 3 video, then 2 audio tracks of Kdenlive 0.7.0
282 // insertion sort - O( tracks*tracks )
283 // Note, this breaks _all_ transitions - but you can move them up and down afterwards.
284 QDomElement tractor_elem = tractor.toElement();
285 if (! tractor_elem.isNull()) {
286 QDomNodeList tracks = tractor_elem.elementsByTagName("track");
287 int size = tracks.size();
289 tractor.insertAfter(track, QDomNode());
291 bool inserted = false;
292 for (int i = 0; i < size; ++i) {
293 QDomElement track_elem = tracks.at(i).toElement();
294 if (track_elem.isNull()) {
295 tractor.insertAfter(track, QDomNode());
299 kDebug() << "playlist_id: " << playlist_id << " producer:" << track_elem.attribute("producer");
300 if (playlist_id < track_elem.attribute("producer")) {
301 tractor.insertBefore(track, track_elem);
307 // Reach here, no insertion, insert last
309 tractor.insertAfter(track, QDomNode());
313 kWarning() << "tractor was not a QDomElement";
314 tractor.insertAfter(track, QDomNode());
318 tractor.removeChild(multitrack);
320 // audio track mixing transitions should not be added to track view, so add required attribute
321 QDomNodeList transitions = m_doc.elementsByTagName("transition");
322 max = transitions.count();
323 for (int i = 0; i < max; i++) {
324 QDomElement tr = transitions.at(i).toElement();
325 if (tr.attribute("combine") == "1" && tr.attribute("mlt_service") == "mix") {
326 QDomElement property = m_doc.createElement("property");
327 property.setAttribute("name", "internal_added");
328 QDomText value = m_doc.createTextNode("237");
329 property.appendChild(value);
330 tr.appendChild(property);
331 property = m_doc.createElement("property");
332 property.setAttribute("name", "mlt_service");
333 value = m_doc.createTextNode("mix");
334 property.appendChild(value);
335 tr.appendChild(property);
337 // convert transition
338 QDomNamedNodeMap attrs = tr.attributes();
339 for (int j = 0; j < attrs.count(); j++) {
340 QString attrName = attrs.item(j).nodeName();
341 if (attrName != "in" && attrName != "out" && attrName != "id") {
342 QDomElement property = m_doc.createElement("property");
343 property.setAttribute("name", attrName);
344 QDomText value = m_doc.createTextNode(attrs.item(j).nodeValue());
345 property.appendChild(value);
346 tr.appendChild(property);
352 // move transitions after tracks
353 for (int i = 0; i < max; i++) {
354 tractor.insertAfter(transitions.at(0), QDomNode());
357 // Fix filters format
358 QDomNodeList entries = m_doc.elementsByTagName("entry");
359 max = entries.count();
360 for (int i = 0; i < max; i++) {
363 QDomNode m = entries.at(i).firstChild();
364 while (!m.isNull()) {
365 if (m.toElement().tagName() == "filter") {
366 QDomElement filt = m.toElement();
367 QDomNamedNodeMap attrs = filt.attributes();
368 QString current_id = filt.attribute("kdenlive_id");
369 if (current_id != last_id) {
371 last_id = current_id;
373 QDomElement e = m_doc.createElement("property");
374 e.setAttribute("name", "kdenlive_ix");
375 QDomText value = m_doc.createTextNode(QString::number(effectix));
376 e.appendChild(value);
378 for (int j = 0; j < attrs.count(); j++) {
379 QDomAttr a = attrs.item(j).toAttr();
381 kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
382 QDomElement e = m_doc.createElement("property");
383 e.setAttribute("name", a.name());
384 QDomText value = m_doc.createTextNode(a.value());
385 e.appendChild(value);
396 QDomNodeList filters = m_doc.elementsByTagName("filter");
397 max = filters.count();
400 for (int i = 0; i < max; i++) {
401 QDomElement filt = filters.at(i).toElement();
402 QDomNamedNodeMap attrs = filt.attributes();
403 QString current_id = filt.attribute("kdenlive_id");
404 if (current_id != last_id) {
406 last_id = current_id;
408 QDomElement e = m_doc.createElement("property");
409 e.setAttribute("name", "kdenlive_ix");
410 QDomText value = m_doc.createTextNode(QString::number(1));
411 e.appendChild(value);
413 for (int j = 0; j < attrs.count(); j++) {
414 QDomAttr a = attrs.item(j).toAttr();
416 kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
417 QDomElement e = m_doc.createElement("property");
418 e.setAttribute("name", a.name());
419 QDomText value = m_doc.createTextNode(a.value());
420 e.appendChild(value);
427 QDomNodeList producers = westley.toElement().elementsByTagName("producer");
428 max = producers.count();
429 for (int i = 0; i < max; i++) {
430 QDomElement prod = producers.at(i).toElement();
431 if (prod.attribute("mlt_service") == "framebuffer") {
432 QString slowmotionprod = prod.attribute("resource");
433 slowmotionprod.replace(':', '?');
434 kDebug() << "// FOUND WRONG SLOWMO, new: " << slowmotionprod;
435 prod.setAttribute("resource", slowmotionprod);
438 // move producers to correct place, markers to a global list, fix clip descriptions
439 QDomElement markers = m_doc.createElement("markers");
440 // This will get the xml producers:
441 producers = m_doc.elementsByTagName("producer");
442 max = producers.count();
443 for (int i = 0; i < max; i++) {
444 QDomElement prod = producers.at(0).toElement();
445 // add resource also as a property (to allow path correction in setNewResource())
446 // TODO: will it work with slowmotion? needs testing
447 /*if (!prod.attribute("resource").isEmpty()) {
448 QDomElement prop_resource = m_doc.createElement("property");
449 prop_resource.setAttribute("name", "resource");
450 QDomText resource = m_doc.createTextNode(prod.attribute("resource"));
451 prop_resource.appendChild(resource);
452 prod.appendChild(prop_resource);
454 QDomNode m = prod.firstChild();
456 if (m.toElement().tagName() == "markers") {
457 QDomNodeList prodchilds = m.childNodes();
458 int maxchild = prodchilds.count();
459 for (int k = 0; k < maxchild; k++) {
460 QDomElement mark = prodchilds.at(0).toElement();
461 mark.setAttribute("id", prod.attribute("id"));
462 markers.insertAfter(mark, QDomNode());
465 } else if (prod.attribute("type").toInt() == TEXT) {
466 // convert title clip
467 if (m.toElement().tagName() == "textclip") {
469 QDomElement titleclip = m.toElement();
470 QDomElement title = tdoc.createElement("kdenlivetitle");
471 tdoc.appendChild(title);
472 QDomNodeList objects = titleclip.childNodes();
473 int maxchild = objects.count();
474 for (int k = 0; k < maxchild; k++) {
476 QDomElement ob = objects.at(k).toElement();
477 if (ob.attribute("type") == "3") {
478 // text object - all of this goes into "xmldata"...
479 QDomElement item = tdoc.createElement("item");
480 item.setAttribute("z-index", ob.attribute("z"));
481 item.setAttribute("type", "QGraphicsTextItem");
482 QDomElement position = tdoc.createElement("position");
483 position.setAttribute("x", ob.attribute("x"));
484 position.setAttribute("y", ob.attribute("y"));
485 QDomElement content = tdoc.createElement("content");
486 content.setAttribute("font", ob.attribute("font_family"));
487 content.setAttribute("font-size", ob.attribute("font_size"));
488 content.setAttribute("font-bold", ob.attribute("bold"));
489 content.setAttribute("font-italic", ob.attribute("italic"));
490 content.setAttribute("font-underline", ob.attribute("underline"));
491 QString col = ob.attribute("color");
493 content.setAttribute("font-color", colorToString(c));
494 // todo: These fields are missing from the newly generated xmldata:
495 // transform, startviewport, endviewport, background
497 QDomText conttxt = tdoc.createTextNode(ob.attribute("text"));
498 content.appendChild(conttxt);
499 item.appendChild(position);
500 item.appendChild(content);
501 title.appendChild(item);
502 } else if (ob.attribute("type") == "5") {
504 QDomElement item = tdoc.createElement("item");
505 item.setAttribute("z-index", ob.attribute("z"));
506 item.setAttribute("type", "QGraphicsRectItem");
507 QDomElement position = tdoc.createElement("position");
508 position.setAttribute("x", ob.attribute("x"));
509 position.setAttribute("y", ob.attribute("y"));
510 QDomElement content = tdoc.createElement("content");
511 QString col = ob.attribute("color");
513 content.setAttribute("brushcolor", colorToString(c));
514 QString rect = "0,0,";
515 rect.append(ob.attribute("width"));
517 rect.append(ob.attribute("height"));
518 content.setAttribute("rect", rect);
519 item.appendChild(position);
520 item.appendChild(content);
521 title.appendChild(item);
524 prod.setAttribute("xmldata", tdoc.toString());
525 // mbd todo: This clearly does not work, as every title gets the same name - trying to leave it empty
526 // QStringList titleInfo = TitleWidget::getFreeTitleInfo(projectFolder());
527 // prod.setAttribute("titlename", titleInfo.at(0));
528 // prod.setAttribute("resource", titleInfo.at(1));
529 //kDebug()<<"TITLE DATA:\n"<<tdoc.toString();
531 } // End conversion of title clips.
533 } else if (m.isText()) {
534 QString comment = m.nodeValue();
535 if (!comment.isEmpty()) {
536 prod.setAttribute("description", comment);
541 int duration = prod.attribute("duration").toInt();
542 if (duration > 0) prod.setAttribute("out", QString::number(duration));
543 // The clip goes back in, but text clips should not go back in, at least not modified
544 westley.insertBefore(prod, QDomNode());
547 QDomNode westley0 = m_doc.elementsByTagName("westley").at(0);
548 if (!markers.firstChild().isNull()) westley0.appendChild(markers);
551 * Convert as much of the kdenlivedoc as possible. Use the producer in
552 * westley. First, remove the old stuff from westley, and add a new
553 * empty one. Also, track the max id in order to use it for the adding
556 int max_kproducer_id = 0;
557 westley0.removeChild(infoXmlNode);
558 QDomElement infoXml_new = m_doc.createElement("kdenlivedoc");
559 infoXml_new.setAttribute("profile", profile);
560 infoXml.setAttribute("position", startPos);
562 // Add all the producers that has a resource in westley
563 QDomElement westley_element = westley0.toElement();
564 if (westley_element.isNull()) {
565 kWarning() << "westley0 element in document was not a QDomElement - unable to add producers to new kdenlivedoc";
567 QDomNodeList wproducers = westley_element.elementsByTagName("producer");
568 int kmax = wproducers.count();
569 for (int i = 0; i < kmax; i++) {
570 QDomElement wproducer = wproducers.at(i).toElement();
571 if (wproducer.isNull()) {
572 kWarning() << "Found producer in westley0, that was not a QDomElement";
575 if (wproducer.attribute("id") == "black") continue;
576 // We have to do slightly different things, depending on the type
577 kDebug() << "Converting producer element with type" << wproducer.attribute("type");
578 if (wproducer.attribute("type").toInt() == TEXT) {
579 kDebug() << "Found TEXT element in producer" << endl;
580 QDomElement kproducer = wproducer.cloneNode(true).toElement();
581 kproducer.setTagName("kdenlive_producer");
582 infoXml_new.appendChild(kproducer);
584 * TODO: Perhaps needs some more changes here to
585 * "frequency", aspect ratio as a float, frame_size,
586 * channels, and later, resource and title name
589 QDomElement kproducer = m_doc.createElement("kdenlive_producer");
590 kproducer.setAttribute("id", wproducer.attribute("id"));
591 if (!wproducer.attribute("description").isEmpty())
592 kproducer.setAttribute("description", wproducer.attribute("description"));
593 kproducer.setAttribute("resource", wproducer.attribute("resource"));
594 kproducer.setAttribute("type", wproducer.attribute("type"));
595 // Testing fix for 358
596 if (!wproducer.attribute("aspect_ratio").isEmpty()) {
597 kproducer.setAttribute("aspect_ratio", wproducer.attribute("aspect_ratio"));
599 if (!wproducer.attribute("source_fps").isEmpty()) {
600 kproducer.setAttribute("fps", wproducer.attribute("source_fps"));
602 if (!wproducer.attribute("length").isEmpty()) {
603 kproducer.setAttribute("duration", wproducer.attribute("length"));
605 infoXml_new.appendChild(kproducer);
607 if (wproducer.attribute("id").toInt() > max_kproducer_id) {
608 max_kproducer_id = wproducer.attribute("id").toInt();
612 #define LOOKUP_FOLDER 1
615 * Look through all the folder elements of the old doc, for each folder,
616 * for each producer, get the id, look it up in the new doc, set the
617 * groupname and groupid. Note, this does not work at the moment - at
618 * least one folder shows up missing, and clips with no folder does not
621 //QDomElement infoXml_old = infoXmlNode.toElement();
622 if (!infoXml_old.isNull()) {
623 QDomNodeList folders = infoXml_old.elementsByTagName("folder");
624 int fsize = folders.size();
625 int groupId = max_kproducer_id + 1; // Start at +1 of max id of the kdenlive_producers
626 for (int i = 0; i < fsize; ++i) {
627 QDomElement folder = folders.at(i).toElement();
628 if (!folder.isNull()) {
629 QString groupName = folder.attribute("name");
630 kDebug() << "groupName: " << groupName << " with groupId: " << groupId;
631 QDomNodeList fproducers = folder.elementsByTagName("producer");
632 int psize = fproducers.size();
633 for (int j = 0; j < psize; ++j) {
634 QDomElement fproducer = fproducers.at(j).toElement();
635 if (!fproducer.isNull()) {
636 QString id = fproducer.attribute("id");
637 // This is not very effective, but compared to loading the clips, its a breeze
638 QDomNodeList kdenlive_producers = infoXml_new.elementsByTagName("kdenlive_producer");
639 int kpsize = kdenlive_producers.size();
640 for (int k = 0; k < kpsize; ++k) {
641 QDomElement kproducer = kdenlive_producers.at(k).toElement(); // Its an element for sure
642 if (id == kproducer.attribute("id")) {
643 // We do not check that it already is part of a folder
644 kproducer.setAttribute("groupid", groupId);
645 kproducer.setAttribute("groupname", groupName);
656 QDomNodeList elements = westley.childNodes();
657 max = elements.count();
658 for (int i = 0; i < max; i++) {
659 QDomElement prod = elements.at(0).toElement();
660 westley0.insertAfter(prod, QDomNode());
663 westley0.appendChild(infoXml_new);
665 westley0.removeChild(westley);
667 // adds <avfile /> information to <kdenlive_producer />
668 QDomNodeList kproducers = m_doc.elementsByTagName("kdenlive_producer");
669 QDomNodeList avfiles = infoXml_old.elementsByTagName("avfile");
670 kDebug() << "found" << avfiles.count() << "<avfile />s and" << kproducers.count() << "<kdenlive_producer />s";
671 for (int i = 0; i < avfiles.count(); ++i) {
672 QDomElement avfile = avfiles.at(i).toElement();
673 QDomElement kproducer;
675 kWarning() << "found an <avfile /> that is not a QDomElement";
677 QString id = avfile.attribute("id");
678 // this is horrible, must be rewritten, it's just for test
679 for (int j = 0; j < kproducers.count(); ++j) {
680 //kDebug() << "checking <kdenlive_producer /> with id" << kproducers.at(j).toElement().attribute("id");
681 if (kproducers.at(j).toElement().attribute("id") == id) {
682 kproducer = kproducers.at(j).toElement();
686 if (kproducer == QDomElement())
687 kWarning() << "no match for <avfile /> with id =" << id;
689 //kDebug() << "ready to set additional <avfile />'s attributes (id =" << id << ")";
690 kproducer.setAttribute("channels", avfile.attribute("channels"));
691 kproducer.setAttribute("duration", avfile.attribute("duration"));
692 kproducer.setAttribute("frame_size", avfile.attribute("width") + 'x' + avfile.attribute("height"));
693 kproducer.setAttribute("frequency", avfile.attribute("frequency"));
694 if (kproducer.attribute("description").isEmpty() && !avfile.attribute("description").isEmpty())
695 kproducer.setAttribute("description", avfile.attribute("description"));
699 infoXml = infoXml_new;
702 if (version <= 0.81) {
703 // Add the tracks information
704 QString tracksOrder = infoXml.attribute("tracks");
705 if (tracksOrder.isEmpty()) {
706 QDomNodeList tracks = m_doc.elementsByTagName("track");
707 for (int i = 0; i < tracks.count(); i++) {
708 QDomElement track = tracks.at(i).toElement();
709 if (track.attribute("producer") != "black_track") {
710 if (track.attribute("hide") == "video")
711 tracksOrder.append('a');
713 tracksOrder.append('v');
717 QDomElement tracksinfo = m_doc.createElement("tracksinfo");
718 for (int i = 0; i < tracksOrder.size(); i++) {
719 QDomElement trackinfo = m_doc.createElement("trackinfo");
720 if (tracksOrder.data()[i] == 'a') {
721 trackinfo.setAttribute("type", "audio");
722 trackinfo.setAttribute("blind", true);
724 trackinfo.setAttribute("blind", false);
725 trackinfo.setAttribute("mute", false);
726 trackinfo.setAttribute("locked", false);
727 tracksinfo.appendChild(trackinfo);
729 infoXml.appendChild(tracksinfo);
732 if (version <= 0.82) {
733 // Convert <westley />s in <mlt />s (MLT extreme makeover)
734 QDomNodeList westleyNodes = m_doc.elementsByTagName("westley");
735 for (int i = 0; i < westleyNodes.count(); i++) {
736 QDomElement westley = westleyNodes.at(i).toElement();
737 westley.setTagName("mlt");
741 if (version <= 0.83) {
742 // Replace point size with pixel size in text titles
743 if (m_doc.toString().contains("font-size")) {
744 KMessageBox::ButtonCode convert = KMessageBox::Continue;
745 QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
746 for (int i = 0; i < kproducerNodes.count() && convert != KMessageBox::No; ++i) {
747 QDomElement kproducer = kproducerNodes.at(i).toElement();
748 if (kproducer.attribute("type").toInt() == TEXT) {
750 data.setContent(kproducer.attribute("xmldata"));
751 QDomNodeList items = data.firstChild().childNodes();
752 for (int j = 0; j < items.count() && convert != KMessageBox::No; ++j) {
753 if (items.at(j).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
754 QDomNamedNodeMap textProperties = items.at(j).namedItem("content").attributes();
755 if (textProperties.namedItem("font-pixel-size").isNull() && !textProperties.namedItem("font-size").isNull()) {
756 // Ask the user if he wants to convert
757 if (convert != KMessageBox::Yes && convert != KMessageBox::No)
758 convert = (KMessageBox::ButtonCode)KMessageBox::warningYesNo(kapp->activeWindow(), i18n("Some of your text clips were saved with size in points, which means different sizes on different displays. Do you want to convert them to pixel size, making them portable? It is recommended you do this on the computer they were first created on, or you could have to adjust their size."), i18n("Update Text Clips"));
759 if (convert == KMessageBox::Yes) {
761 font.setPointSize(textProperties.namedItem("font-size").nodeValue().toInt());
762 QDomElement content = items.at(j).namedItem("content").toElement();
763 content.setAttribute("font-pixel-size", QFontInfo(font).pixelSize());
764 content.removeAttribute("font-size");
765 kproducer.setAttribute("xmldata", data.toString());
767 * You may be tempted to delete the preview file
768 * to force its recreation: bad idea (see
769 * http://www.kdenlive.org/mantis/view.php?id=749)
779 // Fill the <documentproperties /> element
780 QDomElement docProperties = infoXml.firstChildElement("documentproperties");
781 if (docProperties.isNull()) {
782 docProperties = m_doc.createElement("documentproperties");
783 docProperties.setAttribute("zonein", infoXml.attribute("zonein"));
784 docProperties.setAttribute("zoneout", infoXml.attribute("zoneout"));
785 docProperties.setAttribute("zoom", infoXml.attribute("zoom"));
786 docProperties.setAttribute("position", infoXml.attribute("position"));
787 infoXml.appendChild(docProperties);
791 if (version <= 0.84) {
792 // update the title clips to use the new MLT kdenlivetitle producer
793 QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
794 for (int i = 0; i < kproducerNodes.count(); ++i) {
795 QDomElement kproducer = kproducerNodes.at(i).toElement();
796 if (kproducer.attribute("type").toInt() == TEXT) {
797 QString data = kproducer.attribute("xmldata");
798 QString datafile = kproducer.attribute("resource");
799 if (!datafile.endsWith(".kdenlivetitle")) {
800 datafile = QString();
801 kproducer.setAttribute("resource", QString());
803 QString id = kproducer.attribute("id");
804 QDomNodeList mltproducers = m_doc.elementsByTagName("producer");
805 bool foundData = false;
806 bool foundResource = false;
807 bool foundService = false;
808 for (int j = 0; j < mltproducers.count(); j++) {
809 QDomElement wproducer = mltproducers.at(j).toElement();
810 if (wproducer.attribute("id") == id) {
811 QDomNodeList props = wproducer.childNodes();
812 for (int k = 0; k < props.count(); k++) {
813 if (props.at(k).toElement().attribute("name") == "xmldata") {
814 props.at(k).firstChild().setNodeValue(data);
816 } else if (props.at(k).toElement().attribute("name") == "mlt_service") {
817 props.at(k).firstChild().setNodeValue("kdenlivetitle");
819 } else if (props.at(k).toElement().attribute("name") == "resource") {
820 props.at(k).firstChild().setNodeValue(datafile);
821 foundResource = true;
825 QDomElement e = m_doc.createElement("property");
826 e.setAttribute("name", "xmldata");
827 QDomText value = m_doc.createTextNode(data);
828 e.appendChild(value);
829 wproducer.appendChild(e);
832 QDomElement e = m_doc.createElement("property");
833 e.setAttribute("name", "mlt_service");
834 QDomText value = m_doc.createTextNode("kdenlivetitle");
835 e.appendChild(value);
836 wproducer.appendChild(e);
838 if (!foundResource) {
839 QDomElement e = m_doc.createElement("property");
840 e.setAttribute("name", "resource");
841 QDomText value = m_doc.createTextNode(datafile);
842 e.appendChild(value);
843 wproducer.appendChild(e);
851 if (version <= 0.85) {
852 // update the LADSPA effects to use the new ladspa.id format instead of external xml file
853 QDomNodeList effectNodes = m_doc.elementsByTagName("filter");
854 for (int i = 0; i < effectNodes.count(); ++i) {
855 QDomElement effect = effectNodes.at(i).toElement();
856 if (EffectsList::property(effect, "mlt_service") == "ladspa") {
857 // Needs to be converted
858 QStringList info = getInfoFromEffectName(EffectsList::property(effect, "kdenlive_id"));
859 if (info.isEmpty()) continue;
860 // info contains the correct ladspa.id from kdenlive effect name, and a list of parameter's old and new names
861 EffectsList::setProperty(effect, "kdenlive_id", info.at(0));
862 EffectsList::setProperty(effect, "tag", info.at(0));
863 EffectsList::setProperty(effect, "mlt_service", info.at(0));
864 EffectsList::removeProperty(effect, "src");
865 for (int j = 1; j < info.size(); j++) {
866 QString value = EffectsList::property(effect, info.at(j).section('=', 0, 0));
867 if (!value.isEmpty()) {
868 // update parameter name
869 EffectsList::renameProperty(effect, info.at(j).section('=', 0, 0), info.at(j).section('=', 1, 1));
876 if (version <= 0.86) {
877 // Make sure we don't have avformat-novalidate producers, since it caused crashes
878 QDomNodeList producers = m_doc.elementsByTagName("producer");
879 int max = producers.count();
880 for (int i = 0; i < max; i++) {
881 QDomElement prod = producers.at(i).toElement();
882 if (EffectsList::property(prod, "mlt_service") == "avformat-novalidate")
883 EffectsList::setProperty(prod, "mlt_service", "avformat");
886 // 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
888 // Get profile info (width / height)
891 QDomElement profile = m_doc.firstChildElement("profile");
892 if (profile.isNull()) profile = infoXml.firstChildElement("profileinfo");
893 if (profile.isNull()) {
894 // could not find profile info, set PAL
899 profileWidth = profile.attribute("width").toInt();
900 profileHeight = profile.attribute("height").toInt();
902 QDomNodeList transitions = m_doc.elementsByTagName("transition");
903 max = transitions.count();
905 for (int i = 0; i < max; i++) {
906 QDomElement trans = transitions.at(i).toElement();
907 out = trans.attribute("out").toInt() - trans.attribute("in").toInt();
908 QString geom = EffectsList::property(trans, "geometry");
909 Mlt::Geometry *g = new Mlt::Geometry(geom.toUtf8().data(), out, profileWidth, profileHeight);
910 Mlt::GeometryItem item;
911 if (g->next_key(&item, out) == 0) {
912 // We have a keyframe just after last frame, try to move it to last frame
913 if (item.frame() == out + 1) {
917 EffectsList::setProperty(trans, "geometry", g->serialise());
924 if (version <= 0.87) {
925 if (!m_doc.firstChildElement("mlt").hasAttribute("LC_NUMERIC")) {
926 m_doc.firstChildElement("mlt").setAttribute("LC_NUMERIC", "C");
930 // The document has been converted: mark it as modified
931 infoXml.setAttribute("version", currentVersion);
936 QStringList DocumentValidator::getInfoFromEffectName(const QString oldName)
939 // Returns a list to convert old Kdenlive ladspa effects
940 if (oldName == "pitch_shift") {
941 info << "ladspa.1433";
944 else if (oldName == "vinyl") {
945 info << "ladspa.1905";
952 else if (oldName == "room_reverb") {
953 info << "ladspa.1216";
958 else if (oldName == "reverb") {
959 info << "ladspa.1423";
963 else if (oldName == "rate_scale") {
964 info << "ladspa.1417";
967 else if (oldName == "pitch_scale") {
968 info << "ladspa.1193";
971 else if (oldName == "phaser") {
972 info << "ladspa.1217";
975 info << "feedback=2";
978 else if (oldName == "limiter") {
979 info << "ladspa.1913";
984 else if (oldName == "equalizer_15") {
985 info << "ladspa.1197";
1002 else if (oldName == "equalizer") {
1003 info << "ladspa.1901";
1005 info << "midgain=1";
1008 else if (oldName == "declipper") {
1009 info << "ladspa.1195";
1014 QString DocumentValidator::colorToString(const QColor& c)
1016 QString ret = "%1,%2,%3,%4";
1017 ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
1021 bool DocumentValidator::isProject() const
1023 QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
1024 return !infoXmlNode.isNull();
1027 bool DocumentValidator::isModified() const
1032 void DocumentValidator::updateEffects()
1034 // WARNING: order by findDirs will determine which js file to use (in case multiple for the same filter exist)
1035 QMap <QString, KUrl> paths;
1036 #if QT_VERSION >= 0x040700
1037 QMap <QString, QScriptProgram> scripts;
1039 QMap <QString, QString> scripts;
1041 QStringList directories = KGlobal::dirs()->findDirs("appdata", "effects/update");
1042 foreach (const QString &directoryName, directories) {
1043 QDir directory(directoryName);
1044 QStringList fileList = directory.entryList(QStringList() << "*.js", QDir::Files);
1045 foreach (const QString &fileName, fileList) {
1046 QString identifier = fileName;
1048 identifier.replace('_', '.');
1049 paths.insert(identifier, KUrl(directoryName + fileName));
1053 QDomNodeList effects = m_doc.elementsByTagName("filter");
1055 for(int i = 0; i < effects.count(); ++i) {
1056 QDomElement effect = effects.at(i).toElement();
1057 QString effectId = EffectsList::property(effect, "kdenlive_id");
1058 QString effectTag = EffectsList::property(effect, "tag");
1059 QString effectVersionStr = EffectsList::property(effect, "version");
1060 double effectVersion = effectVersionStr.isNull() ? -1 : effectVersionStr.toDouble();
1062 QDomElement effectDescr = MainWindow::customEffects.getEffectByTag(QString(), effectId);
1063 if (effectDescr.isNull()) {
1064 effectDescr = MainWindow::videoEffects.getEffectByTag(effectTag, effectId);
1066 if (effectDescr.isNull()) {
1067 effectDescr = MainWindow::audioEffects.getEffectByTag(effectTag, effectId);
1069 if (!effectDescr.isNull()) {
1070 double serviceVersion = -1;
1071 QDomElement serviceVersionElem = effectDescr.firstChildElement("version");
1072 if (!serviceVersionElem.isNull()) {
1073 serviceVersion = serviceVersionElem.text().toDouble();
1075 if (serviceVersion != effectVersion && paths.contains(effectId)) {
1076 if (!scripts.contains(effectId)) {
1077 QFile scriptFile(paths.value(effectId).path());
1078 if (!scriptFile.open(QIODevice::ReadOnly)) {
1081 #if QT_VERSION >= 0x040700
1082 QScriptProgram scriptProgram(scriptFile.readAll());
1084 QString scriptProgram = scriptFile.readAll();
1087 scripts.insert(effectId, scriptProgram);
1090 QDomDocument scriptDoc;
1091 scriptDoc.appendChild(scriptDoc.importNode(effect, true));
1093 QScriptEngine scriptEngine;
1094 scriptEngine.importExtension("qt.core");
1095 scriptEngine.importExtension("qt.xml");
1096 scriptEngine.evaluate(scripts.value(effectId));
1097 QString effectString = scriptEngine.globalObject().property("update").call(QScriptValue(), QScriptValueList() << serviceVersion << effectVersion << scriptDoc.toString()).toString();
1099 if (!effectString.isEmpty()) {
1100 scriptDoc.setContent(effectString);
1101 QDomNode updatedEffect = effect.ownerDocument().importNode(scriptDoc.documentElement(), true);
1102 effect.parentNode().replaceChild(updatedEffect, effect);
1103 // TODO: set version to avoid dependency on latest MLT