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