]> git.sesse.net Git - kdenlive/blob - src/clipmanager.cpp
- Avoid adding an undo command when no clips are actually added. [1]
[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
29 #include <mlt++/Mlt.h>
30
31 #include <KDebug>
32 #include <KMessageBox>
33 #include <KFileDialog>
34 #include <KApplication>
35 #include <kio/netaccess.h>
36
37 #include <QGraphicsItemGroup>
38
39 ClipManager::ClipManager(KdenliveDoc *doc) :
40         QObject(),
41         m_audioThumbsQueue(),
42         m_doc(doc),
43         m_audioThumbsEnabled(false),
44         m_generatingAudioId()
45 {
46     m_clipIdCounter = 1;
47     m_folderIdCounter = 1;
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 }
52
53 ClipManager::~ClipManager()
54 {
55     qDeleteAll(m_clipList);
56     m_clipList.clear();
57     m_audioThumbsQueue.clear();
58     m_generatingAudioId.clear();
59 }
60
61 void ClipManager::clear()
62 {
63     qDeleteAll(m_clipList);
64     m_clipList.clear();
65     m_clipIdCounter = 1;
66     m_folderIdCounter = 1;
67     m_folderList.clear();
68     m_audioThumbsQueue.clear();
69 }
70
71 void ClipManager::checkAudioThumbs()
72 {
73     if (m_audioThumbsEnabled == KdenliveSettings::audiothumbnails()) return;
74     m_audioThumbsEnabled = KdenliveSettings::audiothumbnails();
75     for (int i = 0; i < m_clipList.count(); i++) {
76         if (m_audioThumbsEnabled) m_audioThumbsQueue.append(m_clipList.at(i)->getId());
77         else m_clipList.at(i)->slotClearAudioCache();
78     }
79     if (m_audioThumbsEnabled) {
80         if (m_generatingAudioId.isEmpty()) startAudioThumbsGeneration();
81     } else {
82         m_audioThumbsQueue.clear();
83         m_generatingAudioId.clear();
84     }
85 }
86
87 void ClipManager::askForAudioThumb(const QString &id)
88 {
89     DocClipBase *clip = getClipById(id);
90     if (clip && KdenliveSettings::audiothumbnails()) {
91         m_audioThumbsQueue.append(id);
92         if (m_generatingAudioId.isEmpty()) startAudioThumbsGeneration();
93     }
94 }
95
96 void ClipManager::startAudioThumbsGeneration()
97 {
98     if (!KdenliveSettings::audiothumbnails()) {
99         m_audioThumbsQueue.clear();
100         m_generatingAudioId.clear();
101         return;
102     }
103     if (!m_audioThumbsQueue.isEmpty()) {
104         m_generatingAudioId = m_audioThumbsQueue.takeFirst();
105         DocClipBase *clip = getClipById(m_generatingAudioId);
106         if (!clip || !clip->slotGetAudioThumbs())
107             endAudioThumbsGeneration(m_generatingAudioId);
108     } else {
109         m_generatingAudioId.clear();
110     }
111 }
112
113 void ClipManager::endAudioThumbsGeneration(const QString &requestedId)
114 {
115     if (!KdenliveSettings::audiothumbnails()) {
116         m_audioThumbsQueue.clear();
117         m_generatingAudioId.clear();
118         return;
119     }
120     if (!m_audioThumbsQueue.isEmpty()) {
121         if (m_generatingAudioId == requestedId) {
122             startAudioThumbsGeneration();
123         }
124     } else {
125         m_generatingAudioId.clear();
126     }
127 }
128
129 void ClipManager::setThumbsProgress(const QString &message, int progress)
130 {
131     m_doc->setThumbsProgress(message, progress);
132 }
133
134 QList <DocClipBase*> ClipManager::documentClipList() const
135 {
136     return m_clipList;
137 }
138
139 QMap <QString, QString> ClipManager::documentFolderList() const
140 {
141     return m_folderList;
142 }
143
144 void ClipManager::addClip(DocClipBase *clip)
145 {
146     m_clipList.append(clip);
147     if (clip->clipType() != COLOR && clip->clipType() != SLIDESHOW  && !clip->fileURL().isEmpty()) {
148         // listen for file change
149         //kDebug() << "// LISTEN FOR: " << clip->fileURL().path();
150         m_fileWatcher.addFile(clip->fileURL().path());
151     }
152     const QString id = clip->getId();
153     if (id.toInt() >= m_clipIdCounter) m_clipIdCounter = id.toInt() + 1;
154     const QString gid = clip->getProperty("groupid");
155     if (!gid.isEmpty() && gid.toInt() >= m_folderIdCounter) m_folderIdCounter = gid.toInt() + 1;
156 }
157
158 void ClipManager::slotDeleteClips(QStringList ids)
159 {
160     QUndoCommand *delClips = new QUndoCommand();
161     delClips->setText(i18np("Delete clip", "Delete clips", ids.size()));
162
163     for (int i = 0; i < ids.size(); i++) {
164         DocClipBase *clip = getClipById(ids.at(i));
165         if (clip) {
166             new AddClipCommand(m_doc, clip->toXML(), ids.at(i), false, delClips);
167         }
168     }
169     m_doc->commandStack()->push(delClips);
170 }
171
172 void ClipManager::deleteClip(const QString &clipId)
173 {
174     for (int i = 0; i < m_clipList.count(); i++) {
175         if (m_clipList.at(i)->getId() == clipId) {
176             if (m_clipList.at(i)->clipType() != COLOR && m_clipList.at(i)->clipType() != SLIDESHOW  && !m_clipList.at(i)->fileURL().isEmpty()) {
177                 //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())) {
178                 // listen for file change
179                 m_fileWatcher.removeFile(m_clipList.at(i)->fileURL().path());
180             }
181             DocClipBase *clip = m_clipList.takeAt(i);
182             delete clip;
183             clip = NULL;
184             break;
185         }
186     }
187 }
188
189 DocClipBase *ClipManager::getClipAt(int pos)
190 {
191     return m_clipList.at(pos);
192 }
193
194 DocClipBase *ClipManager::getClipById(QString clipId)
195 {
196     //kDebug() << "++++  CLIP MAN, LOOKING FOR CLIP ID: " << clipId;
197     clipId = clipId.section('_', 0, 0);
198     for (int i = 0; i < m_clipList.count(); i++) {
199         if (m_clipList.at(i)->getId() == clipId) {
200             //kDebug() << "++++  CLIP MAN, FOUND FOR CLIP ID: " << clipId;
201             return m_clipList.at(i);
202         }
203     }
204     return NULL;
205 }
206
207 const QList <DocClipBase *> ClipManager::getClipByResource(QString resource)
208 {
209     QList <DocClipBase *> list;
210     QString clipResource;
211     for (int i = 0; i < m_clipList.count(); i++) {
212         clipResource = m_clipList.at(i)->getProperty("resource");
213         if (clipResource.isEmpty()) clipResource = m_clipList.at(i)->getProperty("colour");
214         if (clipResource == resource) {
215             list.append(m_clipList.at(i));
216         }
217     }
218     return list;
219 }
220
221 void ClipManager::updatePreviewSettings()
222 {
223     for (int i = 0; i < m_clipList.count(); i++) {
224         if (m_clipList.at(i)->clipType() == AV || m_clipList.at(i)->clipType() == VIDEO) {
225             if (m_clipList.at(i)->producerProperty("meta.media.0.codec.name") && strcmp(m_clipList.at(i)->producerProperty("meta.media.0.codec.name"), "h264") == 0) {
226                 if (KdenliveSettings::dropbframes()) {
227                     m_clipList[i]->setProducerProperty("skip_loop_filter", "all");
228                     m_clipList[i]->setProducerProperty("skip_frame", "bidir");
229                 } else {
230                     m_clipList[i]->setProducerProperty("skip_loop_filter", "");
231                     m_clipList[i]->setProducerProperty("skip_frame", "");
232                 }
233             }
234         }
235     }
236 }
237
238 void ClipManager::clearUnusedProducers()
239 {
240     for (int i = 0; i < m_clipList.count(); i++) {
241         if (m_clipList.at(i)->numReferences() == 0) m_clipList.at(i)->deleteProducers();
242     }
243 }
244
245 void ClipManager::resetProducersList(const QList <Mlt::Producer *> prods)
246 {
247     for (int i = 0; i < m_clipList.count(); i++) {
248         if (m_clipList.at(i)->numReferences() > 0) {
249             m_clipList.at(i)->clearProducers();
250         }
251     }
252     QString id;
253     for (int i = 0; i < prods.count(); i++) {
254         id = prods.at(i)->get("id");
255         kDebug() << "// // // REPLACE CLIP: " << id;
256         if (id.contains('_')) id = id.section('_', 0, 0);
257         DocClipBase *clip = getClipById(id);
258         if (clip) {
259             clip->setProducer(prods.at(i));
260         }
261     }
262     emit checkAllClips();
263 }
264
265 void ClipManager::slotAddClipList(const KUrl::List urls, const QString group, const QString &groupId)
266 {
267     QUndoCommand *addClips = new QUndoCommand();
268
269     foreach(const KUrl &file, urls) {
270         if (KIO::NetAccess::exists(file, KIO::NetAccess::SourceSide, NULL)) {
271             if (!getClipByResource(file.path()).empty()) {
272                 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)
273                     continue;
274             }
275             kDebug() << "Adding clip: " << file.path();
276             QDomDocument doc;
277             QDomElement prod = doc.createElement("producer");
278             doc.appendChild(prod);
279             prod.setAttribute("resource", file.path());
280             uint id = m_clipIdCounter++;
281             prod.setAttribute("id", QString::number(id));
282             if (!group.isEmpty()) {
283                 prod.setAttribute("groupname", group);
284                 prod.setAttribute("groupid", groupId);
285             }
286             KMimeType::Ptr type = KMimeType::findByUrl(file);
287             if (type->name().startsWith("image/")) {
288                 prod.setAttribute("type", (int) IMAGE);
289                 prod.setAttribute("in", 0);
290                 prod.setAttribute("out", m_doc->getFramePos(KdenliveSettings::image_duration()) - 1);
291             } else if (type->is("application/x-kdenlivetitle")) {
292                 // opening a title file
293                 QDomDocument txtdoc("titledocument");
294                 QFile txtfile(file.path());
295                 if (txtfile.open(QIODevice::ReadOnly) && txtdoc.setContent(&txtfile)) {
296                     txtfile.close();
297                     prod.setAttribute("type", (int) TEXT);
298                     prod.setAttribute("xmldata", txtdoc.toString());
299                     prod.setAttribute("transparency", 1);
300                     prod.setAttribute("in", 0);
301                     int out = txtdoc.documentElement().attribute("out").toInt();
302                     if (out > 0)
303                         prod.setAttribute("out", out);
304                     else
305                         prod.setAttribute("out", m_doc->getFramePos(KdenliveSettings::image_duration()) - 1);
306                 } else
307                     txtfile.close();
308             }
309             new AddClipCommand(m_doc, doc.documentElement(), QString::number(id), true, addClips);
310         }
311     }
312     if (addClips->childCount() > 0) {
313         addClips->setText(i18np("Add clip", "Add clips", addClips->childCount()));
314         m_doc->commandStack()->push(addClips);
315     }
316 }
317
318 void ClipManager::slotAddClipFile(const KUrl url, const QString group, const QString &groupId)
319 {
320     slotAddClipList(KUrl::List(url), group, groupId);
321 }
322
323
324 void ClipManager::slotAddColorClipFile(const QString name, const QString color, QString duration, const QString group, const QString &groupId)
325 {
326     QDomDocument doc;
327     QDomElement prod = doc.createElement("producer");
328     doc.appendChild(prod);
329     prod.setAttribute("mlt_service", "colour");
330     prod.setAttribute("colour", color);
331     prod.setAttribute("type", (int) COLOR);
332     uint id = m_clipIdCounter++;
333     prod.setAttribute("id", QString::number(id));
334     prod.setAttribute("in", "0");
335     prod.setAttribute("out", m_doc->getFramePos(duration) - 1);
336     prod.setAttribute("name", name);
337     if (!group.isEmpty()) {
338         prod.setAttribute("groupname", group);
339         prod.setAttribute("groupid", groupId);
340     }
341     AddClipCommand *command = new AddClipCommand(m_doc, doc.documentElement(), QString::number(id), true);
342     m_doc->commandStack()->push(command);
343 }
344
345 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)
346 {
347     QDomDocument doc;
348     QDomElement prod = doc.createElement("producer");
349     doc.appendChild(prod);
350     prod.setAttribute("resource", path);
351     prod.setAttribute("type", (int) SLIDESHOW);
352     uint id = m_clipIdCounter++;
353     prod.setAttribute("id", QString::number(id));
354     prod.setAttribute("in", "0");
355     prod.setAttribute("out", m_doc->getFramePos(duration) * count - 1);
356     prod.setAttribute("ttl", m_doc->getFramePos(duration));
357     prod.setAttribute("luma_duration", m_doc->getFramePos(luma_duration));
358     prod.setAttribute("name", name);
359     prod.setAttribute("loop", loop);
360     prod.setAttribute("fade", fade);
361     prod.setAttribute("softness", QString::number(softness));
362     prod.setAttribute("luma_file", luma_file);
363     if (!group.isEmpty()) {
364         prod.setAttribute("groupname", group);
365         prod.setAttribute("groupid", groupId);
366     }
367     AddClipCommand *command = new AddClipCommand(m_doc, doc.documentElement(), QString::number(id), true);
368     m_doc->commandStack()->push(command);
369 }
370
371
372
373 void ClipManager::slotAddTextClipFile(const QString titleName, int out, const QString xml, const QString group, const QString &groupId)
374 {
375     QDomDocument doc;
376     QDomElement prod = doc.createElement("producer");
377     doc.appendChild(prod);
378     //prod.setAttribute("resource", imagePath);
379     prod.setAttribute("name", titleName);
380     prod.setAttribute("xmldata", xml);
381     uint id = m_clipIdCounter++;
382     prod.setAttribute("id", QString::number(id));
383     if (!group.isEmpty()) {
384         prod.setAttribute("groupname", group);
385         prod.setAttribute("groupid", groupId);
386     }
387     prod.setAttribute("type", (int) TEXT);
388     prod.setAttribute("transparency", "1");
389     prod.setAttribute("in", "0");
390     prod.setAttribute("out", out);
391     AddClipCommand *command = new AddClipCommand(m_doc, doc.documentElement(), QString::number(id), true);
392     m_doc->commandStack()->push(command);
393 }
394
395 void ClipManager::slotAddTextTemplateClip(QString titleName, const KUrl path, const QString group, const QString &groupId)
396 {
397     QDomDocument doc;
398     QDomElement prod = doc.createElement("producer");
399     doc.appendChild(prod);
400     prod.setAttribute("name", titleName);
401     prod.setAttribute("resource", path.path());
402     uint id = m_clipIdCounter++;
403     prod.setAttribute("id", QString::number(id));
404     if (!group.isEmpty()) {
405         prod.setAttribute("groupname", group);
406         prod.setAttribute("groupid", groupId);
407     }
408     prod.setAttribute("type", (int) TEXT);
409     prod.setAttribute("transparency", "1");
410     prod.setAttribute("in", "0");
411
412     int out = 0;
413     QDomDocument titledoc;
414     QFile txtfile(path.path());
415     if (txtfile.open(QIODevice::ReadOnly) && titledoc.setContent(&txtfile)) {
416         txtfile.close();
417         out = titledoc.documentElement().attribute("out").toInt();
418     } else txtfile.close();
419
420     if (out == 0) out = m_doc->getFramePos(KdenliveSettings::image_duration()) - 1;
421     prod.setAttribute("out", out);
422
423     AddClipCommand *command = new AddClipCommand(m_doc, doc.documentElement(), QString::number(id), true);
424     m_doc->commandStack()->push(command);
425 }
426
427 int ClipManager::getFreeClipId()
428 {
429     return m_clipIdCounter++;
430 }
431
432 int ClipManager::getFreeFolderId()
433 {
434     return m_folderIdCounter++;
435 }
436
437 int ClipManager::lastClipId() const
438 {
439     return m_clipIdCounter - 1;
440 }
441
442 QString ClipManager::projectFolder() const
443 {
444     return m_doc->projectFolder().path();
445 }
446
447 void ClipManager::addFolder(const QString &id, const QString &name)
448 {
449     m_folderList.insert(id, name);
450 }
451
452 void ClipManager::deleteFolder(const QString &id)
453 {
454     m_folderList.remove(id);
455 }
456
457 AbstractGroupItem *ClipManager::createGroup()
458 {
459     AbstractGroupItem *group = new AbstractGroupItem(m_doc->fps());
460     m_groupsList.append(group);
461     return group;
462 }
463
464 void ClipManager::removeGroup(AbstractGroupItem *group)
465 {
466     m_groupsList.removeAll(group);
467 }
468
469 QDomElement ClipManager::groupsXml() const
470 {
471     QDomDocument doc;
472     QDomElement groups = doc.createElement("groups");
473     doc.appendChild(groups);
474     for (int i = 0; i < m_groupsList.count(); i++) {
475         QDomElement group = doc.createElement("group");
476         groups.appendChild(group);
477         QList <QGraphicsItem *> children = m_groupsList.at(i)->childItems();
478         for (int j = 0; j < children.count(); j++) {
479             if (children.at(j)->type() == AVWIDGET || children.at(j)->type() == TRANSITIONWIDGET) {
480                 AbstractClipItem *item = static_cast <AbstractClipItem *>(children.at(j));
481                 ItemInfo info = item->info();
482                 if (item->type() == AVWIDGET) {
483                     QDomElement clip = doc.createElement("clipitem");
484                     clip.setAttribute("track", info.track);
485                     clip.setAttribute("position", info.startPos.frames(m_doc->fps()));
486                     group.appendChild(clip);
487                 } else if (item->type() == TRANSITIONWIDGET) {
488                     QDomElement clip = doc.createElement("transitionitem");
489                     clip.setAttribute("track", info.track);
490                     clip.setAttribute("position", info.startPos.frames(m_doc->fps()));
491                     group.appendChild(clip);
492                 }
493             }
494         }
495     }
496     return doc.documentElement();
497 }
498
499
500 void ClipManager::slotClipModified(const QString &path)
501 {
502     // kDebug() << "// CLIP: " << path << " WAS MODIFIED";
503     const QList <DocClipBase *> list = getClipByResource(path);
504     for (int i = 0; i < list.count(); i++) {
505         DocClipBase *clip = list.at(i);
506         if (clip != NULL) emit reloadClip(clip->getId());
507     }
508 }
509
510 void ClipManager::slotClipMissing(const QString &path)
511 {
512     // kDebug() << "// CLIP: " << path << " WAS MISSING";
513     const QList <DocClipBase *> list = getClipByResource(path);
514     for (int i = 0; i < list.count(); i++) {
515         DocClipBase *clip = list.at(i);
516         if (clip != NULL) emit missingClip(clip->getId());
517     }
518 }
519
520 void ClipManager::slotClipAvailable(const QString &path)
521 {
522     // kDebug() << "// CLIP: " << path << " WAS ADDED";
523     const QList <DocClipBase *> list = getClipByResource(path);
524     for (int i = 0; i < list.count(); i++) {
525         DocClipBase *clip = list.at(i);
526         if (clip != NULL) emit availableClip(clip->getId());
527     }
528 }
529
530 int ClipManager::clipsCount() const
531 {
532     return m_clipList.count();
533 }
534
535