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