loadjob.cpp 26.3 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
25
26
27
28
29
30
31
32
33
/***************************************************************************
 *   Copyright (C) 2017 by Nicolas Carion                                  *
 *   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 "loadjob.hpp"
#include "bin/projectclip.h"
#include "bin/projectfolder.h"
#include "bin/projectitemmodel.h"
#include "core.h"
#include "doc/kdenlivedoc.h"
#include "doc/kthumb.h"
#include "kdenlivesettings.h"
#include "klocalizedstring.h"
#include "macros.hpp"
#include "profiles/profilemodel.hpp"
#include "project/dialogs/slideshowclip.h"
34
#include "monitor/monitor.h"
35
#include "xml/xml.hpp"
36
#include <KMessageWidget>
37
38
39
40
41
#include <QMimeDatabase>
#include <QWidget>
#include <mlt++/MltProducer.h>
#include <mlt++/MltProfile.h>

42
LoadJob::LoadJob(const QString &binId, const QDomElement &xml, const std::function<void()> &readyCallBack)
43
44
    : AbstractClipJob(LOADJOB, binId)
    , m_xml(xml)
45
    , m_readyCallBack(readyCallBack)
46
47
48
49
50
51
52
53
54
{
}

const QString LoadJob::getDescription() const
{
    return i18n("Loading clip %1", m_clipId);
}

namespace {
55
ClipType::ProducerType getTypeForService(const QString &id, const QString &path)
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
{
    if (id.isEmpty()) {
        QString ext = path.section(QLatin1Char('.'), -1);
        if (ext == QLatin1String("mlt") || ext == QLatin1String("kdenlive")) {
            return ClipType::Playlist;
        }
        return ClipType::Unknown;
    }
    if (id == QLatin1String("color") || id == QLatin1String("colour")) {
        return ClipType::Color;
    }
    if (id == QLatin1String("kdenlivetitle")) {
        return ClipType::Text;
    }
    if (id == QLatin1String("qtext")) {
        return ClipType::QText;
    }
    if (id == QLatin1String("xml") || id == QLatin1String("consumer")) {
        return ClipType::Playlist;
    }
    if (id == QLatin1String("webvfx")) {
        return ClipType::WebVfx;
    }
    return ClipType::Unknown;
}

// Read the properties of the xml and pass them to the producer. Note that some properties like resource are ignored
Nicolas Carion's avatar
Nicolas Carion committed
83
void processProducerProperties(const std::shared_ptr<Mlt::Producer> &prod, const QDomElement &xml)
84
85
86
87
88
{
    // TODO: there is some duplication with clipcontroller > updateproducer that also copies properties
    QString value;
    QStringList internalProperties;
    internalProperties << QStringLiteral("bypassDuplicate") << QStringLiteral("resource") << QStringLiteral("mlt_service") << QStringLiteral("audio_index")
89
                       << QStringLiteral("video_index") << QStringLiteral("mlt_type") << QStringLiteral("length");
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
    QDomNodeList props;

    if (xml.tagName() == QLatin1String("producer")) {
        props = xml.childNodes();
    } else {
        props = xml.firstChildElement(QStringLiteral("producer")).childNodes();
    }
    for (int i = 0; i < props.count(); ++i) {
        if (props.at(i).toElement().tagName() != QStringLiteral("property")) {
            continue;
        }
        QString propertyName = props.at(i).toElement().attribute(QStringLiteral("name"));
        if (!internalProperties.contains(propertyName) && !propertyName.startsWith(QLatin1Char('_'))) {
            value = props.at(i).firstChild().nodeValue();
            if (propertyName.startsWith(QLatin1String("kdenlive-force."))) {
                // this is a special forced property, pass it
                propertyName.remove(0, 15);
            }
            prod->set(propertyName.toUtf8().constData(), value.toUtf8().constData());
        }
    }
}
} // namespace

// static
std::shared_ptr<Mlt::Producer> LoadJob::loadResource(QString &resource, const QString &type)
{
    if (!resource.startsWith(type)) {
        resource.prepend(type);
    }
    return std::make_shared<Mlt::Producer>(pCore->getCurrentProfile()->profile(), nullptr, resource.toUtf8().constData());
}

std::shared_ptr<Mlt::Producer> LoadJob::loadPlaylist(QString &resource)
{
    std::unique_ptr<Mlt::Profile> xmlProfile(new Mlt::Profile());
    xmlProfile->set_explicit(0);
    std::unique_ptr<Mlt::Producer> producer(new Mlt::Producer(*xmlProfile, "xml", resource.toUtf8().constData()));
    if (!producer->is_valid()) {
Nicolas Carion's avatar
Nicolas Carion committed
129
        qDebug() << "////// ERROR, CANNOT LOAD SELECTED PLAYLIST: " << resource;
130
131
132
133
134
        return nullptr;
    }
    if (pCore->getCurrentProfile()->isCompatible(xmlProfile.get())) {
        // We can use the "xml" producer since profile is the same (using it with different profiles corrupts the project.
        // Beware that "consumer" currently crashes on audio mixes!
135
        //resource.prepend(QStringLiteral("xml:"));
136
137
    } else {
        // This is currently crashing so I guess we'd better reject it for now
138
139
140
141
142
        m_errorMessage.append(i18n("Playlist has a different framerate (%1/%2fps), not recommended.", xmlProfile->frame_rate_num(), xmlProfile->frame_rate_den()));
        QString loader = resource;
        loader.prepend(QStringLiteral("consumer:"));
        pCore->getCurrentProfile()->set_explicit(1);
        return std::make_shared<Mlt::Producer>(pCore->getCurrentProfile()->profile(), loader.toUtf8().constData());
143
144
    }
    pCore->getCurrentProfile()->set_explicit(1);
145
    return std::make_shared<Mlt::Producer>(pCore->getCurrentProfile()->profile(), "xml", resource.toUtf8().constData());
146
147
}

Nicolas Carion's avatar
Nicolas Carion committed
148
void LoadJob::checkProfile(const QString &clipId, const QDomElement &xml, const std::shared_ptr<Mlt::Producer> &producer)
149
150
{
    // Check if clip profile matches
151
    QString service = producer->get("mlt_service");
152
153
154
    // Check for image producer
    if (service == QLatin1String("qimage") || service == QLatin1String("pixbuf")) {
        // This is an image, create profile from image size
155
        int width = producer->get_int("meta.media.width");
156
157
158
        if (width % 8 > 0) {
            width += 8 - width % 8;
        }
159
        int height = producer->get_int("meta.media.height");
160
        height += height % 2;
161
162
163
164
165
166
167
168
169
        if (width > 100 && height > 100) {
            std::unique_ptr<ProfileParam> projectProfile(new ProfileParam(pCore->getCurrentProfile().get()));
            projectProfile->m_width = width;
            projectProfile->m_height = height;
            projectProfile->m_sample_aspect_num = 1;
            projectProfile->m_sample_aspect_den = 1;
            projectProfile->m_display_aspect_num = width;
            projectProfile->m_display_aspect_den = height;
            projectProfile->m_description.clear();
170
            pCore->currentDoc()->switchProfile(projectProfile, clipId, xml);
171
172
173
174
175
176
        } else {
            // Very small image, we probably don't want to use this as profile
        }
    } else if (service.contains(QStringLiteral("avformat"))) {
        std::unique_ptr<Mlt::Profile> blankProfile(new Mlt::Profile());
        blankProfile->set_explicit(0);
177
        blankProfile->from_producer(*producer);
178
179
        std::unique_ptr<ProfileParam> clipProfile(new ProfileParam(blankProfile.get()));
        std::unique_ptr<ProfileParam> projectProfile(new ProfileParam(pCore->getCurrentProfile().get()));
180
        clipProfile->adjustDimensions();
181
182
183
184
185
186
        if (*clipProfile.get() == *projectProfile.get()) {
            if (KdenliveSettings::default_profile().isEmpty()) {
                // Confirm default project format
                KdenliveSettings::setDefault_profile(pCore->getCurrentProfile()->path());
            }
        } else {
187
            // Profiles do not match, propose profile adjustment
188
            pCore->currentDoc()->switchProfile(clipProfile, clipId, xml);
189
190
191
192
193
194
        }
    }
}

void LoadJob::processSlideShow()
{
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
195
196
    int ttl = Xml::getXmlProperty(m_xml, QStringLiteral("ttl")).toInt();
    QString anim = Xml::getXmlProperty(m_xml, QStringLiteral("animation"));
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
    if (!anim.isEmpty()) {
        auto *filter = new Mlt::Filter(pCore->getCurrentProfile()->profile(), "affine");
        if ((filter != nullptr) && filter->is_valid()) {
            int cycle = ttl;
            QString geometry = SlideshowClip::animationToGeometry(anim, cycle);
            if (!geometry.isEmpty()) {
                if (anim.contains(QStringLiteral("low-pass"))) {
                    auto *blur = new Mlt::Filter(pCore->getCurrentProfile()->profile(), "boxblur");
                    if ((blur != nullptr) && blur->is_valid()) {
                        m_producer->attach(*blur);
                    }
                }
                filter->set("transition.geometry", geometry.toUtf8().data());
                filter->set("transition.cycle", cycle);
                m_producer->attach(*filter);
            }
        }
    }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
215
    QString fade = Xml::getXmlProperty(m_xml, QStringLiteral("fade"));
216
217
218
219
220
221
222
    if (fade == QLatin1String("1")) {
        // user wants a fade effect to slideshow
        auto *filter = new Mlt::Filter(pCore->getCurrentProfile()->profile(), "luma");
        if ((filter != nullptr) && filter->is_valid()) {
            if (ttl != 0) {
                filter->set("cycle", ttl);
            }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
223
224
            QString luma_duration = Xml::getXmlProperty(m_xml, QStringLiteral("luma_duration"));
            QString luma_file = Xml::getXmlProperty(m_xml, QStringLiteral("luma_file"));
225
226
227
228
229
            if (!luma_duration.isEmpty()) {
                filter->set("duration", luma_duration.toInt());
            }
            if (!luma_file.isEmpty()) {
                filter->set("luma.resource", luma_file.toUtf8().constData());
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
230
                QString softness = Xml::getXmlProperty(m_xml, QStringLiteral("softness"));
231
232
233
234
235
236
237
238
                if (!softness.isEmpty()) {
                    int soft = softness.toInt();
                    filter->set("luma.softness", (double)soft / 100.0);
                }
            }
            m_producer->attach(*filter);
        }
    }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
239
    QString crop = Xml::getXmlProperty(m_xml, QStringLiteral("crop"));
240
241
242
243
244
245
246
247
248
249
250
251
252
253
    if (crop == QLatin1String("1")) {
        // user wants to center crop the slides
        auto *filter = new Mlt::Filter(pCore->getCurrentProfile()->profile(), "crop");
        if ((filter != nullptr) && filter->is_valid()) {
            filter->set("center", 1);
            m_producer->attach(*filter);
        }
    }
}
bool LoadJob::startJob()
{
    if (m_done) {
        return true;
    }
254
    pCore->getMonitor(Kdenlive::ClipMonitor)->resetPlayOrLoopZone(m_clipId);
255
    m_resource = Xml::getXmlProperty(m_xml, QStringLiteral("resource"));
256
    ClipType::ProducerType type = static_cast<ClipType::ProducerType>(m_xml.attribute(QStringLiteral("type")).toInt());
257
    QString service = Xml::getXmlProperty(m_xml, QStringLiteral("mlt_service"));
258
    if (type == ClipType::Unknown) {
259
        type = getTypeForService(service, m_resource);
260
261
262
263
264
265
266
267
268
269
270
271
    }
    switch (type) {
    case ClipType::Color:
        m_producer = loadResource(m_resource, QStringLiteral("color:"));
        break;
    case ClipType::Text:
    case ClipType::TextTemplate:
        m_producer = loadResource(m_resource, QStringLiteral("kdenlivetitle:"));
        break;
    case ClipType::QText:
        m_producer = loadResource(m_resource, QStringLiteral("qtext:"));
        break;
272
    case ClipType::Playlist: {
273
        m_producer = loadPlaylist(m_resource);
274
275
276
277
        if (!m_errorMessage.isEmpty()) {
            QMetaObject::invokeMethod(pCore.get(), "displayBinMessage", Qt::QueuedConnection, Q_ARG(const QString &, m_errorMessage),
                                  Q_ARG(int, (int)KMessageWidget::Warning));
        }
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
        if (m_resource.endsWith(QLatin1String(".kdenlive"))) {
            QFile f(m_resource);
            QDomDocument doc;
            doc.setContent(&f, false);
            f.close();
            QDomElement pl = doc.documentElement().firstChildElement(QStringLiteral("playlist"));
            if (!pl.isNull()) {
                QString offsetData = Xml::getXmlProperty(pl, QStringLiteral("kdenlive:docproperties.seekOffset"));
                if (offsetData.isEmpty() && Xml::getXmlProperty(pl, QStringLiteral("kdenlive:docproperties.version")) == QLatin1String("0.98")) {
                    offsetData = QStringLiteral("30000");
                }
                if (!offsetData.isEmpty()) {
                    bool ok = false;
                    int offset = offsetData.toInt(&ok);
                    if (ok) {
293
                        qDebug()<<" / / / FIXING OFFSET DATA: "<<offset;
294
                        offset = m_producer->get_playtime() - offset - 1;
295
296
297
                        m_producer->set("out", offset - 1);
                        m_producer->set("length", offset);
                        m_producer->set("kdenlive:duration", offset);
298
                    }
299
300
                } else {
                    qDebug()<<"// NO OFFSET DAT FOUND\n\n";
301
302
303
304
305
                }
            } else {
                qDebug()<<":_______\n______<nEMPTY PLAYLIST\n----";
            }
        }
306
        break;
307
    }
308
309
    case ClipType::SlideShow:
    default:
310
311
312
313
314
315
        if (!service.isEmpty()) {
            service.append(QChar(':'));
            m_producer = loadResource(m_resource, service);
        } else {
            m_producer = std::make_shared<Mlt::Producer>(pCore->getCurrentProfile()->profile(), nullptr, m_resource.toUtf8().constData());
        }
316
317
318
319
320
321
        break;
    }
    if (!m_producer || m_producer->is_blank() || !m_producer->is_valid()) {
        qCDebug(KDENLIVE_LOG) << " / / / / / / / / ERROR / / / / // CANNOT LOAD PRODUCER: " << m_resource;
        m_done = true;
        m_successful = false;
322
323
324
        if (m_producer) {
            m_producer.reset();
        }
325
326
        QMetaObject::invokeMethod(pCore.get(), "displayBinMessage", Qt::QueuedConnection, Q_ARG(const QString &, i18n("Cannot open file %1", m_resource)),
                                  Q_ARG(int, (int)KMessageWidget::Warning));
327
328
329
330
331
        m_errorMessage.append(i18n("ERROR: Could not load clip %1: producer is invalid", m_resource));
        return false;
    }
    processProducerProperties(m_producer, m_xml);
    QString clipName = Xml::getXmlProperty(m_xml, QStringLiteral("kdenlive:clipname"));
332
333
    if (clipName.isEmpty()) {
        clipName = QFileInfo(Xml::getXmlProperty(m_xml, QStringLiteral("kdenlive:originalurl"))).fileName();
334
    }
335
    m_producer->set("kdenlive:clipname", clipName.toUtf8().constData());
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
    QString groupId = Xml::getXmlProperty(m_xml, QStringLiteral("kdenlive:folderid"));
    if (!groupId.isEmpty()) {
        m_producer->set("kdenlive:folderid", groupId.toUtf8().constData());
    }
    int clipOut = 0, duration = 0;
    if (m_xml.hasAttribute(QStringLiteral("out"))) {
        clipOut = m_xml.attribute(QStringLiteral("out")).toInt();
    }
    // setup length here as otherwise default length (currently 15000 frames in MLT) will be taken even if outpoint is larger
    if (type == ClipType::Color || type == ClipType::Text || type == ClipType::TextTemplate || type == ClipType::QText || type == ClipType::Image ||
        type == ClipType::SlideShow) {
        int length;
        if (m_xml.hasAttribute(QStringLiteral("length"))) {
            length = m_xml.attribute(QStringLiteral("length")).toInt();
            clipOut = qMax(1, length - 1);
        } else {
            length = Xml::getXmlProperty(m_xml, QStringLiteral("length")).toInt();
            clipOut -= m_xml.attribute(QStringLiteral("in")).toInt();
            if (length < clipOut) {
                length = clipOut == 1 ? 1 : clipOut + 1;
            }
        }
        // Pass duration if it was forced
        if (m_xml.hasAttribute(QStringLiteral("duration"))) {
            duration = m_xml.attribute(QStringLiteral("duration")).toInt();
            if (length < duration) {
                length = duration;
                if (clipOut > 0) {
                    clipOut = length - 1;
                }
            }
        }
        if (duration == 0) {
            duration = length;
        }
371
372
373
        m_producer->set("length", m_producer->frames_to_time(length, mlt_time_clock));
        int kdenlive_duration = m_producer->time_to_frames(Xml::getXmlProperty(m_xml, QStringLiteral("kdenlive:duration")).toUtf8().constData());
        if (kdenlive_duration > 0) {
Nicolas Carion's avatar
Nicolas Carion committed
374
            m_producer->set("kdenlive:duration", m_producer->frames_to_time(kdenlive_duration, mlt_time_clock));
375
376
377
        } else {
            m_producer->set("kdenlive:duration", m_producer->get("length"));
        }
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
    }
    if (clipOut > 0) {
        m_producer->set_in_and_out(m_xml.attribute(QStringLiteral("in")).toInt(), clipOut);
    }

    if (m_xml.hasAttribute(QStringLiteral("templatetext"))) {
        m_producer->set("templatetext", m_xml.attribute(QStringLiteral("templatetext")).toUtf8().constData());
    }
    duration = duration > 0 ? duration : m_producer->get_playtime();
    if (type == ClipType::SlideShow) {
        processSlideShow();
    }

    int vindex = -1;
    double fps = -1;
    const QString mltService = m_producer->get("mlt_service");
    if (mltService == QLatin1String("xml") || mltService == QLatin1String("consumer")) {
        // MLT playlist, create producer with blank profile to get real profile info
        QString tmpPath = m_resource;
        if (tmpPath.startsWith(QLatin1String("consumer:"))) {
            tmpPath = "xml:" + tmpPath.section(QLatin1Char(':'), 1);
        }
        Mlt::Profile original_profile;
        std::unique_ptr<Mlt::Producer> tmpProd(new Mlt::Producer(original_profile, nullptr, tmpPath.toUtf8().constData()));
        original_profile.set_explicit(1);
        double originalFps = original_profile.fps();
        fps = originalFps;
405
        if (originalFps > 0 && !qFuzzyCompare(originalFps, pCore->getCurrentFps())) {
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
            int originalLength = tmpProd->get_length();
            int fixedLength = (int)(originalLength * pCore->getCurrentFps() / originalFps);
            m_producer->set("length", fixedLength);
            m_producer->set("out", fixedLength - 1);
        }
    } else if (mltService == QLatin1String("avformat")) {
        // check if there are multiple streams
        vindex = m_producer->get_int("video_index");
        // List streams
        int streams = m_producer->get_int("meta.media.nb_streams");
        m_audio_list.clear();
        m_video_list.clear();
        for (int i = 0; i < streams; ++i) {
            QByteArray propertyName = QStringLiteral("meta.media.%1.stream.type").arg(i).toLocal8Bit();
            QString stype = m_producer->get(propertyName.data());
            if (stype == QLatin1String("audio")) {
                m_audio_list.append(i);
            } else if (stype == QLatin1String("video")) {
                m_video_list.append(i);
            }
        }

        if (vindex > -1) {
            char property[200];
            snprintf(property, sizeof(property), "meta.media.%d.stream.frame_rate", vindex);
            fps = m_producer->get_double(property);
        }

        if (fps <= 0) {
            if (m_producer->get_double("meta.media.frame_rate_den") > 0) {
                fps = m_producer->get_double("meta.media.frame_rate_num") / m_producer->get_double("meta.media.frame_rate_den");
            } else {
                fps = m_producer->get_double("source_fps");
            }
        }
    }
    if (fps <= 0 && type == ClipType::Unknown) {
        // something wrong, maybe audio file with embedded image
        QMimeDatabase db;
        QString mime = db.mimeTypeForFile(m_resource).name();
        if (mime.startsWith(QLatin1String("audio"))) {
            m_producer->set("video_index", -1);
            vindex = -1;
        }
    }
    m_done = m_successful = true;
    return true;
}

void LoadJob::processMultiStream()
{
    auto m_binClip = pCore->projectItemModel()->getClipByBinID(m_clipId);

    // We retrieve the folder containing our clip, because we will set the other streams in the same
    auto parent = pCore->projectItemModel()->getRootFolder()->clipId();
    if (auto ptr = m_binClip->parentItem().lock()) {
        parent = std::static_pointer_cast<AbstractProjectItem>(ptr)->clipId();
    } else {
        qDebug() << "Warning, something went wrong while accessing parent of bin clip";
    }
    // This helper lambda request addition of a given stream
467
    auto addStream = [this, parentId = std::move(parent)](int vindex, int aindex, Fun &undo, Fun &redo) {
468
        auto clone = ProjectClip::cloneProducer(m_producer);
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
        clone->set("video_index", vindex);
        clone->set("audio_index", aindex);
        QString id;

        pCore->projectItemModel()->requestAddBinClip(id, clone, parentId, undo, redo);
    };
    Fun undo = []() { return true; };
    Fun redo = []() { return true; };

    if (KdenliveSettings::automultistreams()) {
        for (int i = 1; i < m_video_list.count(); ++i) {
            int vindex = m_video_list.at(i);
            int aindex = 0;
            if (i <= m_audio_list.count() - 1) {
                aindex = m_audio_list.at(i);
            }
485
            addStream(vindex, aindex, undo, redo);
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
        }
        return;
    }

    int width = 60.0 * pCore->getCurrentDar();
    if (width % 2 == 1) {
        width++;
    }

    QScopedPointer<QDialog> dialog(new QDialog(qApp->activeWindow()));
    dialog->setWindowTitle(QStringLiteral("Multi Stream Clip"));
    QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
    QWidget *mainWidget = new QWidget(dialog.data());
    auto *mainLayout = new QVBoxLayout;
    dialog->setLayout(mainLayout);
    mainLayout->addWidget(mainWidget);
    QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
    okButton->setDefault(true);
    okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
    dialog->connect(buttonBox, &QDialogButtonBox::accepted, dialog.data(), &QDialog::accept);
    dialog->connect(buttonBox, &QDialogButtonBox::rejected, dialog.data(), &QDialog::reject);
    okButton->setText(i18n("Import selected clips"));

    QLabel *lab1 = new QLabel(i18n("Additional streams for clip\n %1", m_resource), mainWidget);
    mainLayout->addWidget(lab1);
    QList<QGroupBox *> groupList;
512
    QList<QComboBox *> comboList;
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
    // We start loading the list at 1, video index 0 should already be loaded
    for (int j = 1; j < m_video_list.count(); ++j) {
        m_producer->set("video_index", m_video_list.at(j));
        // TODO this keyframe should be cached
        QImage thumb = KThumb::getFrame(m_producer.get(), 0, width, 60);
        QGroupBox *streamFrame = new QGroupBox(i18n("Video stream %1", m_video_list.at(j)), mainWidget);
        mainLayout->addWidget(streamFrame);
        streamFrame->setProperty("vindex", m_video_list.at(j));
        groupList << streamFrame;
        streamFrame->setCheckable(true);
        streamFrame->setChecked(true);
        auto *vh = new QVBoxLayout(streamFrame);
        QLabel *iconLabel = new QLabel(mainWidget);
        mainLayout->addWidget(iconLabel);
        iconLabel->setPixmap(QPixmap::fromImage(thumb));
        vh->addWidget(iconLabel);
        if (m_audio_list.count() > 1) {
530
            auto *cb = new QComboBox(mainWidget);
531
532
533
534
535
536
537
538
539
540
            mainLayout->addWidget(cb);
            for (int k = 0; k < m_audio_list.count(); ++k) {
                cb->addItem(i18n("Audio stream %1", m_audio_list.at(k)), m_audio_list.at(k));
            }
            comboList << cb;
            cb->setCurrentIndex(qMin(j, m_audio_list.count() - 1));
            vh->addWidget(cb);
        }
        mainLayout->addWidget(streamFrame);
    }
541
    m_producer->set("video_index", m_video_list.at(0));
542
543
544
545
546
547
548
549
550
551
552
553
    mainLayout->addWidget(buttonBox);
    if (dialog->exec() == QDialog::Accepted) {
        // import selected streams
        for (int i = 0; i < groupList.count(); ++i) {
            if (groupList.at(i)->isChecked()) {
                int vindex = groupList.at(i)->property("vindex").toInt();
                int ax = qMin(i, comboList.size() - 1);
                int aindex = -1;
                if (ax >= 0) {
                    // only check audio index if we have several audio streams
                    aindex = comboList.at(ax)->itemData(comboList.at(ax)->currentIndex()).toInt();
                }
554
                addStream(vindex, aindex, undo, redo);
555
556
            }
        }
557
        pCore->pushUndo(undo, redo, i18n("Add additional streams for clip"));
558
559
560
    }
}

561
bool LoadJob::commitResult(Fun &undo, Fun &redo)
562
563
564
565
566
567
568
569
{
    qDebug() << "################### loadjob COMMIT";
    Q_ASSERT(!m_resultConsumed);
    if (!m_done) {
        qDebug() << "ERROR: Trying to consume invalid results";
        return false;
    }
    m_resultConsumed = true;
570
    auto m_binClip = pCore->projectItemModel()->getClipByBinID(m_clipId);
571
    if (!m_successful) {
572
        // TODO: Deleting cannot happen at this stage or we endup in a mutex lock
573
        pCore->projectItemModel()->requestBinClipDeletion(m_binClip, undo, redo);
574
575
        return false;
    }
576
    if (m_xml.hasAttribute(QStringLiteral("_checkProfile")) && m_producer->get_int("video_index") > -1) {
577
        checkProfile(m_clipId, m_xml, m_producer);
578
579
580
581
582
583
    }
    if (m_video_list.size() > 1) {
        processMultiStream();
    }

    // note that the image is moved into lambda, it won't be available from this class anymore
584
    auto operation = [clip = m_binClip, prod = std::move(m_producer)]() {
585
586
587
        clip->setProducer(prod, true);
        return true;
    };
Nicolas Carion's avatar
Nicolas Carion committed
588
    auto reverse = []() {
589
590
591
592
593
        // This is probably not invertible, leave as is.
        return true;
    };
    bool ok = operation();
    if (ok) {
594
        m_readyCallBack();
Jean-Baptiste Mardelle's avatar
cleanup    
Jean-Baptiste Mardelle committed
595
        if (pCore->projectItemModel()->clipsCount() == 1) {
596
597
598
            // Always select first added clip
            pCore->selectBinClip(m_clipId);
        }
599
600
601
602
        UPDATE_UNDO_REDO_NOLOCK(operation, reverse, undo, redo);
    }
    return ok;
}