kdenlivedoc.cpp 73.1 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 24 25
#include "kdenlivedoc.h"
#include "docclipbase.h"
#include "documentchecker.h"
#include "documentvalidator.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/projectlist.h"
Vincent PINON's avatar
Vincent PINON committed
32 33
#include "effectslist/initeffects.h"
#include "dialogs/profilesdialog.h"
Vincent PINON's avatar
Vincent PINON committed
34
#include "titler/titlewidget.h"
35 36
#include "project/notesplugin.h"
#include "project/dialogs/noteswidget.h"
37

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
38
#include <KMessageBox>
39
#include <QProgressDialog>
40
#include <KLocalizedString>
41
#include <KIO/CopyJob>
42
#include <KIO/JobUiDelegate>
43 44
#include <KBookmarkManager>
#include <KBookmark>
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
45

46 47
#include <QCryptographicHash>
#include <QFile>
48 49
#include <QDebug>
#include <QFileDialog>
50
#include <QInputDialog>
51
#include <QDomImplementation>
52 53 54
#include <QUndoGroup>
#include <QTimer>
#include <QUndoStack>
55

56
#include <mlt++/Mlt.h>
57
#include <KJobWidgets/KJobWidgets>
58
#include <QStandardPaths>
59

60 61 62
#include "locale.h"


63
const double DOCUMENTVERSION = 0.88;
64

65
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, QProgressDialog *progressDialog) :
66 67 68 69
    QObject(parent),
    m_autosave(NULL),
    m_url(url),
    m_render(render),
70
    m_notesWidget(notes->widget()),
71 72 73
    m_commandStack(new QUndoStack(undoGroup)),
    m_modified(false),
    m_projectFolder(projectFolder)
74
{
75 76 77 78 79 80 81 82 83 84 85 86
    // 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;

87
    m_clipManager = new ClipManager(this);
88 89
    m_autoSaveTimer = new QTimer(this);
    m_autoSaveTimer->setSingleShot(true);
Laurent Montel's avatar
Laurent Montel committed
90
    connect(m_clipManager, SIGNAL(displayMessage(QString,int)), parent, SLOT(slotGotProgressInfo(QString,int)));
Alberto Villa's avatar
Alberto Villa committed
91
    bool success = false;
92 93

    // init default document properties
94 95 96
    m_documentProperties["zoom"] = '7';
    m_documentProperties["verticalzoom"] = '1';
    m_documentProperties["zonein"] = '0';
97
    m_documentProperties["zoneout"] = "100";
98 99
    m_documentProperties["enableproxy"] = QString::number((int) KdenliveSettings::enableproxy());
    m_documentProperties["proxyparams"] = KdenliveSettings::proxyparams();
100
    m_documentProperties["proxyextension"] = KdenliveSettings::proxyextension();
101 102
    m_documentProperties["generateproxy"] = QString::number((int) KdenliveSettings::generateproxy());
    m_documentProperties["proxyminsize"] = QString::number(KdenliveSettings::proxyminsize());
103 104
    m_documentProperties["generateimageproxy"] = QString::number((int) KdenliveSettings::generateimageproxy());
    m_documentProperties["proxyimageminsize"] = QString::number(KdenliveSettings::proxyimageminsize());
105 106 107 108 109 110 111
#if QT_VERSION >= 0x040700
    m_documentProperties["documentid"] = QString::number(QDateTime::currentMSecsSinceEpoch());
#else
    QDateTime date = QDateTime::currentDateTime();
    m_documentProperties["documentid"] = QString::number(date.toTime_t());
#endif

112 113 114 115 116 117
    // Load properties
    QMapIterator<QString, QString> i(properties);
    while (i.hasNext()) {
        i.next();
        m_documentProperties[i.key()] = i.value();
    }
118 119 120 121 122 123 124
    
    // Load metadata
    QMapIterator<QString, QString> j(metadata);
    while (j.hasNext()) {
        j.next();
        m_documentMetadata[j.key()] = j.value();
    }
125

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
126
    if (QLocale().decimalPoint() != QLocale::system().decimalPoint()) {
127
        setlocale(LC_NUMERIC, "");
128 129 130
        QLocale systemLocale = QLocale::system();
        systemLocale.setNumberOptions(QLocale::OmitGroupSeparator);
        QLocale::setDefault(systemLocale);
131
        // locale conversion might need to be redone
132
        initEffects::parseEffectFiles(setlocale(LC_NUMERIC, NULL));
133 134
    }

135
    *openBackup = false;
136
    
137
    if (!url.isEmpty()) {
138 139
        QFile file(url.path());
        if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
140
            // The file cannot be opened
141
            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) {
142 143 144 145
                *openBackup = true;
            }
            //KMessageBox::error(parent, KIO::NetAccess::lastErrorString());
        }
Alberto Villa's avatar
Alberto Villa committed
146
        else {
147
            QString errorMsg;
148 149
            int line;
            int col;
150
            QDomImplementation::setInvalidDataPolicy(QDomImplementation::DropInvalidChars);
151
            success = m_document.setContent(&file, false, &errorMsg, &line, &col);
152
            file.close();
153

154 155
            if (!success) {
                // It is corrupted
156
                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")));
157
                if (answer == KMessageBox::Yes) {
158 159 160 161 162 163 164 165 166 167 168
                    *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;
169
                            for (int j = 0; j < line && errorPos < playlist.length(); ++j) {
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
                                errorPos = playlist.indexOf("\n", errorPos);
                                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();
                            QDomElement info = mlt.firstChildElement("kdenlivedoc");
                            if (!info.isNull()) info.setAttribute("modified", 1);
                        }
                    }
                }
192
            }
193
            if (success) {
194
                parent->slotGotProgressInfo(i18n("Validating"), 0);
195
                qApp->processEvents();
196
                DocumentValidator validator(m_document, url);
Alberto Villa's avatar
Alberto Villa committed
197
                success = validator.isProject();
198
                if (!success) {
199
                    // It is not a project file
200
                    parent->slotGotProgressInfo(i18n("File %1 is not a Kdenlive project file", m_url.path()), 100);
201 202 203
                    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;
                    }
204
                } else {
205
                    /*
Alberto Villa's avatar
Alberto Villa committed
206 207
                     * Validate the file against the current version (upgrade
                     * and recover it if needed). It is NOT a passive operation
208
                     */
Alberto Villa's avatar
Alberto Villa committed
209 210
                    // TODO: backup the document or alert the user?
                    success = validator.validate(DOCUMENTVERSION);
211
                    if (success) { // Let the validator handle error messages
212
                        parent->slotGotProgressInfo(i18n("Check missing clips"), 0);
213
                        qApp->processEvents();
214 215 216
                        QDomNodeList infoproducers = m_document.elementsByTagName("kdenlive_producer");
                        success = checkDocumentClips(infoproducers);
                        if (success) {
217
                            if (m_document.documentElement().attribute("modified") == "1") setModified(true);
218 219 220 221
                            parent->slotGotProgressInfo(i18n("Loading"), 0);
                            QDomElement mlt = m_document.firstChildElement("mlt");
                            QDomElement infoXml = mlt.firstChildElement("kdenlivedoc");

222 223 224
                            // Set profile, fps, etc for the document
                            setProfilePath(infoXml.attribute("profile"));

225 226 227 228
                            // Check embedded effects
                            QDomElement customeffects = infoXml.firstChildElement("customeffects");
                            if (!customeffects.isNull() && customeffects.hasChildNodes()) {
                                parent->slotGotProgressInfo(i18n("Importing project effects"), 0);
229
                                qApp->processEvents();
230 231 232 233
                                if (saveCustomEffects(customeffects.childNodes())) parent->slotReloadEffects();
                            }

                            QDomElement e;
234 235 236 237
                            // Read notes
                            QDomElement notesxml = infoXml.firstChildElement("documentnotes");
                            if (!notesxml.isNull()) m_notesWidget->setText(notesxml.firstChild().nodeValue());

238 239 240 241 242
                            // Build tracks
                            QDomElement tracksinfo = infoXml.firstChildElement("tracksinfo");
                            if (!tracksinfo.isNull()) {
                                QDomNodeList trackslist = tracksinfo.childNodes();
                                int maxchild = trackslist.count();
243
                                for (int k = 0; k < maxchild; ++k) {
244 245 246 247
                                    e = trackslist.at(k).toElement();
                                    if (e.tagName() == "trackinfo") {
                                        TrackInfo projectTrack;
                                        if (e.attribute("type") == "audio")
248
                                            projectTrack.type = AudioTrack;
249
                                        else
250
                                            projectTrack.type = VideoTrack;
251 252 253 254
                                        projectTrack.isMute = e.attribute("mute").toInt();
                                        projectTrack.isBlind = e.attribute("blind").toInt();
                                        projectTrack.isLocked = e.attribute("locked").toInt();
                                        projectTrack.trackName = e.attribute("trackname");
255
                                        projectTrack.effectsList = EffectsList(true);
256 257
                                        m_tracksList.append(projectTrack);
                                    }
Alberto Villa's avatar
Alberto Villa committed
258
                                }
259
                                mlt.removeChild(tracksinfo);
260
                            }
261
                            QStringList expandedFolders;
262
                            QDomNodeList folders = m_document.elementsByTagName("folder");
263
                            for (int i = 0; i < folders.count(); ++i) {
264
                                e = folders.item(i).cloneNode().toElement();
265
                                if (e.hasAttribute("opened")) expandedFolders.append(e.attribute("id"));
266 267
                                m_clipManager->addFolder(e.attribute("id"), e.attribute("name"));
                            }
268
                            m_documentProperties["expandedfolders"] = expandedFolders.join(";");
269 270 271 272 273

                            const int infomax = infoproducers.count();
                            QDomNodeList producers = m_document.elementsByTagName("producer");
                            const int max = producers.count();

274
                            if (!progressDialog) {
275 276 277 278
                                progressDialog = new QProgressDialog(parent);
                                progressDialog->setWindowTitle(i18n("Loading project"));
                                progressDialog->setLabelText(i18n("Adding clips"));
                                progressDialog->setCancelButton(0);
279 280 281
                            } else {
                                progressDialog->setLabelText(i18n("Adding clips"));
                            }
282
                            progressDialog->setMaximum(infomax);
283 284
                            progressDialog->show();
                            qApp->processEvents();
285

286
                            for (int i = 0; i < infomax; ++i) {
287 288
                                e = infoproducers.item(i).cloneNode().toElement();
                                QString prodId = e.attribute("id");
289
                                if (!e.isNull() && prodId != "black" && !prodId.startsWith(QLatin1String("slowmotion"))) {
290 291 292
                                    e.setTagName("producer");
                                    // Get MLT's original producer properties
                                    QDomElement orig;
293
                                    for (int j = 0; j < max; ++j) {
294 295
                                        QDomNode o = producers.item(j);
                                        QString origId = o.attributes().namedItem("id").nodeValue().section('_', 0, 0);
296
                                        if (origId == prodId) {
297
                                            orig = o.cloneNode().toElement();
298 299 300
                                            break;
                                        }
                                    }
301

302 303 304 305 306 307
                                    if (!addClipInfo(e, orig, prodId)) {
                                        // The user manually aborted the loading.
                                        success = false;
                                        emit resetProjectList();
                                        m_tracksList.clear();
                                        m_clipManager->clear();
Alberto Villa's avatar
Alberto Villa committed
308 309
                                        break;
                                    }
310
                                }
311
                                if (i % 10 == 0)
312
                                    progressDialog->setValue(i);
313
                            }
314 315 316 317 318 319

                            if (success) {
                                QDomElement markers = infoXml.firstChildElement("markers");
                                if (!markers.isNull()) {
                                    QDomNodeList markerslist = markers.childNodes();
                                    int maxchild = markerslist.count();
320
                                    for (int k = 0; k < maxchild; ++k) {
321
                                        e = markerslist.at(k).toElement();
322
                                        if (e.tagName() == "marker") {
323 324
                                            CommentedTime marker(GenTime(e.attribute("time").toDouble()), e.attribute("comment"), e.attribute("type").toInt());
                                            DocClipBase *baseClip = m_clipManager->getClipById(e.attribute("id"));
325
                                            if (baseClip) baseClip->addSnapMarker(marker);
326
                                            else qDebug()<< " / / Warning, missing clip: "<< e.attribute("id");
327
                                        }
Alberto Villa's avatar
Alberto Villa committed
328
                                    }
329
                                    infoXml.removeChild(markers);
330
                                }
331

332
                                m_projectFolder = QUrl(infoXml.attribute("projectfolder"));
333 334
                                QDomElement docproperties = infoXml.firstChildElement("documentproperties");
                                QDomNamedNodeMap props = docproperties.attributes();
335
                                for (int i = 0; i < props.count(); ++i)
336
                                    m_documentProperties.insert(props.item(i).nodeName(), props.item(i).nodeValue());
337 338
                                docproperties = infoXml.firstChildElement("documentmetadata");
                                props = docproperties.attributes();
339
                                for (int i = 0; i < props.count(); ++i)
340
                                    m_documentMetadata.insert(props.item(i).nodeName(), props.item(i).nodeValue());
341

342
                                if (validator.isModified()) setModified(true);
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
343
                                //qDebug() << "Reading file: " << url.path() << ", found clips: " << producers.count();
344
                            }
345 346
                        }
                    }
347
                }
348
            }
349
        }
350
    }
351
    
Alberto Villa's avatar
Alberto Villa committed
352
    // Something went wrong, or a new file was requested: create a new project
353
    if (!success) {
354
        m_url.clear();
355
        setProfilePath(profileName);
356
        m_document = createEmptyDocument(tracks.x(), tracks.y());
357
    }
358

359 360 361 362 363 364 365 366 367 368 369
    // Ask to create the project directory if it does not exist
    if (!QFile::exists(m_projectFolder.path())) {
        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
370 371

    // Make sure the project folder is usable
372
    if (m_projectFolder.isEmpty() || !QFile::exists(m_projectFolder.path())) {
Alberto Villa's avatar
Alberto Villa committed
373
        KMessageBox::information(parent, i18n("Document project folder is invalid, setting it to the default one: %1", KdenliveSettings::defaultprojectfolder()));
374
        m_projectFolder = QUrl(KdenliveSettings::defaultprojectfolder());
Alberto Villa's avatar
Alberto Villa committed
375 376 377
    }

    // Make sure that the necessary folders exist
378 379 380 381
    QDir dir(m_projectFolder.path());
    dir.mkdir("titles");
    dir.mkdir("thumbs");
    dir.mkdir("proxy");
382

383 384
    updateProjectFolderPlacesEntry();

Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
385
    ////qDebug() << "// SETTING SCENE LIST:\n\n" << m_document.toString();
386
    connect(m_autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave()));
387
    connect(m_render, SIGNAL(addClip(QUrl,stringMap)), this, SLOT(slotAddClipFile(QUrl,stringMap)));
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
388 389
}

390 391
KdenliveDoc::~KdenliveDoc()
{
392
    m_autoSaveTimer->stop();
393
    delete m_commandStack;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
394
    //qDebug() << "// DEL CLP MAN";
395
    delete m_clipManager;
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
396
    //qDebug() << "// DEL CLP MAN done";
397
    delete m_autoSaveTimer;
398 399
    if (m_autosave) {
        if (!m_autosave->fileName().isEmpty()) m_autosave->remove();
400 401
        delete m_autosave;
    }
402 403
}

404
int KdenliveDoc::setSceneList()
405
{
406
    m_render->resetProfile(KdenliveSettings::current_profile(), true);
407
    if (m_render->setSceneList(m_document.toString(), m_documentProperties.value("position").toInt()) == -1) {
408 409 410
        // INVALID MLT Consumer, something is wrong
        return -1;
    }
411
    m_documentProperties.remove("position");
412 413
    // m_document xml is now useless, clear it
    m_document.clear();
414
    return 0;
415 416
}

417
QDomDocument KdenliveDoc::createEmptyDocument(int videotracks, int audiotracks)
418
{
419 420
    m_tracksList.clear();

421 422 423
    // 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
424
    for (int i = 0; i < audiotracks; ++i) {
425
        TrackInfo audioTrack;
426
        audioTrack.type = AudioTrack;
427 428 429
        audioTrack.isMute = false;
        audioTrack.isBlind = true;
        audioTrack.isLocked = false;
430
        audioTrack.trackName = QString("Audio ") + QString::number(audiotracks - i);
431
        audioTrack.duration = 0;
432
        audioTrack.effectsList = EffectsList(true);
433
        m_tracksList.append(audioTrack);
434

435
    }
436
    for (int i = 0; i < videotracks; ++i) {
437
        TrackInfo videoTrack;
438
        videoTrack.type = VideoTrack;
439 440 441
        videoTrack.isMute = false;
        videoTrack.isBlind = false;
        videoTrack.isLocked = false;
442
        videoTrack.trackName = QString("Video ") + QString::number(videotracks - i);
443
        videoTrack.duration = 0;
444
        videoTrack.effectsList = EffectsList(true);
445 446 447 448 449
        m_tracksList.append(videoTrack);
    }
    return createEmptyDocument(m_tracksList);
}

450
QDomDocument KdenliveDoc::createEmptyDocument(const QList <TrackInfo> &tracks)
451 452 453 454
{
    // Creating new document
    QDomDocument doc;
    QDomElement mlt = doc.createElement("mlt");
455
    mlt.setAttribute("LC_NUMERIC", "");
456
    doc.appendChild(mlt);
457
    
458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473
    // Create black producer
    // For some unknown reason, we have to build the black producer here and not in renderer.cpp, otherwise
    // the composite transitions with the black track are corrupted.
    QDomElement blk = doc.createElement("producer");
    blk.setAttribute("in", 0);
    blk.setAttribute("out", 500);
    blk.setAttribute("id", "black");

    QDomElement property = doc.createElement("property");
    property.setAttribute("name", "mlt_type");
    QDomText value = doc.createTextNode("producer");
    property.appendChild(value);
    blk.appendChild(property);

    property = doc.createElement("property");
    property.setAttribute("name", "aspect_ratio");
474
    value = doc.createTextNode(QString::number(0));
475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504
    property.appendChild(value);
    blk.appendChild(property);

    property = doc.createElement("property");
    property.setAttribute("name", "length");
    value = doc.createTextNode(QString::number(15000));
    property.appendChild(value);
    blk.appendChild(property);

    property = doc.createElement("property");
    property.setAttribute("name", "eof");
    value = doc.createTextNode("pause");
    property.appendChild(value);
    blk.appendChild(property);

    property = doc.createElement("property");
    property.setAttribute("name", "resource");
    value = doc.createTextNode("black");
    property.appendChild(value);
    blk.appendChild(property);

    property = doc.createElement("property");
    property.setAttribute("name", "mlt_service");
    value = doc.createTextNode("colour");
    property.appendChild(value);
    blk.appendChild(property);

    mlt.appendChild(blk);


505 506
    QDomElement tractor = doc.createElement("tractor");
    tractor.setAttribute("id", "maintractor");
507
    //QDomElement multitrack = doc.createElement("multitrack");
508 509
    QDomElement playlist = doc.createElement("playlist");
    playlist.setAttribute("id", "black_track");
510
    mlt.appendChild(playlist);
511

512 513
    QDomElement blank0 = doc.createElement("entry");
    blank0.setAttribute("in", "0");
514
    blank0.setAttribute("out", "1");
515 516
    blank0.setAttribute("producer", "black");
    playlist.appendChild(blank0);
517 518

    // create playlists
519
    int total = tracks.count() + 1;
520

521
    for (int i = 1; i < total; ++i) {
522 523
        QDomElement playlist = doc.createElement("playlist");
        playlist.setAttribute("id", "playlist" + QString::number(i));
524
        mlt.appendChild(playlist);
525
    }
526 527 528 529 530

    QDomElement track0 = doc.createElement("track");
    track0.setAttribute("producer", "black_track");
    tractor.appendChild(track0);

531
    // create audio and video tracks
532
    for (int i = 1; i < total; ++i) {
533 534
        QDomElement track = doc.createElement("track");
        track.setAttribute("producer", "playlist" + QString::number(i));
535
        if (tracks.at(i - 1).type == AudioTrack) {
536
            track.setAttribute("hide", "video");
537
        } else if (tracks.at(i - 1).isBlind)
538
            track.setAttribute("hide", "video");
539
        if (tracks.at(i - 1).isMute)
540
            track.setAttribute("hide", "audio");
541 542 543
        tractor.appendChild(track);
    }

544
    for (int i = 2; i < total ; ++i) {
545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578
        QDomElement transition = doc.createElement("transition");
        transition.setAttribute("always_active", "1");

        QDomElement property = doc.createElement("property");
        property.setAttribute("name", "a_track");
        QDomText value = doc.createTextNode(QString::number(1));
        property.appendChild(value);
        transition.appendChild(property);

        property = doc.createElement("property");
        property.setAttribute("name", "b_track");
        value = doc.createTextNode(QString::number(i));
        property.appendChild(value);
        transition.appendChild(property);

        property = doc.createElement("property");
        property.setAttribute("name", "mlt_service");
        value = doc.createTextNode("mix");
        property.appendChild(value);
        transition.appendChild(property);

        property = doc.createElement("property");
        property.setAttribute("name", "combine");
        value = doc.createTextNode("1");
        property.appendChild(value);
        transition.appendChild(property);

        property = doc.createElement("property");
        property.setAttribute("name", "internal_added");
        value = doc.createTextNode("237");
        property.appendChild(value);
        transition.appendChild(property);
        tractor.appendChild(transition);
    }
579
    mlt.appendChild(tractor);
580
    return doc;
581 582
}

583

584
void KdenliveDoc::syncGuides(const QList <Guide *> &guides)
585
{
586
    m_guidesXml.clear();
587 588 589
    QDomElement guideNode = m_guidesXml.createElement("guides");
    m_guidesXml.appendChild(guideNode);
    QDomElement e;
590

591
    for (int i = 0; i < guides.count(); ++i) {
592
        e = m_guidesXml.createElement("guide");
593 594
        e.setAttribute("time", guides.at(i)->position().ms() / 1000);
        e.setAttribute("comment", guides.at(i)->label());
595
        guideNode.appendChild(e);
596
    }
597
    setModified(true);
598 599 600
    emit guidesUpdated();
}

601 602
QDomElement KdenliveDoc::guidesXml() const
{
603
    return m_guidesXml.documentElement();
604 605
}

606 607
void KdenliveDoc::slotAutoSave()
{
608 609
    if (m_render && m_autosave) {
        if (!m_autosave->isOpen() && !m_autosave->open(QIODevice::ReadWrite)) {
610
            // show error: could not open the autosave file
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
611
            //qDebug() << "ERROR; CANNOT CREATE AUTOSAVE FILE";
612
        }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
613
        //qDebug() << "// AUTOSAVE FILE: " << m_autosave->fileName();
614
        saveSceneList(m_autosave->fileName(), m_render->sceneList(), QStringList(), true);
615
    }
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
616 617
}

618
void KdenliveDoc::setZoom(int horizontal, int vertical)
619
{
620 621
    m_documentProperties["zoom"] = QString::number(horizontal);
    m_documentProperties["verticalzoom"] = QString::number(vertical);
622 623
}

624
QPoint KdenliveDoc::zoom() const
625
{
626
    return QPoint(m_documentProperties.value("zoom").toInt(), m_documentProperties.value("verticalzoom").toInt());
627 628
}

629 630
void KdenliveDoc::setZone(int start, int end)
{
631 632
    m_documentProperties["zonein"] = QString::number(start);
    m_documentProperties["zoneout"] = QString::number(end);
633 634
}

635 636
QPoint KdenliveDoc::zone() const
{
637
    return QPoint(m_documentProperties.value("zonein").toInt(), m_documentProperties.value("zoneout").toInt());
638 639
}

640
QDomDocument KdenliveDoc::xmlSceneList(const QString &scene, const QStringList &expandedFolders)
641
{
642 643
    QDomDocument sceneList;
    sceneList.setContent(scene, true);
644
    QDomElement mlt = sceneList.firstChildElement("mlt");
645
    if (mlt.isNull() || !mlt.hasChildNodes()) {
646 647
        //scenelist is corrupted
        return sceneList;
648
    }
649

650 651 652 653
    // Set playlist audio volume to 100%
    QDomElement tractor = mlt.firstChildElement("tractor");
    if (!tractor.isNull()) {
        QDomNodeList props = tractor.elementsByTagName("property");
654
        for (int i = 0; i < props.count(); ++i) {
655 656 657 658 659 660 661
            if (props.at(i).toElement().attribute("name") == "meta.volume") {
                props.at(i).firstChild().setNodeValue("1");
                break;
            }
        }
    }

662
    QDomElement addedXml = sceneList.createElement("kdenlivedoc");
663
    mlt.appendChild(addedXml);
664

665 666 667
    // check if project contains custom effects to embed them in project file
    QDomNodeList effects = mlt.elementsByTagName("filter");
    int maxEffects = effects.count();
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
668
    //qDebug() << "// FOUD " << maxEffects << " EFFECTS+++++++++++++++++++++";
669
    QMap <QString, QString> effectIds;
670
    for (int i = 0; i < maxEffects; ++i) {
671 672 673 674
        QDomNode m = effects.at(i);
        QDomNodeList params = m.childNodes();
        QString id;
        QString tag;
675
        for (int j = 0; j < params.count(); ++j) {
676 677 678 679 680 681 682 683 684 685 686 687 688
            QDomElement e = params.item(j).toElement();
            if (e.attribute("name") == "kdenlive_id") {
                id = e.firstChild().nodeValue();
            }
            if (e.attribute("name") == "tag") {
                tag = e.firstChild().nodeValue();
            }
            if (!id.isEmpty() && !tag.isEmpty()) effectIds.insert(id, tag);
        }
    }
    QDomDocument customeffects = initEffects::getUsedCustomEffects(effectIds);
    addedXml.appendChild(sceneList.importNode(customeffects.documentElement(), true));

689
    QDomElement markers = sceneList.createElement("markers");
Alberto Villa's avatar
Alberto Villa committed
690
    addedXml.setAttribute("version", DOCUMENTVERSION);
691
    addedXml.setAttribute("kdenliveversion", KDENLIVE_VERSION);
692
    addedXml.setAttribute("profile", profilePath());
693
    addedXml.setAttribute("projectfolder", m_projectFolder.path());
694 695 696

    QDomElement docproperties = sceneList.createElement("documentproperties");
    QMapIterator<QString, QString> i(m_documentProperties);
697
    while (i.hasNext()) {
698 699 700
        i.next();
        docproperties.setAttribute(i.key(), i.value());
    }
701
    docproperties.setAttribute("position", m_render->seekPosition().frames(m_fps));
702
    addedXml.appendChild(docproperties);
703 704 705 706 707 708 709 710
    
    QDomElement docmetadata = sceneList.createElement("documentmetadata");
    QMapIterator<QString, QString> j(m_documentMetadata);
    while (j.hasNext()) {
        j.next();
        docmetadata.setAttribute(j.key(), j.value());
    }
    addedXml.appendChild(docmetadata);
711

712
    QDomElement docnotes = sceneList.createElement("documentnotes");
713
    QDomText value = sceneList.createTextNode(m_notesWidget->toHtml());
714 715 716
    docnotes.appendChild(value);
    addedXml.appendChild(docnotes);

717 718 719 720 721 722 723 724 725 726 727 728 729 730
    // Add profile info
    QDomElement profileinfo = sceneList.createElement("profileinfo");
    profileinfo.setAttribute("description", m_profile.description);
    profileinfo.setAttribute("frame_rate_num", m_profile.frame_rate_num);
    profileinfo.setAttribute("frame_rate_den", m_profile.frame_rate_den);
    profileinfo.setAttribute("width", m_profile.width);
    profileinfo.setAttribute("height", m_profile.height);
    profileinfo.setAttribute("progressive", m_profile.progressive);
    profileinfo.setAttribute("sample_aspect_num", m_profile.sample_aspect_num);
    profileinfo.setAttribute("sample_aspect_den", m_profile.sample_aspect_den);
    profileinfo.setAttribute("display_aspect_num", m_profile.display_aspect_num);
    profileinfo.setAttribute("display_aspect_den", m_profile.display_aspect_den);
    addedXml.appendChild(profileinfo);

731 732
    // tracks info
    QDomElement tracksinfo = sceneList.createElement("tracksinfo");
733
    foreach(const TrackInfo & info, m_tracksList) {
734
        QDomElement trackinfo = sceneList.createElement("trackinfo");
735
        if (info.type == AudioTrack) trackinfo.setAttribute("type", "audio");
736 737 738
        trackinfo.setAttribute("mute", info.isMute);
        trackinfo.setAttribute("blind", info.isBlind);
        trackinfo.setAttribute("locked", info.isLocked);
739
        trackinfo.setAttribute("trackname", info.trackName);
740 741 742 743
        tracksinfo.appendChild(trackinfo);
    }
    addedXml.appendChild(tracksinfo);

744 745 746 747
    // save project folders
    QMap <QString, QString> folderlist = m_clipManager->documentFolderList();

    QMapIterator<QString, QString> f(folderlist);
748
    while (f.hasNext()) {
749 750 751 752
        f.next();
        QDomElement folder = sceneList.createElement("folder");
        folder.setAttribute("id", f.key());
        folder.setAttribute("name", f.value());
753
        if (expandedFolders.contains(f.key())) folder.setAttribute("opened", "1");
754 755 756 757
        addedXml.appendChild(folder);
    }

    // Save project clips
758 759
    QDomElement e;
    QList <DocClipBase*> list = m_clipManager->documentClipList();
760
    for (int i = 0; i < list.count(); ++i) {
761
        e = list.at(i)->toXML(true);
762 763 764
        e.setTagName("kdenlive_producer");
        addedXml.appendChild(sceneList.importNode(e, true));
        QList < CommentedTime > marks = list.at(i)->commentedSnapMarkers();
765
        for (int j = 0; j < marks.count(); ++j) {
766 767 768 769
            QDomElement marker = sceneList.createElement("marker");
            marker.setAttribute("time", marks.at(j).time().ms() / 1000);
            marker.setAttribute("comment", marks.at(j).comment());
            marker.setAttribute("id", e.attribute("id"));
770
            marker.setAttribute("type", marks.at(j).markerType());
771 772 773 774
            markers.appendChild(marker);
        }
    }
    addedXml.appendChild(markers);
775 776

    // Add guides
777
    if (!m_guidesXml.isNull()) addedXml.appendChild(sceneList.importNode(m_guidesXml.documentElement(), true));
778

779 780 781
    // Add clip groups
    addedXml.appendChild(sceneList.importNode(m_clipManager->groupsXml(), true));

782
    //wes.appendChild(doc.importNode(kdenliveData, true));
783 784 785
    return sceneList;
}

786
bool KdenliveDoc::saveSceneList(const QString &path, const QString &scene, const QStringList &expandedFolders, bool autosave)
787 788 789 790
{
    QDomDocument sceneList = xmlSceneList(scene, expandedFolders);
    if (sceneList.isNull()) {
        //Make sure we don't save if scenelist is corrupted
791
        KMessageBox::error(QApplication::activeWindow(), i18n("Cannot write to file %1, scene list is corrupted.", path));
792 793
        return false;
    }
794 795 796
    
    // Backup current version
    if (!autosave) backupLastSavedVersion(path);
797
    QFile file(path);
798
    
799
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
Jean-Baptiste Mardelle's avatar
Jean-Baptiste Mardelle committed
800
        qWarning() << "//////  ERROR writing to file: " << path;
801
        KMessageBox::error(QApplication::activeWindow(), i18n("Cannot write to file %1", path));
802
        return false;
803
    }
804

805
    file.write(sceneList.toString().toUtf8());