]> git.sesse.net Git - kdenlive/blob - src/documentvalidator.cpp
Add option to automatically import all clips in multi stream clips
[kdenlive] / src / documentvalidator.cpp
1 /***************************************************************************
2  *   Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@kdenlive.org)        *
3  *                                                                         *
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.                                   *
8  *                                                                         *
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.                          *
13  *                                                                         *
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  ***************************************************************************/
19
20
21 #include "documentvalidator.h"
22 #include "definitions.h"
23 #include "initeffects.h"
24 #include "mainwindow.h"
25
26 #include <KDebug>
27 #include <KMessageBox>
28 #include <KApplication>
29 #include <KLocale>
30 #include <KStandardDirs>
31
32 #include <QFile>
33 #include <QColor>
34 #include <QString>
35 #include <QDir>
36 #include <QScriptEngine>
37
38 #include <mlt++/Mlt.h>
39
40 #include <locale>
41
42
43 DocumentValidator::DocumentValidator(QDomDocument doc, KUrl documentUrl):
44         m_doc(doc),
45         m_url(documentUrl),
46         m_modified(false)
47 {}
48
49 bool DocumentValidator::validate(const double currentVersion)
50 {
51     QDomElement mlt = m_doc.firstChildElement("mlt");
52     // At least the root element must be there
53     if (mlt.isNull())
54         return false;
55
56     QDomElement kdenliveDoc = mlt.firstChildElement("kdenlivedoc");
57     // Check if we're validating a Kdenlive project
58     if (kdenliveDoc.isNull())
59         return false;
60     
61     QString rootDir = mlt.attribute("root");
62     if (rootDir == "$CURRENTPATH") {
63         // The document was extracted from a Kdenlive archived project, fix root directory
64         QString playlist = m_doc.toString();
65         playlist.replace("$CURRENTPATH", m_url.directory(KUrl::IgnoreTrailingSlash));
66         m_doc.setContent(playlist);
67         mlt = m_doc.firstChildElement("mlt");
68         kdenliveDoc = mlt.firstChildElement("kdenlivedoc");
69     }
70
71     // Previous MLT / Kdenlive versions used C locale by default
72     QLocale documentLocale = QLocale::c();
73     
74     if (mlt.hasAttribute("LC_NUMERIC")) {
75         // Set locale for the document        
76         const QString newLocale = setlocale(LC_NUMERIC, mlt.attribute("LC_NUMERIC").toUtf8().constData());
77         documentLocale = QLocale(mlt.attribute("LC_NUMERIC"));
78
79         // Make sure Qt locale and C++ locale have the same numeric separator, might not be the case
80         // With some locales since C++ and Qt use a different database for locales
81         char *separator = localeconv()->decimal_point;
82         if (newLocale.isEmpty()) {
83             // Requested locale not available, ask for install
84             KMessageBox::sorry(kapp->activeWindow(), i18n("The document was created in \"%1\" locale, which is not installed on your system. Please install that language pack. Until then, Kdenlive might not be able to correctly open the document.", mlt.attribute("LC_NUMERIC")));
85         }
86         
87         if (separator != documentLocale.decimalPoint()) {
88             KMessageBox::sorry(kapp->activeWindow(), i18n("There is a locale conflict on your system. The document uses locale %1 which uses a \"%2\" as numeric separator (in system libraries) but Qt expects \"%3\". You might not be able to correctly open the project.", mlt.attribute("LC_NUMERIC"), separator, documentLocale.decimalPoint()));
89             kDebug()<<"------\n!!! system locale is not similar to Qt's locale... be prepared for bugs!!!\n------";
90             // HACK: There is a locale conflict, so set locale to at least have correct decimal point
91             if (strncmp(separator, ".", 1) == 0) documentLocale = QLocale::c();
92             else if (strncmp(separator, ",", 1) == 0) documentLocale = QLocale("fr_FR.UTF-8");
93         }
94     }
95     
96     documentLocale.setNumberOptions(QLocale::OmitGroupSeparator);
97     if (documentLocale.decimalPoint() != QLocale().decimalPoint()) {
98         // If loading an older MLT file without LC_NUMERIC, set locale to C which was previously the default
99         if (!mlt.hasAttribute("LC_NUMERIC")) {
100             setlocale(LC_NUMERIC, "C");
101         }
102
103         QLocale::setDefault(documentLocale);
104         // locale conversion might need to be redone
105         initEffects::parseEffectFiles(setlocale(LC_NUMERIC, NULL));
106     }
107
108     bool ok;
109     double version = documentLocale.toDouble(kdenliveDoc.attribute("version"), &ok);
110     if (!ok) {
111         // Could not parse version number, there is probably a conflict in decimal separator
112         QLocale tempLocale = QLocale(mlt.attribute("LC_NUMERIC"));
113         version = tempLocale.toDouble(kdenliveDoc.attribute("version"), &ok);
114         if (!ok) version = kdenliveDoc.attribute("version").toDouble(&ok);
115         if (!ok) kDebug()<<"// CANNOT PARSE VERSION NUMBER, ERROR!";
116     }
117     
118     // Upgrade the document to the latest version
119     if (!upgrade(version, currentVersion))
120         return false;
121
122     /*
123      * Check the syntax (this will be replaced by XSD validation with Qt 4.6)
124      * and correct some errors
125      */
126     {
127         // Return (or create) the tractor
128         QDomElement tractor = mlt.firstChildElement("tractor");
129         if (tractor.isNull()) {
130             m_modified = true;
131             tractor = m_doc.createElement("tractor");
132             tractor.setAttribute("global_feed", "1");
133             tractor.setAttribute("in", "0");
134             tractor.setAttribute("out", "-1");
135             tractor.setAttribute("id", "maintractor");
136             mlt.appendChild(tractor);
137         }
138
139         /*
140          * Make sure at least one track exists, and they're equal in number to
141          * to the maximum between MLT and Kdenlive playlists and tracks
142          */
143         QDomNodeList playlists = m_doc.elementsByTagName("playlist");
144         int tracksMax = playlists.count() - 1; // Remove the black track
145         QDomNodeList tracks = tractor.elementsByTagName("track");
146         tracksMax = qMax(tracks.count() - 1, tracksMax);
147         QDomNodeList tracksinfo = kdenliveDoc.elementsByTagName("trackinfo");
148         tracksMax = qMax(tracksinfo.count(), tracksMax);
149         tracksMax = qMax(1, tracksMax); // Force existance of one track
150         if (playlists.count() - 1 < tracksMax ||
151                 tracks.count() - 1 < tracksMax ||
152                 tracksinfo.count() < tracksMax) {
153             kDebug() << "//// WARNING, PROJECT IS CORRUPTED, MISSING TRACK";
154             m_modified = true;
155             int difference;
156             // use the MLT tracks as reference
157             if (tracks.count() - 1 < tracksMax) {
158                 // Looks like one MLT track is missing, remove the extra Kdenlive track if there is one.
159                 if (tracksinfo.count() != tracks.count() - 1) {
160                     // The Kdenlive tracks are not ok, clear and rebuild them
161                     QDomNode tinfo = kdenliveDoc.firstChildElement("tracksinfo");
162                     QDomNode tnode = tinfo.firstChild();
163                     while (!tnode.isNull()) {
164                         tinfo.removeChild(tnode);
165                         tnode = tinfo.firstChild();
166                     }
167
168                     for (int i = 1; i < tracks.count(); i++) {
169                         QString hide = tracks.at(i).toElement().attribute("hide");
170                         QDomElement newTrack = m_doc.createElement("trackinfo");
171                         if (hide == "video") {
172                             // audio track;
173                             newTrack.setAttribute("type", "audio");
174                             newTrack.setAttribute("blind", 1);
175                             newTrack.setAttribute("mute", 0);
176                             newTrack.setAttribute("lock", 0);
177                         } else {
178                             newTrack.setAttribute("blind", 0);
179                             newTrack.setAttribute("mute", 0);
180                             newTrack.setAttribute("lock", 0);
181                         }
182                         tinfo.appendChild(newTrack);
183                     }
184                 }
185             }
186
187             if (playlists.count() - 1 < tracksMax) {
188                 difference = tracksMax - (playlists.count() - 1);
189                 for (int i = 0; i < difference; ++i) {
190                     QDomElement playlist = m_doc.createElement("playlist");
191                     mlt.appendChild(playlist);
192                 }
193             }
194             if (tracks.count() - 1 < tracksMax) {
195                 difference = tracksMax - (tracks.count() - 1);
196                 for (int i = 0; i < difference; ++i) {
197                     QDomElement track = m_doc.createElement("track");
198                     tractor.appendChild(track);
199                 }
200             }
201             if (tracksinfo.count() < tracksMax) {
202                 QDomElement tracksinfoElm = kdenliveDoc.firstChildElement("tracksinfo");
203                 if (tracksinfoElm.isNull()) {
204                     tracksinfoElm = m_doc.createElement("tracksinfo");
205                     kdenliveDoc.appendChild(tracksinfoElm);
206                 }
207                 difference = tracksMax - tracksinfo.count();
208                 for (int i = 0; i < difference; ++i) {
209                     QDomElement trackinfo = m_doc.createElement("trackinfo");
210                     trackinfo.setAttribute("mute", "0");
211                     trackinfo.setAttribute("locked", "0");
212                     tracksinfoElm.appendChild(trackinfo);
213                 }
214             }
215         }        
216
217         // TODO: check the tracks references
218         // TODO: check internal mix transitions
219         
220     }
221
222     updateEffects();
223
224     return true;
225 }
226
227 bool DocumentValidator::upgrade(double version, const double currentVersion)
228 {
229     kDebug() << "Opening a document with version " << version;
230
231     // No conversion needed
232     if (version == currentVersion) {
233         return true;
234     }
235
236     // The document is too new
237     if (version > currentVersion) {
238         kDebug() << "Unable to open document with version " << version;
239         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"));
240         return false;
241     }
242
243     // Unsupported document versions
244     if (version == 0.5 || version == 0.7) {
245         kDebug() << "Unable to open document with version " << version;
246         KMessageBox::sorry(kapp->activeWindow(), i18n("This project type is unsupported (version %1) and can't be loaded.", version), i18n("Unable to open project"));
247         return false;
248     }
249
250     // <kdenlivedoc />
251     QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
252     QDomElement infoXml = infoXmlNode.toElement();
253     infoXml.setAttribute("upgraded", "1");
254
255     if (version <= 0.6) {
256         QDomElement infoXml_old = infoXmlNode.cloneNode(true).toElement(); // Needed for folders
257         QDomNode westley = m_doc.elementsByTagName("westley").at(1);
258         QDomNode tractor = m_doc.elementsByTagName("tractor").at(0);
259         QDomNode multitrack = m_doc.elementsByTagName("multitrack").at(0);
260         QDomNodeList playlists = m_doc.elementsByTagName("playlist");
261
262         QDomNode props = m_doc.elementsByTagName("properties").at(0).toElement();
263         QString profile = props.toElement().attribute("videoprofile");
264         int startPos = props.toElement().attribute("timeline_position").toInt();
265         if (profile == "dv_wide")
266             profile = "dv_pal_wide";
267
268         // move playlists outside of tractor and add the tracks instead
269         int max = playlists.count();
270         if (westley.isNull()) {
271             westley = m_doc.createElement("westley");
272             m_doc.documentElement().appendChild(westley);
273         }
274         if (tractor.isNull()) {
275             kDebug() << "// NO MLT PLAYLIST, building empty one";
276             QDomElement blank_tractor = m_doc.createElement("tractor");
277             westley.appendChild(blank_tractor);
278             QDomElement blank_playlist = m_doc.createElement("playlist");
279             blank_playlist.setAttribute("id", "black_track");
280             westley.insertBefore(blank_playlist, QDomNode());
281             QDomElement blank_track = m_doc.createElement("track");
282             blank_track.setAttribute("producer", "black_track");
283             blank_tractor.appendChild(blank_track);
284
285             QDomNodeList kdenlivetracks = m_doc.elementsByTagName("kdenlivetrack");
286             for (int i = 0; i < kdenlivetracks.count(); i++) {
287                 blank_playlist = m_doc.createElement("playlist");
288                 blank_playlist.setAttribute("id", "playlist" + QString::number(i));
289                 westley.insertBefore(blank_playlist, QDomNode());
290                 blank_track = m_doc.createElement("track");
291                 blank_track.setAttribute("producer", "playlist" + QString::number(i));
292                 blank_tractor.appendChild(blank_track);
293                 if (kdenlivetracks.at(i).toElement().attribute("cliptype") == "Sound") {
294                     blank_playlist.setAttribute("hide", "video");
295                     blank_track.setAttribute("hide", "video");
296                 }
297             }
298         } else for (int i = 0; i < max; i++) {
299                 QDomNode n = playlists.at(i);
300                 westley.insertBefore(n, QDomNode());
301                 QDomElement pl = n.toElement();
302                 QDomElement track = m_doc.createElement("track");
303                 QString trackType = pl.attribute("hide");
304                 if (!trackType.isEmpty())
305                     track.setAttribute("hide", trackType);
306                 QString playlist_id =  pl.attribute("id");
307                 if (playlist_id.isEmpty()) {
308                     playlist_id = "black_track";
309                     pl.setAttribute("id", playlist_id);
310                 }
311                 track.setAttribute("producer", playlist_id);
312                 //tractor.appendChild(track);
313 #define KEEP_TRACK_ORDER 1
314 #ifdef KEEP_TRACK_ORDER
315                 tractor.insertAfter(track, QDomNode());
316 #else
317                 // Insert the new track in an order that hopefully matches the 3 video, then 2 audio tracks of Kdenlive 0.7.0
318                 // insertion sort - O( tracks*tracks )
319                 // Note, this breaks _all_ transitions - but you can move them up and down afterwards.
320                 QDomElement tractor_elem = tractor.toElement();
321                 if (! tractor_elem.isNull()) {
322                     QDomNodeList tracks = tractor_elem.elementsByTagName("track");
323                     int size = tracks.size();
324                     if (size == 0) {
325                         tractor.insertAfter(track, QDomNode());
326                     } else {
327                         bool inserted = false;
328                         for (int i = 0; i < size; ++i) {
329                             QDomElement track_elem = tracks.at(i).toElement();
330                             if (track_elem.isNull()) {
331                                 tractor.insertAfter(track, QDomNode());
332                                 inserted = true;
333                                 break;
334                             } else {
335                                 kDebug() << "playlist_id: " << playlist_id << " producer:" << track_elem.attribute("producer");
336                                 if (playlist_id < track_elem.attribute("producer")) {
337                                     tractor.insertBefore(track, track_elem);
338                                     inserted = true;
339                                     break;
340                                 }
341                             }
342                         }
343                         // Reach here, no insertion, insert last
344                         if (!inserted) {
345                             tractor.insertAfter(track, QDomNode());
346                         }
347                     }
348                 } else {
349                     kWarning() << "tractor was not a QDomElement";
350                     tractor.insertAfter(track, QDomNode());
351                 }
352 #endif
353             }
354         tractor.removeChild(multitrack);
355
356         // audio track mixing transitions should not be added to track view, so add required attribute
357         QDomNodeList transitions = m_doc.elementsByTagName("transition");
358         max = transitions.count();
359         for (int i = 0; i < max; i++) {
360             QDomElement tr = transitions.at(i).toElement();
361             if (tr.attribute("combine") == "1" && tr.attribute("mlt_service") == "mix") {
362                 QDomElement property = m_doc.createElement("property");
363                 property.setAttribute("name", "internal_added");
364                 QDomText value = m_doc.createTextNode("237");
365                 property.appendChild(value);
366                 tr.appendChild(property);
367                 property = m_doc.createElement("property");
368                 property.setAttribute("name", "mlt_service");
369                 value = m_doc.createTextNode("mix");
370                 property.appendChild(value);
371                 tr.appendChild(property);
372             } else {
373                 // convert transition
374                 QDomNamedNodeMap attrs = tr.attributes();
375                 for (int j = 0; j < attrs.count(); j++) {
376                     QString attrName = attrs.item(j).nodeName();
377                     if (attrName != "in" && attrName != "out" && attrName != "id") {
378                         QDomElement property = m_doc.createElement("property");
379                         property.setAttribute("name", attrName);
380                         QDomText value = m_doc.createTextNode(attrs.item(j).nodeValue());
381                         property.appendChild(value);
382                         tr.appendChild(property);
383                     }
384                 }
385             }
386         }
387
388         // move transitions after tracks
389         for (int i = 0; i < max; i++) {
390             tractor.insertAfter(transitions.at(0), QDomNode());
391         }
392
393         // Fix filters format
394         QDomNodeList entries = m_doc.elementsByTagName("entry");
395         max = entries.count();
396         for (int i = 0; i < max; i++) {
397             QString last_id;
398             int effectix = 0;
399             QDomNode m = entries.at(i).firstChild();
400             while (!m.isNull()) {
401                 if (m.toElement().tagName() == "filter") {
402                     QDomElement filt = m.toElement();
403                     QDomNamedNodeMap attrs = filt.attributes();
404                     QString current_id = filt.attribute("kdenlive_id");
405                     if (current_id != last_id) {
406                         effectix++;
407                         last_id = current_id;
408                     }
409                     QDomElement e = m_doc.createElement("property");
410                     e.setAttribute("name", "kdenlive_ix");
411                     QDomText value = m_doc.createTextNode(QString::number(effectix));
412                     e.appendChild(value);
413                     filt.appendChild(e);
414                     for (int j = 0; j < attrs.count(); j++) {
415                         QDomAttr a = attrs.item(j).toAttr();
416                         if (!a.isNull()) {
417                             kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
418                             QDomElement e = m_doc.createElement("property");
419                             e.setAttribute("name", a.name());
420                             QDomText value = m_doc.createTextNode(a.value());
421                             e.appendChild(value);
422                             filt.appendChild(e);
423
424                         }
425                     }
426                 }
427                 m = m.nextSibling();
428             }
429         }
430
431         /*
432             QDomNodeList filters = m_doc.elementsByTagName("filter");
433             max = filters.count();
434             QString last_id;
435             int effectix = 0;
436             for (int i = 0; i < max; i++) {
437                 QDomElement filt = filters.at(i).toElement();
438                 QDomNamedNodeMap attrs = filt.attributes();
439         QString current_id = filt.attribute("kdenlive_id");
440         if (current_id != last_id) {
441             effectix++;
442             last_id = current_id;
443         }
444         QDomElement e = m_doc.createElement("property");
445                 e.setAttribute("name", "kdenlive_ix");
446                 QDomText value = m_doc.createTextNode(QString::number(1));
447                 e.appendChild(value);
448                 filt.appendChild(e);
449                 for (int j = 0; j < attrs.count(); j++) {
450                     QDomAttr a = attrs.item(j).toAttr();
451                     if (!a.isNull()) {
452                         kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
453                         QDomElement e = m_doc.createElement("property");
454                         e.setAttribute("name", a.name());
455                         QDomText value = m_doc.createTextNode(a.value());
456                         e.appendChild(value);
457                         filt.appendChild(e);
458                     }
459                 }
460             }*/
461
462         // fix slowmotion
463         QDomNodeList producers = westley.toElement().elementsByTagName("producer");
464         max = producers.count();
465         for (int i = 0; i < max; i++) {
466             QDomElement prod = producers.at(i).toElement();
467             if (prod.attribute("mlt_service") == "framebuffer") {
468                 QString slowmotionprod = prod.attribute("resource");
469                 slowmotionprod.replace(':', '?');
470                 kDebug() << "// FOUND WRONG SLOWMO, new: " << slowmotionprod;
471                 prod.setAttribute("resource", slowmotionprod);
472             }
473         }
474         // move producers to correct place, markers to a global list, fix clip descriptions
475         QDomElement markers = m_doc.createElement("markers");
476         // This will get the xml producers:
477         producers = m_doc.elementsByTagName("producer");
478         max = producers.count();
479         for (int i = 0; i < max; i++) {
480             QDomElement prod = producers.at(0).toElement();
481             // add resource also as a property (to allow path correction in setNewResource())
482             // TODO: will it work with slowmotion? needs testing
483             /*if (!prod.attribute("resource").isEmpty()) {
484                 QDomElement prop_resource = m_doc.createElement("property");
485                 prop_resource.setAttribute("name", "resource");
486                 QDomText resource = m_doc.createTextNode(prod.attribute("resource"));
487                 prop_resource.appendChild(resource);
488                 prod.appendChild(prop_resource);
489             }*/
490             QDomNode m = prod.firstChild();
491             if (!m.isNull()) {
492                 if (m.toElement().tagName() == "markers") {
493                     QDomNodeList prodchilds = m.childNodes();
494                     int maxchild = prodchilds.count();
495                     for (int k = 0; k < maxchild; k++) {
496                         QDomElement mark = prodchilds.at(0).toElement();
497                         mark.setAttribute("id", prod.attribute("id"));
498                         markers.insertAfter(mark, QDomNode());
499                     }
500                     prod.removeChild(m);
501                 } else if (prod.attribute("type").toInt() == TEXT) {
502                     // convert title clip
503                     if (m.toElement().tagName() == "textclip") {
504                         QDomDocument tdoc;
505                         QDomElement titleclip = m.toElement();
506                         QDomElement title = tdoc.createElement("kdenlivetitle");
507                         tdoc.appendChild(title);
508                         QDomNodeList objects = titleclip.childNodes();
509                         int maxchild = objects.count();
510                         for (int k = 0; k < maxchild; k++) {
511                             QString objectxml;
512                             QDomElement ob = objects.at(k).toElement();
513                             if (ob.attribute("type") == "3") {
514                                 // text object - all of this goes into "xmldata"...
515                                 QDomElement item = tdoc.createElement("item");
516                                 item.setAttribute("z-index", ob.attribute("z"));
517                                 item.setAttribute("type", "QGraphicsTextItem");
518                                 QDomElement position = tdoc.createElement("position");
519                                 position.setAttribute("x", ob.attribute("x"));
520                                 position.setAttribute("y", ob.attribute("y"));
521                                 QDomElement content = tdoc.createElement("content");
522                                 content.setAttribute("font", ob.attribute("font_family"));
523                                 content.setAttribute("font-size", ob.attribute("font_size"));
524                                 content.setAttribute("font-bold", ob.attribute("bold"));
525                                 content.setAttribute("font-italic", ob.attribute("italic"));
526                                 content.setAttribute("font-underline", ob.attribute("underline"));
527                                 QString col = ob.attribute("color");
528                                 QColor c(col);
529                                 content.setAttribute("font-color", colorToString(c));
530                                 // todo: These fields are missing from the newly generated xmldata:
531                                 // transform, startviewport, endviewport, background
532
533                                 QDomText conttxt = tdoc.createTextNode(ob.attribute("text"));
534                                 content.appendChild(conttxt);
535                                 item.appendChild(position);
536                                 item.appendChild(content);
537                                 title.appendChild(item);
538                             } else if (ob.attribute("type") == "5") {
539                                 // rectangle object
540                                 QDomElement item = tdoc.createElement("item");
541                                 item.setAttribute("z-index", ob.attribute("z"));
542                                 item.setAttribute("type", "QGraphicsRectItem");
543                                 QDomElement position = tdoc.createElement("position");
544                                 position.setAttribute("x", ob.attribute("x"));
545                                 position.setAttribute("y", ob.attribute("y"));
546                                 QDomElement content = tdoc.createElement("content");
547                                 QString col = ob.attribute("color");
548                                 QColor c(col);
549                                 content.setAttribute("brushcolor", colorToString(c));
550                                 QString rect = "0,0,";
551                                 rect.append(ob.attribute("width"));
552                                 rect.append(",");
553                                 rect.append(ob.attribute("height"));
554                                 content.setAttribute("rect", rect);
555                                 item.appendChild(position);
556                                 item.appendChild(content);
557                                 title.appendChild(item);
558                             }
559                         }
560                         prod.setAttribute("xmldata", tdoc.toString());
561                         // mbd todo: This clearly does not work, as every title gets the same name - trying to leave it empty
562                         // QStringList titleInfo = TitleWidget::getFreeTitleInfo(projectFolder());
563                         // prod.setAttribute("titlename", titleInfo.at(0));
564                         // prod.setAttribute("resource", titleInfo.at(1));
565                         //kDebug()<<"TITLE DATA:\n"<<tdoc.toString();
566                         prod.removeChild(m);
567                     } // End conversion of title clips.
568
569                 } else if (m.isText()) {
570                     QString comment = m.nodeValue();
571                     if (!comment.isEmpty()) {
572                         prod.setAttribute("description", comment);
573                     }
574                     prod.removeChild(m);
575                 }
576             }
577             int duration = prod.attribute("duration").toInt();
578             if (duration > 0) prod.setAttribute("out", QString::number(duration));
579             // The clip goes back in, but text clips should not go back in, at least not modified
580             westley.insertBefore(prod, QDomNode());
581         }
582
583         QDomNode westley0 = m_doc.elementsByTagName("westley").at(0);
584         if (!markers.firstChild().isNull()) westley0.appendChild(markers);
585
586         /*
587          * Convert as much of the kdenlivedoc as possible. Use the producer in
588          * westley. First, remove the old stuff from westley, and add a new
589          * empty one. Also, track the max id in order to use it for the adding
590          * of groups/folders
591          */
592         int max_kproducer_id = 0;
593         westley0.removeChild(infoXmlNode);
594         QDomElement infoXml_new = m_doc.createElement("kdenlivedoc");
595         infoXml_new.setAttribute("profile", profile);
596         infoXml.setAttribute("position", startPos);
597
598         // Add all the producers that has a resource in westley
599         QDomElement westley_element = westley0.toElement();
600         if (westley_element.isNull()) {
601             kWarning() << "westley0 element in document was not a QDomElement - unable to add producers to new kdenlivedoc";
602         } else {
603             QDomNodeList wproducers = westley_element.elementsByTagName("producer");
604             int kmax = wproducers.count();
605             for (int i = 0; i < kmax; i++) {
606                 QDomElement wproducer = wproducers.at(i).toElement();
607                 if (wproducer.isNull()) {
608                     kWarning() << "Found producer in westley0, that was not a QDomElement";
609                     continue;
610                 }
611                 if (wproducer.attribute("id") == "black") continue;
612                 // We have to do slightly different things, depending on the type
613                 kDebug() << "Converting producer element with type" << wproducer.attribute("type");
614                 if (wproducer.attribute("type").toInt() == TEXT) {
615                     kDebug() << "Found TEXT element in producer" << endl;
616                     QDomElement kproducer = wproducer.cloneNode(true).toElement();
617                     kproducer.setTagName("kdenlive_producer");
618                     infoXml_new.appendChild(kproducer);
619                     /*
620                      * TODO: Perhaps needs some more changes here to
621                      * "frequency", aspect ratio as a float, frame_size,
622                      * channels, and later, resource and title name
623                      */
624                 } else {
625                     QDomElement kproducer = m_doc.createElement("kdenlive_producer");
626                     kproducer.setAttribute("id", wproducer.attribute("id"));
627                     if (!wproducer.attribute("description").isEmpty())
628                         kproducer.setAttribute("description", wproducer.attribute("description"));
629                     kproducer.setAttribute("resource", wproducer.attribute("resource"));
630                     kproducer.setAttribute("type", wproducer.attribute("type"));
631                     // Testing fix for 358
632                     if (!wproducer.attribute("aspect_ratio").isEmpty()) {
633                         kproducer.setAttribute("aspect_ratio", wproducer.attribute("aspect_ratio"));
634                     }
635                     if (!wproducer.attribute("source_fps").isEmpty()) {
636                         kproducer.setAttribute("fps", wproducer.attribute("source_fps"));
637                     }
638                     if (!wproducer.attribute("length").isEmpty()) {
639                         kproducer.setAttribute("duration", wproducer.attribute("length"));
640                     }
641                     infoXml_new.appendChild(kproducer);
642                 }
643                 if (wproducer.attribute("id").toInt() > max_kproducer_id) {
644                     max_kproducer_id = wproducer.attribute("id").toInt();
645                 }
646             }
647         }
648 #define LOOKUP_FOLDER 1
649 #ifdef LOOKUP_FOLDER
650         /*
651          * Look through all the folder elements of the old doc, for each folder,
652          * for each producer, get the id, look it up in the new doc, set the
653          * groupname and groupid. Note, this does not work at the moment - at
654          * least one folder shows up missing, and clips with no folder does not
655          * show up.
656          */
657         //QDomElement infoXml_old = infoXmlNode.toElement();
658         if (!infoXml_old.isNull()) {
659             QDomNodeList folders = infoXml_old.elementsByTagName("folder");
660             int fsize = folders.size();
661             int groupId = max_kproducer_id + 1; // Start at +1 of max id of the kdenlive_producers
662             for (int i = 0; i < fsize; ++i) {
663                 QDomElement folder = folders.at(i).toElement();
664                 if (!folder.isNull()) {
665                     QString groupName = folder.attribute("name");
666                     kDebug() << "groupName: " << groupName << " with groupId: " << groupId;
667                     QDomNodeList fproducers = folder.elementsByTagName("producer");
668                     int psize = fproducers.size();
669                     for (int j = 0; j < psize; ++j) {
670                         QDomElement fproducer = fproducers.at(j).toElement();
671                         if (!fproducer.isNull()) {
672                             QString id = fproducer.attribute("id");
673                             // This is not very effective, but compared to loading the clips, its a breeze
674                             QDomNodeList kdenlive_producers = infoXml_new.elementsByTagName("kdenlive_producer");
675                             int kpsize = kdenlive_producers.size();
676                             for (int k = 0; k < kpsize; ++k) {
677                                 QDomElement kproducer = kdenlive_producers.at(k).toElement(); // Its an element for sure
678                                 if (id == kproducer.attribute("id")) {
679                                     // We do not check that it already is part of a folder
680                                     kproducer.setAttribute("groupid", groupId);
681                                     kproducer.setAttribute("groupname", groupName);
682                                     break;
683                                 }
684                             }
685                         }
686                     }
687                     ++groupId;
688                 }
689             }
690         }
691 #endif
692         QDomNodeList elements = westley.childNodes();
693         max = elements.count();
694         for (int i = 0; i < max; i++) {
695             QDomElement prod = elements.at(0).toElement();
696             westley0.insertAfter(prod, QDomNode());
697         }
698
699         westley0.appendChild(infoXml_new);
700
701         westley0.removeChild(westley);
702
703         // adds <avfile /> information to <kdenlive_producer />
704         QDomNodeList kproducers = m_doc.elementsByTagName("kdenlive_producer");
705         QDomNodeList avfiles = infoXml_old.elementsByTagName("avfile");
706         kDebug() << "found" << avfiles.count() << "<avfile />s and" << kproducers.count() << "<kdenlive_producer />s";
707         for (int i = 0; i < avfiles.count(); ++i) {
708             QDomElement avfile = avfiles.at(i).toElement();
709             QDomElement kproducer;
710             if (avfile.isNull())
711                 kWarning() << "found an <avfile /> that is not a QDomElement";
712             else {
713                 QString id = avfile.attribute("id");
714                 // this is horrible, must be rewritten, it's just for test
715                 for (int j = 0; j < kproducers.count(); ++j) {
716                     //kDebug() << "checking <kdenlive_producer /> with id" << kproducers.at(j).toElement().attribute("id");
717                     if (kproducers.at(j).toElement().attribute("id") == id) {
718                         kproducer = kproducers.at(j).toElement();
719                         break;
720                     }
721                 }
722                 if (kproducer == QDomElement())
723                     kWarning() << "no match for <avfile /> with id =" << id;
724                 else {
725                     //kDebug() << "ready to set additional <avfile />'s attributes (id =" << id << ")";
726                     kproducer.setAttribute("channels", avfile.attribute("channels"));
727                     kproducer.setAttribute("duration", avfile.attribute("duration"));
728                     kproducer.setAttribute("frame_size", avfile.attribute("width") + 'x' + avfile.attribute("height"));
729                     kproducer.setAttribute("frequency", avfile.attribute("frequency"));
730                     if (kproducer.attribute("description").isEmpty() && !avfile.attribute("description").isEmpty())
731                         kproducer.setAttribute("description", avfile.attribute("description"));
732                 }
733             }
734         }
735         infoXml = infoXml_new;
736     }
737
738     if (version <= 0.81) {
739         // Add the tracks information
740         QString tracksOrder = infoXml.attribute("tracks");
741         if (tracksOrder.isEmpty()) {
742             QDomNodeList tracks = m_doc.elementsByTagName("track");
743             for (int i = 0; i < tracks.count(); i++) {
744                 QDomElement track = tracks.at(i).toElement();
745                 if (track.attribute("producer") != "black_track") {
746                     if (track.attribute("hide") == "video")
747                         tracksOrder.append('a');
748                     else
749                         tracksOrder.append('v');
750                 }
751             }
752         }
753         QDomElement tracksinfo = m_doc.createElement("tracksinfo");
754         for (int i = 0; i < tracksOrder.size(); i++) {
755             QDomElement trackinfo = m_doc.createElement("trackinfo");
756             if (tracksOrder.data()[i] == 'a') {
757                 trackinfo.setAttribute("type", "audio");
758                 trackinfo.setAttribute("blind", true);
759             } else
760                 trackinfo.setAttribute("blind", false);
761             trackinfo.setAttribute("mute", false);
762             trackinfo.setAttribute("locked", false);
763             tracksinfo.appendChild(trackinfo);
764         }
765         infoXml.appendChild(tracksinfo);
766     }
767
768     if (version <= 0.82) {
769         // Convert <westley />s in <mlt />s (MLT extreme makeover)
770         QDomNodeList westleyNodes = m_doc.elementsByTagName("westley");
771         for (int i = 0; i < westleyNodes.count(); i++) {
772             QDomElement westley = westleyNodes.at(i).toElement();
773             westley.setTagName("mlt");
774         }
775     }
776
777     if (version <= 0.83) {
778         // Replace point size with pixel size in text titles
779         if (m_doc.toString().contains("font-size")) {
780             KMessageBox::ButtonCode convert = KMessageBox::Continue;
781             QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
782             for (int i = 0; i < kproducerNodes.count() && convert != KMessageBox::No; ++i) {
783                 QDomElement kproducer = kproducerNodes.at(i).toElement();
784                 if (kproducer.attribute("type").toInt() == TEXT) {
785                     QDomDocument data;
786                     data.setContent(kproducer.attribute("xmldata"));
787                     QDomNodeList items = data.firstChild().childNodes();
788                     for (int j = 0; j < items.count() && convert != KMessageBox::No; ++j) {
789                         if (items.at(j).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
790                             QDomNamedNodeMap textProperties = items.at(j).namedItem("content").attributes();
791                             if (textProperties.namedItem("font-pixel-size").isNull() && !textProperties.namedItem("font-size").isNull()) {
792                                 // Ask the user if he wants to convert
793                                 if (convert != KMessageBox::Yes && convert != KMessageBox::No)
794                                     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"));
795                                 if (convert == KMessageBox::Yes) {
796                                     QFont font;
797                                     font.setPointSize(textProperties.namedItem("font-size").nodeValue().toInt());
798                                     QDomElement content = items.at(j).namedItem("content").toElement();
799                                     content.setAttribute("font-pixel-size", QFontInfo(font).pixelSize());
800                                     content.removeAttribute("font-size");
801                                     kproducer.setAttribute("xmldata", data.toString());
802                                     /*
803                                      * You may be tempted to delete the preview file
804                                      * to force its recreation: bad idea (see
805                                      * http://www.kdenlive.org/mantis/view.php?id=749)
806                                      */
807                                 }
808                             }
809                         }
810                     }
811                 }
812             }
813         }
814
815         // Fill the <documentproperties /> element
816         QDomElement docProperties = infoXml.firstChildElement("documentproperties");
817         if (docProperties.isNull()) {
818             docProperties = m_doc.createElement("documentproperties");
819             docProperties.setAttribute("zonein", infoXml.attribute("zonein"));
820             docProperties.setAttribute("zoneout", infoXml.attribute("zoneout"));
821             docProperties.setAttribute("zoom", infoXml.attribute("zoom"));
822             docProperties.setAttribute("position", infoXml.attribute("position"));
823             infoXml.appendChild(docProperties);
824         }
825     }
826
827     if (version <= 0.84) {
828         // update the title clips to use the new MLT kdenlivetitle producer
829         QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
830         for (int i = 0; i < kproducerNodes.count(); ++i) {
831             QDomElement kproducer = kproducerNodes.at(i).toElement();
832             if (kproducer.attribute("type").toInt() == TEXT) {
833                 QString data = kproducer.attribute("xmldata");
834                 QString datafile = kproducer.attribute("resource");
835                 if (!datafile.endsWith(".kdenlivetitle")) {
836                     datafile = QString();
837                     kproducer.setAttribute("resource", QString());
838                 }
839                 QString id = kproducer.attribute("id");
840                 QDomNodeList mltproducers = m_doc.elementsByTagName("producer");
841                 bool foundData = false;
842                 bool foundResource = false;
843                 bool foundService = false;
844                 for (int j = 0; j < mltproducers.count(); j++) {
845                     QDomElement wproducer = mltproducers.at(j).toElement();
846                     if (wproducer.attribute("id") == id) {
847                         QDomNodeList props = wproducer.childNodes();
848                         for (int k = 0; k < props.count(); k++) {
849                             if (props.at(k).toElement().attribute("name") == "xmldata") {
850                                 props.at(k).firstChild().setNodeValue(data);
851                                 foundData = true;
852                             } else if (props.at(k).toElement().attribute("name") == "mlt_service") {
853                                 props.at(k).firstChild().setNodeValue("kdenlivetitle");
854                                 foundService = true;
855                             } else if (props.at(k).toElement().attribute("name") == "resource") {
856                                 props.at(k).firstChild().setNodeValue(datafile);
857                                 foundResource = true;
858                             }
859                         }
860                         if (!foundData) {
861                             QDomElement e = m_doc.createElement("property");
862                             e.setAttribute("name", "xmldata");
863                             QDomText value = m_doc.createTextNode(data);
864                             e.appendChild(value);
865                             wproducer.appendChild(e);
866                         }
867                         if (!foundService) {
868                             QDomElement e = m_doc.createElement("property");
869                             e.setAttribute("name", "mlt_service");
870                             QDomText value = m_doc.createTextNode("kdenlivetitle");
871                             e.appendChild(value);
872                             wproducer.appendChild(e);
873                         }
874                         if (!foundResource) {
875                             QDomElement e = m_doc.createElement("property");
876                             e.setAttribute("name", "resource");
877                             QDomText value = m_doc.createTextNode(datafile);
878                             e.appendChild(value);
879                             wproducer.appendChild(e);
880                         }
881                         break;
882                     }
883                 }
884             }
885         }
886     }
887     if (version <= 0.85) {
888         // update the LADSPA effects to use the new ladspa.id format instead of external xml file
889         QDomNodeList effectNodes = m_doc.elementsByTagName("filter");
890         for (int i = 0; i < effectNodes.count(); ++i) {
891             QDomElement effect = effectNodes.at(i).toElement();
892             if (EffectsList::property(effect, "mlt_service") == "ladspa") {
893                 // Needs to be converted
894                 QStringList info = getInfoFromEffectName(EffectsList::property(effect, "kdenlive_id"));
895                 if (info.isEmpty()) continue;
896                 // info contains the correct ladspa.id from kdenlive effect name, and a list of parameter's old and new names
897                 EffectsList::setProperty(effect, "kdenlive_id", info.at(0));
898                 EffectsList::setProperty(effect, "tag", info.at(0));
899                 EffectsList::setProperty(effect, "mlt_service", info.at(0));
900                 EffectsList::removeProperty(effect, "src");
901                 for (int j = 1; j < info.size(); j++) {
902                     QString value = EffectsList::property(effect, info.at(j).section('=', 0, 0));
903                     if (!value.isEmpty()) {
904                         // update parameter name
905                         EffectsList::renameProperty(effect, info.at(j).section('=', 0, 0), info.at(j).section('=', 1, 1));
906                     }
907                 }
908             }
909         }
910     }
911
912     if (version <= 0.86) {
913         // Make sure we don't have avformat-novalidate producers, since it caused crashes
914         QDomNodeList producers = m_doc.elementsByTagName("producer");
915         int max = producers.count();
916         for (int i = 0; i < max; i++) {
917             QDomElement prod = producers.at(i).toElement();
918             if (EffectsList::property(prod, "mlt_service") == "avformat-novalidate")
919                 EffectsList::setProperty(prod, "mlt_service", "avformat");
920         }
921
922         // 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
923
924         // Get profile info (width / height)
925         int profileWidth;
926         int profileHeight;
927         QDomElement profile = m_doc.firstChildElement("profile");
928         if (profile.isNull()) profile = infoXml.firstChildElement("profileinfo");
929         if (profile.isNull()) {
930             // could not find profile info, set PAL
931             profileWidth = 720;
932             profileHeight = 576;
933         }
934         else {
935             profileWidth = profile.attribute("width").toInt();
936             profileHeight = profile.attribute("height").toInt();
937         }
938         QDomNodeList transitions = m_doc.elementsByTagName("transition");
939         max = transitions.count();
940         int out;
941         for (int i = 0; i < max; i++) {
942             QDomElement trans = transitions.at(i).toElement();
943             out = trans.attribute("out").toInt() - trans.attribute("in").toInt();
944             QString geom = EffectsList::property(trans, "geometry");
945             Mlt::Geometry *g = new Mlt::Geometry(geom.toUtf8().data(), out, profileWidth, profileHeight);
946             Mlt::GeometryItem item;
947             if (g->next_key(&item, out) == 0) {
948                 // We have a keyframe just after last frame, try to move it to last frame
949                 if (item.frame() == out + 1) {
950                     item.frame(out);
951                     g->insert(item);
952                     g->remove(out + 1);
953                     EffectsList::setProperty(trans, "geometry", g->serialise());
954                 }
955             }
956             delete g;
957         }
958     }
959
960     if (version <= 0.87) {
961         if (!m_doc.firstChildElement("mlt").hasAttribute("LC_NUMERIC")) {
962             m_doc.firstChildElement("mlt").setAttribute("LC_NUMERIC", "C");
963         }
964     }
965
966     // The document has been converted: mark it as modified
967     infoXml.setAttribute("version", currentVersion);
968     m_modified = true;
969     return true;
970 }
971
972 QStringList DocumentValidator::getInfoFromEffectName(const QString oldName)
973 {
974     QStringList info;
975     // Returns a list to convert old Kdenlive ladspa effects
976     if (oldName == "pitch_shift") {
977         info << "ladspa.1433";
978         info << "pitch=0";
979     }
980     else if (oldName == "vinyl") {
981         info << "ladspa.1905";
982         info << "year=0";
983         info << "rpm=1";
984         info << "warping=2";
985         info << "crackle=3";
986         info << "wear=4";
987     }
988     else if (oldName == "room_reverb") {
989         info << "ladspa.1216";
990         info << "room=0";
991         info << "delay=1";
992         info << "damp=2";
993     }
994     else if (oldName == "reverb") {
995         info << "ladspa.1423";
996         info << "room=0";
997         info << "damp=1";
998     }
999     else if (oldName == "rate_scale") {
1000         info << "ladspa.1417";
1001         info << "rate=0";
1002     }
1003     else if (oldName == "pitch_scale") {
1004         info << "ladspa.1193";
1005         info << "coef=0";
1006     }
1007     else if (oldName == "phaser") {
1008         info << "ladspa.1217";
1009         info << "rate=0";
1010         info << "depth=1";
1011         info << "feedback=2";
1012         info << "spread=3";
1013     }
1014     else if (oldName == "limiter") {
1015         info << "ladspa.1913";
1016         info << "gain=0";
1017         info << "limit=1";
1018         info << "release=2";
1019     }
1020     else if (oldName == "equalizer_15") {
1021         info << "ladspa.1197";
1022         info << "1=0";
1023         info << "2=1";
1024         info << "3=2";
1025         info << "4=3";
1026         info << "5=4";
1027         info << "6=5";
1028         info << "7=6";
1029         info << "8=7";
1030         info << "9=8";
1031         info << "10=9";
1032         info << "11=10";
1033         info << "12=11";
1034         info << "13=12";
1035         info << "14=13";
1036         info << "15=14";
1037     }
1038     else if (oldName == "equalizer") {
1039         info << "ladspa.1901";
1040         info << "logain=0";
1041         info << "midgain=1";
1042         info << "higain=2";
1043     }
1044     else if (oldName == "declipper") {
1045         info << "ladspa.1195";
1046     }
1047     return info;
1048 }
1049
1050 QString DocumentValidator::colorToString(const QColor& c)
1051 {
1052     QString ret = "%1,%2,%3,%4";
1053     ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
1054     return ret;
1055 }
1056
1057 bool DocumentValidator::isProject() const
1058 {
1059     QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
1060     return !infoXmlNode.isNull();
1061 }
1062
1063 bool DocumentValidator::isModified() const
1064 {
1065     return m_modified;
1066 }
1067
1068 void DocumentValidator::updateEffects()
1069 {
1070     // WARNING: order by findDirs will determine which js file to use (in case multiple scripts for the same filter exist)
1071     QMap <QString, KUrl> paths;
1072 #if QT_VERSION >= 0x040700
1073     QMap <QString, QScriptProgram> scripts;
1074 #else
1075     QMap <QString, QString> scripts;
1076 #endif
1077     QStringList directories = KGlobal::dirs()->findDirs("appdata", "effects/update");
1078     foreach (const QString &directoryName, directories) {
1079         QDir directory(directoryName);
1080         QStringList fileList = directory.entryList(QStringList() << "*.js", QDir::Files);
1081         foreach (const QString &fileName, fileList) {
1082             QString identifier = fileName;
1083             // remove extension (".js")
1084             identifier.chop(3);
1085             paths.insert(identifier, KUrl(directoryName + fileName));
1086         }
1087     }
1088
1089     QDomNodeList effects = m_doc.elementsByTagName("filter");
1090     int max = effects.count();
1091     QStringList safeEffects;
1092     for(int i = 0; i < max; ++i) {
1093         QDomElement effect = effects.at(i).toElement();
1094         QString effectId = EffectsList::property(effect, "kdenlive_id");
1095         if (safeEffects.contains(effectId)) {
1096             // Do not check the same effect twice if it is at the correct version
1097             // (assume we don't have different versions of the same effect in a project file)
1098             continue;
1099         }
1100         QString effectTag = EffectsList::property(effect, "tag");
1101         QString effectVersionStr = EffectsList::property(effect, "version");
1102         double effectVersion = effectVersionStr.isNull() ? -1 : effectVersionStr.toDouble();
1103
1104         QDomElement effectDescr = MainWindow::customEffects.getEffectByTag(QString(), effectId);
1105         if (effectDescr.isNull()) {
1106             effectDescr = MainWindow::videoEffects.getEffectByTag(effectTag, effectId);
1107         }
1108         if (effectDescr.isNull()) {
1109             effectDescr = MainWindow::audioEffects.getEffectByTag(effectTag, effectId);
1110         }
1111         if (!effectDescr.isNull()) {
1112             double serviceVersion = -1;
1113             QDomElement serviceVersionElem = effectDescr.firstChildElement("version");
1114             if (!serviceVersionElem.isNull()) {
1115                 serviceVersion = serviceVersionElem.text().toDouble();
1116             }
1117             if (serviceVersion != effectVersion && paths.contains(effectId)) {
1118                 if (!scripts.contains(effectId)) {
1119                     QFile scriptFile(paths.value(effectId).path());
1120                     if (!scriptFile.open(QIODevice::ReadOnly)) {
1121                         continue;
1122                     }
1123 #if QT_VERSION >= 0x040700
1124                     QScriptProgram scriptProgram(scriptFile.readAll());
1125 #else
1126                     QString scriptProgram = scriptFile.readAll();
1127 #endif
1128                     scriptFile.close();
1129                     scripts.insert(effectId, scriptProgram);
1130                 }
1131
1132                 QScriptEngine scriptEngine;
1133                 scriptEngine.importExtension("qt.core");
1134                 scriptEngine.importExtension("qt.xml");
1135                 scriptEngine.evaluate(scripts.value(effectId));
1136                 QScriptValue updateRules = scriptEngine.globalObject().property("update");
1137                 if (!updateRules.isValid())
1138                     continue;
1139                 if (updateRules.isFunction()) {
1140                     QDomDocument scriptDoc;
1141                     scriptDoc.appendChild(scriptDoc.importNode(effect, true));
1142
1143                     QString effectString = updateRules.call(QScriptValue(), QScriptValueList()  << serviceVersion << effectVersion << scriptDoc.toString()).toString();
1144
1145                     if (!effectString.isEmpty() && !scriptEngine.hasUncaughtException()) {
1146                         scriptDoc.setContent(effectString);
1147                         QDomNode updatedEffect = effect.ownerDocument().importNode(scriptDoc.documentElement(), true);
1148                         effect.parentNode().replaceChild(updatedEffect, effect);
1149                         m_modified = true;
1150                     }
1151                 } else {
1152                     m_modified = updateEffectParameters(effect.childNodes(), &updateRules, serviceVersion, effectVersion);
1153                 }
1154
1155                 // set version number since MLT won't change it (only initially set it)
1156                 QDomElement versionElem = effect.firstChildElement("version");
1157                 if (EffectsList::property(effect, "version").isNull()) {
1158                     versionElem = effect.ownerDocument().createTextNode(QLocale().toString(serviceVersion)).toElement();
1159                     versionElem.setTagName("property");
1160                     versionElem.setAttribute("name", "version");
1161                     effect.appendChild(versionElem);
1162                 } else {
1163                     EffectsList::setProperty(effect, "version", QLocale().toString(serviceVersion));
1164                 }
1165             }
1166             else safeEffects.append(effectId);
1167         }
1168     }
1169 }
1170
1171 bool DocumentValidator::updateEffectParameters(QDomNodeList parameters, const QScriptValue* updateRules, const double serviceVersion, const double effectVersion)
1172 {
1173     bool updated = false;
1174     bool isDowngrade = serviceVersion < effectVersion;
1175     for (int i = 0; i < parameters.count(); ++i) {
1176         QDomElement parameter = parameters.at(i).toElement();
1177         QScriptValue rules = updateRules->property(parameter.attribute("name"));
1178         if (rules.isValid() && rules.isArray()) {
1179             int rulesCount = rules.property("length").toInt32();
1180             if (isDowngrade) {
1181                 // start with the highest version and downgrade step by step
1182                 for (int j = rulesCount - 1; j >= 0; --j) {
1183                     double version = rules.property(j).property(0).toNumber();
1184                     if (version <= effectVersion && version > serviceVersion) {
1185                         parameter.firstChild().setNodeValue(rules.property(j).property(1).call(QScriptValue(), QScriptValueList() << parameter.text() << isDowngrade).toString());
1186                         updated = true;
1187                     }
1188                 }
1189             } else {
1190                 for (int j = 0; j < rulesCount; ++j) {
1191                     double version = rules.property(j).property(0).toNumber();
1192                     if (version > effectVersion && version <= serviceVersion) {
1193                         parameter.firstChild().setNodeValue(rules.property(j).property(1).call(QScriptValue(), QScriptValueList() << parameter.text() << isDowngrade).toString());
1194                         updated = true;
1195                     }
1196                 }
1197             }
1198         }
1199     }
1200     return updated;
1201 }