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