track.cpp 32.4 KB
Newer Older
1 2
/*
 * Kdenlive timeline track handling MLT playlist
3 4 5
 * Copyright 2015 Kdenlive team <kdenlive@kde.org>
 * Author: Vincent Pinon <vpinon@kde.org>
 *
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 * 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/>.
 * 
 */

24
#include "track.h"
25
#include "headertrack.h"
26
#include "kdenlivesettings.h"
27
#include "clip.h"
28 29
#include "effectmanager.h"

30
#include <QtGlobal>
31
#include <QDebug>
32 33
#include <math.h>

34
Track::Track(int index, const QList<QAction *> &actions, Mlt::Playlist &playlist, TrackType type, QWidget *parent) :
35
    effectsList(EffectsList(true)),
36
    type(type),
37
    trackHeader(NULL),
38
    m_index(index),
39
    m_playlist(playlist)
40
{
41 42 43 44
    QString playlist_name = playlist.get("id");
    if (playlist_name != "black_track") {
        trackHeader = new HeaderTrack(info(), actions, this, parent);
    }
45 46 47 48
}

Track::~Track()
{
49
    if (trackHeader) trackHeader->deleteLater();
50 51 52 53 54 55 56 57 58 59 60
}

// members access

Mlt::Playlist & Track::playlist()
{
    return m_playlist;
}

qreal Track::fps()
{
61
    return m_playlist.get_fps();
62 63 64 65
}

int Track::frame(qreal t)
{
66
    return round(t * fps());
67
}
68 69

qreal Track::length() {
70
    return m_playlist.get_playtime() / fps();
71 72 73
}

// basic clip operations
74
bool Track::add(qreal t, Mlt::Producer *parent, qreal tcut, qreal dtcut, PlaylistState::ClipState state, bool duplicate, TimelineMode::EditMode mode)
75
{
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
76 77 78 79
    Mlt::Producer *cut = NULL;
    if (parent == NULL || !parent->is_valid()) {
        return false;
    }
80 81 82 83 84 85 86
    if (state == PlaylistState::Disabled) {
        QScopedPointer<Mlt::Producer> prodCopy(Clip(*parent).clone());
        prodCopy->set("video_index", -1);
        prodCopy->set("audio_index", -1);
        prodCopy->set("kdenlive:binid", parent->get("id"));
        cut = prodCopy->cut(frame(tcut), frame(dtcut) - 1);
    } else if (duplicate && state != PlaylistState::VideoOnly) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
87
        QScopedPointer<Mlt::Producer> newProd(clipProducer(parent, state));
88
        cut = newProd->cut(frame(tcut), frame(dtcut) - 1);
89 90
    }
    else {
91
        cut = parent->cut(frame(tcut), frame(dtcut) - 1);
92
    }
93 94 95
    if (parent->is_cut()) {
        Clip(*cut).addEffects(*parent);
    }
96
    m_playlist.lock();
97
    bool result = doAdd(t, cut, mode);
98
    m_playlist.unlock();
99
    delete cut;
100
    return result;
101 102
}

103
bool Track::doAdd(qreal t, Mlt::Producer *cut, TimelineMode::EditMode mode)
104
{
Vincent Pinon's avatar
Vincent Pinon committed
105 106 107
    int pos = frame(t);
    int len = cut->get_out() - cut->get_in() + 1;
    if (pos < m_playlist.get_playtime() && mode > 0) {
108
        if (mode == TimelineMode::OverwriteEdit) {
Vincent Pinon's avatar
Vincent Pinon committed
109
            m_playlist.remove_region(pos, len);
110
        } else if (mode == TimelineMode::InsertEdit) {
Vincent Pinon's avatar
Vincent Pinon committed
111 112
            m_playlist.split_at(pos);
        }
113
        //m_playlist.insert_blank(m_playlist.get_clip_index_at(pos), len);
Vincent Pinon's avatar
Vincent Pinon committed
114 115
    }
    m_playlist.consolidate_blanks();
116
    if (m_playlist.insert_at(pos, cut, 1) == m_playlist.count() - 1) {
Vincent Pinon's avatar
Vincent Pinon committed
117
        emit newTrackDuration(m_playlist.get_playtime());
118
    }
Vincent Pinon's avatar
Vincent Pinon committed
119 120 121
    return true;
}

122
bool Track::move(qreal start, qreal end, TimelineMode::EditMode mode)
123 124 125
{
    int pos = frame(start);
    m_playlist.lock();
126
    int clipIndex = m_playlist.get_clip_index_at(pos);
127 128 129 130
    bool durationChanged = false;
    if (clipIndex == m_playlist.count() - 1) {
	durationChanged = true;
    }
131
    QScopedPointer <Mlt::Producer> clipProducer(m_playlist.replace_with_blank(clipIndex));
132 133 134 135 136 137
    if (!clipProducer || clipProducer->is_blank()) {
        qDebug() << "// Cannot get clip at index: "<<clipIndex<<" / "<< start;
        m_playlist.unlock();
        return false;
    }
    m_playlist.consolidate_blanks();
138 139 140 141
    if (end >= m_playlist.get_playtime()) {
	// Clip is inserted at the end of track, duration change event handled in doAdd()
	durationChanged = false;
    }
142
    bool result = doAdd(end, clipProducer.data(), mode);
143
    m_playlist.unlock();
144
    if (durationChanged) {
145
	emit newTrackDuration(m_playlist.get_playtime());
146
    }
147 148 149
    return result;
}

150 151 152 153 154 155 156 157 158
bool Track::isLastClip(qreal t)
{
    int clipIndex = m_playlist.get_clip_index_at(frame(t));
    if (clipIndex >= m_playlist.count() - 1) {
	return true;
    }
    return false;
}

159 160
bool Track::del(qreal t)
{
161
    m_playlist.lock();
162
    bool durationChanged = false;
163 164
    int pos = frame(t);
    int ix = m_playlist.get_clip_index_at(pos);
165 166 167 168
    if (ix == m_playlist.count() - 1) {
	durationChanged = true;
    }
    Mlt::Producer *clip = m_playlist.replace_with_blank(ix);
169
    if (clip) {
170
        delete clip;
171
    }
172
    else {
173
        qWarning("Error deleting clip at %d, tk: %d", pos, m_index);
174
        m_playlist.unlock();
175 176 177
        return false;
    }
    m_playlist.consolidate_blanks();
178
    m_playlist.unlock();
179 180 181
    if (durationChanged) {
        emit newTrackDuration(m_playlist.get_playtime());
    }
182 183 184 185 186
    return true;
}

bool Track::del(qreal t, qreal dt)
{
187
    m_playlist.lock();
188 189
    m_playlist.insert_blank(m_playlist.remove_region(frame(t), frame(dt) + 1), frame(dt));
    m_playlist.consolidate_blanks();
190
    m_playlist.unlock();
191 192 193 194 195
    return true;
}

bool Track::resize(qreal t, qreal dt, bool end)
{
196
    m_playlist.lock();
197 198
    int startFrame = frame(t);
    int index = m_playlist.get_clip_index_at(startFrame);
199
    int length = frame(dt);
200
    QScopedPointer<Mlt::Producer> clip(m_playlist.get_clip(index));
201 202
    if (clip == NULL || clip->is_blank()) {
        qWarning("Can't resize clip at %f", t);
203
	m_playlist.unlock();
204 205 206 207 208
        return false;
    }

    int in = clip->get_in();
    int out = clip->get_out();
209 210 211 212 213 214 215 216
    if (end) {
        // Resizing clip end
        startFrame += out - in;
        out += length;
    } else {
        // Resizing clip start
        in += length;
    }
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235

    //image or color clips are not bounded
    if (in < 0) {out -= in; in = 0;}
    if (clip->get_length() < out + 1) {
        clip->parent().set("length", out + 2);
        clip->set("length", out + 2);
    }

    if (m_playlist.resize_clip(index, in, out)) {
        qWarning("MLT resize failed : clip %d from %d to %d", index, in, out);
        m_playlist.unlock();
        return false;
    }

    //adjust adjacent blank
    if (end) {
        ++index;
        if (index > m_playlist.count() - 1) {
            m_playlist.unlock();
236 237
            // this is the last clip in track, check tracks length to adjust black track and project duration
            emit newTrackDuration(m_playlist.get_playtime());
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
            return true;
        }
        length = -length;
    }
    if (length > 0) { // reducing clip
        m_playlist.insert_blank(index, length - 1);
    } else {
        if (!end) --index;
        if (!m_playlist.is_blank(index)) {
            qWarning("Resizing over non-blank clip %d!", index);
        }
        out = m_playlist.clip_length(index) + length - 1;
        if (out >= 0) {
            if (m_playlist.resize_clip(index, 0, out)) {
                qWarning("Error resizing blank %d", index);
            }
        } else {
            if (m_playlist.remove(index)) {
                qWarning("Error removing blank %d", index);
            }
        }
    }
260

261
    m_playlist.consolidate_blanks();
262 263 264 265
    m_playlist.unlock();
    return true;
}

266 267 268
bool Track::cut(qreal t)
{
    int pos = frame(t);
269
    m_playlist.lock();
270 271 272 273 274 275 276 277 278 279
    int index = m_playlist.get_clip_index_at(pos);
    if (m_playlist.is_blank(index)) {
        return false;
    }
    if (m_playlist.split(index, pos - m_playlist.clip_start(index) - 1)) {
        qWarning("MLT split failed");
        m_playlist.unlock();
        return false;
    }
    m_playlist.unlock();
280 281
    QScopedPointer<Mlt::Producer> clip1(m_playlist.get_clip(index + 1));
    QScopedPointer<Mlt::Producer> clip2(m_playlist.get_clip(index));
282
    qDebug()<<"CLIP CUT ID: "<<clip1->get("id")<<" / "<<clip1->parent().get("id");
283 284 285
    Clip (*clip1).addEffects(*clip2, true);
    // adjust filters in/out
    Clip (*clip2).adjustEffectsLength();
286 287
    return true;
}
288

289 290
bool Track::needsDuplicate(const QString &service) const
{
291
    return (service.contains(QStringLiteral("avformat")) || service.contains(QStringLiteral("consumer")) || service.contains(QStringLiteral("xml")));
292
}
Vincent Pinon's avatar
Vincent Pinon committed
293

294 295
void Track::lockTrack(bool locked)
{
296
    if (!trackHeader) return;
297
    setProperty(QStringLiteral("kdenlive:locked_track"), locked ? 1 : 0);
298 299 300
    trackHeader->setLock(locked);
}

301 302 303
void Track::replaceId(const QString &id)
{
    QString idForAudioTrack = id + QLatin1Char('_') + m_playlist.get("id") + "_audio";
304
    QString idForVideoTrack = id + "_video";
305 306 307 308
    QString idForTrack = id + QLatin1Char('_') + m_playlist.get("id");
    //TODO: slowmotion
    for (int i = 0; i < m_playlist.count(); i++) {
        if (m_playlist.is_blank(i)) continue;
309
        QScopedPointer<Mlt::Producer> p(m_playlist.get_clip(i));
310
        QString current = p->parent().get("id");
311
	if (current == id || current == idForTrack || current == idForAudioTrack || current == idForVideoTrack || current.startsWith("slowmotion:" + id + ":")) {
312 313 314 315 316 317
	    current.prepend("#");
	    p->parent().set("id", current.toUtf8().constData());
	}
    }
}

318
QList <Track::SlowmoInfo> Track::getSlowmotionInfos(const QString &id)
319
{
320 321
    QList <Track::SlowmoInfo> list;
    QLocale locale;
322 323
    for (int i = 0; i < m_playlist.count(); i++) {
        if (m_playlist.is_blank(i)) continue;
324
        QScopedPointer<Mlt::Producer> p(m_playlist.get_clip(i));
325
        QString current = p->parent().get("id");
326
	if (!current.startsWith(QLatin1String("#"))) {
327 328 329 330
	    continue;
	}
	current.remove(0, 1);
	if (current.startsWith("slowmotion:" + id + ":")) {
331 332 333 334
            Track::SlowmoInfo info;
            info.readFromString(current.section(":", 2), locale);
            list << info;
        }
335 336 337 338 339
    }
    return list;
}

bool Track::replaceAll(const QString &id, Mlt::Producer *original, Mlt::Producer *videoOnlyProducer, QMap <QString, Mlt::Producer *> newSlowMos)
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
340 341
{
    bool found = false;
342 343
    QString idForAudioTrack;
    QString idForVideoTrack;
344 345
    QString service = original->parent().get("mlt_service");
    QString idForTrack = original->parent().get("id");
346
    QLocale locale;
347
    if (needsDuplicate(service)) {
348
        // We have to use the track clip duplication functions, because of audio glitches in MLT's multitrack
349
        idForAudioTrack = idForTrack + QLatin1Char('_') + m_playlist.get("id") + "_audio";
350
        idForVideoTrack = idForTrack + "_video";
351
        idForTrack.append(QLatin1Char('_') + m_playlist.get("id"));
352 353 354
    }
    Mlt::Producer *trackProducer = NULL;
    Mlt::Producer *audioTrackProducer = NULL;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
355 356 357

    for (int i = 0; i < m_playlist.count(); i++) {
        if (m_playlist.is_blank(i)) continue;
358
        QScopedPointer<Mlt::Producer> p(m_playlist.get_clip(i));
359
        QString current = p->parent().get("id");
360
	if (!current.startsWith(QLatin1String("#"))) {
361 362 363
	    continue;
	}
	current.remove(0, 1);
364
        Mlt::Producer *cut = NULL;
365 366
	if (current.startsWith("slowmotion:" + id + ":")) {
	      // Slowmotion producer, just update resource
367
	      Mlt::Producer *slowProd = newSlowMos.value(current.section(QStringLiteral(":"), 2));
368 369 370 371 372 373 374
	      if (!slowProd || !slowProd->is_valid()) {
		    qDebug()<<"/// WARNING, couldn't find replacement slowmo for "<<id;
		    continue;
	      }
	      cut = slowProd->cut(p->get_in(), p->get_out());
	}
        if (!cut && idForAudioTrack.isEmpty()) {
375 376 377 378
            if (current == idForTrack) {
                // No duplication required
                cut = original->cut(p->get_in(), p->get_out());
            }
379 380 381
            else {
		continue;
	    }
382
        }
383
        if (!cut && p->parent().get_int("audio_index") == -1 && current == id) {
384 385 386
	        // No audio - no duplication required
                cut = original->cut(p->get_in(), p->get_out());
	}
387
        else if (!cut && current == idForTrack) {
388 389 390 391 392 393 394
            // Use duplicate
            if (trackProducer == NULL) {
                trackProducer = Clip(*original).clone();
                trackProducer->set("id", idForTrack.toUtf8().constData());
            }
            cut = trackProducer->cut(p->get_in(), p->get_out());
        }
395
        else if (!cut && current == idForAudioTrack) {
396 397 398 399 400
            if (audioTrackProducer == NULL) {
                audioTrackProducer = clipProducer(original, PlaylistState::AudioOnly, true);
            }
            cut = audioTrackProducer->cut(p->get_in(), p->get_out());
        }
401
        else if (!cut && current == idForVideoTrack) {
402 403 404
            cut = videoOnlyProducer->cut(p->get_in(), p->get_out());
        }
        if (cut) {
405
            Clip(*cut).addEffects(*p);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
406 407 408 409 410 411 412
            m_playlist.remove(i);
            m_playlist.insert(*cut, i);
            m_playlist.consolidate_blanks();
            found = true;
            delete cut;
        }
    }
413
    return found;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
414 415
}

Vincent Pinon's avatar
Vincent Pinon committed
416
//TODO: cut: checkSlowMotionProducer
417
bool Track::replace(qreal t, Mlt::Producer *prod, PlaylistState::ClipState state, PlaylistState::ClipState originalState) {
418
    m_playlist.lock();
Vincent Pinon's avatar
Vincent Pinon committed
419
    int index = m_playlist.get_clip_index_at(frame(t));
420
    Mlt::Producer *cut;
421
    QScopedPointer <Mlt::Producer> orig(m_playlist.replace_with_blank(index));
422
    QString service = prod->get("mlt_service");
423 424
    if (state == PlaylistState::Disabled) {
        QScopedPointer<Mlt::Producer> prodCopy(Clip(*prod).clone());
425 426
        // Reset id to let MLT give a new one
        prodCopy->set("id", (char*)NULL);
427 428 429
        prodCopy->set("video_index", -1);
        prodCopy->set("audio_index", -1);
        prodCopy->set("kdenlive:binid", prod->get("id"));
430
        prodCopy->set("kdenlive:clipstate", (int) originalState);
431 432
        cut = prodCopy->cut(orig->get_in(), orig->get_out());
    } else if (state != PlaylistState::VideoOnly && service != QLatin1String("timewarp")) {
433 434 435 436 437 438 439 440
        // Get track duplicate
        Mlt::Producer *copyProd = clipProducer(prod, state);
        cut = copyProd->cut(orig->get_in(), orig->get_out());
        delete copyProd;
    } else {
        cut = prod->cut(orig->get_in(), orig->get_out());
    }
    Clip(*cut).addEffects(*orig);
441
    bool ok = m_playlist.insert_at(frame(t), cut, 1) >= 0;
442
    delete cut;
Vincent Pinon's avatar
Vincent Pinon committed
443 444 445 446
    m_playlist.unlock();
    return ok;
}

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
447 448 449 450 451 452 453 454 455
void Track::updateEffects(const QString &id, Mlt::Producer *original)
{
    QString idForAudioTrack;
    QString idForVideoTrack;
    QString service = original->parent().get("mlt_service");
    QString idForTrack = original->parent().get("id");
    if (needsDuplicate(service)) {
        // We have to use the track clip duplication functions, because of audio glitches in MLT's multitrack
        idForAudioTrack = idForTrack + QLatin1Char('_') + m_playlist.get("id") + "_audio";
456
        idForVideoTrack = idForTrack + "_video";
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
457 458 459 460 461
        idForTrack.append(QLatin1Char('_') + m_playlist.get("id"));
    }

    for (int i = 0; i < m_playlist.count(); i++) {
        if (m_playlist.is_blank(i)) continue;
462
        QScopedPointer<Mlt::Producer> p(m_playlist.get_clip(i));
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
463 464
	Mlt::Producer origin = p->parent();
        QString current = origin.get("id");
465 466
	if (current.startsWith(QLatin1String("slowmotion:"))) {
            if (current.section(QStringLiteral(":"), 1, 1) == id) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
467
		Clip(origin).replaceEffects(*original);
468 469 470 471 472 473
            }
	}
	else if (current == id) {
            // we are directly using original producer, no need to update effects
            continue;
	}
474
	else if (current.section(QStringLiteral("_"), 0, 0) == id) {
475 476
            Clip(origin).replaceEffects(*original);
	}
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
477 478 479
    }
}

480
/*Mlt::Producer &Track::find(const QByteArray &name, const QByteArray &value, int startindex) {
Vincent Pinon's avatar
Vincent Pinon committed
481
    for (int i = startindex; i < m_playlist.count(); i++) {
482
        if (m_playlist.is_blank(i)) continue;
483 484 485 486
        QScopedPointer<Mlt::Producer> p(m_playlist.get_clip(i));
        if (value == p->parent().get(name.constData())) {
            return p->parent();
        }
487
    }
488 489
    return Mlt::0;
}*/
Vincent Pinon's avatar
Vincent Pinon committed
490

491
Mlt::Producer *Track::clipProducer(Mlt::Producer *parent, PlaylistState::ClipState state, bool forceCreation) {
492 493
    QString service = parent->parent().get("mlt_service");
    QString originalId = parent->parent().get("id");
494
    if (!needsDuplicate(service) || state == PlaylistState::VideoOnly || originalId.endsWith(QLatin1String("_video"))) {
495 496 497
        // Don't clone producer for track if it has no audio
        return new Mlt::Producer(*parent);
    }
498
    originalId = originalId.section(QStringLiteral("_"), 0, 0);
499
    QString idForTrack = originalId + QLatin1Char('_') + m_playlist.get("id");
500 501 502
    if (state == PlaylistState::AudioOnly) {
        idForTrack.append("_audio");
    }
503 504 505 506 507 508 509 510
    if (!forceCreation) {
        for (int i = 0; i < m_playlist.count(); i++) {
            if (m_playlist.is_blank(i)) continue;
            QScopedPointer<Mlt::Producer> p(m_playlist.get_clip(i));
            if (QString(p->parent().get("id")) == idForTrack) {
                return new Mlt::Producer(p->parent());
            }
        }
Vincent Pinon's avatar
Vincent Pinon committed
511
    }
512
    Mlt::Producer *prod = Clip(parent->parent()).clone();
Vincent Pinon's avatar
Vincent Pinon committed
513
    prod->set("id", idForTrack.toUtf8().constData());
514 515 516
    if (state == PlaylistState::AudioOnly) {
        prod->set("video_index", -1);
    }
Vincent Pinon's avatar
Vincent Pinon committed
517 518
    return prod;
}
519 520 521 522 523

bool Track::hasAudio() 
{
    for (int i = 0; i < m_playlist.count(); i++) {
        if (m_playlist.is_blank(i)) continue;
524
        QScopedPointer<Mlt::Producer> p(m_playlist.get_clip(i));
525
        QString service = p->get("mlt_service");
526
        if (service == QLatin1String("xml") || service == QLatin1String("consumer") || p->get_int("audio_index") > -1) {
527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562
            return true;
        }
    }
    return false;
}

void Track::setProperty(const QString &name, const QString &value)
{
    m_playlist.set(name.toUtf8().constData(), value.toUtf8().constData());
}

void Track::setProperty(const QString &name, int value)
{
    m_playlist.set(name.toUtf8().constData(), value);
}

const QString Track::getProperty(const QString &name)
{
    return QString(m_playlist.get(name.toUtf8().constData()));
}

int Track::getIntProperty(const QString &name)
{
    return m_playlist.get_int(name.toUtf8().constData());
}

TrackInfo Track::info()
{
    TrackInfo info;
    info.trackName = m_playlist.get("kdenlive:track_name");
    info.isLocked= m_playlist.get_int("kdenlive:locked_track");
    int currentState = m_playlist.parent().get_int("hide");
    info.isMute = currentState & 2;
    info.isBlind = currentState & 1;
    info.type = type;
    info.effectsList = effectsList;
563
    info.composite = m_playlist.get_int("kdenlive:composite");
564 565 566 567 568
    return info;
}

void Track::setInfo(TrackInfo info)
{
569
    if (!trackHeader) return;
570 571
    m_playlist.set("kdenlive:track_name", info.trackName.toUtf8().constData());
    m_playlist.set("kdenlive:locked_track", info.isLocked ? 1 : 0);
572
    m_playlist.set("kdenlive:composite", info.composite ? 1 : 0);
573 574 575 576 577 578 579 580
    int state = 0;
    if (info.isMute) {
        if (info.isBlind) state = 3;
        else state = 2;
    }
    else if (info.isBlind) state = 1;
    m_playlist.parent().set("hide", state);
    type = info.type;
581
    trackHeader->updateStatus(info);
582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605
}

int Track::state()
{
    return m_playlist.parent().get_int("hide");
}

void Track::setState(int state)
{
    m_playlist.parent().set("hide", state);
}

int Track::getBlankLength(int pos, bool fromBlankStart)
{
    int clipIndex = m_playlist.get_clip_index_at(pos);
    if (clipIndex == m_playlist.count()) {
        // We are after the end of the playlist
        return -1;
    }
    if (!m_playlist.is_blank(clipIndex)) return 0;
    if (fromBlankStart) return m_playlist.clip_length(clipIndex);
    return m_playlist.clip_length(clipIndex) + m_playlist.clip_start(clipIndex) - pos;
}

606 607 608
void Track::updateClipProperties(const QString &id, QMap <QString, QString> properties)
{
    QString idForTrack = id + QLatin1Char('_') + m_playlist.get("id");
609
    QString idForVideoTrack = id + "_video";
610
    QString idForAudioTrack = idForTrack + "_audio";
611
    // slowmotion producers are updated in renderer
612 613 614

    for (int i = 0; i < m_playlist.count(); i++) {
        if (m_playlist.is_blank(i)) continue;
615
        QScopedPointer<Mlt::Producer> p(m_playlist.get_clip(i));
616 617
        QString current = p->parent().get("id");
        QStringList processed;
618
        if (!processed.contains(current) && (current == idForTrack || current == idForAudioTrack || current == idForVideoTrack)) {
619 620 621 622 623 624 625 626 627 628
            QMapIterator<QString, QString> i(properties);
            while (i.hasNext()) {
                i.next();
                p->parent().set(i.key().toUtf8().constData(), i.value().toUtf8().constData());
            }
            processed << current;
        }
    }
}

629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654
Mlt::Producer *Track::buildSlowMoProducer(Mlt::Properties passProps, const QString &url, const QString &id, Track::SlowmoInfo info)
{
    QLocale locale;
    Mlt::Producer *prod = new Mlt::Producer(*m_playlist.profile(), 0, ("timewarp:" + url).toUtf8().constData());
    if (!prod->is_valid()) {
	qDebug()<<"++++ FAILED TO CREATE SLOWMO PROD";
	return NULL;
    }
    QString producerid = "slowmotion:" + id + ':' + info.toString(locale);
    prod->set("id", producerid.toUtf8().constData());

    // copy producer props
    for (int i = 0; i < passProps.count(); i++) {
	prod->set(passProps.get_name(i), passProps.get(i)); 
    }
    // set clip state
    switch ((int) info.state) {
        case PlaylistState::VideoOnly:
            prod->set("audio_index", -1);
            break;
        case PlaylistState::AudioOnly:
            prod->set("video_index", -1);
            break;
        default:
            break;
    }
655 656 657
    QString slowmoId = info.toString(locale);
    slowmoId.append(prod->get("warp_resource"));
    emit storeSlowMotion(slowmoId, prod);
658 659 660 661
    return prod;
}

int Track::changeClipSpeed(ItemInfo info, ItemInfo speedIndependantInfo, PlaylistState::ClipState state, double speed, int strobe, Mlt::Producer *prod, const QString &id, Mlt::Properties passProps, bool removeEffect)
662
{
663
    //TODO: invalidate preview rendering
664
    int newLength = 0;
665
    int startPos = info.startPos.frames(fps());
666 667
    int clipIndex = m_playlist.get_clip_index_at(startPos);
    int clipLength = m_playlist.clip_length(clipIndex);
668
    m_playlist.lock();
669
    QScopedPointer<Mlt::Producer> original(m_playlist.get_clip(clipIndex));
670 671
    if (original == NULL) {
        qDebug()<<"// No clip to apply effect";
672
        m_playlist.unlock();
673 674 675 676 677
        return -1;
    }
    if (!original->is_valid() || original->is_blank()) {
        // invalid clip
        qDebug()<<"// Invalid clip to apply effect";
678
        m_playlist.unlock();
679 680 681 682 683 684
        return -1;
    }
    Mlt::Producer clipparent = original->parent();
    if (!clipparent.is_valid() || clipparent.is_blank()) {
        // invalid clip
        qDebug()<<"// Invalid parent to apply effect";
685
        m_playlist.unlock();
686 687
        return -1;
    }
688 689
    QLocale locale;
    if (speed <= 0 && speed > -1) speed = 1.0;
690
    QString serv = clipparent.get("mlt_service");
691 692 693 694 695
    QString url;
    if (serv == QLatin1String("timewarp")) {
        url = QString::fromUtf8(clipparent.get("warp_resource"));
    } else {
        url = QString::fromUtf8(clipparent.get("resource"));
696
    }
697 698 699 700 701
    url.prepend(locale.toString(speed) + ':');
    Track::SlowmoInfo slowInfo;
    slowInfo.speed = speed;
    slowInfo.strobe = strobe;
    slowInfo.state = state;
702
    if (serv.contains(QStringLiteral("avformat"))) {
703 704
	if (speed != 1.0 || strobe > 1) {
	    if (!prod || !prod->is_valid()) {
705 706 707 708
		prod = buildSlowMoProducer(passProps, url, id, slowInfo);
                if (prod == NULL) {
                    // error, abort
                    qDebug()<<"++++ FAILED TO CREATE SLOWMO PROD";
709
                    m_playlist.unlock();
710 711
                    return -1;
                }
712
	    }
713
	    QScopedPointer <Mlt::Producer> clip(m_playlist.replace_with_blank(clipIndex));
714 715 716 717 718 719 720
	    m_playlist.consolidate_blanks(0);

	    // Check that the blank space is long enough for our new duration
	    clipIndex = m_playlist.get_clip_index_at(startPos);
	    int blankEnd = m_playlist.clip_start(clipIndex) + m_playlist.clip_length(clipIndex);
	    Mlt::Producer *cut;
	    if (clipIndex + 1 < m_playlist.count() && (startPos + clipLength / speed > blankEnd)) {
721 722 723
		GenTime maxLength = GenTime(blankEnd, fps()) - info.startPos;
		cut = prod->cut((int)(info.cropStart.frames(fps()) / speed), (int)(info.cropStart.frames(fps()) / speed + maxLength.frames(fps()) - 1));
	    } else cut = prod->cut((int)(info.cropStart.frames(fps()) / speed), (int)((info.cropStart.frames(fps()) + clipLength) / speed - 1));
724 725

	    // move all effects to the correct producer
726
	    Clip(*cut).addEffects(*clip);
727 728 729 730 731
	    m_playlist.insert_at(startPos, cut, 1);
	    delete cut;
	    clipIndex = m_playlist.get_clip_index_at(startPos);
	    newLength = m_playlist.clip_length(clipIndex);
	} else if (speed == 1.0 && strobe < 2) {
732
	    QScopedPointer <Mlt::Producer> clip(m_playlist.replace_with_blank(clipIndex));
733 734 735 736 737 738 739 740 741
	    m_playlist.consolidate_blanks(0);

	    // Check that the blank space is long enough for our new duration
	    clipIndex = m_playlist.get_clip_index_at(startPos);
	    int blankEnd = m_playlist.clip_start(clipIndex) + m_playlist.clip_length(clipIndex);

	    Mlt::Producer *cut;
	
	    if (!prod || !prod->is_valid()) {
742 743 744 745
		prod = buildSlowMoProducer(passProps, url, id, slowInfo);
                if (prod == NULL) {
                    // error, abort
                    qDebug()<<"++++ FAILED TO CREATE SLOWMO PROD";
746
                    m_playlist.unlock();
747 748
                    return -1;
                }
749
            }
750

751 752 753 754
	    int originalStart = (int)(speedIndependantInfo.cropStart.frames(fps()));
	    if (clipIndex + 1 < m_playlist.count() && (info.startPos + speedIndependantInfo.cropDuration).frames(fps()) > blankEnd) {
		GenTime maxLength = GenTime(blankEnd, fps()) - info.startPos;
		cut = prod->cut(originalStart, (int)(originalStart + maxLength.frames(fps()) - 1));
755
	    } else {
756
		cut = prod->cut(originalStart, (int)(originalStart + speedIndependantInfo.cropDuration.frames(fps())) - 1);
757
	    }
758

759
	    // move all effects to the correct producer
760
	    Clip(*cut).addEffects(*clip);
761 762 763 764
	    m_playlist.insert_at(startPos, cut, 1);
	    delete cut;
	    clipIndex = m_playlist.get_clip_index_at(startPos);
	    newLength = m_playlist.clip_length(clipIndex);
765
	}
766
    } else if (serv == QLatin1String("timewarp")) {
767
        if (!prod || !prod->is_valid()) {
768 769 770 771
            prod = buildSlowMoProducer(passProps, url, id, slowInfo);
            if (prod == NULL) {
                // error, abort
                qDebug()<<"++++ FAILED TO CREATE SLOWMO PROD";
772
                m_playlist.unlock();
773 774
                return -1;
            }
775
        }
776 777 778
        if (removeEffect) {
            prod = clipProducer(prod, state);
        }
779
        QScopedPointer <Mlt::Producer> clip(m_playlist.replace_with_blank(clipIndex));
780
        m_playlist.consolidate_blanks(0);
781 782 783
        int duration;
        int originalStart;
        if (speed == 1.0) {
784 785
          duration = speedIndependantInfo.cropDuration.frames(fps());
          originalStart = speedIndependantInfo.cropStart.frames(fps());
786
        } else {
787 788
          duration = (int) (speedIndependantInfo.cropDuration.frames(fps()) / speed + 0.5);
          originalStart = (int)(speedIndependantInfo.cropStart.frames(fps()) / speed + 0.5);
789
        }
790
        //qDebug()<<"/ / /UPDATE SPEED: "<<speed<<", "<<speedIndependantInfo.cropStart.frames(fps())<<":"<<originalStart;
791 792 793 794 795
        // Check that the blank space is long enough for our new duration
        clipIndex = m_playlist.get_clip_index_at(startPos);
        int blankEnd = m_playlist.clip_start(clipIndex) + m_playlist.clip_length(clipIndex);

        Mlt::Producer *cut;
796
        if (clipIndex + 1 < m_playlist.count() && (startPos + duration > blankEnd)) {
797 798
            GenTime maxLength = GenTime(blankEnd, fps()) - info.startPos;
            cut = prod->cut(originalStart, (int)(originalStart + maxLength.frames(fps()) - 1));
799 800 801
        } else {
	      cut = prod->cut(originalStart, originalStart + duration - 1);
	}
802 803

        // move all effects to the correct producer
804
        Clip(*cut).addEffects(*clip);
805 806 807 808
        m_playlist.insert_at(startPos, cut, 1);
        delete cut;
        clipIndex = m_playlist.get_clip_index_at(startPos);
        newLength = m_playlist.clip_length(clipIndex);
809 810
        if (removeEffect)
            delete prod;
811
    }
812
    //Do not delete prod, it is now stored in the slowmotion producers list
813
    m_playlist.unlock();
814 815 816 817
    if (clipIndex + 1 == m_playlist.count()) {
        // We changed the speed of last clip in playlist, check track length
        emit newTrackDuration(m_playlist.get_playtime());
    }
818 819 820
    return newLength;
}

821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837
int Track::index() const
{
    return m_index;
}


int Track::spaceLength(int pos, bool fromBlankStart)
{
    int clipIndex = m_playlist.get_clip_index_at(pos);
    if (clipIndex == m_playlist.count()) {
        // We are after the end of the playlist
        return -1;
    }
    if (!m_playlist.is_blank(clipIndex)) return 0;
    if (fromBlankStart) return m_playlist.clip_length(clipIndex);
    return m_playlist.clip_length(clipIndex) + m_playlist.clip_start(clipIndex) - pos;
}
838

839 840 841 842 843 844 845 846 847 848 849
void Track::disableEffects(bool disable)
{
    for (int i = 0; i < m_playlist.count(); i++) {
        QScopedPointer<Mlt::Producer> original(m_playlist.get_clip(i));
        if (original == NULL || !original->is_valid() || original->is_blank()) {
            // invalid clip
            continue;
        }
        Clip(*original).disableEffects(disable);
    }
}
850 851 852 853 854 855 856 857 858 859 860 861

bool Track::addEffect(double start, EffectsParameterList params)
{
    int pos = frame(start);
    int clipIndex = m_playlist.get_clip_index_at(pos);
    int duration = m_playlist.clip_length(clipIndex);
    QScopedPointer<Mlt::Producer> clip(m_playlist.get_clip(clipIndex));
    if (!clip) {
        return false;
    }
    Mlt::Service clipService(clip->get_service());
    EffectManager effect(clipService);
862
    return effect.addEffect(params, duration);
863 864
}

865 866 867 868
bool Track::addTrackEffect(EffectsParameterList params)
{
    Mlt::Service trackService(m_playlist.get_service());
    EffectManager effect(trackService);
869
    int duration = m_playlist.get_playtime() - 1;
870
    return effect.addEffect(params, duration);
871 872 873 874 875 876 877 878 879 880 881 882 883
}

bool Track::editEffect(double start, EffectsParameterList params, bool replace)
{
    int pos = frame(start);
    int clipIndex = m_playlist.get_clip_index_at(pos);
    int duration = m_playlist.clip_length(clipIndex);
    QScopedPointer<Mlt::Producer> clip(m_playlist.get_clip(clipIndex));
    if (!clip) {
        return false;
    }
    Mlt::Service clipService(clip->get_service());
    EffectManager effect(clipService);
884
    return effect.editEffect(params, duration, replace);
885 886 887 888 889
}

bool Track::editTrackEffect(EffectsParameterList params, bool replace)
{
    EffectManager effect(m_playlist);
890
    int duration = m_playlist.get_playtime() - 1;
891
    return effect.editEffect(params, duration, replace);
892 893 894 895 896 897 898 899 900 901 902 903
}

bool Track::removeEffect(double start, int effectIndex, bool updateIndex)
{
    int pos = frame(start);
    int clipIndex = m_playlist.get_clip_index_at(pos);
    QScopedPointer<Mlt::Producer> clip(m_playlist.get_clip(clipIndex));
    if (!clip) {
        return false;
    }
    Mlt::Service clipService(clip->get_service());
    EffectManager effect(clipService);
904
    return effect.removeEffect(effectIndex, updateIndex);
905 906 907 908 909
}

bool Track::removeTrackEffect(int effectIndex, bool updateIndex)
{
    EffectManager effect(m_playlist);
910
    return effect.removeEffect(effectIndex, updateIndex);
911 912
}

913 914 915 916 917 918 919 920 921 922
bool Track::enableEffects(double start, const QList <int> &effectIndexes, bool disable)
{
    int pos = frame(start);
    int clipIndex = m_playlist.get_clip_index_at(pos);
    QScopedPointer<Mlt::Producer> clip(m_playlist.get_clip(clipIndex));
    if (!clip) {
        return false;
    }
    Mlt::Service clipService(clip->get_service());
    EffectManager effect(clipService);
923
    return effect.enableEffects(effectIndexes, disable);
924 925 926 927 928
}

bool Track::enableTrackEffects(const QList <int> &effectIndexes, bool disable)
{
    EffectManager effect(m_playlist);
929
    return effect.enableEffects(effectIndexes, disable);
930 931 932 933 934 935 936 937 938 939 940 941
}

bool Track::moveEffect(double start, int oldPos, int newPos)
{
    int pos = frame(start);
    int clipIndex = m_playlist.get_clip_index_at(pos);
    QScopedPointer<Mlt::Producer> clip(m_playlist.get_clip(clipIndex));
    if (!clip) {
        return false;
    }
    Mlt::Service clipService(clip->get_service());
    EffectManager effect(clipService);
942
    return effect.moveEffect(oldPos, newPos);
943 944 945 946 947
}

bool Track::moveTrackEffect(int oldPos, int newPos)
{
    EffectManager effect(m_playlist);
948
    return effect.moveEffect(oldPos, newPos);
949
}