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