]> git.sesse.net Git - kdenlive/blob - src/clipmanager.cpp
Fix freeze on reloading a missing clip, don't reload twice missing clips that were...
[kdenlive] / src / clipmanager.cpp
1 /***************************************************************************
2  *   Copyright (C) 2008 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 "clipmanager.h"
22 #include "commands/addclipcommand.h"
23 #include "kdenlivesettings.h"
24 #include "docclipbase.h"
25 #include "kdenlivedoc.h"
26 #include "abstractclipitem.h"
27 #include "abstractgroupitem.h"
28 #include "titledocument.h"
29 #include "kthumb.h"
30
31 #include <mlt++/Mlt.h>
32
33 #include <KDebug>
34 #include <KMessageBox>
35 #include <KFileDialog>
36 #include <KApplication>
37 #include <kio/netaccess.h>
38
39 #include <QGraphicsItemGroup>
40 #include <QtConcurrentRun>
41
42 #include <KFileMetaInfo>
43
44 ClipManager::ClipManager(KdenliveDoc *doc) :
45     QObject(),
46     m_audioThumbsQueue(),
47     m_doc(doc),
48     m_abortThumb(false),
49     m_closing(false),
50     m_abortAudioThumb(false)
51 {
52     m_clipIdCounter = 1;
53     m_folderIdCounter = 1;
54     m_modifiedTimer.setInterval(1500);
55     connect(&m_fileWatcher, SIGNAL(dirty(const QString &)), this, SLOT(slotClipModified(const QString &)));
56     connect(&m_fileWatcher, SIGNAL(deleted(const QString &)), this, SLOT(slotClipMissing(const QString &)));
57
58     // Seems like a dirty signal is emitted anyways when a watched file is created, so don't react twice.
59     //connect(&m_fileWatcher, SIGNAL(created(const QString &)), this, SLOT(slotClipAvailable(const QString &)));
60     connect(&m_modifiedTimer, SIGNAL(timeout()), this, SLOT(slotProcessModifiedClips()));
61
62 #if KDE_IS_VERSION(4,5,0)
63     KImageCache::deleteCache("kdenlive-thumbs");
64     pixmapCache = new KImageCache("kdenlive-thumbs", 1000000);
65     pixmapCache->setEvictionPolicy(KSharedDataCache::EvictOldest);
66 #endif
67 }
68
69 ClipManager::~ClipManager()
70 {
71     m_closing = true;
72     m_abortThumb = true;
73     m_abortAudioThumb = true;
74     m_thumbsThread.waitForFinished();
75     m_audioThumbsThread.waitForFinished();
76     m_thumbsMutex.lock();
77     m_requestedThumbs.clear();
78     m_audioThumbsQueue.clear();
79     m_thumbsMutex.unlock();
80
81     qDeleteAll(m_clipList);
82     m_clipList.clear();
83 #if KDE_IS_VERSION(4,5,0)
84     delete pixmapCache;
85 #endif
86 }
87
88 void ClipManager::clear()
89 {
90     m_abortThumb = true;
91     m_abortAudioThumb = true;
92     m_thumbsThread.waitForFinished();
93     m_audioThumbsThread.waitForFinished();
94     m_thumbsMutex.lock();
95     m_requestedThumbs.clear();
96     m_audioThumbsQueue.clear();
97     m_thumbsMutex.unlock();
98     m_abortThumb = false;
99     m_abortAudioThumb = false;
100     m_folderList.clear();
101     m_modifiedClips.clear();
102     qDeleteAll(m_clipList);
103     m_clipList.clear();
104     m_clipIdCounter = 1;
105     m_folderIdCounter = 1;
106 #if KDE_IS_VERSION(4,5,0)
107     pixmapCache->clear();
108 #endif
109 }
110
111 void ClipManager::clearCache()
112 {
113 #if KDE_IS_VERSION(4,5,0)
114     pixmapCache->clear();
115 #endif
116 }
117
118 void ClipManager::requestThumbs(const QString id, QList <int> frames)
119 {
120     m_thumbsMutex.lock();
121     foreach (int frame, frames) {
122         m_requestedThumbs.insertMulti(id, frame);
123     }
124     m_thumbsMutex.unlock();
125     if (!m_thumbsThread.isRunning() && !m_abortThumb) {
126         m_thumbsThread = QtConcurrent::run(this, &ClipManager::slotGetThumbs);
127     }
128 }
129
130 void ClipManager::stopThumbs(const QString &id)
131 {
132     if (m_closing || (m_requestedThumbs.isEmpty() && m_processingThumbId != id && m_audioThumbsQueue.isEmpty() && m_processingAudioThumbId != id)) return;
133     // Abort video thumbs for this clip
134     m_abortThumb = true;
135     m_thumbsThread.waitForFinished();
136     m_thumbsMutex.lock();
137     m_requestedThumbs.remove(id);
138     m_audioThumbsQueue.removeAll(id);
139     m_thumbsMutex.unlock();
140     m_abortThumb = false;
141
142     // Abort audio thumbs for this clip
143     if (m_processingAudioThumbId == id) {
144         m_abortAudioThumb = true;
145         m_audioThumbsThread.waitForFinished();
146         m_abortAudioThumb = false;
147     }
148     
149     if (!m_thumbsThread.isRunning() && !m_requestedThumbs.isEmpty()) {
150         m_thumbsThread = QtConcurrent::run(this, &ClipManager::slotGetThumbs);
151     }
152     
153     if (!m_audioThumbsThread.isRunning() && !m_audioThumbsQueue.isEmpty()) {
154         m_audioThumbsThread = QtConcurrent::run(this, &ClipManager::slotGetAudioThumbs);
155     }
156 }
157
158 void ClipManager::slotGetThumbs()
159 {
160     QMap<QString, int>::const_iterator i;
161     int max;
162     int done = 0;
163     while (!m_requestedThumbs.isEmpty() && !m_abortThumb) {
164         m_thumbsMutex.lock();
165         i = m_requestedThumbs.constBegin();
166         m_processingThumbId = i.key();
167         QList<int> values = m_requestedThumbs.values(m_processingThumbId);
168         m_requestedThumbs.remove(m_processingThumbId);
169         m_thumbsMutex.unlock();
170         qSort(values);
171         DocClipBase *clip = getClipById(m_processingThumbId);
172         if (!clip) continue;
173         max = m_requestedThumbs.size() + values.count();
174         while (!values.isEmpty() && clip->thumbProducer() && !m_abortThumb) {
175             clip->thumbProducer()->getThumb(values.takeFirst());
176             done++;
177             if (max > 3) emit displayMessage(i18n("Loading thumbnails"), 100 * done / max);
178         }
179     }
180     m_processingThumbId.clear();
181     emit displayMessage(QString(), -1);
182 }
183
184 void ClipManager::checkAudioThumbs()
185 {
186     if (!KdenliveSettings::audiothumbnails()) {
187         if (m_audioThumbsThread.isRunning()) {
188             m_abortAudioThumb = true;
189             m_thumbsMutex.lock();
190             m_audioThumbsQueue.clear();
191             m_thumbsMutex.unlock();
192             m_audioThumbsThread.waitForFinished();
193             m_abortAudioThumb = false;
194         }
195         return;
196     }
197
198     m_thumbsMutex.lock();
199     for (int i = 0; i < m_clipList.count(); i++) {
200         DocClipBase *clip = m_clipList.at(i);
201         if (clip->hasAudioThumb() && !clip->audioThumbCreated())
202             m_audioThumbsQueue.append(m_clipList.at(i)->getId());
203     }
204     m_thumbsMutex.unlock();
205     if (!m_audioThumbsThread.isRunning() && !m_audioThumbsQueue.isEmpty()) {
206         m_audioThumbsThread = QtConcurrent::run(this, &ClipManager::slotGetAudioThumbs);
207     }
208 }
209
210 void ClipManager::askForAudioThumb(const QString &id)
211 {
212     DocClipBase *clip = getClipById(id);
213     if (clip && KdenliveSettings::audiothumbnails() && (clip->hasAudioThumb())) {
214         m_thumbsMutex.lock();
215         if (!m_audioThumbsQueue.contains(id)) m_audioThumbsQueue.append(id);
216         m_thumbsMutex.unlock();
217         if (!m_audioThumbsThread.isRunning()) m_audioThumbsThread = QtConcurrent::run(this, &ClipManager::slotGetAudioThumbs);
218     }
219 }
220
221 void ClipManager::slotGetAudioThumbs()
222 {
223     Mlt::Profile prof((char*) KdenliveSettings::current_profile().toUtf8().constData());
224     mlt_audio_format audioFormat = mlt_audio_pcm;
225     while (!m_abortAudioThumb && !m_audioThumbsQueue.isEmpty()) {
226         m_thumbsMutex.lock();
227         m_processingAudioThumbId = m_audioThumbsQueue.takeFirst();
228         m_thumbsMutex.unlock();
229         DocClipBase *clip = getClipById(m_processingAudioThumbId);
230         if (!clip || clip->audioThumbCreated()) continue;
231         KUrl url = clip->fileURL();
232         QString hash = clip->getClipHash();
233         if (hash.isEmpty()) continue;
234         QString audioPath = projectFolder() + "/thumbs/" + hash + ".thumb";
235         double lengthInFrames = clip->duration().frames(m_doc->fps());
236         //FIXME: should this be hardcoded??
237         int channels = 2;
238         int frequency = 48000;
239         int arrayWidth = 20;
240         double frame = 0.0;
241         audioByteArray storeIn;
242         QFile f(audioPath);
243         if (QFileInfo(audioPath).size() > 0 && f.open(QIODevice::ReadOnly)) {
244             const QByteArray channelarray = f.readAll();
245             f.close();
246             if (channelarray.size() != arrayWidth*(frame + lengthInFrames) * channels) {
247                 kDebug() << "--- BROKEN THUMB FOR: " << url.fileName() << " ---------------------- ";
248                 f.remove();
249                 continue;
250             }
251             kDebug() << "reading audio thumbs from file";
252
253             int h1 = arrayWidth * channels;
254             int h2 = (int) frame * h1;
255             int h3;
256             for (int z = (int) frame; z < (int)(frame + lengthInFrames) && !m_abortAudioThumb; z++) {
257                 h3 = 0;
258                 for (int c = 0; c < channels; c++) {
259                     QByteArray audioArray(arrayWidth, '\x00');
260                     for (int i = 0; i < arrayWidth; i++) {
261                         audioArray[i] = channelarray.at(h2 + h3 + i);
262                     }
263                     h3 += arrayWidth;
264                     storeIn[z][c] = audioArray;
265                 }
266                 h2 += h1;
267             }
268             if (!m_abortAudioThumb) clip->updateAudioThumbnail(storeIn);
269             continue;
270         } 
271         
272         if (!f.open(QIODevice::WriteOnly)) {
273             kDebug() << "++++++++  ERROR WRITING TO FILE: " << audioPath;
274             kDebug() << "++++++++  DISABLING AUDIO THUMBS";
275             m_thumbsMutex.lock();
276             m_audioThumbsQueue.clear();
277             m_thumbsMutex.unlock();
278             KdenliveSettings::setAudiothumbnails(false);
279             break;
280         }
281
282         Mlt::Producer producer(prof, url.path().toUtf8().constData());
283         if (!producer.is_valid()) {
284             kDebug() << "++++++++  INVALID CLIP: " << url.path();
285             continue;
286         }
287         
288         producer.set("video_index", "-1");
289
290         if (KdenliveSettings::normaliseaudiothumbs()) {
291             Mlt::Filter m_convert(prof, "volume");
292             m_convert.set("gain", "normalise");
293             producer.attach(m_convert);
294         }
295
296         int last_val = 0;
297         int val = 0;
298         double framesPerSecond = mlt_producer_get_fps(producer.get_producer());
299         Mlt::Frame *mlt_frame;
300
301         for (int z = (int) frame; z < (int)(frame + lengthInFrames) && producer.is_valid() &&  !m_abortAudioThumb; z++) {
302             val = (int)((z - frame) / (frame + lengthInFrames) * 100.0);
303             if (last_val != val && val > 1) {
304                 setThumbsProgress(i18n("Creating audio thumbnail for %1", url.fileName()), val);
305                 last_val = val;
306             }
307             producer.seek(z);
308             mlt_frame = producer.get_frame();
309             if (mlt_frame && mlt_frame->is_valid()) {
310                 int samples = mlt_sample_calculator(framesPerSecond, frequency, mlt_frame->get_position());
311                 qint16* pcm = static_cast<qint16*>(mlt_frame->get_audio(audioFormat, frequency, channels, samples));
312                 for (int c = 0; c < channels; c++) {
313                     QByteArray audioArray;
314                     audioArray.resize(arrayWidth);
315                     for (int i = 0; i < audioArray.size(); i++) {
316                         audioArray[i] = ((*(pcm + c + i * samples / audioArray.size())) >> 9) + 127 / 2 ;
317                     }
318                     f.write(audioArray);
319                     storeIn[z][c] = audioArray;
320                 }
321             } else {
322                 f.write(QByteArray(arrayWidth, '\x00'));
323             }
324             delete mlt_frame;
325         }
326         f.close();
327         setThumbsProgress(i18n("Creating audio thumbnail for %1", url.fileName()), -1);
328         if (m_abortAudioThumb) {
329             f.remove();
330         } else {
331             clip->updateAudioThumbnail(storeIn);
332         }
333     }
334     m_processingAudioThumbId.clear();
335 }
336
337 void ClipManager::setThumbsProgress(const QString &message, int progress)
338 {
339     m_doc->setThumbsProgress(message, progress);
340 }
341
342 QList <DocClipBase*> ClipManager::documentClipList() const
343 {
344     return m_clipList;
345 }
346
347 QMap <QString, QString> ClipManager::documentFolderList() const
348 {
349     return m_folderList;
350 }
351
352 void ClipManager::addClip(DocClipBase *clip)
353 {
354     m_clipList.append(clip);
355     if (clip->clipType() != COLOR && clip->clipType() != SLIDESHOW  && !clip->fileURL().isEmpty()) {
356         // listen for file change
357         //kDebug() << "// LISTEN FOR: " << clip->fileURL().path();
358         m_fileWatcher.addFile(clip->fileURL().path());
359     }
360     const QString id = clip->getId();
361     if (id.toInt() >= m_clipIdCounter) m_clipIdCounter = id.toInt() + 1;
362     const QString gid = clip->getProperty("groupid");
363     if (!gid.isEmpty() && gid.toInt() >= m_folderIdCounter) m_folderIdCounter = gid.toInt() + 1;
364 }
365
366 void ClipManager::slotDeleteClips(QStringList ids)
367 {
368     QUndoCommand *delClips = new QUndoCommand();
369     delClips->setText(i18np("Delete clip", "Delete clips", ids.size()));
370
371     for (int i = 0; i < ids.size(); i++) {
372         DocClipBase *clip = getClipById(ids.at(i));
373         if (clip) {
374             new AddClipCommand(m_doc, clip->toXML(), ids.at(i), false, delClips);
375         }
376     }
377     m_doc->commandStack()->push(delClips);
378 }
379
380 void ClipManager::deleteClip(const QString &clipId)
381 {
382     for (int i = 0; i < m_clipList.count(); i++) {
383         if (m_clipList.at(i)->getId() == clipId) {
384             if (m_clipList.at(i)->clipType() != COLOR && m_clipList.at(i)->clipType() != SLIDESHOW  && !m_clipList.at(i)->fileURL().isEmpty()) {
385                 //if (m_clipList.at(i)->clipType() == IMAGE || m_clipList.at(i)->clipType() == AUDIO || (m_clipList.at(i)->clipType() == TEXT && !m_clipList.at(i)->fileURL().isEmpty())) {
386                 // listen for file change
387                 m_fileWatcher.removeFile(m_clipList.at(i)->fileURL().path());
388             }
389             DocClipBase *clip = m_clipList.takeAt(i);
390             delete clip;
391             clip = NULL;
392             break;
393         }
394     }
395 }
396
397 DocClipBase *ClipManager::getClipAt(int pos)
398 {
399     return m_clipList.at(pos);
400 }
401
402 DocClipBase *ClipManager::getClipById(QString clipId)
403 {
404     //kDebug() << "++++  CLIP MAN, LOOKING FOR CLIP ID: " << clipId;
405     clipId = clipId.section('_', 0, 0);
406     for (int i = 0; i < m_clipList.count(); i++) {
407         if (m_clipList.at(i)->getId() == clipId) {
408             //kDebug() << "++++  CLIP MAN, FOUND FOR CLIP ID: " << clipId;
409             return m_clipList.at(i);
410         }
411     }
412     return NULL;
413 }
414
415 const QList <DocClipBase *> ClipManager::getClipByResource(QString resource)
416 {
417     QList <DocClipBase *> list;
418     QString clipResource;
419     QString proxyResource;
420     for (int i = 0; i < m_clipList.count(); i++) {
421         clipResource = m_clipList.at(i)->getProperty("resource");
422         proxyResource = m_clipList.at(i)->getProperty("proxy");
423         if (clipResource.isEmpty()) clipResource = m_clipList.at(i)->getProperty("colour");
424         if (clipResource == resource || proxyResource == resource) {
425             list.append(m_clipList.at(i));
426         }
427     }
428     return list;
429 }
430
431
432 void ClipManager::clearUnusedProducers()
433 {
434     for (int i = 0; i < m_clipList.count(); i++) {
435         if (m_clipList.at(i)->numReferences() == 0) m_clipList.at(i)->deleteProducers();
436     }
437 }
438
439 void ClipManager::resetProducersList(const QList <Mlt::Producer *> prods, bool displayRatioChanged, bool fpsChanged)
440 {
441     for (int i = 0; i < m_clipList.count(); i++) {
442         if (m_clipList.at(i)->numReferences() > 0 || displayRatioChanged || fpsChanged) {
443             m_clipList.at(i)->deleteProducers();
444         }
445     }
446     QString id;
447     Mlt::Producer *prod;
448     QStringList brokenClips;
449     for (int i = 0; i < prods.count(); i++) {
450         prod = prods.at(i);
451         id = prod->get("id");
452         if (id.contains('_')) id = id.section('_', 0, 0);
453         DocClipBase *clip = getClipById(id);
454         QString markup = prod->get("markup");
455         if (prod->is_blank() || !prod->is_valid() || !markup.isEmpty()) {
456             // The clip is broken (missing proxy or source clip)
457             kDebug()<<"// WARNING, CLIP "<<id<<" Cannot be loaded";
458             brokenClips << id;
459         }
460         else if (clip) {
461             clip->setProducer(prod, false, true);
462         }
463     }
464     emit checkAllClips(displayRatioChanged, fpsChanged, brokenClips);
465 }
466
467 void ClipManager::slotAddClipList(const KUrl::List urls, const QString &group, const QString &groupId, const QString &comment)
468 {
469     QUndoCommand *addClips = new QUndoCommand();
470     foreach(const KUrl & file, urls) {
471         if (QFile::exists(file.path())) {//KIO::NetAccess::exists(file, KIO::NetAccess::SourceSide, NULL)) {
472             if (!getClipByResource(file.path()).empty()) {
473                 if (KMessageBox::warningContinueCancel(kapp->activeWindow(), i18n("Clip <b>%1</b><br />already exists in project, what do you want to do?", file.path()), i18n("Clip already exists")) == KMessageBox::Cancel)
474                     continue;
475             }
476             kDebug() << "Adding clip: " << file.path();
477             QDomDocument doc;
478             QDomElement prod = doc.createElement("producer");
479             doc.appendChild(prod);
480             prod.setAttribute("resource", file.path());
481             uint id = m_clipIdCounter++;
482             prod.setAttribute("id", QString::number(id));
483             if (!comment.isEmpty()) prod.setAttribute("description", comment);
484             if (!group.isEmpty()) {
485                 prod.setAttribute("groupname", group);
486                 prod.setAttribute("groupid", groupId);
487             }
488             KMimeType::Ptr type = KMimeType::findByUrl(file);
489             if (type->name().startsWith("image/")) {
490                 prod.setAttribute("type", (int) IMAGE);
491                 prod.setAttribute("in", 0);
492                 prod.setAttribute("out", m_doc->getFramePos(KdenliveSettings::image_duration()));
493                 if (KdenliveSettings::autoimagetransparency()) prod.setAttribute("transparency", 1);
494                 // Read EXIF metadata for JPEG
495                 if (type->is("image/jpeg")) {
496                     KFileMetaInfo metaInfo(file.path(), QString("image/jpeg"), KFileMetaInfo::TechnicalInfo);
497                     const QHash<QString, KFileMetaInfoItem> metaInfoItems = metaInfo.items();
498                     foreach(const KFileMetaInfoItem & metaInfoItem, metaInfoItems) {
499                         prod.setAttribute("meta.attr." + metaInfoItem.name().section("#", 1), metaInfoItem.value().toString());
500                     }
501                 }
502             } else if (type->is("application/x-kdenlivetitle")) {
503                 // opening a title file
504                 QDomDocument txtdoc("titledocument");
505                 QFile txtfile(file.path());
506                 if (txtfile.open(QIODevice::ReadOnly) && txtdoc.setContent(&txtfile)) {
507                     txtfile.close();
508                     prod.setAttribute("type", (int) TEXT);
509                     // extract embeded images
510                     QDomNodeList items = txtdoc.elementsByTagName("content");
511                     for (int i = 0; i < items.count() ; i++) {
512                         QDomElement content = items.item(i).toElement();
513                         if (content.hasAttribute("base64")) {
514                             QString titlesFolder = m_doc->projectFolder().path(KUrl::AddTrailingSlash) + "titles/";
515                             QString path = TitleDocument::extractBase64Image(titlesFolder, content.attribute("base64"));
516                             if (!path.isEmpty()) {
517                                 content.setAttribute("url", path);
518                                 content.removeAttribute("base64");
519                             }
520                         }
521                     }
522                     QString titleData = txtdoc.toString();
523                     prod.setAttribute("xmldata", titleData);
524                     prod.setAttribute("transparency", 1);
525                     prod.setAttribute("in", 0);
526                     int out = txtdoc.documentElement().attribute("out").toInt();
527                     if (out > 0)
528                         prod.setAttribute("out", out);
529                     else
530                         prod.setAttribute("out", m_doc->getFramePos(KdenliveSettings::title_duration()));
531                 } else
532                     txtfile.close();
533             }
534             new AddClipCommand(m_doc, doc.documentElement(), QString::number(id), true, addClips);
535         }
536         else kDebug()<<"// CANNOT READ FILE: "<<file;
537     }
538     if (addClips->childCount() > 0) {
539         addClips->setText(i18np("Add clip", "Add clips", addClips->childCount()));
540         m_doc->commandStack()->push(addClips);
541     }
542 }
543
544 void ClipManager::slotAddClipFile(const KUrl &url, const QString &group, const QString &groupId, const QString &comment)
545 {
546     slotAddClipList(KUrl::List(url), group, groupId, comment);
547 }
548
549 void ClipManager::slotAddXmlClipFile(const QString &name, const QDomElement &xml, const QString &group, const QString &groupId)
550 {
551     QDomDocument doc;
552     doc.appendChild(doc.importNode(xml, true));
553     QDomElement prod = doc.documentElement();
554     prod.setAttribute("type", (int) PLAYLIST);
555     uint id = m_clipIdCounter++;
556     prod.setAttribute("id", QString::number(id));
557     prod.setAttribute("name", name);
558     if (!group.isEmpty()) {
559         prod.setAttribute("groupname", group);
560         prod.setAttribute("groupid", groupId);
561     }
562     AddClipCommand *command = new AddClipCommand(m_doc, doc.documentElement(), QString::number(id), true);
563     m_doc->commandStack()->push(command);
564 }
565
566 void ClipManager::slotAddColorClipFile(const QString &name, const QString &color, QString duration, const QString &group, const QString &groupId)
567 {
568     QDomDocument doc;
569     QDomElement prod = doc.createElement("producer");
570     doc.appendChild(prod);
571     prod.setAttribute("mlt_service", "colour");
572     prod.setAttribute("colour", color);
573     prod.setAttribute("type", (int) COLOR);
574     uint id = m_clipIdCounter++;
575     prod.setAttribute("id", QString::number(id));
576     prod.setAttribute("in", "0");
577     prod.setAttribute("out", m_doc->getFramePos(duration));
578     prod.setAttribute("name", name);
579     if (!group.isEmpty()) {
580         prod.setAttribute("groupname", group);
581         prod.setAttribute("groupid", groupId);
582     }
583     AddClipCommand *command = new AddClipCommand(m_doc, doc.documentElement(), QString::number(id), true);
584     m_doc->commandStack()->push(command);
585 }
586
587 void ClipManager::slotAddSlideshowClipFile(QMap <QString, QString> properties, const QString &group, const QString &groupId)
588 {
589     QDomDocument doc;
590     QDomElement prod = doc.createElement("producer");
591     doc.appendChild(prod);
592     QMap<QString, QString>::const_iterator i = properties.constBegin();
593     while (i != properties.constEnd()) {
594         prod.setAttribute(i.key(), i.value());
595         ++i;
596     }
597     prod.setAttribute("type", (int) SLIDESHOW);
598     uint id = m_clipIdCounter++;
599     if (!group.isEmpty()) {
600         prod.setAttribute("groupname", group);
601         prod.setAttribute("groupid", groupId);
602     }
603     AddClipCommand *command = new AddClipCommand(m_doc, doc.documentElement(), QString::number(id), true);
604     m_doc->commandStack()->push(command);
605 }
606
607
608
609 void ClipManager::slotAddTextClipFile(const QString &titleName, int out, const QString &xml, const QString &group, const QString &groupId)
610 {
611     QDomDocument doc;
612     QDomElement prod = doc.createElement("producer");
613     doc.appendChild(prod);
614     //prod.setAttribute("resource", imagePath);
615     prod.setAttribute("name", titleName);
616     prod.setAttribute("xmldata", xml);
617     uint id = m_clipIdCounter++;
618     prod.setAttribute("id", QString::number(id));
619     if (!group.isEmpty()) {
620         prod.setAttribute("groupname", group);
621         prod.setAttribute("groupid", groupId);
622     }
623     prod.setAttribute("type", (int) TEXT);
624     prod.setAttribute("transparency", "1");
625     prod.setAttribute("in", "0");
626     prod.setAttribute("out", out);
627     AddClipCommand *command = new AddClipCommand(m_doc, doc.documentElement(), QString::number(id), true);
628     m_doc->commandStack()->push(command);
629 }
630
631 void ClipManager::slotAddTextTemplateClip(QString titleName, const KUrl &path, const QString &group, const QString &groupId)
632 {
633     QDomDocument doc;
634     QDomElement prod = doc.createElement("producer");
635     doc.appendChild(prod);
636     prod.setAttribute("name", titleName);
637     prod.setAttribute("resource", path.path());
638     uint id = m_clipIdCounter++;
639     prod.setAttribute("id", QString::number(id));
640     if (!group.isEmpty()) {
641         prod.setAttribute("groupname", group);
642         prod.setAttribute("groupid", groupId);
643     }
644     prod.setAttribute("type", (int) TEXT);
645     prod.setAttribute("transparency", "1");
646     prod.setAttribute("in", "0");
647
648     int out = 0;
649     QDomDocument titledoc;
650     QFile txtfile(path.path());
651     if (txtfile.open(QIODevice::ReadOnly) && titledoc.setContent(&txtfile)) {
652         txtfile.close();
653         out = titledoc.documentElement().attribute("out").toInt();
654     } else txtfile.close();
655
656     if (out == 0) out = m_doc->getFramePos(KdenliveSettings::image_duration());
657     prod.setAttribute("out", out);
658
659     AddClipCommand *command = new AddClipCommand(m_doc, doc.documentElement(), QString::number(id), true);
660     m_doc->commandStack()->push(command);
661 }
662
663 int ClipManager::getFreeClipId()
664 {
665     return m_clipIdCounter++;
666 }
667
668 int ClipManager::getFreeFolderId()
669 {
670     return m_folderIdCounter++;
671 }
672
673 int ClipManager::lastClipId() const
674 {
675     return m_clipIdCounter - 1;
676 }
677
678 QString ClipManager::projectFolder() const
679 {
680     return m_doc->projectFolder().path();
681 }
682
683 void ClipManager::addFolder(const QString &id, const QString &name)
684 {
685     m_folderList.insert(id, name);
686 }
687
688 void ClipManager::deleteFolder(const QString &id)
689 {
690     m_folderList.remove(id);
691 }
692
693 AbstractGroupItem *ClipManager::createGroup()
694 {
695     AbstractGroupItem *group = new AbstractGroupItem(m_doc->fps());
696     m_groupsList.append(group);
697     return group;
698 }
699
700 void ClipManager::removeGroup(AbstractGroupItem *group)
701 {
702     m_groupsList.removeAll(group);
703 }
704
705 QDomElement ClipManager::groupsXml() const
706 {
707     QDomDocument doc;
708     QDomElement groups = doc.createElement("groups");
709     doc.appendChild(groups);
710     for (int i = 0; i < m_groupsList.count(); i++) {
711         QDomElement group = doc.createElement("group");
712         groups.appendChild(group);
713         QList <QGraphicsItem *> children = m_groupsList.at(i)->childItems();
714         for (int j = 0; j < children.count(); j++) {
715             if (children.at(j)->type() == AVWIDGET || children.at(j)->type() == TRANSITIONWIDGET) {
716                 AbstractClipItem *item = static_cast <AbstractClipItem *>(children.at(j));
717                 ItemInfo info = item->info();
718                 if (item->type() == AVWIDGET) {
719                     QDomElement clip = doc.createElement("clipitem");
720                     clip.setAttribute("track", info.track);
721                     clip.setAttribute("position", info.startPos.frames(m_doc->fps()));
722                     group.appendChild(clip);
723                 } else if (item->type() == TRANSITIONWIDGET) {
724                     QDomElement clip = doc.createElement("transitionitem");
725                     clip.setAttribute("track", info.track);
726                     clip.setAttribute("position", info.startPos.frames(m_doc->fps()));
727                     group.appendChild(clip);
728                 }
729             }
730         }
731     }
732     return doc.documentElement();
733 }
734
735
736 void ClipManager::slotClipModified(const QString &path)
737 {
738     //kDebug() << "// CLIP: " << path << " WAS MODIFIED";
739     const QList <DocClipBase *> list = getClipByResource(path);
740     for (int i = 0; i < list.count(); i++) {
741         DocClipBase *clip = list.at(i);
742         if (clip != NULL) {
743             QString id = clip->getId();
744             if (!m_modifiedClips.contains(id))
745                 emit modifiedClip(id);
746             m_modifiedClips[id] = QTime::currentTime();
747         }
748     }
749     if (!m_modifiedTimer.isActive()) m_modifiedTimer.start();
750 }
751
752 void ClipManager::slotProcessModifiedClips()
753 {
754     if (!m_modifiedClips.isEmpty()) {
755         QMapIterator<QString, QTime> i(m_modifiedClips);
756         while (i.hasNext()) {
757             i.next();
758             if (QTime::currentTime().msecsTo(i.value()) <= -1500) {
759                 emit reloadClip(i.key());
760                 m_modifiedClips.remove(i.key());
761                 break;
762             }
763         }
764     }
765     if (m_modifiedClips.isEmpty()) m_modifiedTimer.stop();
766 }
767
768 void ClipManager::slotClipMissing(const QString &path)
769 {
770     // kDebug() << "// CLIP: " << path << " WAS MISSING";
771     const QList <DocClipBase *> list = getClipByResource(path);
772     for (int i = 0; i < list.count(); i++) {
773         DocClipBase *clip = list.at(i);
774         if (clip != NULL) emit missingClip(clip->getId());
775     }
776 }
777
778 void ClipManager::slotClipAvailable(const QString &path)
779 {
780     // kDebug() << "// CLIP: " << path << " WAS ADDED";
781     const QList <DocClipBase *> list = getClipByResource(path);
782     for (int i = 0; i < list.count(); i++) {
783         DocClipBase *clip = list.at(i);
784         if (clip != NULL) emit availableClip(clip->getId());
785     }
786 }
787
788 int ClipManager::clipsCount() const
789 {
790     return m_clipList.count();
791 }
792
793