X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=src%2Fkdenlivedoc.cpp;h=1ce567cb1a1dcacc8d7e2e94bb3f2f21cfea463e;hb=a3eee7be24126f5a3458d488f44cd61c66135e17;hp=0b56aa9494d3a5e90b993e25d7bcb4fbaf3baec0;hpb=000c8e2337fe448a6e35896f3ef1a95dedbb39c7;p=kdenlive diff --git a/src/kdenlivedoc.cpp b/src/kdenlivedoc.cpp index 0b56aa94..127cd32c 100644 --- a/src/kdenlivedoc.cpp +++ b/src/kdenlivedoc.cpp @@ -24,71 +24,185 @@ #include "kdenlivesettings.h" #include "renderer.h" #include "clipmanager.h" -#include "titlewidget.h" +#include "widgets/titlewidget.h" #include "mainwindow.h" #include "documentchecker.h" #include "documentvalidator.h" -#include "kdenlive-config.h" +#include "config-kdenlive.h" +#include "initeffects.h" #include #include #include -#include +#include +#include #include #include #include +#include #include +#include +#include +#include +#include #include #include #include +#include +#include +#include +#include #include -const double DOCUMENTVERSION = 0.84; - -KdenliveDoc::KdenliveDoc(const KUrl &url, const KUrl &projectFolder, QUndoGroup *undoGroup, QString profileName, const QPoint tracks, Render *render, MainWindow *parent) : - QObject(parent), - m_autosave(NULL), - m_url(url), - m_render(render), - m_commandStack(new QUndoStack(undoGroup)), - m_modified(false), - m_projectFolder(projectFolder), - m_documentLoadingStep(0.0), - m_documentLoadingProgress(0), - m_abortLoading(false) -{ - m_clipManager = new ClipManager(this); +#include "locale.h" + + +const double DOCUMENTVERSION = 0.88; + +KdenliveDoc::KdenliveDoc(const KUrl &url, const KUrl &projectFolder, QUndoGroup *undoGroup, const QString &profileName, const QMap & properties, const QMap & metadata, const QPoint &tracks, Render *render, KTextEdit *notes, bool *openBackup, QGLWidget *glContext, MainWindow *parent, KProgressDialog *progressDialog) : + QObject(parent), + m_autosave(NULL), + m_url(url), + m_render(render), + m_notesWidget(notes), + m_commandStack(new QUndoStack(undoGroup)), + m_modified(false), + m_projectFolder(projectFolder) +{ + // 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; + + m_clipManager = new ClipManager(this, glContext); m_autoSaveTimer = new QTimer(this); m_autoSaveTimer->setSingleShot(true); + connect(m_clipManager, SIGNAL(displayMessage(QString,int)), parent, SLOT(slotGotProgressInfo(QString,int))); bool success = false; // init default document properties - m_documentProperties["zoom"] = "7"; - m_documentProperties["verticalzoom"] = "1"; - m_documentProperties["zonein"] = "0"; + m_documentProperties["zoom"] = '7'; + m_documentProperties["verticalzoom"] = '1'; + m_documentProperties["zonein"] = '0'; m_documentProperties["zoneout"] = "100"; + m_documentProperties["enableproxy"] = QString::number((int) KdenliveSettings::enableproxy()); + m_documentProperties["proxyparams"] = KdenliveSettings::proxyparams(); + m_documentProperties["proxyextension"] = KdenliveSettings::proxyextension(); + m_documentProperties["generateproxy"] = QString::number((int) KdenliveSettings::generateproxy()); + m_documentProperties["proxyminsize"] = QString::number(KdenliveSettings::proxyminsize()); + m_documentProperties["generateimageproxy"] = QString::number((int) KdenliveSettings::generateimageproxy()); + m_documentProperties["proxyimageminsize"] = QString::number(KdenliveSettings::proxyimageminsize()); +#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 + + // Load properties + QMapIterator i(properties); + while (i.hasNext()) { + i.next(); + m_documentProperties[i.key()] = i.value(); + } + + // Load metadata + QMapIterator j(metadata); + while (j.hasNext()) { + j.next(); + m_documentMetadata[j.key()] = j.value(); + } + + if (QLocale().decimalPoint() != QLocale::system().decimalPoint()) { + setlocale(LC_NUMERIC, ""); + QLocale systemLocale = QLocale::system(); + systemLocale.setNumberOptions(QLocale::OmitGroupSeparator); + QLocale::setDefault(systemLocale); + // locale conversion might need to be redone + initEffects::parseEffectFiles(setlocale(LC_NUMERIC, NULL)); + } + *openBackup = false; + if (!url.isEmpty()) { QString tmpFile; success = KIO::NetAccess::download(url.path(), tmpFile, parent); - if (!success) // The file cannot be opened - KMessageBox::error(parent, KIO::NetAccess::lastErrorString()); + if (!success) { + // The file cannot be opened + if (KMessageBox::warningContinueCancel(parent, i18n("Cannot open the project file, error is:\n%1\nDo you want to open a backup file?", KIO::NetAccess::lastErrorString()), i18n("Error opening file"), KGuiItem(i18n("Open Backup"))) == KMessageBox::Continue) { + *openBackup = true; + } + //KMessageBox::error(parent, KIO::NetAccess::lastErrorString()); + } else { QFile file(tmpFile); QString errorMsg; - success = m_document.setContent(&file, false, &errorMsg); + int line; + int col; + QDomImplementation::setInvalidDataPolicy(QDomImplementation::DropInvalidChars); + success = m_document.setContent(&file, false, &errorMsg, &line, &col); file.close(); - KIO::NetAccess::removeTempFile(tmpFile); - if (!success) // It is corrupted - KMessageBox::error(parent, errorMsg); - else { - DocumentValidator validator(m_document); + + if (!success) { + // It is corrupted + 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"))); + if (answer == KMessageBox::Yes) { + *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; + for (int j = 0; j < line && errorPos < playlist.length(); j++) { + 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); + } + } + } + } + if (success) { + parent->slotGotProgressInfo(i18n("Validating"), 0); + qApp->processEvents(); + DocumentValidator validator(m_document, url); success = validator.isProject(); - if (!success) // It is not a project file - parent->slotGotProgressInfo(i18n("File %1 is not a Kdenlive project file.", m_url.path()), 100); - else { + if (!success) { + // It is not a project file + parent->slotGotProgressInfo(i18n("File %1 is not a Kdenlive project file", m_url.path()), 100); + 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; + } + } else { /* * Validate the file against the current version (upgrade * and recover it if needed). It is NOT a passive operation @@ -96,119 +210,163 @@ KdenliveDoc::KdenliveDoc(const KUrl &url, const KUrl &projectFolder, QUndoGroup // TODO: backup the document or alert the user? success = validator.validate(DOCUMENTVERSION); if (success) { // Let the validator handle error messages - QDomElement mlt = m_document.firstChildElement("mlt"); - QDomElement infoXml = mlt.firstChildElement("kdenlivedoc"); - - profileName = infoXml.attribute("profile"); - m_projectFolder = KUrl(infoXml.attribute("projectfolder")); - QDomElement docproperties = infoXml.firstChildElement("documentproperties"); - QDomNamedNodeMap props = docproperties.attributes(); - for (int i = 0; i < props.count(); i++) { - m_documentProperties.insert(props.item(i).nodeName(), props.item(i).nodeValue()); - } - // Build tracks - QDomElement e; - QDomElement tracksinfo = infoXml.firstChildElement("tracksinfo"); - TrackInfo projectTrack; - if (!tracksinfo.isNull()) { - QDomNodeList trackslist = tracksinfo.childNodes(); - int maxchild = trackslist.count(); - for (int k = 0; k < maxchild; k++) { - e = trackslist.at(k).toElement(); - if (e.tagName() == "trackinfo") { - if (e.attribute("type") == "audio") projectTrack.type = AUDIOTRACK; - else projectTrack.type = VIDEOTRACK; - projectTrack.isMute = e.attribute("mute").toInt(); - projectTrack.isBlind = e.attribute("blind").toInt(); - projectTrack.isLocked = e.attribute("locked").toInt(); - m_tracksList.append(projectTrack); - } - } - mlt.removeChild(tracksinfo); - } - QDomNodeList producers = m_document.elementsByTagName("producer"); + parent->slotGotProgressInfo(i18n("Check missing clips"), 0); + qApp->processEvents(); QDomNodeList infoproducers = m_document.elementsByTagName("kdenlive_producer"); - if (checkDocumentClips(infoproducers) == false) m_abortLoading = true; - const int max = producers.count(); - const int infomax = infoproducers.count(); - - QDomNodeList folders = m_document.elementsByTagName("folder"); - for (int i = 0; i < folders.count(); i++) { - e = folders.item(i).cloneNode().toElement(); - m_clipManager->addFolder(e.attribute("id"), e.attribute("name")); - } + success = checkDocumentClips(infoproducers); + if (success) { + if (m_document.documentElement().attribute("modified") == "1") setModified(true); + parent->slotGotProgressInfo(i18n("Loading"), 0); + QDomElement mlt = m_document.firstChildElement("mlt"); + QDomElement infoXml = mlt.firstChildElement("kdenlivedoc"); + + // Set profile, fps, etc for the document + setProfilePath(infoXml.attribute("profile")); + + // Check embedded effects + QDomElement customeffects = infoXml.firstChildElement("customeffects"); + if (!customeffects.isNull() && customeffects.hasChildNodes()) { + parent->slotGotProgressInfo(i18n("Importing project effects"), 0); + qApp->processEvents(); + if (saveCustomEffects(customeffects.childNodes())) parent->slotReloadEffects(); + } - if (max > 0) { - m_documentLoadingStep = 100.0 / (max + infomax + m_document.elementsByTagName("entry").count()); - parent->slotGotProgressInfo(i18n("Loading project clips"), (int) m_documentLoadingProgress); - } + QDomElement e; + // Read notes + QDomElement notesxml = infoXml.firstChildElement("documentnotes"); + if (!notesxml.isNull()) m_notesWidget->setText(notesxml.firstChild().nodeValue()); + + // Build tracks + QDomElement tracksinfo = infoXml.firstChildElement("tracksinfo"); + if (!tracksinfo.isNull()) { + QDomNodeList trackslist = tracksinfo.childNodes(); + int maxchild = trackslist.count(); + for (int k = 0; k < maxchild; k++) { + e = trackslist.at(k).toElement(); + if (e.tagName() == "trackinfo") { + TrackInfo projectTrack; + if (e.attribute("type") == "audio") + projectTrack.type = AudioTrack; + else + projectTrack.type = VideoTrack; + projectTrack.isMute = e.attribute("mute").toInt(); + projectTrack.isBlind = e.attribute("blind").toInt(); + projectTrack.isLocked = e.attribute("locked").toInt(); + projectTrack.trackName = e.attribute("trackname"); + projectTrack.effectsList = EffectsList(true); + m_tracksList.append(projectTrack); + } + } + mlt.removeChild(tracksinfo); + } + QStringList expandedFolders; + QDomNodeList folders = m_document.elementsByTagName("folder"); + for (int i = 0; i < folders.count(); ++i) { + e = folders.item(i).cloneNode().toElement(); + if (e.hasAttribute("opened")) expandedFolders.append(e.attribute("id")); + m_clipManager->addFolder(e.attribute("id"), e.attribute("name")); + } + m_documentProperties["expandedfolders"] = expandedFolders.join(";"); + const int infomax = infoproducers.count(); + QDomNodeList producers = m_document.elementsByTagName("producer"); + const int max = producers.count(); - for (int i = 0; i < infomax && !m_abortLoading; i++) { - e = infoproducers.item(i).cloneNode().toElement(); - if (m_documentLoadingStep > 0) { - m_documentLoadingProgress += m_documentLoadingStep; - parent->slotGotProgressInfo(QString(), (int) m_documentLoadingProgress); - //qApp->processEvents(); + if (!progressDialog) { + progressDialog = new KProgressDialog(parent, i18n("Loading project"), i18n("Adding clips")); + progressDialog->setAllowCancel(false); + } else { + progressDialog->setLabelText(i18n("Adding clips")); } - QString prodId = e.attribute("id"); - if (!e.isNull() && prodId != "black" && !prodId.startsWith("slowmotion") && !m_abortLoading) { - e.setTagName("producer"); - // Get MLT's original producer properties - QDomElement orig; - for (int j = 0; j < max; j++) { - QDomElement o = producers.item(j).cloneNode().toElement(); - QString origId = o.attribute("id").section('_', 0, 0); - if (origId == prodId) { - orig = o; + progressDialog->progressBar()->setMaximum(infomax); + progressDialog->show(); + qApp->processEvents(); + + for (int i = 0; i < infomax; ++i) { + e = infoproducers.item(i).cloneNode().toElement(); + QString prodId = e.attribute("id"); + if (!e.isNull() && prodId != "black" && !prodId.startsWith("slowmotion")) { + e.setTagName("producer"); + // Get MLT's original producer properties + QDomElement orig; + for (int j = 0; j < max; j++) { + QDomNode o = producers.item(j); + QString origId = o.attributes().namedItem("id").nodeValue().section('_', 0, 0); + if (origId == prodId) { + orig = o.cloneNode().toElement(); + break; + } + } + + if (!addClipInfo(e, orig, prodId)) { + // The user manually aborted the loading. + success = false; + emit resetProjectList(); + m_tracksList.clear(); + m_clipManager->clear(); break; } } - addClipInfo(e, orig, prodId); - kDebug() << "// KDENLIVE PRODUCER: " << prodId; + if (i % 10 == 0) + progressDialog->progressBar()->setValue(i); } - } - if (m_abortLoading) { - //parent->slotGotProgressInfo(i18n("File %1 is not a Kdenlive project file."), 100); - emit resetProjectList(); - m_documentProperties.remove("position"); - m_url = KUrl(); - m_tracksList.clear(); - kWarning() << "Aborted loading of: " << url.path(); - m_document = createEmptyDocument(KdenliveSettings::videotracks(), KdenliveSettings::audiotracks()); - setProfilePath(KdenliveSettings::default_profile()); - m_clipManager->clear(); - } else { - QDomElement markers = infoXml.firstChildElement("markers"); - if (!markers.isNull()) { - QDomNodeList markerslist = markers.childNodes(); - int maxchild = markerslist.count(); - for (int k = 0; k < maxchild; k++) { - e = markerslist.at(k).toElement(); - if (e.tagName() == "marker") { - m_clipManager->getClipById(e.attribute("id"))->addSnapMarker(GenTime(e.attribute("time").toDouble()), e.attribute("comment")); + + if (success) { + QDomElement markers = infoXml.firstChildElement("markers"); + if (!markers.isNull()) { + QDomNodeList markerslist = markers.childNodes(); + int maxchild = markerslist.count(); + for (int k = 0; k < maxchild; k++) { + e = markerslist.at(k).toElement(); + if (e.tagName() == "marker") { + CommentedTime marker(GenTime(e.attribute("time").toDouble()), e.attribute("comment"), e.attribute("type").toInt()); + DocClipBase *baseClip = m_clipManager->getClipById(e.attribute("id")); + if (baseClip) baseClip->addSnapMarker(marker); + else kDebug()<< " / / Warning, missing clip: "<< e.attribute("id"); + } } + infoXml.removeChild(markers); } - infoXml.removeChild(markers); + + m_projectFolder = KUrl(infoXml.attribute("projectfolder")); + QDomElement docproperties = infoXml.firstChildElement("documentproperties"); + QDomNamedNodeMap props = docproperties.attributes(); + for (int i = 0; i < props.count(); ++i) + m_documentProperties.insert(props.item(i).nodeName(), props.item(i).nodeValue()); + docproperties = infoXml.firstChildElement("documentmetadata"); + props = docproperties.attributes(); + for (int i = 0; i < props.count(); ++i) + m_documentMetadata.insert(props.item(i).nodeName(), props.item(i).nodeValue()); + + if (validator.isModified()) setModified(true); + kDebug() << "Reading file: " << url.path() << ", found clips: " << producers.count(); } - setProfilePath(profileName); - setModified(validator.isModified()); - kDebug() << "Reading file: " << url.path() << ", found clips: " << producers.count(); } } } } + KIO::NetAccess::removeTempFile(tmpFile); } - } else setProfilePath(profileName); - + } + // Something went wrong, or a new file was requested: create a new project if (!success) { - setProfilePath(QString()); - m_url = KUrl(); + m_url.clear(); + setProfilePath(profileName); m_document = createEmptyDocument(tracks.x(), tracks.y()); } - // Set the video profile (empty == default) + // 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())); + } + } + } // Make sure the project folder is usable if (m_projectFolder.isEmpty() || !KIO::NetAccess::exists(m_projectFolder.path(), KIO::NetAccess::DestinationSide, parent)) { @@ -219,14 +377,13 @@ KdenliveDoc::KdenliveDoc(const KUrl &url, const KUrl &projectFolder, QUndoGroup // Make sure that the necessary folders exist KStandardDirs::makeDir(m_projectFolder.path(KUrl::AddTrailingSlash) + "titles/"); KStandardDirs::makeDir(m_projectFolder.path(KUrl::AddTrailingSlash) + "thumbs/"); - KStandardDirs::makeDir(m_projectFolder.path(KUrl::AddTrailingSlash) + "ladspa/"); + KStandardDirs::makeDir(m_projectFolder.path(KUrl::AddTrailingSlash) + "proxy/"); - kDebug() << "Kdenlive document, init timecode: " << m_fps; - if (m_fps == 30000.0 / 1001.0) m_timecode.setFormat(30, true); - else m_timecode.setFormat((int)(m_fps + 0.5)); + updateProjectFolderPlacesEntry(); //kDebug() << "// SETTING SCENE LIST:\n\n" << m_document.toString(); connect(m_autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); + connect(m_render, SIGNAL(addClip(KUrl,stringMap)), this, SLOT(slotAddClipFile(KUrl,stringMap))); } KdenliveDoc::~KdenliveDoc() @@ -245,6 +402,7 @@ KdenliveDoc::~KdenliveDoc() int KdenliveDoc::setSceneList() { + m_render->resetProfile(KdenliveSettings::current_profile(), true); if (m_render->setSceneList(m_document.toString(), m_documentProperties.value("position").toInt()) == -1) { // INVALID MLT Consumer, something is wrong return -1; @@ -252,42 +410,114 @@ int KdenliveDoc::setSceneList() m_documentProperties.remove("position"); // m_document xml is now useless, clear it m_document.clear(); - checkProjectClips(); return 0; } -QDomDocument KdenliveDoc::createEmptyDocument(const int videotracks, const int audiotracks) +QDomDocument KdenliveDoc::createEmptyDocument(int videotracks, int audiotracks) +{ + m_tracksList.clear(); + + // 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 + for (int i = 0; i < audiotracks; ++i) { + TrackInfo audioTrack; + audioTrack.type = AudioTrack; + audioTrack.isMute = false; + audioTrack.isBlind = true; + audioTrack.isLocked = false; + audioTrack.trackName = QString("Audio ") + QString::number(audiotracks - i); + audioTrack.duration = 0; + audioTrack.effectsList = EffectsList(true); + m_tracksList.append(audioTrack); + + } + for (int i = 0; i < videotracks; ++i) { + TrackInfo videoTrack; + videoTrack.type = VideoTrack; + videoTrack.isMute = false; + videoTrack.isBlind = false; + videoTrack.isLocked = false; + videoTrack.trackName = QString("Video ") + QString::number(videotracks - i); + videoTrack.duration = 0; + videoTrack.effectsList = EffectsList(true); + m_tracksList.append(videoTrack); + } + return createEmptyDocument(m_tracksList); +} + +QDomDocument KdenliveDoc::createEmptyDocument(const QList &tracks) { // Creating new document QDomDocument doc; QDomElement mlt = doc.createElement("mlt"); + mlt.setAttribute("LC_NUMERIC", ""); doc.appendChild(mlt); + + // 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"); + value = doc.createTextNode(QString::number(0)); + 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); - TrackInfo videoTrack; - videoTrack.type = VIDEOTRACK; - videoTrack.isMute = false; - videoTrack.isBlind = false; - videoTrack.isLocked = false; - - TrackInfo audioTrack; - audioTrack.type = AUDIOTRACK; - audioTrack.isMute = false; - audioTrack.isBlind = true; - audioTrack.isLocked = false; - QDomElement tractor = doc.createElement("tractor"); tractor.setAttribute("id", "maintractor"); - QDomElement multitrack = doc.createElement("multitrack"); + //QDomElement multitrack = doc.createElement("multitrack"); QDomElement playlist = doc.createElement("playlist"); playlist.setAttribute("id", "black_track"); mlt.appendChild(playlist); + QDomElement blank0 = doc.createElement("entry"); + blank0.setAttribute("in", "0"); + blank0.setAttribute("out", "1"); + blank0.setAttribute("producer", "black"); + playlist.appendChild(blank0); // create playlists - int total = audiotracks + videotracks + 1; + int total = tracks.count() + 1; - for (int i = 1; i < total; i++) { + for (int i = 1; i < total; ++i) { QDomElement playlist = doc.createElement("playlist"); playlist.setAttribute("id", "playlist" + QString::number(i)); mlt.appendChild(playlist); @@ -297,24 +527,20 @@ QDomDocument KdenliveDoc::createEmptyDocument(const int videotracks, const int a track0.setAttribute("producer", "black_track"); tractor.appendChild(track0); - // create audio tracks - for (int i = 1; i < audiotracks + 1; i++) { - QDomElement track = doc.createElement("track"); - track.setAttribute("producer", "playlist" + QString::number(i)); - track.setAttribute("hide", "video"); - tractor.appendChild(track); - m_tracksList.append(audioTrack); - } - - // create video tracks - for (int i = audiotracks + 1; i < total; i++) { + // create audio and video tracks + for (int i = 1; i < total; ++i) { QDomElement track = doc.createElement("track"); track.setAttribute("producer", "playlist" + QString::number(i)); + if (tracks.at(i - 1).type == AudioTrack) { + track.setAttribute("hide", "video"); + } else if (tracks.at(i - 1).isBlind) + track.setAttribute("hide", "video"); + if (tracks.at(i - 1).isMute) + track.setAttribute("hide", "audio"); tractor.appendChild(track); - m_tracksList.append(videoTrack); } - for (int i = 2; i < total ; i++) { + for (int i = 2; i < total ; ++i) { QDomElement transition = doc.createElement("transition"); transition.setAttribute("always_active", "1"); @@ -354,14 +580,14 @@ QDomDocument KdenliveDoc::createEmptyDocument(const int videotracks, const int a } -void KdenliveDoc::syncGuides(QList guides) +void KdenliveDoc::syncGuides(const QList &guides) { m_guidesXml.clear(); QDomElement guideNode = m_guidesXml.createElement("guides"); m_guidesXml.appendChild(guideNode); QDomElement e; - for (int i = 0; i < guides.count(); i++) { + for (int i = 0; i < guides.count(); ++i) { e = m_guidesXml.createElement("guide"); e.setAttribute("time", guides.at(i)->position().ms() / 1000); e.setAttribute("comment", guides.at(i)->label()); @@ -384,15 +610,7 @@ void KdenliveDoc::slotAutoSave() kDebug() << "ERROR; CANNOT CREATE AUTOSAVE FILE"; } kDebug() << "// AUTOSAVE FILE: " << m_autosave->fileName(); - QString doc; - if (KdenliveSettings::dropbframes()) { - KdenliveSettings::setDropbframes(false); - m_clipManager->updatePreviewSettings(); - doc = m_render->sceneList(); - KdenliveSettings::setDropbframes(true); - m_clipManager->updatePreviewSettings(); - } else doc = m_render->sceneList(); - saveSceneList(m_autosave->fileName(), doc); + saveSceneList(m_autosave->fileName(), m_render->sceneList(), QStringList(), true); } } @@ -418,14 +636,55 @@ QPoint KdenliveDoc::zone() const return QPoint(m_documentProperties.value("zonein").toInt(), m_documentProperties.value("zoneout").toInt()); } -bool KdenliveDoc::saveSceneList(const QString &path, const QString &scene) +QDomDocument KdenliveDoc::xmlSceneList(const QString &scene, const QStringList &expandedFolders) { QDomDocument sceneList; sceneList.setContent(scene, true); QDomElement mlt = sceneList.firstChildElement("mlt"); + if (mlt.isNull() || !mlt.hasChildNodes()) { + //scenelist is corrupted + return sceneList; + } + + // Set playlist audio volume to 100% + QDomElement tractor = mlt.firstChildElement("tractor"); + if (!tractor.isNull()) { + QDomNodeList props = tractor.elementsByTagName("property"); + for (int i = 0; i < props.count(); ++i) { + if (props.at(i).toElement().attribute("name") == "meta.volume") { + props.at(i).firstChild().setNodeValue("1"); + break; + } + } + } + QDomElement addedXml = sceneList.createElement("kdenlivedoc"); mlt.appendChild(addedXml); + // check if project contains custom effects to embed them in project file + QDomNodeList effects = mlt.elementsByTagName("filter"); + int maxEffects = effects.count(); + kDebug() << "// FOUD " << maxEffects << " EFFECTS+++++++++++++++++++++"; + QMap effectIds; + for (int i = 0; i < maxEffects; ++i) { + QDomNode m = effects.at(i); + QDomNodeList params = m.childNodes(); + QString id; + QString tag; + for (int j = 0; j < params.count(); j++) { + 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)); + QDomElement markers = sceneList.createElement("markers"); addedXml.setAttribute("version", DOCUMENTVERSION); addedXml.setAttribute("kdenliveversion", VERSION); @@ -440,6 +699,19 @@ bool KdenliveDoc::saveSceneList(const QString &path, const QString &scene) } docproperties.setAttribute("position", m_render->seekPosition().frames(m_fps)); addedXml.appendChild(docproperties); + + QDomElement docmetadata = sceneList.createElement("documentmetadata"); + QMapIterator j(m_documentMetadata); + while (j.hasNext()) { + j.next(); + docmetadata.setAttribute(j.key(), j.value()); + } + addedXml.appendChild(docmetadata); + + QDomElement docnotes = sceneList.createElement("documentnotes"); + QDomText value = sceneList.createTextNode(m_notesWidget->toHtml()); + docnotes.appendChild(value); + addedXml.appendChild(docnotes); // Add profile info QDomElement profileinfo = sceneList.createElement("profileinfo"); @@ -457,12 +729,13 @@ bool KdenliveDoc::saveSceneList(const QString &path, const QString &scene) // tracks info QDomElement tracksinfo = sceneList.createElement("tracksinfo"); - foreach(const TrackInfo &info, m_tracksList) { + foreach(const TrackInfo & info, m_tracksList) { QDomElement trackinfo = sceneList.createElement("trackinfo"); - if (info.type == AUDIOTRACK) trackinfo.setAttribute("type", "audio"); + if (info.type == AudioTrack) trackinfo.setAttribute("type", "audio"); trackinfo.setAttribute("mute", info.isMute); trackinfo.setAttribute("blind", info.isBlind); trackinfo.setAttribute("locked", info.isLocked); + trackinfo.setAttribute("trackname", info.trackName); tracksinfo.appendChild(trackinfo); } addedXml.appendChild(tracksinfo); @@ -476,14 +749,15 @@ bool KdenliveDoc::saveSceneList(const QString &path, const QString &scene) QDomElement folder = sceneList.createElement("folder"); folder.setAttribute("id", f.key()); folder.setAttribute("name", f.value()); + if (expandedFolders.contains(f.key())) folder.setAttribute("opened", "1"); addedXml.appendChild(folder); } // Save project clips QDomElement e; QList list = m_clipManager->documentClipList(); - for (int i = 0; i < list.count(); i++) { - e = list.at(i)->toXML(); + for (int i = 0; i < list.count(); ++i) { + e = list.at(i)->toXML(true); e.setTagName("kdenlive_producer"); addedXml.appendChild(sceneList.importNode(e, true)); QList < CommentedTime > marks = list.at(i)->commentedSnapMarkers(); @@ -492,6 +766,7 @@ bool KdenliveDoc::saveSceneList(const QString &path, const QString &scene) marker.setAttribute("time", marks.at(j).time().ms() / 1000); marker.setAttribute("comment", marks.at(j).comment()); marker.setAttribute("id", e.attribute("id")); + marker.setAttribute("type", marks.at(j).markerType()); markers.appendChild(marker); } } @@ -504,8 +779,22 @@ bool KdenliveDoc::saveSceneList(const QString &path, const QString &scene) addedXml.appendChild(sceneList.importNode(m_clipManager->groupsXml(), true)); //wes.appendChild(doc.importNode(kdenliveData, true)); + return sceneList; +} +bool KdenliveDoc::saveSceneList(const QString &path, const QString &scene, const QStringList &expandedFolders, bool autosave) +{ + QDomDocument sceneList = xmlSceneList(scene, expandedFolders); + if (sceneList.isNull()) { + //Make sure we don't save if scenelist is corrupted + KMessageBox::error(kapp->activeWindow(), i18n("Cannot write to file %1, scene list is corrupted.", path)); + return false; + } + + // Backup current version + if (!autosave) backupLastSavedVersion(path); QFile file(path); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { kWarning() << "////// ERROR writing to file: " << path; KMessageBox::error(kapp->activeWindow(), i18n("Cannot write to file %1", path)); @@ -519,6 +808,18 @@ bool KdenliveDoc::saveSceneList(const QString &path, const QString &scene) return false; } file.close(); + if (!autosave) { + cleanupBackupFiles(); + QFileInfo info(file); + QString fileName = KUrl(path).fileName().section('.', 0, -2); + fileName.append('-' + m_documentProperties.value("documentid")); + fileName.append(info.lastModified().toString("-yyyy-MM-dd-hh-mm")); + fileName.append(".kdenlive.png"); + KUrl backupFile = m_projectFolder; + backupFile.addPath(".backup/"); + backupFile.addPath(fileName); + emit saveTimelinePreview(backupFile.path()); + } return true; } @@ -540,17 +841,20 @@ void KdenliveDoc::setProjectFolder(KUrl url) KStandardDirs::makeDir(url.path()); KStandardDirs::makeDir(url.path(KUrl::AddTrailingSlash) + "titles/"); KStandardDirs::makeDir(url.path(KUrl::AddTrailingSlash) + "thumbs/"); - if (KMessageBox::questionYesNo(kapp->activeWindow(), i18n("You have changed the project folder. Do you want to copy the cached data from %1 to the new folder %2?").arg(m_projectFolder.path(), url.path())) == KMessageBox::Yes) moveProjectData(url); + KStandardDirs::makeDir(url.path(KUrl::AddTrailingSlash) + "proxy/"); + if (KMessageBox::questionYesNo(kapp->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); m_projectFolder = url; + + updateProjectFolderPlacesEntry(); } -void KdenliveDoc::moveProjectData(KUrl url) +void KdenliveDoc::moveProjectData(const KUrl &url) { QList list = m_clipManager->documentClipList(); - //TODO: Also move ladspa effects files - for (int i = 0; i < list.count(); i++) { + KUrl::List cacheUrls; + for (int i = 0; i < list.count(); ++i) { DocClipBase *clip = list.at(i); - if (clip->clipType() == TEXT) { + if (clip->clipType() == Text) { // the image for title clip must be moved KUrl oldUrl = clip->fileURL(); KUrl newUrl = KUrl(url.path(KUrl::AddTrailingSlash) + "titles/" + oldUrl.fileName()); @@ -559,18 +863,23 @@ void KdenliveDoc::moveProjectData(KUrl url) } QString hash = clip->getClipHash(); KUrl oldVideoThumbUrl = KUrl(m_projectFolder.path(KUrl::AddTrailingSlash) + "thumbs/" + hash + ".png"); - KUrl oldAudioThumbUrl = KUrl(m_projectFolder.path(KUrl::AddTrailingSlash) + "thumbs/" + hash + ".thumb"); if (KIO::NetAccess::exists(oldVideoThumbUrl, KIO::NetAccess::SourceSide, 0)) { - KUrl newUrl = KUrl(url.path(KUrl::AddTrailingSlash) + "thumbs/" + hash + ".png"); - KIO::Job *job = KIO::copy(oldVideoThumbUrl, newUrl); - KIO::NetAccess::synchronousRun(job, 0); + cacheUrls << oldVideoThumbUrl; } + KUrl oldAudioThumbUrl = KUrl(m_projectFolder.path(KUrl::AddTrailingSlash) + "thumbs/" + hash + ".thumb"); if (KIO::NetAccess::exists(oldAudioThumbUrl, KIO::NetAccess::SourceSide, 0)) { - KUrl newUrl = KUrl(url.path(KUrl::AddTrailingSlash) + "thumbs/" + hash + ".thumb"); - KIO::Job *job = KIO::copy(oldAudioThumbUrl, newUrl); - if (KIO::NetAccess::synchronousRun(job, 0)) clip->refreshThumbUrl(); + cacheUrls << oldAudioThumbUrl; + } + KUrl oldVideoProxyUrl = KUrl(m_projectFolder.path(KUrl::AddTrailingSlash) + "proxy/" + hash + '.' + KdenliveSettings::proxyextension()); + if (KIO::NetAccess::exists(oldVideoProxyUrl, KIO::NetAccess::SourceSide, 0)) { + cacheUrls << oldVideoProxyUrl; } } + if (!cacheUrls.isEmpty()) { + KIO::Job *job = KIO::copy(cacheUrls, KUrl(url.path(KUrl::AddTrailingSlash) + "thumbs/")); + job->ui()->setWindow(kapp->activeWindow()); + KIO::NetAccess::synchronousRun(job, 0); + } } const QString &KdenliveDoc::profilePath() const @@ -583,11 +892,14 @@ MltVideoProfile KdenliveDoc::mltProfile() const return m_profile; } -void KdenliveDoc::setProfilePath(QString path) +bool KdenliveDoc::setProfilePath(QString path) { - if (path.isEmpty()) path = KdenliveSettings::default_profile(); - if (path.isEmpty()) path = "dv_pal"; + if (path.isEmpty()) + path = KdenliveSettings::default_profile(); + if (path.isEmpty()) + path = QLatin1String("dv_pal"); m_profile = ProfilesDialog::getVideoProfile(path); + double current_fps = m_fps; if (m_profile.path.isEmpty()) { // Profile not found, use embedded profile QDomElement profileInfo = m_document.elementsByTagName("profileinfo").at(0).toElement(); @@ -638,11 +950,12 @@ void KdenliveDoc::setProfilePath(QString path) m_width = m_profile.width; m_height = m_profile.height; kDebug() << "Kdenlive document, init timecode from path: " << path << ", " << m_fps; - if (m_fps == 30000.0 / 1001.0) m_timecode.setFormat(30, true); - else m_timecode.setFormat((int)(m_fps + 0.5)); + m_timecode.setFormat(m_fps); + KdenliveSettings::setCurrent_profile(m_profile.path); + return (current_fps != m_fps); } -double KdenliveDoc::dar() +double KdenliveDoc::dar() const { return (double) m_profile.display_aspect_num / m_profile.display_aspect_den; } @@ -652,12 +965,6 @@ void KdenliveDoc::setThumbsProgress(const QString &message, int progress) emit progressInfo(message, progress); } -void KdenliveDoc::loadingProgressed() -{ - m_documentLoadingProgress += m_documentLoadingStep; - emit progressInfo(QString(), (int) m_documentLoadingProgress); -} - QUndoStack *KdenliveDoc::commandStack() { return m_commandStack; @@ -677,19 +984,10 @@ void KdenliveDoc::setRenderer(Render *render) { emit progressInfo(QString(), -1); }*/ -void KdenliveDoc::checkProjectClips() +void KdenliveDoc::checkProjectClips(bool displayRatioChanged, bool fpsChanged) { - kDebug() << "+++++++++++++ + + + + CHK PCLIPS"; if (m_render == NULL) return; - m_clipManager->resetProducersList(m_render->producersList()); -} - -void KdenliveDoc::updatePreviewSettings() -{ - m_clipManager->updatePreviewSettings(); - m_render->updatePreviewSettings(); - m_clipManager->resetProducersList(m_render->producersList()); - + m_clipManager->resetProducersList(m_render->producersList(), displayRatioChanged, fpsChanged); } Render *KdenliveDoc::renderer() @@ -702,9 +1000,9 @@ void KdenliveDoc::updateClip(const QString &id) emit updateClipDisplay(id); } -int KdenliveDoc::getFramePos(QString duration) +int KdenliveDoc::getFramePos(const QString &duration) { - return m_timecode.getFrameCount(duration, m_fps); + return m_timecode.getFrameCount(duration); } QString KdenliveDoc::producerName(const QString &id) @@ -712,7 +1010,7 @@ QString KdenliveDoc::producerName(const QString &id) QString result = "unnamed"; QDomNodeList prods = producersList(); int ct = prods.count(); - for (int i = 0; i < ct ; i++) { + for (int i = 0; i < ct ; ++i) { QDomElement e = prods.item(i).toElement(); if (e.attribute("id") != "black" && e.attribute("id") == id) { result = e.attribute("name"); @@ -766,7 +1064,7 @@ KUrl KdenliveDoc::url() const return m_url; } -void KdenliveDoc::setUrl(KUrl url) +void KdenliveDoc::setUrl(const KUrl &url) { m_url = url; } @@ -794,7 +1092,7 @@ const QString KdenliveDoc::description() const return m_url.fileName() + " / " + m_profile.description; } -void KdenliveDoc::addClip(QDomElement elem, QString clipId, bool createClipItem) +bool KdenliveDoc::addClip(QDomElement elem, const QString &clipId, bool createClipItem) { const QString producerId = clipId.section('_', 0, 0); DocClipBase *clip = m_clipManager->getClipById(producerId); @@ -803,46 +1101,27 @@ void KdenliveDoc::addClip(QDomElement elem, QString clipId, bool createClipItem) elem.setAttribute("id", producerId); QString path = elem.attribute("resource"); QString extension; - if (elem.attribute("type").toInt() == SLIDESHOW) { + if (elem.attribute("type").toInt() == SlideShow) { extension = KUrl(path).fileName(); path = KUrl(path).directory(); - } /*else if (elem.attribute("type").toInt() == TEXT && QFile::exists(path) == false) { - kDebug() << "// TITLE: " << elem.attribute("name") << " Preview file: " << elem.attribute("resource") << " DOES NOT EXIST"; - QString titlename = elem.attribute("name"); - QString titleresource; - if (titlename.isEmpty()) { - QStringList titleInfo = TitleWidget::getFreeTitleInfo(projectFolder()); - titlename = titleInfo.at(0); - titleresource = titleInfo.at(1); - elem.setAttribute("name", titlename); - kDebug() << "// New title set to: " << titlename; - } else { - titleresource = TitleWidget::getFreeTitleInfo(projectFolder()).at(1); - //titleresource = TitleWidget::getTitleResourceFromName(projectFolder(), titlename); - } - TitleWidget *dia_ui = new TitleWidget(KUrl(), KUrl(titleresource).directory(), m_render, kapp->activeWindow()); - QDomDocument doc; - doc.setContent(elem.attribute("xmldata")); - dia_ui->setXml(doc); - QImage pix = dia_ui->renderedPixmap(); - pix.save(titleresource); - elem.setAttribute("resource", titleresource); - setNewClipResource(clipId, titleresource); - delete dia_ui; - }*/ - - if (path.isEmpty() == false && QFile::exists(path) == false && elem.attribute("type").toInt() != TEXT && !elem.hasAttribute("placeholder")) { + } + if (elem.hasAttribute("_missingsource")) { + // Clip has proxy but missing original source + } + else if (path.isEmpty() == false && QFile::exists(path) == false && elem.attribute("type").toInt() != Text && !elem.hasAttribute("placeholder")) { kDebug() << "// FOUND MISSING CLIP: " << path << ", TYPE: " << elem.attribute("type").toInt(); const QString size = elem.attribute("file_size"); const QString hash = elem.attribute("file_hash"); QString newpath; int action = KMessageBox::No; if (!size.isEmpty() && !hash.isEmpty()) { - if (!m_searchFolder.isEmpty()) newpath = searchFileRecursively(m_searchFolder, size, hash); - else action = (KMessageBox::ButtonCode) KMessageBox::questionYesNoCancel(kapp->activeWindow(), i18n("Clip %1
is invalid, what do you want to do?", path), i18n("File not found"), KGuiItem(i18n("Search automatically")), KGuiItem(i18n("Keep as placeholder"))); + if (!m_searchFolder.isEmpty()) + newpath = searchFileRecursively(m_searchFolder, size, hash); + else + action = (KMessageBox::ButtonCode) KMessageBox::questionYesNoCancel(kapp->activeWindow(), i18n("Clip %1
is invalid, what do you want to do?", path), i18n("File not found"), KGuiItem(i18n("Search automatically")), KGuiItem(i18n("Keep as placeholder"))); } else { - if (elem.attribute("type").toInt() == SLIDESHOW) { - int res = KMessageBox::questionYesNoCancel(kapp->activeWindow(), i18n("Clip %1
is invalid or missing, what do you want to do?", path), i18n("File not found"), KGuiItem(i18n("Search manually")), KGuiItem(i18n("Keep as placeholder"))); + if (elem.attribute("type").toInt() == SlideShow) { + int res = KMessageBox::questionYesNoCancel(kapp->activeWindow(), i18n("Clip %1
is invalid or missing, what do you want to do?", path), i18n("File not found"), KGuiItem(i18n("Search manually")), KGuiItem(i18n("Keep as placeholder"))); if (res == KMessageBox::Yes) newpath = KFileDialog::getExistingDirectory(KUrl("kfiledialog:///clipfolder"), kapp->activeWindow(), i18n("Looking for %1", path)); else { @@ -850,7 +1129,7 @@ void KdenliveDoc::addClip(QDomElement elem, QString clipId, bool createClipItem) action = res; } } else { - int res = KMessageBox::questionYesNoCancel(kapp->activeWindow(), i18n("Clip %1
is invalid or missing, what do you want to do?", path), i18n("File not found"), KGuiItem(i18n("Search manually")), KGuiItem(i18n("Keep as placeholder"))); + int res = KMessageBox::questionYesNoCancel(kapp->activeWindow(), i18n("Clip %1
is invalid or missing, what do you want to do?", path), i18n("File not found"), KGuiItem(i18n("Search manually")), KGuiItem(i18n("Keep as placeholder"))); if (res == KMessageBox::Yes) newpath = KFileDialog::getOpenFileName(KUrl("kfiledialog:///clipfolder"), QString(), kapp->activeWindow(), i18n("Looking for %1", path)); else { @@ -862,18 +1141,18 @@ void KdenliveDoc::addClip(QDomElement elem, QString clipId, bool createClipItem) if (action == KMessageBox::Yes) { kDebug() << "// ASKED FOR SRCH CLIP: " << clipId; m_searchFolder = KFileDialog::getExistingDirectory(KUrl("kfiledialog:///clipfolder"), kapp->activeWindow()); - if (!m_searchFolder.isEmpty()) { + if (!m_searchFolder.isEmpty()) newpath = searchFileRecursively(QDir(m_searchFolder), size, hash); - } } else if (action == KMessageBox::Cancel) { - m_abortLoading = true; - return; + return false; } else if (action == KMessageBox::No) { // Keep clip as placeHolder elem.setAttribute("placeholder", '1'); } if (!newpath.isEmpty()) { - if (elem.attribute("type").toInt() == SLIDESHOW) newpath.append('/' + extension); + kDebug() << "// NEW CLIP PATH FOR CLIP " << clipId << " : " << newpath; + if (elem.attribute("type").toInt() == SlideShow) + newpath.append('/' + extension); elem.setAttribute("resource", newpath); setNewClipResource(clipId, newpath); setModified(true); @@ -885,17 +1164,16 @@ void KdenliveDoc::addClip(QDomElement elem, QString clipId, bool createClipItem) if (createClipItem) { emit addProjectClip(clip); - qApp->processEvents(); - m_render->getFileProperties(clip->toXML(), clip->getId(), true); } -} + return true; +} void KdenliveDoc::setNewClipResource(const QString &id, const QString &path) { QDomNodeList prods = m_document.elementsByTagName("producer"); int maxprod = prods.count(); - for (int i = 0; i < maxprod; i++) { + for (int i = 0; i < maxprod; ++i) { QDomNode m = prods.at(i); QString prodId = m.toElement().attribute("id"); if (prodId == id || prodId.startsWith(id + '_')) { @@ -917,7 +1195,7 @@ QString KdenliveDoc::searchFileRecursively(const QDir &dir, const QString &match QByteArray fileData; QByteArray fileHash; QStringList filesAndDirs = dir.entryList(QDir::Files | QDir::Readable); - for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); i++) { + for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); ++i) { QFile file(dir.absoluteFilePath(filesAndDirs.at(i))); if (file.open(QIODevice::ReadOnly)) { if (QString::number(file.size()) == matchSize) { @@ -925,7 +1203,7 @@ QString KdenliveDoc::searchFileRecursively(const QDir &dir, const QString &match * 1 MB = 1 second per 450 files (or faster) * 10 MB = 9 seconds per 450 files (or faster) */ - if (file.size() > 1000000*2) { + if (file.size() > 1000000 * 2) { fileData = file.read(1000000); if (file.seek(file.size() - 1000000)) fileData.append(file.readAll()); @@ -935,12 +1213,14 @@ QString KdenliveDoc::searchFileRecursively(const QDir &dir, const QString &match fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5); if (QString(fileHash.toHex()) == matchHash) return file.fileName(); + else + kDebug() << filesAndDirs.at(i) << "size match but not hash"; } } - kDebug() << filesAndDirs.at(i) << file.size() << fileHash.toHex(); + //kDebug() << filesAndDirs.at(i) << file.size() << fileHash.toHex(); } filesAndDirs = dir.entryList(QDir::Dirs | QDir::Readable | QDir::Executable | QDir::NoDotAndDotDot); - for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); i++) { + for (int i = 0; i < filesAndDirs.size() && foundFileName.isEmpty(); ++i) { foundFileName = searchFileRecursively(dir.absoluteFilePath(filesAndDirs.at(i)), matchSize, matchHash); if (!foundFileName.isEmpty()) break; @@ -948,66 +1228,60 @@ QString KdenliveDoc::searchFileRecursively(const QDir &dir, const QString &match return foundFileName; } -void KdenliveDoc::addClipInfo(QDomElement elem, QDomElement orig, QString clipId) +bool KdenliveDoc::addClipInfo(QDomElement elem, QDomElement orig, const QString &clipId) { DocClipBase *clip = m_clipManager->getClipById(clipId); if (clip == NULL) { - addClip(elem, clipId, false); + if (!addClip(elem, clipId, false)) + return false; } else { QMap properties; QDomNamedNodeMap attributes = elem.attributes(); - QString attrname; - for (int i = 0; i < attributes.count(); i++) { - attrname = attributes.item(i).nodeName(); + for (int i = 0; i < attributes.count(); ++i) { + QString attrname = attributes.item(i).nodeName(); if (attrname != "resource") properties.insert(attrname, attributes.item(i).nodeValue()); - kDebug() << attrname << " = " << attributes.item(i).nodeValue(); + //kDebug() << attrname << " = " << attributes.item(i).nodeValue(); } clip->setProperties(properties); emit addProjectClip(clip, false); } if (orig != QDomElement()) { QMap meta; - QDomNode m = orig.firstChild(); - while (!m.isNull()) { + for (QDomNode m = orig.firstChild(); !m.isNull(); m = m.nextSibling()) { QString name = m.toElement().attribute("name"); if (name.startsWith("meta.attr")) { - meta.insert(name.section('.', 2, 3), m.firstChild().nodeValue()); + if (name.endsWith(".markup")) name = name.section('.', 0, -2); + meta.insert(name.section('.', 2, -1), m.firstChild().nodeValue()); } - m = m.nextSibling(); } if (!meta.isEmpty()) { - if (clip == NULL) clip = m_clipManager->getClipById(clipId); - if (clip) clip->setMetadata(meta); + if (clip == NULL) + clip = m_clipManager->getClipById(clipId); + if (clip) + clip->setMetadata(meta); } } + return true; } -void KdenliveDoc::deleteProjectClip(QList ids) -{ - for (int i = 0; i < ids.size(); ++i) { - emit deleteTimelineClip(ids.at(i)); - m_clipManager->slotDeleteClip(ids.at(i)); - } - setModified(true); -} void KdenliveDoc::deleteClip(const QString &clipId) { emit signalDeleteProjectClip(clipId); } -void KdenliveDoc::slotAddClipList(const KUrl::List urls, const QString group, const QString &groupId) +void KdenliveDoc::slotAddClipList(const KUrl::List &urls, const stringMap &data) { - m_clipManager->slotAddClipList(urls, group, groupId); - emit selectLastAddedClip(QString::number(m_clipManager->lastClipId())); + m_clipManager->slotAddClipList(urls, data); + //emit selectLastAddedClip(QString::number(m_clipManager->lastClipId())); setModified(true); } -void KdenliveDoc::slotAddClipFile(const KUrl url, const QString group, const QString &groupId) +void KdenliveDoc::slotAddClipFile(const KUrl &url, const stringMap &data) { - m_clipManager->slotAddClipFile(url, group, groupId); + m_clipManager->slotAddClipFile(url, data); emit selectLastAddedClip(QString::number(m_clipManager->lastClipId())); setModified(true); } @@ -1022,16 +1296,23 @@ DocClipBase *KdenliveDoc::getBaseClip(const QString &clipId) return m_clipManager->getClipById(clipId); } -void KdenliveDoc::slotCreateColorClip(const QString &name, const QString &color, const QString &duration, QString group, const QString &groupId) +void KdenliveDoc::slotCreateXmlClip(const QString &name, const QDomElement &xml, const QString &group, const QString &groupId) +{ + m_clipManager->slotAddXmlClipFile(name, xml, group, groupId); + setModified(true); + emit selectLastAddedClip(QString::number(m_clipManager->lastClipId())); +} + +void KdenliveDoc::slotCreateColorClip(const QString &name, const QString &color, const QString &duration, const QString &group, const QString &groupId) { m_clipManager->slotAddColorClipFile(name, color, duration, group, groupId); setModified(true); emit selectLastAddedClip(QString::number(m_clipManager->lastClipId())); } -void KdenliveDoc::slotCreateSlideshowClipFile(const QString name, const QString path, int count, const QString duration, const bool loop, const bool fade, const QString &luma_duration, const QString &luma_file, const int softness, QString group, const QString &groupId) +void KdenliveDoc::slotCreateSlideshowClipFile(const QMap &properties, const QString &group, const QString &groupId) { - m_clipManager->slotAddSlideshowClipFile(name, path, count, duration, loop, fade, luma_duration, luma_file, softness, group, groupId); + m_clipManager->slotAddSlideshowClipFile(properties, group, groupId); setModified(true); emit selectLastAddedClip(QString::number(m_clipManager->lastClipId())); } @@ -1040,12 +1321,8 @@ void KdenliveDoc::slotCreateTextClip(QString group, const QString &groupId, cons { QString titlesFolder = projectFolder().path(KUrl::AddTrailingSlash) + "titles/"; KStandardDirs::makeDir(titlesFolder); - TitleWidget *dia_ui = new TitleWidget(templatePath, m_timecode, titlesFolder, m_render, kapp->activeWindow()); + QPointer dia_ui = new TitleWidget(templatePath, m_timecode, titlesFolder, m_render, kapp->activeWindow()); if (dia_ui->exec() == QDialog::Accepted) { - /*QStringList titleInfo = TitleWidget::getFreeTitleInfo(projectFolder()); - QImage pix = dia_ui->renderedPixmap(); - pix.save(titleInfo.at(1));*/ - //dia_ui->saveTitle(path + ".kdenlivetitle"); m_clipManager->slotAddTextClipFile(i18n("Title clip"), dia_ui->duration(), dia_ui->xml().toString(), group, groupId); setModified(true); emit selectLastAddedClip(QString::number(m_clipManager->lastClipId())); @@ -1053,24 +1330,18 @@ void KdenliveDoc::slotCreateTextClip(QString group, const QString &groupId, cons delete dia_ui; } -void KdenliveDoc::slotCreateTextTemplateClip(QString group, const QString &groupId, KUrl path) +void KdenliveDoc::slotCreateTextTemplateClip(const QString &group, const QString &groupId, KUrl path) { QString titlesFolder = projectFolder().path(KUrl::AddTrailingSlash) + "titles/"; if (path.isEmpty()) { - path = KFileDialog::getOpenUrl(KUrl(titlesFolder), "*.kdenlivetitle", kapp->activeWindow(), i18n("Enter Template Path")); + path = KFileDialog::getOpenUrl(KUrl(titlesFolder), "application/x-kdenlivetitle", kapp->activeWindow(), i18n("Enter Template Path")); } if (path.isEmpty()) return; - QStringList titleInfo = TitleWidget::getFreeTitleInfo(projectFolder(), true); - //TODO: rewrite with new title system (just set resource) - /*TitleWidget *dia_ui = new TitleWidget(path, titlesFolder, m_render, kapp->activeWindow()); - QImage pix = dia_ui->renderedPixmap(); - pix.save(titleInfo.at(1)); - delete dia_ui; - m_clipManager->slotAddTextTemplateClip(titleInfo.at(0), titleInfo.at(1), path, group, groupId); - setModified(true);*/ + m_clipManager->slotAddTextTemplateClip(i18n("Template title clip"), path, group, groupId); + setModified(true); emit selectLastAddedClip(QString::number(m_clipManager->lastClipId())); } @@ -1081,30 +1352,60 @@ int KdenliveDoc::tracksCount() const TrackInfo KdenliveDoc::trackInfoAt(int ix) const { + if (ix < 0 || ix >= m_tracksList.count()) { + kWarning() << "Track INFO outisde of range"; + return TrackInfo(); + } return m_tracksList.at(ix); } void KdenliveDoc::switchTrackAudio(int ix, bool hide) { + if (ix < 0 || ix >= m_tracksList.count()) { + kWarning() << "SWITCH Track outisde of range"; + return; + } m_tracksList[ix].isMute = hide; // !m_tracksList.at(ix).isMute; } void KdenliveDoc::switchTrackLock(int ix, bool lock) { + if (ix < 0 || ix >= m_tracksList.count()) { + kWarning() << "Track Lock outisde of range"; + return; + } m_tracksList[ix].isLocked = lock; } bool KdenliveDoc::isTrackLocked(int ix) const { + if (ix < 0 || ix >= m_tracksList.count()) { + kWarning() << "Track Lock outisde of range"; + return true; + } return m_tracksList.at(ix).isLocked; } void KdenliveDoc::switchTrackVideo(int ix, bool hide) { + if (ix < 0 || ix >= m_tracksList.count()) { + kWarning() << "SWITCH Track outisde of range"; + return; + } m_tracksList[ix].isBlind = hide; // !m_tracksList.at(ix).isBlind; } -void KdenliveDoc::insertTrack(int ix, TrackInfo type) +int KdenliveDoc::trackDuration(int ix) +{ + return m_tracksList.at(ix).duration; +} + +void KdenliveDoc::setTrackDuration(int ix, int duration) +{ + m_tracksList[ix].duration = duration; +} + +void KdenliveDoc::insertTrack(int ix, const TrackInfo &type) { if (ix == -1) m_tracksList << type; else m_tracksList.insert(ix, type); @@ -1112,15 +1413,24 @@ void KdenliveDoc::insertTrack(int ix, TrackInfo type) void KdenliveDoc::deleteTrack(int ix) { + if (ix < 0 || ix >= m_tracksList.count()) { + kWarning() << "Delete Track outisde of range"; + return; + } m_tracksList.removeAt(ix); } -void KdenliveDoc::setTrackType(int ix, TrackInfo type) +void KdenliveDoc::setTrackType(int ix, const TrackInfo &type) { + if (ix < 0 || ix >= m_tracksList.count()) { + kWarning() << "SET Track Type outisde of range"; + return; + } m_tracksList[ix].type = type.type; m_tracksList[ix].isMute = type.isMute; m_tracksList[ix].isBlind = type.isBlind; m_tracksList[ix].isLocked = type.isLocked; + m_tracksList[ix].trackName = type.trackName; } const QList KdenliveDoc::tracksList() const @@ -1132,71 +1442,432 @@ QPoint KdenliveDoc::getTracksCount() const { int audio = 0; int video = 0; - foreach(const TrackInfo &info, m_tracksList) { - if (info.type == VIDEOTRACK) video++; + foreach(const TrackInfo & info, m_tracksList) { + if (info.type == VideoTrack) video++; else audio++; } return QPoint(video, audio); } -void KdenliveDoc::cachePixmap(const QString &fileId, const QPixmap &pix) const +void KdenliveDoc::cacheImage(const QString &fileId, const QImage &img) const +{ + img.save(m_projectFolder.path(KUrl::AddTrailingSlash) + "thumbs/" + fileId + ".png"); +} + +bool KdenliveDoc::checkDocumentClips(QDomNodeList infoproducers) +{ + DocumentChecker d(infoproducers, m_document); + return (d.hasErrorInClips() == false); + + /* int clipType; + QDomElement e; + QString id; + QString resource; + QList missingClips; + for (int i = 0; i < infoproducers.count(); ++i) { + e = infoproducers.item(i).toElement(); + clipType = e.attribute("type").toInt(); + if (clipType == COLOR) continue; + if (clipType == TEXT) { + //TODO: Check is clip template is missing (xmltemplate) or hash changed + continue; + } + id = e.attribute("id"); + resource = e.attribute("resource"); + if (clipType == SLIDESHOW) resource = KUrl(resource).directory(); + if (!KIO::NetAccess::exists(KUrl(resource), KIO::NetAccess::SourceSide, 0)) { + // Missing clip found + missingClips.append(e); + } else { + // Check if the clip has changed + if (clipType != SLIDESHOW && e.hasAttribute("file_hash")) { + if (e.attribute("file_hash") != DocClipBase::getHash(e.attribute("resource"))) + e.removeAttribute("file_hash"); + } + } + } + if (missingClips.isEmpty()) return true; + DocumentChecker d(missingClips, m_document); + return (d.exec() == QDialog::Accepted);*/ +} + +void KdenliveDoc::setDocumentProperty(const QString &name, const QString &value) +{ + m_documentProperties[name] = value; +} + +const QString KdenliveDoc::getDocumentProperty(const QString &name) const { - pix.save(m_projectFolder.path(KUrl::AddTrailingSlash) + "thumbs/" + fileId + ".png"); + return m_documentProperties.value(name); } -QString KdenliveDoc::getLadspaFile() const +QMap KdenliveDoc::getRenderProperties() const { - int ct = 0; - QString counter = QString::number(ct).rightJustified(5, '0', false); - while (QFile::exists(m_projectFolder.path(KUrl::AddTrailingSlash) + "ladspa/" + counter + ".ladspa")) { - ct++; - counter = QString::number(ct).rightJustified(5, '0', false); + QMap renderProperties; + QMapIterator i(m_documentProperties); + while (i.hasNext()) { + i.next(); + if (i.key().startsWith("render")) renderProperties.insert(i.key(), i.value()); } - return m_projectFolder.path(KUrl::AddTrailingSlash) + "ladspa/" + counter + ".ladspa"; + return renderProperties; } -bool KdenliveDoc::checkDocumentClips(QDomNodeList infoproducers) +void KdenliveDoc::addTrackEffect(int ix, QDomElement effect) +{ + if (ix < 0 || ix >= m_tracksList.count()) { + kWarning() << "Add Track effect outisde of range"; + return; + } + effect.setAttribute("kdenlive_ix", m_tracksList.at(ix).effectsList.count() + 1); + + // Init parameter value & keyframes if required + QDomNodeList params = effect.elementsByTagName("parameter"); + for (int i = 0; i < params.count(); ++i) { + QDomElement e = params.item(i).toElement(); + + // Check if this effect has a variable parameter + if (e.attribute("default").contains('%')) { + double evaluatedValue = ProfilesDialog::getStringEval(m_profile, e.attribute("default")); + e.setAttribute("default", evaluatedValue); + if (e.hasAttribute("value") && e.attribute("value").startsWith('%')) { + e.setAttribute("value", evaluatedValue); + } + } + + if (!e.isNull() && (e.attribute("type") == "keyframe" || e.attribute("type") == "simplekeyframe")) { + QString def = e.attribute("default"); + // Effect has a keyframe type parameter, we need to set the values + if (e.attribute("keyframes").isEmpty()) { + e.setAttribute("keyframes", "0:" + def + ';'); + kDebug() << "///// EFFECT KEYFRAMES INITED: " << e.attribute("keyframes"); + //break; + } + } + + if (effect.attribute("id") == "crop") { + // default use_profile to 1 for clips with proxies to avoid problems when rendering + if (e.attribute("name") == "use_profile" && getDocumentProperty("enableproxy") == "1") + e.setAttribute("value", "1"); + } + } + + m_tracksList[ix].effectsList.append(effect); +} + +void KdenliveDoc::removeTrackEffect(int ix, const QDomElement &effect) +{ + if (ix < 0 || ix >= m_tracksList.count()) { + kWarning() << "Remove Track effect outisde of range"; + return; + } + int index; + int toRemove = effect.attribute("kdenlive_ix").toInt(); + for (int i = 0; i < m_tracksList.at(ix).effectsList.count(); ++i) { + index = m_tracksList.at(ix).effectsList.at(i).attribute("kdenlive_ix").toInt(); + if (toRemove == index) { + m_tracksList[ix].effectsList.removeAt(toRemove); + break; + } + } +} + +void KdenliveDoc::setTrackEffect(int trackIndex, int effectIndex, QDomElement effect) +{ + if (trackIndex < 0 || trackIndex >= m_tracksList.count()) { + kWarning() << "Set Track effect outisde of range"; + return; + } + if (effectIndex <= 0 || effectIndex > (m_tracksList.at(trackIndex).effectsList.count()) || effect.isNull()) { + kDebug() << "Invalid effect index: " << effectIndex; + return; + } + m_tracksList[trackIndex].effectsList.removeAt(effect.attribute("kdenlive_ix").toInt()); + effect.setAttribute("kdenlive_ix", effectIndex); + m_tracksList[trackIndex].effectsList.insert(effect); + //m_tracksList[trackIndex].effectsList.updateEffect(effect); +} + +void KdenliveDoc::enableTrackEffects(int trackIndex, const QList &effectIndexes, bool disable) +{ + if (trackIndex < 0 || trackIndex >= m_tracksList.count()) { + kWarning() << "Set Track effect outisde of range"; + return; + } + EffectsList list = m_tracksList.at(trackIndex).effectsList; + QDomElement effect; + for (int i = 0; i < effectIndexes.count(); ++i) { + effect = list.itemFromIndex(effectIndexes.at(i)); + if (!effect.isNull()) effect.setAttribute("disable", (int) disable); + } +} + +const EffectsList KdenliveDoc::getTrackEffects(int ix) +{ + if (ix < 0 || ix >= m_tracksList.count()) { + kWarning() << "Get Track effects outisde of range"; + return EffectsList(); + } + return m_tracksList.at(ix).effectsList; +} + +QDomElement KdenliveDoc::getTrackEffect(int trackIndex, int effectIndex) const +{ + if (trackIndex < 0 || trackIndex >= m_tracksList.count()) { + kWarning() << "Get Track effect outisde of range"; + return QDomElement(); + } + EffectsList list = m_tracksList.at(trackIndex).effectsList; + if (effectIndex > list.count() || effectIndex < 1 || list.itemFromIndex(effectIndex).isNull()) return QDomElement(); + return list.itemFromIndex(effectIndex).cloneNode().toElement(); +} + +int KdenliveDoc::hasTrackEffect(int trackIndex, const QString &tag, const QString &id) const +{ + if (trackIndex < 0 || trackIndex >= m_tracksList.count()) { + kWarning() << "Get Track effect outisde of range"; + return -1; + } + EffectsList list = m_tracksList.at(trackIndex).effectsList; + return list.hasEffect(tag, id); +} + +bool KdenliveDoc::saveCustomEffects(const QDomNodeList &customeffects) { - int clipType; QDomElement e; - QString id; - QString resource; - QList missingClips; - for (int i = 0; i < infoproducers.count(); i++) { - e = infoproducers.item(i).toElement(); - clipType = e.attribute("type").toInt(); - if (clipType == COLOR) continue; - if (clipType == TEXT) { - //TODO: Check is clip template is missing (xmltemplate) or hash changed - continue; + QStringList importedEffects; + int maxchild = customeffects.count(); + for (int i = 0; i < maxchild; ++i) { + e = customeffects.at(i).toElement(); + const QString id = e.attribute("id"); + const QString tag = e.attribute("tag"); + if (!id.isEmpty()) { + // Check if effect exists or save it + if (MainWindow::customEffects.hasEffect(tag, id) == -1) { + QDomDocument doc; + doc.appendChild(doc.importNode(e, true)); + QString path = KStandardDirs::locateLocal("appdata", "effects/", true); + path += id + ".xml"; + if (!QFile::exists(path)) { + importedEffects << id; + QFile file(path); + if (file.open(QFile::WriteOnly | QFile::Truncate)) { + QTextStream out(&file); + out << doc.toString(); + } + } + } } - id = e.attribute("id"); - resource = e.attribute("resource"); - if (clipType == SLIDESHOW) resource = KUrl(resource).directory(); - if (!KIO::NetAccess::exists(KUrl(resource), KIO::NetAccess::SourceSide, 0)) { - // Missing clip found - missingClips.append(e); - } else { - // Check if the clip has changed - if (clipType != SLIDESHOW && e.hasAttribute("file_hash")) { - if (e.attribute("file_hash") != DocClipBase::getHash(e.attribute("resource"))) - e.removeAttribute("file_hash"); + } + if (!importedEffects.isEmpty()) + KMessageBox::informationList(kapp->activeWindow(), i18n("The following effects were imported from the project:"), importedEffects); + return (!importedEffects.isEmpty()); +} + +void KdenliveDoc::updateProjectFolderPlacesEntry() +{ + /* + * For similar and more code have a look at kfileplacesmodel.cpp and the included files: + * http://websvn.kde.org/trunk/KDE/kdelibs/kfile/kfileplacesmodel.cpp?view=markup + */ + + const QString file = KStandardDirs::locateLocal("data", "kfileplaces/bookmarks.xml"); + KBookmarkManager *bookmarkManager = KBookmarkManager::managerForFile(file, "kfilePlaces"); + if (!bookmarkManager) return; + KBookmarkGroup root = bookmarkManager->root(); + + KBookmark bookmark = root.first(); + + QString kdenliveName = KGlobal::mainComponent().componentName(); + KUrl documentLocation = m_projectFolder; + + bool exists = false; + + while (!bookmark.isNull()) { + // UDI not empty indicates a device + QString udi = bookmark.metaDataItem("UDI"); + QString appName = bookmark.metaDataItem("OnlyInApp"); + + if (udi.isEmpty() && appName == kdenliveName && bookmark.text() == i18n("Project Folder")) { + if (bookmark.url() != documentLocation) { + bookmark.setUrl(documentLocation); + bookmarkManager->emitChanged(root); } + exists = true; + break; } + + bookmark = root.next(bookmark); + } + + // if entry does not exist yet (was not found), well, create it then + if (!exists) { + bookmark = root.addBookmark(i18n("Project Folder"), documentLocation, "folder-favorites"); + // Make this user selectable ? + bookmark.setMetaDataItem("OnlyInApp", kdenliveName); + bookmarkManager->emitChanged(root); } - if (missingClips.isEmpty()) return true; - DocumentChecker d(missingClips, m_document); - return (d.exec() == QDialog::Accepted); } -void KdenliveDoc::setDocumentProperty(const QString &name, const QString &value) +QStringList KdenliveDoc::getExpandedFolders() { - m_documentProperties[name] = value; + QStringList result = m_documentProperties.value("expandedfolders").split(';'); + // this property is only needed once when opening project, so clear it now + m_documentProperties.remove("expandedfolders"); + return result; } -const QString KdenliveDoc::getDocumentProperty(const QString &name) const +// static +double KdenliveDoc::getDisplayRatio(const QString &path) { - return m_documentProperties.value(name); + QFile file(path); + QDomDocument doc; + if (!file.open(QIODevice::ReadOnly)) { + kWarning() << "ERROR, CANNOT READ: " << path; + return 0; + } + if (!doc.setContent(&file)) { + kWarning() << "ERROR, CANNOT READ: " << path; + file.close(); + return 0; + } + file.close(); + QDomNodeList list = doc.elementsByTagName("profile"); + if (list.isEmpty()) return 0; + QDomElement profile = list.at(0).toElement(); + double den = profile.attribute("display_aspect_den").toDouble(); + if (den > 0) return profile.attribute("display_aspect_num").toDouble() / den; + return 0; +} + +void KdenliveDoc::backupLastSavedVersion(const QString &path) +{ + // Ensure backup folder exists + if (path.isEmpty()) return; + QFile file(path); + KUrl backupFile = m_projectFolder; + backupFile.addPath(".backup/"); + KIO::NetAccess::mkdir(backupFile, kapp->activeWindow()); + QString fileName = KUrl(path).fileName().section('.', 0, -2); + QFileInfo info(file); + fileName.append('-' + m_documentProperties.value("documentid")); + fileName.append(info.lastModified().toString("-yyyy-MM-dd-hh-mm")); + fileName.append(".kdenlive"); + backupFile.addPath(fileName); + + if (file.exists()) { + // delete previous backup if it was done less than 60 seconds ago + QFile::remove(backupFile.path()); + if (!QFile::copy(path, backupFile.path())) { + KMessageBox::information(kapp->activeWindow(), i18n("Cannot create backup copy:\n%1", backupFile.path())); + } + } +} + +void KdenliveDoc::cleanupBackupFiles() +{ + KUrl backupFile = m_projectFolder; + backupFile.addPath(".backup/"); + QDir dir(backupFile.path()); + QString projectFile = url().fileName().section('.', 0, -2); + projectFile.append('-' + m_documentProperties.value("documentid")); + projectFile.append("-??"); + projectFile.append("??"); + projectFile.append("-??"); + projectFile.append("-??"); + projectFile.append("-??"); + projectFile.append("-??.kdenlive"); + + QStringList filter; + backupFile.addPath(projectFile); + filter << projectFile; + dir.setNameFilters(filter); + QFileInfoList resultList = dir.entryInfoList(QDir::Files, QDir::Time); + + QDateTime d = QDateTime::currentDateTime(); + QStringList hourList; + QStringList dayList; + QStringList weekList; + QStringList oldList; + for (int i = 0; i < resultList.count(); ++i) { + if (d.secsTo(resultList.at(i).lastModified()) < 3600) { + // files created in the last hour + hourList.append(resultList.at(i).absoluteFilePath()); + } + else if (d.secsTo(resultList.at(i).lastModified()) < 43200) { + // files created in the day + dayList.append(resultList.at(i).absoluteFilePath()); + } + else if (d.daysTo(resultList.at(i).lastModified()) < 8) { + // files created in the week + weekList.append(resultList.at(i).absoluteFilePath()); + } + else { + // older files + oldList.append(resultList.at(i).absoluteFilePath()); + } + } + if (hourList.count() > 20) { + int step = hourList.count() / 10; + for (int i = 0; i < hourList.count(); i += step) { + kDebug()<<"REMOVE AT: "< 20) { + int step = dayList.count() / 10; + for (int i = 0; i < dayList.count(); i += step) { + dayList.removeAt(i); + --i; + } + } else dayList.clear(); + if (weekList.count() > 20) { + int step = weekList.count() / 10; + for (int i = 0; i < weekList.count(); i += step) { + weekList.removeAt(i); + --i; + } + } else weekList.clear(); + if (oldList.count() > 20) { + int step = oldList.count() / 10; + for (int i = 0; i < oldList.count(); i += step) { + oldList.removeAt(i); + --i; + } + } else oldList.clear(); + + QString f; + while (hourList.count() > 0) { + f = hourList.takeFirst(); + QFile::remove(f); + QFile::remove(f + ".png"); + } + while (dayList.count() > 0) { + f = dayList.takeFirst(); + QFile::remove(f); + QFile::remove(f + ".png"); + } + while (weekList.count() > 0) { + f = weekList.takeFirst(); + QFile::remove(f); + QFile::remove(f + ".png"); + } + while (oldList.count() > 0) { + f = oldList.takeFirst(); + QFile::remove(f); + QFile::remove(f + ".png"); + } +} + +const QMap KdenliveDoc::metadata() const +{ + return m_documentMetadata; +} + +void KdenliveDoc::setMetadata(const QMap &meta) +{ + setModified(true); + m_documentMetadata = meta; } #include "kdenlivedoc.moc"