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