]> git.sesse.net Git - kdenlive/blob - src/docclipbase.cpp
Prepare importing of keyframes from clip analysis
[kdenlive] / src / docclipbase.cpp
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)        *
7  *                                                                         *
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.                                   *
12  *                                                                         *
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.                          *
17  *                                                                         *
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  ***************************************************************************/
23
24
25
26
27 #include "docclipbase.h"
28 #include "kdenlivesettings.h"
29 #include "kthumb.h"
30 #include "clipmanager.h"
31 #include "slideshowclip.h"
32
33 #include <KIO/NetAccess>
34 #include <KStandardDirs>
35 #include <KDebug>
36
37 #include <QCryptographicHash>
38 #include <QtConcurrentRun>
39
40 #include <cstdio>
41
42 DocClipBase::DocClipBase(ClipManager *clipManager, QDomElement xml, const QString &id) :
43         QObject(),
44         m_audioFrameCache(),
45         m_refcount(0),
46         m_baseTrackProducers(),
47         m_videoTrackProducers(),
48         m_audioTrackProducers(),
49         m_snapMarkers(QList < CommentedTime >()),
50         m_duration(),
51         m_thumbProd(NULL),
52         m_audioThumbCreated(false),
53         m_id(id),
54         m_placeHolder(xml.hasAttribute("placeholder")),
55         m_properties()
56 {
57     int type = xml.attribute("type").toInt();
58     m_clipType = (CLIPTYPE) type;
59     if (m_placeHolder) xml.removeAttribute("placeholder");
60     QDomNamedNodeMap attributes = xml.attributes();
61     for (int i = 0; i < attributes.count(); i++) {
62         QString name = attributes.item(i).nodeName();
63         if (name.startsWith("meta.attr.")) {
64             m_metadata.insert(name.section('.', 2, 3), attributes.item(i).nodeValue());
65         } else m_properties.insert(name, attributes.item(i).nodeValue());
66     }
67
68     if (xml.hasAttribute("cutzones")) {
69         QStringList cuts = xml.attribute("cutzones").split(';', QString::SkipEmptyParts);
70         for (int i = 0; i < cuts.count(); i++) {
71             QString z = cuts.at(i);
72             addCutZone(z.section('-', 0, 0).toInt(), z.section('-', 1, 1).toInt(), z.section('-', 2, 2));
73         }
74     }
75
76     if (xml.hasAttribute("analysisdata")) {
77         QStringList adata = xml.attribute("analysisdata").split('#', QString::SkipEmptyParts);
78         for (int i = 0; i < adata.count(); i++)
79             m_analysisdata.insert(adata.at(i).section('?', 0, 0), adata.at(i).section('?', 1, 1));
80     }
81
82     KUrl url = KUrl(xml.attribute("resource"));
83     if (!m_properties.contains("file_hash") && !url.isEmpty()) getFileHash(url.path());
84
85     if (xml.hasAttribute("duration")) {
86         setDuration(GenTime(xml.attribute("duration").toInt(), KdenliveSettings::project_fps()));
87     } else {
88         int out = xml.attribute("out").toInt();
89         int in = xml.attribute("in").toInt();
90         setDuration(GenTime(out - in, KdenliveSettings::project_fps()));
91     }
92
93     if (!m_properties.contains("name")) m_properties.insert("name", url.fileName());
94
95     m_thumbProd = new KThumb(clipManager, url, m_id, m_properties.value("file_hash"));
96     
97     // Setup timer to trigger audio thumbs creation
98     m_audioTimer.setSingleShot(true);
99     m_audioTimer.setInterval(800);
100     connect(&m_audioTimer, SIGNAL(timeout()), m_thumbProd, SLOT(slotCreateAudioThumbs()));
101     
102 }
103
104 DocClipBase::~DocClipBase()
105 {
106     m_audioTimer.stop();
107     delete m_thumbProd;
108     m_thumbProd = NULL;
109     qDeleteAll(m_toDeleteProducers);
110     m_toDeleteProducers.clear();
111     qDeleteAll(m_baseTrackProducers);
112     m_baseTrackProducers.clear();
113     qDeleteAll(m_audioTrackProducers);
114     m_audioTrackProducers.clear();
115     qDeleteAll(m_videoTrackProducers);
116     m_videoTrackProducers.clear();
117 }
118
119 void DocClipBase::setZone(QPoint zone)
120 {
121     m_properties.insert("zone_in", QString::number(zone.x()));
122     m_properties.insert("zone_out", QString::number(zone.y()));
123 }
124
125 QPoint DocClipBase::zone() const
126 {
127     QPoint zone(m_properties.value("zone_in", "0").toInt(), m_properties.value("zone_out", "50").toInt());
128     return zone;
129 }
130
131
132 bool DocClipBase::hasAudioThumb() const
133 {
134     if (m_clipType == AUDIO || m_clipType == AV || m_clipType == PLAYLIST) return true;
135     return false;
136 }
137
138 void DocClipBase::slotClearAudioCache()
139 {
140     m_audioFrameCache.clear();
141     m_audioThumbCreated = false;
142 }
143
144 /*void DocClipBase::getClipMainThumb() {
145     if (m_thumbProd) m_thumbProd->getMainThumb(m_properties.value("thumbnail").toInt());
146 }*/
147
148 KThumb *DocClipBase::thumbProducer()
149 {
150     return m_thumbProd;
151 }
152
153 bool DocClipBase::audioThumbCreated() const
154 {
155     return m_audioThumbCreated;
156 }
157
158 const QString DocClipBase::name() const
159 {
160
161     return m_properties.value("name");
162 }
163
164 const QString &DocClipBase::getId() const
165 {
166     return m_id;
167 }
168
169 const CLIPTYPE & DocClipBase::clipType() const
170 {
171     return m_clipType;
172 }
173
174 void DocClipBase::setClipType(CLIPTYPE type)
175 {
176     m_clipType = type;
177     m_properties.insert("type", QString::number((int) type));
178 }
179
180 KUrl DocClipBase::fileURL() const
181 {
182     QString res = m_properties.value("resource");
183     if (m_clipType != COLOR && !res.isEmpty()) return KUrl(res);
184     return KUrl();
185 }
186
187 void DocClipBase::setClipThumbFrame(const uint &ix)
188 {
189     m_properties.insert("thumbnail", QString::number((int) ix));
190 }
191
192 uint DocClipBase::getClipThumbFrame() const
193 {
194     return (uint) m_properties.value("thumbnail").toInt();
195 }
196
197 const QString DocClipBase::description() const
198 {
199     return m_properties.value("description");
200 }
201
202 bool DocClipBase::isTransparent() const
203 {
204     return (m_properties.value("transparency") == "1");
205 }
206
207 const QString DocClipBase::getProperty(const QString &prop) const
208 {
209     return m_properties.value(prop);
210 }
211
212 void DocClipBase::setDuration(GenTime dur)
213 {
214     m_duration = dur;
215     m_properties.insert("duration", QString::number((int) dur.frames(KdenliveSettings::project_fps())));
216 }
217
218 const GenTime &DocClipBase::duration() const
219 {
220     return m_duration;
221 }
222
223 const GenTime DocClipBase::maxDuration() const
224 {
225     if (m_clipType == COLOR || m_clipType == IMAGE || m_clipType == TEXT || (m_clipType == SLIDESHOW &&  m_properties.value("loop") == "1")) {
226         /*const GenTime dur(15000, KdenliveSettings::project_fps());
227         return dur;*/
228         return GenTime();
229     }
230     return m_duration;
231 }
232
233 bool DocClipBase::hasFileSize() const
234 {
235     return true;
236 }
237
238 qulonglong DocClipBase::fileSize() const
239 {
240     return m_properties.value("file_size").toULongLong();
241 }
242
243 // virtual
244 QDomElement DocClipBase::toXML(bool hideTemporaryProperties) const
245 {
246     QDomDocument doc;
247     QDomElement clip = doc.createElement("producer");
248
249     QMapIterator<QString, QString> i(m_properties);
250     while (i.hasNext()) {
251         i.next();
252         if (hideTemporaryProperties && i.key().startsWith('_')) continue;
253         if (!i.value().isEmpty()) clip.setAttribute(i.key(), i.value());
254     }
255     doc.appendChild(clip);
256     if (!m_cutZones.isEmpty()) {
257         QStringList cuts;
258         for (int i = 0; i < m_cutZones.size(); i++) {
259             CutZoneInfo info = m_cutZones.at(i);
260             cuts << QString::number(info.zone.x()) + "-" + QString::number(info.zone.y()) + "-" + info.description;
261         }
262         clip.setAttribute("cutzones", cuts.join(";"));
263     }
264     QString adata;
265     if (!m_analysisdata.isEmpty()) {
266         QMapIterator<QString, QString> i(m_analysisdata);
267         while (i.hasNext()) {
268             i.next();
269             //WARNING: a ? and # separator is not a good idea
270             adata.append(i.key() + "?" + i.value() + "#");
271         }
272     }
273     clip.setAttribute("analysisdata", adata);
274     //kDebug() << "/// CLIP XML: " << doc.toString();
275     return doc.documentElement();
276 }
277
278
279 void DocClipBase::setAudioThumbCreated(bool isDone)
280 {
281     m_audioThumbCreated = isDone;
282 }
283
284 void DocClipBase::updateAudioThumbnail(const audioByteArray& data)
285 {
286     //kDebug() << "CLIPBASE RECIEDVED AUDIO DATA*********************************************";
287     m_audioFrameCache = data;
288     m_audioThumbCreated = true;
289     emit gotAudioData();
290 }
291
292 QList < GenTime > DocClipBase::snapMarkers() const
293 {
294     QList < GenTime > markers;
295
296     for (int count = 0; count < m_snapMarkers.count(); ++count) {
297         markers.append(m_snapMarkers.at(count).time());
298     }
299
300     return markers;
301 }
302
303 QList < CommentedTime > DocClipBase::commentedSnapMarkers() const
304 {
305     return m_snapMarkers;
306 }
307
308
309 void DocClipBase::addSnapMarker(const CommentedTime marker)
310 {
311     QList < CommentedTime >::Iterator it = m_snapMarkers.begin();
312     for (it = m_snapMarkers.begin(); it != m_snapMarkers.end(); ++it) {
313         if ((*it).time() >= marker.time())
314             break;
315     }
316
317     if ((it != m_snapMarkers.end()) && ((*it).time() == marker.time())) {
318         (*it).setComment(marker.comment());
319         (*it).setMarkerType(marker.markerType());
320         //kError() << "trying to add Snap Marker that already exists, this will cause inconsistancies with undo/redo";
321     } else {
322         m_snapMarkers.insert(it, marker);
323     }
324 }
325
326 void DocClipBase::editSnapMarker(const GenTime & time, QString comment)
327 {
328     QList < CommentedTime >::Iterator it;
329     for (it = m_snapMarkers.begin(); it != m_snapMarkers.end(); ++it) {
330         if ((*it).time() == time)
331             break;
332     }
333     if (it != m_snapMarkers.end()) {
334         (*it).setComment(comment);
335     } else {
336         kError() << "trying to edit Snap Marker that does not already exists";
337     }
338 }
339
340 QString DocClipBase::deleteSnapMarker(const GenTime & time)
341 {
342     QString result = i18n("Marker");
343     QList < CommentedTime >::Iterator itt = m_snapMarkers.begin();
344
345     while (itt != m_snapMarkers.end()) {
346         if ((*itt).time() == time)
347             break;
348         ++itt;
349     }
350
351     if ((itt != m_snapMarkers.end()) && ((*itt).time() == time)) {
352         result = (*itt).comment();
353         m_snapMarkers.erase(itt);
354     }
355     return result;
356 }
357
358
359 GenTime DocClipBase::hasSnapMarkers(const GenTime & time)
360 {
361     QList < CommentedTime >::Iterator itt = m_snapMarkers.begin();
362
363     while (itt != m_snapMarkers.end()) {
364         if ((*itt).time() == time)
365             return time;
366         ++itt;
367     }
368
369     return GenTime(0.0);
370 }
371
372 GenTime DocClipBase::findPreviousSnapMarker(const GenTime & currTime)
373 {
374     int it;
375     for (it = 0; it < m_snapMarkers.count(); it++) {
376         if (m_snapMarkers.at(it).time() >= currTime)
377             break;
378     }
379     if (it == 0) return GenTime();
380     else if (it == m_snapMarkers.count() - 1 && m_snapMarkers.at(it).time() < currTime)
381         return m_snapMarkers.at(it).time();
382     else return m_snapMarkers.at(it - 1).time();
383 }
384
385 GenTime DocClipBase::findNextSnapMarker(const GenTime & currTime)
386 {
387     int it;
388     for (it = 0; it < m_snapMarkers.count(); it++) {
389         if (m_snapMarkers.at(it).time() > currTime)
390             break;
391     }
392     if (it < m_snapMarkers.count() && m_snapMarkers.at(it).time() > currTime) return m_snapMarkers.at(it).time();
393     return duration();
394 }
395
396 QString DocClipBase::markerComment(GenTime t) const
397 {
398     QList < CommentedTime >::ConstIterator itt = m_snapMarkers.begin();
399     while (itt != m_snapMarkers.end()) {
400         if ((*itt).time() == t)
401             return (*itt).comment();
402         ++itt;
403     }
404     return QString();
405 }
406
407 CommentedTime DocClipBase::markerAt(GenTime t) const
408 {
409     QList < CommentedTime >::ConstIterator itt = m_snapMarkers.begin();
410     while (itt != m_snapMarkers.end()) {
411         if ((*itt).time() == t)
412             return (*itt);
413         ++itt;
414     }
415     return CommentedTime();
416 }
417
418 void DocClipBase::clearThumbProducer()
419 {
420     if (m_thumbProd) m_thumbProd->clearProducer();
421 }
422
423 void DocClipBase::reloadThumbProducer()
424 {
425     if (m_thumbProd && !m_thumbProd->hasProducer())
426         m_thumbProd->setProducer(getProducer());
427 }
428
429 void DocClipBase::deleteProducers()
430 {
431     if (m_thumbProd) m_thumbProd->clearProducer();
432     
433     if (numReferences() > 0 && (!m_baseTrackProducers.isEmpty() || !m_videoTrackProducers.isEmpty() || !m_audioTrackProducers.isEmpty())) {
434         // Clip is used in timeline, delay producers deletion
435         for (int i = 0; i < m_baseTrackProducers.count(); i++) {
436             m_toDeleteProducers.append(m_baseTrackProducers.at(i));
437         }
438         for (int i = 0; i < m_videoTrackProducers.count(); i++) {
439             m_toDeleteProducers.append(m_videoTrackProducers.at(i));
440         }
441         for (int i = 0; i < m_audioTrackProducers.count(); i++) {
442             m_toDeleteProducers.append(m_audioTrackProducers.at(i));
443         }
444     }
445     else {
446         qDeleteAll(m_baseTrackProducers);
447         qDeleteAll(m_videoTrackProducers);
448         qDeleteAll(m_audioTrackProducers);
449         m_replaceMutex.unlock();
450     }
451     m_baseTrackProducers.clear();
452     m_videoTrackProducers.clear();
453     m_audioTrackProducers.clear();
454 }
455
456 void DocClipBase::cleanupProducers()
457 {
458     /*
459     int ct = 0;
460     kDebug()<<"----------------------------------------------------------------------------------";
461     for (int i = 0; i < m_toDeleteProducers.count(); i++) {
462         if (m_toDeleteProducers.at(i) != NULL) {
463             Mlt::Properties props(m_toDeleteProducers.at(i)->get_properties());
464             if (props.ref_count() > 2) {
465                 kDebug()<<"PRODUCER: "<<i<<", COUNTS: "<<props.ref_count();
466                 //exit(1);
467             }
468             ct++;
469         }
470     }*/
471
472     if (!isClean()) {
473       qDeleteAll(m_toDeleteProducers);
474       m_toDeleteProducers.clear();
475       m_replaceMutex.unlock();
476     }
477 }
478
479 bool DocClipBase::isClean() const
480 {
481     return m_toDeleteProducers.isEmpty();
482 }
483
484 void DocClipBase::setValid()
485 {
486     m_placeHolder = false;
487 }
488
489 void DocClipBase::setProducer(Mlt::Producer *producer, bool reset, bool readPropertiesFromProducer)
490 {
491     if (producer == NULL) return;
492     if (reset) {
493         QMutexLocker locker(&m_producerMutex);
494         m_replaceMutex.lock();
495         deleteProducers();
496     }
497     QString id = producer->get("id");
498     if (m_placeHolder || !producer->is_valid()) {
499         char *tmp = qstrdup(i18n("Missing clip").toUtf8().constData());
500         producer->set("markup", tmp);
501         producer->set("bgcolour", "0xff0000ff");
502         producer->set("pad", "10");
503         delete[] tmp;
504     }
505     else if (m_thumbProd && !m_thumbProd->hasProducer()) {
506         if (m_clipType != AUDIO) {
507             if (!id.endsWith("_audio"))
508                 m_thumbProd->setProducer(producer);
509         }
510         else m_thumbProd->setProducer(producer);
511         getAudioThumbs();
512     }
513     bool updated = false;
514     if (id.contains('_')) {
515         // this is a subtrack producer, insert it at correct place
516         id = id.section('_', 1);
517         if (id.endsWith("audio")) {
518             int pos = id.section('_', 0, 0).toInt();
519             if (pos >= m_audioTrackProducers.count()) {
520                 while (m_audioTrackProducers.count() - 1 < pos) {
521                     m_audioTrackProducers.append(NULL);
522                 }
523             }
524             if (m_audioTrackProducers.at(pos) == NULL) {
525                 m_audioTrackProducers[pos] = producer;
526                 updated = true;
527             }
528             else delete producer;
529             return;
530         } else if (id.endsWith("video")) {
531             int pos = 0;
532             // Keep compatibility with older projects where video only producers were not track specific
533             if (id.contains('_')) pos = id.section('_', 0, 0).toInt();
534             if (pos >= m_videoTrackProducers.count()) {
535                 while (m_videoTrackProducers.count() - 1 < pos) {
536                     m_videoTrackProducers.append(NULL);
537                 }
538             }
539             if (m_videoTrackProducers.at(pos) == NULL) {
540                 m_videoTrackProducers[pos] = producer;
541                 updated = true;
542             }
543             else delete producer;
544             return;
545         }
546         int pos = id.toInt();
547         if (pos >= m_baseTrackProducers.count()) {
548             while (m_baseTrackProducers.count() - 1 < pos) {
549                 m_baseTrackProducers.append(NULL);
550             }
551         }
552         if (m_baseTrackProducers.at(pos) == NULL) {
553             m_baseTrackProducers[pos] = producer;
554             updated = true;
555         }
556         else delete producer;
557     } else {
558         if (m_baseTrackProducers.isEmpty()) {
559             m_baseTrackProducers.append(producer);
560             updated = true;
561         }
562         else if (m_baseTrackProducers.at(0) == NULL) {
563             m_baseTrackProducers[0] = producer;
564             updated = true;
565         }
566         else delete producer;
567     }
568     if (updated && readPropertiesFromProducer && (m_clipType != COLOR && m_clipType != IMAGE && m_clipType != TEXT))
569         setDuration(GenTime(producer->get_length(), KdenliveSettings::project_fps()));
570 }
571
572 static double getPixelAspect(QMap<QString, QString>& props) {
573     int width = props.value("frame_size").section('x', 0, 0).toInt();
574     int height = props.value("frame_size").section('x', 1, 1).toInt();
575     int aspectNumerator = props.value("force_aspect_num").toInt();
576     int aspectDenominator = props.value("force_aspect_den").toInt();
577     if (aspectDenominator != 0 && width != 0)
578         return double(height) * aspectNumerator / aspectDenominator / width;    
579     else
580         return 1.0;
581 }
582
583 Mlt::Producer *DocClipBase::audioProducer(int track)
584 {
585     QMutexLocker locker(&m_producerMutex);
586     if (m_audioTrackProducers.count() <= track) {
587         while (m_audioTrackProducers.count() - 1 < track) {
588             m_audioTrackProducers.append(NULL);
589         }
590     }
591     if (m_audioTrackProducers.at(track) == NULL) {
592         int i;
593         for (i = 0; i < m_audioTrackProducers.count(); i++)
594             if (m_audioTrackProducers.at(i) != NULL) break;
595         Mlt::Producer *base;
596         if (i >= m_audioTrackProducers.count()) {
597             // Could not find a valid producer for that clip
598             locker.unlock();
599             base = getProducer();
600             if (base == NULL) {
601                 return NULL;
602             }
603             locker.relock();
604         }
605         else base = m_audioTrackProducers.at(i);
606         m_audioTrackProducers[track] = cloneProducer(base);
607         adjustProducerProperties(m_audioTrackProducers.at(track), QString(getId() + '_' + QString::number(track) + "_audio"), false, true);
608     }
609     return m_audioTrackProducers.at(track);
610 }
611
612
613 void DocClipBase::adjustProducerProperties(Mlt::Producer *prod, const QString &id, bool mute, bool blind)
614 {
615         if (m_properties.contains("force_aspect_num") && m_properties.contains("force_aspect_den") && m_properties.contains("frame_size"))
616             prod->set("force_aspect_ratio", getPixelAspect(m_properties));
617         if (m_properties.contains("force_fps")) prod->set("force_fps", m_properties.value("force_fps").toDouble());
618         if (m_properties.contains("force_progressive")) prod->set("force_progressive", m_properties.value("force_progressive").toInt());
619         if (m_properties.contains("force_tff")) prod->set("force_tff", m_properties.value("force_tff").toInt());
620         if (m_properties.contains("threads")) prod->set("threads", m_properties.value("threads").toInt());
621         if (mute) prod->set("audio_index", -1);
622         else if (m_properties.contains("audio_index")) prod->set("audio_index", m_properties.value("audio_index").toInt());
623         if (blind) prod->set("video_index", -1);
624         else if (m_properties.contains("video_index")) prod->set("video_index", m_properties.value("video_index").toInt());
625         prod->set("id", id.toUtf8().constData());
626         if (m_properties.contains("force_colorspace")) prod->set("force_colorspace", m_properties.value("force_colorspace").toInt());
627         if (m_properties.contains("full_luma")) prod->set("set.force_full_luma", m_properties.value("full_luma").toInt());
628         if (m_properties.contains("proxy_out")) {
629             // We have a proxy clip, make sure the proxy has same duration as original
630             prod->set("length", m_properties.value("duration").toInt());
631             prod->set("out", m_properties.value("proxy_out").toInt());
632         }
633
634 }
635
636 Mlt::Producer *DocClipBase::videoProducer(int track)
637 {
638     QMutexLocker locker(&m_producerMutex);
639     if (m_videoTrackProducers.count() <= track) {
640         while (m_videoTrackProducers.count() - 1 < track) {
641             m_videoTrackProducers.append(NULL);
642         }
643     }
644     if (m_videoTrackProducers.at(track) == NULL) {
645         int i;
646         for (i = 0; i < m_videoTrackProducers.count(); i++)
647             if (m_videoTrackProducers.at(i) != NULL) break;
648         Mlt::Producer *base;
649         if (i >= m_videoTrackProducers.count()) {
650             // Could not find a valid producer for that clip
651             locker.unlock();
652             base = getProducer();
653             if (base == NULL) {
654                 return NULL;
655             }
656             locker.relock();
657         }
658         else base = m_videoTrackProducers.at(i);
659         m_videoTrackProducers[track] = cloneProducer(base);
660         adjustProducerProperties(m_videoTrackProducers.at(track), QString(getId() + '_' + QString::number(track) + "_video"), true, false);
661     }
662     return m_videoTrackProducers.at(track);
663 }
664
665 Mlt::Producer *DocClipBase::getCloneProducer()
666 {
667     Mlt::Producer *source = NULL;
668     Mlt::Producer *prod = NULL;
669     if (m_clipType != AUDIO && m_clipType != AV && m_clipType != PLAYLIST) {
670         source = getProducer();
671         if (!source) return NULL;
672     }
673     if (m_clipType == COLOR) {
674         prod = new Mlt::Producer(*(source->profile()), 0, QString("colour:" + QString(source->get("resource"))).toUtf8().constData());
675     } else if (m_clipType == TEXT) {
676         prod = new Mlt::Producer(*(source->profile()), 0, QString("kdenlivetitle:" + QString(source->get("resource"))).toUtf8().constData());
677         if (prod && prod->is_valid() && m_properties.contains("xmldata"))
678             prod->set("xmldata", m_properties.value("xmldata").toUtf8().constData());
679     }
680     if (!prod) {
681         if (!source) {
682             QMutexLocker locker(&m_producerMutex);
683             for (int i = 0; i < m_baseTrackProducers.count(); i++) {
684                 if (m_baseTrackProducers.at(i) != NULL) {
685                     source = m_baseTrackProducers.at(i);
686                     break;
687                 }
688             }
689             if (!source) return NULL;
690         }
691         prod = cloneProducer(source);
692     }
693     if (prod) {
694         adjustProducerProperties(prod, getId() + "_", false, false);
695         if (!m_properties.contains("proxy_out")) {
696             // Adjust length in case...
697             if (m_properties.contains("duration")) prod->set("length", m_properties.value("duration").toInt());
698             if (m_properties.contains("out"))prod->set("out", m_properties.value("out").toInt());
699         }
700         if (m_clipType == AUDIO) {
701             prod->set("_audioclip", 1);
702         }
703     }
704     return prod;
705 }
706
707
708 Mlt::Producer *DocClipBase::getProducer(int track)
709 {
710     QMutexLocker locker(&m_producerMutex);
711     if (track == -1 || (m_clipType != AUDIO && m_clipType != AV && m_clipType != PLAYLIST)) {
712         if (m_baseTrackProducers.count() == 0) {
713             return NULL;
714         }
715         for (int i = 0; i < m_baseTrackProducers.count(); i++) {
716             if (m_baseTrackProducers.at(i) != NULL) {
717                 return m_baseTrackProducers.at(i);
718             }
719         }
720         return NULL;
721     }
722     if (track >= m_baseTrackProducers.count()) {
723         while (m_baseTrackProducers.count() - 1 < track) {
724             m_baseTrackProducers.append(NULL);
725         }
726     }
727     if (m_baseTrackProducers.at(track) == NULL) {
728         int i;
729         for (i = 0; i < m_baseTrackProducers.count(); i++)
730             if (m_baseTrackProducers.at(i) != NULL) break;
731
732         if (i >= m_baseTrackProducers.count()) {
733             // Could not find a valid producer for that clip, check in 
734             return NULL;
735         }
736         Mlt::Producer *prod = cloneProducer(m_baseTrackProducers.at(i));
737         adjustProducerProperties(prod, QString(getId() + '_' + QString::number(track)), false, false);
738         m_baseTrackProducers[track] = prod;
739     }
740     return m_baseTrackProducers.at(track);
741 }
742
743
744 Mlt::Producer *DocClipBase::cloneProducer(Mlt::Producer *source)
745 {
746     Mlt::Producer *result = NULL;
747     QString url = QString::fromUtf8(source->get("resource"));
748     if (url == "<playlist>" || url == "<tractor>" || url == "<producer>") {
749         // Xml producer sometimes loses the correct url
750         url = m_properties.value("resource");
751     }
752     if (m_clipType == SLIDESHOW || KIO::NetAccess::exists(KUrl(url), KIO::NetAccess::SourceSide, 0)) {
753         result = new Mlt::Producer(*(source->profile()), url.toUtf8().constData());
754     }
755     if (result == NULL || !result->is_valid()) {
756         // placeholder clip
757         QString txt = "+" + i18n("Missing clip") + ".txt";
758         char *tmp = qstrdup(txt.toUtf8().constData());
759         result = new Mlt::Producer(*source->profile(), tmp);
760         delete[] tmp;
761         if (result == NULL || !result->is_valid())
762             result = new Mlt::Producer(*(source->profile()), "colour:red");
763         else {
764             result->set("bgcolour", "0xff0000ff");
765             result->set("pad", "10");
766         }
767         return result;
768     }
769     /*Mlt::Properties src_props(source->get_properties());
770     Mlt::Properties props(result->get_properties());
771     props.inherit(src_props);*/
772     return result;
773 }
774
775 void DocClipBase::setProducerProperty(const char *name, int data)
776 {
777     QMutexLocker locker(&m_producerMutex);
778     for (int i = 0; i < m_baseTrackProducers.count(); i++) {
779         if (m_baseTrackProducers.at(i) != NULL)
780             m_baseTrackProducers[i]->set(name, data);
781     }
782 }
783
784 void DocClipBase::setProducerProperty(const char *name, double data)
785 {
786     QMutexLocker locker(&m_producerMutex);
787     for (int i = 0; i < m_baseTrackProducers.count(); i++) {
788         if (m_baseTrackProducers.at(i) != NULL)
789             m_baseTrackProducers[i]->set(name, data);
790     }
791 }
792
793 void DocClipBase::setProducerProperty(const char *name, const char *data)
794 {
795     QMutexLocker locker(&m_producerMutex);
796     for (int i = 0; i < m_baseTrackProducers.count(); i++) {
797         if (m_baseTrackProducers.at(i) != NULL)
798             m_baseTrackProducers[i]->set(name, data);
799     }
800 }
801
802 void DocClipBase::resetProducerProperty(const char *name)
803 {
804     QMutexLocker locker(&m_producerMutex);
805     for (int i = 0; i < m_baseTrackProducers.count(); i++) {
806         if (m_baseTrackProducers.at(i) != NULL)
807             m_baseTrackProducers[i]->set(name, (const char*) NULL);
808     }
809 }
810
811 const char *DocClipBase::producerProperty(const char *name) const
812 {
813     for (int i = 0; i < m_baseTrackProducers.count(); i++) {
814         if (m_baseTrackProducers.at(i) != NULL) {
815             return m_baseTrackProducers.at(i)->get(name);
816         }
817     }
818     return NULL;
819 }
820
821
822 void DocClipBase::slotRefreshProducer()
823 {
824     if (m_baseTrackProducers.count() == 0) return;
825     if (m_clipType == SLIDESHOW) {
826         setProducerProperty("ttl", getProperty("ttl").toInt());
827         //m_clipProducer->set("id", getProperty("id"));
828         if (!getProperty("animation").isEmpty()) {
829             Mlt::Service clipService(m_baseTrackProducers.at(0)->get_service());
830             int ct = 0;
831             Mlt::Filter *filter = clipService.filter(ct);
832             while (filter) {
833                 if (strcmp(filter->get("mlt_service"), "affine") == 0) {
834                     break;
835                 } else if (strcmp(filter->get("mlt_service"), "boxblur") == 0) {
836                     clipService.detach(*filter);
837                 } else ct++;
838                 filter = clipService.filter(ct);
839             }
840
841             if (!filter || strcmp(filter->get("mlt_service"), "affine")) {
842                 // filter does not exist, create it.
843                 Mlt::Filter *filter = new Mlt::Filter(*(m_baseTrackProducers.at(0)->profile()), "affine");
844                 if (filter && filter->is_valid()) {
845                     int cycle = getProperty("ttl").toInt();
846                     QString geometry = SlideshowClip::animationToGeometry(getProperty("animation"), cycle);
847                     if (!geometry.isEmpty()) {
848                         if (getProperty("animation").contains("low-pass")) {
849                             Mlt::Filter *blur = new Mlt::Filter(*(m_baseTrackProducers.at(0)->profile()), "boxblur");
850                             if (blur && blur->is_valid())
851                                 clipService.attach(*blur);
852                         }
853                         filter->set("transition.geometry", geometry.toUtf8().data());
854                         filter->set("transition.cycle", cycle);
855                         clipService.attach(*filter);
856                     }
857                 }
858             }
859         } else {
860             Mlt::Service clipService(m_baseTrackProducers.at(0)->get_service());
861             int ct = 0;
862             Mlt::Filter *filter = clipService.filter(0);
863             while (filter) {
864                 if (strcmp(filter->get("mlt_service"), "affine") == 0 || strcmp(filter->get("mlt_service"), "boxblur") == 0) {
865                     clipService.detach(*filter);
866                 } else ct++;
867                 filter = clipService.filter(ct);
868             }
869         }
870         if (getProperty("fade") == "1") {
871             // we want a fade filter effect
872             kDebug() << "////////////   FADE WANTED";
873             Mlt::Service clipService(m_baseTrackProducers.at(0)->get_service());
874             int ct = 0;
875             Mlt::Filter *filter = clipService.filter(ct);
876             while (filter) {
877                 if (strcmp(filter->get("mlt_service"), "luma") == 0) {
878                     break;
879                 }
880                 ct++;
881                 filter = clipService.filter(ct);
882             }
883
884             if (filter && strcmp(filter->get("mlt_service"), "luma") == 0) {
885                 filter->set("cycle", getProperty("ttl").toInt());
886                 filter->set("duration", getProperty("luma_duration").toInt());
887                 filter->set("luma.resource", getProperty("luma_file").toUtf8().data());
888                 if (!getProperty("softness").isEmpty()) {
889                     int soft = getProperty("softness").toInt();
890                     filter->set("luma.softness", (double) soft / 100.0);
891                 }
892             } else {
893                 // filter does not exist, create it...
894                 Mlt::Filter *filter = new Mlt::Filter(*(m_baseTrackProducers.at(0)->profile()), "luma");
895                 filter->set("cycle", getProperty("ttl").toInt());
896                 filter->set("duration", getProperty("luma_duration").toInt());
897                 filter->set("luma.resource", getProperty("luma_file").toUtf8().data());
898                 if (!getProperty("softness").isEmpty()) {
899                     int soft = getProperty("softness").toInt();
900                     filter->set("luma.softness", (double) soft / 100.0);
901                 }
902                 clipService.attach(*filter);
903             }
904         } else {
905             kDebug() << "////////////   FADE NOT WANTED!!!";
906             Mlt::Service clipService(m_baseTrackProducers.at(0)->get_service());
907             int ct = 0;
908             Mlt::Filter *filter = clipService.filter(0);
909             while (filter) {
910                 if (strcmp(filter->get("mlt_service"), "luma") == 0) {
911                     clipService.detach(*filter);
912                 } else ct++;
913                 filter = clipService.filter(ct);
914             }
915         }
916         if (getProperty("crop") == "1") {
917             // we want a center crop filter effect
918             Mlt::Service clipService(m_baseTrackProducers.at(0)->get_service());
919             int ct = 0;
920             Mlt::Filter *filter = clipService.filter(ct);
921             while (filter) {
922                 if (strcmp(filter->get("mlt_service"), "crop") == 0) {
923                     break;
924                 }
925                 ct++;
926                 filter = clipService.filter(ct);
927             }
928
929             if (!filter || strcmp(filter->get("mlt_service"), "crop")) {
930                 // filter does not exist, create it...
931                 Mlt::Filter *filter = new Mlt::Filter(*(m_baseTrackProducers.at(0)->profile()), "crop");
932                 filter->set("center", 1);
933                 clipService.attach(*filter);
934             }
935         } else {
936             Mlt::Service clipService(m_baseTrackProducers.at(0)->get_service());
937             int ct = 0;
938             Mlt::Filter *filter = clipService.filter(0);
939             while (filter) {
940                 if (strcmp(filter->get("mlt_service"), "crop") == 0) {
941                     clipService.detach(*filter);
942                 } else ct++;
943                 filter = clipService.filter(ct);
944             }
945         }
946     }
947 }
948
949 void DocClipBase::setProperties(QMap <QString, QString> properties)
950 {
951     // changing clip type is not allowed
952     properties.remove("type");
953     QMapIterator<QString, QString> i(properties);
954     bool refreshProducer = false;
955     QStringList keys;
956     keys << "luma_duration" << "luma_file" << "fade" << "ttl" << "softness" << "crop" << "animation";
957     QString oldProxy = m_properties.value("proxy");
958     while (i.hasNext()) {
959         i.next();
960         setProperty(i.key(), i.value());
961         if (m_clipType == SLIDESHOW && keys.contains(i.key())) refreshProducer = true;
962     }
963     if (properties.contains("proxy")) {
964         QString value = properties.value("proxy");
965         // If value is "-", that means user manually disabled proxy on this clip
966         if (value.isEmpty() || value == "-") {
967             // reset proxy
968             emit abortProxy(m_id, oldProxy);
969         }
970         else {
971             emit createProxy(m_id);
972         }
973     }
974     if (refreshProducer) slotRefreshProducer();
975 }
976
977 void DocClipBase::setMetadata(QMap <QString, QString> properties)
978 {
979     QMapIterator<QString, QString> i(properties);
980     while (i.hasNext()) {
981         i.next();
982         if (i.value().isEmpty() && m_metadata.contains(i.key())) {
983             m_metadata.remove(i.key());
984         } else {
985             m_metadata.insert(i.key(), i.value());
986         }
987     }
988 }
989
990 QMap <QString, QString> DocClipBase::metadata() const
991 {
992     return m_metadata;
993 }
994
995 void DocClipBase::clearProperty(const QString &key)
996 {
997     m_properties.remove(key);
998 }
999
1000 void DocClipBase::getFileHash(const QString &url)
1001 {
1002     if (m_clipType == SLIDESHOW) return;
1003     QFile file(url);
1004     if (file.open(QIODevice::ReadOnly)) { // write size and hash only if resource points to a file
1005         QByteArray fileData;
1006         QByteArray fileHash;
1007         //kDebug() << "SETTING HASH of" << value;
1008         m_properties.insert("file_size", QString::number(file.size()));
1009         /*
1010                * 1 MB = 1 second per 450 files (or faster)
1011                * 10 MB = 9 seconds per 450 files (or faster)
1012                */
1013         if (file.size() > 1000000*2) {
1014             fileData = file.read(1000000);
1015             if (file.seek(file.size() - 1000000))
1016                 fileData.append(file.readAll());
1017         } else
1018             fileData = file.readAll();
1019         file.close();
1020         fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
1021         m_properties.insert("file_hash", QString(fileHash.toHex()));
1022     }
1023 }
1024
1025 bool DocClipBase::checkHash() const
1026 {
1027     KUrl url = fileURL();
1028     if (!url.isEmpty() && getClipHash() != getHash(url.path())) return false;
1029     return true;
1030 }
1031
1032 QString DocClipBase::getClipHash() const
1033 {
1034     QString hash;
1035     if (m_clipType == SLIDESHOW) hash = QCryptographicHash::hash(m_properties.value("resource").toAscii().data(), QCryptographicHash::Md5).toHex();
1036     else if (m_clipType == COLOR) hash = QCryptographicHash::hash(m_properties.value("colour").toAscii().data(), QCryptographicHash::Md5).toHex();
1037     else if (m_clipType == TEXT) hash = QCryptographicHash::hash(QString("title" + getId() + m_properties.value("xmldata")).toUtf8().data(), QCryptographicHash::Md5).toHex();
1038     else {
1039         if (m_properties.contains("file_hash")) hash = m_properties.value("file_hash");
1040         if (hash.isEmpty()) hash = getHash(fileURL().path());
1041         
1042     }
1043     return hash;
1044 }
1045
1046 void DocClipBase::setPlaceHolder(bool place)
1047 {
1048     m_placeHolder = place;
1049 }
1050
1051 // static
1052 QString DocClipBase::getHash(const QString &path)
1053 {
1054     QFile file(path);
1055     if (file.open(QIODevice::ReadOnly)) { // write size and hash only if resource points to a file
1056         QByteArray fileData;
1057         QByteArray fileHash;
1058         /*
1059                * 1 MB = 1 second per 450 files (or faster)
1060                * 10 MB = 9 seconds per 450 files (or faster)
1061                */
1062         if (file.size() > 1000000*2) {
1063             fileData = file.read(1000000);
1064             if (file.seek(file.size() - 1000000))
1065                 fileData.append(file.readAll());
1066         } else
1067             fileData = file.readAll();
1068         file.close();
1069         return QCryptographicHash::hash(fileData, QCryptographicHash::Md5).toHex();
1070     }
1071     return QString();
1072 }
1073
1074 void DocClipBase::refreshThumbUrl()
1075 {
1076     if (m_thumbProd) m_thumbProd->updateThumbUrl(m_properties.value("file_hash"));
1077 }
1078
1079 void DocClipBase::setProperty(const QString &key, const QString &value)
1080 {
1081     m_properties.insert(key, value);
1082     if (key == "resource") {
1083         getFileHash(value);
1084         if (m_thumbProd) m_thumbProd->updateClipUrl(KUrl(value), m_properties.value("file_hash"));
1085     } else if (key == "out") setDuration(GenTime(value.toInt(), KdenliveSettings::project_fps()));
1086     //else if (key == "transparency") m_clipProducer->set("transparency", value.toInt());
1087     else if (key == "colour") {
1088         setProducerProperty("colour", value.toUtf8().data());
1089     } else if (key == "templatetext") {
1090         setProducerProperty("templatetext", value.toUtf8().data());
1091         setProducerProperty("force_reload", 1);
1092     } else if (key == "xmldata") {
1093         setProducerProperty("xmldata", value.toUtf8().data());
1094         setProducerProperty("force_reload", 1);
1095     } else if (key == "force_aspect_num") {
1096         if (value.isEmpty()) {
1097             m_properties.remove("force_aspect_num");
1098             resetProducerProperty("force_aspect_ratio");
1099         } else setProducerProperty("force_aspect_ratio", getPixelAspect(m_properties));
1100     } else if (key == "force_aspect_den") {
1101         if (value.isEmpty()) {
1102             m_properties.remove("force_aspect_den");
1103             resetProducerProperty("force_aspect_ratio");
1104         } else setProducerProperty("force_aspect_ratio", getPixelAspect(m_properties));
1105     } else if (key == "force_fps") {
1106         if (value.isEmpty()) {
1107             m_properties.remove("force_fps");
1108             resetProducerProperty("force_fps");
1109         } else setProducerProperty("force_fps", value.toDouble());
1110     } else if (key == "force_progressive") {
1111         if (value.isEmpty()) {
1112             m_properties.remove("force_progressive");
1113             resetProducerProperty("force_progressive");
1114         } else setProducerProperty("force_progressive", value.toInt());
1115     } else if (key == "force_tff") {
1116         if (value.isEmpty()) {
1117             m_properties.remove("force_tff");
1118             resetProducerProperty("force_tff");
1119         } else setProducerProperty("force_tff", value.toInt());
1120     } else if (key == "threads") {
1121         if (value.isEmpty()) {
1122             m_properties.remove("threads");
1123             setProducerProperty("threads", 1);
1124         } else setProducerProperty("threads", value.toInt());
1125     } else if (key == "video_index") {
1126         if (value.isEmpty()) {
1127             m_properties.remove("video_index");
1128             setProducerProperty("video_index", m_properties.value("default_video").toInt());
1129         } else setProducerProperty("video_index", value.toInt());
1130     } else if (key == "audio_index") {
1131         if (value.isEmpty()) {
1132             m_properties.remove("audio_index");
1133             setProducerProperty("audio_index", m_properties.value("default_audio").toInt());
1134         } else setProducerProperty("audio_index", value.toInt());
1135     } else if (key == "force_colorspace") {
1136         if (value.isEmpty()) {
1137             m_properties.remove("force_colorspace");
1138             resetProducerProperty("force_colorspace");
1139         } else setProducerProperty("force_colorspace", value.toInt());
1140     } else if (key == "full_luma") {
1141         if (value.isEmpty()) {
1142             m_properties.remove("full_luma");
1143             resetProducerProperty("set.force_full_luma");
1144         } else setProducerProperty("set.force_full_luma", value.toInt());
1145     }
1146 }
1147
1148 QMap <QString, QString> DocClipBase::properties() const
1149 {
1150     return m_properties;
1151 }
1152
1153 QMap <QString, QString> DocClipBase::currentProperties(QMap <QString, QString> props)
1154 {
1155     QMap <QString, QString> currentProps;
1156     QMap<QString, QString>::const_iterator i = props.constBegin();
1157     while (i != props.constEnd()) {
1158         currentProps.insert(i.key(), m_properties.value(i.key()));
1159         ++i;
1160     }
1161     return currentProps;
1162 }
1163
1164 bool DocClipBase::getAudioThumbs()
1165 {
1166     if (m_thumbProd == NULL || isPlaceHolder() || !KdenliveSettings::audiothumbnails()) return false;
1167     if (m_audioThumbCreated) {
1168         return false;
1169     }
1170     m_audioTimer.start();
1171     return true;
1172 }
1173
1174 bool DocClipBase::isPlaceHolder() const
1175 {
1176     return m_placeHolder;
1177 }
1178
1179 void DocClipBase::addCutZone(int in, int out, QString desc)
1180 {
1181     CutZoneInfo info;
1182     info.zone = QPoint(in, out);
1183     info.description = desc;
1184     for (int i = 0; i < m_cutZones.count(); i++)
1185         if (m_cutZones.at(i).zone == info.zone) {
1186             return;
1187         }
1188     m_cutZones.append(info);
1189 }
1190
1191 bool DocClipBase::hasCutZone(QPoint p) const
1192 {
1193     for (int i = 0; i < m_cutZones.count(); i++)
1194         if (m_cutZones.at(i).zone == p) return true;
1195     return false;
1196 }
1197
1198
1199 void DocClipBase::removeCutZone(int in, int out)
1200 {
1201     QPoint p(in, out);
1202     for (int i = 0; i < m_cutZones.count(); i++) {
1203         if (m_cutZones.at(i).zone == p) {
1204             m_cutZones.removeAt(i);
1205             i--;
1206         }
1207     }
1208 }
1209
1210 void DocClipBase::updateCutZone(int oldin, int oldout, int in, int out, QString desc)
1211 {
1212     QPoint old(oldin, oldout);
1213     for (int i = 0; i < m_cutZones.size(); ++i) {
1214         if (m_cutZones.at(i).zone == old) {
1215             CutZoneInfo info;
1216             info.zone = QPoint(in, out);
1217             info.description = desc;
1218             m_cutZones.replace(i, info);
1219             break;
1220         }
1221     }
1222 }
1223
1224 QList <CutZoneInfo> DocClipBase::cutZones() const
1225 {
1226     return m_cutZones;
1227 }
1228
1229 bool DocClipBase::hasVideoCodec(const QString &codec) const
1230 {
1231     Mlt::Producer *prod = NULL;
1232     if (m_baseTrackProducers.count() == 0) return false;
1233     for (int i = 0; i < m_baseTrackProducers.count(); i++) {
1234         if (m_baseTrackProducers.at(i) != NULL) {
1235             prod = m_baseTrackProducers.at(i);
1236             break;
1237         }
1238     }
1239
1240     if (!prod) return false;
1241     int default_video = prod->get_int("video_index");
1242     char property[200];
1243     snprintf(property, sizeof(property), "meta.media.%d.codec.name", default_video);
1244     return prod->get(property) == codec;
1245 }
1246
1247 bool DocClipBase::hasAudioCodec(const QString &codec) const
1248 {
1249     Mlt::Producer *prod = NULL;
1250     if (m_baseTrackProducers.count() == 0) return false;
1251     for (int i = 0; i < m_baseTrackProducers.count(); i++) {
1252         if (m_baseTrackProducers.at(i) != NULL) {
1253             prod = m_baseTrackProducers.at(i);
1254             break;
1255         }
1256     }
1257     if (!prod) return false;
1258     int default_video = prod->get_int("audio_index");
1259     char property[200];
1260     snprintf(property, sizeof(property), "meta.media.%d.codec.name", default_video);
1261     return prod->get(property) == codec;
1262 }
1263
1264
1265 void DocClipBase::slotExtractImage(QList <int> frames)
1266 {
1267     if (m_thumbProd == NULL) return;
1268     m_thumbProd->extractImage(frames);
1269 }
1270
1271 QImage DocClipBase::extractImage(int frame, int width, int height)
1272 {
1273     if (m_thumbProd == NULL) return QImage();
1274     QMutexLocker locker(&m_producerMutex);
1275     return m_thumbProd->extractImage(frame, width, height);
1276 }
1277
1278 void DocClipBase::setAnalysisData(const QString &name, const QString &data)
1279 {
1280     if (data.isEmpty()) m_analysisdata.remove(name);
1281     else m_analysisdata.insert(name, data);
1282 }
1283
1284 QMap <QString, QString> DocClipBase::analysisData() const
1285 {
1286     return m_analysisdata;
1287 }
1288