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