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