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