kthumb.cpp 12.5 KB
Newer Older
1 2 3 4 5 6
/***************************************************************************
                        krender.cpp  -  description
                           -------------------
  begin                : Fri Nov 22 2002
  copyright            : (C) 2002 by Jason Wood
  email                : jasonwood@blueyonder.co.uk
7
  copyright            : (C) 2005 Lcio Fl�io Corr�
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
  email                : lucio.correa@gmail.com
  copyright            : (C) Marco Gittler
  email                : g.marco@freenet.de

***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

23 24 25 26 27 28
#include "kthumb.h"
#include "clipmanager.h"
#include "renderer.h"
#include "kdenlivesettings.h"

#include <mlt++/Mlt.h>
29

30 31 32 33 34
#include <kio/netaccess.h>
#include <kdebug.h>
#include <klocale.h>
#include <kfileitem.h>
#include <kmessagebox.h>
35
#include <KStandardDirs>
36

37 38 39
#include <qxml.h>
#include <QImage>
#include <QApplication>
40
#include <QtConcurrentRun>
41
#include <QVarLengthArray>
Laurent Montel's avatar
Laurent Montel committed
42
#include <QPainter>
43

Laurent Montel's avatar
Laurent Montel committed
44
KThumb::KThumb(ClipManager *clipManager, const KUrl &url, const QString &id, const QString &hash, QObject * parent) :
45 46 47 48
    QObject(parent),
    m_url(url),
    m_thumbFile(),
    m_dar(1),
49
    m_ratio(1),
50 51
    m_producer(NULL),
    m_clipManager(clipManager),
52
    m_id(id)
53
{
54
    m_thumbFile = clipManager->projectFolder() + "/thumbs/" + hash + ".thumb";
55 56
}

57 58
KThumb::~KThumb()
{
59
    if (m_producer) m_clipManager->stopThumbs(m_id);
60
    m_producer = NULL;
61 62
    m_intraFramesQueue.clear();
    m_intra.waitForFinished();
63 64
}

65 66
void KThumb::setProducer(Mlt::Producer *producer)
{
67
    if (m_producer) m_clipManager->stopThumbs(m_id);
68 69
    m_intraFramesQueue.clear();
    m_intra.waitForFinished();
70
    QMutexLocker lock(&m_mutex);
71
    m_producer = producer;
Ray Lehtiniemi's avatar
Ray Lehtiniemi committed
72 73
    // FIXME: the profile() call leaks an object, but trying to free
    // it leads to a double-free in Profile::~Profile()
74
    if (producer) {
75 76 77
        Mlt::Profile *profile = producer->profile();
        m_dar = profile->dar();
        m_ratio = (double) profile->width() / profile->height();
78
    }
79 80
}

81 82
void KThumb::clearProducer()
{
83
    if (m_producer) setProducer(NULL);
84 85
}

86 87
bool KThumb::hasProducer() const
{
88 89 90
    return m_producer != NULL;
}

91 92
void KThumb::updateThumbUrl(const QString &hash)
{
93 94 95
    m_thumbFile = m_clipManager->projectFolder() + "/thumbs/" + hash + ".thumb";
}

96
void KThumb::updateClipUrl(const KUrl &url, const QString &hash)
97
{
98
    m_url = url;
99
    m_thumbFile = m_clipManager->projectFolder() + "/thumbs/" + hash + ".thumb";
100
}
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
101 102

//static
103
QPixmap KThumb::getImage(const KUrl& url, int width, int height)
104
{
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
105
    if (url.isEmpty()) return QPixmap();
106
    return getImage(url, 0, width, height);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
107 108
}

109
void KThumb::extractImage(const QList<int> &frames)
110
{
111
    if (!KdenliveSettings::videothumbnails() || m_producer == NULL) return;
112
    m_clipManager->slotRequestThumbs(m_id, frames);
113
}
114

115 116

void KThumb::getThumb(int frame)
117
{
118
    const int theight = Kdenlive::DefaultThumbHeight;
119 120
    const int swidth = (int)(theight * m_ratio + 0.5);
    const int dwidth = (int)(theight * m_dar + 0.5);
121 122
    QImage img = getProducerFrame(frame, swidth, dwidth, theight);
    emit thumbReady(frame, img);
123 124
}

125 126 127 128 129 130 131 132
void KThumb::getGenericThumb(int frame, int height, int type)
{
    const int swidth = (int)(height * m_ratio + 0.5);
    const int dwidth = (int)(height * m_dar + 0.5);
    QImage img = getProducerFrame(frame, swidth, dwidth, height);
    m_clipManager->projectTreeThumbReady(m_id, frame, img, type);
}

133
QImage KThumb::extractImage(int frame, int width, int height)
134
{
135
    if (m_producer == NULL) {
136
        QImage img(width, height, QImage::Format_ARGB32_Premultiplied);
137
        img.fill(QColor(Qt::black).rgb());
138
        return img;
139
    }
140
    return getProducerFrame(frame, (int) (height * m_ratio + 0.5), width, height);
141 142
}

143
//static
144
QPixmap KThumb::getImage(const KUrl& url, int frame, int width, int height)
145
{
146
    Mlt::Profile profile(KdenliveSettings::current_profile().toUtf8().constData());
147 148
    QPixmap pix(width, height);
    if (url.isEmpty()) return pix;
149
    Mlt::Producer *producer = new Mlt::Producer(profile, url.path().toUtf8().constData());
150 151
    double swidth = (double) profile.width() / profile.height();
    pix = QPixmap::fromImage(getFrame(producer, frame, (int) (height * swidth + 0.5), width, height));
152 153
    delete producer;
    return pix;
154 155
}

156 157 158 159 160 161 162 163 164 165 166 167 168

QImage KThumb::getProducerFrame(int framepos, int frameWidth, int displayWidth, int height)
{
    if (m_producer == NULL || !m_producer->is_valid()) {
        QImage p(displayWidth, height, QImage::Format_ARGB32_Premultiplied);
        p.fill(QColor(Qt::red).rgb());
        return p;
    }
    if (m_producer->is_blank()) {
        QImage p(displayWidth, height, QImage::Format_ARGB32_Premultiplied);
        p.fill(QColor(Qt::black).rgb());
        return p;
    }
169
    QMutexLocker lock(&m_mutex);
170 171
    m_producer->seek(framepos);
    Mlt::Frame *frame = m_producer->get_frame();
172 173 174
    /*frame->set("rescale.interp", "nearest");
    frame->set("deinterlace_method", "onefield");
    frame->set("top_field_first", -1 );*/
175 176 177 178 179
    QImage p = getFrame(frame, frameWidth, displayWidth, height);
    delete frame;
    return p;
}

180
//static
181
QImage KThumb::getFrame(Mlt::Producer *producer, int framepos, int frameWidth, int displayWidth, int height)
182
{
183
    if (producer == NULL || !producer->is_valid()) {
184 185
        QImage p(displayWidth, height, QImage::Format_ARGB32_Premultiplied);
        p.fill(QColor(Qt::red).rgb());
186
        return p;
187
    }
Marco Gittler's avatar
Marco Gittler committed
188
    if (producer->is_blank()) {
189 190
        QImage p(displayWidth, height, QImage::Format_ARGB32_Premultiplied);
        p.fill(QColor(Qt::black).rgb());
Marco Gittler's avatar
Marco Gittler committed
191 192 193
        return p;
    }

194 195
    producer->seek(framepos);
    Mlt::Frame *frame = producer->get_frame();
196 197 198
    frame->set("rescale.interp", "nearest");
    frame->set("deinterlace_method", "onefield");
    frame->set("top_field_first", -1 );
199
    const QImage p = getFrame(frame, frameWidth, displayWidth, height);
200 201 202 203 204 205 206 207 208 209
    delete frame;
    return p;
}


//static
QImage KThumb::getFrame(Mlt::Frame *frame, int frameWidth, int displayWidth, int height)
{
    QImage p(displayWidth, height, QImage::Format_ARGB32_Premultiplied);
    if (frame == NULL || !frame->is_valid()) {
210
        p.fill(QColor(Qt::red).rgb());
211
        return p;
212
    }
213
    
214
    int ow = frameWidth;
215
    int oh = height;
216
    mlt_image_format format = mlt_image_rgb24a;
217 218
    //frame->set("progressive", "1");
    if (ow % 2 == 1) ow++;
219
    QImage image(ow, oh, QImage::Format_ARGB32_Premultiplied);
220
    const uchar* imagedata = frame->get_image(format, ow, oh);
221 222 223 224
    if (imagedata == NULL) {
        p.fill(QColor(Qt::red).rgb());
        return p;
    }
225 226 227 228
    memcpy(image.bits(), imagedata, ow * oh * 4);//.byteCount());
    
    //const uchar* imagedata = frame->get_image(format, ow, oh);
    //QImage image(imagedata, ow, oh, QImage::Format_ARGB32_Premultiplied);
229
    if (!image.isNull()) {
230
        if (ow > (2 * displayWidth)) {
231
            // there was a scaling problem, do it manually
232
            image = image.scaled(displayWidth, height).rgbSwapped();
233 234 235
        } else {
            image = image.scaled(displayWidth, height, Qt::IgnoreAspectRatio).rgbSwapped();
        }
236 237
#if QT_VERSION >= 0x040800
	p.fill(QColor(100, 100, 100, 70));
238
        QPainter painter(&p);
239 240 241 242 243
#else
	p.fill(Qt::transparent);
	QPainter painter(&p);
	painter.fillRect(p.rect(), QColor(100, 100, 100, 70));
#endif
244 245
        painter.drawImage(p.rect(), image);
        painter.end();
246
    } else
247
        p.fill(QColor(Qt::red).rgb());
Marco Gittler's avatar
Marco Gittler committed
248
    return p;
249
}
250 251

//static
252
uint KThumb::imageVariance(const QImage &image )
253
{
Jean-Baptiste Mardelle's avatar
cleanup  
Jean-Baptiste Mardelle committed
254 255 256 257
    uint delta = 0;
    uint avg = 0;
    uint bytes = image.numBytes();
    uint STEPS = bytes/2;
258
    QVarLengthArray<uchar> pivot(STEPS);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
259
    const uchar *bits=image.bits();
260
    // First pass: get pivots and taking average
261
    for( uint i=0; i<STEPS ; ++i ){
Jean-Baptiste Mardelle's avatar
cleanup  
Jean-Baptiste Mardelle committed
262
        pivot[i] = bits[2 * i];
263
#if QT_VERSION >= 0x040700
Jean-Baptiste Mardelle's avatar
cleanup  
Jean-Baptiste Mardelle committed
264
        avg+=pivot.at(i);
265 266 267
#else
        avg+=pivot[i];
#endif
268
    }
269 270
    if (STEPS)
        avg=avg/STEPS;
271
    // Second Step: calculate delta (average?)
272
    for (uint i=0; i<STEPS; ++i)
273
    {
274
#if QT_VERSION >= 0x040700
Jean-Baptiste Mardelle's avatar
cleanup  
Jean-Baptiste Mardelle committed
275
        int curdelta=abs(int(avg - pivot.at(i)));
276 277 278
#else
        int curdelta=abs(int(avg - pivot[i]));
#endif
279 280
        delta+=curdelta;
    }
281 282 283 284
    if (STEPS)
        return delta/STEPS;
    else
        return 0;
285 286
}

287 288 289 290 291
/*
void KThumb::getImage(KUrl url, int frame, int width, int height)
{
    if (url.isEmpty()) return;
    QPixmap image(width, height);
292
    Mlt::Producer m_producer(url.path().toUtf8().constData());
293 294 295
    image.fill(Qt::black);

    if (m_producer.is_blank()) {
296 297
 emit thumbReady(frame, image);
 return;
298 299 300 301 302 303 304 305 306 307
    }
    Mlt::Filter m_convert("avcolour_space");
    m_convert.set("forced", mlt_image_rgb24a);
    m_producer.attach(m_convert);
    m_producer.seek(frame);
    Mlt::Frame * m_frame = m_producer.get_frame();
    mlt_image_format format = mlt_image_rgb24a;
    width = width - 2;
    height = height - 2;
    if (m_frame && m_frame->is_valid()) {
308 309 310
     uint8_t *thumb = m_frame->get_image(format, width, height);
     QImage tmpimage(thumb, width, height, 32, NULL, 0, QImage::IgnoreEndian);
     if (!tmpimage.isNull()) bitBlt(&image, 1, 1, &tmpimage, 0, 0, width + 2, height + 2);
311 312 313 314 315 316 317 318 319
    }
    if (m_frame) delete m_frame;
    emit thumbReady(frame, image);
}

void KThumb::getThumbs(KUrl url, int startframe, int endframe, int width, int height)
{
    if (url.isEmpty()) return;
    QPixmap image(width, height);
320
    Mlt::Producer m_producer(url.path().toUtf8().constData());
321
    image.fill(QColor(Qt::black).rgb());
322 323

    if (m_producer.is_blank()) {
324 325 326
 emit thumbReady(startframe, image);
 emit thumbReady(endframe, image);
 return;
327 328 329 330 331 332 333 334 335 336 337
    }
    Mlt::Filter m_convert("avcolour_space");
    m_convert.set("forced", mlt_image_rgb24a);
    m_producer.attach(m_convert);
    m_producer.seek(startframe);
    Mlt::Frame * m_frame = m_producer.get_frame();
    mlt_image_format format = mlt_image_rgb24a;
    width = width - 2;
    height = height - 2;

    if (m_frame && m_frame->is_valid()) {
338 339 340
     uint8_t *thumb = m_frame->get_image(format, width, height);
     QImage tmpimage(thumb, width, height, 32, NULL, 0, QImage::IgnoreEndian);
     if (!tmpimage.isNull()) bitBlt(&image, 1, 1, &tmpimage, 0, 0, width - 2, height - 2);
341 342 343 344 345 346 347 348 349
    }
    if (m_frame) delete m_frame;
    emit thumbReady(startframe, image);

    image.fill(Qt::black);
    m_producer.seek(endframe);
    m_frame = m_producer.get_frame();

    if (m_frame && m_frame->is_valid()) {
350 351 352
     uint8_t *thumb = m_frame->get_image(format, width, height);
     QImage tmpimage(thumb, width, height, 32, NULL, 0, QImage::IgnoreEndian);
     if (!tmpimage.isNull()) bitBlt(&image, 1, 1, &tmpimage, 0, 0, width - 2, height - 2);
353 354 355 356
    }
    if (m_frame) delete m_frame;
    emit thumbReady(endframe, image);
}
357 358
*/

359 360
void KThumb::slotCreateAudioThumbs()
{
361
    m_clipManager->askForAudioThumb(m_id);
362
}
363

364
#if KDE_IS_VERSION(4,5,0)
365
void KThumb::queryIntraThumbs(const QList <int> &missingFrames)
366
{
367
    foreach (int i, missingFrames) {
368 369 370
        if (!m_intraFramesQueue.contains(i)) m_intraFramesQueue.append(i);
    }
    qSort(m_intraFramesQueue);
371 372 373
    if (!m_intra.isRunning()) {
        m_intra = QtConcurrent::run(this, &KThumb::slotGetIntraThumbs);
    }
374 375 376 377
}

void KThumb::slotGetIntraThumbs()
{
378 379 380
    const int theight = KdenliveSettings::trackheight();
    const int frameWidth = (int)(theight * m_ratio + 0.5);
    const int displayWidth = (int)(theight * m_dar + 0.5);
381
    QString path = m_url.path() + '_';
382
    bool addedThumbs = false;
383 384 385 386

    while (!m_intraFramesQueue.isEmpty()) {
        int pos = m_intraFramesQueue.takeFirst();
        if (!m_clipManager->pixmapCache->contains(path + QString::number(pos))) {
387
            if (m_clipManager->pixmapCache->insertImage(path + QString::number(pos), getProducerFrame(pos, frameWidth, displayWidth, theight))) {
388 389 390
                addedThumbs = true;
            }
            else kDebug()<<"// INSERT FAILD FOR: "<<pos;
391 392 393
        }
        m_intraFramesQueue.removeAll(pos);
    }
394
    if (addedThumbs) emit thumbsCached();
395 396
}

397
QImage KThumb::findCachedThumb(const QString &path)
398 399 400 401 402 403
{
    QImage img;
    m_clipManager->pixmapCache->findImage(path, &img);
    return img;
}
#endif
404

405
#include "kthumb.moc"
406