projectclip.cpp 46.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
/*
Copyright (C) 2012  Till Theato <root@ttill.de>
Copyright (C) 2014  Jean-Baptiste Mardelle <jb@kdenlive.org>
This file is part of Kdenlive. See www.kdenlive.org.

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) version 3 or any later version
accepted by the membership of KDE e.V. (or its successor approved
by the membership of KDE e.V.), which shall act as a proxy 
defined in Section 14 of version 3 of the license.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "projectclip.h"
#include "projectfolder.h"
25
#include "projectsubclip.h"
26
#include "bin.h"
27
#include "timecode.h"
28 29
#include "doc/kthumb.h"
#include "kdenlivesettings.h"
30
#include "timeline/clip.h"
31
#include "project/projectcommands.h"
32
#include "mltcontroller/clipcontroller.h"
33
#include "lib/audio/audioStreamInfo.h"
34
#include "mltcontroller/clippropertiescontroller.h"
35 36 37

#include <QDomElement>
#include <QFile>
38
#include <QDir>
39 40
#include <QDebug>
#include <QCryptographicHash>
41
#include <QtConcurrent>
42
#include <KLocalizedString>
43
#include <KMessageBox>
44 45


Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
46
ProjectClip::ProjectClip(const QString &id, QIcon thumb, ClipController *controller, ProjectFolder* parent) :
47
    AbstractProjectItem(AbstractProjectItem::ClipItem, id, parent)
48
    , m_abortAudioThumb(false)
Vincent Pinon's avatar
Vincent Pinon committed
49
    , m_controller(controller)
50
    , m_thumbsProducer(NULL)
51
{
52
    m_clipStatus = StatusReady;
53 54
    m_name = m_controller->clipName();
    m_duration = m_controller->getStringDuration();
55
    m_date = m_controller->date;
56
    m_description = m_controller->description();
57
    m_type = m_controller->clipType();
58 59 60 61 62
    if (m_type == Audio) {
        m_thumbnail = QIcon::fromTheme(QStringLiteral("audio-x-generic"));
    } else {
        m_thumbnail = thumb;
    }
63 64
    // Make sure we have a hash for this clip
    hash();
65
    setParent(parent);
66
    connect(this, &ProjectClip::updateJobStatus, this, &ProjectClip::setJobStatus);
67
    bin()->loadSubClips(id, m_controller->getPropertiesFromPrefix(QStringLiteral("kdenlive:clipzone.")));
68
    createAudioThumbs();
69 70
}

71
ProjectClip::ProjectClip(const QDomElement& description, QIcon thumb, ProjectFolder* parent) :
72
    AbstractProjectItem(AbstractProjectItem::ClipItem, description, parent)
73
    , m_abortAudioThumb(false)
Vincent Pinon's avatar
Vincent Pinon committed
74
    , m_controller(NULL)
75
    , m_type(Unknown)
76
    , m_thumbsProducer(NULL)
77 78
{
    Q_ASSERT(description.hasAttribute("id"));
79
    m_clipStatus = StatusWaiting;
80
    m_thumbnail = thumb;
81 82
    if (description.hasAttribute(QStringLiteral("type"))) {
        m_type = (ClipType) description.attribute(QStringLiteral("type")).toInt();
83 84 85
        if (m_type == Audio) {
            m_thumbnail = QIcon::fromTheme(QStringLiteral("audio-x-generic"));
        }
86
    }
87 88
    m_temporaryUrl = QUrl::fromLocalFile(getXmlProperty(description, QStringLiteral("resource")));
    QString clipName = getXmlProperty(description, QStringLiteral("kdenlive:clipname"));
89 90 91
    if (!clipName.isEmpty()) {
        m_name = clipName;
    }
92 93
    else if (m_temporaryUrl.isValid()) {
        m_name = m_temporaryUrl.fileName();
94
    }
95
    else m_name = i18n("Untitled");
96
    connect(this, &ProjectClip::updateJobStatus, this, &ProjectClip::setJobStatus);
97
    setParent(parent);
98 99 100 101 102
}


ProjectClip::~ProjectClip()
{
103
    // controller is deleted in bincontroller
104
    abortAudioThumbs();
105 106 107
    bin()->slotAbortAudioThumb(m_id);
    QMutexLocker audioLock(&m_audioMutex);
    m_thumbMutex.lock();
108
    m_requestedThumbs.clear();
109
    m_thumbMutex.unlock();
110
    m_thumbThread.waitForFinished();
111
    delete m_thumbsProducer;
112
    audioFrameCache.clear();
113 114
}

115 116 117
void ProjectClip::abortAudioThumbs()
{
    m_abortAudioThumb = true;
118
    emit doAbortAudioThumbs();
119 120
}

121 122 123 124 125
QString ProjectClip::getToolTip() const
{
    return url().toLocalFile();
}

126
QString ProjectClip::getXmlProperty(const QDomElement &producer, const QString &propertyName, const QString &defaultValue)
127
{
128
    QString value = defaultValue;
129
    QDomNodeList props = producer.elementsByTagName(QStringLiteral("property"));
130
    for (int i = 0; i < props.count(); ++i) {
131
        if (props.at(i).toElement().attribute(QStringLiteral("name")) == propertyName) {
132 133 134 135 136 137 138
            value = props.at(i).firstChild().nodeValue();
            break;
        }
    }
    return value;
}

139
void ProjectClip::updateAudioThumbnail(QVariantList audioLevels)
140
{
141
    audioFrameCache = audioLevels;
142
    m_controller->audioThumbCreated = true;
143
    bin()->emitRefreshAudioThumbs(m_id);
144 145 146 147 148 149 150 151 152
    emit gotAudioData();
}

QList < CommentedTime > ProjectClip::commentedSnapMarkers() const
{
    if (m_controller) return m_controller->commentedSnapMarkers();
    return QList < CommentedTime > ();
}

153 154 155 156 157 158
QStringList ProjectClip::markersText(GenTime in, GenTime out) const
{
    if (m_controller) return m_controller->markerComments(in, out);
    return QStringList();
}

159 160
bool ProjectClip::audioThumbCreated() const
{
161
    return (m_controller && m_controller->audioThumbCreated);
162 163
}

164 165
ClipType ProjectClip::clipType() const
{
166
    return m_type;
167 168
}

169 170 171 172 173 174 175 176 177 178 179 180
bool ProjectClip::hasParent(const QString &id) const
{
    AbstractProjectItem *par = parent();
    while (par) {
	if (par->clipId() == id) {
	    return true;
	}
	par = par->parent();
    }
    return false;
}

181 182 183 184 185 186 187 188
ProjectClip* ProjectClip::clip(const QString &id)
{
    if (id == m_id) {
        return this;
    }
    return NULL;
}

189 190
ProjectFolder* ProjectClip::folder(const QString &id)
{
191
    Q_UNUSED(id)
192 193 194
    return NULL;
}

195 196 197 198 199
void ProjectClip::disableEffects(bool disable)
{
    if (m_controller) m_controller->disableEffects(disable);
}

200 201 202
ProjectSubClip* ProjectClip::getSubClip(int in, int out)
{
    for (int i = 0; i < count(); ++i) {
Vincent Pinon's avatar
Vincent Pinon committed
203
        ProjectSubClip *clip = static_cast<ProjectSubClip *>(at(i))->subClip(in, out);
204 205 206 207 208 209 210
        if (clip) {
            return clip;
        }
    }
    return NULL;
}

211 212 213 214 215 216 217 218 219 220 221 222
QStringList ProjectClip::subClipIds() const
{
    QStringList subIds;
    for (int i = 0; i < count(); ++i) {
        AbstractProjectItem *clip = at(i);
        if (clip) {
            subIds << clip->clipId();
        }
    }
    return subIds;
}

223 224 225 226 227 228 229 230
ProjectClip* ProjectClip::clipAt(int ix)
{
    if (ix == index()) {
        return this;
    }
    return NULL;
}

231
/*bool ProjectClip::isValid() const
232
{
233 234
    return m_controller->isValid();
}*/
235 236 237

QUrl ProjectClip::url() const
{
238
    if (m_controller) return m_controller->clipUrl();
239
    return m_temporaryUrl;
240 241 242 243
}

bool ProjectClip::hasLimitedDuration() const
{
244 245 246 247
    if (m_controller) {
        return m_controller->hasLimitedDuration();
    }
    return true;
248 249
}

250
GenTime ProjectClip::duration() const
251
{
252 253
    if (m_controller) {
	return m_controller->getPlaytime();
254
    }
255
    return GenTime();
256 257
}

258
void ProjectClip::reloadProducer(bool thumbnailOnly)
259 260 261
{
    QDomDocument doc;
    QDomElement xml = toXml(doc);
262 263
    if (thumbnailOnly) {
        // set a special flag to request thumbnail only
264
        xml.setAttribute(QStringLiteral("thumbnailOnly"), QStringLiteral("1"));
265
    }
266
    bin()->reloadProducer(m_id, xml);
267 268 269 270
}

void ProjectClip::setCurrent(bool current, bool notify)
{
271
    Q_UNUSED(notify)
272
    if (current && m_controller) {
Jean-Baptiste Mardelle's avatar
cleanup  
Jean-Baptiste Mardelle committed
273
        bin()->openProducer(m_controller);
274
        bin()->editMasterEffect(m_controller);
275 276 277
    }
}

278
QDomElement ProjectClip::toXml(QDomDocument& document, bool includeMeta)
279
{
280
    if (m_controller) {
281
        m_controller->getProducerXML(document, includeMeta);
282
        return document.documentElement().firstChildElement(QStringLiteral("producer"));
283 284 285 286 287 288 289 290 291 292
    } else {
        // We only have very basic infos, ike id and url, pass them
        QDomElement prod = document.createElement(QStringLiteral("producer"));
        prod.setAttribute(QStringLiteral("id"), m_id);
        EffectsList::setProperty(prod, QStringLiteral("resource"), m_temporaryUrl.path());
        if (m_type != Unknown) {
            prod.setAttribute(QStringLiteral("type"), (int) m_type);
        }
        document.appendChild(prod);
        return prod;
293 294 295 296 297
    }
}

void ProjectClip::setThumbnail(QImage img)
{
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
298 299
    QPixmap thumb = roundedPixmap(QPixmap::fromImage(img));
    if (hasProxy() && !thumb.isNull()) {
300
        // Overlay proxy icon
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
301
        QPainter p(&thumb);
302
        QColor c(220, 220, 10, 200);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
303
        QRect r(0, 0, thumb.height() / 2.5, thumb.height() / 2.5);
304 305 306 307 308 309 310 311
        p.fillRect(r, c);
        QFont font = p.font();
        font.setPixelSize(r.height());
        font.setBold(true);
        p.setFont(font);
        p.setPen(Qt::black);
        p.drawText(r, Qt::AlignCenter, i18nc("The first letter of Proxy, used as abbreviation", "P"));
    }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
312
    m_thumbnail = QIcon(thumb);
313
    emit thumbUpdated(img);
314 315 316
    bin()->emitItemUpdated(this);
}

317 318 319 320 321
QPixmap ProjectClip::thumbnail(int width, int height)
{
    return m_thumbnail.pixmap(width, height);
}

322
bool ProjectClip::setProducer(ClipController *controller, bool replaceProducer)
323
{
324
    if (!replaceProducer && m_controller) {
Vincent Pinon's avatar
Vincent Pinon committed
325
        qDebug()<<"// RECEIVED PRODUCER BUT WE ALREADY HAVE ONE\n----------";
326
        return false;
327
    }
328
    bool isNewProducer = true;
329 330
    if (m_controller) {
        // Replace clip for this controller
331 332
        resetProducerProperty("kdenlive:file_hash");
        isNewProducer = false;
333
    }
334
    else if (controller) {
335 336 337
        // We did not yet have the controller, update info
        m_controller = controller;
        if (m_name.isEmpty()) m_name = m_controller->clipName();
338
        m_date = m_controller->date;
339
        m_description = m_controller->description();
340
        m_temporaryUrl.clear();
341 342 343 344 345 346
        if (m_type == Unknown) {
            m_type = m_controller->clipType();
            if (m_type == Audio) {
                m_thumbnail = QIcon::fromTheme(QStringLiteral("audio-x-generic"));
            }
        }
347
    }
Vincent Pinon's avatar
Vincent Pinon committed
348 349
    if (m_controller)
        m_duration = m_controller->getStringDuration();
350
    m_clipStatus = StatusReady;
351
    if (!hasProxy()) bin()->emitRefreshPanel(m_id);
352
    bin()->emitItemUpdated(this);
353 354
    // Make sure we have a hash for this clip
    hash();
355
    createAudioThumbs();
356
    return isNewProducer;
357 358 359 360
}

void ProjectClip::createAudioThumbs()
{
361
    if (KdenliveSettings::audiothumbnails() && (m_type == AV || m_type == Audio || m_type == Playlist)) {
362
        bin()->requestAudioThumbs(m_id);
363
    }
364 365
}

366
Mlt::Producer *ProjectClip::originalProducer()
367
{
368 369 370 371
    if (!m_controller) {
        return NULL;
    }
    return &m_controller->originalProducer();
372 373
}

374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395
Mlt::Producer *ProjectClip::thumbProducer()
{
    QMutexLocker locker(&m_producerMutex);
    if (m_thumbsProducer) {
        return m_thumbsProducer;
    }
    if (!m_controller) {
        return NULL;
    }
    Mlt::Producer prod = m_controller->originalProducer();
    Clip clip(prod);
    m_thumbsProducer = clip.softClone(ClipController::getPassPropertiesList());
    // Check if we are using GPU accel, then we need to use alternate producer
    if (KdenliveSettings::gpu_accel()) {
        Mlt::Filter scaler(*prod.profile(), "swscale");
        Mlt::Filter converter(*prod.profile(), "avcolor_space");
        m_thumbsProducer->attach(scaler);
        m_thumbsProducer->attach(converter);
    }
    return m_thumbsProducer;
}

396 397 398 399 400
ClipController *ProjectClip::controller()
{
    return m_controller;
}

401
bool ProjectClip::isReady() const
402
{
403
    return m_controller != NULL && m_clipStatus == StatusReady;
404 405
}

406
/*void ProjectClip::setZone(const QPoint &zone)
407 408
{
    m_zone = zone;
409
}*/
410 411 412

QPoint ProjectClip::zone() const
{
413 414
    int x = getProducerIntProperty(QStringLiteral("kdenlive:zone_in"));
    int y = getProducerIntProperty(QStringLiteral("kdenlive:zone_out"));
415
    return QPoint(x, y);
416 417
}

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
418 419 420 421 422 423 424 425
void ProjectClip::resetProducerProperty(const QString &name)
{
    if (m_controller) {
        m_controller->resetProperty(name);
    }
}

void ProjectClip::setProducerProperty(const QString &name, int data)
426
{
427 428
    if (m_controller) {
	m_controller->setProperty(name, data);
429 430 431
    }
}

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
432
void ProjectClip::setProducerProperty(const QString &name, double data)
433
{
434 435
    if (m_controller) {
        m_controller->setProperty(name, data);
436 437 438
    }
}

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
439
void ProjectClip::setProducerProperty(const QString &name, const QString &data)
440
{
441 442
    if (m_controller) {
        m_controller->setProperty(name, data);
443 444 445
    }
}

446 447 448 449 450 451 452 453 454 455 456 457 458 459
QMap <QString, QString> ProjectClip::currentProperties(const QMap <QString, QString> &props)
{
    QMap <QString, QString> currentProps;
    if (!m_controller) {
        return currentProps;
    }
    QMap<QString, QString>::const_iterator i = props.constBegin();
    while (i != props.constEnd()) {
        currentProps.insert(i.key(), m_controller->property(i.key()));
        ++i;
    }
    return currentProps;
}

460 461 462 463 464 465 466 467
QColor ProjectClip::getProducerColorProperty(const QString &key) const
{
    if (m_controller) {
        return m_controller->color_property(key);
    }
    return QColor();
}

468 469 470 471 472 473 474 475
int ProjectClip::getProducerIntProperty(const QString &key) const
{
    int value = 0;
    if (m_controller) {
        value = m_controller->int_property(key);
    }
    return value;
}
476

477 478 479 480 481 482 483 484 485
qint64 ProjectClip::getProducerInt64Property(const QString &key) const
{
    qint64 value = 0;
    if (m_controller) {
        value = m_controller->int64_property(key);
    }
    return value;
}

486 487 488 489 490 491 492 493 494
double ProjectClip::getDoubleProducerProperty(const QString &key) const
{
    double value = 0;
    if (m_controller) {
        value = m_controller->double_property(key);
    }
    return value;
}

495
QString ProjectClip::getProducerProperty(const QString &key) const
496 497
{
    QString value;
498 499
    if (m_controller) {
	value = m_controller->property(key);
500 501 502 503 504 505
    }
    return value;
}

const QString ProjectClip::hash()
{
506
    if (m_controller) {
507
        QString clipHash = m_controller->property(QStringLiteral("kdenlive:file_hash"));
508 509
        if (!clipHash.isEmpty()) {
            return clipHash;
510
        }
511
    }
512
    return getFileHash();
513 514
}

515
const QString ProjectClip::getFileHash() const
516
{
517 518 519 520 521 522 523 524
    QByteArray fileData;
    QByteArray fileHash;
    switch (m_type) {
      case SlideShow:
          fileData = m_controller ? m_controller->clipUrl().toLocalFile().toUtf8() : m_temporaryUrl.toLocalFile().toUtf8();
          fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
          break;
      case Text:
525
          fileData = m_controller ? m_controller->property(QStringLiteral("xmldata")).toUtf8() : name().toUtf8();
526 527
          fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
          break;
Vincent Pinon's avatar
Vincent Pinon committed
528 529 530 531
      case QText:
          fileData = m_controller ? m_controller->property(QStringLiteral("text")).toUtf8() : name().toUtf8();
          fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
          break;
532
      case Color:
533
          fileData = m_controller ? m_controller->property(QStringLiteral("resource")).toUtf8() : name().toUtf8();
534
          fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
535 536 537 538 539
          break;
      default:
          QFile file(m_controller ? m_controller->clipUrl().toLocalFile() : m_temporaryUrl.toLocalFile());
          if (file.open(QIODevice::ReadOnly)) { // write size and hash only if resource points to a file
              /*
540 541 542
               * 1 MB = 1 second per 450 files (or faster)
               * 10 MB = 9 seconds per 450 files (or faster)
               */
543 544 545 546 547 548 549
            if (file.size() > 2000000) {
                fileData = file.read(1000000);
                if (file.seek(file.size() - 1000000))
                    fileData.append(file.readAll());
            } else
                fileData = file.readAll();
            file.close();
550
            if (m_controller) m_controller->setProperty(QStringLiteral("kdenlive:file_size"), QString::number(file.size()));
551 552 553 554 555 556 557
            fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
          }
          break;
    }
    if (fileHash.isEmpty()) return QString();
    QString result = fileHash.toHex();
    if (m_controller) {
558
	m_controller->setProperty(QStringLiteral("kdenlive:file_hash"), result);
559
    }
560
    return result;
561
}
562 563 564 565 566 567

double ProjectClip::getOriginalFps() const
{
    if (!m_controller) return 0;
    return m_controller->originalFps();
}
568 569 570

bool ProjectClip::hasProxy() const
{
571 572
    QString proxy = getProducerProperty(QStringLiteral("kdenlive:proxy"));
    if (proxy.isEmpty() || proxy == QLatin1String("-")) return false;
573 574 575
    return true;
}

576
void ProjectClip::setProperties(QMap <QString, QString> properties, bool refreshPanel)
577 578
{
    QMapIterator<QString, QString> i(properties);
579
    QMap <QString, QString> passProperties;
580
    bool refreshAnalysis = false;
581 582 583
    bool reload = false;
    // Some properties also need to be passed to track producers
    QStringList timelineProperties;
584 585 586 587 588
    if (properties.contains(QLatin1String("templatetext"))) {
        m_description = properties.value(QLatin1String("templatetext"));
        bin()->emitItemUpdated(this);
        refreshPanel = true;
    }
589
    timelineProperties << QStringLiteral("force_aspect_ratio") << QStringLiteral("video_index") << QStringLiteral("audio_index") << QStringLiteral("set.force_full_luma")<< QStringLiteral("full_luma") <<QStringLiteral("threads") <<QStringLiteral("force_colorspace")<<QStringLiteral("force_tff")<<QStringLiteral("force_progressive")<<QStringLiteral("force_fps");
590
    QStringList keys;
591
    keys << QStringLiteral("luma_duration") << QStringLiteral("luma_file") << QStringLiteral("fade") << QStringLiteral("ttl") << QStringLiteral("softness") << QStringLiteral("crop") << QStringLiteral("animation");
592 593
    while (i.hasNext()) {
        i.next();
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
594
        setProducerProperty(i.key(), i.value());
595 596 597
        if (m_type == SlideShow && keys.contains(i.key())) {
            reload = true;
        }
598
        if (i.key().startsWith(QLatin1String("kdenlive:clipanalysis"))) refreshAnalysis = true;
599 600 601
        if (timelineProperties.contains(i.key())) {
            passProperties.insert(i.key(), i.value());
        }
602
    }
603 604
    if (properties.contains(QStringLiteral("kdenlive:proxy"))) {
        QString value = properties.value(QStringLiteral("kdenlive:proxy"));
605
        // If value is "-", that means user manually disabled proxy on this clip
606
        if (value.isEmpty() || value == QLatin1String("-")) {
607 608 609 610
            // reset proxy
            if (bin()->hasPendingJob(m_id, AbstractClipJob::PROXYJOB)) {
                bin()->discardJobs(m_id, AbstractClipJob::PROXYJOB);
            }
611 612 613
            else {
                reloadProducer();
            }
614 615
        }
        else {
616
            // A proxy was requested, make sure to keep original url
617
            setProducerProperty(QStringLiteral("kdenlive:originalurl"), url().toLocalFile());
618
            bin()->startJob(m_id, AbstractClipJob::PROXYJOB);
619 620
        }
    }
621
    else if (properties.contains(QStringLiteral("resource")) || properties.contains(QStringLiteral("templatetext")) || properties.contains(QStringLiteral("autorotate"))) {
622
        // Clip resource changed, update thumbnail
623
        if (m_type != Color) {
624
            reloadProducer();
625
        }
626
        else reload = true;
627
    }
628
    if (properties.contains(QStringLiteral("xmldata")) || !passProperties.isEmpty()) {
629
        reload = true;
630
    }
631
    if (refreshAnalysis) emit refreshAnalysisPanel();
632
    if (properties.contains(QStringLiteral("length"))) {
633 634 635 636
        m_duration = m_controller->getStringDuration();
        bin()->emitItemUpdated(this);
    }

637 638
    if (properties.contains(QStringLiteral("kdenlive:clipname"))) {
        m_name = properties.value(QStringLiteral("kdenlive:clipname"));
639
        refreshPanel = true;
640 641
        bin()->emitItemUpdated(this);
    }
642 643 644 645
    if (refreshPanel) {
        // Some of the clip properties have changed through a command, update properties panel
        emit refreshPropertiesPanel();
    }
646
    if (reload) {
647
        // producer has changed, refresh monitor and thumbnail
648
        reloadProducer(true);
649
        bin()->refreshClip(m_id);
650
    }
651 652 653
    if (!passProperties.isEmpty()) {
        bin()->updateTimelineProducers(m_id, passProperties);
    }
654 655
}

656
void ProjectClip::setJobStatus(int jobType, int status, int progress, const QString &statusMessage)
657
{
658
    m_jobType = (AbstractClipJob::JOBTYPE) jobType;
659
    if (progress > 0) {
660
        if (m_jobProgress == progress) return;
661
        m_jobProgress = progress;
662 663
    }
    else {
664 665 666
        m_jobProgress = status;
        if ((status == JobAborted || status == JobCrashed  || status == JobDone) && !statusMessage.isEmpty()) {
            m_jobMessage = statusMessage;
667
            bin()->emitMessage(statusMessage, OperationCompletedMessage);
668
        }
669 670 671
    }
    bin()->emitItemUpdated(this);
}
672

673 674 675

ClipPropertiesController *ProjectClip::buildProperties(QWidget *parent)
{
676
    ClipPropertiesController *panel = new ClipPropertiesController(bin()->projectTimecode(), m_controller, parent);
677
    connect(this, SIGNAL(refreshPropertiesPanel()), panel, SLOT(slotReloadProperties()));
678
    connect(this, SIGNAL(refreshAnalysisPanel()), panel, SLOT(slotFillAnalysisData()));
679 680 681
    return panel;
}

682 683
void ProjectClip::updateParentInfo(const QString &folderid, const QString &foldername)
{
684
    Q_UNUSED(foldername)
685
    m_controller->setProperty(QStringLiteral("kdenlive:folderid"), folderid);
686 687
}

688 689 690
bool ProjectClip::matches(QString condition)
{
    //TODO
691
    Q_UNUSED(condition)
692 693 694
    return true;
}

695
const QString ProjectClip::codec(bool audioCodec) const
696
{
697 698
    if (!m_controller) return QString();
    return m_controller->codec(audioCodec);
699
}
700

701
bool ProjectClip::rename(const QString &name, int column)
702 703 704
{
    QMap <QString, QString> newProperites;
    QMap <QString, QString> oldProperites;
705 706 707 708 709
    bool edited = false;
    switch (column) {
      case 0:
        if (m_name == name) return false;
        // Rename clip
710 711
        oldProperites.insert(QStringLiteral("kdenlive:clipname"), m_name);
        newProperites.insert(QStringLiteral("kdenlive:clipname"), name);
712 713 714 715 716 717
        m_name = name;
        edited = true;
        break;
      case 2:
        if (m_description == name) return false;
        // Rename clip
718 719 720 721 722 723 724
        if (m_type == TextTemplate) {
            oldProperites.insert(QStringLiteral("templatetext"), m_description);
            newProperites.insert(QStringLiteral("templatetext"), name);
        } else {
            oldProperites.insert(QStringLiteral("kdenlive:description"), m_description);
            newProperites.insert(QStringLiteral("kdenlive:description"), name);
        }
725 726 727 728 729 730 731 732
        m_description = name;
        edited = true;
        break;
    }
    if (edited) {
        bin()->slotEditClipCommand(m_id, oldProperites, newProperites);
    }
    return edited;
733 734
}

735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762
void ProjectClip::addClipMarker(QList <CommentedTime> newMarkers, QUndoCommand *groupCommand)
{
    if (!m_controller) return;
    QList <CommentedTime> oldMarkers;
    for (int i = 0; i < newMarkers.count(); ++i) {
        CommentedTime oldMarker = m_controller->markerAt(newMarkers.at(i).time());
        if (oldMarker == CommentedTime()) {
            oldMarker = newMarkers.at(i);
            oldMarker.setMarkerType(-1);
        }
        oldMarkers << oldMarker;
    }
    (void) new AddMarkerCommand(this, oldMarkers, newMarkers, groupCommand);
}

bool ProjectClip::deleteClipMarkers(QUndoCommand *command)
{
    QList <CommentedTime> markers = commentedSnapMarkers();
    if (markers.isEmpty()) {
        return false;
    }
    QList <CommentedTime> newMarkers;
    for (int i = 0; i < markers.size(); ++i) {
        CommentedTime marker = markers.at(i);
        marker.setMarkerType(-1);
        newMarkers << marker;
    }
    new AddMarkerCommand(this, markers, newMarkers, command);
763
    return true;
764 765 766 767 768 769 770 771 772 773 774 775 776 777
}

void ProjectClip::addMarkers(QList <CommentedTime> &markers)
{
    if (!m_controller) return;
    for (int i = 0; i < markers.count(); ++i) {
      if (markers.at(i).markerType() < 0) m_controller->deleteSnapMarker(markers.at(i).time());
      else m_controller->addSnapMarker(markers.at(i));
    }
    // refresh markers in clip monitor
    bin()->refreshClipMarkers(m_id);
    // refresh markers in timeline clips
    emit refreshClipDisplay();
}
778

Vincent Pinon's avatar
Vincent Pinon committed
779
void ProjectClip::addEffect(const ProfileInfo &pInfo, QDomElement &effect)
780
{
781
    m_controller->addEffect(pInfo, effect);
782
    bin()->updateMasterEffect(m_controller);
783
    bin()->emitItemUpdated(this);
784 785
}

786
void ProjectClip::removeEffect(int ix)
787
{
788
    m_controller->removeEffect(ix);
789
    bin()->updateMasterEffect(m_controller);
790 791 792 793 794 795 796
    bin()->emitItemUpdated(this);
}

QVariant ProjectClip::data(DataType type) const
{
    switch (type) {
      case AbstractProjectItem::IconOverlay:
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
797
            return m_controller != NULL ? (m_controller->hasEffects() ? QVariant("kdenlive-track_has_effect") : QVariant()) : QVariant();
798 799 800 801 802
            break;
        default:
	    break;
    }
    return AbstractProjectItem::data(type);
803
}
804

805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848
void ProjectClip::slotQueryIntraThumbs(QList <int> frames)
{
    QMutexLocker lock(&m_intraThumbMutex);
    for (int i = 0; i < frames.count(); i++) {
        if (!m_intraThumbs.contains(frames.at(i))) {
            m_intraThumbs << frames.at(i);
        }
    }
    qSort(m_intraThumbs);
    if (!m_intraThread.isRunning()) {
        m_intraThread = QtConcurrent::run(this, &ProjectClip::doExtractIntra);
    }
}

void ProjectClip::doExtractIntra()
{
    Mlt::Producer *prod = thumbProducer();
    if (prod == NULL || !prod->is_valid()) return;
    int fullWidth = (int)((double) 150 * prod->profile()->dar() + 0.5);
    int max = prod->get_length();
    int pos;
    while (!m_intraThumbs.isEmpty()) {
        m_intraThumbMutex.lock();
        pos = m_intraThumbs.takeFirst();
        m_intraThumbMutex.unlock();
        if (pos >= max) pos = max - 1;
        const QString path = url().path() + '_' + QString::number(pos);
        QImage img = bin()->findCachedPixmap(path);
        if (!img.isNull()) {
            // Cache already contains image
            continue;
        }
	prod->seek(pos);
	Mlt::Frame *frame = prod->get_frame();
	if (frame && frame->is_valid()) {
            img = KThumb::getFrame(frame, fullWidth, 150);
            bin()->cachePixmap(path, img);
            emit thumbReady(pos, img);
        }
        delete frame;
    }
}


849
void ProjectClip::slotExtractImage(QList <int> frames)
850 851 852 853 854 855 856 857 858 859 860 861 862 863
{
    QMutexLocker lock(&m_thumbMutex);
    for (int i = 0; i < frames.count(); i++) {
        if (!m_requestedThumbs.contains(frames.at(i))) {
            m_requestedThumbs << frames.at(i);
        }
    }
    qSort(m_requestedThumbs);
    if (!m_thumbThread.isRunning()) {
        m_thumbThread = QtConcurrent::run(this, &ProjectClip::doExtractImage);
    }
}

void ProjectClip::doExtractImage()
864
{
865
    Mlt::Producer *prod = thumbProducer();
866
    if (prod == NULL || !prod->is_valid()) return;
867 868
    int fullWidth = (int)((double) 150 * prod->profile()->dar() + 0.5);
    QDir thumbFolder(bin()->projectFolder().path() + "/thumbs/");
869 870 871
    int max = prod->get_length();
    while (!m_requestedThumbs.isEmpty()) {
        m_thumbMutex.lock();
872
        int pos = m_requestedThumbs.takeFirst();
873
        m_thumbMutex.unlock();
874 875
        if (thumbFolder.exists(hash() + '#' + QString::number(pos) + ".png")) {
            emit thumbReady(pos, QImage(thumbFolder.absoluteFilePath(hash() + '#' + QString::number(pos) + ".png")));
876 877
            continue;
        }
878 879 880 881 882 883 884
        if (pos >= max) pos = max - 1;
        const QString path = url().path() + '_' + QString::number(pos);
        QImage img = bin()->findCachedPixmap(path);
        if (!img.isNull()) {
            emit thumbReady(pos, img);
            continue;
        }
885 886 887
	prod->seek(pos);
	Mlt::Frame *frame = prod->get_frame();
	if (frame && frame->is_valid()) {
888 889
            img = KThumb::getFrame(frame, fullWidth, 150);
            bin()->cachePixmap(path, img);