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