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