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