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