loadjob.cpp 27.4 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
        return nullptr;
    }
132 133
    std::unique_ptr<ProfileInfo> prof(new ProfileParam(xmlProfile.get()));
    if (static_cast<ProfileInfo*>(pCore->getCurrentProfile().get()) == prof.get()) {
134 135
        // 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!
136
        //resource.prepend(QStringLiteral("xml:"));
137 138
    } else {
        // This is currently crashing so I guess we'd better reject it for now
139 140 141
        if (!pCore->getCurrentProfile()->isCompatible(xmlProfile.get())) {
            m_errorMessage.append(i18n("Playlist has a different framerate (%1/%2fps), not recommended.", xmlProfile->frame_rate_num(), xmlProfile->frame_rate_den()));
        }
142 143 144 145
        QString loader = resource;
        loader.prepend(QStringLiteral("consumer:"));
        pCore->getCurrentProfile()->set_explicit(1);
        return std::make_shared<Mlt::Producer>(pCore->getCurrentProfile()->profile(), loader.toUtf8().constData());
146 147
    }
    pCore->getCurrentProfile()->set_explicit(1);
148
    return std::make_shared<Mlt::Producer>(pCore->getCurrentProfile()->profile(), "xml", resource.toUtf8().constData());
149 150
}

Nicolas Carion's avatar
Nicolas Carion committed
151
void LoadJob::checkProfile(const QString &clipId, const QDomElement &xml, const std::shared_ptr<Mlt::Producer> &producer)
152 153
{
    // Check if clip profile matches
154
    QString service = producer->get("mlt_service");
155 156 157
    // Check for image producer
    if (service == QLatin1String("qimage") || service == QLatin1String("pixbuf")) {
        // This is an image, create profile from image size
158
        int width = producer->get_int("meta.media.width");
159 160 161
        if (width % 8 > 0) {
            width += 8 - width % 8;
        }
162
        int height = producer->get_int("meta.media.height");
163
        height += height % 2;
164 165 166 167 168 169 170 171 172
        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();
173
            pCore->currentDoc()->switchProfile(projectProfile, clipId, xml);
174 175 176 177 178 179
        } 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);
180
        blankProfile->from_producer(*producer);
181 182
        std::unique_ptr<ProfileParam> clipProfile(new ProfileParam(blankProfile.get()));
        std::unique_ptr<ProfileParam> projectProfile(new ProfileParam(pCore->getCurrentProfile().get()));
183
        clipProfile->adjustDimensions();
184 185 186 187 188 189
        if (*clipProfile.get() == *projectProfile.get()) {
            if (KdenliveSettings::default_profile().isEmpty()) {
                // Confirm default project format
                KdenliveSettings::setDefault_profile(pCore->getCurrentProfile()->path());
            }
        } else {
190
            // Profiles do not match, propose profile adjustment
191
            pCore->currentDoc()->switchProfile(clipProfile, clipId, xml);
192 193 194 195 196 197
        }
    }
}

void LoadJob::processSlideShow()
{
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
198 199
    int ttl = Xml::getXmlProperty(m_xml, QStringLiteral("ttl")).toInt();
    QString anim = Xml::getXmlProperty(m_xml, QStringLiteral("animation"));
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
    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
218
    QString fade = Xml::getXmlProperty(m_xml, QStringLiteral("fade"));
219 220 221 222 223 224 225
    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
226 227
            QString luma_duration = Xml::getXmlProperty(m_xml, QStringLiteral("luma_duration"));
            QString luma_file = Xml::getXmlProperty(m_xml, QStringLiteral("luma_file"));
228 229 230 231 232
            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
233
                QString softness = Xml::getXmlProperty(m_xml, QStringLiteral("softness"));
234 235 236 237 238 239 240 241
                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
242
    QString crop = Xml::getXmlProperty(m_xml, QStringLiteral("crop"));
243 244 245 246 247 248 249 250 251 252 253 254 255 256
    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;
    }
257
    pCore->getMonitor(Kdenlive::ClipMonitor)->resetPlayOrLoopZone(m_clipId);
258
    m_resource = Xml::getXmlProperty(m_xml, QStringLiteral("resource"));
259
    int duration = 0;
260
    ClipType::ProducerType type = static_cast<ClipType::ProducerType>(m_xml.attribute(QStringLiteral("type")).toInt());
261
    QString service = Xml::getXmlProperty(m_xml, QStringLiteral("mlt_service"));
262
    if (type == ClipType::Unknown) {
263
        type = getTypeForService(service, m_resource);
264 265 266 267 268 269
    }
    switch (type) {
    case ClipType::Color:
        m_producer = loadResource(m_resource, QStringLiteral("color:"));
        break;
    case ClipType::Text:
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
    case ClipType::TextTemplate: {
            QFile txtfile(m_resource);
            QDomDocument txtdoc(QStringLiteral("titledocument"));
            if (txtfile.open(QIODevice::ReadOnly) && txtdoc.setContent(&txtfile)) {
                txtfile.close();
                if (txtdoc.documentElement().hasAttribute(QStringLiteral("duration"))) {
                    duration = txtdoc.documentElement().attribute(QStringLiteral("duration")).toInt();
                } else if (txtdoc.documentElement().hasAttribute(QStringLiteral("out"))) {
                    duration = txtdoc.documentElement().attribute(QStringLiteral("out")).toInt();
                }
            }
            m_producer = loadResource(m_resource, QStringLiteral("kdenlivetitle:"));
            if (duration <= 0) {
                duration = pCore->currentDoc()->getFramePos(KdenliveSettings::title_duration()) - 1;
            }
            m_producer->set("length", duration);
            m_producer->set("kdenlive:duration", duration);
            m_producer->set("out", duration - 1);
        }
289 290 291 292
        break;
    case ClipType::QText:
        m_producer = loadResource(m_resource, QStringLiteral("qtext:"));
        break;
293
    case ClipType::Playlist: {
294
        m_producer = loadPlaylist(m_resource);
295 296 297 298
        if (!m_errorMessage.isEmpty()) {
            QMetaObject::invokeMethod(pCore.get(), "displayBinMessage", Qt::QueuedConnection, Q_ARG(const QString &, m_errorMessage),
                                  Q_ARG(int, (int)KMessageWidget::Warning));
        }
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
        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) {
314
                        qDebug()<<" / / / FIXING OFFSET DATA: "<<offset;
315
                        offset = m_producer->get_playtime() - offset - 1;
316 317 318
                        m_producer->set("out", offset - 1);
                        m_producer->set("length", offset);
                        m_producer->set("kdenlive:duration", offset);
319
                    }
320 321
                } else {
                    qDebug()<<"// NO OFFSET DAT FOUND\n\n";
322 323 324 325 326
                }
            } else {
                qDebug()<<":_______\n______<nEMPTY PLAYLIST\n----";
            }
        }
327
        break;
328
    }
329 330
    case ClipType::SlideShow:
    default:
331 332 333 334 335 336
        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());
        }
337 338 339 340 341 342
        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;
343 344 345
        if (m_producer) {
            m_producer.reset();
        }
346 347
        QMetaObject::invokeMethod(pCore.get(), "displayBinMessage", Qt::QueuedConnection, Q_ARG(const QString &, i18n("Cannot open file %1", m_resource)),
                                  Q_ARG(int, (int)KMessageWidget::Warning));
348 349 350 351 352
        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"));
353 354
    if (clipName.isEmpty()) {
        clipName = QFileInfo(Xml::getXmlProperty(m_xml, QStringLiteral("kdenlive:originalurl"))).fileName();
355
    }
356
    m_producer->set("kdenlive:clipname", clipName.toUtf8().constData());
357 358 359 360
    QString groupId = Xml::getXmlProperty(m_xml, QStringLiteral("kdenlive:folderid"));
    if (!groupId.isEmpty()) {
        m_producer->set("kdenlive:folderid", groupId.toUtf8().constData());
    }
361
    int clipOut = 0;
362 363 364 365
    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
366 367
    if (duration == 0 && (type == ClipType::Color || type == ClipType::Text || type == ClipType::TextTemplate || type == ClipType::QText || type == ClipType::Image ||
        type == ClipType::SlideShow)) {
368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391
        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;
        }
392 393 394
        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
395
            m_producer->set("kdenlive:duration", m_producer->frames_to_time(kdenlive_duration, mlt_time_clock));
396 397 398
        } else {
            m_producer->set("kdenlive:duration", m_producer->get("length"));
        }
399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424
    }
    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());
    }
    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;
425
        if (originalFps > 0 && !qFuzzyCompare(originalFps, pCore->getCurrentFps())) {
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 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486
            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
487
    auto addStream = [this, parentId = std::move(parent)](int vindex, int aindex, Fun &undo, Fun &redo) {
488
        auto clone = ProjectClip::cloneProducer(m_producer);
489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504
        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);
            }
505
            addStream(vindex, aindex, undo, redo);
506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531
        }
        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;
532
    QList<QComboBox *> comboList;
533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549
    // 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) {
550
            auto *cb = new QComboBox(mainWidget);
551 552 553 554 555 556 557 558 559 560
            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);
    }
561
    m_producer->set("video_index", m_video_list.at(0));
562 563 564 565 566 567 568 569 570 571 572 573
    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();
                }
574
                addStream(vindex, aindex, undo, redo);
575 576
            }
        }
577
        pCore->pushUndo(undo, redo, i18n("Add additional streams for clip"));
578 579 580
    }
}

581
bool LoadJob::commitResult(Fun &undo, Fun &redo)
582 583 584 585 586 587 588 589
{
    qDebug() << "################### loadjob COMMIT";
    Q_ASSERT(!m_resultConsumed);
    if (!m_done) {
        qDebug() << "ERROR: Trying to consume invalid results";
        return false;
    }
    m_resultConsumed = true;
590
    auto m_binClip = pCore->projectItemModel()->getClipByBinID(m_clipId);
591
    if (!m_successful) {
592
        // TODO: Deleting cannot happen at this stage or we endup in a mutex lock
593
        pCore->projectItemModel()->requestBinClipDeletion(m_binClip, undo, redo);
594 595
        return false;
    }
596
    if (m_xml.hasAttribute(QStringLiteral("_checkProfile")) && m_producer->get_int("video_index") > -1) {
597
        checkProfile(m_clipId, m_xml, m_producer);
598 599 600 601 602 603
    }
    if (m_video_list.size() > 1) {
        processMultiStream();
    }

    // note that the image is moved into lambda, it won't be available from this class anymore
604
    auto operation = [clip = m_binClip, prod = std::move(m_producer)]() {
605 606 607
        clip->setProducer(prod, true);
        return true;
    };
Nicolas Carion's avatar
Nicolas Carion committed
608
    auto reverse = []() {
609 610 611 612 613
        // This is probably not invertible, leave as is.
        return true;
    };
    bool ok = operation();
    if (ok) {
614
        m_readyCallBack();
Jean-Baptiste Mardelle's avatar
cleanup  
Jean-Baptiste Mardelle committed
615
        if (pCore->projectItemModel()->clipsCount() == 1) {
616 617 618
            // Always select first added clip
            pCore->selectBinClip(m_clipId);
        }
619 620 621 622
        UPDATE_UNDO_REDO_NOLOCK(operation, reverse, undo, redo);
    }
    return ok;
}