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