1 /***************************************************************************
2 * Copyright (C) 2008 by Jean-Baptiste Mardelle (jb@kdenlive.org) *
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. *
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. *
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 ***************************************************************************/
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"
29 #include <mlt++/Mlt.h>
32 #include <KMessageBox>
33 #include <KFileDialog>
34 #include <KApplication>
35 #include <kio/netaccess.h>
37 #include <QGraphicsItemGroup>
39 ClipManager::ClipManager(KdenliveDoc *doc) :
46 m_folderIdCounter = 1;
47 m_modifiedTimer.setInterval(1500);
48 connect(&m_fileWatcher, SIGNAL(dirty(const QString &)), this, SLOT(slotClipModified(const QString &)));
49 connect(&m_fileWatcher, SIGNAL(deleted(const QString &)), this, SLOT(slotClipMissing(const QString &)));
50 connect(&m_fileWatcher, SIGNAL(created(const QString &)), this, SLOT(slotClipAvailable(const QString &)));
51 connect(&m_modifiedTimer, SIGNAL(timeout()), this, SLOT(slotProcessModifiedClips()));
54 ClipManager::~ClipManager()
56 qDeleteAll(m_clipList);
58 m_audioThumbsQueue.clear();
59 m_generatingAudioId.clear();
62 void ClipManager::clear()
64 qDeleteAll(m_clipList);
67 m_folderIdCounter = 1;
69 m_audioThumbsQueue.clear();
70 m_modifiedClips.clear();
73 void ClipManager::checkAudioThumbs()
75 if (!KdenliveSettings::audiothumbnails()) {
76 if (!m_generatingAudioId.isEmpty()) {
77 DocClipBase *clip = getClipById(m_generatingAudioId);
78 if (clip) clip->slotClearAudioCache();
80 m_audioThumbsQueue.clear();
81 m_generatingAudioId.clear();
85 for (int i = 0; i < m_clipList.count(); i++) {
86 m_audioThumbsQueue.append(m_clipList.at(i)->getId());
88 if (m_generatingAudioId.isEmpty()) startAudioThumbsGeneration();
91 void ClipManager::askForAudioThumb(const QString &id)
93 DocClipBase *clip = getClipById(id);
94 if (clip && KdenliveSettings::audiothumbnails()) {
95 m_audioThumbsQueue.append(id);
96 if (m_generatingAudioId.isEmpty()) startAudioThumbsGeneration();
100 void ClipManager::startAudioThumbsGeneration()
102 if (!KdenliveSettings::audiothumbnails()) {
103 m_audioThumbsQueue.clear();
104 m_generatingAudioId.clear();
107 if (!m_audioThumbsQueue.isEmpty()) {
108 m_generatingAudioId = m_audioThumbsQueue.takeFirst();
109 DocClipBase *clip = getClipById(m_generatingAudioId);
110 if (!clip || !clip->slotGetAudioThumbs())
111 endAudioThumbsGeneration(m_generatingAudioId);
113 m_generatingAudioId.clear();
117 void ClipManager::endAudioThumbsGeneration(const QString &requestedId)
119 if (!KdenliveSettings::audiothumbnails()) {
120 m_audioThumbsQueue.clear();
121 m_generatingAudioId.clear();
124 if (!m_audioThumbsQueue.isEmpty()) {
125 if (m_generatingAudioId == requestedId) {
126 startAudioThumbsGeneration();
129 m_generatingAudioId.clear();
133 void ClipManager::setThumbsProgress(const QString &message, int progress)
135 m_doc->setThumbsProgress(message, progress);
138 QList <DocClipBase*> ClipManager::documentClipList() const
143 QMap <QString, QString> ClipManager::documentFolderList() const
148 void ClipManager::addClip(DocClipBase *clip)
150 m_clipList.append(clip);
151 if (clip->clipType() != COLOR && clip->clipType() != SLIDESHOW && !clip->fileURL().isEmpty()) {
152 // listen for file change
153 //kDebug() << "// LISTEN FOR: " << clip->fileURL().path();
154 m_fileWatcher.addFile(clip->fileURL().path());
156 const QString id = clip->getId();
157 if (id.toInt() >= m_clipIdCounter) m_clipIdCounter = id.toInt() + 1;
158 const QString gid = clip->getProperty("groupid");
159 if (!gid.isEmpty() && gid.toInt() >= m_folderIdCounter) m_folderIdCounter = gid.toInt() + 1;
162 void ClipManager::slotDeleteClips(QStringList ids)
164 QUndoCommand *delClips = new QUndoCommand();
165 delClips->setText(i18np("Delete clip", "Delete clips", ids.size()));
167 for (int i = 0; i < ids.size(); i++) {
168 DocClipBase *clip = getClipById(ids.at(i));
170 new AddClipCommand(m_doc, clip->toXML(), ids.at(i), false, delClips);
173 m_doc->commandStack()->push(delClips);
176 void ClipManager::deleteClip(const QString &clipId)
178 for (int i = 0; i < m_clipList.count(); i++) {
179 if (m_clipList.at(i)->getId() == clipId) {
180 if (m_clipList.at(i)->clipType() != COLOR && m_clipList.at(i)->clipType() != SLIDESHOW && !m_clipList.at(i)->fileURL().isEmpty()) {
181 //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())) {
182 // listen for file change
183 m_fileWatcher.removeFile(m_clipList.at(i)->fileURL().path());
185 DocClipBase *clip = m_clipList.takeAt(i);
193 DocClipBase *ClipManager::getClipAt(int pos)
195 return m_clipList.at(pos);
198 DocClipBase *ClipManager::getClipById(QString clipId)
200 //kDebug() << "++++ CLIP MAN, LOOKING FOR CLIP ID: " << clipId;
201 clipId = clipId.section('_', 0, 0);
202 for (int i = 0; i < m_clipList.count(); i++) {
203 if (m_clipList.at(i)->getId() == clipId) {
204 //kDebug() << "++++ CLIP MAN, FOUND FOR CLIP ID: " << clipId;
205 return m_clipList.at(i);
211 const QList <DocClipBase *> ClipManager::getClipByResource(QString resource)
213 QList <DocClipBase *> list;
214 QString clipResource;
215 for (int i = 0; i < m_clipList.count(); i++) {
216 clipResource = m_clipList.at(i)->getProperty("resource");
217 if (clipResource.isEmpty()) clipResource = m_clipList.at(i)->getProperty("colour");
218 if (clipResource == resource) {
219 list.append(m_clipList.at(i));
225 void ClipManager::updatePreviewSettings()
227 for (int i = 0; i < m_clipList.count(); i++) {
228 if (m_clipList.at(i)->clipType() == AV || m_clipList.at(i)->clipType() == VIDEO) {
229 if (m_clipList.at(i)->producerProperty("meta.media.0.codec.name") && strcmp(m_clipList.at(i)->producerProperty("meta.media.0.codec.name"), "h264") == 0) {
230 if (KdenliveSettings::dropbframes()) {
231 m_clipList[i]->setProducerProperty("skip_loop_filter", "all");
232 m_clipList[i]->setProducerProperty("skip_frame", "bidir");
234 m_clipList[i]->setProducerProperty("skip_loop_filter", "");
235 m_clipList[i]->setProducerProperty("skip_frame", "");
242 void ClipManager::clearUnusedProducers()
244 for (int i = 0; i < m_clipList.count(); i++) {
245 if (m_clipList.at(i)->numReferences() == 0) m_clipList.at(i)->deleteProducers();
249 void ClipManager::resetProducersList(const QList <Mlt::Producer *> prods)
251 for (int i = 0; i < m_clipList.count(); i++) {
252 if (m_clipList.at(i)->numReferences() > 0) {
253 m_clipList.at(i)->clearProducers();
257 for (int i = 0; i < prods.count(); i++) {
258 id = prods.at(i)->get("id");
259 kDebug() << "// // // REPLACE CLIP: " << id;
260 if (id.contains('_')) id = id.section('_', 0, 0);
261 DocClipBase *clip = getClipById(id);
263 clip->setProducer(prods.at(i));
266 emit checkAllClips();
269 void ClipManager::slotAddClipList(const KUrl::List urls, const QString group, const QString &groupId)
271 QUndoCommand *addClips = new QUndoCommand();
273 foreach(const KUrl &file, urls) {
274 if (KIO::NetAccess::exists(file, KIO::NetAccess::SourceSide, NULL)) {
275 if (!getClipByResource(file.path()).empty()) {
276 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)
279 kDebug() << "Adding clip: " << file.path();
281 QDomElement prod = doc.createElement("producer");
282 doc.appendChild(prod);
283 prod.setAttribute("resource", file.path());
284 uint id = m_clipIdCounter++;
285 prod.setAttribute("id", QString::number(id));
286 if (!group.isEmpty()) {
287 prod.setAttribute("groupname", group);
288 prod.setAttribute("groupid", groupId);
290 KMimeType::Ptr type = KMimeType::findByUrl(file);
291 if (type->name().startsWith("image/")) {
292 prod.setAttribute("type", (int) IMAGE);
293 prod.setAttribute("in", 0);
294 prod.setAttribute("out", m_doc->getFramePos(KdenliveSettings::image_duration()) - 1);
295 } else if (type->is("application/x-kdenlivetitle")) {
296 // opening a title file
297 QDomDocument txtdoc("titledocument");
298 QFile txtfile(file.path());
299 if (txtfile.open(QIODevice::ReadOnly) && txtdoc.setContent(&txtfile)) {
301 prod.setAttribute("type", (int) TEXT);
302 prod.setAttribute("xmldata", txtdoc.toString());
303 prod.setAttribute("transparency", 1);
304 prod.setAttribute("in", 0);
305 int out = txtdoc.documentElement().attribute("out").toInt();
307 prod.setAttribute("out", out);
309 prod.setAttribute("out", m_doc->getFramePos(KdenliveSettings::image_duration()) - 1);
313 new AddClipCommand(m_doc, doc.documentElement(), QString::number(id), true, addClips);
316 if (addClips->childCount() > 0) {
317 addClips->setText(i18np("Add clip", "Add clips", addClips->childCount()));
318 m_doc->commandStack()->push(addClips);
322 void ClipManager::slotAddClipFile(const KUrl url, const QString group, const QString &groupId)
324 slotAddClipList(KUrl::List(url), group, groupId);
328 void ClipManager::slotAddColorClipFile(const QString name, const QString color, QString duration, const QString group, const QString &groupId)
331 QDomElement prod = doc.createElement("producer");
332 doc.appendChild(prod);
333 prod.setAttribute("mlt_service", "colour");
334 prod.setAttribute("colour", color);
335 prod.setAttribute("type", (int) COLOR);
336 uint id = m_clipIdCounter++;
337 prod.setAttribute("id", QString::number(id));
338 prod.setAttribute("in", "0");
339 prod.setAttribute("out", m_doc->getFramePos(duration) - 1);
340 prod.setAttribute("name", name);
341 if (!group.isEmpty()) {
342 prod.setAttribute("groupname", group);
343 prod.setAttribute("groupid", groupId);
345 AddClipCommand *command = new AddClipCommand(m_doc, doc.documentElement(), QString::number(id), true);
346 m_doc->commandStack()->push(command);
349 void ClipManager::slotAddSlideshowClipFile(const QString name, const QString path, int count, const QString duration, const bool loop, const bool fade, const QString &luma_duration, const QString &luma_file, const int softness, QString group, const QString &groupId)
352 QDomElement prod = doc.createElement("producer");
353 doc.appendChild(prod);
354 prod.setAttribute("resource", path);
355 prod.setAttribute("type", (int) SLIDESHOW);
356 uint id = m_clipIdCounter++;
357 prod.setAttribute("id", QString::number(id));
358 prod.setAttribute("in", "0");
359 prod.setAttribute("out", m_doc->getFramePos(duration) * count - 1);
360 prod.setAttribute("ttl", m_doc->getFramePos(duration));
361 prod.setAttribute("luma_duration", m_doc->getFramePos(luma_duration));
362 prod.setAttribute("name", name);
363 prod.setAttribute("loop", loop);
364 prod.setAttribute("fade", fade);
365 prod.setAttribute("softness", QString::number(softness));
366 prod.setAttribute("luma_file", luma_file);
367 if (!group.isEmpty()) {
368 prod.setAttribute("groupname", group);
369 prod.setAttribute("groupid", groupId);
371 AddClipCommand *command = new AddClipCommand(m_doc, doc.documentElement(), QString::number(id), true);
372 m_doc->commandStack()->push(command);
377 void ClipManager::slotAddTextClipFile(const QString titleName, int out, const QString xml, const QString group, const QString &groupId)
380 QDomElement prod = doc.createElement("producer");
381 doc.appendChild(prod);
382 //prod.setAttribute("resource", imagePath);
383 prod.setAttribute("name", titleName);
384 prod.setAttribute("xmldata", xml);
385 uint id = m_clipIdCounter++;
386 prod.setAttribute("id", QString::number(id));
387 if (!group.isEmpty()) {
388 prod.setAttribute("groupname", group);
389 prod.setAttribute("groupid", groupId);
391 prod.setAttribute("type", (int) TEXT);
392 prod.setAttribute("transparency", "1");
393 prod.setAttribute("in", "0");
394 prod.setAttribute("out", out);
395 AddClipCommand *command = new AddClipCommand(m_doc, doc.documentElement(), QString::number(id), true);
396 m_doc->commandStack()->push(command);
399 void ClipManager::slotAddTextTemplateClip(QString titleName, const KUrl path, const QString group, const QString &groupId)
402 QDomElement prod = doc.createElement("producer");
403 doc.appendChild(prod);
404 prod.setAttribute("name", titleName);
405 prod.setAttribute("resource", path.path());
406 uint id = m_clipIdCounter++;
407 prod.setAttribute("id", QString::number(id));
408 if (!group.isEmpty()) {
409 prod.setAttribute("groupname", group);
410 prod.setAttribute("groupid", groupId);
412 prod.setAttribute("type", (int) TEXT);
413 prod.setAttribute("transparency", "1");
414 prod.setAttribute("in", "0");
417 QDomDocument titledoc;
418 QFile txtfile(path.path());
419 if (txtfile.open(QIODevice::ReadOnly) && titledoc.setContent(&txtfile)) {
421 out = titledoc.documentElement().attribute("out").toInt();
422 } else txtfile.close();
424 if (out == 0) out = m_doc->getFramePos(KdenliveSettings::image_duration()) - 1;
425 prod.setAttribute("out", out);
427 AddClipCommand *command = new AddClipCommand(m_doc, doc.documentElement(), QString::number(id), true);
428 m_doc->commandStack()->push(command);
431 int ClipManager::getFreeClipId()
433 return m_clipIdCounter++;
436 int ClipManager::getFreeFolderId()
438 return m_folderIdCounter++;
441 int ClipManager::lastClipId() const
443 return m_clipIdCounter - 1;
446 QString ClipManager::projectFolder() const
448 return m_doc->projectFolder().path();
451 void ClipManager::addFolder(const QString &id, const QString &name)
453 m_folderList.insert(id, name);
456 void ClipManager::deleteFolder(const QString &id)
458 m_folderList.remove(id);
461 AbstractGroupItem *ClipManager::createGroup()
463 AbstractGroupItem *group = new AbstractGroupItem(m_doc->fps());
464 m_groupsList.append(group);
468 void ClipManager::removeGroup(AbstractGroupItem *group)
470 m_groupsList.removeAll(group);
473 QDomElement ClipManager::groupsXml() const
476 QDomElement groups = doc.createElement("groups");
477 doc.appendChild(groups);
478 for (int i = 0; i < m_groupsList.count(); i++) {
479 QDomElement group = doc.createElement("group");
480 groups.appendChild(group);
481 QList <QGraphicsItem *> children = m_groupsList.at(i)->childItems();
482 for (int j = 0; j < children.count(); j++) {
483 if (children.at(j)->type() == AVWIDGET || children.at(j)->type() == TRANSITIONWIDGET) {
484 AbstractClipItem *item = static_cast <AbstractClipItem *>(children.at(j));
485 ItemInfo info = item->info();
486 if (item->type() == AVWIDGET) {
487 QDomElement clip = doc.createElement("clipitem");
488 clip.setAttribute("track", info.track);
489 clip.setAttribute("position", info.startPos.frames(m_doc->fps()));
490 group.appendChild(clip);
491 } else if (item->type() == TRANSITIONWIDGET) {
492 QDomElement clip = doc.createElement("transitionitem");
493 clip.setAttribute("track", info.track);
494 clip.setAttribute("position", info.startPos.frames(m_doc->fps()));
495 group.appendChild(clip);
500 return doc.documentElement();
504 void ClipManager::slotClipModified(const QString &path)
506 //kDebug() << "// CLIP: " << path << " WAS MODIFIED";
507 const QList <DocClipBase *> list = getClipByResource(path);
508 for (int i = 0; i < list.count(); i++) {
509 DocClipBase *clip = list.at(i);
511 QString id = clip->getId();
512 if (!m_modifiedClips.contains(id))
513 emit modifiedClip(id);
514 m_modifiedClips[id] = QTime::currentTime();
517 if (!m_modifiedTimer.isActive()) m_modifiedTimer.start();
520 void ClipManager::slotProcessModifiedClips()
522 if (!m_modifiedClips.isEmpty()) {
523 QMapIterator<QString, QTime> i(m_modifiedClips);
524 while (i.hasNext()) {
526 if (QTime::currentTime().msecsTo(i.value()) <= -1500) {
527 emit reloadClip(i.key());
528 m_modifiedClips.remove(i.key());
533 if (m_modifiedClips.isEmpty()) m_modifiedTimer.stop();
536 void ClipManager::slotClipMissing(const QString &path)
538 // kDebug() << "// CLIP: " << path << " WAS MISSING";
539 const QList <DocClipBase *> list = getClipByResource(path);
540 for (int i = 0; i < list.count(); i++) {
541 DocClipBase *clip = list.at(i);
542 if (clip != NULL) emit missingClip(clip->getId());
546 void ClipManager::slotClipAvailable(const QString &path)
548 // kDebug() << "// CLIP: " << path << " WAS ADDED";
549 const QList <DocClipBase *> list = getClipByResource(path);
550 for (int i = 0; i < list.count(); i++) {
551 DocClipBase *clip = list.at(i);
552 if (clip != NULL) emit availableClip(clip->getId());
556 int ClipManager::clipsCount() const
558 return m_clipList.count();