]> git.sesse.net Git - kdenlive/blob - src/kthumb.cpp
Fix some more threading crashes, almost there :)
[kdenlive] / src / kthumb.cpp
1 /***************************************************************************
2                         krender.cpp  -  description
3                            -------------------
4   begin                : Fri Nov 22 2002
5   copyright            : (C) 2002 by Jason Wood
6   email                : jasonwood@blueyonder.co.uk
7   copyright            : (C) 2005 Lcio Fl�io Corr�
8   email                : lucio.correa@gmail.com
9   copyright            : (C) Marco Gittler
10   email                : g.marco@freenet.de
11
12 ***************************************************************************/
13
14 /***************************************************************************
15  *                                                                         *
16  *   This program is free software; you can redistribute it and/or modify  *
17  *   it under the terms of the GNU General Public License as published by  *
18  *   the Free Software Foundation; either version 2 of the License, or     *
19  *   (at your option) any later version.                                   *
20  *                                                                         *
21  ***************************************************************************/
22
23 #include "kthumb.h"
24 #include "clipmanager.h"
25 #include "renderer.h"
26 #include "kdenlivesettings.h"
27
28 #include <mlt++/Mlt.h>
29
30 #include <kio/netaccess.h>
31 #include <kdebug.h>
32 #include <klocale.h>
33 #include <kfileitem.h>
34 #include <kmessagebox.h>
35 #include <KStandardDirs>
36
37 #include <qxml.h>
38 #include <QImage>
39 #include <QApplication>
40 #include <QtConcurrentRun>
41 #include <QVarLengthArray>
42
43 KThumb::KThumb(ClipManager *clipManager, KUrl url, const QString &id, const QString &hash, QObject * parent, const char */*name*/) :
44     QObject(parent),
45     m_audioThumbProducer(),
46     m_url(url),
47     m_thumbFile(),
48     m_dar(1),
49     m_ratio(1),
50     m_producer(NULL),
51     m_clipManager(clipManager),
52     m_id(id),
53     m_stopAudioThumbs(false)
54 {
55     m_thumbFile = clipManager->projectFolder() + "/thumbs/" + hash + ".thumb";
56 }
57
58 KThumb::~KThumb()
59 {
60     m_listMutex.lock();
61     m_requestedThumbs.clear();
62     m_listMutex.unlock();
63     m_intraFramesQueue.clear();
64     if (m_audioThumbProducer.isRunning()) {
65         m_stopAudioThumbs = true;
66         m_audioThumbProducer.waitForFinished();
67         slotAudioThumbOver();
68     }
69     m_future.waitForFinished();
70     m_intra.waitForFinished();
71 }
72
73 void KThumb::setProducer(Mlt::Producer *producer)
74 {
75     m_listMutex.lock();
76     m_requestedThumbs.clear();
77     m_listMutex.unlock();
78     m_intraFramesQueue.clear();
79     m_future.waitForFinished();
80     m_intra.waitForFinished();
81     m_mutex.lock();
82     m_producer = producer;
83     // FIXME: the profile() call leaks an object, but trying to free
84     // it leads to a double-free in Profile::~Profile()
85     if (producer) {
86         m_dar = producer->profile()->dar();
87         m_ratio = (double) producer->profile()->width() / producer->profile()->height();
88     }
89     m_mutex.unlock();
90 }
91
92 void KThumb::clearProducer()
93 {
94     setProducer(NULL);
95 }
96
97 bool KThumb::hasProducer() const
98 {
99     return m_producer != NULL;
100 }
101
102 void KThumb::updateThumbUrl(const QString &hash)
103 {
104     m_thumbFile = m_clipManager->projectFolder() + "/thumbs/" + hash + ".thumb";
105 }
106
107 void KThumb::updateClipUrl(KUrl url, const QString &hash)
108 {
109     m_url = url;
110     m_thumbFile = m_clipManager->projectFolder() + "/thumbs/" + hash + ".thumb";
111 }
112
113 //static
114 QPixmap KThumb::getImage(KUrl url, int width, int height)
115 {
116     if (url.isEmpty()) return QPixmap();
117     return getImage(url, 0, width, height);
118 }
119
120 void KThumb::extractImage(int frame, int frame2)
121 {
122     if (!KdenliveSettings::videothumbnails() || m_producer == NULL) return;
123     m_listMutex.lock();
124     if (frame != -1 && !m_requestedThumbs.contains(frame)) m_requestedThumbs.append(frame);
125     if (frame2 != -1 && !m_requestedThumbs.contains(frame2)) m_requestedThumbs.append(frame2);
126     qSort(m_requestedThumbs);
127     m_listMutex.unlock();
128     if (!m_future.isRunning()) {
129         m_future = QtConcurrent::run(this, &KThumb::doGetThumbs);
130     }
131 }
132
133 void KThumb::doGetThumbs()
134 {
135     const int theight = KdenliveSettings::trackheight();
136     const int swidth = (int)(theight * m_ratio + 0.5);
137     const int dwidth = (int)(theight * m_dar + 0.5);
138
139     while (!m_requestedThumbs.isEmpty()) {
140         m_listMutex.lock();
141         int frame = m_requestedThumbs.takeFirst();
142         m_listMutex.unlock();
143         if (frame != -1) {
144             QImage img = getProducerFrame(frame, swidth, dwidth, theight);
145             emit thumbReady(frame, img);
146         }
147     }
148 }
149
150 QPixmap KThumb::extractImage(int frame, int width, int height)
151 {
152     if (m_producer == NULL) {
153         QPixmap p(width, height);
154         p.fill(Qt::black);
155         return p;
156     }
157     QImage img = getProducerFrame(frame, (int) (height * m_ratio + 0.5), width, height);
158     return QPixmap::fromImage(img);
159 }
160
161 //static
162 QPixmap KThumb::getImage(KUrl url, int frame, int width, int height)
163 {
164     Mlt::Profile profile(KdenliveSettings::current_profile().toUtf8().constData());
165     QPixmap pix(width, height);
166     if (url.isEmpty()) return pix;
167
168     //"<mlt><playlist><producer resource=\"" + url.path() + "\" /></playlist></mlt>");
169     //Mlt::Producer producer(profile, "xml-string", tmp);
170     Mlt::Producer *producer = new Mlt::Producer(profile, url.path().toUtf8().constData());
171     double swidth = (double) profile.width() / profile.height();
172     pix = QPixmap::fromImage(getFrame(producer, frame, (int) (height * swidth + 0.5), width, height));
173     delete producer;
174     return pix;
175 }
176
177
178 QImage KThumb::getProducerFrame(int framepos, int frameWidth, int displayWidth, int height)
179 {
180     if (m_producer == NULL || !m_producer->is_valid()) {
181         QImage p(displayWidth, height, QImage::Format_ARGB32_Premultiplied);
182         p.fill(QColor(Qt::red).rgb());
183         return p;
184     }
185     if (m_producer->is_blank()) {
186         QImage p(displayWidth, height, QImage::Format_ARGB32_Premultiplied);
187         p.fill(QColor(Qt::black).rgb());
188         return p;
189     }
190     m_mutex.lock();
191     m_producer->seek(framepos);
192     Mlt::Frame *frame = m_producer->get_frame();
193     QImage p = getFrame(frame, frameWidth, displayWidth, height);
194     delete frame;
195     m_mutex.unlock();
196     return p;
197 }
198
199 //static
200 QImage KThumb::getFrame(Mlt::Producer *producer, int framepos, int frameWidth, int displayWidth, int height)
201 {
202     if (producer == NULL || !producer->is_valid()) {
203         QImage p(displayWidth, height, QImage::Format_ARGB32_Premultiplied);
204         p.fill(QColor(Qt::red).rgb());
205         return p;
206     }
207     if (producer->is_blank()) {
208         QImage p(displayWidth, height, QImage::Format_ARGB32_Premultiplied);
209         p.fill(QColor(Qt::black).rgb());
210         return p;
211     }
212
213     producer->seek(framepos);
214     Mlt::Frame *frame = producer->get_frame();
215     QImage p = getFrame(frame, frameWidth, displayWidth, height);
216     delete frame;
217     return p;
218 }
219
220
221 //static
222 QImage KThumb::getFrame(Mlt::Frame *frame, int frameWidth, int displayWidth, int height)
223 {
224     QImage p(displayWidth, height, QImage::Format_ARGB32_Premultiplied);
225     if (frame == NULL || !frame->is_valid()) {
226         p.fill(QColor(Qt::red).rgb());
227         return p;
228     }
229
230     int ow = frameWidth;
231     int oh = height;
232     mlt_image_format format = mlt_image_rgb24a;
233     
234     const uchar* imagedata = frame->get_image(format, ow, oh);
235     QImage image(imagedata, ow, oh, QImage::Format_ARGB32_Premultiplied);
236     
237     if (!image.isNull()) {
238         if (ow > (2 * displayWidth)) {
239             // there was a scaling problem, do it manually
240             image = image.scaled(displayWidth, height).rgbSwapped();
241         } else {
242             image = image.scaled(displayWidth, height, Qt::IgnoreAspectRatio).rgbSwapped();
243         }
244         p.fill(QColor(Qt::black).rgb());
245         QPainter painter(&p);
246         painter.drawImage(p.rect(), image);
247         painter.end();
248     } else
249         p.fill(QColor(Qt::red).rgb());
250     return p;
251 }
252
253 //static
254 uint KThumb::imageVariance(QImage image )
255 {
256     uint delta = 0;
257     uint avg = 0;
258     uint bytes = image.numBytes();
259     uint STEPS = bytes/2;
260     QVarLengthArray<uchar> pivot(STEPS);
261     const uchar *bits=image.bits();
262     // First pass: get pivots and taking average
263     for( uint i=0; i<STEPS ; i++ ){
264         pivot[i] = bits[2 * i];
265 #if QT_VERSION >= 0x040700
266         avg+=pivot.at(i);
267 #else
268         avg+=pivot[i];
269 #endif
270     }
271     avg=avg/STEPS;
272     // Second Step: calculate delta (average?)
273     for (uint i=0; i<STEPS; i++)
274     {
275 #if QT_VERSION >= 0x040700
276         int curdelta=abs(int(avg - pivot.at(i)));
277 #else
278         int curdelta=abs(int(avg - pivot[i]));
279 #endif
280         delta+=curdelta;
281     }
282     return delta/STEPS;
283 }
284
285 /*
286 void KThumb::getImage(KUrl url, int frame, int width, int height)
287 {
288     if (url.isEmpty()) return;
289     QPixmap image(width, height);
290     Mlt::Producer m_producer(url.path().toUtf8().constData());
291     image.fill(Qt::black);
292
293     if (m_producer.is_blank()) {
294  emit thumbReady(frame, image);
295  return;
296     }
297     Mlt::Filter m_convert("avcolour_space");
298     m_convert.set("forced", mlt_image_rgb24a);
299     m_producer.attach(m_convert);
300     m_producer.seek(frame);
301     Mlt::Frame * m_frame = m_producer.get_frame();
302     mlt_image_format format = mlt_image_rgb24a;
303     width = width - 2;
304     height = height - 2;
305     if (m_frame && m_frame->is_valid()) {
306      uint8_t *thumb = m_frame->get_image(format, width, height);
307      QImage tmpimage(thumb, width, height, 32, NULL, 0, QImage::IgnoreEndian);
308      if (!tmpimage.isNull()) bitBlt(&image, 1, 1, &tmpimage, 0, 0, width + 2, height + 2);
309     }
310     if (m_frame) delete m_frame;
311     emit thumbReady(frame, image);
312 }
313
314 void KThumb::getThumbs(KUrl url, int startframe, int endframe, int width, int height)
315 {
316     if (url.isEmpty()) return;
317     QPixmap image(width, height);
318     Mlt::Producer m_producer(url.path().toUtf8().constData());
319     image.fill(Qt::black);
320
321     if (m_producer.is_blank()) {
322  emit thumbReady(startframe, image);
323  emit thumbReady(endframe, image);
324  return;
325     }
326     Mlt::Filter m_convert("avcolour_space");
327     m_convert.set("forced", mlt_image_rgb24a);
328     m_producer.attach(m_convert);
329     m_producer.seek(startframe);
330     Mlt::Frame * m_frame = m_producer.get_frame();
331     mlt_image_format format = mlt_image_rgb24a;
332     width = width - 2;
333     height = height - 2;
334
335     if (m_frame && m_frame->is_valid()) {
336      uint8_t *thumb = m_frame->get_image(format, width, height);
337      QImage tmpimage(thumb, width, height, 32, NULL, 0, QImage::IgnoreEndian);
338      if (!tmpimage.isNull()) bitBlt(&image, 1, 1, &tmpimage, 0, 0, width - 2, height - 2);
339     }
340     if (m_frame) delete m_frame;
341     emit thumbReady(startframe, image);
342
343     image.fill(Qt::black);
344     m_producer.seek(endframe);
345     m_frame = m_producer.get_frame();
346
347     if (m_frame && m_frame->is_valid()) {
348      uint8_t *thumb = m_frame->get_image(format, width, height);
349      QImage tmpimage(thumb, width, height, 32, NULL, 0, QImage::IgnoreEndian);
350      if (!tmpimage.isNull()) bitBlt(&image, 1, 1, &tmpimage, 0, 0, width - 2, height - 2);
351     }
352     if (m_frame) delete m_frame;
353     emit thumbReady(endframe, image);
354 }
355 */
356 void KThumb::stopAudioThumbs()
357 {
358     if (m_audioThumbProducer.isRunning()) {
359         m_stopAudioThumbs = true;
360         m_audioThumbProducer.waitForFinished();
361         slotAudioThumbOver();
362     }
363 }
364
365 void KThumb::removeAudioThumb()
366 {
367     if (m_thumbFile.isEmpty()) return;
368     stopAudioThumbs();
369     QFile f(m_thumbFile);
370     f.remove();
371 }
372
373 void KThumb::getAudioThumbs(int channel, double frame, double frameLength, int arrayWidth)
374 {
375     if (channel == 0) {
376         slotAudioThumbOver();
377         return;
378     }
379     if (m_audioThumbProducer.isRunning()) {
380         return;
381     }
382
383     audioByteArray storeIn;
384     //FIXME: Hardcoded!!!
385     m_frequency = 48000;
386     m_channels = channel;
387
388     QFile f(m_thumbFile);
389     if (f.open(QIODevice::ReadOnly)) {
390         const QByteArray channelarray = f.readAll();
391         f.close();
392         if (channelarray.size() != arrayWidth*(frame + frameLength)*m_channels) {
393             kDebug() << "--- BROKEN THUMB FOR: " << m_url.fileName() << " ---------------------- " << endl;
394             f.remove();
395             slotAudioThumbOver();
396             return;
397         }
398
399         kDebug() << "reading audio thumbs from file";
400
401         int h1 = arrayWidth * m_channels;
402         int h2 = (int) frame * h1;
403         int h3;
404         for (int z = (int) frame; z < (int)(frame + frameLength); z++) {
405             h3 = 0;
406             for (int c = 0; c < m_channels; c++) {
407                 QByteArray m_array(arrayWidth, '\x00');
408                 for (int i = 0; i < arrayWidth; i++) {
409                     m_array[i] = channelarray.at(h2 + h3 + i);
410                 }
411                 h3 += arrayWidth;
412                 storeIn[z][c] = m_array;
413             }
414             h2 += h1;
415         }
416         emit audioThumbReady(storeIn);
417         slotAudioThumbOver();
418     } else {
419         if (m_audioThumbProducer.isRunning()) return;
420         m_audioThumbFile.setFileName(m_thumbFile);
421         m_frame = frame;
422         m_frameLength = frameLength;
423         m_arrayWidth = arrayWidth;
424         m_audioThumbProducer = QtConcurrent::run(this, &KThumb::slotCreateAudioThumbs);
425         /*m_audioThumbProducer.init(m_url, m_thumbFile, frame, frameLength, m_frequency, m_channels, arrayWidth);
426         m_audioThumbProducer.start(QThread::LowestPriority);*/
427         // kDebug() << "STARTING GENERATE THMB FOR: " <<m_id<<", URL: "<< m_url << " ................................";
428     }
429 }
430
431 void KThumb::slotCreateAudioThumbs()
432 {
433     Mlt::Profile prof((char*) KdenliveSettings::current_profile().toUtf8().data());
434     Mlt::Producer producer(prof, m_url.path().toUtf8().data());
435     if (!producer.is_valid()) {
436         kDebug() << "++++++++  INVALID CLIP: " << m_url.path();
437         return;
438     }
439     if (!m_audioThumbFile.open(QIODevice::WriteOnly)) {
440         kDebug() << "++++++++  ERROR WRITING TO FILE: " << m_audioThumbFile.fileName();
441         kDebug() << "++++++++  DISABLING AUDIO THUMBS";
442         KdenliveSettings::setAudiothumbnails(false);
443         return;
444     }
445
446     if (KdenliveSettings::normaliseaudiothumbs()) {
447         Mlt::Filter m_convert(prof, "volume");
448         m_convert.set("gain", "normalise");
449         producer.attach(m_convert);
450     }
451
452     int last_val = 0;
453     int val = 0;
454     //kDebug() << "for " << m_frame << " " << m_frameLength << " " << m_producer.is_valid();
455     for (int z = (int) m_frame; z < (int)(m_frame + m_frameLength) && producer.is_valid(); z++) {
456         if (m_stopAudioThumbs) break;
457         val = (int)((z - m_frame) / (m_frame + m_frameLength) * 100.0);
458         if (last_val != val && val > 1) {
459             m_clipManager->setThumbsProgress(i18n("Creating thumbnail for %1", m_url.fileName()), val);
460             last_val = val;
461         }
462         producer.seek(z);
463         Mlt::Frame *mlt_frame = producer.get_frame();
464         if (mlt_frame && mlt_frame->is_valid()) {
465             double m_framesPerSecond = mlt_producer_get_fps(producer.get_producer());
466             int m_samples = mlt_sample_calculator(m_framesPerSecond, m_frequency, mlt_frame_get_position(mlt_frame->get_frame()));
467             mlt_audio_format m_audioFormat = mlt_audio_pcm;
468             qint16* m_pcm = static_cast<qint16*>(mlt_frame->get_audio(m_audioFormat, m_frequency, m_channels, m_samples));
469
470             for (int c = 0; c < m_channels; c++) {
471                 QByteArray m_array;
472                 m_array.resize(m_arrayWidth);
473                 for (int i = 0; i < m_array.size(); i++) {
474                     m_array[i] = ((*(m_pcm + c + i * m_samples / m_array.size())) >> 9) + 127 / 2 ;
475                 }
476                 m_audioThumbFile.write(m_array);
477
478             }
479         } else {
480             m_audioThumbFile.write(QByteArray(m_arrayWidth, '\x00'));
481         }
482         delete mlt_frame;
483     }
484     m_audioThumbFile.close();
485     if (m_stopAudioThumbs) {
486         m_audioThumbFile.remove();
487     } else {
488         slotAudioThumbOver();
489     }
490 }
491
492 void KThumb::slotAudioThumbOver()
493 {
494     m_clipManager->setThumbsProgress(i18n("Creating thumbnail for %1", m_url.fileName()), -1);
495     m_clipManager->endAudioThumbsGeneration(m_id);
496 }
497
498 void KThumb::askForAudioThumbs(const QString &id)
499 {
500     m_clipManager->askForAudioThumb(id);
501 }
502
503 #if KDE_IS_VERSION(4,5,0)
504 void KThumb::queryIntraThumbs(QList <int> missingFrames)
505 {
506     foreach (int i, missingFrames) {
507         if (!m_intraFramesQueue.contains(i)) m_intraFramesQueue.append(i);
508     }
509     qSort(m_intraFramesQueue);
510     if (!m_intra.isRunning()) {
511         m_intra = QtConcurrent::run(this, &KThumb::slotGetIntraThumbs);
512     }
513 }
514
515 void KThumb::slotGetIntraThumbs()
516 {
517     const int theight = KdenliveSettings::trackheight();
518     const int frameWidth = (int)(theight * m_ratio + 0.5);
519     const int displayWidth = (int)(theight * m_dar + 0.5);
520     QString path = m_url.path() + "_";
521     bool addedThumbs = false;
522
523     while (!m_intraFramesQueue.isEmpty()) {
524         int pos = m_intraFramesQueue.takeFirst();
525         if (!m_clipManager->pixmapCache->contains(path + QString::number(pos))) {
526             if (m_clipManager->pixmapCache->insertImage(path + QString::number(pos), getProducerFrame(pos, frameWidth, displayWidth, theight))) {
527                 addedThumbs = true;
528             }
529             else kDebug()<<"// INSERT FAILD FOR: "<<pos;
530         }
531         m_intraFramesQueue.removeAll(pos);
532     }
533     if (addedThumbs) emit thumbsCached();
534 }
535
536 QImage KThumb::findCachedThumb(const QString path)
537 {
538     QImage img;
539     m_clipManager->pixmapCache->findImage(path, &img);
540     return img;
541 }
542 #endif
543
544 #include "kthumb.moc"
545