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