]> git.sesse.net Git - kdenlive/blob - src/kdenlivedoc.cpp
Implement document notes:
[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 "documentvalidator.h"
31 #include "kdenlive-config.h"
32
33 #include <KDebug>
34 #include <KStandardDirs>
35 #include <KMessageBox>
36 #include <KLocale>
37 #include <KFileDialog>
38 #include <KIO/NetAccess>
39 #include <KIO/CopyJob>
40 #include <KApplication>
41
42 #include <QCryptographicHash>
43 #include <QFile>
44 #include <QInputDialog>
45 #include <QDomImplementation>
46
47 #include <mlt++/Mlt.h>
48
49 const double DOCUMENTVERSION = 0.85;
50
51 KdenliveDoc::KdenliveDoc(const KUrl &url, const KUrl &projectFolder, QUndoGroup *undoGroup, QString profileName, const QPoint tracks, Render *render, KTextEdit *notes, MainWindow *parent) :
52     QObject(parent),
53     m_autosave(NULL),
54     m_url(url),
55     m_render(render),
56     m_notesWidget(notes),
57     m_commandStack(new QUndoStack(undoGroup)),
58     m_modified(false),
59     m_projectFolder(projectFolder)
60 {
61     m_clipManager = new ClipManager(this);
62     m_autoSaveTimer = new QTimer(this);
63     m_autoSaveTimer->setSingleShot(true);
64     bool success = false;
65
66     // init default document properties
67     m_documentProperties["zoom"] = "7";
68     m_documentProperties["verticalzoom"] = "1";
69     m_documentProperties["zonein"] = "0";
70     m_documentProperties["zoneout"] = "100";
71
72     if (!url.isEmpty()) {
73         QString tmpFile;
74         success = KIO::NetAccess::download(url.path(), tmpFile, parent);
75         if (!success) // The file cannot be opened
76             KMessageBox::error(parent, KIO::NetAccess::lastErrorString());
77         else {
78             QFile file(tmpFile);
79             QString errorMsg;
80             QDomImplementation impl;
81             impl.setInvalidDataPolicy(QDomImplementation::DropInvalidChars);
82             success = m_document.setContent(&file, false, &errorMsg);
83             file.close();
84             KIO::NetAccess::removeTempFile(tmpFile);
85
86             if (!success) // It is corrupted
87                 KMessageBox::error(parent, errorMsg);
88             else {
89                 parent->slotGotProgressInfo(i18n("Validating"), 0);
90                 DocumentValidator validator(m_document);
91                 success = validator.isProject();
92                 if (!success) {
93                     // It is not a project file
94                     parent->slotGotProgressInfo(i18n("File %1 is not a Kdenlive project file", m_url.path()), 100);
95                 } else {
96                     /*
97                      * Validate the file against the current version (upgrade
98                      * and recover it if needed). It is NOT a passive operation
99                      */
100                     // TODO: backup the document or alert the user?
101                     success = validator.validate(DOCUMENTVERSION);
102                     if (success) { // Let the validator handle error messages
103                         parent->slotGotProgressInfo(i18n("Check missing clips"), 0);
104                         QDomNodeList infoproducers = m_document.elementsByTagName("kdenlive_producer");
105                         success = checkDocumentClips(infoproducers);
106                         if (success) {
107                             parent->slotGotProgressInfo(i18n("Loading"), 0);
108                             QDomElement mlt = m_document.firstChildElement("mlt");
109                             QDomElement infoXml = mlt.firstChildElement("kdenlivedoc");
110                             QDomElement e;
111
112                             // Read notes
113                             QDomElement notesxml = infoXml.firstChildElement("documentnotes");
114                             if (!notesxml.isNull()) m_notesWidget->setText(notesxml.firstChild().nodeValue());
115
116                             // Build tracks
117                             QDomElement tracksinfo = infoXml.firstChildElement("tracksinfo");
118                             if (!tracksinfo.isNull()) {
119                                 QDomNodeList trackslist = tracksinfo.childNodes();
120                                 int maxchild = trackslist.count();
121                                 for (int k = 0; k < maxchild; k++) {
122                                     e = trackslist.at(k).toElement();
123                                     if (e.tagName() == "trackinfo") {
124                                         TrackInfo projectTrack;
125                                         if (e.attribute("type") == "audio")
126                                             projectTrack.type = AUDIOTRACK;
127                                         else
128                                             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                                         projectTrack.trackName = e.attribute("trackname");
133                                         m_tracksList.append(projectTrack);
134                                     }
135                                 }
136                                 mlt.removeChild(tracksinfo);
137                             }
138
139                             QDomNodeList                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    folders = m_document.elementsByTagName("folder");
140                             for (int i = 0; i < folders.count(); i++) {
141                                 e = folders.item(i).cloneNode().toElement();
142                                 m_clipManager->addFolder(e.attribute("id"), e.attribute("name"));
143                             }
144
145                             const int infomax = infoproducers.count();
146                             QDomNodeList producers = m_document.elementsByTagName("producer");
147                             const int max = producers.count();
148
149                             for (int i = 0; i < infomax; i++) {
150                                 e = infoproducers.item(i).cloneNode().toElement();
151                                 QString prodId = e.attribute("id");
152                                 if (!e.isNull() && prodId != "black" && !prodId.startsWith("slowmotion")) {
153                                     e.setTagName("producer");
154                                     // Get MLT's original producer properties
155                                     QDomElement orig;
156                                     for (int j = 0; j < max; j++) {
157                                         QDomElement o = producers.item(j).cloneNode().toElement();
158                                         QString origId = o.attribute("id").section('_', 0, 0);
159                                         if (origId == prodId) {
160                                             orig = o;
161                                             break;
162                                         }
163                                     }
164                                     if (!addClipInfo(e, orig, prodId)) {
165                                         // The user manually aborted the loading.
166                                         success = false;
167                                         emit resetProjectList();
168                                         m_tracksList.clear();
169                                         m_clipManager->clear();
170                                         break;
171                                     }
172                                 }
173                             }
174
175                             if (success) {
176                                 QDomElement markers = infoXml.firstChildElement("markers");
177                                 if (!markers.isNull()) {
178                                     QDomNodeList markerslist = markers.childNodes();
179                                     int maxchild = markerslist.count();
180                                     for (int k = 0; k < maxchild; k++) {
181                                         e = markerslist.at(k).toElement();
182                                         if (e.tagName() == "marker")
183                                             m_clipManager->getClipById(e.attribute("id"))->addSnapMarker(GenTime(e.attribute("time").toDouble()), e.attribute("comment"));
184                                     }
185                                     infoXml.removeChild(markers);
186                                 }
187
188                                 m_projectFolder = KUrl(infoXml.attribute("projectfolder"));
189                                 QDomElement docproperties = infoXml.firstChildElement("documentproperties");
190                                 QDomNamedNodeMap props = docproperties.attributes();
191                                 for (int i = 0; i < props.count(); i++)
192                                     m_documentProperties.insert(props.item(i).nodeName(), props.item(i).nodeValue());
193                                 setProfilePath(infoXml.attribute("profile"));
194                                 setModified(validator.isModified());
195                                 kDebug() << "Reading file: " << url.path() << ", found clips: " << producers.count();
196                             }
197                         }
198                     }
199                 }
200             }
201         }
202     }
203
204     // Something went wrong, or a new file was requested: create a new project
205     if (!success) {
206         m_url = KUrl();
207         setProfilePath(profileName);
208         m_document = createEmptyDocument(tracks.x(), tracks.y());
209     }
210
211     // Set the video profile (empty == default)
212     KdenliveSettings::setCurrent_profile(profilePath());
213
214     // Ask to create the project directory if it does not exist
215     if (!QFile::exists(m_projectFolder.path())) {
216         int create = KMessageBox::questionYesNo(parent, i18n("Project directory %1 does not exist. Create it?", m_projectFolder.path()));
217         if (create == KMessageBox::Yes) {
218             QDir projectDir(m_projectFolder.path());
219             bool ok = projectDir.mkpath(m_projectFolder.path());
220             if (!ok) {
221                 KMessageBox::sorry(parent, i18n("The directory %1, could not be created.\nPlease make sure you have the required permissions.", m_projectFolder.path()));
222             }
223         }
224     }
225
226     // Make sure the project folder is usable
227     if (m_projectFolder.isEmpty() || !KIO::NetAccess::exists(m_projectFolder.path(), KIO::NetAccess::DestinationSide, parent)) {
228         KMessageBox::information(parent, i18n("Document project folder is invalid, setting it to the default one: %1", KdenliveSettings::defaultprojectfolder()));
229         m_projectFolder = KUrl(KdenliveSettings::defaultprojectfolder());
230     }
231
232     // Make sure that the necessary folders exist
233     KStandardDirs::makeDir(m_projectFolder.path(KUrl::AddTrailingSlash) + "titles/");
234     KStandardDirs::makeDir(m_projectFolder.path(KUrl::AddTrailingSlash) + "thumbs/");
235     KStandardDirs::makeDir(m_projectFolder.path(KUrl::AddTrailingSlash) + "ladspa/");
236
237     //kDebug() << "// SETTING SCENE LIST:\n\n" << m_document.toString();
238     connect(m_autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
239 }
240
241 KdenliveDoc::~KdenliveDoc()
242 {
243     m_autoSaveTimer->stop();
244     delete m_commandStack;
245     kDebug() << "// DEL CLP MAN";
246     delete m_clipManager;
247     kDebug() << "// DEL CLP MAN done";
248     delete m_autoSaveTimer;
249     if (m_autosave) {
250         if (!m_autosave->fileName().isEmpty()) m_autosave->remove();
251         delete m_autosave;
252     }
253 }
254
255 int KdenliveDoc::setSceneList()
256 {
257     m_render->resetProfile(KdenliveSettings::current_profile());
258     if (m_render->setSceneList(m_document.toString(), m_documentProperties.value("position").toInt()) == -1) {
259         // INVALID MLT Consumer, something is wrong
260         return -1;
261     }
262     m_documentProperties.remove("position");
263     // m_document xml is now useless, clear it
264     m_document.clear();
265     return 0;
266 }
267
268 QDomDocument KdenliveDoc::createEmptyDocument(int videotracks, int audiotracks)
269 {
270     m_tracksList.clear();
271
272     // Tracks are added Â«backwards», so we need to reverse the track numbering
273     // mbt 331: http://www.kdenlive.org/mantis/view.php?id=331
274     // Better default names for tracks: Audio 1 etc. instead of blank numbers
275     for (int i = 0; i < audiotracks; i++) {
276         TrackInfo audioTrack;
277         audioTrack.type = AUDIOTRACK;
278         audioTrack.isMute = false;
279         audioTrack.isBlind = true;
280         audioTrack.isLocked = false;
281         audioTrack.trackName = QString("Audio ") + QString::number(audiotracks - i);
282         m_tracksList.append(audioTrack);
283
284     }
285     for (int i = 0; i < videotracks; i++) {
286         TrackInfo videoTrack;
287         videoTrack.type = VIDEOTRACK;
288         videoTrack.isMute = false;
289         videoTrack.isBlind = false;
290         videoTrack.isLocked = false;
291         videoTrack.trackName = QString("Video ") + QString::number(videotracks - i);
292         m_tracksList.append(videoTrack);
293     }
294     return createEmptyDocument(m_tracksList);
295 }
296
297 QDomDocument KdenliveDoc::createEmptyDocument(QList <TrackInfo> tracks)
298 {
299     // Creating new document
300     QDomDocument doc;
301     QDomElement mlt = doc.createElement("mlt");
302     doc.appendChild(mlt);
303
304
305     // Create black producer
306     // For some unknown reason, we have to build the black producer here and not in renderer.cpp, otherwise
307     // the composite transitions with the black track are corrupted.
308     QDomElement blk = doc.createElement("producer");
309     blk.setAttribute("in", 0);
310     blk.setAttribute("out", 500);
311     blk.setAttribute("id", "black");
312
313     QDomElement property = doc.createElement("property");
314     property.setAttribute("name", "mlt_type");
315     QDomText value = doc.createTextNode("producer");
316     property.appendChild(value);
317     blk.appendChild(property);
318
319     property = doc.createElement("property");
320     property.setAttribute("name", "aspect_ratio");
321     value = doc.createTextNode(QString::number(0.0));
322     property.appendChild(value);
323     blk.appendChild(property);
324
325     property = doc.createElement("property");
326     property.setAttribute("name", "length");
327     value = doc.createTextNode(QString::number(15000));
328     property.appendChild(value);
329     blk.appendChild(property);
330
331     property = doc.createElement("property");
332     property.setAttribute("name", "eof");
333     value = doc.createTextNode("pause");
334     property.appendChild(value);
335     blk.appendChild(property);
336
337     property = doc.createElement("property");
338     property.setAttribute("name", "resource");
339     value = doc.createTextNode("black");
340     property.appendChild(value);
341     blk.appendChild(property);
342
343     property = doc.createElement("property");
344     property.setAttribute("name", "mlt_service");
345     value = doc.createTextNode("colour");
346     property.appendChild(value);
347     blk.appendChild(property);
348
349     mlt.appendChild(blk);
350
351
352     QDomElement tractor = doc.createElement("tractor");
353     tractor.setAttribute("id", "maintractor");
354     QDomElement multitrack = doc.createElement("multitrack");
355     QDomElement playlist = doc.createElement("playlist");
356     playlist.setAttribute("id", "black_track");
357     mlt.appendChild(playlist);
358
359     QDomElement blank0 = doc.createElement("entry");
360     blank0.setAttribute("in", "0");
361     blank0.setAttribute("out", "1");
362     blank0.setAttribute("producer", "black");
363     playlist.appendChild(blank0);
364
365     // create playlists
366     int total = tracks.count() + 1;
367
368     for (int i = 1; i < total; i++) {
369         QDomElement playlist = doc.createElement("playlist");
370         playlist.setAttribute("id", "playlist" + QString::number(i));
371         mlt.appendChild(playlist);
372     }
373
374     QDomElement track0 = doc.createElement("track");
375     track0.setAttribute("producer", "black_track");
376     tractor.appendChild(track0);
377
378     // create audio and video tracks
379     for (int i = 1; i < total; i++) {
380         QDomElement track = doc.createElement("track");
381         track.setAttribute("producer", "playlist" + QString::number(i));
382         if (tracks.at(i - 1).type == AUDIOTRACK) {
383             track.setAttribute("hide", "video");
384         } else if (tracks.at(i - 1).isBlind)
385             track.setAttribute("hide", "video");
386         if (tracks.at(i - 1).isMute)
387             track.setAttribute("hide", "audio");
388         tractor.appendChild(track);
389     }
390
391     for (int i = 2; i < total ; i++) {
392         QDomElement transition = doc.createElement("transition");
393         transition.setAttribute("always_active", "1");
394
395         QDomElement property = doc.createElement("property");
396         property.setAttribute("name", "a_track");
397         QDomText value = doc.createTextNode(QString::number(1));
398         property.appendChild(value);
399         transition.appendChild(property);
400
401         property = doc.createElement("property");
402         property.setAttribute("name", "b_track");
403         value = doc.createTextNode(QString::number(i));
404         property.appendChild(value);
405         transition.appendChild(property);
406
407         property = doc.createElement("property");
408         property.setAttribute("name", "mlt_service");
409         value = doc.createTextNode("mix");
410         property.appendChild(value);
411         transition.appendChild(property);
412
413         property = doc.createElement("property");
414         property.setAttribute("name", "combine");
415         value = doc.createTextNode("1");
416         property.appendChild(value);
417         transition.appendChild(property);
418
419         property = doc.createElement("property");
420         property.setAttribute("name", "internal_added");
421         value = doc.createTextNode("237");
422         property.appendChild(value);
423         transition.appendChild(property);
424         tractor.appendChild(transition);
425     }
426     mlt.appendChild(tractor);
427     return doc;
428 }
429
430
431 void KdenliveDoc::syncGuides(QList <Guide *> guides)
432 {
433     m_guidesXml.clear();
434     QDomElement guideNode = m_guidesXml.createElement("guides");
435     m_guidesXml.appendChild(guideNode);
436     QDomElement e;
437
438     for (int i = 0; i < guides.count(); i++) {
439         e = m_guidesXml.createElement("guide");
440         e.setAttribute("time", guides.at(i)->position().ms() / 1000);
441         e.setAttribute("comment", guides.at(i)->label());
442         guideNode.appendChild(e);
443     }
444     setModified(true);
445     emit guidesUpdated();
446 }
447
448 QDomElement KdenliveDoc::guidesXml() const
449 {
450     return m_guidesXml.documentElement();
451 }
452
453 void KdenliveDoc::slotAutoSave()
454 {
455     if (m_render && m_autosave) {
456         if (!m_autosave->isOpen() && !m_autosave->open(QIODevice::ReadWrite)) {
457             // show error: could not open the autosave file
458             kDebug() << "ERROR; CANNOT CREATE AUTOSAVE FILE";
459         }
460         kDebug() << "// AUTOSAVE FILE: " << m_autosave->fileName();
461         QString doc;
462         if (KdenliveSettings::dropbframes()) {
463             KdenliveSettings::setDropbframes(false);
464             m_clipManager->updatePreviewSettings();
465             doc = m_render->sceneList();
466             KdenliveSettings::setDropbframes(true);
467             m_clipManager->updatePreviewSettings();
468         } else doc = m_render->sceneList();
469         saveSceneList(m_autosave->fileName(), doc);
470     }
471 }
472
473 void KdenliveDoc::setZoom(int horizontal, int vertical)
474 {
475     m_documentProperties["zoom"] = QString::number(horizontal);
476     m_documentProperties["verticalzoom"] = QString::number(vertical);
477 }
478
479 QPoint KdenliveDoc::zoom() const
480 {
481     return QPoint(m_documentProperties.value("zoom").toInt(), m_documentProperties.value("verticalzoom").toInt());
482 }
483
484 void KdenliveDoc::setZone(int start, int end)
485 {
486     m_documentProperties["zonein"] = QString::number(start);
487     m_documentProperties["zoneout"] = QString::number(end);
488 }
489
490 QPoint KdenliveDoc::zone() const
491 {
492     return QPoint(m_documentProperties.value("zonein").toInt(), m_documentProperties.value("zoneout").toInt());
493 }
494
495 bool KdenliveDoc::saveSceneList(const QString &path, const QString &scene)
496 {
497     QDomDocument sceneList;
498     sceneList.setContent(scene, true);
499     QDomElement mlt = sceneList.firstChildElement("mlt");
500     if (mlt.isNull() || !mlt.hasChildNodes()) {
501         //Make sure we don't save if scenelist is corrupted
502         KMessageBox::error(kapp->activeWindow(), i18n("Cannot write to file %1, scene list is corrupted.", path));
503         return false;
504     }
505     QDomElement addedXml = sceneList.createElement("kdenlivedoc");
506     mlt.appendChild(addedXml);
507
508     QDomElement markers = sceneList.createElement("markers");
509     addedXml.setAttribute("version", DOCUMENTVERSION);
510     addedXml.setAttribute("kdenliveversion", VERSION);
511     addedXml.setAttribute("profile", profilePath());
512     addedXml.setAttribute("projectfolder", m_projectFolder.path());
513
514     QDomElement docproperties = sceneList.createElement("documentproperties");
515     QMapIterator<QString, QString> i(m_documentProperties);
516     while (i.hasNext()) {
517         i.next();
518         docproperties.setAttribute(i.key(), i.value());
519     }
520     docproperties.setAttribute("position", m_render->seekPosition().frames(m_fps));
521     addedXml.appendChild(docproperties);
522
523     QDomElement docnotes = sceneList.createElement("documentnotes");
524     QDomText value = sceneList.createTextNode(m_notesWidget->toPlainText());
525     docnotes.appendChild(value);
526     addedXml.appendChild(docnotes);
527
528     // Add profile info
529     QDomElement profileinfo = sceneList.createElement("profileinfo");
530     profileinfo.setAttribute("description", m_profile.description);
531     profileinfo.setAttribute("frame_rate_num", m_profile.frame_rate_num);
532     profileinfo.setAttribute("frame_rate_den", m_profile.frame_rate_den);
533     profileinfo.setAttribute("width", m_profile.width);
534     profileinfo.setAttribute("height", m_profile.height);
535     profileinfo.setAttribute("progressive", m_profile.progressive);
536     profileinfo.setAttribute("sample_aspect_num", m_profile.sample_aspect_num);
537     profileinfo.setAttribute("sample_aspect_den", m_profile.sample_aspect_den);
538     profileinfo.setAttribute("display_aspect_num", m_profile.display_aspect_num);
539     profileinfo.setAttribute("display_aspect_den", m_profile.display_aspect_den);
540     addedXml.appendChild(profileinfo);
541
542     // tracks info
543     QDomElement tracksinfo = sceneList.createElement("tracksinfo");
544     foreach(const TrackInfo & info, m_tracksList) {
545         QDomElement trackinfo = sceneList.createElement("trackinfo");
546         if (info.type == AUDIOTRACK) trackinfo.setAttribute("type", "audio");
547         trackinfo.setAttribute("mute", info.isMute);
548         trackinfo.setAttribute("blind", info.isBlind);
549         trackinfo.setAttribute("locked", info.isLocked);
550         trackinfo.setAttribute("trackname", info.trackName);
551         tracksinfo.appendChild(trackinfo);
552     }
553     addedXml.appendChild(tracksinfo);
554
555     // save project folders
556     QMap <QString, QString> folderlist = m_clipManager->documentFolderList();
557
558     QMapIterator<QString, QString> f(folderlist);
559     while (f.hasNext()) {
560         f.next();
561         QDomElement folder = sceneList.createElement("folder");
562         folder.setAttribute("id", f.key());
563         folder.setAttribute("name", f.value());
564         addedXml.appendChild(folder);
565     }
566
567     // Save project clips
568     QDomElement e;
569     QList <DocClipBase*> list = m_clipManager->documentClipList();
570     for (int i = 0; i < list.count(); i++) {
571         e = list.at(i)->toXML();
572         e.setTagName("kdenlive_producer");
573         addedXml.appendChild(sceneList.importNode(e, true));
574         QList < CommentedTime > marks = list.at(i)->commentedSnapMarkers();
575         for (int j = 0; j < marks.count(); j++) {
576             QDomElement marker = sceneList.createElement("marker");
577             marker.setAttribute("time", marks.at(j).time().ms() / 1000);
578             marker.setAttribute("comment", marks.at(j).comment());
579             marker.setAttribute("id", e.attribute("id"));
580             markers.appendChild(marker);
581         }
582     }
583     addedXml.appendChild(markers);
584
585     // Add guides
586     if (!m_guidesXml.isNull()) addedXml.appendChild(sceneList.importNode(m_guidesXml.documentElement(), true));
587
588     // Add clip groups
589     addedXml.appendChild(sceneList.importNode(m_clipManager->groupsXml(), true));
590
591     //wes.appendChild(doc.importNode(kdenliveData, true));
592
593     QFile file(path);
594     if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
595         kWarning() << "//////  ERROR writing to file: " << path;
596         KMessageBox::error(kapp->activeWindow(), i18n("Cannot write to file %1", path));
597         return false;
598     }
599
600     file.write(sceneList.toString().toUtf8());
601     if (file.error() != QFile::NoError) {
602         KMessageBox::error(kapp->activeWindow(), i18n("Cannot write to file %1", path));
603         file.close();
604         return false;
605     }
606     file.close();
607     return true;
608 }
609
610 ClipManager *KdenliveDoc::clipManager()
611 {
612     return m_clipManager;
613 }
614
615 KUrl KdenliveDoc::projectFolder() const
616 {
617     //if (m_projectFolder.isEmpty()) return KUrl(KStandardDirs::locateLocal("appdata", "/projects/"));
618     return m_projectFolder;
619 }
620
621 void KdenliveDoc::setProjectFolder(KUrl url)
622 {
623     if (url == m_projectFolder) return;
624     setModified(true);
625     KStandardDirs::makeDir(url.path());
626     KStandardDirs::makeDir(url.path(KUrl::AddTrailingSlash) + "titles/");
627     KStandardDirs::makeDir(url.path(KUrl::AddTrailingSlash) + "thumbs/");
628     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);
629     m_projectFolder = url;
630 }
631
632 void KdenliveDoc::moveProjectData(KUrl url)
633 {
634     QList <DocClipBase*> list = m_clipManager->documentClipList();
635     //TODO: Also move ladspa effects files
636     for (int i = 0; i < list.count(); i++) {
637         DocClipBase *clip = list.at(i);
638         if (clip->clipType() == TEXT) {
639             // the image for title clip must be moved
640             KUrl oldUrl = clip->fileURL();
641             KUrl newUrl = KUrl(url.path(KUrl::AddTrailingSlash) + "titles/" + oldUrl.fileName());
642             KIO::Job *job = KIO::copy(oldUrl, newUrl);
643             if (KIO::NetAccess::synchronousRun(job, 0)) clip->setProperty("resource", newUrl.path());
644         }
645         QString hash = clip->getClipHash();
646         KUrl oldVideoThumbUrl = KUrl(m_projectFolder.path(KUrl::AddTrailingSlash) + "thumbs/" + hash + ".png");
647         KUrl oldAudioThumbUrl = KUrl(m_projectFolder.path(KUrl::AddTrailingSlash) + "thumbs/" + hash + ".thumb");
648         if (KIO::NetAccess::exists(oldVideoThumbUrl, KIO::NetAccess::SourceSide, 0)) {
649             KUrl newUrl = KUrl(url.path(KUrl::AddTrailingSlash) + "thumbs/" + hash + ".png");
650             KIO::Job *job = KIO::copy(oldVideoThumbUrl, newUrl);
651             KIO::NetAccess::synchronousRun(job, 0);
652         }
653         if (KIO::NetAccess::exists(oldAudioThumbUrl, KIO::NetAccess::SourceSide, 0)) {
654             KUrl newUrl = KUrl(url.path(KUrl::AddTrailingSlash) + "thumbs/" + hash + ".thumb");
655             KIO::Job *job = KIO::copy(oldAudioThumbUrl, newUrl);
656             if (KIO::NetAccess::synchronousRun(job, 0)) clip->refreshThumbUrl();
657         }
658     }
659 }
660
661 const QString &KdenliveDoc::profilePath() const
662 {
663     return m_profile.path;
664 }
665
666 MltVideoProfile KdenliveDoc::mltProfile() const
667 {
668     return m_profile;
669 }
670
671 bool KdenliveDoc::setProfilePath(QString path)
672 {
673     if (path.isEmpty()) path = KdenliveSettings::default_profile();
674     if (path.isEmpty()) path = "dv_pal";
675     m_profile = ProfilesDialog::getVideoProfile(path);
676     bool current_fps = m_fps;
677     if (m_profile.path.isEmpty()) {
678         // Profile not found, use embedded profile
679         QDomElement profileInfo = m_document.elementsByTagName("profileinfo").at(0).toElement();
680         if (profileInfo.isNull()) {
681             KMessageBox::information(kapp->activeWindow(), i18n("Project profile was not found, using default profile."), i18n("Missing Profile"));
682             m_profile = ProfilesDialog::getVideoProfile(KdenliveSettings::default_profile());
683         } else {
684             m_profile.description = profileInfo.attribute("description");
685             m_profile.frame_rate_num = profileInfo.attribute("frame_rate_num").toInt();
686             m_profile.frame_rate_den = profileInfo.attribute("frame_rate_den").toInt();
687             m_profile.width = profileInfo.attribute("width").toInt();
688             m_profile.height = profileInfo.attribute("height").toInt();
689             m_profile.progressive = profileInfo.attribute("progressive").toInt();
690             m_profile.sample_aspect_num = profileInfo.attribute("sample_aspect_num").toInt();
691             m_profile.sample_aspect_den = profileInfo.attribute("sample_aspect_den").toInt();
692             m_profile.display_aspect_num = profileInfo.attribute("display_aspect_num").toInt();
693             m_profile.display_aspect_den = profileInfo.attribute("display_aspect_den").toInt();
694             QString existing = ProfilesDialog::existingProfile(m_profile);
695             if (!existing.isEmpty()) {
696                 m_profile = ProfilesDialog::getVideoProfile(existing);
697                 KMessageBox::information(kapp->activeWindow(), i18n("Project profile not found, replacing with existing one: %1", m_profile.description), i18n("Missing Profile"));
698             } else {
699                 QString newDesc = m_profile.description;
700                 bool ok = true;
701                 while (ok && (newDesc.isEmpty() || ProfilesDialog::existingProfileDescription(newDesc))) {
702                     newDesc = QInputDialog::getText(kapp->activeWindow(), i18n("Existing Profile"), i18n("Your project uses an unknown profile.\nIt uses an existing profile name: %1.\nPlease choose a new name to save it", newDesc), QLineEdit::Normal, newDesc, &ok);
703                 }
704                 if (ok == false) {
705                     // User canceled, use default profile
706                     m_profile = ProfilesDialog::getVideoProfile(KdenliveSettings::default_profile());
707                 } else {
708                     if (newDesc != m_profile.description) {
709                         // Profile description existed, was replaced by new one
710                         m_profile.description = newDesc;
711                     } else {
712                         KMessageBox::information(kapp->activeWindow(), i18n("Project profile was not found, it will be added to your system now."), i18n("Missing Profile"));
713                     }
714                     ProfilesDialog::saveProfile(m_profile);
715                 }
716             }
717             setModified(true);
718         }
719     }
720
721     KdenliveSettings::setProject_display_ratio((double) m_profile.display_aspect_num / m_profile.display_aspect_den);
722     m_fps = (double) m_profile.frame_rate_num / m_profile.frame_rate_den;
723     KdenliveSettings::setProject_fps(m_fps);
724     m_width = m_profile.width;
725     m_height = m_profile.height;
726     kDebug() << "Kdenlive document, init timecode from path: " << path << ",  " << m_fps;
727     m_timecode.setFormat(m_fps);
728     return (current_fps != m_fps);
729 }
730
731 double KdenliveDoc::dar() const
732 {
733     return (double) m_profile.display_aspect_num / m_profile.display_aspect_den;
734 }
735
736 void KdenliveDoc::setThumbsProgress(const QString &message, int progress)
737 {
738     emit progressInfo(message, progress);
739 }
740
741 QUndoStack *KdenliveDoc::commandStack()
742 {
743     return m_commandStack;
744 }
745
746 /*
747 void KdenliveDoc::setRenderer(Render *render) {
748     if (m_render) return;
749     m_render = render;
750     emit progressInfo(i18n("Loading playlist..."), 0);
751     //qApp->processEvents();
752     if (m_render) {
753         m_render->setSceneList(m_document.toString(), m_startPos);
754         kDebug() << "// SETTING SCENE LIST:\n\n" << m_document.toString();
755         checkProjectClips();
756     }
757     emit progressInfo(QString(), -1);
758 }*/
759
760 void KdenliveDoc::checkProjectClips()
761 {
762     if (m_render == NULL) return;
763     m_clipManager->resetProducersList(m_render->producersList());
764 }
765
766 void KdenliveDoc::updatePreviewSettings()
767 {
768     m_clipManager->updatePreviewSettings();
769     m_render->updatePreviewSettings();
770     QList <Mlt::Producer *> prods = m_render->producersList();
771     m_clipManager->resetProducersList(m_render->producersList());
772     qDeleteAll(prods);
773     prods.clear();
774 }
775
776 Render *KdenliveDoc::renderer()
777 {
778     return m_render;
779 }
780
781 void KdenliveDoc::updateClip(const QString id)
782 {
783     emit updateClipDisplay(id);
784 }
785
786 int KdenliveDoc::getFramePos(QString duration)
787 {
788     return m_timecode.getFrameCount(duration);
789 }
790
791 QString KdenliveDoc::producerName(const QString &id)
792 {
793     QString result = "unnamed";
794     QDomNodeList prods = producersList();
795     int ct = prods.count();
796     for (int i = 0; i <  ct ; i++) {
797         QDomElement e = prods.item(i).toElement();
798         if (e.attribute("id") != "black" && e.attribute("id") == id) {
799             result = e.attribute("name");
800             if (result.isEmpty()) result = KUrl(e.attribute("resource")).fileName();
801             break;
802         }
803     }
804     return result;
805 }
806
807 QDomDocument KdenliveDoc::toXml()
808 {
809     return m_document;
810 }
811
812 Timecode KdenliveDoc::timecode() const
813 {
814     return m_timecode;
815 }
816
817 QDomNodeList KdenliveDoc::producersList()
818 {
819     return m_document.elementsByTagName("producer");
820 }
821
822 double KdenliveDoc::projectDuration() const
823 {
824     if (m_render)
825         return GenTime(m_render->getLength(), m_fps).ms() / 1000;
826     else
827         return 0;
828 }
829
830 double KdenliveDoc::fps() const
831 {
832     return m_fps;
833 }
834
835 int KdenliveDoc::width() const
836 {
837     return m_width;
838 }
839
840 int KdenliveDoc::height() const
841 {
842     return m_height;
843 }
844
845 KUrl KdenliveDoc::url() const
846 {
847     return m_url;
848 }
849
850 void KdenliveDoc::setUrl(KUrl url)
851 {
852     m_url = url;
853 }
854
855 void KdenliveDoc::setModified(bool mod)
856 {
857     if (!m_url.isEmpty() && mod && KdenliveSettings::crashrecovery()) {
858         m_autoSaveTimer->start(3000);
859     }
860     if (mod == m_modified) return;
861     m_modified = mod;
862     emit docModified(m_modified);
863 }
864
865 bool KdenliveDoc::isModified() const
866 {
867     return m_modified;
868 }
869
870 const QString KdenliveDoc::description() const
871 {
872     if (m_url.isEmpty())
873         return i18n("Untitled") + " / " + m_profile.description;
874     else
875         return m_url.fileName() + " / " + m_profile.description;
876 }
877
878 bool KdenliveDoc::addClip(QDomElement elem, QString clipId, bool createClipItem)
879 {
880     const QString producerId = clipId.section('_', 0, 0);
881     DocClipBase *clip = m_clipManager->getClipById(producerId);
882
883     if (clip == NULL) {
884         elem.setAttribute("id", producerId);
885         QString path = elem.attribute("resource");
886         QString extension;
887         if (elem.attribute("type").toInt() == SLIDESHOW) {
888             extension = KUrl(path).fileName();
889             path = KUrl(path).directory();
890         }
891
892         if (path.isEmpty() == false && QFile::exists(path) == false && elem.attribute("type").toInt() != TEXT && !elem.hasAttribute("placeholder")) {
893             kDebug() << "// FOUND MISSING CLIP: " << path << ", TYPE: " << elem.attribute("type").toInt();
894             const QString size = elem.attribute("file_size");
895             const QString hash = elem.attribute("file_hash");
896             QString newpath;
897             int action = KMessageBox::No;
898             if (!size.isEmpty() && !hash.isEmpty()) {
899                 if (!m_searchFolder.isEmpty())
900                     newpath = searchFileRecursively(m_searchFolder, size, hash);
901                 else
902                     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")));
903             } else {
904                 if (elem.attribute("type").toInt() == SLIDESHOW) {
905                     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")));
906                     if (res == KMessageBox::Yes)
907                         newpath = KFileDialog::getExistingDirectory(KUrl("kfiledialog:///clipfolder"), kapp->activeWindow(), i18n("Looking for %1", path));
908                     else {
909                         // Abort project loading
910                         action = res;
911                     }
912                 } else {
913                     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")));
914                     if (res == KMessageBox::Yes)
915                         newpath = KFileDialog::getOpenFileName(KUrl("kfiledialog:///clipfolder"), QString(), kapp->activeWindow(), i18n("Looking for %1", path));
916                     else {
917                         // Abort project loading
918                         action = res;
919                     }
920                 }
921             }
922             if (action == KMessageBox::Yes) {
923                 kDebug() << "// ASKED FOR SRCH CLIP: " << clipId;
924                 m_searchFolder = KFileDialog::getExistingDirectory(KUrl("kfiledialog:///clipfolder"), kapp->activeWindow());
925                 if (!m_searchFolder.isEmpty())
926                     newpath = searchFileRecursively(QDir(m_searchFolder), size, hash);
927             } else if (action == KMessageBox::Cancel) {
928                 return false;
929             } else if (action == KMessageBox::No) {
930                 // Keep clip as placeHolder
931                 elem.setAttribute("placeholder", '1');
932             }
933             if (!newpath.isEmpty()) {
934                 if (elem.attribute("type").toInt() == SLIDESHOW)
935                     newpath.append('/' + extension);
936                 elem.setAttribute("resource", newpath);
937                 setNewClipResource(clipId, newpath);
938                 setModified(true);
939             }
940         }
941         clip = new DocClipBase(m_clipManager, elem, producerId);
942         m_clipManager->addClip(clip);
943     }
944
945     if (createClipItem) {
946         emit addProjectClip(clip);
947         //qApp->processEvents();
948     }
949
950     return true;
951 }
952
953 void KdenliveDoc::setNewClipResource(const QString &id, const QString &path)
954 {
955     QDomNodeList prods = m_document.elementsByTagName("producer");
956     int maxprod = prods.count();
957     for (int i = 0; i < maxprod; i++) {
958         QDomNode m = prods.at(i);
959         QString prodId = m.toElement().attribute("id");
960         if (prodId == id || prodId.startsWith(id + '_')) {
961             QDomNodeList params = m.childNodes();
962             for (int j = 0; j < params.count(); j++) {
963                 QDomElement e = params.item(j).toElement();
964                 if (e.attribute("name") == "resource") {
965                     e.firstChild().setNodeValue(path);
966                     break;
967                 }
968             }
969         }
970     }
971 }
972
973 QString KdenliveDoc::searchFileRecursively(const QDir &dir, const QString &matchSize, const QString &matchHash) const
974 {
975     QString foundFileName;
976     QByteArray fileData;
977     QByteArray fileHash;
978     QStringList filesAndDirs = dir.entryList(QDir::Files | QDir::Readable);
979     for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); i++) {
980         QFile file(dir.absoluteFilePath(filesAndDirs.at(i)));
981         if (file.open(QIODevice::ReadOnly)) {
982             if (QString::number(file.size()) == matchSize) {
983                 /*
984                 * 1 MB = 1 second per 450 files (or faster)
985                 * 10 MB = 9 seconds per 450 files (or faster)
986                 */
987                 if (file.size() > 1000000 * 2) {
988                     fileData = file.read(1000000);
989                     if (file.seek(file.size() - 1000000))
990                         fileData.append(file.readAll());
991                 } else
992                     fileData = file.readAll();
993                 file.close();
994                 fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
995                 if (QString(fileHash.toHex()) == matchHash)
996                     return file.fileName();
997             }
998         }
999         kDebug() << filesAndDirs.at(i) << file.size() << fileHash.toHex();
1000     }
1001     filesAndDirs = dir.entryList(QDir::Dirs | QDir::Readable | QDir::Executable | QDir::NoDotAndDotDot);
1002     for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); i++) {
1003         foundFileName = searchFileRecursively(dir.absoluteFilePath(filesAndDirs.at(i)), matchSize, matchHash);
1004         if (!foundFileName.isEmpty())
1005             break;
1006     }
1007     return foundFileName;
1008 }
1009
1010 bool KdenliveDoc::addClipInfo(QDomElement elem, QDomElement orig, QString clipId)
1011 {
1012     DocClipBase *clip = m_clipManager->getClipById(clipId);
1013     if (clip == NULL) {
1014         if (!addClip(elem, clipId, false))
1015             return false;
1016     } else {
1017         QMap <QString, QString> properties;
1018         QDomNamedNodeMap attributes = elem.attributes();
1019         for (int i = 0; i < attributes.count(); i++) {
1020             QString attrname = attributes.item(i).nodeName();
1021             if (attrname != "resource")
1022                 properties.insert(attrname, attributes.item(i).nodeValue());
1023             kDebug() << attrname << " = " << attributes.item(i).nodeValue();
1024         }
1025         clip->setProperties(properties);
1026         emit addProjectClip(clip, false);
1027     }
1028     if (orig != QDomElement()) {
1029         QMap<QString, QString> meta;
1030         for (QDomNode m = orig.firstChild(); !m.isNull(); m = m.nextSibling()) {
1031             QString name = m.toElement().attribute("name");
1032             if (name.startsWith("meta.attr"))
1033                 meta.insert(name.section('.', 2, 3), m.firstChild().nodeValue());
1034         }
1035         if (!meta.isEmpty()) {
1036             if (clip == NULL)
1037                 clip = m_clipManager->getClipById(clipId);
1038             if (clip)
1039                 clip->setMetadata(meta);
1040         }
1041     }
1042     return true;
1043 }
1044
1045
1046 void KdenliveDoc::deleteClip(const QString &clipId)
1047 {
1048     emit signalDeleteProjectClip(clipId);
1049 }
1050
1051 void KdenliveDoc::slotAddClipList(const KUrl::List urls, const QString group, const QString &groupId)
1052 {
1053     m_clipManager->slotAddClipList(urls, group, groupId);
1054     //emit selectLastAddedClip(QString::number(m_clipManager->lastClipId()));
1055     setModified(true);
1056 }
1057
1058
1059 void KdenliveDoc::slotAddClipFile(const KUrl url, const QString group, const QString &groupId)
1060 {
1061     m_clipManager->slotAddClipFile(url, group, groupId);
1062     emit selectLastAddedClip(QString::number(m_clipManager->lastClipId()));
1063     setModified(true);
1064 }
1065
1066 const QString KdenliveDoc::getFreeClipId()
1067 {
1068     return QString::number(m_clipManager->getFreeClipId());
1069 }
1070
1071 DocClipBase *KdenliveDoc::getBaseClip(const QString &clipId)
1072 {
1073     return m_clipManager->getClipById(clipId);
1074 }
1075
1076 void KdenliveDoc::slotCreateXmlClip(const QString &name, const QDomElement xml, QString group, const QString &groupId)
1077 {
1078     m_clipManager->slotAddXmlClipFile(name, xml, group, groupId);
1079     setModified(true);
1080     emit selectLastAddedClip(QString::number(m_clipManager->lastClipId()));
1081 }
1082
1083 void KdenliveDoc::slotCreateColorClip(const QString &name, const QString &color, const QString &duration, QString group, const QString &groupId)
1084 {
1085     m_clipManager->slotAddColorClipFile(name, color, duration, group, groupId);
1086     setModified(true);
1087     emit selectLastAddedClip(QString::number(m_clipManager->lastClipId()));
1088 }
1089
1090 void KdenliveDoc::slotCreateSlideshowClipFile(const QString name, const QString path, int count, const QString duration,
1091         const bool loop, const bool crop, const bool fade,
1092         const QString &luma_duration, const QString &luma_file, const int softness,
1093         const QString &animation, QString group, const QString &groupId)
1094 {
1095     m_clipManager->slotAddSlideshowClipFile(name, path, count, duration, loop,
1096                                             crop, fade, luma_duration,
1097                                             luma_file, softness,
1098                                             animation, group, groupId);
1099     setModified(true);
1100     emit selectLastAddedClip(QString::number(m_clipManager->lastClipId()));
1101 }
1102
1103 void KdenliveDoc::slotCreateTextClip(QString group, const QString &groupId, const QString &templatePath)
1104 {
1105     QString titlesFolder = projectFolder().path(KUrl::AddTrailingSlash) + "titles/";
1106     KStandardDirs::makeDir(titlesFolder);
1107     TitleWidget *dia_ui = new TitleWidget(templatePath, m_timecode, titlesFolder, m_render, kapp->activeWindow());
1108     if (dia_ui->exec() == QDialog::Accepted) {
1109         m_clipManager->slotAddTextClipFile(i18n("Title clip"), dia_ui->outPoint(), dia_ui->xml().toString(), group, groupId);
1110         setModified(true);
1111         emit selectLastAddedClip(QString::number(m_clipManager->lastClipId()));
1112     }
1113     delete dia_ui;
1114 }
1115
1116 void KdenliveDoc::slotCreateTextTemplateClip(QString group, const QString &groupId, KUrl path)
1117 {
1118     QString titlesFolder = projectFolder().path(KUrl::AddTrailingSlash) + "titles/";
1119     if (path.isEmpty()) {
1120         path = KFileDialog::getOpenUrl(KUrl(titlesFolder), "*.kdenlivetitle", kapp->activeWindow(), i18n("Enter Template Path"));
1121     }
1122
1123     if (path.isEmpty()) return;
1124
1125     //TODO: rewrite with new title system (just set resource)
1126     m_clipManager->slotAddTextTemplateClip(i18n("Template title clip"), path, group, groupId);
1127     setModified(true);
1128     emit selectLastAddedClip(QString::number(m_clipManager->lastClipId()));
1129 }
1130
1131 int KdenliveDoc::tracksCount() const
1132 {
1133     return m_tracksList.count();
1134 }
1135
1136 TrackInfo KdenliveDoc::trackInfoAt(int ix) const
1137 {
1138     if (ix < 0 || ix >= m_tracksList.count()) {
1139         kWarning() << "Track INFO outisde of range";
1140         return TrackInfo();
1141     }
1142     return m_tracksList.at(ix);
1143 }
1144
1145 void KdenliveDoc::switchTrackAudio(int ix, bool hide)
1146 {
1147     if (ix < 0 || ix >= m_tracksList.count()) {
1148         kWarning() << "SWITCH Track outisde of range";
1149         return;
1150     }
1151     m_tracksList[ix].isMute = hide; // !m_tracksList.at(ix).isMute;
1152 }
1153
1154 void KdenliveDoc::switchTrackLock(int ix, bool lock)
1155 {
1156     if (ix < 0 || ix >= m_tracksList.count()) {
1157         kWarning() << "Track Lock outisde of range";
1158         return;
1159     }
1160     m_tracksList[ix].isLocked = lock;
1161 }
1162
1163 bool KdenliveDoc::isTrackLocked(int ix) const
1164 {
1165     if (ix < 0 || ix >= m_tracksList.count()) {
1166         kWarning() << "Track Lock outisde of range";
1167         return true;
1168     }
1169     return m_tracksList.at(ix).isLocked;
1170 }
1171
1172 void KdenliveDoc::switchTrackVideo(int ix, bool hide)
1173 {
1174     if (ix < 0 || ix >= m_tracksList.count()) {
1175         kWarning() << "SWITCH Track outisde of range";
1176         return;
1177     }
1178     m_tracksList[ix].isBlind = hide; // !m_tracksList.at(ix).isBlind;
1179 }
1180
1181 void KdenliveDoc::insertTrack(int ix, TrackInfo type)
1182 {
1183     if (ix == -1) m_tracksList << type;
1184     else m_tracksList.insert(ix, type);
1185 }
1186
1187 void KdenliveDoc::deleteTrack(int ix)
1188 {
1189     if (ix < 0 || ix >= m_tracksList.count()) {
1190         kWarning() << "Delete Track outisde of range";
1191         return;
1192     }
1193     m_tracksList.removeAt(ix);
1194 }
1195
1196 void KdenliveDoc::setTrackType(int ix, TrackInfo type)
1197 {
1198     if (ix < 0 || ix >= m_tracksList.count()) {
1199         kWarning() << "SET Track Type outisde of range";
1200         return;
1201     }
1202     m_tracksList[ix].type = type.type;
1203     m_tracksList[ix].isMute = type.isMute;
1204     m_tracksList[ix].isBlind = type.isBlind;
1205     m_tracksList[ix].isLocked = type.isLocked;
1206     m_tracksList[ix].trackName = type.trackName;
1207 }
1208
1209 const QList <TrackInfo> KdenliveDoc::tracksList() const
1210 {
1211     return m_tracksList;
1212 }
1213
1214 QPoint KdenliveDoc::getTracksCount() const
1215 {
1216     int audio = 0;
1217     int video = 0;
1218     foreach(const TrackInfo & info, m_tracksList) {
1219         if (info.type == VIDEOTRACK) video++;
1220         else audio++;
1221     }
1222     return QPoint(video, audio);
1223 }
1224
1225 void KdenliveDoc::cachePixmap(const QString &fileId, const QPixmap &pix) const
1226 {
1227     pix.save(m_projectFolder.path(KUrl::AddTrailingSlash) + "thumbs/" + fileId + ".png");
1228 }
1229
1230 QString KdenliveDoc::getLadspaFile() const
1231 {
1232     int ct = 0;
1233     QString counter = QString::number(ct).rightJustified(5, '0', false);
1234     while (QFile::exists(m_projectFolder.path(KUrl::AddTrailingSlash) + "ladspa/" + counter + ".ladspa")) {
1235         ct++;
1236         counter = QString::number(ct).rightJustified(5, '0', false);
1237     }
1238     return m_projectFolder.path(KUrl::AddTrailingSlash) + "ladspa/" + counter + ".ladspa";
1239 }
1240
1241 bool KdenliveDoc::checkDocumentClips(QDomNodeList infoproducers)
1242 {
1243     DocumentChecker d(infoproducers, m_document);
1244     return (d.hasMissingClips() == false);
1245
1246     /*    int clipType;
1247         QDomElement e;
1248         QString id;
1249         QString resource;
1250         QList <QDomElement> missingClips;
1251         for (int i = 0; i < infoproducers.count(); i++) {
1252             e = infoproducers.item(i).toElement();
1253             clipType = e.attribute("type").toInt();
1254             if (clipType == COLOR) continue;
1255             if (clipType == TEXT) {
1256                 //TODO: Check is clip template is missing (xmltemplate) or hash changed
1257                 continue;
1258             }
1259             id = e.attribute("id");
1260             resource = e.attribute("resource");
1261             if (clipType == SLIDESHOW) resource = KUrl(resource).directory();
1262             if (!KIO::NetAccess::exists(KUrl(resource), KIO::NetAccess::SourceSide, 0)) {
1263                 // Missing clip found
1264                 missingClips.append(e);
1265             } else {
1266                 // Check if the clip has changed
1267                 if (clipType != SLIDESHOW && e.hasAttribute("file_hash")) {
1268                     if (e.attribute("file_hash") != DocClipBase::getHash(e.attribute("resource")))
1269                         e.removeAttribute("file_hash");
1270                 }
1271             }
1272         }
1273         if (missingClips.isEmpty()) return true;
1274         DocumentChecker d(missingClips, m_document);
1275         return (d.exec() == QDialog::Accepted);*/
1276 }
1277
1278 void KdenliveDoc::setDocumentProperty(const QString &name, const QString &value)
1279 {
1280     m_documentProperties[name] = value;
1281 }
1282
1283 const QString KdenliveDoc::getDocumentProperty(const QString &name) const
1284 {
1285     return m_documentProperties.value(name);
1286 }
1287
1288 QMap <QString, QString> KdenliveDoc::getRenderProperties() const
1289 {
1290     QMap <QString, QString> renderProperties;
1291     QMapIterator<QString, QString> i(m_documentProperties);
1292     while (i.hasNext()) {
1293         i.next();
1294         if (i.key().startsWith("render")) renderProperties.insert(i.key(), i.value());
1295     }
1296     return renderProperties;
1297 }
1298
1299 void KdenliveDoc::addTrackEffect(int ix, QDomElement effect)
1300 {
1301     if (ix < 0 || ix >= m_tracksList.count()) {
1302         kWarning() << "Add Track effect outisde of range";
1303         return;
1304     }
1305     effect.setAttribute("kdenlive_ix", m_tracksList.at(ix).effectsList.count() + 1);
1306
1307     // Init parameter value & keyframes if required
1308     QDomNodeList params = effect.elementsByTagName("parameter");
1309     for (int i = 0; i < params.count(); i++) {
1310         QDomElement e = params.item(i).toElement();
1311
1312         // Check if this effect has a variable parameter
1313         if (e.attribute("default").startsWith('%')) {
1314             double evaluatedValue = ProfilesDialog::getStringEval(m_profile, e.attribute("default"));
1315             e.setAttribute("default", evaluatedValue);
1316             if (e.hasAttribute("value") && e.attribute("value").startsWith('%')) {
1317                 e.setAttribute("value", evaluatedValue);
1318             }
1319         }
1320
1321         if (!e.isNull() && (e.attribute("type") == "keyframe" || e.attribute("type") == "simplekeyframe")) {
1322             QString def = e.attribute("default");
1323             // Effect has a keyframe type parameter, we need to set the values
1324             if (e.attribute("keyframes").isEmpty()) {
1325                 e.setAttribute("keyframes", "0:" + def + ';');
1326                 kDebug() << "///// EFFECT KEYFRAMES INITED: " << e.attribute("keyframes");
1327                 //break;
1328             }
1329         }
1330     }
1331
1332     m_tracksList[ix].effectsList.append(effect);
1333 }
1334
1335 void KdenliveDoc::removeTrackEffect(int ix, QDomElement effect)
1336 {
1337     if (ix < 0 || ix >= m_tracksList.count()) {
1338         kWarning() << "Remove Track effect outisde of range";
1339         return;
1340     }
1341     QString index;
1342     QString toRemove = effect.attribute("kdenlive_ix");
1343     for (int i = 0; i < m_tracksList.at(ix).effectsList.count(); ++i) {
1344         index = m_tracksList.at(ix).effectsList.at(i).attribute("kdenlive_ix");
1345         if (toRemove == index) {
1346             m_tracksList[ix].effectsList.removeAt(i);
1347             i--;
1348         } else if (index.toInt() > toRemove.toInt()) {
1349             m_tracksList[ix].effectsList.item(i).setAttribute("kdenlive_ix", index.toInt() - 1);
1350         }
1351     }
1352 }
1353
1354 void KdenliveDoc::setTrackEffect(int trackIndex, int effectIndex, QDomElement effect)
1355 {
1356     if (trackIndex < 0 || trackIndex >= m_tracksList.count()) {
1357         kWarning() << "Set Track effect outisde of range";
1358         return;
1359     }
1360     if (effectIndex < 0 || effectIndex > (m_tracksList.at(trackIndex).effectsList.count() - 1) || effect.isNull()) {
1361         kDebug() << "Invalid effect index: " << effectIndex;
1362         return;
1363     }
1364     effect.setAttribute("kdenlive_ix", effectIndex + 1);
1365     m_tracksList[trackIndex].effectsList.replace(effectIndex, effect);
1366 }
1367
1368 const EffectsList KdenliveDoc::getTrackEffects(int ix)
1369 {
1370     if (ix < 0 || ix >= m_tracksList.count()) {
1371         kWarning() << "Get Track effects outisde of range";
1372         return EffectsList();
1373     }
1374     return m_tracksList.at(ix).effectsList;
1375 }
1376
1377 QDomElement KdenliveDoc::getTrackEffect(int trackIndex, int effectIndex) const
1378 {
1379     if (trackIndex < 0 || trackIndex >= m_tracksList.count()) {
1380         kWarning() << "Get Track effect outisde of range";
1381         return QDomElement();
1382     }
1383     EffectsList list = m_tracksList.at(trackIndex).effectsList;
1384     if (effectIndex > list.count() - 1 || effectIndex < 0 || list.at(effectIndex).isNull()) return QDomElement();
1385     return list.at(effectIndex).cloneNode().toElement();
1386 }
1387
1388 #include "kdenlivedoc.moc"
1389