1 /***************************************************************************
2 * DocClipBase.cpp - description *
3 * ------------------- *
4 * begin : Fri Apr 12 2002 *
5 * Copyright (C) 2002 by Jason Wood (jasonwood@blueyonder.co.uk) *
6 * Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@kdenlive.org) *
8 * This program is free software; you can redistribute it and/or modify *
9 * it under the terms of the GNU General Public License as published by *
10 * the Free Software Foundation; either version 2 of the License, or *
11 * (at your option) any later version. *
13 * This program is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16 * GNU General Public License for more details. *
18 * You should have received a copy of the GNU General Public License *
19 * along with this program; if not, write to the *
20 * Free Software Foundation, Inc., *
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
22 ***************************************************************************/
27 #include "docclipbase.h"
28 #include "kdenlivesettings.h"
30 #include "clipmanager.h"
31 #include "slideshowclip.h"
33 #include <KIO/NetAccess>
34 #include <KStandardDirs>
35 #include <KApplication>
38 #include <QCryptographicHash>
39 #include <QtConcurrentRun>
42 #include <kmessagebox.h>
44 DocClipBase::DocClipBase(ClipManager *clipManager, QDomElement xml, const QString &id) :
49 m_baseTrackProducers(),
50 m_videoTrackProducers(),
51 m_audioTrackProducers(),
52 m_snapMarkers(QList < CommentedTime >()),
55 m_audioThumbCreated(false),
57 m_placeHolder(xml.hasAttribute("placeholder")),
60 int type = xml.attribute("type").toInt();
61 m_clipType = (CLIPTYPE) type;
62 if (m_placeHolder) xml.removeAttribute("placeholder");
63 QDomNamedNodeMap attributes = xml.attributes();
64 for (int i = 0; i < attributes.count(); i++) {
65 QString name = attributes.item(i).nodeName();
66 if (name.startsWith("meta.attr.")) {
67 m_metadata.insert(name.section('.', 2), QStringList() << attributes.item(i).nodeValue());
68 } else m_properties.insert(name, attributes.item(i).nodeValue());
70 QDomNodeList metas = xml.elementsByTagName("metaproperty");
71 for (int i = 0; i < metas.count(); i++) {
72 QDomElement e = metas.item(i).toElement();
74 m_metadata.insert(e.attribute("name").section('.', 2), QStringList() << e.firstChild().nodeValue() << e.attribute("tool"));
77 if (xml.hasAttribute("cutzones")) {
78 QStringList cuts = xml.attribute("cutzones").split(';', QString::SkipEmptyParts);
79 for (int i = 0; i < cuts.count(); i++) {
80 QString z = cuts.at(i);
81 addCutZone(z.section('-', 0, 0).toInt(), z.section('-', 1, 1).toInt(), z.section('-', 2, 2));
85 if (xml.hasAttribute("analysisdata")) {
86 QStringList adata = xml.attribute("analysisdata").split('#', QString::SkipEmptyParts);
87 for (int i = 0; i < adata.count(); i++)
88 m_analysisdata.insert(adata.at(i).section('?', 0, 0), adata.at(i).section('?', 1, 1));
91 KUrl url = KUrl(xml.attribute("resource"));
92 if (!m_properties.contains("file_hash") && !url.isEmpty()) getFileHash(url.path());
94 if (xml.hasAttribute("duration")) {
95 setDuration(GenTime(xml.attribute("duration").toInt(), KdenliveSettings::project_fps()));
97 int out = xml.attribute("out").toInt();
98 int in = xml.attribute("in").toInt();
99 if (out > in) setDuration(GenTime(out - in + 1, KdenliveSettings::project_fps()));
102 if (!m_properties.contains("name")) m_properties.insert("name", url.fileName());
104 m_thumbProd = new KThumb(clipManager, url, m_id, m_properties.value("file_hash"));
106 // Setup timer to trigger audio thumbs creation
107 m_audioTimer.setSingleShot(true);
108 m_audioTimer.setInterval(800);
109 connect(&m_audioTimer, SIGNAL(timeout()), m_thumbProd, SLOT(slotCreateAudioThumbs()));
113 DocClipBase::~DocClipBase()
118 qDeleteAll(m_toDeleteProducers);
119 m_toDeleteProducers.clear();
120 qDeleteAll(m_baseTrackProducers);
121 m_baseTrackProducers.clear();
122 qDeleteAll(m_audioTrackProducers);
123 m_audioTrackProducers.clear();
124 qDeleteAll(m_videoTrackProducers);
125 m_videoTrackProducers.clear();
128 void DocClipBase::setZone(QPoint zone)
130 m_properties.insert("zone_in", QString::number(zone.x()));
131 m_properties.insert("zone_out", QString::number(zone.y()));
134 QPoint DocClipBase::zone() const
136 QPoint zone(m_properties.value("zone_in", "0").toInt(), m_properties.value("zone_out", "50").toInt());
141 bool DocClipBase::hasAudioThumb() const
143 if (m_clipType == AUDIO || m_clipType == AV || m_clipType == PLAYLIST) return true;
147 void DocClipBase::slotClearAudioCache()
149 audioFrameCache.clear();
150 m_audioThumbCreated = false;
153 /*void DocClipBase::getClipMainThumb() {
154 if (m_thumbProd) m_thumbProd->getMainThumb(m_properties.value("thumbnail").toInt());
157 KThumb *DocClipBase::thumbProducer()
162 bool DocClipBase::audioThumbCreated() const
164 return m_audioThumbCreated;
167 const QString DocClipBase::name() const
170 return m_properties.value("name");
173 const QString &DocClipBase::getId() const
178 const CLIPTYPE & DocClipBase::clipType() const
183 void DocClipBase::setClipType(CLIPTYPE type)
186 m_properties.insert("type", QString::number((int) type));
189 KUrl DocClipBase::fileURL() const
191 QString res = m_properties.value("resource");
192 if (m_clipType != COLOR && !res.isEmpty()) return KUrl(res);
196 void DocClipBase::setClipThumbFrame(const uint &ix)
198 m_properties.insert("thumbnail", QString::number((int) ix));
201 uint DocClipBase::getClipThumbFrame() const
203 return (uint) m_properties.value("thumbnail").toInt();
206 const QString DocClipBase::description() const
208 return m_properties.value("description");
211 bool DocClipBase::isTransparent() const
213 return (m_properties.value("transparency") == "1");
216 const QString DocClipBase::getProperty(const QString &prop) const
218 return m_properties.value(prop);
221 void DocClipBase::setDuration(GenTime dur)
224 m_properties.insert("duration", QString::number((int) dur.frames(KdenliveSettings::project_fps())));
227 const GenTime &DocClipBase::duration() const
232 const GenTime DocClipBase::maxDuration() const
234 if (m_clipType == COLOR || m_clipType == IMAGE || m_clipType == TEXT || (m_clipType == SLIDESHOW && m_properties.value("loop") == "1")) {
235 /*const GenTime dur(15000, KdenliveSettings::project_fps());
242 bool DocClipBase::hasFileSize() const
247 qulonglong DocClipBase::fileSize() const
249 return m_properties.value("file_size").toULongLong();
253 QDomElement DocClipBase::toXML(bool hideTemporaryProperties) const
256 QDomElement clip = doc.createElement("producer");
258 QMapIterator<QString, QString> i(m_properties);
259 while (i.hasNext()) {
261 if (hideTemporaryProperties && i.key().startsWith('_')) continue;
262 if (!i.value().isEmpty()) clip.setAttribute(i.key(), i.value());
265 QMapIterator<QString, QStringList> j(m_metadata);
266 // Metadata name can have special chars so we cannot pass it as simple attribute
267 while (j.hasNext()) {
269 if (!j.value().isEmpty()) {
270 QDomElement property = doc.createElement("metaproperty");
271 property.setAttribute("name", "meta.attr." + j.key());
272 QStringList values = j.value();
273 QDomText value = doc.createTextNode(values.at(0));
274 if (values.count() > 1) property.setAttribute("tool", values.at(1));
275 property.appendChild(value);
276 clip.appendChild(property);
279 doc.appendChild(clip);
280 if (!m_cutZones.isEmpty()) {
282 for (int i = 0; i < m_cutZones.size(); i++) {
283 CutZoneInfo info = m_cutZones.at(i);
284 cuts << QString::number(info.zone.x()) + "-" + QString::number(info.zone.y()) + "-" + info.description;
286 clip.setAttribute("cutzones", cuts.join(";"));
289 if (!m_analysisdata.isEmpty()) {
290 QMapIterator<QString, QString> i(m_analysisdata);
291 while (i.hasNext()) {
293 //WARNING: a ? and # separator is not a good idea
294 adata.append(i.key() + "?" + i.value() + "#");
297 clip.setAttribute("analysisdata", adata);
298 //kDebug() << "/// CLIP XML: " << doc.toString();
299 return doc.documentElement();
303 void DocClipBase::setAudioThumbCreated(bool isDone)
305 m_audioThumbCreated = isDone;
308 void DocClipBase::updateAudioThumbnail(const audioByteArray& data)
310 //kDebug() << "CLIPBASE RECIEDVED AUDIO DATA*********************************************";
311 audioFrameCache = data;
312 m_audioThumbCreated = true;
316 QList < GenTime > DocClipBase::snapMarkers() const
318 QList < GenTime > markers;
319 for (int count = 0; count < m_snapMarkers.count(); ++count) {
320 markers.append(m_snapMarkers.at(count).time());
326 QList < CommentedTime > DocClipBase::commentedSnapMarkers() const
328 return m_snapMarkers;
332 void DocClipBase::addSnapMarker(const CommentedTime marker)
334 QList < CommentedTime >::Iterator it = m_snapMarkers.begin();
335 for (it = m_snapMarkers.begin(); it != m_snapMarkers.end(); ++it) {
336 if ((*it).time() >= marker.time())
340 if ((it != m_snapMarkers.end()) && ((*it).time() == marker.time())) {
341 (*it).setComment(marker.comment());
342 (*it).setMarkerType(marker.markerType());
343 //kError() << "trying to add Snap Marker that already exists, this will cause inconsistancies with undo/redo";
345 m_snapMarkers.insert(it, marker);
349 void DocClipBase::editSnapMarker(const GenTime & time, QString comment)
351 QList < CommentedTime >::Iterator it;
352 for (it = m_snapMarkers.begin(); it != m_snapMarkers.end(); ++it) {
353 if ((*it).time() == time)
356 if (it != m_snapMarkers.end()) {
357 (*it).setComment(comment);
359 kError() << "trying to edit Snap Marker that does not already exists";
363 QString DocClipBase::deleteSnapMarker(const GenTime & time)
365 QString result = i18n("Marker");
366 QList < CommentedTime >::Iterator itt = m_snapMarkers.begin();
368 while (itt != m_snapMarkers.end()) {
369 if ((*itt).time() == time)
374 if ((itt != m_snapMarkers.end()) && ((*itt).time() == time)) {
375 result = (*itt).comment();
376 m_snapMarkers.erase(itt);
382 GenTime DocClipBase::hasSnapMarkers(const GenTime & time)
384 QList < CommentedTime >::Iterator itt = m_snapMarkers.begin();
386 while (itt != m_snapMarkers.end()) {
387 if ((*itt).time() == time)
395 GenTime DocClipBase::findPreviousSnapMarker(const GenTime & currTime)
398 for (it = 0; it < m_snapMarkers.count(); it++) {
399 if (m_snapMarkers.at(it).time() >= currTime)
402 if (it == 0) return GenTime();
403 else if (it == m_snapMarkers.count() - 1 && m_snapMarkers.at(it).time() < currTime)
404 return m_snapMarkers.at(it).time();
405 else return m_snapMarkers.at(it - 1).time();
408 GenTime DocClipBase::findNextSnapMarker(const GenTime & currTime)
411 for (it = 0; it < m_snapMarkers.count(); it++) {
412 if (m_snapMarkers.at(it).time() > currTime)
415 if (it < m_snapMarkers.count() && m_snapMarkers.at(it).time() > currTime) return m_snapMarkers.at(it).time();
419 QString DocClipBase::markerComment(GenTime t) const
421 QList < CommentedTime >::ConstIterator itt = m_snapMarkers.begin();
422 while (itt != m_snapMarkers.end()) {
423 if ((*itt).time() == t)
424 return (*itt).comment();
430 CommentedTime DocClipBase::markerAt(GenTime t) const
432 QList < CommentedTime >::ConstIterator itt = m_snapMarkers.begin();
433 while (itt != m_snapMarkers.end()) {
434 if ((*itt).time() == t)
438 return CommentedTime();
441 void DocClipBase::clearThumbProducer()
443 if (m_thumbProd) m_thumbProd->clearProducer();
446 void DocClipBase::reloadThumbProducer()
448 if (m_thumbProd && !m_thumbProd->hasProducer())
449 m_thumbProd->setProducer(getProducer());
452 void DocClipBase::deleteProducers()
454 if (m_thumbProd) m_thumbProd->clearProducer();
456 if (numReferences() > 0 && (!m_baseTrackProducers.isEmpty() || !m_videoTrackProducers.isEmpty() || !m_audioTrackProducers.isEmpty())) {
457 // Clip is used in timeline, delay producers deletion
458 for (int i = 0; i < m_baseTrackProducers.count(); i++) {
459 m_toDeleteProducers.append(m_baseTrackProducers.at(i));
461 for (int i = 0; i < m_videoTrackProducers.count(); i++) {
462 m_toDeleteProducers.append(m_videoTrackProducers.at(i));
464 for (int i = 0; i < m_audioTrackProducers.count(); i++) {
465 m_toDeleteProducers.append(m_audioTrackProducers.at(i));
469 qDeleteAll(m_baseTrackProducers);
470 qDeleteAll(m_videoTrackProducers);
471 qDeleteAll(m_audioTrackProducers);
472 m_replaceMutex.unlock();
474 m_baseTrackProducers.clear();
475 m_videoTrackProducers.clear();
476 m_audioTrackProducers.clear();
479 void DocClipBase::cleanupProducers()
483 kDebug()<<"----------------------------------------------------------------------------------";
484 for (int i = 0; i < m_toDeleteProducers.count(); i++) {
485 if (m_toDeleteProducers.at(i) != NULL) {
486 Mlt::Properties props(m_toDeleteProducers.at(i)->get_properties());
487 if (props.ref_count() > 2) {
488 kDebug()<<"PRODUCER: "<<i<<", COUNTS: "<<props.ref_count();
496 qDeleteAll(m_toDeleteProducers);
497 m_toDeleteProducers.clear();
498 m_replaceMutex.unlock();
502 bool DocClipBase::isClean() const
504 return m_toDeleteProducers.isEmpty();
507 void DocClipBase::setValid()
509 m_placeHolder = false;
512 void DocClipBase::setProducer(Mlt::Producer *producer, bool reset, bool readPropertiesFromProducer)
514 if (producer == NULL) return;
516 QMutexLocker locker(&m_producerMutex);
517 m_replaceMutex.lock();
520 QString id = producer->get("id");
521 if (m_placeHolder || !producer->is_valid()) {
522 char *tmp = qstrdup(i18n("Missing clip").toUtf8().constData());
523 producer->set("markup", tmp);
524 producer->set("bgcolour", "0xff0000ff");
525 producer->set("pad", "10");
528 else if (m_thumbProd && !m_thumbProd->hasProducer()) {
529 if (m_clipType != AUDIO) {
530 if (!id.endsWith("_audio"))
531 m_thumbProd->setProducer(producer);
533 else m_thumbProd->setProducer(producer);
536 bool updated = false;
537 if (id.contains('_')) {
538 // this is a subtrack producer, insert it at correct place
539 id = id.section('_', 1);
540 if (id.endsWith("audio")) {
541 int pos = id.section('_', 0, 0).toInt();
542 if (pos >= m_audioTrackProducers.count()) {
543 while (m_audioTrackProducers.count() - 1 < pos) {
544 m_audioTrackProducers.append(NULL);
547 if (m_audioTrackProducers.at(pos) == NULL) {
548 m_audioTrackProducers[pos] = producer;
551 else delete producer;
553 } else if (id.endsWith("video")) {
555 // Keep compatibility with older projects where video only producers were not track specific
556 if (id.contains('_')) pos = id.section('_', 0, 0).toInt();
557 if (pos >= m_videoTrackProducers.count()) {
558 while (m_videoTrackProducers.count() - 1 < pos) {
559 m_videoTrackProducers.append(NULL);
562 if (m_videoTrackProducers.at(pos) == NULL) {
563 m_videoTrackProducers[pos] = producer;
566 else delete producer;
569 int pos = id.toInt();
570 if (pos >= m_baseTrackProducers.count()) {
571 while (m_baseTrackProducers.count() - 1 < pos) {
572 m_baseTrackProducers.append(NULL);
575 if (m_baseTrackProducers.at(pos) == NULL) {
576 m_baseTrackProducers[pos] = producer;
579 else delete producer;
581 if (m_baseTrackProducers.isEmpty()) {
582 m_baseTrackProducers.append(producer);
585 else if (m_baseTrackProducers.at(0) == NULL) {
586 m_baseTrackProducers[0] = producer;
589 else delete producer;
591 if (updated && readPropertiesFromProducer && (m_clipType != COLOR && m_clipType != IMAGE && m_clipType != TEXT))
592 setDuration(GenTime(producer->get_length(), KdenliveSettings::project_fps()));
595 static double getPixelAspect(QMap<QString, QString>& props) {
596 int width = props.value("frame_size").section('x', 0, 0).toInt();
597 int height = props.value("frame_size").section('x', 1, 1).toInt();
598 int aspectNumerator = props.value("force_aspect_num").toInt();
599 int aspectDenominator = props.value("force_aspect_den").toInt();
600 if (aspectDenominator != 0 && width != 0)
601 return double(height) * aspectNumerator / aspectDenominator / width;
606 Mlt::Producer *DocClipBase::audioProducer(int track)
608 QMutexLocker locker(&m_producerMutex);
609 if (m_audioTrackProducers.count() <= track) {
610 while (m_audioTrackProducers.count() - 1 < track) {
611 m_audioTrackProducers.append(NULL);
614 if (m_audioTrackProducers.at(track) == NULL) {
616 for (i = 0; i < m_audioTrackProducers.count(); i++)
617 if (m_audioTrackProducers.at(i) != NULL) break;
619 if (i >= m_audioTrackProducers.count()) {
620 // Could not find a valid producer for that clip
622 base = getProducer();
628 else base = m_audioTrackProducers.at(i);
629 m_audioTrackProducers[track] = cloneProducer(base);
630 adjustProducerProperties(m_audioTrackProducers.at(track), QString(getId() + '_' + QString::number(track) + "_audio"), false, true);
632 return m_audioTrackProducers.at(track);
636 void DocClipBase::adjustProducerProperties(Mlt::Producer *prod, const QString &id, bool mute, bool blind)
638 if (m_properties.contains("force_aspect_num") && m_properties.contains("force_aspect_den") && m_properties.contains("frame_size"))
639 prod->set("force_aspect_ratio", getPixelAspect(m_properties));
640 if (m_properties.contains("force_fps")) prod->set("force_fps", m_properties.value("force_fps").toDouble());
641 if (m_properties.contains("force_progressive")) prod->set("force_progressive", m_properties.value("force_progressive").toInt());
642 if (m_properties.contains("force_tff")) prod->set("force_tff", m_properties.value("force_tff").toInt());
643 if (m_properties.contains("threads")) prod->set("threads", m_properties.value("threads").toInt());
644 if (mute) prod->set("audio_index", -1);
645 else if (m_properties.contains("audio_index")) prod->set("audio_index", m_properties.value("audio_index").toInt());
646 if (blind) prod->set("video_index", -1);
647 else if (m_properties.contains("video_index")) prod->set("video_index", m_properties.value("video_index").toInt());
648 prod->set("id", id.toUtf8().constData());
649 if (m_properties.contains("force_colorspace")) prod->set("force_colorspace", m_properties.value("force_colorspace").toInt());
650 if (m_properties.contains("full_luma")) prod->set("set.force_full_luma", m_properties.value("full_luma").toInt());
651 if (m_properties.contains("proxy_out")) {
652 // We have a proxy clip, make sure the proxy has same duration as original
653 prod->set("length", m_properties.value("duration").toInt());
654 prod->set("out", m_properties.value("proxy_out").toInt());
659 Mlt::Producer *DocClipBase::videoProducer(int track)
661 QMutexLocker locker(&m_producerMutex);
662 if (m_videoTrackProducers.count() <= track) {
663 while (m_videoTrackProducers.count() - 1 < track) {
664 m_videoTrackProducers.append(NULL);
667 if (m_videoTrackProducers.at(track) == NULL) {
669 for (i = 0; i < m_videoTrackProducers.count(); i++)
670 if (m_videoTrackProducers.at(i) != NULL) break;
672 if (i >= m_videoTrackProducers.count()) {
673 // Could not find a valid producer for that clip
675 base = getProducer();
681 else base = m_videoTrackProducers.at(i);
682 m_videoTrackProducers[track] = cloneProducer(base);
683 adjustProducerProperties(m_videoTrackProducers.at(track), QString(getId() + '_' + QString::number(track) + "_video"), true, false);
685 return m_videoTrackProducers.at(track);
688 Mlt::Producer *DocClipBase::getCloneProducer()
690 Mlt::Producer *source = NULL;
691 Mlt::Producer *prod = NULL;
692 if (m_clipType != AUDIO && m_clipType != AV && m_clipType != PLAYLIST) {
693 source = getProducer();
694 if (!source) return NULL;
696 if (m_clipType == COLOR) {
697 prod = new Mlt::Producer(*(source->profile()), 0, QString("colour:" + QString(source->get("resource"))).toUtf8().constData());
698 } else if (m_clipType == TEXT) {
699 prod = new Mlt::Producer(*(source->profile()), 0, QString("kdenlivetitle:" + QString(source->get("resource"))).toUtf8().constData());
700 if (prod && prod->is_valid() && m_properties.contains("xmldata"))
701 prod->set("xmldata", m_properties.value("xmldata").toUtf8().constData());
705 QMutexLocker locker(&m_producerMutex);
706 for (int i = 0; i < m_baseTrackProducers.count(); i++) {
707 if (m_baseTrackProducers.at(i) != NULL) {
708 source = m_baseTrackProducers.at(i);
712 if (!source) return NULL;
714 prod = cloneProducer(source);
717 adjustProducerProperties(prod, getId() + "_", false, false);
718 if (!m_properties.contains("proxy_out")) {
719 // Adjust length in case...
720 if (m_properties.contains("duration")) prod->set("length", m_properties.value("duration").toInt());
721 if (m_properties.contains("out"))prod->set("out", m_properties.value("out").toInt());
723 if (m_clipType == AUDIO) {
724 prod->set("_audioclip", 1);
731 Mlt::Producer *DocClipBase::getProducer(int track)
733 QMutexLocker locker(&m_producerMutex);
734 if (track == -1 || (m_clipType != AUDIO && m_clipType != AV && m_clipType != PLAYLIST)) {
735 if (m_baseTrackProducers.count() == 0) {
738 for (int i = 0; i < m_baseTrackProducers.count(); i++) {
739 if (m_baseTrackProducers.at(i) != NULL) {
740 return m_baseTrackProducers.at(i);
745 if (track >= m_baseTrackProducers.count()) {
746 while (m_baseTrackProducers.count() - 1 < track) {
747 m_baseTrackProducers.append(NULL);
750 if (m_baseTrackProducers.at(track) == NULL) {
752 for (i = 0; i < m_baseTrackProducers.count(); i++)
753 if (m_baseTrackProducers.at(i) != NULL) break;
755 if (i >= m_baseTrackProducers.count()) {
756 // Could not find a valid producer for that clip, check in
759 Mlt::Producer *prod = cloneProducer(m_baseTrackProducers.at(i));
760 adjustProducerProperties(prod, QString(getId() + '_' + QString::number(track)), false, false);
761 m_baseTrackProducers[track] = prod;
763 return m_baseTrackProducers.at(track);
767 Mlt::Producer *DocClipBase::cloneProducer(Mlt::Producer *source)
769 Mlt::Producer *result = NULL;
770 QString url = QString::fromUtf8(source->get("resource"));
771 if (url == "<playlist>" || url == "<tractor>" || url == "<producer>") {
772 // Xml producer sometimes loses the correct url
773 url = m_properties.value("resource");
775 if (m_clipType == SLIDESHOW || KIO::NetAccess::exists(KUrl(url), KIO::NetAccess::SourceSide, 0)) {
776 result = new Mlt::Producer(*(source->profile()), url.toUtf8().constData());
778 if (result == NULL || !result->is_valid()) {
780 QString txt = "+" + i18n("Missing clip") + ".txt";
781 char *tmp = qstrdup(txt.toUtf8().constData());
782 result = new Mlt::Producer(*source->profile(), tmp);
784 if (result == NULL || !result->is_valid())
785 result = new Mlt::Producer(*(source->profile()), "colour:red");
787 result->set("bgcolour", "0xff0000ff");
788 result->set("pad", "10");
792 /*Mlt::Properties src_props(source->get_properties());
793 Mlt::Properties props(result->get_properties());
794 props.inherit(src_props);*/
798 void DocClipBase::setProducerProperty(const char *name, int data)
800 QMutexLocker locker(&m_producerMutex);
801 for (int i = 0; i < m_baseTrackProducers.count(); i++) {
802 if (m_baseTrackProducers.at(i) != NULL)
803 m_baseTrackProducers[i]->set(name, data);
807 void DocClipBase::setProducerProperty(const char *name, double data)
809 QMutexLocker locker(&m_producerMutex);
810 for (int i = 0; i < m_baseTrackProducers.count(); i++) {
811 if (m_baseTrackProducers.at(i) != NULL)
812 m_baseTrackProducers[i]->set(name, data);
816 void DocClipBase::setProducerProperty(const char *name, const char *data)
818 QMutexLocker locker(&m_producerMutex);
819 for (int i = 0; i < m_baseTrackProducers.count(); i++) {
820 if (m_baseTrackProducers.at(i) != NULL)
821 m_baseTrackProducers[i]->set(name, data);
825 void DocClipBase::resetProducerProperty(const char *name)
827 QMutexLocker locker(&m_producerMutex);
828 for (int i = 0; i < m_baseTrackProducers.count(); i++) {
829 if (m_baseTrackProducers.at(i) != NULL)
830 m_baseTrackProducers[i]->set(name, (const char*) NULL);
834 const char *DocClipBase::producerProperty(const char *name) const
836 for (int i = 0; i < m_baseTrackProducers.count(); i++) {
837 if (m_baseTrackProducers.at(i) != NULL) {
838 return m_baseTrackProducers.at(i)->get(name);
845 void DocClipBase::slotRefreshProducer()
847 if (m_baseTrackProducers.count() == 0) return;
848 if (m_clipType == SLIDESHOW) {
849 setProducerProperty("ttl", getProperty("ttl").toInt());
850 //m_clipProducer->set("id", getProperty("id"));
851 if (!getProperty("animation").isEmpty()) {
852 Mlt::Service clipService(m_baseTrackProducers.at(0)->get_service());
854 Mlt::Filter *filter = clipService.filter(ct);
856 if (strcmp(filter->get("mlt_service"), "affine") == 0) {
858 } else if (strcmp(filter->get("mlt_service"), "boxblur") == 0) {
859 clipService.detach(*filter);
861 filter = clipService.filter(ct);
864 if (!filter || strcmp(filter->get("mlt_service"), "affine")) {
865 // filter does not exist, create it.
866 Mlt::Filter *filter = new Mlt::Filter(*(m_baseTrackProducers.at(0)->profile()), "affine");
867 if (filter && filter->is_valid()) {
868 int cycle = getProperty("ttl").toInt();
869 QString geometry = SlideshowClip::animationToGeometry(getProperty("animation"), cycle);
870 if (!geometry.isEmpty()) {
871 if (getProperty("animation").contains("low-pass")) {
872 Mlt::Filter *blur = new Mlt::Filter(*(m_baseTrackProducers.at(0)->profile()), "boxblur");
873 if (blur && blur->is_valid())
874 clipService.attach(*blur);
876 filter->set("transition.geometry", geometry.toUtf8().data());
877 filter->set("transition.cycle", cycle);
878 clipService.attach(*filter);
883 Mlt::Service clipService(m_baseTrackProducers.at(0)->get_service());
885 Mlt::Filter *filter = clipService.filter(0);
887 if (strcmp(filter->get("mlt_service"), "affine") == 0 || strcmp(filter->get("mlt_service"), "boxblur") == 0) {
888 clipService.detach(*filter);
890 filter = clipService.filter(ct);
893 if (getProperty("fade") == "1") {
894 // we want a fade filter effect
895 kDebug() << "//////////// FADE WANTED";
896 Mlt::Service clipService(m_baseTrackProducers.at(0)->get_service());
898 Mlt::Filter *filter = clipService.filter(ct);
900 if (strcmp(filter->get("mlt_service"), "luma") == 0) {
904 filter = clipService.filter(ct);
907 if (filter && strcmp(filter->get("mlt_service"), "luma") == 0) {
908 filter->set("cycle", getProperty("ttl").toInt());
909 filter->set("duration", getProperty("luma_duration").toInt());
910 filter->set("luma.resource", getProperty("luma_file").toUtf8().data());
911 if (!getProperty("softness").isEmpty()) {
912 int soft = getProperty("softness").toInt();
913 filter->set("luma.softness", (double) soft / 100.0);
916 // filter does not exist, create it...
917 Mlt::Filter *filter = new Mlt::Filter(*(m_baseTrackProducers.at(0)->profile()), "luma");
918 filter->set("cycle", getProperty("ttl").toInt());
919 filter->set("duration", getProperty("luma_duration").toInt());
920 filter->set("luma.resource", getProperty("luma_file").toUtf8().data());
921 if (!getProperty("softness").isEmpty()) {
922 int soft = getProperty("softness").toInt();
923 filter->set("luma.softness", (double) soft / 100.0);
925 clipService.attach(*filter);
928 kDebug() << "//////////// FADE NOT WANTED!!!";
929 Mlt::Service clipService(m_baseTrackProducers.at(0)->get_service());
931 Mlt::Filter *filter = clipService.filter(0);
933 if (strcmp(filter->get("mlt_service"), "luma") == 0) {
934 clipService.detach(*filter);
936 filter = clipService.filter(ct);
939 if (getProperty("crop") == "1") {
940 // we want a center crop filter effect
941 Mlt::Service clipService(m_baseTrackProducers.at(0)->get_service());
943 Mlt::Filter *filter = clipService.filter(ct);
945 if (strcmp(filter->get("mlt_service"), "crop") == 0) {
949 filter = clipService.filter(ct);
952 if (!filter || strcmp(filter->get("mlt_service"), "crop")) {
953 // filter does not exist, create it...
954 Mlt::Filter *filter = new Mlt::Filter(*(m_baseTrackProducers.at(0)->profile()), "crop");
955 filter->set("center", 1);
956 clipService.attach(*filter);
959 Mlt::Service clipService(m_baseTrackProducers.at(0)->get_service());
961 Mlt::Filter *filter = clipService.filter(0);
963 if (strcmp(filter->get("mlt_service"), "crop") == 0) {
964 clipService.detach(*filter);
966 filter = clipService.filter(ct);
972 void DocClipBase::setProperties(QMap <QString, QString> properties)
974 // changing clip type is not allowed
975 properties.remove("type");
976 QMapIterator<QString, QString> i(properties);
977 bool refreshProducer = false;
979 keys << "luma_duration" << "luma_file" << "fade" << "ttl" << "softness" << "crop" << "animation";
980 QString oldProxy = m_properties.value("proxy");
981 while (i.hasNext()) {
983 setProperty(i.key(), i.value());
984 if (m_clipType == SLIDESHOW && keys.contains(i.key())) refreshProducer = true;
986 if (properties.contains("proxy")) {
987 QString value = properties.value("proxy");
988 // If value is "-", that means user manually disabled proxy on this clip
989 if (value.isEmpty() || value == "-") {
991 emit abortProxy(m_id, oldProxy);
994 emit createProxy(m_id);
997 if (refreshProducer) slotRefreshProducer();
1000 void DocClipBase::setMetadata(QMap <QString, QString> properties, QString tool)
1002 QMapIterator<QString, QString> i(properties);
1003 while (i.hasNext()) {
1005 if (i.value().isEmpty() && m_metadata.contains(i.key())) {
1006 m_metadata.remove(i.key());
1008 m_metadata.insert(i.key(), QStringList() << i.value() << tool);
1013 QMap <QString, QStringList> DocClipBase::metadata() const
1018 void DocClipBase::clearProperty(const QString &key)
1020 m_properties.remove(key);
1023 void DocClipBase::getFileHash(const QString &url)
1025 if (m_clipType == SLIDESHOW) return;
1027 if (file.open(QIODevice::ReadOnly)) { // write size and hash only if resource points to a file
1028 QByteArray fileData;
1029 QByteArray fileHash;
1030 //kDebug() << "SETTING HASH of" << value;
1031 m_properties.insert("file_size", QString::number(file.size()));
1033 * 1 MB = 1 second per 450 files (or faster)
1034 * 10 MB = 9 seconds per 450 files (or faster)
1036 if (file.size() > 1000000*2) {
1037 fileData = file.read(1000000);
1038 if (file.seek(file.size() - 1000000))
1039 fileData.append(file.readAll());
1041 fileData = file.readAll();
1043 fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
1044 m_properties.insert("file_hash", QString(fileHash.toHex()));
1048 bool DocClipBase::checkHash() const
1050 KUrl url = fileURL();
1051 if (!url.isEmpty() && getClipHash() != getHash(url.path())) return false;
1055 QString DocClipBase::getClipHash() const
1058 if (m_clipType == SLIDESHOW) hash = QCryptographicHash::hash(m_properties.value("resource").toAscii().data(), QCryptographicHash::Md5).toHex();
1059 else if (m_clipType == COLOR) hash = QCryptographicHash::hash(m_properties.value("colour").toAscii().data(), QCryptographicHash::Md5).toHex();
1060 else if (m_clipType == TEXT) hash = QCryptographicHash::hash(QString("title" + getId() + m_properties.value("xmldata")).toUtf8().data(), QCryptographicHash::Md5).toHex();
1062 if (m_properties.contains("file_hash")) hash = m_properties.value("file_hash");
1063 if (hash.isEmpty()) hash = getHash(fileURL().path());
1069 void DocClipBase::setPlaceHolder(bool place)
1071 m_placeHolder = place;
1075 QString DocClipBase::getHash(const QString &path)
1078 if (file.open(QIODevice::ReadOnly)) { // write size and hash only if resource points to a file
1079 QByteArray fileData;
1080 QByteArray fileHash;
1082 * 1 MB = 1 second per 450 files (or faster)
1083 * 10 MB = 9 seconds per 450 files (or faster)
1085 if (file.size() > 1000000*2) {
1086 fileData = file.read(1000000);
1087 if (file.seek(file.size() - 1000000))
1088 fileData.append(file.readAll());
1090 fileData = file.readAll();
1092 return QCryptographicHash::hash(fileData, QCryptographicHash::Md5).toHex();
1097 void DocClipBase::refreshThumbUrl()
1099 if (m_thumbProd) m_thumbProd->updateThumbUrl(m_properties.value("file_hash"));
1102 void DocClipBase::setProperty(const QString &key, const QString &value)
1104 m_properties.insert(key, value);
1105 if (key == "resource") {
1107 if (m_thumbProd) m_thumbProd->updateClipUrl(KUrl(value), m_properties.value("file_hash"));
1108 //else if (key == "transparency") m_clipProducer->set("transparency", value.toInt());
1109 } else if (key == "out") {
1110 setDuration(GenTime(value.toInt() + 1, KdenliveSettings::project_fps()));
1112 else if (key == "colour") {
1113 setProducerProperty("colour", value.toUtf8().data());
1114 } else if (key == "templatetext") {
1115 setProducerProperty("templatetext", value.toUtf8().data());
1116 setProducerProperty("force_reload", 1);
1117 } else if (key == "xmldata") {
1118 setProducerProperty("xmldata", value.toUtf8().data());
1119 setProducerProperty("force_reload", 1);
1120 } else if (key == "force_aspect_num") {
1121 if (value.isEmpty()) {
1122 m_properties.remove("force_aspect_num");
1123 resetProducerProperty("force_aspect_ratio");
1124 } else setProducerProperty("force_aspect_ratio", getPixelAspect(m_properties));
1125 } else if (key == "force_aspect_den") {
1126 if (value.isEmpty()) {
1127 m_properties.remove("force_aspect_den");
1128 resetProducerProperty("force_aspect_ratio");
1129 } else setProducerProperty("force_aspect_ratio", getPixelAspect(m_properties));
1130 } else if (key == "force_fps") {
1131 if (value.isEmpty()) {
1132 m_properties.remove("force_fps");
1133 resetProducerProperty("force_fps");
1134 } else setProducerProperty("force_fps", value.toDouble());
1135 } else if (key == "force_progressive") {
1136 if (value.isEmpty()) {
1137 m_properties.remove("force_progressive");
1138 resetProducerProperty("force_progressive");
1139 } else setProducerProperty("force_progressive", value.toInt());
1140 } else if (key == "force_tff") {
1141 if (value.isEmpty()) {
1142 m_properties.remove("force_tff");
1143 resetProducerProperty("force_tff");
1144 } else setProducerProperty("force_tff", value.toInt());
1145 } else if (key == "threads") {
1146 if (value.isEmpty()) {
1147 m_properties.remove("threads");
1148 setProducerProperty("threads", 1);
1149 } else setProducerProperty("threads", value.toInt());
1150 } else if (key == "video_index") {
1151 if (value.isEmpty()) {
1152 m_properties.remove("video_index");
1153 setProducerProperty("video_index", m_properties.value("default_video").toInt());
1154 } else setProducerProperty("video_index", value.toInt());
1155 } else if (key == "audio_index") {
1156 if (value.isEmpty()) {
1157 m_properties.remove("audio_index");
1158 setProducerProperty("audio_index", m_properties.value("default_audio").toInt());
1159 } else setProducerProperty("audio_index", value.toInt());
1160 } else if (key == "force_colorspace") {
1161 if (value.isEmpty()) {
1162 m_properties.remove("force_colorspace");
1163 resetProducerProperty("force_colorspace");
1164 } else setProducerProperty("force_colorspace", value.toInt());
1165 } else if (key == "full_luma") {
1166 if (value.isEmpty()) {
1167 m_properties.remove("full_luma");
1168 resetProducerProperty("set.force_full_luma");
1169 } else setProducerProperty("set.force_full_luma", value.toInt());
1173 QMap <QString, QString> DocClipBase::properties() const
1175 return m_properties;
1178 QMap <QString, QString> DocClipBase::currentProperties(QMap <QString, QString> props)
1180 QMap <QString, QString> currentProps;
1181 QMap<QString, QString>::const_iterator i = props.constBegin();
1182 while (i != props.constEnd()) {
1183 currentProps.insert(i.key(), m_properties.value(i.key()));
1186 return currentProps;
1189 bool DocClipBase::getAudioThumbs()
1191 if (m_thumbProd == NULL || isPlaceHolder() || !KdenliveSettings::audiothumbnails()) return false;
1192 if (m_audioThumbCreated) {
1195 m_audioTimer.start();
1199 bool DocClipBase::isPlaceHolder() const
1201 return m_placeHolder;
1204 void DocClipBase::addCutZone(int in, int out, QString desc)
1207 info.zone = QPoint(in, out);
1208 info.description = desc;
1209 for (int i = 0; i < m_cutZones.count(); i++)
1210 if (m_cutZones.at(i).zone == info.zone) {
1213 m_cutZones.append(info);
1216 bool DocClipBase::hasCutZone(QPoint p) const
1218 for (int i = 0; i < m_cutZones.count(); i++)
1219 if (m_cutZones.at(i).zone == p) return true;
1224 void DocClipBase::removeCutZone(int in, int out)
1227 for (int i = 0; i < m_cutZones.count(); i++) {
1228 if (m_cutZones.at(i).zone == p) {
1229 m_cutZones.removeAt(i);
1235 void DocClipBase::updateCutZone(int oldin, int oldout, int in, int out, QString desc)
1237 QPoint old(oldin, oldout);
1238 for (int i = 0; i < m_cutZones.size(); ++i) {
1239 if (m_cutZones.at(i).zone == old) {
1241 info.zone = QPoint(in, out);
1242 info.description = desc;
1243 m_cutZones.replace(i, info);
1249 QList <CutZoneInfo> DocClipBase::cutZones() const
1254 bool DocClipBase::hasVideoCodec(const QString &codec) const
1256 Mlt::Producer *prod = NULL;
1257 if (m_baseTrackProducers.count() == 0) return false;
1258 for (int i = 0; i < m_baseTrackProducers.count(); i++) {
1259 if (m_baseTrackProducers.at(i) != NULL) {
1260 prod = m_baseTrackProducers.at(i);
1265 if (!prod) return false;
1266 int default_video = prod->get_int("video_index");
1268 snprintf(property, sizeof(property), "meta.media.%d.codec.name", default_video);
1269 return prod->get(property) == codec;
1272 bool DocClipBase::hasAudioCodec(const QString &codec) const
1274 Mlt::Producer *prod = NULL;
1275 if (m_baseTrackProducers.count() == 0) return false;
1276 for (int i = 0; i < m_baseTrackProducers.count(); i++) {
1277 if (m_baseTrackProducers.at(i) != NULL) {
1278 prod = m_baseTrackProducers.at(i);
1282 if (!prod) return false;
1283 int default_video = prod->get_int("audio_index");
1285 snprintf(property, sizeof(property), "meta.media.%d.codec.name", default_video);
1286 return prod->get(property) == codec;
1290 void DocClipBase::slotExtractImage(QList <int> frames)
1292 if (m_thumbProd == NULL) return;
1293 m_thumbProd->extractImage(frames);
1296 QImage DocClipBase::extractImage(int frame, int width, int height)
1298 if (m_thumbProd == NULL) return QImage();
1299 QMutexLocker locker(&m_producerMutex);
1300 return m_thumbProd->extractImage(frame, width, height);
1303 void DocClipBase::setAnalysisData(const QString &name, const QString &data, int offset)
1305 if (data.isEmpty()) m_analysisdata.remove(name);
1307 if (m_analysisdata.contains(name)) {
1308 if (KMessageBox::questionYesNo(kapp->activeWindow(), i18n("Clip already contains analysis data %1", name), QString(), KGuiItem(i18n("Merge")), KGuiItem(i18n("Add"))) == KMessageBox::Yes) {
1310 Mlt::Profile *profile = m_baseTrackProducers.at(0)->profile();
1311 Mlt::Geometry geometry(m_analysisdata.value(name).toUtf8().data(), m_properties.value("duration").toInt(), profile->width(), profile->height());
1312 Mlt::Geometry newGeometry(data.toUtf8().data(), m_properties.value("duration").toInt(), profile->width(), profile->height());
1313 Mlt::GeometryItem item;
1315 while (!newGeometry.next_key(&item, pos)) {
1317 item.frame(pos + offset);
1319 geometry.insert(item);
1321 m_analysisdata.insert(name, geometry.serialise());
1324 // Add data with another name
1326 QString newname = name + " " + QString::number(i);
1327 while (m_analysisdata.contains(newname)) {
1329 newname = name + " " + QString::number(i);
1331 m_analysisdata.insert(newname, geometryWithOffset(data, offset));
1334 else m_analysisdata.insert(name, geometryWithOffset(data, offset));
1338 const QString DocClipBase::geometryWithOffset(QString data, int offset)
1340 if (offset == 0) return data;
1341 Mlt::Profile *profile = m_baseTrackProducers.at(0)->profile();
1342 Mlt::Geometry geometry(data.toUtf8().data(), m_properties.value("duration").toInt(), profile->width(), profile->height());
1343 Mlt::Geometry newgeometry(NULL, m_properties.value("duration").toInt(), profile->width(), profile->height());
1344 Mlt::GeometryItem item;
1346 while (!geometry.next_key(&item, pos)) {
1348 item.frame(pos + offset);
1350 newgeometry.insert(item);
1352 return newgeometry.serialise();
1355 QMap <QString, QString> DocClipBase::analysisData() const
1357 return m_analysisdata;