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