]> git.sesse.net Git - kdenlive/blob - src/clipmanager.cpp
Make sure we only use one thread to create video thumbnails
[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 "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_generatingAudioId(),
49     m_abortThumb(false)
50 {
51     m_clipIdCounter = 1;
52     m_folderIdCounter = 1;
53     m_modifiedTimer.setInterval(1500);
54     connect(&m_fileWatcher, SIGNAL(dirty(const QString &)), this, SLOT(slotClipModified(const QString &)));
55     connect(&m_fileWatcher, SIGNAL(deleted(const QString &)), this, SLOT(slotClipMissing(const QString &)));
56     connect(&m_fileWatcher, SIGNAL(created(const QString &)), this, SLOT(slotClipAvailable(const QString &)));
57     connect(&m_modifiedTimer, SIGNAL(timeout()), this, SLOT(slotProcessModifiedClips()));
58
59 #if KDE_IS_VERSION(4,5,0)
60     KImageCache::deleteCache("kdenlive-thumbs");
61     pixmapCache = new KImageCache("kdenlive-thumbs", 1000000);
62     pixmapCache->setEvictionPolicy(KSharedDataCache::EvictOldest);
63 #endif
64 }
65
66 ClipManager::~ClipManager()
67 {
68     m_thumbsMutex.lock();
69     m_requestedThumbs.clear();
70     m_thumbsMutex.unlock();
71     m_abortThumb = true;
72     m_thumbsThread.waitForFinished();
73     m_audioThumbsQueue.clear();
74     m_generatingAudioId.clear();
75     m_thumbsMutex.lock();
76     m_requestedThumbs.clear();
77     m_thumbsMutex.unlock();
78     qDeleteAll(m_clipList);
79     m_clipList.clear();
80 #if KDE_IS_VERSION(4,5,0)
81     delete pixmapCache;
82 #endif
83 }
84
85 void ClipManager::clear()
86 {
87     m_thumbsMutex.lock();
88     m_requestedThumbs.clear();
89     m_thumbsMutex.unlock();
90     m_abortThumb = true;
91     m_thumbsThread.waitForFinished();
92     m_abortThumb = false;
93     m_folderList.clear();
94     m_audioThumbsQueue.clear();
95     m_modifiedClips.clear();
96     qDeleteAll(m_clipList);
97     m_clipList.clear();
98     m_clipIdCounter = 1;
99     m_folderIdCounter = 1;
100 #if KDE_IS_VERSION(4,5,0)
101     pixmapCache->clear();
102 #endif
103 }
104
105 void ClipManager::clearCache()
106 {
107 #if KDE_IS_VERSION(4,5,0)
108     pixmapCache->clear();
109 #endif
110 }
111
112 void ClipManager::requestThumbs(const QString id, QList <int> frames)
113 {
114     kDebug()<<"// Request thbs: "<<id<<": "<<frames;
115     m_thumbsMutex.lock();
116     foreach (int frame, frames) {
117         m_requestedThumbs.insertMulti(id, frame);
118     }
119     m_thumbsMutex.unlock();
120     if (!m_thumbsThread.isRunning() && !m_abortThumb) {
121         m_thumbsThread = QtConcurrent::run(this, &ClipManager::slotGetThumbs);
122     }
123 }
124
125 void ClipManager::stopThumbs(const QString &id)
126 {
127     m_abortThumb = true;
128     m_thumbsThread.waitForFinished();
129     m_thumbsMutex.lock();
130     m_requestedThumbs.remove(id);
131     m_thumbsMutex.unlock();
132     m_abortThumb = false;
133     if (!m_thumbsThread.isRunning()) {
134         m_thumbsThread = QtConcurrent::run(this, &ClipManager::slotGetThumbs);
135     }
136 }
137
138 void ClipManager::slotGetThumbs()
139 {
140     QMap<QString, int>::const_iterator i = m_requestedThumbs.constBegin();
141     while (i != m_requestedThumbs.constEnd() && !m_abortThumb) {
142         const QString producerId = i.key();
143         m_thumbsMutex.lock();
144         QList<int> values = m_requestedThumbs.values(producerId);
145         m_requestedThumbs.remove(producerId);
146         m_thumbsMutex.unlock();
147         qSort(values);
148         DocClipBase *clip = getClipById(producerId);
149         if (!clip) continue;
150         while (!values.isEmpty() && clip->thumbProducer() && !m_abortThumb) {
151             clip->thumbProducer()->getThumb(values.takeFirst());
152         }
153         if (m_abortThumb) {
154             // keep the requested frames that were not processed
155             m_thumbsMutex.lock();
156             foreach (int frame, values)
157                 m_requestedThumbs.insertMulti(producerId, frame);
158             m_thumbsMutex.unlock();
159         }
160         i = m_requestedThumbs.constBegin();
161     }
162 }
163
164 void ClipManager::checkAudioThumbs()
165 {
166     if (!KdenliveSettings::audiothumbnails()) {
167         if (!m_generatingAudioId.isEmpty()) {
168             DocClipBase *clip = getClipById(m_generatingAudioId);
169             if (clip) clip->slotClearAudioCache();
170         }
171         m_audioThumbsQueue.clear();
172         m_generatingAudioId.clear();
173         return;
174     }
175
176     for (int i = 0; i < m_clipList.count(); i++) {
177         m_audioThumbsQueue.append(m_clipList.at(i)->getId());
178     }
179     if (m_generatingAudioId.isEmpty()) startAudioThumbsGeneration();
180 }
181
182 void ClipManager::askForAudioThumb(const QString &id)
183 {
184     DocClipBase *clip = getClipById(id);
185     if (clip && KdenliveSettings::audiothumbnails()) {
186         m_audioThumbsQueue.append(id);
187         if (m_generatingAudioId.isEmpty()) startAudioThumbsGeneration();
188     }
189 }
190
191 void ClipManager::startAudioThumbsGeneration()
192 {
193     if (!KdenliveSettings::audiothumbnails()) {
194         m_audioThumbsQueue.clear();
195         m_generatingAudioId.clear();
196         return;
197     }
198     if (!m_audioThumbsQueue.isEmpty()) {
199         m_generatingAudioId = m_audioThumbsQueue.takeFirst();
200         DocClipBase *clip = getClipById(m_generatingAudioId);
201         if (!clip || !clip->slotGetAudioThumbs())
202             endAudioThumbsGeneration(m_generatingAudioId);
203     } else {
204         m_generatingAudioId.clear();
205     }
206 }
207
208 void ClipManager::endAudioThumbsGeneration(const QString &requestedId)
209 {
210     if (!KdenliveSettings::audiothumbnails()) {
211         m_audioThumbsQueue.clear();
212         m_generatingAudioId.clear();
213         return;
214     }
215     if (!m_audioThumbsQueue.isEmpty()) {
216         if (m_generatingAudioId == requestedId) {
217             startAudioThumbsGeneration();
218         }
219     } else {
220         m_generatingAudioId.clear();
221     }
222 }
223
224 void ClipManager::setThumbsProgress(const QString &message, int progress)
225 {
226     m_doc->setThumbsProgress(message, progress);
227 }
228
229 QList <DocClipBase*> ClipManager::documentClipList() const
230 {
231     return m_clipList;
232 }
233
234 QMap <QString, QString> ClipManager::documentFolderList() const
235 {
236     return m_folderList;
237 }
238
239 void ClipManager::addClip(DocClipBase *clip)
240 {
241     m_clipList.append(clip);
242     if (clip->clipType() != COLOR && clip->clipType() != SLIDESHOW  && !clip->fileURL().isEmpty()) {
243         // listen for file change
244         //kDebug() << "// LISTEN FOR: " << clip->fileURL().path();
245         m_fileWatcher.addFile(clip->fileURL().path());
246     }
247     const QString id = clip->getId();
248     if (id.toInt() >= m_clipIdCounter) m_clipIdCounter = id.toInt() + 1;
249     const QString gid = clip->getProperty("groupid");
250     if (!gid.isEmpty() && gid.toInt() >= m_folderIdCounter) m_folderIdCounter = gid.toInt() + 1;
251 }
252
253 void ClipManager::slotDeleteClips(QStringList ids)
254 {
255     QUndoCommand *delClips = new QUndoCommand();
256     delClips->setText(i18np("Delete clip", "Delete clips", ids.size()));
257
258     for (int i = 0; i < ids.size(); i++) {
259         DocClipBase *clip = getClipById(ids.at(i));
260         if (clip) {
261             new AddClipCommand(m_doc, clip->toXML(), ids.at(i), false, delClips);
262         }
263     }
264     m_doc->commandStack()->push(delClips);
265 }
266
267 void ClipManager::deleteClip(const QString &clipId)
268 {
269     for (int i = 0; i < m_clipList.count(); i++) {
270         if (m_clipList.at(i)->getId() == clipId) {
271             if (m_clipList.at(i)->clipType() != COLOR && m_clipList.at(i)->clipType() != SLIDESHOW  && !m_clipList.at(i)->fileURL().isEmpty()) {
272                 //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())) {
273                 // listen for file change
274                 m_fileWatcher.removeFile(m_clipList.at(i)->fileURL().path());
275             }
276             DocClipBase *clip = m_clipList.takeAt(i);
277             delete clip;
278             clip = NULL;
279             break;
280         }
281     }
282 }
283
284 DocClipBase *ClipManager::getClipAt(int pos)
285 {
286     return m_clipList.at(pos);
287 }
288
289 DocClipBase *ClipManager::getClipById(QString clipId)
290 {
291     //kDebug() << "++++  CLIP MAN, LOOKING FOR CLIP ID: " << clipId;
292     clipId = clipId.section('_', 0, 0);
293     for (int i = 0; i < m_clipList.count(); i++) {
294         if (m_clipList.at(i)->getId() == clipId) {
295             //kDebug() << "++++  CLIP MAN, FOUND FOR CLIP ID: " << clipId;
296             return m_clipList.at(i);
297         }
298     }
299     return NULL;
300 }
301
302 const QList <DocClipBase *> ClipManager::getClipByResource(QString resource)
303 {
304     QList <DocClipBase *> list;
305     QString clipResource;
306     QString proxyResource;
307     for (int i = 0; i < m_clipList.count(); i++) {
308         clipResource = m_clipList.at(i)->getProperty("resource");
309         proxyResource = m_clipList.at(i)->getProperty("proxy");
310         if (clipResource.isEmpty()) clipResource = m_clipList.at(i)->getProperty("colour");
311         if (clipResource == resource || proxyResource == resource) {
312             list.append(m_clipList.at(i));
313         }
314     }
315     return list;
316 }
317
318
319 void ClipManager::clearUnusedProducers()
320 {
321     for (int i = 0; i < m_clipList.count(); i++) {
322         if (m_clipList.at(i)->numReferences() == 0) m_clipList.at(i)->deleteProducers();
323     }
324 }
325
326 void ClipManager::resetProducersList(const QList <Mlt::Producer *> prods, bool displayRatioChanged, bool fpsChanged)
327 {
328     for (int i = 0; i < m_clipList.count(); i++) {
329         if (m_clipList.at(i)->numReferences() > 0 || displayRatioChanged || fpsChanged) {
330             m_clipList.at(i)->deleteProducers();
331         }
332     }
333     QString id;
334     for (int i = 0; i < prods.count(); i++) {
335         id = prods.at(i)->get("id");
336         if (id.contains('_')) id = id.section('_', 0, 0);
337         DocClipBase *clip = getClipById(id);
338         if (clip) {
339             clip->setProducer(prods.at(i), false, true);
340         }
341     }
342     emit checkAllClips(displayRatioChanged, fpsChanged);
343 }
344
345 void ClipManager::slotAddClipList(const KUrl::List urls, const QString &group, const QString &groupId)
346 {
347     QUndoCommand *addClips = new QUndoCommand();
348
349     foreach(const KUrl & file, urls) {
350         if (KIO::NetAccess::exists(file, KIO::NetAccess::SourceSide, NULL)) {
351             if (!getClipByResource(file.path()).empty()) {
352                 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)
353                     continue;
354             }
355             kDebug() << "Adding clip: " << file.path();
356             QDomDocument doc;
357             QDomElement prod = doc.createElement("producer");
358             doc.appendChild(prod);
359             prod.setAttribute("resource", file.path());
360             uint id = m_clipIdCounter++;
361             prod.setAttribute("id", QString::number(id));
362             if (!group.isEmpty()) {
363                 prod.setAttribute("groupname", group);
364                 prod.setAttribute("groupid", groupId);
365             }
366             KMimeType::Ptr type = KMimeType::findByUrl(file);
367             if (type->name().startsWith("image/")) {
368                 prod.setAttribute("type", (int) IMAGE);
369                 prod.setAttribute("in", 0);
370                 prod.setAttribute("out", m_doc->getFramePos(KdenliveSettings::image_duration()));
371                 if (KdenliveSettings::autoimagetransparency()) prod.setAttribute("transparency", 1);
372                 // Read EXIF metadata for JPEG
373                 if (type->is("image/jpeg")) {
374                     KFileMetaInfo metaInfo(file.path(), QString("image/jpeg"), KFileMetaInfo::TechnicalInfo);
375                     const QHash<QString, KFileMetaInfoItem> metaInfoItems = metaInfo.items();
376                     foreach(const KFileMetaInfoItem & metaInfoItem, metaInfoItems) {
377                         prod.setAttribute("meta.attr." + metaInfoItem.name().section("#", 1), metaInfoItem.value().toString());
378                     }
379                 }
380             } else if (type->is("application/x-kdenlivetitle")) {
381                 // opening a title file
382                 QDomDocument txtdoc("titledocument");
383                 QFile txtfile(file.path());
384                 if (txtfile.open(QIODevice::ReadOnly) && txtdoc.setContent(&txtfile)) {
385                     txtfile.close();
386                     prod.setAttribute("type", (int) TEXT);
387                     // extract embeded images
388                     QDomNodeList items = txtdoc.elementsByTagName("content");
389                     for (int i = 0; i < items.count() ; i++) {
390                         QDomElement content = items.item(i).toElement();
391                         if (content.hasAttribute("base64")) {
392                             QString titlesFolder = m_doc->projectFolder().path(KUrl::AddTrailingSlash) + "titles/";
393                             QString path = TitleDocument::extractBase64Image(titlesFolder, content.attribute("base64"));
394                             if (!path.isEmpty()) {
395                                 content.setAttribute("url", path);
396                                 content.removeAttribute("base64");
397                             }
398                         }
399                     }
400                     QString titleData = txtdoc.toString();
401                     prod.setAttribute("xmldata", titleData);
402                     prod.setAttribute("transparency", 1);
403                     prod.setAttribute("in", 0);
404                     int out = txtdoc.documentElement().attribute("out").toInt();
405                     if (out > 0)
406                         prod.setAttribute("out", out);
407                     else
408                         prod.setAttribute("out", m_doc->getFramePos(KdenliveSettings::title_duration()));
409                 } else
410                     txtfile.close();
411             }
412             new AddClipCommand(m_doc, doc.documentElement(), QString::number(id), true, addClips);
413         }
414     }
415     if (addClips->childCount() > 0) {
416         addClips->setText(i18np("Add clip", "Add clips", addClips->childCount()));
417         m_doc->commandStack()->push(addClips);
418     }
419 }
420
421 void ClipManager::slotAddClipFile(const KUrl &url, const QString &group, const QString &groupId)
422 {
423     slotAddClipList(KUrl::List(url), group, groupId);
424 }
425
426 void ClipManager::slotAddXmlClipFile(const QString &name, const QDomElement &xml, const QString &group, const QString &groupId)
427 {
428     QDomDocument doc;
429     doc.appendChild(doc.importNode(xml, true));
430     QDomElement prod = doc.documentElement();
431     prod.setAttribute("type", (int) PLAYLIST);
432     uint id = m_clipIdCounter++;
433     prod.setAttribute("id", QString::number(id));
434     prod.setAttribute("name", name);
435     if (!group.isEmpty()) {
436         prod.setAttribute("groupname", group);
437         prod.setAttribute("groupid", groupId);
438     }
439     AddClipCommand *command = new AddClipCommand(m_doc, doc.documentElement(), QString::number(id), true);
440     m_doc->commandStack()->push(command);
441 }
442
443 void ClipManager::slotAddColorClipFile(const QString &name, const QString &color, QString duration, const QString &group, const QString &groupId)
444 {
445     QDomDocument doc;
446     QDomElement prod = doc.createElement("producer");
447     doc.appendChild(prod);
448     prod.setAttribute("mlt_service", "colour");
449     prod.setAttribute("colour", color);
450     prod.setAttribute("type", (int) COLOR);
451     uint id = m_clipIdCounter++;
452     prod.setAttribute("id", QString::number(id));
453     prod.setAttribute("in", "0");
454     prod.setAttribute("out", m_doc->getFramePos(duration));
455     prod.setAttribute("name", name);
456     if (!group.isEmpty()) {
457         prod.setAttribute("groupname", group);
458         prod.setAttribute("groupid", groupId);
459     }
460     AddClipCommand *command = new AddClipCommand(m_doc, doc.documentElement(), QString::number(id), true);
461     m_doc->commandStack()->push(command);
462 }
463
464 void ClipManager::slotAddSlideshowClipFile(const QString &name, const QString &path, int count, const QString &duration,
465         const bool loop, const bool crop, const bool fade,
466         const QString &luma_duration, const QString &luma_file, const int softness,
467         const QString &animation, const QString &group, const QString &groupId)
468 {
469     QDomDocument doc;
470     QDomElement prod = doc.createElement("producer");
471     doc.appendChild(prod);
472     prod.setAttribute("resource", path);
473     prod.setAttribute("type", (int) SLIDESHOW);
474     uint id = m_clipIdCounter++;
475     prod.setAttribute("id", QString::number(id));
476     prod.setAttribute("in", "0");
477     prod.setAttribute("out", m_doc->getFramePos(duration) * count);
478     prod.setAttribute("ttl", m_doc->getFramePos(duration));
479     prod.setAttribute("luma_duration", m_doc->getFramePos(luma_duration));
480     prod.setAttribute("name", name);
481     prod.setAttribute("loop", loop);
482     prod.setAttribute("crop", crop);
483     prod.setAttribute("fade", fade);
484     prod.setAttribute("softness", QString::number(softness));
485     prod.setAttribute("luma_file", luma_file);
486     prod.setAttribute("animation", animation);
487     if (!group.isEmpty()) {
488         prod.setAttribute("groupname", group);
489         prod.setAttribute("groupid", groupId);
490     }
491     AddClipCommand *command = new AddClipCommand(m_doc, doc.documentElement(), QString::number(id), true);
492     m_doc->commandStack()->push(command);
493 }
494
495
496
497 void ClipManager::slotAddTextClipFile(const QString &titleName, int out, const QString &xml, const QString &group, const QString &groupId)
498 {
499     QDomDocument doc;
500     QDomElement prod = doc.createElement("producer");
501     doc.appendChild(prod);
502     //prod.setAttribute("resource", imagePath);
503     prod.setAttribute("name", titleName);
504     prod.setAttribute("xmldata", xml);
505     uint id = m_clipIdCounter++;
506     prod.setAttribute("id", QString::number(id));
507     if (!group.isEmpty()) {
508         prod.setAttribute("groupname", group);
509         prod.setAttribute("groupid", groupId);
510     }
511     prod.setAttribute("type", (int) TEXT);
512     prod.setAttribute("transparency", "1");
513     prod.setAttribute("in", "0");
514     prod.setAttribute("out", out);
515     AddClipCommand *command = new AddClipCommand(m_doc, doc.documentElement(), QString::number(id), true);
516     m_doc->commandStack()->push(command);
517 }
518
519 void ClipManager::slotAddTextTemplateClip(QString titleName, const KUrl &path, const QString &group, const QString &groupId)
520 {
521     QDomDocument doc;
522     QDomElement prod = doc.createElement("producer");
523     doc.appendChild(prod);
524     prod.setAttribute("name", titleName);
525     prod.setAttribute("resource", path.path());
526     uint id = m_clipIdCounter++;
527     prod.setAttribute("id", QString::number(id));
528     if (!group.isEmpty()) {
529         prod.setAttribute("groupname", group);
530         prod.setAttribute("groupid", groupId);
531     }
532     prod.setAttribute("type", (int) TEXT);
533     prod.setAttribute("transparency", "1");
534     prod.setAttribute("in", "0");
535
536     int out = 0;
537     QDomDocument titledoc;
538     QFile txtfile(path.path());
539     if (txtfile.open(QIODevice::ReadOnly) && titledoc.setContent(&txtfile)) {
540         txtfile.close();
541         out = titledoc.documentElement().attribute("out").toInt();
542     } else txtfile.close();
543
544     if (out == 0) out = m_doc->getFramePos(KdenliveSettings::image_duration());
545     prod.setAttribute("out", out);
546
547     AddClipCommand *command = new AddClipCommand(m_doc, doc.documentElement(), QString::number(id), true);
548     m_doc->commandStack()->push(command);
549 }
550
551 int ClipManager::getFreeClipId()
552 {
553     return m_clipIdCounter++;
554 }
555
556 int ClipManager::getFreeFolderId()
557 {
558     return m_folderIdCounter++;
559 }
560
561 int ClipManager::lastClipId() const
562 {
563     return m_clipIdCounter - 1;
564 }
565
566 QString ClipManager::projectFolder() const
567 {
568     return m_doc->projectFolder().path();
569 }
570
571 void ClipManager::addFolder(const QString &id, const QString &name)
572 {
573     m_folderList.insert(id, name);
574 }
575
576 void ClipManager::deleteFolder(const QString &id)
577 {
578     m_folderList.remove(id);
579 }
580
581 AbstractGroupItem *ClipManager::createGroup()
582 {
583     AbstractGroupItem *group = new AbstractGroupItem(m_doc->fps());
584     m_groupsList.append(group);
585     return group;
586 }
587
588 void ClipManager::removeGroup(AbstractGroupItem *group)
589 {
590     m_groupsList.removeAll(group);
591 }
592
593 QDomElement ClipManager::groupsXml() const
594 {
595     QDomDocument doc;
596     QDomElement groups = doc.createElement("groups");
597     doc.appendChild(groups);
598     for (int i = 0; i < m_groupsList.count(); i++) {
599         QDomElement group = doc.createElement("group");
600         groups.appendChild(group);
601         QList <QGraphicsItem *> children = m_groupsList.at(i)->childItems();
602         for (int j = 0; j < children.count(); j++) {
603             if (children.at(j)->type() == AVWIDGET || children.at(j)->type() == TRANSITIONWIDGET) {
604                 AbstractClipItem *item = static_cast <AbstractClipItem *>(children.at(j));
605                 ItemInfo info = item->info();
606                 if (item->type() == AVWIDGET) {
607                     QDomElement clip = doc.createElement("clipitem");
608                     clip.setAttribute("track", info.track);
609                     clip.setAttribute("position", info.startPos.frames(m_doc->fps()));
610                     group.appendChild(clip);
611                 } else if (item->type() == TRANSITIONWIDGET) {
612                     QDomElement clip = doc.createElement("transitionitem");
613                     clip.setAttribute("track", info.track);
614                     clip.setAttribute("position", info.startPos.frames(m_doc->fps()));
615                     group.appendChild(clip);
616                 }
617             }
618         }
619     }
620     return doc.documentElement();
621 }
622
623
624 void ClipManager::slotClipModified(const QString &path)
625 {
626     //kDebug() << "// CLIP: " << path << " WAS MODIFIED";
627     const QList <DocClipBase *> list = getClipByResource(path);
628     for (int i = 0; i < list.count(); i++) {
629         DocClipBase *clip = list.at(i);
630         if (clip != NULL) {
631             QString id = clip->getId();
632             if (!m_modifiedClips.contains(id))
633                 emit modifiedClip(id);
634             m_modifiedClips[id] = QTime::currentTime();
635         }
636     }
637     if (!m_modifiedTimer.isActive()) m_modifiedTimer.start();
638 }
639
640 void ClipManager::slotProcessModifiedClips()
641 {
642     if (!m_modifiedClips.isEmpty()) {
643         QMapIterator<QString, QTime> i(m_modifiedClips);
644         while (i.hasNext()) {
645             i.next();
646             if (QTime::currentTime().msecsTo(i.value()) <= -1500) {
647                 emit reloadClip(i.key());
648                 m_modifiedClips.remove(i.key());
649                 break;
650             }
651         }
652     }
653     if (m_modifiedClips.isEmpty()) m_modifiedTimer.stop();
654 }
655
656 void ClipManager::slotClipMissing(const QString &path)
657 {
658     // kDebug() << "// CLIP: " << path << " WAS MISSING";
659     const QList <DocClipBase *> list = getClipByResource(path);
660     for (int i = 0; i < list.count(); i++) {
661         DocClipBase *clip = list.at(i);
662         if (clip != NULL) emit missingClip(clip->getId());
663     }
664 }
665
666 void ClipManager::slotClipAvailable(const QString &path)
667 {
668     // kDebug() << "// CLIP: " << path << " WAS ADDED";
669     const QList <DocClipBase *> list = getClipByResource(path);
670     for (int i = 0; i < list.count(); i++) {
671         DocClipBase *clip = list.at(i);
672         if (clip != NULL) emit availableClip(clip->getId());
673     }
674 }
675
676 int ClipManager::clipsCount() const
677 {
678     return m_clipList.count();
679 }
680
681