projectclip.cpp 47.2 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 "utils/KoIconUtils.h"
35
#include "mltcontroller/clippropertiescontroller.h"
36 37 38

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


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

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


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

118 119 120
void ProjectClip::abortAudioThumbs()
{
    m_abortAudioThumb = true;
121
    emit doAbortAudioThumbs();
122 123
}

124 125 126 127 128
QString ProjectClip::getToolTip() const
{
    return url().toLocalFile();
}

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

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

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

156 157 158 159 160 161
QStringList ProjectClip::markersText(GenTime in, GenTime out) const
{
    if (m_controller) return m_controller->markerComments(in, out);
    return QStringList();
}

162 163
bool ProjectClip::audioThumbCreated() const
{
164
    return (m_controller && m_controller->audioThumbCreated);
165 166
}

167 168
ClipType ProjectClip::clipType() const
{
169
    return m_type;
170 171
}

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

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

192 193
ProjectFolder* ProjectClip::folder(const QString &id)
{
194
    Q_UNUSED(id)
195 196 197
    return NULL;
}

198 199 200 201 202
void ProjectClip::disableEffects(bool disable)
{
    if (m_controller) m_controller->disableEffects(disable);
}

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

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

226 227 228 229 230 231 232 233
ProjectClip* ProjectClip::clipAt(int ix)
{
    if (ix == index()) {
        return this;
    }
    return NULL;
}

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

239 240 241 242 243 244
bool ProjectClip::hasUrl() const
{
    if (m_controller && (m_type != Color) && (m_type != Unknown)) return (m_controller->clipUrl().isValid());
    return false;
}

245 246
QUrl ProjectClip::url() const
{
247
    if (m_controller) return m_controller->clipUrl();
248
    return m_temporaryUrl;
249 250 251 252
}

bool ProjectClip::hasLimitedDuration() const
{
253 254 255 256
    if (m_controller) {
        return m_controller->hasLimitedDuration();
    }
    return true;
257 258
}

259
GenTime ProjectClip::duration() const
260
{
261 262
    if (m_controller) {
	return m_controller->getPlaytime();
263
    }
264
    return GenTime();
265 266
}

267
void ProjectClip::reloadProducer(bool refreshOnly)
268 269 270
{
    QDomDocument doc;
    QDomElement xml = toXml(doc);
271
    if (refreshOnly) {
272
        // set a special flag to request thumbnail only
273
        xml.setAttribute(QStringLiteral("refreshOnly"), QStringLiteral("1"));
274
    }
275
    bin()->reloadProducer(m_id, xml);
276 277 278 279
}

void ProjectClip::setCurrent(bool current, bool notify)
{
280
    Q_UNUSED(notify)
281
    if (current && m_controller) {
Jean-Baptiste Mardelle's avatar
cleanup  
Jean-Baptiste Mardelle committed
282
        bin()->openProducer(m_controller);
283
        bin()->editMasterEffect(m_controller);
284 285 286
    }
}

287
QDomElement ProjectClip::toXml(QDomDocument& document, bool includeMeta)
288
{
289
    if (m_controller) {
290
        m_controller->getProducerXML(document, includeMeta);
291
        return document.documentElement().firstChildElement(QStringLiteral("producer"));
292 293 294 295 296 297 298 299 300 301
    } 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;
302 303 304 305 306
    }
}

void ProjectClip::setThumbnail(QImage img)
{
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
307 308
    QPixmap thumb = roundedPixmap(QPixmap::fromImage(img));
    if (hasProxy() && !thumb.isNull()) {
309
        // Overlay proxy icon
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
310
        QPainter p(&thumb);
311
        QColor c(220, 220, 10, 200);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
312
        QRect r(0, 0, thumb.height() / 2.5, thumb.height() / 2.5);
313 314 315 316 317 318 319 320
        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
321
    m_thumbnail = QIcon(thumb);
322
    emit thumbUpdated(img);
323 324 325
    bin()->emitItemUpdated(this);
}

326 327 328 329 330
QPixmap ProjectClip::thumbnail(int width, int height)
{
    return m_thumbnail.pixmap(width, height);
}

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

void ProjectClip::createAudioThumbs()
{
370
    if (KdenliveSettings::audiothumbnails() && (m_type == AV || m_type == Audio || m_type == Playlist)) {
371 372
        bin()->requestAudioThumbs(m_id, duration().ms());
        emit updateJobStatus(AbstractClipJob::THUMBJOB, JobWaiting, 0);
373
    }
374 375
}

376
Mlt::Producer *ProjectClip::originalProducer()
377
{
378 379 380 381
    if (!m_controller) {
        return NULL;
    }
    return &m_controller->originalProducer();
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);
    if (KdenliveSettings::gpu_accel()) {
396
        m_thumbsProducer = clip.softClone(ClipController::getPassPropertiesList());
397 398 399 400
        Mlt::Filter scaler(*prod.profile(), "swscale");
        Mlt::Filter converter(*prod.profile(), "avcolor_space");
        m_thumbsProducer->attach(scaler);
        m_thumbsProducer->attach(converter);
401 402
    } else {
        m_thumbsProducer = clip.clone();
403 404 405 406
    }
    return m_thumbsProducer;
}

407 408 409 410 411
ClipController *ProjectClip::controller()
{
    return m_controller;
}

412
bool ProjectClip::isReady() const
413
{
414
    return m_controller != NULL && m_clipStatus == StatusReady;
415 416
}

417
/*void ProjectClip::setZone(const QPoint &zone)
418 419
{
    m_zone = zone;
420
}*/
421 422 423

QPoint ProjectClip::zone() const
{
424 425
    int x = getProducerIntProperty(QStringLiteral("kdenlive:zone_in"));
    int y = getProducerIntProperty(QStringLiteral("kdenlive:zone_out"));
426
    return QPoint(x, y);
427 428
}

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
429 430 431 432 433 434 435 436
void ProjectClip::resetProducerProperty(const QString &name)
{
    if (m_controller) {
        m_controller->resetProperty(name);
    }
}

void ProjectClip::setProducerProperty(const QString &name, int data)
437
{
438 439
    if (m_controller) {
	m_controller->setProperty(name, data);
440 441 442
    }
}

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
443
void ProjectClip::setProducerProperty(const QString &name, double data)
444
{
445 446
    if (m_controller) {
        m_controller->setProperty(name, data);
447 448 449
    }
}

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
450
void ProjectClip::setProducerProperty(const QString &name, const QString &data)
451
{
452 453
    if (m_controller) {
        m_controller->setProperty(name, data);
454 455 456
    }
}

457 458 459 460 461 462 463 464 465 466 467 468 469 470
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;
}

471 472 473 474 475 476 477 478
QColor ProjectClip::getProducerColorProperty(const QString &key) const
{
    if (m_controller) {
        return m_controller->color_property(key);
    }
    return QColor();
}

479 480 481 482 483 484 485 486
int ProjectClip::getProducerIntProperty(const QString &key) const
{
    int value = 0;
    if (m_controller) {
        value = m_controller->int_property(key);
    }
    return value;
}
487

488 489 490 491 492 493 494 495 496
qint64 ProjectClip::getProducerInt64Property(const QString &key) const
{
    qint64 value = 0;
    if (m_controller) {
        value = m_controller->int64_property(key);
    }
    return value;
}

497 498 499 500 501 502 503 504 505
double ProjectClip::getDoubleProducerProperty(const QString &key) const
{
    double value = 0;
    if (m_controller) {
        value = m_controller->double_property(key);
    }
    return value;
}

506
QString ProjectClip::getProducerProperty(const QString &key) const
507 508
{
    QString value;
509 510
    if (m_controller) {
	value = m_controller->property(key);
511 512 513 514 515 516
    }
    return value;
}

const QString ProjectClip::hash()
{
517
    if (m_controller) {
518
        QString clipHash = m_controller->property(QStringLiteral("kdenlive:file_hash"));
519 520
        if (!clipHash.isEmpty()) {
            return clipHash;
521
        }
522
    }
523
    return getFileHash();
524 525
}

526
const QString ProjectClip::getFileHash() const
527
{
528 529 530 531 532 533 534 535
    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:
536
          fileData = m_controller ? m_controller->property(QStringLiteral("xmldata")).toUtf8() : name().toUtf8();
537 538
          fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
          break;
Vincent Pinon's avatar
Vincent Pinon committed
539 540 541 542
      case QText:
          fileData = m_controller ? m_controller->property(QStringLiteral("text")).toUtf8() : name().toUtf8();
          fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
          break;
543
      case Color:
544
          fileData = m_controller ? m_controller->property(QStringLiteral("resource")).toUtf8() : name().toUtf8();
545
          fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
546 547 548 549 550
          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
              /*
551 552 553
               * 1 MB = 1 second per 450 files (or faster)
               * 10 MB = 9 seconds per 450 files (or faster)
               */
554 555 556 557 558 559 560
            if (file.size() > 2000000) {
                fileData = file.read(1000000);
                if (file.seek(file.size() - 1000000))
                    fileData.append(file.readAll());
            } else
                fileData = file.readAll();
            file.close();
561
            if (m_controller) m_controller->setProperty(QStringLiteral("kdenlive:file_size"), QString::number(file.size()));
562 563 564 565 566 567 568
            fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5);
          }
          break;
    }
    if (fileHash.isEmpty()) return QString();
    QString result = fileHash.toHex();
    if (m_controller) {
569
	m_controller->setProperty(QStringLiteral("kdenlive:file_hash"), result);
570
    }
571
    return result;
572
}
573 574 575 576 577 578

double ProjectClip::getOriginalFps() const
{
    if (!m_controller) return 0;
    return m_controller->originalFps();
}
579 580 581

bool ProjectClip::hasProxy() const
{
582 583
    QString proxy = getProducerProperty(QStringLiteral("kdenlive:proxy"));
    if (proxy.isEmpty() || proxy == QLatin1String("-")) return false;
584 585 586
    return true;
}

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

648 649
    if (properties.contains(QStringLiteral("kdenlive:clipname"))) {
        m_name = properties.value(QStringLiteral("kdenlive:clipname"));
650
        refreshPanel = true;
651 652
        bin()->emitItemUpdated(this);
    }
653 654 655 656
    if (refreshPanel) {
        // Some of the clip properties have changed through a command, update properties panel
        emit refreshPropertiesPanel();
    }
657
    if (reload) {
658
        // producer has changed, refresh monitor and thumbnail
659
        reloadProducer(true);
660
        bin()->refreshClip(m_id);
661
    }
662 663 664
    if (!passProperties.isEmpty()) {
        bin()->updateTimelineProducers(m_id, passProperties);
    }
665 666
}

667
void ProjectClip::setJobStatus(int jobType, int status, int progress, const QString &statusMessage)
668
{
669
    m_jobType = (AbstractClipJob::JOBTYPE) jobType;
670
    if (progress > 0) {
671
        if (m_jobProgress == progress) return;
672
        m_jobProgress = progress;
673 674
    }
    else {
675
        m_jobProgress = status;
676 677 678
        if (m_jobType == AbstractClipJob::PROXYJOB && (status == JobAborted || status == JobCrashed)) {
            setProducerProperty(QStringLiteral("kdenlive:proxy"), QStringLiteral("-"));
        }
679 680
        if ((status == JobAborted || status == JobCrashed  || status == JobDone) && !statusMessage.isEmpty()) {
            m_jobMessage = statusMessage;
681
            bin()->emitMessage(statusMessage, 100, OperationCompletedMessage);
682
        }
683 684 685
    }
    bin()->emitItemUpdated(this);
}
686

687 688 689

ClipPropertiesController *ProjectClip::buildProperties(QWidget *parent)
{
690
    ClipPropertiesController *panel = new ClipPropertiesController(bin()->projectTimecode(), m_controller, parent);
691
    connect(this, SIGNAL(refreshPropertiesPanel()), panel, SLOT(slotReloadProperties()));
692
    connect(this, SIGNAL(refreshAnalysisPanel()), panel, SLOT(slotFillAnalysisData()));
693 694 695
    return panel;
}

696 697
void ProjectClip::updateParentInfo(const QString &folderid, const QString &foldername)
{
698
    Q_UNUSED(foldername)
699
    m_controller->setProperty(QStringLiteral("kdenlive:folderid"), folderid);
700 701
}

702 703 704
bool ProjectClip::matches(QString condition)
{
    //TODO
705
    Q_UNUSED(condition)
706 707 708
    return true;
}

709
const QString ProjectClip::codec(bool audioCodec) const
710
{
711 712
    if (!m_controller) return QString();
    return m_controller->codec(audioCodec);
713
}
714

715
bool ProjectClip::rename(const QString &name, int column)
716 717 718
{
    QMap <QString, QString> newProperites;
    QMap <QString, QString> oldProperites;
719 720 721 722 723
    bool edited = false;
    switch (column) {
      case 0:
        if (m_name == name) return false;
        // Rename clip
724 725
        oldProperites.insert(QStringLiteral("kdenlive:clipname"), m_name);
        newProperites.insert(QStringLiteral("kdenlive:clipname"), name);
726 727 728 729 730 731
        m_name = name;
        edited = true;
        break;
      case 2:
        if (m_description == name) return false;
        // Rename clip
732 733 734 735 736 737 738
        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);
        }
739 740 741 742 743 744 745 746
        m_description = name;
        edited = true;
        break;
    }
    if (edited) {
        bin()->slotEditClipCommand(m_id, oldProperites, newProperites);
    }
    return edited;
747 748
}

749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776
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);
777
    return true;
778 779 780 781 782 783 784 785 786 787 788 789 790 791
}

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();
}
792

Vincent Pinon's avatar
Vincent Pinon committed
793
void ProjectClip::addEffect(const ProfileInfo &pInfo, QDomElement &effect)
794
{
795
    m_controller->addEffect(pInfo, effect);
796
    bin()->updateMasterEffect(m_controller);
797
    bin()->emitItemUpdated(this);
798 799
}

800
void ProjectClip::removeEffect(int ix)
801
{
802
    m_controller->removeEffect(ix);
803
    bin()->updateMasterEffect(m_controller);
804 805 806 807 808 809 810
    bin()->emitItemUpdated(this);
}

QVariant ProjectClip::data(DataType type) const
{
    switch (type) {
      case AbstractProjectItem::IconOverlay:
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
811
            return m_controller != NULL ? (m_controller->hasEffects() ? QVariant("kdenlive-track_has_effect") : QVariant()) : QVariant();
812 813 814 815 816
            break;
        default:
	    break;
    }
    return AbstractProjectItem::data(type);
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 849 850 851 852 853 854 855 856 857 858 859 860 861 862
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;
    }
}


863
void ProjectClip::slotExtractImage(QList <int> frames)
864 865 866 867 868 869 870 871 872 873 874 875 876 877
{
    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()
878
{
879
    Mlt::Producer *prod = thumbProducer();
880
    if (prod == NULL || !prod->is_valid()) return;
881
    int fullWidth = (int)((double) 150 * prod->profile()->dar() + 0.5);
882 883
    bool ok = false;
    QDir thumbFolder = bin()->getCacheDir(CacheThumbs, &ok);
884 885 886
    int max = prod->get_length();
    while (!m_requestedThumbs.isEmpty()) {
        m_thumbMutex.lock();
887
        int pos = m_requestedThumbs.takeFirst();
888
        m_thumbMutex.unlock();
889
        if (ok && thumbFolder.exists(hash() + '#' + QString::number(pos) + ".png")) {
890
            emit thumbReady(pos, QImage(thumbFolder.absoluteFilePath(hash() + '#' + QString::number(pos) + ".png")));
891 892
            continue;
        }
893 894 895 896 897 898 899
        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;
        }
900 901 902
	prod->seek(pos);
	Mlt::Frame *frame = prod->get_frame();
	if (frame && frame->is_valid()) {
903 904
            img = KThumb::getFrame(frame, fullWidth, 150);
            bin()->cachePixmap(path, img);
905
            emit thumbReady(pos, img);
906 907 908
        }
        delete frame;
    }
909
}
910

911 912
void ProjectClip::slotExtractSubImage(QList <int> frames)
{
913
    Mlt::Producer *prod = thumbProducer();
914
    if (prod == NULL || !prod->is_valid()) return;
915
    int fullWidth = (int)((double) 150 * prod->profile()->dar() + 0.5);
916 917
    bool ok = false;
    QDir thumbFolder = bin()->getCacheDir(CacheThumbs, &ok);
918
    int max = prod->get_length();
919 920
    for (int i = 0; i < frames.count(); i++) {
        int pos = frames.at(i);
921
        QString path = thumbFolder.absoluteFilePath(hash() + "#" + QString::number(pos) + ".png");
922 923 924 925 926 927