]> git.sesse.net Git - kdenlive/blob - src/documentvalidator.cpp
move zonein, zoneout and zoom from <kdenlivedoc /> to <documentproperties /> when...
[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 DocumentValidator::DocumentValidator(QDomDocument doc):
33         m_doc(doc),
34         m_modified(false)
35 {}
36
37 bool DocumentValidator::validate(const double currentVersion)
38 {
39     // Check if we're validating a Kdenlive project
40     if (!isProject())
41         return false;
42
43     // Upgrade the document to the latest version
44     QDomNode kdenlivedocNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
45     QDomElement kdenlivedocElm = kdenlivedocNode.toElement();
46     if (!upgrade(kdenlivedocElm.attribute("version").toDouble(), currentVersion))
47         return false;
48
49     /*
50      * Check the syntax (this will be replaced by XSD validation with Qt 4.6)
51      * and correct some errors
52      */
53     QDomNode mltNode = m_doc.elementsByTagName("mlt").at(0);
54     QDomElement mltElm = mltNode.toElement();
55     if (mltElm.isNull()) // At least the root element must be there
56         return false;
57     else {
58         // Return (or create) the tractor
59         QDomNode tractorNode = m_doc.elementsByTagName("tractor").at(0);
60         QDomElement tractorElm = tractorNode.toElement();
61         if (tractorElm.isNull()) {
62             m_modified = true;
63             tractorElm = m_doc.createElement("tractor");
64             tractorElm.setAttribute("global_feed", "1");
65             tractorElm.setAttribute("in", "0");
66             tractorElm.setAttribute("out", "-1");
67             tractorElm.setAttribute("id", "maintractor");
68             mltElm.appendChild(tractorElm);
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 = m_doc.elementsByTagName("track");
78         tracksMax = qMax(tracks.count() - 1, tracksMax);
79         QDomNodeList tracksinfo = m_doc.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             m_modified = true;
86             int difference;
87             if (playlists.count() - 1 < tracksMax) {
88                 difference = tracksMax - (playlists.count() - 1);
89                 for (int i = 0; i < difference; ++i) {
90                     QDomElement playlist = m_doc.createElement("playlist");
91                     mltElm.appendChild(playlist);
92                 }
93             }
94             if (tracks.count() - 1 < tracksMax) {
95                 difference = tracksMax - (tracks.count() - 1);
96                 for (int i = 0; i < difference; ++i) {
97                     QDomElement track = m_doc.createElement("track");
98                     tractorElm.appendChild(track);
99                 }
100             }
101             if (tracksinfo.count() < tracksMax) {
102                 QDomNode tracksinfoNode = m_doc.elementsByTagName("tracksinfo").at(0);
103                 QDomElement tracksinfoElm = tracksinfoNode.toElement();
104                 if (tracksinfoElm.isNull()) {
105                     tracksinfoElm = m_doc.createElement("tracksinfo");
106                     kdenlivedocElm.appendChild(tracksinfoElm);
107                 }
108                 difference = tracksMax - tracksinfo.count();
109                 for (int i = 0; i < difference; ++i) {
110                     QDomElement trackinfo = m_doc.createElement("trackinfo");
111                     trackinfo.setAttribute("mute", "0");
112                     trackinfo.setAttribute("locked", "0");
113                     tracksinfoElm.appendChild(trackinfo);
114                 }
115             }
116         }
117
118         // TODO: check the tracks references
119         // TODO: check internal mix transitions
120     }
121
122     return true;
123 }
124
125 bool DocumentValidator::upgrade(double version, const double currentVersion)
126 {
127     kDebug() << "Opening a document with version " << version;
128
129     // No conversion needed
130     if (version == currentVersion) {
131         // TODO: uncomment when currentVersion == 0.84
132         //return true;
133     }
134
135     // The document is too new
136     if (version > currentVersion) {
137         kDebug() << "Unable to open document with version " << version;
138         KMessageBox::sorry(kapp->activeWindow(), i18n("This project type is unsupported (version %1) and can't be loaded.\nPlease consider upgrading you Kdenlive version.", version), i18n("Unable to open project"));
139         return false;
140     }
141
142     // Unsupported document versions
143     if (version == 0.5 || version == 0.7) {
144         kDebug() << "Unable to open document with version " << version;
145         KMessageBox::sorry(kapp->activeWindow(), i18n("This project type is unsupported (version %1) and can't be loaded.", version), i18n("Unable to open project"));
146         return false;
147     }
148
149     // <kdenlivedoc />
150     QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
151     QDomElement infoXml = infoXmlNode.toElement();
152
153     if (version <= 0.6) {
154         QDomElement infoXml_old = infoXmlNode.cloneNode(true).toElement(); // Needed for folders
155         QDomNode westley = m_doc.elementsByTagName("westley").at(1);
156         QDomNode tractor = m_doc.elementsByTagName("tractor").at(0);
157         QDomNode multitrack = m_doc.elementsByTagName("multitrack").at(0);
158         QDomNodeList playlists = m_doc.elementsByTagName("playlist");
159
160         QDomNode props = m_doc.elementsByTagName("properties").at(0).toElement();
161         QString profile = props.toElement().attribute("videoprofile");
162         int startPos = props.toElement().attribute("timeline_position").toInt();
163         if (profile == "dv_wide")
164             profile = "dv_pal_wide";
165
166         // move playlists outside of tractor and add the tracks instead
167         int max = playlists.count();
168         if (westley.isNull()) {
169             westley = m_doc.createElement("westley");
170             m_doc.documentElement().appendChild(westley);
171         }
172         if (tractor.isNull()) {
173             kDebug() << "// NO MLT PLAYLIST, building empty one";
174             QDomElement blank_tractor = m_doc.createElement("tractor");
175             westley.appendChild(blank_tractor);
176             QDomElement blank_playlist = m_doc.createElement("playlist");
177             blank_playlist.setAttribute("id", "black_track");
178             westley.insertBefore(blank_playlist, QDomNode());
179             QDomElement blank_track = m_doc.createElement("track");
180             blank_track.setAttribute("producer", "black_track");
181             blank_tractor.appendChild(blank_track);
182
183             QDomNodeList kdenlivetracks = m_doc.elementsByTagName("kdenlivetrack");
184             for (int i = 0; i < kdenlivetracks.count(); i++) {
185                 blank_playlist = m_doc.createElement("playlist");
186                 blank_playlist.setAttribute("id", "playlist" + QString::number(i));
187                 westley.insertBefore(blank_playlist, QDomNode());
188                 blank_track = m_doc.createElement("track");
189                 blank_track.setAttribute("producer", "playlist" + QString::number(i));
190                 blank_tractor.appendChild(blank_track);
191                 if (kdenlivetracks.at(i).toElement().attribute("cliptype") == "Sound") {
192                     blank_playlist.setAttribute("hide", "video");
193                     blank_track.setAttribute("hide", "video");
194                 }
195             }
196         } else for (int i = 0; i < max; i++) {
197                 QDomNode n = playlists.at(i);
198                 westley.insertBefore(n, QDomNode());
199                 QDomElement pl = n.toElement();
200                 QDomElement track = m_doc.createElement("track");
201                 QString trackType = pl.attribute("hide");
202                 if (!trackType.isEmpty())
203                     track.setAttribute("hide", trackType);
204                 QString playlist_id =  pl.attribute("id");
205                 if (playlist_id.isEmpty()) {
206                     playlist_id = "black_track";
207                     pl.setAttribute("id", playlist_id);
208                 }
209                 track.setAttribute("producer", playlist_id);
210                 //tractor.appendChild(track);
211 #define KEEP_TRACK_ORDER 1
212 #ifdef KEEP_TRACK_ORDER
213                 tractor.insertAfter(track, QDomNode());
214 #else
215                 // Insert the new track in an order that hopefully matches the 3 video, then 2 audio tracks of Kdenlive 0.7.0
216                 // insertion sort - O( tracks*tracks )
217                 // Note, this breaks _all_ transitions - but you can move them up and down afterwards.
218                 QDomElement tractor_elem = tractor.toElement();
219                 if (! tractor_elem.isNull()) {
220                     QDomNodeList tracks = tractor_elem.elementsByTagName("track");
221                     int size = tracks.size();
222                     if (size == 0) {
223                         tractor.insertAfter(track, QDomNode());
224                     } else {
225                         bool inserted = false;
226                         for (int i = 0; i < size; ++i) {
227                             QDomElement track_elem = tracks.at(i).toElement();
228                             if (track_elem.isNull()) {
229                                 tractor.insertAfter(track, QDomNode());
230                                 inserted = true;
231                                 break;
232                             } else {
233                                 kDebug() << "playlist_id: " << playlist_id << " producer:" << track_elem.attribute("producer");
234                                 if (playlist_id < track_elem.attribute("producer")) {
235                                     tractor.insertBefore(track, track_elem);
236                                     inserted = true;
237                                     break;
238                                 }
239                             }
240                         }
241                         // Reach here, no insertion, insert last
242                         if (!inserted) {
243                             tractor.insertAfter(track, QDomNode());
244                         }
245                     }
246                 } else {
247                     kWarning() << "tractor was not a QDomElement";
248                     tractor.insertAfter(track, QDomNode());
249                 }
250 #endif
251             }
252         tractor.removeChild(multitrack);
253
254         // audio track mixing transitions should not be added to track view, so add required attribute
255         QDomNodeList transitions = m_doc.elementsByTagName("transition");
256         max = transitions.count();
257         for (int i = 0; i < max; i++) {
258             QDomElement tr = transitions.at(i).toElement();
259             if (tr.attribute("combine") == "1" && tr.attribute("mlt_service") == "mix") {
260                 QDomElement property = m_doc.createElement("property");
261                 property.setAttribute("name", "internal_added");
262                 QDomText value = m_doc.createTextNode("237");
263                 property.appendChild(value);
264                 tr.appendChild(property);
265                 property = m_doc.createElement("property");
266                 property.setAttribute("name", "mlt_service");
267                 value = m_doc.createTextNode("mix");
268                 property.appendChild(value);
269                 tr.appendChild(property);
270             } else {
271                 // convert transition
272                 QDomNamedNodeMap attrs = tr.attributes();
273                 for (int j = 0; j < attrs.count(); j++) {
274                     QString attrName = attrs.item(j).nodeName();
275                     if (attrName != "in" && attrName != "out" && attrName != "id") {
276                         QDomElement property = m_doc.createElement("property");
277                         property.setAttribute("name", attrName);
278                         QDomText value = m_doc.createTextNode(attrs.item(j).nodeValue());
279                         property.appendChild(value);
280                         tr.appendChild(property);
281                     }
282                 }
283             }
284         }
285
286         // move transitions after tracks
287         for (int i = 0; i < max; i++) {
288             tractor.insertAfter(transitions.at(0), QDomNode());
289         }
290
291         // Fix filters format
292         QDomNodeList entries = m_doc.elementsByTagName("entry");
293         max = entries.count();
294         for (int i = 0; i < max; i++) {
295             QString last_id;
296             int effectix = 0;
297             QDomNode m = entries.at(i).firstChild();
298             while (!m.isNull()) {
299                 if (m.toElement().tagName() == "filter") {
300                     QDomElement filt = m.toElement();
301                     QDomNamedNodeMap attrs = filt.attributes();
302                     QString current_id = filt.attribute("kdenlive_id");
303                     if (current_id != last_id) {
304                         effectix++;
305                         last_id = current_id;
306                     }
307                     QDomElement e = m_doc.createElement("property");
308                     e.setAttribute("name", "kdenlive_ix");
309                     QDomText value = m_doc.createTextNode(QString::number(effectix));
310                     e.appendChild(value);
311                     filt.appendChild(e);
312                     for (int j = 0; j < attrs.count(); j++) {
313                         QDomAttr a = attrs.item(j).toAttr();
314                         if (!a.isNull()) {
315                             kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
316                             QDomElement e = m_doc.createElement("property");
317                             e.setAttribute("name", a.name());
318                             QDomText value = m_doc.createTextNode(a.value());
319                             e.appendChild(value);
320                             filt.appendChild(e);
321
322                         }
323                     }
324                 }
325                 m = m.nextSibling();
326             }
327         }
328
329         /*
330             QDomNodeList filters = m_doc.elementsByTagName("filter");
331             max = filters.count();
332             QString last_id;
333             int effectix = 0;
334             for (int i = 0; i < max; i++) {
335                 QDomElement filt = filters.at(i).toElement();
336                 QDomNamedNodeMap attrs = filt.attributes();
337         QString current_id = filt.attribute("kdenlive_id");
338         if (current_id != last_id) {
339             effectix++;
340             last_id = current_id;
341         }
342         QDomElement e = m_doc.createElement("property");
343                 e.setAttribute("name", "kdenlive_ix");
344                 QDomText value = m_doc.createTextNode(QString::number(1));
345                 e.appendChild(value);
346                 filt.appendChild(e);
347                 for (int j = 0; j < attrs.count(); j++) {
348                     QDomAttr a = attrs.item(j).toAttr();
349                     if (!a.isNull()) {
350                         kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
351                         QDomElement e = m_doc.createElement("property");
352                         e.setAttribute("name", a.name());
353                         QDomText value = m_doc.createTextNode(a.value());
354                         e.appendChild(value);
355                         filt.appendChild(e);
356                     }
357                 }
358             }*/
359
360         // fix slowmotion
361         QDomNodeList producers = westley.toElement().elementsByTagName("producer");
362         max = producers.count();
363         for (int i = 0; i < max; i++) {
364             QDomElement prod = producers.at(i).toElement();
365             if (prod.attribute("mlt_service") == "framebuffer") {
366                 QString slowmotionprod = prod.attribute("resource");
367                 slowmotionprod.replace(':', '?');
368                 kDebug() << "// FOUND WRONG SLOWMO, new: " << slowmotionprod;
369                 prod.setAttribute("resource", slowmotionprod);
370             }
371         }
372         // move producers to correct place, markers to a global list, fix clip descriptions
373         QDomElement markers = m_doc.createElement("markers");
374         // This will get the xml producers:
375         producers = m_doc.elementsByTagName("producer");
376         max = producers.count();
377         for (int i = 0; i < max; i++) {
378             QDomElement prod = producers.at(0).toElement();
379             // add resource also as a property (to allow path correction in setNewResource())
380             // TODO: will it work with slowmotion? needs testing
381             /*if (!prod.attribute("resource").isEmpty()) {
382                 QDomElement prop_resource = m_doc.createElement("property");
383                 prop_resource.setAttribute("name", "resource");
384                 QDomText resource = m_doc.createTextNode(prod.attribute("resource"));
385                 prop_resource.appendChild(resource);
386                 prod.appendChild(prop_resource);
387             }*/
388             QDomNode m = prod.firstChild();
389             if (!m.isNull()) {
390                 if (m.toElement().tagName() == "markers") {
391                     QDomNodeList prodchilds = m.childNodes();
392                     int maxchild = prodchilds.count();
393                     for (int k = 0; k < maxchild; k++) {
394                         QDomElement mark = prodchilds.at(0).toElement();
395                         mark.setAttribute("id", prod.attribute("id"));
396                         markers.insertAfter(mark, QDomNode());
397                     }
398                     prod.removeChild(m);
399                 } else if (prod.attribute("type").toInt() == TEXT) {
400                     // convert title clip
401                     if (m.toElement().tagName() == "textclip") {
402                         QDomDocument tdoc;
403                         QDomElement titleclip = m.toElement();
404                         QDomElement title = tdoc.createElement("kdenlivetitle");
405                         tdoc.appendChild(title);
406                         QDomNodeList objects = titleclip.childNodes();
407                         int maxchild = objects.count();
408                         for (int k = 0; k < maxchild; k++) {
409                             QString objectxml;
410                             QDomElement ob = objects.at(k).toElement();
411                             if (ob.attribute("type") == "3") {
412                                 // text object - all of this goes into "xmldata"...
413                                 QDomElement item = tdoc.createElement("item");
414                                 item.setAttribute("z-index", ob.attribute("z"));
415                                 item.setAttribute("type", "QGraphicsTextItem");
416                                 QDomElement position = tdoc.createElement("position");
417                                 position.setAttribute("x", ob.attribute("x"));
418                                 position.setAttribute("y", ob.attribute("y"));
419                                 QDomElement content = tdoc.createElement("content");
420                                 content.setAttribute("font", ob.attribute("font_family"));
421                                 content.setAttribute("font-size", ob.attribute("font_size"));
422                                 content.setAttribute("font-bold", ob.attribute("bold"));
423                                 content.setAttribute("font-italic", ob.attribute("italic"));
424                                 content.setAttribute("font-underline", ob.attribute("underline"));
425                                 QString col = ob.attribute("color");
426                                 QColor c(col);
427                                 content.setAttribute("font-color", colorToString(c));
428                                 // todo: These fields are missing from the newly generated xmldata:
429                                 // transform, startviewport, endviewport, background
430
431                                 QDomText conttxt = tdoc.createTextNode(ob.attribute("text"));
432                                 content.appendChild(conttxt);
433                                 item.appendChild(position);
434                                 item.appendChild(content);
435                                 title.appendChild(item);
436                             } else if (ob.attribute("type") == "5") {
437                                 // rectangle object
438                                 QDomElement item = tdoc.createElement("item");
439                                 item.setAttribute("z-index", ob.attribute("z"));
440                                 item.setAttribute("type", "QGraphicsRectItem");
441                                 QDomElement position = tdoc.createElement("position");
442                                 position.setAttribute("x", ob.attribute("x"));
443                                 position.setAttribute("y", ob.attribute("y"));
444                                 QDomElement content = tdoc.createElement("content");
445                                 QString col = ob.attribute("color");
446                                 QColor c(col);
447                                 content.setAttribute("brushcolor", colorToString(c));
448                                 QString rect = "0,0,";
449                                 rect.append(ob.attribute("width"));
450                                 rect.append(",");
451                                 rect.append(ob.attribute("height"));
452                                 content.setAttribute("rect", rect);
453                                 item.appendChild(position);
454                                 item.appendChild(content);
455                                 title.appendChild(item);
456                             }
457                         }
458                         prod.setAttribute("xmldata", tdoc.toString());
459                         // mbd todo: This clearly does not work, as every title gets the same name - trying to leave it empty
460                         // QStringList titleInfo = TitleWidget::getFreeTitleInfo(projectFolder());
461                         // prod.setAttribute("titlename", titleInfo.at(0));
462                         // prod.setAttribute("resource", titleInfo.at(1));
463                         //kDebug()<<"TITLE DATA:\n"<<tdoc.toString();
464                         prod.removeChild(m);
465                     } // End conversion of title clips.
466
467                 } else if (m.isText()) {
468                     QString comment = m.nodeValue();
469                     if (!comment.isEmpty()) {
470                         prod.setAttribute("description", comment);
471                     }
472                     prod.removeChild(m);
473                 }
474             }
475             int duration = prod.attribute("duration").toInt();
476             if (duration > 0) prod.setAttribute("out", QString::number(duration));
477             // The clip goes back in, but text clips should not go back in, at least not modified
478             westley.insertBefore(prod, QDomNode());
479         }
480
481         QDomNode westley0 = m_doc.elementsByTagName("westley").at(0);
482         if (!markers.firstChild().isNull()) westley0.appendChild(markers);
483
484         /*
485          * Convert as much of the kdenlivedoc as possible. Use the producer in
486          * westley. First, remove the old stuff from westley, and add a new
487          * empty one. Also, track the max id in order to use it for the adding
488          * of groups/folders
489          */
490         int max_kproducer_id = 0;
491         westley0.removeChild(infoXmlNode);
492         QDomElement infoXml_new = m_doc.createElement("kdenlivedoc");
493         infoXml_new.setAttribute("profile", profile);
494         infoXml.setAttribute("position", startPos);
495
496         // Add all the producers that has a resource in westley
497         QDomElement westley_element = westley0.toElement();
498         if (westley_element.isNull()) {
499             kWarning() << "westley0 element in document was not a QDomElement - unable to add producers to new kdenlivedoc";
500         } else {
501             QDomNodeList wproducers = westley_element.elementsByTagName("producer");
502             int kmax = wproducers.count();
503             for (int i = 0; i < kmax; i++) {
504                 QDomElement wproducer = wproducers.at(i).toElement();
505                 if (wproducer.isNull()) {
506                     kWarning() << "Found producer in westley0, that was not a QDomElement";
507                     continue;
508                 }
509                 if (wproducer.attribute("id") == "black") continue;
510                 // We have to do slightly different things, depending on the type
511                 kDebug() << "Converting producer element with type" << wproducer.attribute("type");
512                 if (wproducer.attribute("type").toInt() == TEXT) {
513                     kDebug() << "Found TEXT element in producer" << endl;
514                     QDomElement kproducer = wproducer.cloneNode(true).toElement();
515                     kproducer.setTagName("kdenlive_producer");
516                     infoXml_new.appendChild(kproducer);
517                     /*
518                      * TODO: Perhaps needs some more changes here to
519                      * "frequency", aspect ratio as a float, frame_size,
520                      * channels, and later, resource and title name
521                      */
522                 } else {
523                     QDomElement kproducer = m_doc.createElement("kdenlive_producer");
524                     kproducer.setAttribute("id", wproducer.attribute("id"));
525                     if (!wproducer.attribute("description").isEmpty())
526                         kproducer.setAttribute("description", wproducer.attribute("description"));
527                     kproducer.setAttribute("resource", wproducer.attribute("resource"));
528                     kproducer.setAttribute("type", wproducer.attribute("type"));
529                     // Testing fix for 358
530                     if (!wproducer.attribute("aspect_ratio").isEmpty()) {
531                         kproducer.setAttribute("aspect_ratio", wproducer.attribute("aspect_ratio"));
532                     }
533                     if (!wproducer.attribute("source_fps").isEmpty()) {
534                         kproducer.setAttribute("fps", wproducer.attribute("source_fps"));
535                     }
536                     if (!wproducer.attribute("length").isEmpty()) {
537                         kproducer.setAttribute("duration", wproducer.attribute("length"));
538                     }
539                     infoXml_new.appendChild(kproducer);
540                 }
541                 if (wproducer.attribute("id").toInt() > max_kproducer_id) {
542                     max_kproducer_id = wproducer.attribute("id").toInt();
543                 }
544             }
545         }
546 #define LOOKUP_FOLDER 1
547 #ifdef LOOKUP_FOLDER
548         /*
549          * Look through all the folder elements of the old doc, for each folder,
550          * for each producer, get the id, look it up in the new doc, set the
551          * groupname and groupid. Note, this does not work at the moment - at
552          * least one folder shows up missing, and clips with no folder does not
553          * show up.
554          */
555         //QDomElement infoXml_old = infoXmlNode.toElement();
556         if (!infoXml_old.isNull()) {
557             QDomNodeList folders = infoXml_old.elementsByTagName("folder");
558             int fsize = folders.size();
559             int groupId = max_kproducer_id + 1; // Start at +1 of max id of the kdenlive_producers
560             for (int i = 0; i < fsize; ++i) {
561                 QDomElement folder = folders.at(i).toElement();
562                 if (!folder.isNull()) {
563                     QString groupName = folder.attribute("name");
564                     kDebug() << "groupName: " << groupName << " with groupId: " << groupId;
565                     QDomNodeList fproducers = folder.elementsByTagName("producer");
566                     int psize = fproducers.size();
567                     for (int j = 0; j < psize; ++j) {
568                         QDomElement fproducer = fproducers.at(j).toElement();
569                         if (!fproducer.isNull()) {
570                             QString id = fproducer.attribute("id");
571                             // This is not very effective, but compared to loading the clips, its a breeze
572                             QDomNodeList kdenlive_producers = infoXml_new.elementsByTagName("kdenlive_producer");
573                             int kpsize = kdenlive_producers.size();
574                             for (int k = 0; k < kpsize; ++k) {
575                                 QDomElement kproducer = kdenlive_producers.at(k).toElement(); // Its an element for sure
576                                 if (id == kproducer.attribute("id")) {
577                                     // We do not check that it already is part of a folder
578                                     kproducer.setAttribute("groupid", groupId);
579                                     kproducer.setAttribute("groupname", groupName);
580                                     break;
581                                 }
582                             }
583                         }
584                     }
585                     ++groupId;
586                 }
587             }
588         }
589 #endif
590         QDomNodeList elements = westley.childNodes();
591         max = elements.count();
592         for (int i = 0; i < max; i++) {
593             QDomElement prod = elements.at(0).toElement();
594             westley0.insertAfter(prod, QDomNode());
595         }
596
597         westley0.appendChild(infoXml_new);
598
599         westley0.removeChild(westley);
600
601         // adds <avfile /> information to <kdenlive_producer />
602         QDomNodeList kproducers = m_doc.elementsByTagName("kdenlive_producer");
603         QDomNodeList avfiles = infoXml_old.elementsByTagName("avfile");
604         kDebug() << "found" << avfiles.count() << "<avfile />s and" << kproducers.count() << "<kdenlive_producer />s";
605         for (int i = 0; i < avfiles.count(); ++i) {
606             QDomElement avfile = avfiles.at(i).toElement();
607             QDomElement kproducer;
608             if (avfile.isNull())
609                 kWarning() << "found an <avfile /> that is not a QDomElement";
610             else {
611                 QString id = avfile.attribute("id");
612                 // this is horrible, must be rewritten, it's just for test
613                 for (int j = 0; j < kproducers.count(); ++j) {
614                     //kDebug() << "checking <kdenlive_producer /> with id" << kproducers.at(j).toElement().attribute("id");
615                     if (kproducers.at(j).toElement().attribute("id") == id) {
616                         kproducer = kproducers.at(j).toElement();
617                         break;
618                     }
619                 }
620                 if (kproducer == QDomElement())
621                     kWarning() << "no match for <avfile /> with id =" << id;
622                 else {
623                     //kDebug() << "ready to set additional <avfile />'s attributes (id =" << id << ")";
624                     kproducer.setAttribute("channels", avfile.attribute("channels"));
625                     kproducer.setAttribute("duration", avfile.attribute("duration"));
626                     kproducer.setAttribute("frame_size", avfile.attribute("width") + 'x' + avfile.attribute("height"));
627                     kproducer.setAttribute("frequency", avfile.attribute("frequency"));
628                     if (kproducer.attribute("description").isEmpty() && !avfile.attribute("description").isEmpty())
629                         kproducer.setAttribute("description", avfile.attribute("description"));
630                 }
631             }
632         }
633         infoXml = infoXml_new;
634     }
635
636     if (version <= 0.81) {
637         // Add the tracks information
638         QString tracksOrder = infoXml.attribute("tracks");
639         if (tracksOrder.isEmpty()) {
640             QDomNodeList tracks = m_doc.elementsByTagName("track");
641             for (int i = 0; i < tracks.count(); i++) {
642                 QDomElement track = tracks.at(i).toElement();
643                 if (track.attribute("producer") != "black_track") {
644                     if (track.attribute("hide") == "video")
645                         tracksOrder.append('a');
646                     else
647                         tracksOrder.append('v');
648                 }
649             }
650         }
651         QDomElement tracksinfo = m_doc.createElement("tracksinfo");
652         for (int i = 0; i < tracksOrder.size(); i++) {
653             QDomElement trackinfo = m_doc.createElement("trackinfo");
654             if (tracksOrder.data()[i] == 'a') {
655                 trackinfo.setAttribute("type", "audio");
656                 trackinfo.setAttribute("blind", true);
657             } else
658                 trackinfo.setAttribute("blind", false);
659             trackinfo.setAttribute("mute", false);
660             trackinfo.setAttribute("locked", false);
661             tracksinfo.appendChild(trackinfo);
662         }
663         infoXml.appendChild(tracksinfo);
664     }
665
666     if (version <= 0.82) {
667         // Convert <westley />s in <mlt />s (MLT extreme makeover)
668         QDomNodeList westleyNodes = m_doc.elementsByTagName("westley");
669         for (int i = 0; i < westleyNodes.count(); i++) {
670             QDomElement westley = westleyNodes.at(i).toElement();
671             westley.setTagName("mlt");
672         }
673     }
674
675     if (version <= 0.83) {
676         // Replace point size with pixel size in text titles
677         if (m_doc.toString().contains("font-size")) {
678             KMessageBox::ButtonCode convert;
679             QDomNodeList kproducerNodes = m_doc.elementsByTagName("kdenlive_producer");
680             for (int i = 0; i < kproducerNodes.count() && convert != KMessageBox::No; ++i) {
681                 QDomElement kproducer = kproducerNodes.at(i).toElement();
682                 if (kproducer.attribute("type").toInt() == TEXT) {
683                     QDomDocument data;
684                     data.setContent(kproducer.attribute("xmldata"));
685                     QDomNodeList items = data.firstChild().childNodes();
686                     for (int j = 0; j < items.count() && convert != KMessageBox::No; ++j) {
687                         if (items.at(j).attributes().namedItem("type").nodeValue() == "QGraphicsTextItem") {
688                             QDomNamedNodeMap textProperties = items.at(j).namedItem("content").attributes();
689                             if (textProperties.namedItem("font-pixel-size").isNull() && !textProperties.namedItem("font-size").isNull()) {
690                                 // Ask the user if he wants to convert
691                                 if (convert != KMessageBox::Yes && convert != KMessageBox::No)
692                                     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"));
693                                 if (convert == KMessageBox::Yes) {
694                                     QFont font;
695                                     font.setPointSize(textProperties.namedItem("font-size").nodeValue().toInt());
696                                     QDomElement content = items.at(j).namedItem("content").toElement();
697                                     content.setAttribute("font-pixel-size", QFontInfo(font).pixelSize());
698                                     content.removeAttribute("font-size");
699                                     kproducer.setAttribute("xmldata", data.toString());
700                                     /*
701                                      * You may be tempted to delete the preview file
702                                      * to force its recreation: bad idea (see
703                                      * http://www.kdenlive.org/mantis/view.php?id=749)
704                                      */
705                                 }
706                             }
707                         }
708                     }
709                 }
710             }
711         }
712
713         // Fill the <documentproperties /> element
714         QDomElement docProperties = infoXml.firstChildElement("documentproperties");
715         if (docProperties.isNull()) {
716             docProperties = m_doc.createElement("documentproperties");
717             docProperties.setAttribute("zonein", infoXml.attribute("zonein"));
718             docProperties.setAttribute("zoneout", infoXml.attribute("zoneout"));
719             docProperties.setAttribute("zoom", infoXml.attribute("zoom"));
720             infoXml.appendChild(docProperties);
721         }
722     }
723
724     // The document has been converted: mark it as modified
725     infoXml.setAttribute("version", currentVersion);
726     m_modified = true;
727
728     return true;
729 }
730
731 QString DocumentValidator::colorToString(const QColor& c)
732 {
733     QString ret = "%1,%2,%3,%4";
734     ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
735     return ret;
736 }
737
738 bool DocumentValidator::isProject() const
739 {
740     QDomNode infoXmlNode = m_doc.elementsByTagName("kdenlivedoc").at(0);
741     return !infoXmlNode.isNull();
742 }
743
744 bool DocumentValidator::isModified() const
745 {
746     return m_modified;
747 }