]> git.sesse.net Git - kdenlive/blob - src/kdenlivedoc.cpp
Fix issue 0000237: Unable to open Kdenlive 0.5 project (http://www.kdenlive.org/manti...
[kdenlive] / src / kdenlivedoc.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 #include <QCryptographicHash>
21 #include <QFile>
22
23 #include <KDebug>
24 #include <KStandardDirs>
25 #include <KMessageBox>
26 #include <KLocale>
27 #include <KFileDialog>
28 #include <KIO/NetAccess>
29 #include <KIO/CopyJob>
30 #include <KApplication>
31
32
33 #include <mlt++/Mlt.h>
34
35 #include "kdenlivedoc.h"
36 #include "docclipbase.h"
37 #include "profilesdialog.h"
38 #include "kdenlivesettings.h"
39 #include "renderer.h"
40 #include "clipmanager.h"
41 #include "addfoldercommand.h"
42 #include "editfoldercommand.h"
43 #include "titlewidget.h"
44 #include "mainwindow.h"
45
46
47 KdenliveDoc::KdenliveDoc(const KUrl &url, const KUrl &projectFolder, QUndoGroup *undoGroup, const QString &profileName, const QPoint tracks, MainWindow *parent): QObject(parent), m_render(NULL), m_url(url), m_projectFolder(projectFolder), m_commandStack(new QUndoStack(undoGroup)), m_modified(false), m_documentLoadingProgress(0), m_documentLoadingStep(0.0), m_startPos(0), m_zoom(7), m_autosave(NULL) {
48     m_clipManager = new ClipManager(this);
49     m_autoSaveTimer = new QTimer(this);
50     m_autoSaveTimer->setSingleShot(true);
51     if (!url.isEmpty()) {
52         QString tmpFile;
53         if (KIO::NetAccess::download(url.path(), tmpFile, parent)) {
54             QFile file(tmpFile);
55             m_document.setContent(&file, false);
56             file.close();
57             QDomNode infoXmlNode = m_document.elementsByTagName("kdenlivedoc").at(0);
58             QDomNode westley = m_document.elementsByTagName("westley").at(0);
59             if (!infoXmlNode.isNull()) {
60                 QDomElement infoXml = infoXmlNode.toElement();
61                 double version = infoXml.attribute("version").toDouble();
62
63                 // Upgrade old Kdenlive documents to current version
64                 if (!convertDocument(version)) {
65                     KMessageBox::sorry(parent, i18n("This project type is unsupported and can't be loaded."), i18n("Unable to open project"));
66                     m_document = createEmptyDocument(tracks.x(), tracks.y());
67                     setProfilePath(profileName);
68                 } else {
69                     /*
70                      * read again <kdenlivedoc> and <westley> to get all the new
71                      * stuff (convertDocument() can now do anything without breaking
72                      * document loading)
73                      */
74                     infoXmlNode = m_document.elementsByTagName("kdenlivedoc").at(0);
75                     infoXml = infoXmlNode.toElement();
76                     version = infoXml.attribute("version").toDouble();
77                     westley = m_document.elementsByTagName("westley").at(0);
78
79                     QString profilePath = infoXml.attribute("profile");
80                     QString projectFolderPath = infoXml.attribute("projectfolder");
81                     if (!projectFolderPath.isEmpty()) m_projectFolder = KUrl(projectFolderPath);
82                     if (m_projectFolder.isEmpty()) m_projectFolder = KUrl(KdenliveSettings::defaultprojectfolder());
83                     m_startPos = infoXml.attribute("position").toInt();
84                     m_zoom = infoXml.attribute("zoom", "7").toInt();
85                     setProfilePath(profilePath);
86
87                     // Build tracks
88                     QString tracks = infoXml.attribute("tracks");
89                     TrackInfo videoTrack;
90                     videoTrack.type = VIDEOTRACK;
91                     videoTrack.isMute = false;
92                     videoTrack.isBlind = false;
93
94                     TrackInfo audioTrack;
95                     audioTrack.type = AUDIOTRACK;
96                     audioTrack.isMute = false;
97                     audioTrack.isBlind = true;
98                     for (int i = 0; i < tracks.size(); i++) {
99                         if (tracks.data()[i] == 'v') m_tracksList.append(videoTrack);
100                         else m_tracksList.append(audioTrack);
101                     }
102
103
104                     QDomElement e;
105                     QDomNodeList producers = m_document.elementsByTagName("producer");
106                     QDomNodeList infoproducers = m_document.elementsByTagName("kdenlive_producer");
107                     const int max = producers.count();
108                     const int infomax = infoproducers.count();
109
110                     if (max > 0) {
111                         m_documentLoadingStep = 100.0 / (max + infomax + m_document.elementsByTagName("entry").count());
112                         parent->slotGotProgressInfo(i18n("Loading project clips"), (int) m_documentLoadingProgress);
113                     }
114
115                     for (int i = 0; i < max; i++) {
116                         e = producers.item(i).cloneNode().toElement();
117                         if (m_documentLoadingStep > 0) {
118                             m_documentLoadingProgress += m_documentLoadingStep;
119                             parent->slotGotProgressInfo(QString(), (int) m_documentLoadingProgress);
120                             //qApp->processEvents();
121                         }
122                         QString prodId = e.attribute("id");
123                         if (!e.isNull() && prodId != "black" && !prodId.startsWith("slowmotion")/*&& prodId.toInt() > 0*/) {
124                             // addClip(e, prodId, false);
125                             kDebug() << "// PROD: " << prodId;
126                         }
127                     }
128
129                     for (int i = 0; i < infomax; i++) {
130                         e = infoproducers.item(i).cloneNode().toElement();
131                         if (m_documentLoadingStep > 0) {
132                             m_documentLoadingProgress += m_documentLoadingStep;
133                             parent->slotGotProgressInfo(QString(), (int) m_documentLoadingProgress);
134                             //qApp->processEvents();
135                         }
136                         QString prodId = e.attribute("id");
137                         if (!e.isNull() && prodId != "black" && !prodId.startsWith("slowmotion")) {
138                             e.setTagName("producer");
139                             addClipInfo(e, prodId);
140                             kDebug() << "// NLIVE PROD: " << prodId;
141                         }
142                     }
143
144                     QDomNode markers = m_document.elementsByTagName("markers").at(0);
145                     if (!markers.isNull()) {
146                         QDomNodeList markerslist = markers.childNodes();
147                         int maxchild = markerslist.count();
148                         for (int k = 0; k < maxchild; k++) {
149                             e = markerslist.at(k).toElement();
150                             if (e.tagName() == "marker") {
151                                 m_clipManager->getClipById(e.attribute("id"))->addSnapMarker(GenTime(e.attribute("time").toDouble()), e.attribute("comment"));
152                             }
153                         }
154                         westley.removeChild(markers);
155                     }
156                     m_document.removeChild(infoXmlNode);
157
158                     kDebug() << "Reading file: " << url.path() << ", found clips: " << producers.count();
159                 }
160             } else {
161                 parent->slotGotProgressInfo(i18n("File %1 is not a Kdenlive project file."), 100);
162                 kWarning() << "  NO KDENLIVE INFO FOUND IN FILE: " << url.path();
163                 m_document = createEmptyDocument(tracks.x(), tracks.y());
164                 setProfilePath(profileName);
165             }
166             KIO::NetAccess::removeTempFile(tmpFile);
167         } else {
168             KMessageBox::error(parent, KIO::NetAccess::lastErrorString());
169             parent->slotGotProgressInfo(i18n("File %1 is not a Kdenlive project file."), 100);
170             m_document = createEmptyDocument(tracks.x(), tracks.y());
171             setProfilePath(profileName);
172         }
173     } else {
174         m_document = createEmptyDocument(tracks.x(), tracks.y());
175         setProfilePath(profileName);
176     }
177     if (m_projectFolder.isEmpty()) m_projectFolder = KUrl(KdenliveSettings::defaultprojectfolder());
178
179     // make sure that the necessary folders exist
180     KStandardDirs::makeDir(m_projectFolder.path() + "/titles/");
181     KStandardDirs::makeDir(m_projectFolder.path() + "/thumbs/");
182
183     m_scenelist = m_document.toString();
184     kDebug() << "KDEnnlive document, init timecode: " << m_fps;
185     if (m_fps == 30000.0 / 1001.0) m_timecode.setFormat(30, true);
186     else m_timecode.setFormat((int) m_fps);
187
188     connect(m_autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
189 }
190
191 KdenliveDoc::~KdenliveDoc() {
192     delete m_commandStack;
193     delete m_clipManager;
194     delete m_autoSaveTimer;
195     if (m_autosave) {
196         m_autosave->remove();
197         delete m_autosave;
198     }
199 }
200
201 QDomDocument KdenliveDoc::createEmptyDocument(const int videotracks, const int audiotracks) {
202     // Creating new document
203     QDomDocument doc;
204     QDomElement westley = doc.createElement("westley");
205     doc.appendChild(westley);
206
207
208     TrackInfo videoTrack;
209     videoTrack.type = VIDEOTRACK;
210     videoTrack.isMute = false;
211     videoTrack.isBlind = false;
212
213     TrackInfo audioTrack;
214     audioTrack.type = AUDIOTRACK;
215     audioTrack.isMute = false;
216     audioTrack.isBlind = true;
217
218     QDomElement tractor = doc.createElement("tractor");
219     tractor.setAttribute("id", "maintractor");
220     QDomElement multitrack = doc.createElement("multitrack");
221     QDomElement playlist = doc.createElement("playlist");
222     playlist.setAttribute("id", "black_track");
223     westley.appendChild(playlist);
224
225
226     // create playlists
227     int total = audiotracks + videotracks + 1;
228
229     for (int i = 1; i < total; i++) {
230         QDomElement playlist = doc.createElement("playlist");
231         playlist.setAttribute("id", "playlist" + QString::number(i));
232         westley.appendChild(playlist);
233     }
234
235     QDomElement track0 = doc.createElement("track");
236     track0.setAttribute("producer", "black_track");
237     tractor.appendChild(track0);
238
239     // create audio tracks
240     for (int i = 1; i < audiotracks + 1; i++) {
241         QDomElement track = doc.createElement("track");
242         track.setAttribute("producer", "playlist" + QString::number(i));
243         track.setAttribute("hide", "video");
244         tractor.appendChild(track);
245         m_tracksList.append(audioTrack);
246     }
247
248     // create video tracks
249     for (int i = audiotracks + 1; i < total; i++) {
250         QDomElement track = doc.createElement("track");
251         track.setAttribute("producer", "playlist" + QString::number(i));
252         tractor.appendChild(track);
253         m_tracksList.append(videoTrack);
254     }
255
256     for (uint i = 2; i < total ; i++) {
257         QDomElement transition = doc.createElement("transition");
258         transition.setAttribute("always_active", "1");
259
260         QDomElement property = doc.createElement("property");
261         property.setAttribute("name", "a_track");
262         QDomText value = doc.createTextNode(QString::number(1));
263         property.appendChild(value);
264         transition.appendChild(property);
265
266         property = doc.createElement("property");
267         property.setAttribute("name", "b_track");
268         value = doc.createTextNode(QString::number(i));
269         property.appendChild(value);
270         transition.appendChild(property);
271
272         property = doc.createElement("property");
273         property.setAttribute("name", "mlt_service");
274         value = doc.createTextNode("mix");
275         property.appendChild(value);
276         transition.appendChild(property);
277
278         property = doc.createElement("property");
279         property.setAttribute("name", "combine");
280         value = doc.createTextNode("1");
281         property.appendChild(value);
282         transition.appendChild(property);
283
284         property = doc.createElement("property");
285         property.setAttribute("name", "internal_added");
286         value = doc.createTextNode("237");
287         property.appendChild(value);
288         transition.appendChild(property);
289         tractor.appendChild(transition);
290     }
291     westley.appendChild(tractor);
292     return doc;
293 }
294
295
296 void KdenliveDoc::syncGuides(QList <Guide *> guides) {
297     QDomDocument doc;
298     QDomElement e;
299     m_guidesXml.clear();
300     m_guidesXml = doc.createElement("guides");
301
302     for (int i = 0; i < guides.count(); i++) {
303         e = doc.createElement("guide");
304         e.setAttribute("time", guides.at(i)->position().ms() / 1000);
305         e.setAttribute("comment", guides.at(i)->label());
306         m_guidesXml.appendChild(e);
307     }
308     emit guidesUpdated();
309 }
310
311 QDomElement KdenliveDoc::guidesXml() const {
312     return m_guidesXml;
313 }
314
315 void KdenliveDoc::slotAutoSave() {
316     if (m_render && m_autosave) {
317         if (!m_autosave->isOpen() && !m_autosave->open(QIODevice::ReadWrite)) {
318             // show error: could not open the autosave file
319             kDebug() << "ERROR; CANNOT CREATE AUTOSAVE FILE";
320         }
321         kDebug() << "// AUTOSAVE FILE: " << m_autosave->fileName();
322         QDomDocument doc;
323         doc.setContent(m_render->sceneList());
324         saveSceneList(m_autosave->fileName(), doc);
325     }
326 }
327
328 void KdenliveDoc::setZoom(int factor) {
329     m_zoom = factor;
330 }
331
332 int KdenliveDoc::zoom() const {
333     return m_zoom;
334 }
335
336 bool KdenliveDoc::convertDocument(double version) {
337     kDebug() << "Opening a document with version " << version;
338
339     // Opening a old Kdenlive document
340     if (version == 0.5 || version == 0.7) {
341         kDebug() << "Unable to open document with version " << version;
342         // TODO: convert 0.7 (0.5?) files to the new document format.
343         return FALSE;
344     }
345
346     if (version == 0.8) {
347         // Add the tracks information
348         QString tracksOrder;
349         QDomNodeList tracks = m_document.elementsByTagName("track");
350         int max = tracks.count();
351         for (int i = 0; i < max; i++) {
352             QDomElement t = tracks.at(i).toElement();
353             if (t.attribute("hide") == "video")
354                 tracksOrder.append('a');
355             else if (t.attribute("producer") != "black_track")
356                 tracksOrder.append('v');
357         }
358         QDomNode kdenlivedoc = m_document.elementsByTagName("kdenlivedoc").at(0);
359         QDomElement infoXml = kdenlivedoc.toElement();
360         QString currentTrackOrder = infoXml.attribute("tracks");
361         if (currentTrackOrder.isEmpty()) infoXml.setAttribute("tracks", tracksOrder);
362
363         return TRUE;
364     }
365
366     QDomNode westley = m_document.elementsByTagName("westley").at(1);
367     QDomNode tractor = m_document.elementsByTagName("tractor").at(0);
368     QDomNode kdenlivedoc = m_document.elementsByTagName("kdenlivedoc").at(0);
369     QDomElement kdenlivedoc_old = kdenlivedoc.cloneNode(true).toElement(); // Needed for folders
370     QDomNode multitrack = m_document.elementsByTagName("multitrack").at(0);
371     QDomNodeList playlists = m_document.elementsByTagName("playlist");
372
373     QDomNode props = m_document.elementsByTagName("properties").at(0).toElement();
374     QString profile = props.toElement().attribute("videoprofile");
375     m_startPos = props.toElement().attribute("timeline_position").toInt();
376     if (profile == "dv_wide") profile = "dv_pal_wide";
377
378     // move playlists outside of tractor and add the tracks instead
379     int max = playlists.count();
380     for (int i = 0; i < max; i++) {
381         QDomNode n = playlists.at(i);
382         westley.insertBefore(n, QDomNode());
383         QDomElement pl = n.toElement();
384         QDomElement track = m_document.createElement("track");
385         QString trackType = pl.attribute("hide");
386         if (!trackType.isEmpty())
387             track.setAttribute("hide", trackType);
388         QString playlist_id =  pl.attribute("id");
389         if (playlist_id.isEmpty()) {
390             playlist_id = "black_track";
391             pl.setAttribute("id", playlist_id);
392         }
393         track.setAttribute("producer", playlist_id);
394         //tractor.appendChild(track);
395 #define KEEP_TRACK_ORDER 1
396 #ifdef KEEP_TRACK_ORDER
397         tractor.insertAfter(track, QDomNode());
398 #else
399         // Insert the new track in an order that hopefully matches the 3 video, then 2 audio tracks of Kdenlive 0.7.0
400         // insertion sort - O( tracks*tracks )
401         // Note, this breaks _all_ transitions - but you can move them up and down afterwards.
402         QDomElement tractor_elem = tractor.toElement();
403         if (! tractor_elem.isNull()) {
404             QDomNodeList tracks = tractor_elem.elementsByTagName("track");
405             int size = tracks.size();
406             if (size == 0) {
407                 tractor.insertAfter(track, QDomNode());
408             } else {
409                 bool inserted = false;
410                 for (int i = 0; i < size; ++i) {
411                     QDomElement track_elem = tracks.at(i).toElement();
412                     if (track_elem.isNull()) {
413                         tractor.insertAfter(track, QDomNode());
414                         inserted = true;
415                         break;
416                     } else {
417                         kDebug() << "playlist_id: " << playlist_id << " producer:" << track_elem.attribute("producer");
418                         if (playlist_id < track_elem.attribute("producer")) {
419                             tractor.insertBefore(track, track_elem);
420                             inserted = true;
421                             break;
422                         }
423                     }
424                 }
425                 // Reach here, no insertion, insert last
426                 if (!inserted) {
427                     tractor.insertAfter(track, QDomNode());
428                 }
429             }
430         } else {
431             kWarning() << "tractor was not a QDomElement";
432             tractor.insertAfter(track, QDomNode());
433         }
434 #endif
435     }
436     tractor.removeChild(multitrack);
437
438     // write tracks order now that they've been sorted
439     QString tracksOrder;
440     QDomNodeList tracks = m_document.elementsByTagName("track");
441     for (int i = 0; i < tracks.count(); ++i) {
442         QDomElement track = tracks.at(i).toElement();
443         if (track.attribute("hide") == "video")
444             tracksOrder.append('a');
445         else if (track.attribute("producer") != "black_track")
446             tracksOrder.append('v');
447     }
448
449     // audio track mixing transitions should not be added to track view, so add required attribute
450     QDomNodeList transitions = m_document.elementsByTagName("transition");
451     max = transitions.count();
452     for (int i = 0; i < max; i++) {
453         QDomElement tr = transitions.at(i).toElement();
454         if (tr.attribute("combine") == "1" && tr.attribute("mlt_service") == "mix") {
455             QDomElement property = m_document.createElement("property");
456             property.setAttribute("name", "internal_added");
457             QDomText value = m_document.createTextNode("237");
458             property.appendChild(value);
459             tr.appendChild(property);
460         } else {
461             // convert transition
462             QDomNamedNodeMap attrs = tr.attributes();
463             for (unsigned int j = 0; j < attrs.count(); j++) {
464                 QString attrName = attrs.item(j).nodeName();
465                 if (attrName != "in" && attrName != "out" && attrName != "id") {
466                     QDomElement property = m_document.createElement("property");
467                     property.setAttribute("name", attrName);
468                     QDomText value = m_document.createTextNode(attrs.item(j).nodeValue());
469                     property.appendChild(value);
470                     tr.appendChild(property);
471                 }
472             }
473         }
474     }
475
476     // move transitions after tracks
477     for (int i = 0; i < max; i++) {
478         tractor.insertAfter(transitions.at(0), QDomNode());
479     }
480
481     // Fix filters format
482     QDomNodeList entries = m_document.elementsByTagName("entry");
483     max = entries.count();
484     for (int i = 0; i < max; i++) {
485         QString last_id;
486         int effectix = 0;
487         QDomNode m = entries.at(i).firstChild();
488         while (!m.isNull()) {
489             if (m.toElement().tagName() == "filter") {
490                 QDomElement filt = m.toElement();
491                 QDomNamedNodeMap attrs = filt.attributes();
492                 QString current_id = filt.attribute("kdenlive_id");
493                 if (current_id != last_id) {
494                     effectix++;
495                     last_id = current_id;
496                 }
497                 QDomElement e = m_document.createElement("property");
498                 e.setAttribute("name", "kdenlive_ix");
499                 QDomText value = m_document.createTextNode(QString::number(effectix));
500                 e.appendChild(value);
501                 filt.appendChild(e);
502                 for (int j = 0; j < attrs.count(); j++) {
503                     QDomAttr a = attrs.item(j).toAttr();
504                     if (!a.isNull()) {
505                         kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
506                         QDomElement e = m_document.createElement("property");
507                         e.setAttribute("name", a.name());
508                         QDomText value = m_document.createTextNode(a.value());
509                         e.appendChild(value);
510                         filt.appendChild(e);
511
512                     }
513                 }
514             }
515             m = m.nextSibling();
516         }
517     }
518
519     /*
520         QDomNodeList filters = m_document.elementsByTagName("filter");
521         max = filters.count();
522         QString last_id;
523         int effectix = 0;
524         for (int i = 0; i < max; i++) {
525             QDomElement filt = filters.at(i).toElement();
526             QDomNamedNodeMap attrs = filt.attributes();
527      QString current_id = filt.attribute("kdenlive_id");
528      if (current_id != last_id) {
529          effectix++;
530          last_id = current_id;
531      }
532      QDomElement e = m_document.createElement("property");
533             e.setAttribute("name", "kdenlive_ix");
534             QDomText value = m_document.createTextNode(QString::number(1));
535             e.appendChild(value);
536             filt.appendChild(e);
537             for (int j = 0; j < attrs.count(); j++) {
538                 QDomAttr a = attrs.item(j).toAttr();
539                 if (!a.isNull()) {
540                     kDebug() << " FILTER; adding :" << a.name() << ":" << a.value();
541                     QDomElement e = m_document.createElement("property");
542                     e.setAttribute("name", a.name());
543                     QDomText value = m_document.createTextNode(a.value());
544                     e.appendChild(value);
545                     filt.appendChild(e);
546                 }
547             }
548         }*/
549
550     // fix slowmotion
551     QDomNodeList producers = westley.toElement().elementsByTagName("producer");
552     max = producers.count();
553     for (int i = 0; i < max; i++) {
554         QDomElement prod = producers.at(i).toElement();
555         if (prod.attribute("mlt_service") == "framebuffer") {
556             QString slowmotionprod = prod.attribute("resource");
557             slowmotionprod.replace(':', '?');
558             kDebug() << "// FOUND WRONG SLOWMO, new: " << slowmotionprod;
559             prod.setAttribute("resource", slowmotionprod);
560         }
561     }
562     // move producers to correct place, markers to a global list, fix clip descriptions
563     QDomElement markers = m_document.createElement("markers");
564     // This will get the westley producers:
565     producers = m_document.elementsByTagName("producer");
566     max = producers.count();
567     for (int i = 0; i < max; i++) {
568         QDomElement prod = producers.at(0).toElement();
569         // add resource also as a property (to allow path correction in setNewResource())
570         // TODO: will it work with slowmotion? needs testing
571         if (!prod.attribute("resource").isEmpty()) {
572             QDomElement prop_resource = m_document.createElement("property");
573             prop_resource.setAttribute("name", "resource");
574             QDomText resource = m_document.createTextNode(prod.attribute("resource"));
575             prop_resource.appendChild(resource);
576             prod.appendChild(prop_resource);
577         }
578         QDomNode m = prod.firstChild();
579         if (!m.isNull()) {
580             if (m.toElement().tagName() == "markers") {
581                 QDomNodeList prodchilds = m.childNodes();
582                 int maxchild = prodchilds.count();
583                 for (int k = 0; k < maxchild; k++) {
584                     QDomElement mark = prodchilds.at(0).toElement();
585                     mark.setAttribute("id", prod.attribute("id"));
586                     markers.insertAfter(mark, QDomNode());
587                 }
588                 prod.removeChild(m);
589             } else if (prod.attribute("type").toInt() == TEXT) {
590                 // convert title clip
591                 if (m.toElement().tagName() == "textclip") {
592                     QDomDocument tdoc;
593                     QDomElement titleclip = m.toElement();
594                     QDomElement title = tdoc.createElement("kdenlivetitle");
595                     tdoc.appendChild(title);
596                     QDomNodeList objects = titleclip.childNodes();
597                     int maxchild = objects.count();
598                     for (int k = 0; k < maxchild; k++) {
599                         QString objectxml;
600                         QDomElement ob = objects.at(k).toElement();
601                         if (ob.attribute("type") == "3") {
602                             // text object - all of this goes into "xmldata"...
603                             QDomElement item = tdoc.createElement("item");
604                             item.setAttribute("z-index", ob.attribute("z"));
605                             item.setAttribute("type", "QGraphicsTextItem");
606                             QDomElement position = tdoc.createElement("position");
607                             position.setAttribute("x", ob.attribute("x"));
608                             position.setAttribute("y", ob.attribute("y"));
609                             QDomElement content = tdoc.createElement("content");
610                             content.setAttribute("font", ob.attribute("font_family"));
611                             content.setAttribute("font-size", ob.attribute("font_size"));
612                             content.setAttribute("font-bold", ob.attribute("bold"));
613                             content.setAttribute("font-italic", ob.attribute("italic"));
614                             content.setAttribute("font-underline", ob.attribute("underline"));
615                             QString col = ob.attribute("color");
616                             QColor c(col);
617                             content.setAttribute("font-color", colorToString(c));
618                             // todo: These fields are missing from the newly generated xmldata:
619                             // transform, startviewport, endviewport, background
620
621                             QDomText conttxt = tdoc.createTextNode(ob.attribute("text"));
622                             content.appendChild(conttxt);
623                             item.appendChild(position);
624                             item.appendChild(content);
625                             title.appendChild(item);
626                         } else if (ob.attribute("type") == "5") {
627                             // rectangle object
628                             QDomElement item = tdoc.createElement("item");
629                             item.setAttribute("z-index", ob.attribute("z"));
630                             item.setAttribute("type", "QGraphicsRectItem");
631                             QDomElement position = tdoc.createElement("position");
632                             position.setAttribute("x", ob.attribute("x"));
633                             position.setAttribute("y", ob.attribute("y"));
634                             QDomElement content = tdoc.createElement("content");
635                             QString col = ob.attribute("color");
636                             QColor c(col);
637                             content.setAttribute("brushcolor", colorToString(c));
638                             QString rect = "0,0,";
639                             rect.append(ob.attribute("width"));
640                             rect.append(",");
641                             rect.append(ob.attribute("height"));
642                             content.setAttribute("rect", rect);
643                             item.appendChild(position);
644                             item.appendChild(content);
645                             title.appendChild(item);
646                         }
647                     }
648                     prod.setAttribute("xmldata", tdoc.toString());
649                     // mbd todo: This clearly does not work, as every title gets the same name - trying to leave it empty
650                     // QStringList titleInfo = TitleWidget::getFreeTitleInfo(projectFolder());
651                     // prod.setAttribute("titlename", titleInfo.at(0));
652                     // prod.setAttribute("resource", titleInfo.at(1));
653                     //kDebug()<<"TITLE DATA:\n"<<tdoc.toString();
654                     prod.removeChild(m);
655                 } // End conversion of title clips.
656
657             } else if (m.isText()) {
658                 QString comment = m.nodeValue();
659                 if (!comment.isEmpty()) {
660                     prod.setAttribute("description", comment);
661                 }
662                 prod.removeChild(m);
663             }
664         }
665         int duration = prod.attribute("duration").toInt();
666         if (duration > 0) prod.setAttribute("out", QString::number(duration));
667         // The clip goes back in, but text clips should not go back in, at least not modified
668         westley.insertBefore(prod, QDomNode());
669
670     }
671
672     QDomNode westley0 = m_document.elementsByTagName("westley").at(0);
673     if (!markers.firstChild().isNull()) westley0.appendChild(markers);
674
675
676     // Convert as much of the kdenlivedoc as possible. Use the producer in westley
677     // First, remove the old stuff from westley, and add a new empty one
678     // Also, track the max id in order to use it for the adding of groups/folders
679     int max_kproducer_id = 0;
680     westley0.removeChild(kdenlivedoc);
681     QDomElement kdenlivedoc_new = m_document.createElement("kdenlivedoc");
682     kdenlivedoc_new.setAttribute("profile", profile);
683     kdenlivedoc_new.setAttribute("tracks", tracksOrder);
684     // Add all the producers that has a ressource in westley
685     QDomElement westley_element = westley0.toElement();
686     if (westley_element.isNull()) {
687         kWarning() << "westley0 element in document was not a QDomElement - unable to add producers to new kdenlivedoc";
688     } else {
689         QDomNodeList wproducers = westley_element.elementsByTagName("producer");
690         int kmax = wproducers.count();
691         for (int i = 0; i < kmax; i++) {
692             QDomElement wproducer = wproducers.at(i).toElement();
693             if (wproducer.isNull()) {
694                 kWarning() << "Found producer in westley0, that was not a QDomElement";
695             } else {
696                 // We have to do slightly different things, depending on the type
697                 kDebug() << "Converting producer element with type" << wproducer.attribute("type");
698                 if (wproducer.attribute("type").toInt() == TEXT) {
699                     kDebug() << "Found TEXT element in producer" << endl;
700                     QDomElement kproducer = wproducer.cloneNode(true).toElement();
701                     kproducer.setTagName("kdenlive_producer");
702                     kdenlivedoc_new.appendChild(kproducer);
703                     // TODO: Perhaps needs some more changes here to "frequency", aspect ratio as a float, frame_size, channels, and later, ressource and title name
704                 } else {
705                     QDomElement kproducer = m_document.createElement("kdenlive_producer");
706                     kproducer.setAttribute("id", wproducer.attribute("id"));
707                     if (!wproducer.attribute("description").isEmpty())
708                         kproducer.setAttribute("description", wproducer.attribute("description"));
709                     kproducer.setAttribute("resource", wproducer.attribute("resource"));
710                     kproducer.setAttribute("type", wproducer.attribute("type"));
711                     // Testing fix for 358
712                     if (!wproducer.attribute("aspect_ratio").isEmpty()) {
713                         kproducer.setAttribute("aspect_ratio", wproducer.attribute("aspect_ratio"));
714                     }
715                     if (!wproducer.attribute("source_fps").isEmpty()) {
716                         kproducer.setAttribute("fps", wproducer.attribute("source_fps"));
717                     }
718                     if (!wproducer.attribute("length").isEmpty()) {
719                         kproducer.setAttribute("duration", wproducer.attribute("length"));
720                     }
721                     kdenlivedoc_new.appendChild(kproducer);
722                 }
723                 if (wproducer.attribute("id").toInt() > max_kproducer_id) {
724                     max_kproducer_id = wproducer.attribute("id").toInt();
725                 }
726             }
727         }
728     }
729 #define LOOKUP_FOLDER 1
730 #ifdef LOOKUP_FOLDER
731     // Look through all the folder elements of the old doc, for each folder, for each producer,
732     // get the id, look it up in the new doc, set the groupname and groupid
733     // Note, this does not work at the moment - at least one folders shows up missing, and clips with no folder
734     // does not show up.
735     //    QDomElement kdenlivedoc_old = kdenlivedoc.toElement();
736     if (!kdenlivedoc_old.isNull()) {
737         QDomNodeList folders = kdenlivedoc_old.elementsByTagName("folder");
738         int fsize = folders.size();
739         int groupId = max_kproducer_id + 1; // Start at +1 of max id of the kdenlive_producers
740         for (int i = 0; i < fsize; ++i) {
741             QDomElement folder = folders.at(i).toElement();
742             if (!folder.isNull()) {
743                 QString groupName = folder.attribute("name");
744                 kDebug() << "groupName: " << groupName << " with groupId: " << groupId;
745                 QDomNodeList fproducers = folder.elementsByTagName("producer");
746                 int psize = fproducers.size();
747                 for (int j = 0; j < psize; ++j) {
748                     QDomElement fproducer = fproducers.at(j).toElement();
749                     if (!fproducer.isNull()) {
750                         QString id = fproducer.attribute("id");
751                         // This is not very effective, but compared to loading the clips, its a breeze
752                         QDomNodeList kdenlive_producers = kdenlivedoc_new.elementsByTagName("kdenlive_producer");
753                         int kpsize = kdenlive_producers.size();
754                         for (int k = 0; k < kpsize; ++k) {
755                             QDomElement kproducer = kdenlive_producers.at(k).toElement(); // Its an element for sure
756                             if (id == kproducer.attribute("id")) {
757                                 // We do not check that it already is part of a folder
758                                 kproducer.setAttribute("groupid", groupId);
759                                 kproducer.setAttribute("groupname", groupName);
760                                 break;
761                             }
762                         }
763                     }
764                 }
765                 ++groupId;
766             }
767         }
768     }
769 #endif
770     westley0.appendChild(kdenlivedoc_new);
771
772     QDomNodeList elements = westley.childNodes();
773     max = elements.count();
774     for (int i = 0; i < max; i++) {
775         QDomElement prod = elements.at(0).toElement();
776         westley0.insertAfter(prod, QDomNode());
777     }
778
779     westley0.removeChild(westley);
780
781     // experimental and probably slow
782     // adds <avfile /> information to <kdenlive_producer />
783     QDomNodeList kproducers = m_document.elementsByTagName("kdenlive_producer");
784     QDomNodeList avfiles = kdenlivedoc_old.elementsByTagName("avfile");
785     kDebug() << "found" << avfiles.count() << "<avfile />s and" << kproducers.count() << "<kdenlive_producer />s";
786     for (int i = 0; i < avfiles.count(); ++i) {
787         QDomElement avfile = avfiles.at(i).toElement();
788         QDomElement kproducer = QDomElement();
789         if (avfile.isNull())
790             kWarning() << "found an <avfile /> that is not a QDomElement";
791         else {
792             QString id = avfile.attribute("id");
793             // this is horrible, must be rewritten, it's just for test
794             for (int j = 0; j < kproducers.count(); ++j) {
795                 //kDebug() << "checking <kdenlive_producer /> with id" << kproducers.at(j).toElement().attribute("id");
796                 if (kproducers.at(j).toElement().attribute("id") == id) {
797                     kproducer = kproducers.at(j).toElement();
798                     break;
799                 }
800             }
801             if (kproducer == QDomElement())
802                 kWarning() << "no match for <avfile /> with id =" << id;
803             else {
804                 //kDebug() << "ready to set additional <avfile />'s attributes (id =" << id << ")";
805                 kproducer.setAttribute("channels", avfile.attribute("channels"));
806                 kproducer.setAttribute("duration", avfile.attribute("duration"));
807                 kproducer.setAttribute("frame_size", avfile.attribute("width") + "x" + avfile.attribute("height"));
808                 kproducer.setAttribute("frequency", avfile.attribute("frequency"));
809                 if (kproducer.attribute("description").isEmpty() && !avfile.attribute("description").isEmpty())
810                     kproducer.setAttribute("description", avfile.attribute("description"));
811             }
812         }
813     }
814
815     //kDebug() << "/////////////////  CONVERTED DOC:";
816     //kDebug() << m_document.toString();
817     /*
818     QFile file( "converted.kdenlive" );
819     if ( file.open( QIODevice::WriteOnly ) ) {
820       QTextStream stream( &file );
821       stream << m_document.toString();
822       file.close();
823     } else {
824       kDebug() << "Unable to dump file to converted.kdenlive";
825     }
826     */
827     //kDebug() << "/////////////////  END CONVERTED DOC:";
828
829     return TRUE;
830 }
831
832 QString KdenliveDoc::colorToString(const QColor& c) {
833     QString ret = "%1,%2,%3,%4";
834     ret = ret.arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha());
835     return ret;
836 }
837
838 bool KdenliveDoc::saveSceneList(const QString &path, QDomDocument sceneList) {
839     QDomNode wes = sceneList.elementsByTagName("westley").at(0);
840
841     QDomElement addedXml = sceneList.createElement("kdenlivedoc");
842     QDomElement markers = sceneList.createElement("markers");
843     addedXml.setAttribute("version", "0.8");
844     addedXml.setAttribute("profile", profilePath());
845     addedXml.setAttribute("position", m_render->seekPosition().frames(m_fps));
846     addedXml.setAttribute("projectfolder", m_projectFolder.path());
847     addedXml.setAttribute("tracks", getTracksInfo());
848     addedXml.setAttribute("zoom", m_zoom);
849
850     QDomElement e;
851     QList <DocClipBase*> list = m_clipManager->documentClipList();
852     for (int i = 0; i < list.count(); i++) {
853         e = list.at(i)->toXML();
854         e.setTagName("kdenlive_producer");
855         addedXml.appendChild(sceneList.importNode(e, true));
856         QList < CommentedTime > marks = list.at(i)->commentedSnapMarkers();
857         for (int j = 0; j < marks.count(); j++) {
858             QDomElement marker = sceneList.createElement("marker");
859             marker.setAttribute("time", marks.at(j).time().ms() / 1000);
860             marker.setAttribute("comment", marks.at(j).comment());
861             marker.setAttribute("id", e.attribute("id"));
862             markers.appendChild(marker);
863         }
864     }
865     addedXml.appendChild(markers);
866     if (!m_guidesXml.isNull()) addedXml.appendChild(sceneList.importNode(m_guidesXml, true));
867
868     wes.appendChild(addedXml);
869     //wes.appendChild(doc.importNode(kdenliveData, true));
870
871     QFile file(path);
872     if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
873         kWarning() << "//////  ERROR writing to file: " << path;
874         KMessageBox::error(kapp->activeWindow(), i18n("Cannot write to file %1", path));
875         return false;
876     }
877     QTextStream out(&file);
878     out << sceneList.toString();
879     file.close();
880     return true;
881 }
882
883 ClipManager *KdenliveDoc::clipManager() {
884     return m_clipManager;
885 }
886
887 KUrl KdenliveDoc::projectFolder() const {
888     //if (m_projectFolder.isEmpty()) return KUrl(KStandardDirs::locateLocal("appdata", "/projects/"));
889     return m_projectFolder;
890 }
891
892 void KdenliveDoc::setProjectFolder(KUrl url) {
893     if (url == m_projectFolder) return;
894     setModified(true);
895     KStandardDirs::makeDir(url.path());
896     KStandardDirs::makeDir(url.path() + "/titles/");
897     KStandardDirs::makeDir(url.path() + "/thumbs/");
898     if (KMessageBox::questionYesNo(kapp->activeWindow(), i18n("You have changed the project folder. Do you want to copy the cached data from %1 to the new folder %2 ?").arg(m_projectFolder.path(), url.path())) == KMessageBox::Yes) moveProjectData(url);
899     m_projectFolder = url;
900 }
901
902 void KdenliveDoc::moveProjectData(KUrl url) {
903     QList <DocClipBase*> list = m_clipManager->documentClipList();
904     for (int i = 0; i < list.count(); i++) {
905         DocClipBase *clip = list.at(i);
906         if (clip->clipType() == TEXT) {
907             // the image for title clip must be moved
908             KUrl oldUrl = clip->fileURL();
909             KUrl newUrl = KUrl(url.path() + "/titles/" + oldUrl.fileName());
910             KIO::Job *job = KIO::copy(oldUrl, newUrl);
911             if (KIO::NetAccess::synchronousRun(job, 0)) clip->setProperty("resource", newUrl.path());
912         }
913         QString hash = clip->getClipHash();
914         KUrl oldVideoThumbUrl = KUrl(m_projectFolder.path() + "/thumbs/" + hash + ".png");
915         KUrl oldAudioThumbUrl = KUrl(m_projectFolder.path() + "/thumbs/" + hash + ".thumb");
916         if (KIO::NetAccess::exists(oldVideoThumbUrl, KIO::NetAccess::SourceSide, 0)) {
917             KUrl newUrl = KUrl(url.path() + "/thumbs/" + hash + ".png");
918             KIO::Job *job = KIO::copy(oldVideoThumbUrl, newUrl);
919             KIO::NetAccess::synchronousRun(job, 0);
920         }
921         if (KIO::NetAccess::exists(oldAudioThumbUrl, KIO::NetAccess::SourceSide, 0)) {
922             KUrl newUrl = KUrl(url.path() + "/thumbs/" + hash + ".thumb");
923             KIO::Job *job = KIO::copy(oldAudioThumbUrl, newUrl);
924             if (KIO::NetAccess::synchronousRun(job, 0)) clip->refreshThumbUrl();
925         }
926     }
927 }
928
929 QString KdenliveDoc::profilePath() const {
930     return m_profile.path;
931 }
932
933 MltVideoProfile KdenliveDoc::mltProfile() const {
934     return m_profile;
935 }
936
937 void KdenliveDoc::setProfilePath(QString path) {
938     if (path.isEmpty()) path = KdenliveSettings::default_profile();
939     if (path.isEmpty()) path = "dv_pal";
940     m_profile = ProfilesDialog::getVideoProfile(path);
941     KdenliveSettings::setProject_display_ratio((double) m_profile.display_aspect_num / m_profile.display_aspect_den);
942     m_fps = (double) m_profile.frame_rate_num / m_profile.frame_rate_den;
943     m_width = m_profile.width;
944     m_height = m_profile.height;
945     kDebug() << "KDEnnlive document, init timecode from path: " << path << ",  " << m_fps;
946     if (m_fps == 30000.0 / 1001.0) m_timecode.setFormat(30, true);
947     else m_timecode.setFormat((int) m_fps);
948 }
949
950 const double KdenliveDoc::dar() {
951     return (double) m_profile.display_aspect_num / m_profile.display_aspect_den;
952 }
953
954 void KdenliveDoc::setThumbsProgress(const QString &message, int progress) {
955     emit progressInfo(message, progress);
956 }
957
958 void KdenliveDoc::loadingProgressed() {
959     m_documentLoadingProgress += m_documentLoadingStep;
960     emit progressInfo(QString(), (int) m_documentLoadingProgress);
961 }
962
963 QUndoStack *KdenliveDoc::commandStack() {
964     return m_commandStack;
965 }
966
967 void KdenliveDoc::setRenderer(Render *render) {
968     if (m_render) return;
969     m_render = render;
970     emit progressInfo(i18n("Loading playlist..."), 0);
971     //qApp->processEvents();
972     if (m_render) {
973         m_render->setSceneList(m_document.toString(), m_startPos);
974         kDebug() << "// SETTING SCENE LIST:\n\n" << m_document.toString();
975         checkProjectClips();
976     }
977     emit progressInfo(QString(), -1);
978 }
979
980 void KdenliveDoc::checkProjectClips() {
981     if (m_render == NULL) return;
982     QList <Mlt::Producer *> prods = m_render->producersList();
983     QString id ;
984     QString prodId ;
985     QString prodTrack ;
986     for (int i = 0; i < prods.count(); i++) {
987         id = prods.at(i)->get("id");
988         prodId = id.section('_', 0, 0);
989         prodTrack = id.section('_', 1, 1);
990         DocClipBase *clip = m_clipManager->getClipById(prodId);
991         if (clip) clip->setProducer(prods.at(i));
992         if (clip && clip->clipType() == TEXT && !QFile::exists(clip->fileURL().path())) {
993             // regenerate text clip image if required
994             kDebug() << "// TITLE: " << clip->getProperty("titlename") << " Preview file: " << clip->getProperty("resource") << " DOES NOT EXIST";
995             QString titlename = clip->getProperty("titlename");
996             QString titleresource;
997             if (titlename.isEmpty()) {
998                 QStringList titleInfo = TitleWidget::getFreeTitleInfo(projectFolder());
999                 titlename = titleInfo.at(0);
1000                 titleresource = titleInfo.at(1);
1001                 clip->setProperty("titlename", titlename);
1002                 kDebug() << "// New title set to: " << titlename;
1003             } else {
1004                 titleresource = TitleWidget::getTitleResourceFromName(projectFolder(), titlename);
1005             }
1006             QString titlepath = projectFolder().path() + "/titles/";
1007             TitleWidget *dia_ui = new TitleWidget(KUrl(), titlepath, m_render, kapp->activeWindow());
1008             QDomDocument doc;
1009             doc.setContent(clip->getProperty("xmldata"));
1010             dia_ui->setXml(doc);
1011             QPixmap pix = dia_ui->renderedPixmap();
1012             pix.save(titleresource);
1013             clip->setProperty("resource", titleresource);
1014             delete dia_ui;
1015             clip->producer()->set("force_reload", 1);
1016         }
1017     }
1018 }
1019
1020 Render *KdenliveDoc::renderer() {
1021     return m_render;
1022 }
1023
1024 void KdenliveDoc::updateClip(const QString &id) {
1025     emit updateClipDisplay(id);
1026 }
1027
1028 int KdenliveDoc::getFramePos(QString duration) {
1029     return m_timecode.getFrameCount(duration, m_fps);
1030 }
1031
1032 QString KdenliveDoc::producerName(const QString &id) {
1033     QString result = "unnamed";
1034     QDomNodeList prods = producersList();
1035     int ct = prods.count();
1036     for (int i = 0; i <  ct ; i++) {
1037         QDomElement e = prods.item(i).toElement();
1038         if (e.attribute("id") != "black" && e.attribute("id") == id) {
1039             result = e.attribute("name");
1040             if (result.isEmpty()) result = KUrl(e.attribute("resource")).fileName();
1041             break;
1042         }
1043     }
1044     return result;
1045 }
1046
1047 void KdenliveDoc::setProducerDuration(const QString &id, int duration) {
1048     QDomNodeList prods = producersList();
1049     int ct = prods.count();
1050     for (int i = 0; i <  ct ; i++) {
1051         QDomElement e = prods.item(i).toElement();
1052         if (e.attribute("id") != "black" && e.attribute("id") == id) {
1053             e.setAttribute("duration", QString::number(duration));
1054             break;
1055         }
1056     }
1057 }
1058
1059 int KdenliveDoc::getProducerDuration(const QString &id) {
1060     int result = 0;
1061     QDomNodeList prods = producersList();
1062     int ct = prods.count();
1063     for (int i = 0; i <  ct ; i++) {
1064         QDomElement e = prods.item(i).toElement();
1065         if (e.attribute("id") != "black" && e.attribute("id") == id) {
1066             result = e.attribute("duration").toInt();
1067             break;
1068         }
1069     }
1070     return result;
1071 }
1072
1073
1074 QDomDocument KdenliveDoc::generateSceneList() {
1075     QDomDocument doc;
1076     QDomElement westley = doc.createElement("westley");
1077     doc.appendChild(westley);
1078     QDomElement prod = doc.createElement("producer");
1079 }
1080
1081 QDomDocument KdenliveDoc::toXml() const {
1082     return m_document;
1083 }
1084
1085 Timecode KdenliveDoc::timecode() const {
1086     return m_timecode;
1087 }
1088
1089 QDomNodeList KdenliveDoc::producersList() {
1090     return m_document.elementsByTagName("producer");
1091 }
1092
1093 void KdenliveDoc::backupMltPlaylist() {
1094     if (m_render) m_scenelist = m_render->sceneList();
1095 }
1096
1097 double KdenliveDoc::projectDuration() const {
1098     if (m_render) return GenTime(m_render->getLength(), m_fps).ms() / 1000;
1099 }
1100
1101 double KdenliveDoc::fps() const {
1102     return m_fps;
1103 }
1104
1105 int KdenliveDoc::width() const {
1106     return m_width;
1107 }
1108
1109 int KdenliveDoc::height() const {
1110     return m_height;
1111 }
1112
1113 KUrl KdenliveDoc::url() const {
1114     return m_url;
1115 }
1116
1117 void KdenliveDoc::setUrl(KUrl url) {
1118     m_url = url;
1119 }
1120
1121 void KdenliveDoc::setModified(bool mod) {
1122     if (!m_url.isEmpty() && mod && KdenliveSettings::crashrecovery()) {
1123         m_autoSaveTimer->start(3000);
1124     }
1125     if (mod == m_modified) return;
1126     m_modified = mod;
1127     emit docModified(m_modified);
1128 }
1129
1130 bool KdenliveDoc::isModified() const {
1131     return m_modified;
1132 }
1133
1134 QString KdenliveDoc::description() const {
1135     if (m_url.isEmpty())
1136         return i18n("Untitled") + " / " + m_profile.description;
1137     else
1138         return m_url.fileName() + " / " + m_profile.description;
1139 }
1140
1141 void KdenliveDoc::addClip(QDomElement elem, QString clipId, bool createClipItem) {
1142     const QString producerId = clipId.section('_', 0, 0);
1143     int subtrack = clipId.section('_', 1, 1).toInt();
1144     DocClipBase *clip = m_clipManager->getClipById(producerId);
1145     if (clip == NULL) {
1146         elem.setAttribute("id", producerId);
1147         QString path = elem.attribute("resource");
1148         QString extension;
1149         if (elem.attribute("type").toInt() == SLIDESHOW) {
1150             extension = KUrl(path).fileName();
1151             path = KUrl(path).directory();
1152         }
1153         if (!path.isEmpty() && !QFile::exists(path)) {
1154             const QString size = elem.attribute("file_size");
1155             const QString hash = elem.attribute("file_hash");
1156             QString newpath;
1157             KMessageBox::ButtonCode action = KMessageBox::No;
1158             if (!size.isEmpty() && !hash.isEmpty()) {
1159                 if (!m_searchFolder.isEmpty()) newpath = searchFileRecursively(m_searchFolder, size, hash);
1160                 else action = (KMessageBox::ButtonCode)KMessageBox::messageBox(kapp->activeWindow(), KMessageBox::WarningYesNo, i18n("<qt>Clip <b>%1</b><br>is invalid, what do you want to do?", path), i18n("File not found"), KGuiItem(i18n("Search automatically")), /*KGuiItem(i18n("Remove from project")), */KGuiItem(i18n("Keep as placeholder")));
1161             } else {
1162                 if (elem.attribute("type").toInt() == SLIDESHOW) {
1163                     if (KMessageBox::messageBox(kapp->activeWindow(), KMessageBox::WarningYesNo, i18n("<qt>Clip <b>%1</b><br>is invalid, what do you want to do?", path), i18n("File not found"), KGuiItem(i18n("Search automatically")), /*KGuiItem(i18n("Remove from project")),*/ KGuiItem(i18n("Keep as placeholder"))) == KMessageBox::Yes)
1164                         newpath = KFileDialog::getExistingDirectory(KUrl("kfiledialog:///clipfolder"), kapp->activeWindow(), i18n("Looking for %1", path));
1165                 } else newpath = KFileDialog::getOpenFileName(KUrl("kfiledialog:///clipfolder"), QString(), kapp->activeWindow(), i18n("Looking for %1", path));
1166             }
1167             if (action == KMessageBox::Yes) {
1168                 kDebug() << "// ASKED FOR SRCH CLIP: " << clipId;
1169                 m_searchFolder = KFileDialog::getExistingDirectory(KUrl("kfiledialog:///clipfolder"), kapp->activeWindow());
1170                 if (!m_searchFolder.isEmpty()) {
1171                     newpath = searchFileRecursively(QDir(m_searchFolder), size, hash);
1172                 }
1173             }
1174             if (!newpath.isEmpty()) {
1175                 if (elem.attribute("type").toInt() == SLIDESHOW) newpath.append('/' + extension);
1176                 elem.setAttribute("resource", newpath);
1177                 setNewClipResource(clipId, newpath);
1178                 setModified(true);
1179             }
1180         }
1181         clip = new DocClipBase(m_clipManager, elem, producerId);
1182         m_clipManager->addClip(clip);
1183     }
1184     if (createClipItem) emit addProjectClip(clip);
1185 }
1186
1187 void KdenliveDoc::setNewClipResource(const QString &id, const QString &path) {
1188     QDomNodeList prods = m_document.elementsByTagName("producer");
1189     int maxprod = prods.count();
1190     for (int i = 0; i < maxprod; i++) {
1191         QDomNode m = prods.at(i);
1192         QString prodId = m.toElement().attribute("id");
1193         if (prodId == id || prodId.startsWith(id + "_")) {
1194             QDomNodeList params = m.childNodes();
1195             for (int j = 0; j < params.count(); j++) {
1196                 QDomElement e = params.item(j).toElement();
1197                 if (e.attribute("name") == "resource") {
1198                     e.firstChild().setNodeValue(path);
1199                     break;
1200                 }
1201             }
1202         }
1203     }
1204 }
1205
1206 QString KdenliveDoc::searchFileRecursively(const QDir &dir, const QString &matchSize, const QString &matchHash) const {
1207     QString foundFileName;
1208     QByteArray fileData;
1209     QByteArray fileHash;
1210     QStringList filesAndDirs = dir.entryList(QDir::Files | QDir::Readable);
1211     for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); i++) {
1212         QFile file(dir.absoluteFilePath(filesAndDirs.at(i)));
1213         if (file.open(QIODevice::ReadOnly)) {
1214             if (QString::number(file.size()) == matchSize) {
1215                 /*
1216                 * 1 MB = 1 second per 450 files (or faster)
1217                 * 10 MB = 9 seconds per 450 files (or faster)
1218                 */
1219                 if (file.size() > 1000000*2) {
1220                     fileData = file.read(1000000);
1221                     if (file.seek(file.size() - 1000000))
1222                         fileData.append(file.readAll());
1223                 } else
1224                     fileData = file.readAll();
1225                 file.close();
1226                 fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
1227                 if (QString(fileHash.toHex()) == matchHash)
1228                     return file.fileName();
1229             }
1230         }
1231         kDebug() << filesAndDirs.at(i) << file.size() << fileHash.toHex();
1232     }
1233     filesAndDirs = dir.entryList(QDir::Dirs | QDir::Readable | QDir::Executable | QDir::NoDotAndDotDot);
1234     for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); i++) {
1235         foundFileName = searchFileRecursively(dir.absoluteFilePath(filesAndDirs.at(i)), matchSize, matchHash);
1236         if (!foundFileName.isEmpty())
1237             break;
1238     }
1239     return foundFileName;
1240 }
1241
1242 void KdenliveDoc::addClipInfo(QDomElement elem, QString clipId) {
1243     DocClipBase *clip = m_clipManager->getClipById(clipId);
1244     if (clip == NULL) {
1245         addClip(elem, clipId);
1246     } else {
1247         QMap <QString, QString> properties;
1248         QDomNamedNodeMap attributes = elem.attributes();
1249         QString attrname;
1250         for (unsigned int i = 0; i < attributes.count(); i++) {
1251             attrname = attributes.item(i).nodeName();
1252             if (attrname != "resource")
1253                 properties.insert(attrname, attributes.item(i).nodeValue());
1254             kDebug() << attrname << " = " << attributes.item(i).nodeValue();
1255         }
1256         clip->setProperties(properties);
1257         emit addProjectClip(clip, false);
1258     }
1259 }
1260
1261 void KdenliveDoc::addFolder(const QString foldername, const QString &clipId, bool edit) {
1262     emit addProjectFolder(foldername, clipId, false, edit);
1263 }
1264
1265 void KdenliveDoc::deleteFolder(const QString foldername, const QString &clipId) {
1266     emit addProjectFolder(foldername, clipId, true);
1267 }
1268
1269 void KdenliveDoc::deleteProjectClip(QList <QString> ids) {
1270     for (int i = 0; i < ids.size(); ++i) {
1271         emit deleteTimelineClip(ids.at(i));
1272         m_clipManager->slotDeleteClip(ids.at(i));
1273     }
1274     setModified(true);
1275 }
1276
1277 void KdenliveDoc::deleteProjectFolder(QMap <QString, QString> map) {
1278     QMapIterator<QString, QString> i(map);
1279     while (i.hasNext()) {
1280         i.next();
1281         slotDeleteFolder(i.key(), i.value());
1282     }
1283     setModified(true);
1284 }
1285
1286 void KdenliveDoc::deleteClip(const QString &clipId) {
1287     emit signalDeleteProjectClip(clipId);
1288     m_clipManager->deleteClip(clipId);
1289 }
1290
1291 void KdenliveDoc::slotAddClipList(const KUrl::List urls, const QString group, const QString &groupId) {
1292     m_clipManager->slotAddClipList(urls, group, groupId);
1293     emit selectLastAddedClip(QString::number(m_clipManager->lastClipId()));
1294     setModified(true);
1295 }
1296
1297
1298 void KdenliveDoc::slotAddClipFile(const KUrl url, const QString group, const QString &groupId) {
1299     kDebug() << "/////////  DOCUM, ADD CLP: " << url;
1300     m_clipManager->slotAddClipFile(url, group, groupId);
1301     emit selectLastAddedClip(QString::number(m_clipManager->lastClipId()));
1302     setModified(true);
1303 }
1304
1305 void KdenliveDoc::slotAddFolder(const QString folderName) {
1306     AddFolderCommand *command = new AddFolderCommand(this, folderName, QString::number(m_clipManager->getFreeClipId()), true);
1307     commandStack()->push(command);
1308     setModified(true);
1309 }
1310
1311 void KdenliveDoc::slotDeleteFolder(const QString folderName, const QString &id) {
1312     AddFolderCommand *command = new AddFolderCommand(this, folderName, id, false);
1313     commandStack()->push(command);
1314     setModified(true);
1315 }
1316
1317 void KdenliveDoc::slotEditFolder(const QString newfolderName, const QString oldfolderName, const QString &clipId) {
1318     EditFolderCommand *command = new EditFolderCommand(this, newfolderName, oldfolderName, clipId, false);
1319     commandStack()->push(command);
1320     setModified(true);
1321 }
1322
1323 const QString&KdenliveDoc::getFreeClipId() {
1324     return QString::number(m_clipManager->getFreeClipId());
1325 }
1326
1327 DocClipBase *KdenliveDoc::getBaseClip(const QString &clipId) {
1328     return m_clipManager->getClipById(clipId);
1329 }
1330
1331 void KdenliveDoc::slotAddColorClipFile(const QString name, const QString color, QString duration, const QString group, const QString &groupId) {
1332     m_clipManager->slotAddColorClipFile(name, color, duration, group, groupId);
1333     setModified(true);
1334 }
1335
1336 void KdenliveDoc::slotAddSlideshowClipFile(const QString name, const QString path, int count, const QString duration, const bool loop, const bool fade, const QString &luma_duration, const QString &luma_file, const int softness, const QString group, const QString &groupId) {
1337     m_clipManager->slotAddSlideshowClipFile(name, path, count, duration, loop, fade, luma_duration, luma_file, softness, group, groupId);
1338     setModified(true);
1339 }
1340
1341 void KdenliveDoc::slotCreateTextClip(QString group, const QString &groupId) {
1342     QString titlesFolder = projectFolder().path() + "/titles/";
1343     KStandardDirs::makeDir(titlesFolder);
1344     TitleWidget *dia_ui = new TitleWidget(KUrl(), titlesFolder, m_render, kapp->activeWindow());
1345     if (dia_ui->exec() == QDialog::Accepted) {
1346         QStringList titleInfo = TitleWidget::getFreeTitleInfo(projectFolder());
1347         QPixmap pix = dia_ui->renderedPixmap();
1348         pix.save(titleInfo.at(1));
1349         //dia_ui->saveTitle(path + ".kdenlivetitle");
1350         m_clipManager->slotAddTextClipFile(titleInfo.at(0), titleInfo.at(1), dia_ui->xml().toString(), QString(), QString());
1351         setModified(true);
1352     }
1353     delete dia_ui;
1354 }
1355
1356 int KdenliveDoc::tracksCount() const {
1357     return m_tracksList.count();
1358 }
1359
1360 TrackInfo KdenliveDoc::trackInfoAt(int ix) const {
1361     return m_tracksList.at(ix);
1362 }
1363
1364 void KdenliveDoc::switchTrackAudio(int ix, bool hide) {
1365     m_tracksList[ix].isMute = hide; // !m_tracksList.at(ix).isMute;
1366 }
1367
1368 void KdenliveDoc::switchTrackVideo(int ix, bool hide) {
1369     m_tracksList[ix].isBlind = hide; // !m_tracksList.at(ix).isBlind;
1370 }
1371
1372 void KdenliveDoc::insertTrack(int ix, TrackInfo type) {
1373     if (ix == -1) m_tracksList << type;
1374     else m_tracksList.insert(ix, type);
1375 }
1376
1377 void KdenliveDoc::deleteTrack(int ix) {
1378     m_tracksList.removeAt(ix);
1379 }
1380
1381 void KdenliveDoc::setTrackType(int ix, TrackInfo type) {
1382     m_tracksList[ix].type = type.type;
1383     m_tracksList[ix].isMute = type.isMute;
1384     m_tracksList[ix].isBlind = type.isBlind;
1385 }
1386
1387 const QList <TrackInfo> KdenliveDoc::tracksList() const {
1388     return m_tracksList;
1389 }
1390
1391 QPoint KdenliveDoc::getTracksCount() const {
1392     int audio = 0;
1393     int video = 0;
1394     foreach(const TrackInfo &info, m_tracksList) {
1395         if (info.type == VIDEOTRACK) video++;
1396         else audio++;
1397     }
1398     return QPoint(video, audio);
1399 }
1400
1401 QString KdenliveDoc::getTracksInfo() const {
1402     QString result;
1403     foreach(const TrackInfo &info, m_tracksList) {
1404         if (info.type == VIDEOTRACK) result.append('v');
1405         else result.append('a');
1406     }
1407     return result;
1408 }
1409
1410 void KdenliveDoc::cachePixmap(const QString &fileId, const QPixmap &pix) const {
1411     pix.save(m_projectFolder.path() + "/thumbs/" + fileId + ".png");
1412 }
1413
1414 #include "kdenlivedoc.moc"
1415