kdenlivedoc.cpp 63.3 KB
Newer Older
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
1
/***************************************************************************
2
 *   Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@kdenlive.org)        *
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
3 4 5 6 7 8
 *                                                                         *
 *   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) any later version.                                   *
 *                                                                         *
9 10 11 12 13 14 15 16 17
 *   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, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA          *
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
18 19
 ***************************************************************************/

20

Vincent Pinon's avatar
Vincent Pinon committed
21 22 23
#include "kdenlivedoc.h"
#include "documentchecker.h"
#include "documentvalidator.h"
24
#include "mltcontroller/clipcontroller.h"
25
#include "mltcontroller/producerqueue.h"
Vincent Pinon's avatar
Vincent Pinon committed
26
#include <config-kdenlive.h>
27 28
#include "kdenlivesettings.h"
#include "renderer.h"
29
#include "mainwindow.h"
Vincent Pinon's avatar
Vincent Pinon committed
30
#include "project/clipmanager.h"
31
#include "project/projectcommands.h"
32
#include "bin/bincommands.h"
Vincent Pinon's avatar
Vincent Pinon committed
33 34
#include "effectslist/initeffects.h"
#include "dialogs/profilesdialog.h"
Vincent Pinon's avatar
Vincent Pinon committed
35
#include "titler/titlewidget.h"
36 37
#include "project/notesplugin.h"
#include "project/dialogs/noteswidget.h"
38 39 40
#include "core.h"
#include "bin/bin.h"
#include "bin/projectclip.h"
41
#include "utils/KoIconUtils.h"
42
#include "mltcontroller/bincontroller.h"
43
#include "mltcontroller/effectscontroller.h"
44

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
45
#include <KMessageBox>
46
#include <KRecentDirs>
47
#include <klocalizedstring.h>
48
#include <KIO/CopyJob>
49
#include <KIO/JobUiDelegate>
50 51
#include <KBookmarkManager>
#include <KBookmark>
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
52

53 54
#include <QCryptographicHash>
#include <QFile>
55 56
#include <QDebug>
#include <QFileDialog>
57
#include <QInputDialog>
58
#include <QDomImplementation>
59 60 61
#include <QUndoGroup>
#include <QTimer>
#include <QUndoStack>
62
#include <QTextEdit>
63

64
#include <mlt++/Mlt.h>
65
#include <KJobWidgets/KJobWidgets>
66
#include <QStandardPaths>
67

Vincent Pinon's avatar
Vincent Pinon committed
68 69 70 71
#include <locale>
#ifdef Q_OS_MAC
#include <xlocale.h>
#endif
72

73
const double DOCUMENTVERSION = 0.94;
74

75
KdenliveDoc::KdenliveDoc(const QUrl &url, const QUrl &projectFolder, QUndoGroup *undoGroup, const QString &profileName, const QMap <QString, QString>& properties, const QMap <QString, QString>& metadata, const QPoint &tracks, Render *render, NotesPlugin *notes, bool *openBackup, MainWindow *parent) :
76 77 78
    QObject(parent),
    m_autosave(NULL),
    m_url(url),
79 80
    m_width(0),
    m_height(0),
81
    m_render(render),
82
    m_notesWidget(notes->widget()),
83 84 85
    m_commandStack(new QUndoStack(undoGroup)),
    m_modified(false),
    m_projectFolder(projectFolder)
86
{
87 88 89 90 91 92 93 94 95 96 97
    // init m_profile struct
    m_profile.frame_rate_num = 0;
    m_profile.frame_rate_den = 0;
    m_profile.width = 0;
    m_profile.height = 0;
    m_profile.progressive = 0;
    m_profile.sample_aspect_num = 0;
    m_profile.sample_aspect_den = 0;
    m_profile.display_aspect_num = 0;
    m_profile.display_aspect_den = 0;
    m_profile.colorspace = 0;
98
    m_clipManager = new ClipManager(this);
Laurent Montel's avatar
Laurent Montel committed
99
    connect(m_clipManager, SIGNAL(displayMessage(QString,int)), parent, SLOT(slotGotProgressInfo(QString,int)));
Alberto Villa's avatar
Alberto Villa committed
100
    bool success = false;
101
    connect(m_commandStack, SIGNAL(indexChanged(int)), this, SLOT(slotModified()));
102
    connect(m_render, SIGNAL(setDocumentNotes(QString)), this, SLOT(slotSetDocumentNotes(QString)));
103
    connect(pCore->producerQueue(), &ProducerQueue::switchProfile, this, &KdenliveDoc::switchProfile);
104
    //connect(m_commandStack, SIGNAL(cleanChanged(bool)), this, SLOT(setModified(bool)));
105

106 107 108 109 110
    // Init clip modification tracker
    m_modifiedTimer.setInterval(1500);
    connect(&m_fileWatcher, &KDirWatch::dirty, this, &KdenliveDoc::slotClipModified);
    connect(&m_fileWatcher, &KDirWatch::deleted, this, &KdenliveDoc::slotClipMissing);
    connect(&m_modifiedTimer, &QTimer::timeout, this, &KdenliveDoc::slotProcessModifiedClips);
111 112

    // init default document properties
113 114 115 116 117 118 119 120 121 122 123 124
    m_documentProperties[QStringLiteral("zoom")] = '7';
    m_documentProperties[QStringLiteral("verticalzoom")] = '1';
    m_documentProperties[QStringLiteral("zonein")] = '0';
    m_documentProperties[QStringLiteral("zoneout")] = QStringLiteral("100");
    m_documentProperties[QStringLiteral("enableproxy")] = QString::number((int) KdenliveSettings::enableproxy());
    m_documentProperties[QStringLiteral("proxyparams")] = KdenliveSettings::proxyparams();
    m_documentProperties[QStringLiteral("proxyextension")] = KdenliveSettings::proxyextension();
    m_documentProperties[QStringLiteral("generateproxy")] = QString::number((int) KdenliveSettings::generateproxy());
    m_documentProperties[QStringLiteral("proxyminsize")] = QString::number(KdenliveSettings::proxyminsize());
    m_documentProperties[QStringLiteral("generateimageproxy")] = QString::number((int) KdenliveSettings::generateimageproxy());
    m_documentProperties[QStringLiteral("proxyimageminsize")] = QString::number(KdenliveSettings::proxyimageminsize());
    m_documentProperties[QStringLiteral("documentid")] = QString::number(QDateTime::currentMSecsSinceEpoch());
125

126 127 128 129 130 131
    // Load properties
    QMapIterator<QString, QString> i(properties);
    while (i.hasNext()) {
        i.next();
        m_documentProperties[i.key()] = i.value();
    }
132

133 134 135 136 137 138
    // Load metadata
    QMapIterator<QString, QString> j(metadata);
    while (j.hasNext()) {
        j.next();
        m_documentMetadata[j.key()] = j.value();
    }
139

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
140
    if (QLocale().decimalPoint() != QLocale::system().decimalPoint()) {
141
        setlocale(LC_NUMERIC, "");
142 143 144
        QLocale systemLocale = QLocale::system();
        systemLocale.setNumberOptions(QLocale::OmitGroupSeparator);
        QLocale::setDefault(systemLocale);
145
        // locale conversion might need to be redone
146
        initEffects::parseEffectFiles(pCore->binController()->mltRepository(), setlocale(LC_NUMERIC, NULL));
147 148
    }

149
    *openBackup = false;
150
    if (url.isValid()) {
151
        QFile file(url.toLocalFile());
152
        if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
153
            // The file cannot be opened
154
            if (KMessageBox::warningContinueCancel(parent, i18n("Cannot open the project file,\nDo you want to open a backup file?"), i18n("Error opening file"), KGuiItem(i18n("Open Backup"))) == KMessageBox::Continue) {
155 156 157 158
                *openBackup = true;
            }
            //KMessageBox::error(parent, KIO::NetAccess::lastErrorString());
        }
Alberto Villa's avatar
Alberto Villa committed
159
        else {
160
            qDebug()<<" // / processing file open";
161
            QString errorMsg;
162 163
            int line;
            int col;
164
            QDomImplementation::setInvalidDataPolicy(QDomImplementation::DropInvalidChars);
165
            success = m_document.setContent(&file, false, &errorMsg, &line, &col);
166
            file.close();
167

168 169
            if (!success) {
                // It is corrupted
170
                int answer = KMessageBox::warningYesNoCancel (parent, i18n("Cannot open the project file, error is:\n%1 (line %2, col %3)\nDo you want to open a backup file?", errorMsg, line, col), i18n("Error opening file"), KGuiItem(i18n("Open Backup")), KGuiItem(i18n("Recover")));
171
                if (answer == KMessageBox::Yes) {
172 173 174 175 176 177 178 179 180 181 182
                    *openBackup = true;
                }
                else if (answer == KMessageBox::No) {
                    // Try to recover broken file produced by Kdenlive 0.9.4
                    if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
                        int correction = 0;
                        QString playlist = file.readAll();
                        while (!success && correction < 2) {
                            int errorPos = 0;
                            line--;
                            col = col - 2;
183
                            for (int j = 0; j < line && errorPos < playlist.length(); ++j) {
184
                                errorPos = playlist.indexOf(QStringLiteral("\n"), errorPos);
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
                                errorPos++;
                            }
                            errorPos += col;
                            if (errorPos >= playlist.length()) break;
                            playlist.remove(errorPos, 1);
                            line = 0;
                            col = 0;
                            success = m_document.setContent(playlist, false, &errorMsg, &line, &col);
                            correction++;
                        }
                        if (!success) {
                            KMessageBox::sorry(parent, i18n("Cannot recover this project file"));
                        }
                        else {
                            // Document was modified, ask for backup
                            QDomElement mlt = m_document.documentElement();
201
                            mlt.setAttribute(QStringLiteral("modified"), 1);
202 203 204
                        }
                    }
                }
205
            }
206
            else {
207
                qDebug()<<" // / processing file open: validate";
208
                parent->slotGotProgressInfo(i18n("Validating"), 0);
209
                qApp->processEvents();
210
                DocumentValidator validator(m_document, url);
Alberto Villa's avatar
Alberto Villa committed
211
                success = validator.isProject();
212
                if (!success) {
213
                    // It is not a project file
214
                    parent->slotGotProgressInfo(i18n("File %1 is not a Kdenlive project file", m_url.path()), 100);
215 216 217
                    if (KMessageBox::warningContinueCancel(parent, i18n("File %1 is not a valid project file.\nDo you want to open a backup file?", m_url.path()), i18n("Error opening file"), KGuiItem(i18n("Open Backup"))) == KMessageBox::Continue) {
                        *openBackup = true;
                    }
218
                } else {
219
                    /*
Alberto Villa's avatar
Alberto Villa committed
220 221
                     * Validate the file against the current version (upgrade
                     * and recover it if needed). It is NOT a passive operation
222
                     */
Alberto Villa's avatar
Alberto Villa committed
223 224
                    // TODO: backup the document or alert the user?
                    success = validator.validate(DOCUMENTVERSION);
225 226 227
                    if (success && !KdenliveSettings::gpu_accel()) {
                        success = validator.checkMovit();
                    }
228
                    if (success) { // Let the validator handle error messages
229
                        qDebug()<<" // / processing file validate ok";
230
                        parent->slotGotProgressInfo(i18n("Check missing clips"), 0);
231
                        qApp->processEvents();
232
                        DocumentChecker d(m_url, m_document);
233
                        success = !d.hasErrorInClips();
234
                        if (success) {
235
                            loadDocumentProperties();
236
                            if (m_document.documentElement().attribute(QStringLiteral("modified")) == QLatin1String("1")) setModified(true);
237
                            if (validator.isModified()) setModified(true);
238 239
                        }
                    }
240
                }
241
            }
242
        }
243
    }
244

Alberto Villa's avatar
Alberto Villa committed
245
    // Something went wrong, or a new file was requested: create a new project
246
    if (!success) {
247
        m_url.clear();
248
        m_profile = ProfilesDialog::getVideoProfile(profileName);
249
        m_document = createEmptyDocument(tracks.x(), tracks.y());
250
        updateProjectProfile(false);
251
    }
252

253
    // Ask to create the project directory if it does not exist
254 255
    QFileInfo checkProjectFolder(m_projectFolder.toString(QUrl::RemoveFilename | QUrl::RemoveScheme));
    if (!QFile::exists(m_projectFolder.path()) && checkProjectFolder.isWritable()) {
256 257 258 259 260 261 262 263 264
        int create = KMessageBox::questionYesNo(parent, i18n("Project directory %1 does not exist. Create it?", m_projectFolder.path()));
        if (create == KMessageBox::Yes) {
            QDir projectDir(m_projectFolder.path());
            bool ok = projectDir.mkpath(m_projectFolder.path());
            if (!ok) {
                KMessageBox::sorry(parent, i18n("The directory %1, could not be created.\nPlease make sure you have the required permissions.", m_projectFolder.path()));
            }
        }
    }
Alberto Villa's avatar
Alberto Villa committed
265 266

    // Make sure the project folder is usable
267
    if (m_projectFolder.isEmpty() || !QFile::exists(m_projectFolder.path())) {
Alberto Villa's avatar
Alberto Villa committed
268
        KMessageBox::information(parent, i18n("Document project folder is invalid, setting it to the default one: %1", KdenliveSettings::defaultprojectfolder()));
269
        m_projectFolder = QUrl::fromLocalFile(KdenliveSettings::defaultprojectfolder());
Alberto Villa's avatar
Alberto Villa committed
270 271 272
    }

    // Make sure that the necessary folders exist
273
    QDir dir(m_projectFolder.path());
274 275 276 277
    dir.mkdir(QStringLiteral("titles"));
    dir.mkdir(QStringLiteral("thumbs"));
    dir.mkdir(QStringLiteral("proxy"));
    dir.mkdir(QStringLiteral(".backup"));
278

279
    updateProjectFolderPlacesEntry();
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
280 281
}

282 283 284 285 286 287
void KdenliveDoc::slotSetDocumentNotes(const QString &notes)
{
    m_notesWidget->setHtml(notes);
}


288 289
KdenliveDoc::~KdenliveDoc()
{
290
    delete m_commandStack;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
291
    //qDebug() << "// DEL CLP MAN";
292
    delete m_clipManager;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
293
    //qDebug() << "// DEL CLP MAN done";
294 295
    if (m_autosave) {
        if (!m_autosave->fileName().isEmpty()) m_autosave->remove();
296 297
        delete m_autosave;
    }
298 299
}

300
int KdenliveDoc::setSceneList()
301
{
302 303
    //m_render->resetProfile(m_profile);
    pCore->bin()->isLoading = true;
304
    pCore->producerQueue()->abortOperations();
305
    if (m_render->setSceneList(m_document.toString(), m_documentProperties.value(QStringLiteral("position")).toInt()) == -1) {
306 307 308
        // INVALID MLT Consumer, something is wrong
        return -1;
    }
309
    pCore->bin()->isLoading = false;
310
    pCore->binController()->checkThumbnails(projectFolder().path() + "/thumbs/");
311
    m_documentProperties.remove(QStringLiteral("position"));
312
    pCore->monitorManager()->activateMonitor(Kdenlive::ClipMonitor, true);
313
    return 0;
314 315
}

316
QDomDocument KdenliveDoc::createEmptyDocument(int videotracks, int audiotracks)
317
{
318
    QList <TrackInfo> tracks;
319 320 321
    // Tracks are added «backwards», so we need to reverse the track numbering
    // mbt 331: http://www.kdenlive.org/mantis/view.php?id=331
    // Better default names for tracks: Audio 1 etc. instead of blank numbers
322
    for (int i = 0; i < audiotracks; ++i) {
323
        TrackInfo audioTrack;
324
        audioTrack.type = AudioTrack;
325 326 327
        audioTrack.isMute = false;
        audioTrack.isBlind = true;
        audioTrack.isLocked = false;
328
        audioTrack.trackName = i18n("Audio %1", audiotracks - i);
329
        audioTrack.duration = 0;
330
        audioTrack.effectsList = EffectsList(true);
331
        tracks.append(audioTrack);
332

333
    }
334
    for (int i = 0; i < videotracks; ++i) {
335
        TrackInfo videoTrack;
336
        videoTrack.type = VideoTrack;
337 338 339
        videoTrack.isMute = false;
        videoTrack.isBlind = false;
        videoTrack.isLocked = false;
340
        videoTrack.trackName = i18n("Video %1", i + 1);
341
        videoTrack.duration = 0;
342
        videoTrack.effectsList = EffectsList(true);
343
        tracks.append(videoTrack);
344
    }
345
    return createEmptyDocument(tracks);
346 347
}

348
QDomDocument KdenliveDoc::createEmptyDocument(const QList <TrackInfo> &tracks)
349 350 351
{
    // Creating new document
    QDomDocument doc;
352 353
    QDomElement mlt = doc.createElement(QStringLiteral("mlt"));
    mlt.setAttribute(QStringLiteral("LC_NUMERIC"), QLatin1String(""));
354
    doc.appendChild(mlt);
355

356 357 358 359
    QDomElement blk = doc.createElement(QStringLiteral("producer"));
    blk.setAttribute(QStringLiteral("in"), 0);
    blk.setAttribute(QStringLiteral("out"), 500);
    blk.setAttribute(QStringLiteral("id"), QStringLiteral("black"));
360

361 362 363
    QDomElement property = doc.createElement(QStringLiteral("property"));
    property.setAttribute(QStringLiteral("name"), QStringLiteral("mlt_type"));
    QDomText value = doc.createTextNode(QStringLiteral("producer"));
364 365 366
    property.appendChild(value);
    blk.appendChild(property);

367 368
    property = doc.createElement(QStringLiteral("property"));
    property.setAttribute(QStringLiteral("name"), QStringLiteral("aspect_ratio"));
369
    value = doc.createTextNode(QString::number(0));
370 371 372
    property.appendChild(value);
    blk.appendChild(property);

373 374
    property = doc.createElement(QStringLiteral("property"));
    property.setAttribute(QStringLiteral("name"), QStringLiteral("length"));
375 376 377 378
    value = doc.createTextNode(QString::number(15000));
    property.appendChild(value);
    blk.appendChild(property);

379 380 381
    property = doc.createElement(QStringLiteral("property"));
    property.setAttribute(QStringLiteral("name"), QStringLiteral("eof"));
    value = doc.createTextNode(QStringLiteral("pause"));
382 383 384
    property.appendChild(value);
    blk.appendChild(property);

385 386 387
    property = doc.createElement(QStringLiteral("property"));
    property.setAttribute(QStringLiteral("name"), QStringLiteral("resource"));
    value = doc.createTextNode(QStringLiteral("black"));
388 389 390
    property.appendChild(value);
    blk.appendChild(property);

391 392 393
    property = doc.createElement(QStringLiteral("property"));
    property.setAttribute(QStringLiteral("name"), QStringLiteral("mlt_service"));
    value = doc.createTextNode(QStringLiteral("colour"));
394 395 396 397 398 399
    property.appendChild(value);
    blk.appendChild(property);

    mlt.appendChild(blk);


400 401 402
    QDomElement tractor = doc.createElement(QStringLiteral("tractor"));
    tractor.setAttribute(QStringLiteral("id"), QStringLiteral("maintractor"));
    tractor.setAttribute(QStringLiteral("global_feed"), 1);
403
    //QDomElement multitrack = doc.createElement("multitrack");
404 405
    QDomElement playlist = doc.createElement(QStringLiteral("playlist"));
    playlist.setAttribute(QStringLiteral("id"), QStringLiteral("black_track"));
406
    mlt.appendChild(playlist);
407

408 409 410 411
    QDomElement blank0 = doc.createElement(QStringLiteral("entry"));
    blank0.setAttribute(QStringLiteral("in"), QStringLiteral("0"));
    blank0.setAttribute(QStringLiteral("out"), QStringLiteral("1"));
    blank0.setAttribute(QStringLiteral("producer"), QStringLiteral("black"));
412
    playlist.appendChild(blank0);
413 414

    // create playlists
415
    int total = tracks.count();
416 417
    // The lower video track will recieve composite transitions
    int lowerVideoTrack = -1;
418
    for (int i = 0; i < total; ++i) {
419
        QDomElement playlist = doc.createElement(QStringLiteral("playlist"));
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
420 421
        playlist.setAttribute(QStringLiteral("id"), "playlist" + QString::number(i+1));
        playlist.setAttribute(QStringLiteral("kdenlive:track_name"), tracks.at(i).trackName);
422
        if (tracks.at(i).type == AudioTrack) {
423
            playlist.setAttribute(QStringLiteral("kdenlive:audio_track"), 1);
424 425 426
        } else if (lowerVideoTrack == -1) {
            // Register first video track
            lowerVideoTrack = i + 1;
427
        }
428
        mlt.appendChild(playlist);
429
    }
430

431 432
    QDomElement track0 = doc.createElement(QStringLiteral("track"));
    track0.setAttribute(QStringLiteral("producer"), QStringLiteral("black_track"));
433 434
    tractor.appendChild(track0);

435
    // create audio and video tracks
436
    for (int i = 0; i < total; ++i) {
437
        QDomElement track = doc.createElement(QStringLiteral("track"));
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
438
        track.setAttribute(QStringLiteral("producer"), "playlist" + QString::number(i+1));
439
        if (tracks.at(i).type == AudioTrack) {
440
            track.setAttribute(QStringLiteral("hide"), QStringLiteral("video"));
441 442
        } else if (tracks.at(i).isBlind) {
            if (tracks.at(i).isMute) {
443
                track.setAttribute(QStringLiteral("hide"), QStringLiteral("all"));
444
            }
445
            else track.setAttribute(QStringLiteral("hide"), QStringLiteral("video"));
446
        }
447
        else if (tracks.at(i).isMute)
448
            track.setAttribute(QStringLiteral("hide"), QStringLiteral("audio"));
449 450 451
        tractor.appendChild(track);
    }

452
    for (int i = 0; i < total; i++) {
453 454
        QDomElement transition = doc.createElement(QStringLiteral("transition"));
        transition.setAttribute(QStringLiteral("always_active"), QStringLiteral("1"));
455

456 457 458
        QDomElement property = doc.createElement(QStringLiteral("property"));
        property.setAttribute(QStringLiteral("name"), QStringLiteral("mlt_service"));
        value = doc.createTextNode(QStringLiteral("mix"));
459 460 461
        property.appendChild(value);
        transition.appendChild(property);

462 463
        property = doc.createElement(QStringLiteral("property"));
        property.setAttribute(QStringLiteral("name"), QStringLiteral("a_track"));
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
464
        QDomText value = doc.createTextNode(QStringLiteral("0"));
465 466 467
        property.appendChild(value);
        transition.appendChild(property);

468 469
        property = doc.createElement(QStringLiteral("property"));
        property.setAttribute(QStringLiteral("name"), QStringLiteral("b_track"));
470
        value = doc.createTextNode(QString::number(i + 1));
471 472 473
        property.appendChild(value);
        transition.appendChild(property);

474 475 476
        property = doc.createElement(QStringLiteral("property"));
        property.setAttribute(QStringLiteral("name"), QStringLiteral("combine"));
        value = doc.createTextNode(QStringLiteral("1"));
477 478 479
        property.appendChild(value);
        transition.appendChild(property);

480 481 482
        property = doc.createElement(QStringLiteral("property"));
        property.setAttribute(QStringLiteral("name"), QStringLiteral("internal_added"));
        value = doc.createTextNode(QStringLiteral("237"));
483 484
        property.appendChild(value);
        transition.appendChild(property);
485

486
        tractor.appendChild(transition);
487
        if (i >= lowerVideoTrack && tracks.at(i).type == VideoTrack) {
488
            // Only add composite transition if both tracks are video
489 490 491
            transition = doc.createElement(QStringLiteral("transition"));
            property = doc.createElement(QStringLiteral("property"));
            property.setAttribute(QStringLiteral("name"), QStringLiteral("mlt_service"));
492 493 494
            property.appendChild(doc.createTextNode(KdenliveSettings::gpu_accel() ? "movit.overlay" : "frei0r.cairoblend"));
            transition.appendChild(property);

495 496
            property = doc.createElement(QStringLiteral("property"));
            property.setAttribute(QStringLiteral("name"), QStringLiteral("a_track"));
497
            property.appendChild(doc.createTextNode(QString::number(lowerVideoTrack)));
498 499
            transition.appendChild(property);

500 501
            property = doc.createElement(QStringLiteral("property"));
            property.setAttribute(QStringLiteral("name"), QStringLiteral("b_track"));
502
            property.appendChild(doc.createTextNode(QString::number(i+1)));
503 504
            transition.appendChild(property);

505 506 507
            property = doc.createElement(QStringLiteral("property"));
            property.setAttribute(QStringLiteral("name"), QStringLiteral("internal_added"));
            property.appendChild(doc.createTextNode(QStringLiteral("237")));
508 509 510 511
            transition.appendChild(property);

            tractor.appendChild(transition);
        }
512
    }
513
    mlt.appendChild(tractor);
514
    return doc;
515 516
}

517
bool KdenliveDoc::useProxy() const
518
{
519
    return m_documentProperties.value(QStringLiteral("enableproxy")).toInt();
520 521
}

522 523 524 525 526 527 528 529 530
bool KdenliveDoc::autoGenerateProxy(int width) const
{
    return m_documentProperties.value(QStringLiteral("generateproxy")).toInt() && width > m_documentProperties.value(QStringLiteral("proxyminsize")).toInt();
}

bool KdenliveDoc::autoGenerateImageProxy(int width) const
{
    return m_documentProperties.value(QStringLiteral("generateimageproxy")).toInt() && width > m_documentProperties.value(QStringLiteral("proxyimageminsize")).toInt();
}
531

532
void KdenliveDoc::slotAutoSave()
533
{
534 535
    if (m_render && m_autosave) {
        if (!m_autosave->isOpen() && !m_autosave->open(QIODevice::ReadWrite)) {
536
            // show error: could not open the autosave file
537
            qDebug() << "ERROR; CANNOT CREATE AUTOSAVE FILE";
538
        }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
539
        //qDebug() << "// AUTOSAVE FILE: " << m_autosave->fileName();
540
        QDomDocument sceneList = xmlSceneList(m_render->sceneList());
541 542 543 544 545 546 547 548
        if (sceneList.isNull()) {
            //Make sure we don't save if scenelist is corrupted
            KMessageBox::error(QApplication::activeWindow(), i18n("Cannot write to file %1, scene list is corrupted.", m_autosave->fileName()));
            return;
        }
        m_autosave->resize(0); 
        m_autosave->write(sceneList.toString().toUtf8());
        m_autosave->flush();
549
    }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
550 551
}

552
void KdenliveDoc::setZoom(int horizontal, int vertical)
553
{
554 555
    m_documentProperties[QStringLiteral("zoom")] = QString::number(horizontal);
    m_documentProperties[QStringLiteral("verticalzoom")] = QString::number(vertical);
556 557
}

558
QPoint KdenliveDoc::zoom() const
559
{
560
    return QPoint(m_documentProperties.value(QStringLiteral("zoom")).toInt(), m_documentProperties.value(QStringLiteral("verticalzoom")).toInt());
561 562
}

563 564
void KdenliveDoc::setZone(int start, int end)
{
565 566
    m_documentProperties[QStringLiteral("zonein")] = QString::number(start);
    m_documentProperties[QStringLiteral("zoneout")] = QString::number(end);
567 568
}

569 570
QPoint KdenliveDoc::zone() const
{
571
    return QPoint(m_documentProperties.value(QStringLiteral("zonein")).toInt(), m_documentProperties.value(QStringLiteral("zoneout")).toInt());
572 573
}

574
QDomDocument KdenliveDoc::xmlSceneList(const QString &scene)
575
{
576 577
    QDomDocument sceneList;
    sceneList.setContent(scene, true);
578
    QDomElement mlt = sceneList.firstChildElement(QStringLiteral("mlt"));
579
    if (mlt.isNull() || !mlt.hasChildNodes()) {
580 581
        //scenelist is corrupted
        return sceneList;
582
    }
583

584
    // Set playlist audio volume to 100%
585
    QDomElement tractor = mlt.firstChildElement(QStringLiteral("tractor"));
586
    if (!tractor.isNull()) {
587
        QDomNodeList props = tractor.elementsByTagName(QStringLiteral("property"));
588
        for (int i = 0; i < props.count(); ++i) {
589 590
            if (props.at(i).toElement().attribute(QStringLiteral("name")) == QLatin1String("meta.volume")) {
                props.at(i).firstChild().setNodeValue(QStringLiteral("1"));
591 592 593 594
                break;
            }
        }
    }
595
    QDomNodeList pls = mlt.elementsByTagName(QStringLiteral("playlist"));
596 597
    QDomElement mainPlaylist;
    for (int i = 0; i < pls.count(); ++i) {
598
        if (pls.at(i).toElement().attribute(QStringLiteral("id")) == pCore->binController()->binPlaylistId()) {
599 600 601 602
            mainPlaylist = pls.at(i).toElement();
            break;
        }
    }
603

604
    // check if project contains custom effects to embed them in project file
605
    QDomNodeList effects = mlt.elementsByTagName(QStringLiteral("filter"));
606
    int maxEffects = effects.count();
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
607
    //qDebug() << "// FOUD " << maxEffects << " EFFECTS+++++++++++++++++++++";
608
    QMap <QString, QString> effectIds;
609
    for (int i = 0; i < maxEffects; ++i) {
610 611 612 613
        QDomNode m = effects.at(i);
        QDomNodeList params = m.childNodes();
        QString id;
        QString tag;
614
        for (int j = 0; j < params.count(); ++j) {
615
            QDomElement e = params.item(j).toElement();
616
            if (e.attribute(QStringLiteral("name")) == QLatin1String("kdenlive_id")) {
617 618
                id = e.firstChild().nodeValue();
            }
619
            if (e.attribute(QStringLiteral("name")) == QLatin1String("tag")) {
620 621 622 623 624
                tag = e.firstChild().nodeValue();
            }
            if (!id.isEmpty() && !tag.isEmpty()) effectIds.insert(id, tag);
        }
    }
625
    //TODO: find a way to process this before rendering MLT scenelist to xml
626
    QDomDocument customeffects = initEffects::getUsedCustomEffects(effectIds);
627 628 629
    if (customeffects.documentElement().childNodes().count() > 0) {
	EffectsList::setProperty(mainPlaylist, QStringLiteral("kdenlive:customeffects"), customeffects.toString());
    }
630
    //addedXml.appendChild(sceneList.importNode(customeffects.documentElement(), true));
631

632
    //TODO: move metadata to previous step in saving process
633
    QDomElement docmetadata = sceneList.createElement(QStringLiteral("documentmetadata"));
634 635 636 637 638
    QMapIterator<QString, QString> j(m_documentMetadata);
    while (j.hasNext()) {
        j.next();
        docmetadata.setAttribute(j.key(), j.value());
    }
639
    //addedXml.appendChild(docmetadata);
640

641 642 643
    return sceneList;
}

644 645 646 647 648 649
QString KdenliveDoc::documentNotes() const
{
    QString text = m_notesWidget->toPlainText().simplified();
    if (text.isEmpty()) return QString();
    return m_notesWidget->toHtml();
}
650

651
bool KdenliveDoc::saveSceneList(const QString &path, const QString &scene)
652
{
653
    QDomDocument sceneList = xmlSceneList(scene);
654 655
    if (sceneList.isNull()) {
        //Make sure we don't save if scenelist is corrupted
656
        KMessageBox::error(QApplication::activeWindow(), i18n("Cannot write to file %1, scene list is corrupted.", path));
657 658
        return false;
    }
659 660
    
    // Backup current version
661
    backupLastSavedVersion(path);
662
    QFile file(path);
663
    
664
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
665
        qWarning() << "//////  ERROR writing to file: " << path;
666
        KMessageBox::error(QApplication::activeWindow(), i18n("Cannot write to file %1", path));
667
        return false;
668
    }
669

670
    file.write(sceneList.toString().toUtf8());
671
    if (file.error() != QFile::NoError) {
672
        KMessageBox::error(QApplication::activeWindow(), i18n("Cannot write to file %1", path));
673 674 675
        file.close();
        return false;
    }
676
    file.close();
677 678 679
    cleanupBackupFiles();
    QFileInfo info(file);
    QString fileName = QUrl::fromLocalFile(path).fileName().section('.', 0, -2);
680 681
    fileName.append('-' + m_documentProperties.value(QStringLiteral("documentid")));
    fileName.append(info.lastModified().toString(QStringLiteral("-yyyy-MM-dd-hh-mm")));
682 683 684
    fileName.append(".kdenlive.png");
    QDir backupFolder(m_projectFolder.path() + "/.backup");
    emit saveTimelinePreview(backupFolder.absoluteFilePath(fileName));
685
    return true;
686 687
}

688 689
ClipManager *KdenliveDoc::clipManager()
{
690
    return m_clipManager;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
691 692
}

693 694 695 696 697
QString KdenliveDoc::groupsXml() const
{
    return m_clipManager->groupsXml();
}

698
QUrl KdenliveDoc::projectFolder() const
699
{
700
    //if (m_projectFolder.isEmpty()) return QUrl(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "//projects/");
701 702 703
    return m_projectFolder;
}

704
void KdenliveDoc::setProjectFolder(QUrl url)
705
{
706
    if (url == m_projectFolder) return;
707
    setModified(true);
708 709 710
    QDir dir(url.toLocalFile());
    if (!dir.exists()) {
        dir.mkpath(dir.absolutePath());
711
    }
712 713 714 715
    dir.mkdir(QStringLiteral("titles"));
    dir.mkdir(QStringLiteral("thumbs"));
    dir.mkdir(QStringLiteral("proxy"));
    dir.mkdir(QStringLiteral(".backup"));
716
    if (KMessageBox::questionYesNo(QApplication::activeWindow(), i18n("You have changed the project folder. Do you want to copy the cached data from %1 to the new folder %2?", m_projectFolder.path(), url.path())) == KMessageBox::Yes) moveProjectData(url);
717
    m_projectFolder = url;
718 719

    updateProjectFolderPlacesEntry();
720 721
}

722
void KdenliveDoc::moveProjectData(const QUrl &url)
723
{
724
    QList <ClipController*> list = pCore->binController()->getControllerList();
725
    QList<QUrl> cacheUrls;
726
    for (int i = 0; i < list.count(); ++i) {
727
        ClipController *clip = list.at(i);
728
        if (clip->clipType() == Text) {
729
            // the image for title clip must be moved
730
            QUrl oldUrl = clip->clipUrl();
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
731
            QUrl newUrl = QUrl::fromLocalFile(url.toLocalFile() + QDir::separator() + "titles/" + oldUrl.fileName());
732
            KIO::Job *job = KIO::copy(oldUrl, newUrl);
733
            if (job->exec()) clip->setProperty(QStringLiteral("resource"), newUrl.path());
734 735
        }
        QString hash = clip->getClipHash();
736
        QUrl oldVideoThumbUrl = QUrl::fromLocalFile(m_projectFolder.path() + QDir::separator() + "thumbs/" + hash + ".png");
737
        if (QFile::exists(oldVideoThumbUrl.path())) {
738
            cacheUrls << oldVideoThumbUrl;
739
        }
740
        QUrl oldAudioThumbUrl = QUrl::fromLocalFile(m_projectFolder.path() + QDir::separator() + "thumbs/" + hash + ".thumb");
741
        if (QFile::exists(oldAudioThumbUrl.path())) {
742
            cacheUrls << oldAudioThumbUrl;
743
        }
744
        QUrl oldVideoProxyUrl = QUrl::fromLocalFile(m_projectFolder.path() + QDir::separator() + "proxy/" + hash + '.' + KdenliveSettings::proxyextension());
745
        if (QFile::exists(oldVideoProxyUrl.path())) {