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