]> git.sesse.net Git - kdenlive/blob - src/clipmanager.cpp
Improve reloading of externally modified clips (wait for 1.5 second before reloading...
[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_generatingAudioId()
44 {
45     m_clipIdCounter = 1;
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()));
52 }
53
54 ClipManager::~ClipManager()
55 {
56     qDeleteAll(m_clipList);
57     m_clipList.clear();
58     m_audioThumbsQueue.clear();
59     m_generatingAudioId.clear();
60 }
61
62 void ClipManager::clear()
63 {
64     qDeleteAll(m_clipList);
65     m_clipList.clear();
66     m_clipIdCounter = 1;
67     m_folderIdCounter = 1;
68     m_folderList.clear();
69     m_audioThumbsQueue.clear();
70     m_modifiedClips.clear();
71 }
72
73 void ClipManager::checkAudioThumbs()
74 {
75     if (!KdenliveSettings::audiothumbnails()) {
76         if (!m_generatingAudioId.isEmpty()) {
77             DocClipBase *clip = getClipById(m_generatingAudioId);
78             if (clip) clip->slotClearAudioCache();
79         }
80         m_audioThumbsQueue.clear();
81         m_generatingAudioId.clear();
82         return;
83     }
84
85     for (int i = 0; i < m_clipList.count(); i++) {
86         m_audioThumbsQueue.append(m_clipList.at(i)->getId());
87     }
88     if (m_generatingAudioId.isEmpty()) startAudioThumbsGeneration();
89 }
90
91 void ClipManager::askForAudioThumb(const QString &id)
92 {
93     DocClipBase *clip = getClipById(id);
94     if (clip && KdenliveSettings::audiothumbnails()) {
95         m_audioThumbsQueue.append(id);
96         if (m_generatingAudioId.isEmpty()) startAudioThumbsGeneration();
97     }
98 }
99
100 void ClipManager::startAudioThumbsGeneration()
101 {
102     if (!KdenliveSettings::audiothumbnails()) {
103         m_audioThumbsQueue.clear();
104         m_generatingAudioId.clear();
105         return;
106     }
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);
112     } else {
113         m_generatingAudioId.clear();
114     }
115 }
116
117 void ClipManager::endAudioThumbsGeneration(const QString &requestedId)
118 {
119     if (!KdenliveSettings::audiothumbnails()) {
120         m_audioThumbsQueue.clear();
121         m_generatingAudioId.clear();
122         return;
123     }
124     if (!m_audioThumbsQueue.isEmpty()) {
125         if (m_generatingAudioId == requestedId) {
126             startAudioThumbsGeneration();
127         }
128     } else {
129         m_generatingAudioId.clear();
130     }
131 }
132
133 void ClipManager::setThumbsProgress(const QString &message, int progress)
134 {
135     m_doc->setThumbsProgress(message, progress);
136 }
137
138 QList <DocClipBase*> ClipManager::documentClipList() const
139 {
140     return m_clipList;
141 }
142
143 QMap <QString, QString> ClipManager::documentFolderList() const
144 {
145     return m_folderList;
146 }
147
148 void ClipManager::addClip(DocClipBase *clip)
149 {
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());
155     }
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;
160 }
161
162 void ClipManager::slotDeleteClips(QStringList ids)
163 {
164     QUndoCommand *delClips = new QUndoCommand();
165     delClips->setText(i18np("Delete clip", "Delete clips", ids.size()));
166
167     for (int i = 0; i < ids.size(); i++) {
168         DocClipBase *clip = getClipById(ids.at(i));
169         if (clip) {
170             new AddClipCommand(m_doc, clip->toXML(), ids.at(i), false, delClips);
171         }
172     }
173     m_doc->commandStack()->push(delClips);
174 }
175
176 void ClipManager::deleteClip(const QString &clipId)
177 {
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());
184             }
185             DocClipBase *clip = m_clipList.takeAt(i);
186             delete clip;
187             clip = NULL;
188             break;
189         }
190     }
191 }
192
193 DocClipBase *ClipManager::getClipAt(int pos)
194 {
195     return m_clipList.at(pos);
196 }
197
198 DocClipBase *ClipManager::getClipById(QString clipId)
199 {
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);
206         }
207     }
208     return NULL;
209 }
210
211 const QList <DocClipBase *> ClipManager::getClipByResource(QString resource)
212 {
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));
220         }
221     }
222     return list;
223 }
224
225 void ClipManager::updatePreviewSettings()
226 {
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");
233                 } else {
234                     m_clipList[i]->setProducerProperty("skip_loop_filter", "");
235                     m_clipList[i]->setProducerProperty("skip_frame", "");
236                 }
237             }
238         }
239     }
240 }
241
242 void ClipManager::clearUnusedProducers()
243 {
244     for (int i = 0; i < m_clipList.count(); i++) {
245         if (m_clipList.at(i)->numReferences() == 0) m_clipList.at(i)->deleteProducers();
246     }
247 }
248
249 void ClipManager::resetProducersList(const QList <Mlt::Producer *> prods)
250 {
251     for (int i = 0; i < m_clipList.count(); i++) {
252         if (m_clipList.at(i)->numReferences() > 0) {
253             m_clipList.at(i)->clearProducers();
254         }
255     }
256     QString id;
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);
262         if (clip) {
263             clip->setProducer(prods.at(i));
264         }
265     }
266     emit checkAllClips();
267 }
268
269 void ClipManager::slotAddClipList(const KUrl::List urls, const QString group, const QString &groupId)
270 {
271     QUndoCommand *addClips = new QUndoCommand();
272
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)
277                     continue;
278             }
279             kDebug() << "Adding clip: " << file.path();
280             QDomDocument doc;
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);
289             }
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)) {
300                     txtfile.close();
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();
306                     if (out > 0)
307                         prod.setAttribute("out", out);
308                     else
309                         prod.setAttribute("out", m_doc->getFramePos(KdenliveSettings::image_duration()) - 1);
310                 } else
311                     txtfile.close();
312             }
313             new AddClipCommand(m_doc, doc.documentElement(), QString::number(id), true, addClips);
314         }
315     }
316     if (addClips->childCount() > 0) {
317         addClips->setText(i18np("Add clip", "Add clips", addClips->childCount()));
318         m_doc->commandStack()->push(addClips);
319     }
320 }
321
322 void ClipManager::slotAddClipFile(const KUrl url, const QString group, const QString &groupId)
323 {
324     slotAddClipList(KUrl::List(url), group, groupId);
325 }
326
327
328 void ClipManager::slotAddColorClipFile(const QString name, const QString color, QString duration, const QString group, const QString &groupId)
329 {
330     QDomDocument doc;
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);
344     }
345     AddClipCommand *command = new AddClipCommand(m_doc, doc.documentElement(), QString::number(id), true);
346     m_doc->commandStack()->push(command);
347 }
348
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)
350 {
351     QDomDocument doc;
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);
370     }
371     AddClipCommand *command = new AddClipCommand(m_doc, doc.documentElement(), QString::number(id), true);
372     m_doc->commandStack()->push(command);
373 }
374
375
376
377 void ClipManager::slotAddTextClipFile(const QString titleName, int out, const QString xml, const QString group, const QString &groupId)
378 {
379     QDomDocument doc;
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);
390     }
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);
397 }
398
399 void ClipManager::slotAddTextTemplateClip(QString titleName, const KUrl path, const QString group, const QString &groupId)
400 {
401     QDomDocument doc;
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);
411     }
412     prod.setAttribute("type", (int) TEXT);
413     prod.setAttribute("transparency", "1");
414     prod.setAttribute("in", "0");
415
416     int out = 0;
417     QDomDocument titledoc;
418     QFile txtfile(path.path());
419     if (txtfile.open(QIODevice::ReadOnly) && titledoc.setContent(&txtfile)) {
420         txtfile.close();
421         out = titledoc.documentElement().attribute("out").toInt();
422     } else txtfile.close();
423
424     if (out == 0) out = m_doc->getFramePos(KdenliveSettings::image_duration()) - 1;
425     prod.setAttribute("out", out);
426
427     AddClipCommand *command = new AddClipCommand(m_doc, doc.documentElement(), QString::number(id), true);
428     m_doc->commandStack()->push(command);
429 }
430
431 int ClipManager::getFreeClipId()
432 {
433     return m_clipIdCounter++;
434 }
435
436 int ClipManager::getFreeFolderId()
437 {
438     return m_folderIdCounter++;
439 }
440
441 int ClipManager::lastClipId() const
442 {
443     return m_clipIdCounter - 1;
444 }
445
446 QString ClipManager::projectFolder() const
447 {
448     return m_doc->projectFolder().path();
449 }
450
451 void ClipManager::addFolder(const QString &id, const QString &name)
452 {
453     m_folderList.insert(id, name);
454 }
455
456 void ClipManager::deleteFolder(const QString &id)
457 {
458     m_folderList.remove(id);
459 }
460
461 AbstractGroupItem *ClipManager::createGroup()
462 {
463     AbstractGroupItem *group = new AbstractGroupItem(m_doc->fps());
464     m_groupsList.append(group);
465     return group;
466 }
467
468 void ClipManager::removeGroup(AbstractGroupItem *group)
469 {
470     m_groupsList.removeAll(group);
471 }
472
473 QDomElement ClipManager::groupsXml() const
474 {
475     QDomDocument doc;
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);
496                 }
497             }
498         }
499     }
500     return doc.documentElement();
501 }
502
503
504 void ClipManager::slotClipModified(const QString &path)
505 {
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);
510         if (clip != NULL) {
511             QString id = clip->getId();
512             if (!m_modifiedClips.contains(id))
513                 emit modifiedClip(id);
514             m_modifiedClips[id] = QTime::currentTime();
515         }
516     }
517     if (!m_modifiedTimer.isActive()) m_modifiedTimer.start();
518 }
519
520 void ClipManager::slotProcessModifiedClips()
521 {
522     if (!m_modifiedClips.isEmpty()) {
523         QMapIterator<QString, QTime> i(m_modifiedClips);
524         while (i.hasNext()) {
525             i.next();
526             if (QTime::currentTime().msecsTo(i.value()) <= -1500) {
527                 emit reloadClip(i.key());
528                 m_modifiedClips.remove(i.key());
529                 break;
530             }
531         }
532     }
533     if (m_modifiedClips.isEmpty()) m_modifiedTimer.stop();
534 }
535
536 void ClipManager::slotClipMissing(const QString &path)
537 {
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());
543     }
544 }
545
546 void ClipManager::slotClipAvailable(const QString &path)
547 {
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());
553     }
554 }
555
556 int ClipManager::clipsCount() const
557 {
558     return m_clipList.count();
559 }
560
561