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