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