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