diff --git a/src/doc/kdenlivedoc.cpp b/src/doc/kdenlivedoc.cpp index 7ca06e974..ba8b571c5 100644 --- a/src/doc/kdenlivedoc.cpp +++ b/src/doc/kdenlivedoc.cpp @@ -1,1608 +1,1625 @@ /*************************************************************************** * Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@kdenlive.org) * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * 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 * ***************************************************************************/ #include "kdenlivedoc.h" #include "documentchecker.h" #include "documentvalidator.h" #include "mltcontroller/clipcontroller.h" #include "mltcontroller/producerqueue.h" #include #include "kdenlivesettings.h" #include "renderer.h" #include "mainwindow.h" #include "project/clipmanager.h" #include "project/projectcommands.h" #include "bin/bincommands.h" #include "effectslist/initeffects.h" #include "dialogs/profilesdialog.h" #include "titler/titlewidget.h" #include "project/notesplugin.h" #include "project/dialogs/noteswidget.h" #include "core.h" #include "bin/bin.h" #include "bin/projectclip.h" #include "utils/KoIconUtils.h" #include "mltcontroller/bincontroller.h" #include "mltcontroller/effectscontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_MAC #include #endif const double DOCUMENTVERSION = 0.94; KdenliveDoc::KdenliveDoc(const QUrl &url, const QUrl &projectFolder, QUndoGroup *undoGroup, const QString &profileName, const QMap & properties, const QMap & metadata, const QPoint &tracks, Render *render, NotesPlugin *notes, bool *openBackup, MainWindow *parent) : QObject(parent), m_autosave(NULL), m_url(url), m_width(0), m_height(0), m_render(render), m_notesWidget(notes->widget()), 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); connect(m_clipManager, SIGNAL(displayMessage(QString,int)), parent, SLOT(slotGotProgressInfo(QString,int))); bool success = false; connect(m_commandStack, SIGNAL(indexChanged(int)), this, SLOT(slotModified())); connect(m_render, SIGNAL(setDocumentNotes(QString)), this, SLOT(slotSetDocumentNotes(QString))); connect(pCore->producerQueue(), &ProducerQueue::switchProfile, this, &KdenliveDoc::switchProfile); //connect(m_commandStack, SIGNAL(cleanChanged(bool)), this, SLOT(setModified(bool))); // Init clip modification tracker m_modifiedTimer.setInterval(1500); connect(&m_fileWatcher, &KDirWatch::dirty, this, &KdenliveDoc::slotClipModified); connect(&m_fileWatcher, &KDirWatch::deleted, this, &KdenliveDoc::slotClipMissing); connect(&m_modifiedTimer, &QTimer::timeout, this, &KdenliveDoc::slotProcessModifiedClips); // init default document properties m_documentProperties[QStringLiteral("zoom")] = '7'; m_documentProperties[QStringLiteral("verticalzoom")] = '1'; m_documentProperties[QStringLiteral("zonein")] = '0'; m_documentProperties[QStringLiteral("zoneout")] = QStringLiteral("100"); m_documentProperties[QStringLiteral("enableproxy")] = QString::number((int) KdenliveSettings::enableproxy()); m_documentProperties[QStringLiteral("proxyparams")] = KdenliveSettings::proxyparams(); m_documentProperties[QStringLiteral("proxyextension")] = KdenliveSettings::proxyextension(); m_documentProperties[QStringLiteral("generateproxy")] = QString::number((int) KdenliveSettings::generateproxy()); m_documentProperties[QStringLiteral("proxyminsize")] = QString::number(KdenliveSettings::proxyminsize()); m_documentProperties[QStringLiteral("generateimageproxy")] = QString::number((int) KdenliveSettings::generateimageproxy()); m_documentProperties[QStringLiteral("proxyimageminsize")] = QString::number(KdenliveSettings::proxyimageminsize()); m_documentProperties[QStringLiteral("documentid")] = QString::number(QDateTime::currentMSecsSinceEpoch()); // 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(pCore->binController()->mltRepository(), setlocale(LC_NUMERIC, NULL)); } *openBackup = false; if (url.isValid()) { QFile file(url.toLocalFile()); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { // The file cannot be opened 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) { *openBackup = true; } //KMessageBox::error(parent, KIO::NetAccess::lastErrorString()); } else { qDebug()<<" // / processing file open"; QString errorMsg; int line; int col; QDomImplementation::setInvalidDataPolicy(QDomImplementation::DropInvalidChars); success = m_document.setContent(&file, false, &errorMsg, &line, &col); file.close(); 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(QStringLiteral("\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(); mlt.setAttribute(QStringLiteral("modified"), 1); } } } } else { qDebug()<<" // / processing file open: validate"; 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); 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 */ // TODO: backup the document or alert the user? success = validator.validate(DOCUMENTVERSION); if (success && !KdenliveSettings::gpu_accel()) { success = validator.checkMovit(); } if (success) { // Let the validator handle error messages qDebug()<<" // / processing file validate ok"; parent->slotGotProgressInfo(i18n("Check missing clips"), 0); qApp->processEvents(); DocumentChecker d(m_url, m_document); success = !d.hasErrorInClips(); if (success) { loadDocumentProperties(); if (m_document.documentElement().attribute(QStringLiteral("modified")) == QLatin1String("1")) setModified(true); if (validator.isModified()) setModified(true); } } } } } } // Something went wrong, or a new file was requested: create a new project if (!success) { m_url.clear(); m_profile = ProfilesDialog::getVideoProfile(profileName); m_document = createEmptyDocument(tracks.x(), tracks.y()); updateProjectProfile(false); } // Ask to create the project directory if it does not exist QFileInfo checkProjectFolder(m_projectFolder.toString(QUrl::RemoveFilename | QUrl::RemoveScheme)); if (!QFile::exists(m_projectFolder.path()) && checkProjectFolder.isWritable()) { 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() || !QFile::exists(m_projectFolder.path())) { KMessageBox::information(parent, i18n("Document project folder is invalid, setting it to the default one: %1", KdenliveSettings::defaultprojectfolder())); m_projectFolder = QUrl::fromLocalFile(KdenliveSettings::defaultprojectfolder()); } // Make sure that the necessary folders exist QDir dir(m_projectFolder.path()); dir.mkdir(QStringLiteral("titles")); dir.mkdir(QStringLiteral("thumbs")); dir.mkdir(QStringLiteral("proxy")); dir.mkdir(QStringLiteral(".backup")); updateProjectFolderPlacesEntry(); } void KdenliveDoc::slotSetDocumentNotes(const QString ¬es) { m_notesWidget->setHtml(notes); } KdenliveDoc::~KdenliveDoc() { delete m_commandStack; //qDebug() << "// DEL CLP MAN"; delete m_clipManager; //qDebug() << "// DEL CLP MAN done"; if (m_autosave) { if (!m_autosave->fileName().isEmpty()) m_autosave->remove(); delete m_autosave; } } int KdenliveDoc::setSceneList() { //m_render->resetProfile(m_profile); pCore->bin()->isLoading = true; pCore->producerQueue()->abortOperations(); if (m_render->setSceneList(m_document.toString(), m_documentProperties.value(QStringLiteral("position")).toInt()) == -1) { // INVALID MLT Consumer, something is wrong return -1; } pCore->bin()->isLoading = false; pCore->binController()->checkThumbnails(projectFolder().path() + "/thumbs/"); m_documentProperties.remove(QStringLiteral("position")); pCore->monitorManager()->activateMonitor(Kdenlive::ClipMonitor, true); return 0; } QDomDocument KdenliveDoc::createEmptyDocument(int videotracks, int audiotracks) { QList tracks; // 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 = i18n("Audio %1", audiotracks - i); audioTrack.duration = 0; audioTrack.effectsList = EffectsList(true); tracks.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 = i18n("Video %1", i + 1); videoTrack.duration = 0; videoTrack.effectsList = EffectsList(true); tracks.append(videoTrack); } return createEmptyDocument(tracks); } QDomDocument KdenliveDoc::createEmptyDocument(const QList &tracks) { // Creating new document QDomDocument doc; QDomElement mlt = doc.createElement(QStringLiteral("mlt")); mlt.setAttribute(QStringLiteral("LC_NUMERIC"), QLatin1String("")); doc.appendChild(mlt); QDomElement blk = doc.createElement(QStringLiteral("producer")); blk.setAttribute(QStringLiteral("in"), 0); blk.setAttribute(QStringLiteral("out"), 500); blk.setAttribute(QStringLiteral("id"), QStringLiteral("black")); QDomElement property = doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("mlt_type")); QDomText value = doc.createTextNode(QStringLiteral("producer")); property.appendChild(value); blk.appendChild(property); property = doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("aspect_ratio")); value = doc.createTextNode(QString::number(0)); property.appendChild(value); blk.appendChild(property); property = doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("length")); value = doc.createTextNode(QString::number(15000)); property.appendChild(value); blk.appendChild(property); property = doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("eof")); value = doc.createTextNode(QStringLiteral("pause")); property.appendChild(value); blk.appendChild(property); property = doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("resource")); value = doc.createTextNode(QStringLiteral("black")); property.appendChild(value); blk.appendChild(property); property = doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("mlt_service")); value = doc.createTextNode(QStringLiteral("colour")); property.appendChild(value); blk.appendChild(property); mlt.appendChild(blk); QDomElement tractor = doc.createElement(QStringLiteral("tractor")); tractor.setAttribute(QStringLiteral("id"), QStringLiteral("maintractor")); tractor.setAttribute(QStringLiteral("global_feed"), 1); //QDomElement multitrack = doc.createElement("multitrack"); QDomElement playlist = doc.createElement(QStringLiteral("playlist")); playlist.setAttribute(QStringLiteral("id"), QStringLiteral("black_track")); mlt.appendChild(playlist); QDomElement blank0 = doc.createElement(QStringLiteral("entry")); blank0.setAttribute(QStringLiteral("in"), QStringLiteral("0")); blank0.setAttribute(QStringLiteral("out"), QStringLiteral("1")); blank0.setAttribute(QStringLiteral("producer"), QStringLiteral("black")); playlist.appendChild(blank0); // create playlists int total = tracks.count(); // The lower video track will recieve composite transitions int lowerVideoTrack = -1; for (int i = 0; i < total; ++i) { QDomElement playlist = doc.createElement(QStringLiteral("playlist")); playlist.setAttribute(QStringLiteral("id"), "playlist" + QString::number(i+1)); playlist.setAttribute(QStringLiteral("kdenlive:track_name"), tracks.at(i).trackName); if (tracks.at(i).type == AudioTrack) { playlist.setAttribute(QStringLiteral("kdenlive:audio_track"), 1); } else if (lowerVideoTrack == -1) { // Register first video track lowerVideoTrack = i + 1; } mlt.appendChild(playlist); } QDomElement track0 = doc.createElement(QStringLiteral("track")); track0.setAttribute(QStringLiteral("producer"), QStringLiteral("black_track")); tractor.appendChild(track0); // create audio and video tracks for (int i = 0; i < total; ++i) { QDomElement track = doc.createElement(QStringLiteral("track")); track.setAttribute(QStringLiteral("producer"), "playlist" + QString::number(i+1)); if (tracks.at(i).type == AudioTrack) { track.setAttribute(QStringLiteral("hide"), QStringLiteral("video")); } else if (tracks.at(i).isBlind) { if (tracks.at(i).isMute) { track.setAttribute(QStringLiteral("hide"), QStringLiteral("all")); } else track.setAttribute(QStringLiteral("hide"), QStringLiteral("video")); } else if (tracks.at(i).isMute) track.setAttribute(QStringLiteral("hide"), QStringLiteral("audio")); tractor.appendChild(track); } for (int i = 0; i < total; i++) { QDomElement transition = doc.createElement(QStringLiteral("transition")); transition.setAttribute(QStringLiteral("always_active"), QStringLiteral("1")); QDomElement property = doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("mlt_service")); value = doc.createTextNode(QStringLiteral("mix")); property.appendChild(value); transition.appendChild(property); property = doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("a_track")); QDomText value = doc.createTextNode(QStringLiteral("0")); property.appendChild(value); transition.appendChild(property); property = doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("b_track")); value = doc.createTextNode(QString::number(i + 1)); property.appendChild(value); transition.appendChild(property); property = doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("combine")); value = doc.createTextNode(QStringLiteral("1")); property.appendChild(value); transition.appendChild(property); property = doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("internal_added")); value = doc.createTextNode(QStringLiteral("237")); property.appendChild(value); transition.appendChild(property); tractor.appendChild(transition); if (i >= lowerVideoTrack && tracks.at(i).type == VideoTrack) { // Only add composite transition if both tracks are video transition = doc.createElement(QStringLiteral("transition")); property = doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("mlt_service")); property.appendChild(doc.createTextNode(KdenliveSettings::gpu_accel() ? "movit.overlay" : "frei0r.cairoblend")); transition.appendChild(property); property = doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("a_track")); property.appendChild(doc.createTextNode(QString::number(lowerVideoTrack))); transition.appendChild(property); property = doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("b_track")); property.appendChild(doc.createTextNode(QString::number(i+1))); transition.appendChild(property); property = doc.createElement(QStringLiteral("property")); property.setAttribute(QStringLiteral("name"), QStringLiteral("internal_added")); property.appendChild(doc.createTextNode(QStringLiteral("237"))); transition.appendChild(property); tractor.appendChild(transition); } } mlt.appendChild(tractor); return doc; } bool KdenliveDoc::useProxy() const { return m_documentProperties.value(QStringLiteral("enableproxy")).toInt(); } bool KdenliveDoc::autoGenerateProxy(int width) const { return m_documentProperties.value(QStringLiteral("generateproxy")).toInt() && width > m_documentProperties.value(QStringLiteral("proxyminsize")).toInt(); } bool KdenliveDoc::autoGenerateImageProxy(int width) const { return m_documentProperties.value(QStringLiteral("generateimageproxy")).toInt() && width > m_documentProperties.value(QStringLiteral("proxyimageminsize")).toInt(); } void KdenliveDoc::slotAutoSave() { if (m_render && m_autosave) { if (!m_autosave->isOpen() && !m_autosave->open(QIODevice::ReadWrite)) { // show error: could not open the autosave file qDebug() << "ERROR; CANNOT CREATE AUTOSAVE FILE"; } //qDebug() << "// AUTOSAVE FILE: " << m_autosave->fileName(); QDomDocument sceneList = xmlSceneList(m_render->sceneList()); if (sceneList.isNull()) { //Make sure we don't save if scenelist is corrupted KMessageBox::error(QApplication::activeWindow(), i18n("Cannot write to file %1, scene list is corrupted.", m_autosave->fileName())); return; } m_autosave->resize(0); m_autosave->write(sceneList.toString().toUtf8()); m_autosave->flush(); } } void KdenliveDoc::setZoom(int horizontal, int vertical) { m_documentProperties[QStringLiteral("zoom")] = QString::number(horizontal); m_documentProperties[QStringLiteral("verticalzoom")] = QString::number(vertical); } QPoint KdenliveDoc::zoom() const { return QPoint(m_documentProperties.value(QStringLiteral("zoom")).toInt(), m_documentProperties.value(QStringLiteral("verticalzoom")).toInt()); } void KdenliveDoc::setZone(int start, int end) { m_documentProperties[QStringLiteral("zonein")] = QString::number(start); m_documentProperties[QStringLiteral("zoneout")] = QString::number(end); } QPoint KdenliveDoc::zone() const { return QPoint(m_documentProperties.value(QStringLiteral("zonein")).toInt(), m_documentProperties.value(QStringLiteral("zoneout")).toInt()); } QDomDocument KdenliveDoc::xmlSceneList(const QString &scene) { QDomDocument sceneList; sceneList.setContent(scene, true); QDomElement mlt = sceneList.firstChildElement(QStringLiteral("mlt")); if (mlt.isNull() || !mlt.hasChildNodes()) { //scenelist is corrupted return sceneList; } // Set playlist audio volume to 100% QDomElement tractor = mlt.firstChildElement(QStringLiteral("tractor")); if (!tractor.isNull()) { QDomNodeList props = tractor.elementsByTagName(QStringLiteral("property")); for (int i = 0; i < props.count(); ++i) { if (props.at(i).toElement().attribute(QStringLiteral("name")) == QLatin1String("meta.volume")) { props.at(i).firstChild().setNodeValue(QStringLiteral("1")); break; } } } QDomNodeList pls = mlt.elementsByTagName(QStringLiteral("playlist")); QDomElement mainPlaylist; for (int i = 0; i < pls.count(); ++i) { if (pls.at(i).toElement().attribute(QStringLiteral("id")) == pCore->binController()->binPlaylistId()) { mainPlaylist = pls.at(i).toElement(); break; } } // check if project contains custom effects to embed them in project file QDomNodeList effects = mlt.elementsByTagName(QStringLiteral("filter")); int maxEffects = effects.count(); //qDebug() << "// 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(QStringLiteral("name")) == QLatin1String("kdenlive_id")) { id = e.firstChild().nodeValue(); } if (e.attribute(QStringLiteral("name")) == QLatin1String("tag")) { tag = e.firstChild().nodeValue(); } if (!id.isEmpty() && !tag.isEmpty()) effectIds.insert(id, tag); } } //TODO: find a way to process this before rendering MLT scenelist to xml QDomDocument customeffects = initEffects::getUsedCustomEffects(effectIds); if (customeffects.documentElement().childNodes().count() > 0) { EffectsList::setProperty(mainPlaylist, QStringLiteral("kdenlive:customeffects"), customeffects.toString()); } //addedXml.appendChild(sceneList.importNode(customeffects.documentElement(), true)); //TODO: move metadata to previous step in saving process QDomElement docmetadata = sceneList.createElement(QStringLiteral("documentmetadata")); QMapIterator j(m_documentMetadata); while (j.hasNext()) { j.next(); docmetadata.setAttribute(j.key(), j.value()); } //addedXml.appendChild(docmetadata); return sceneList; } QString KdenliveDoc::documentNotes() const { QString text = m_notesWidget->toPlainText().simplified(); if (text.isEmpty()) return QString(); return m_notesWidget->toHtml(); } bool KdenliveDoc::saveSceneList(const QString &path, const QString &scene) { QDomDocument sceneList = xmlSceneList(scene); if (sceneList.isNull()) { //Make sure we don't save if scenelist is corrupted KMessageBox::error(QApplication::activeWindow(), i18n("Cannot write to file %1, scene list is corrupted.", path)); return false; } // Backup current version backupLastSavedVersion(path); QFile file(path); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qWarning() << "////// ERROR writing to file: " << path; KMessageBox::error(QApplication::activeWindow(), i18n("Cannot write to file %1", path)); return false; } file.write(sceneList.toString().toUtf8()); if (file.error() != QFile::NoError) { KMessageBox::error(QApplication::activeWindow(), i18n("Cannot write to file %1", path)); file.close(); return false; } file.close(); cleanupBackupFiles(); QFileInfo info(file); QString fileName = QUrl::fromLocalFile(path).fileName().section('.', 0, -2); fileName.append('-' + m_documentProperties.value(QStringLiteral("documentid"))); fileName.append(info.lastModified().toString(QStringLiteral("-yyyy-MM-dd-hh-mm"))); fileName.append(".kdenlive.png"); QDir backupFolder(m_projectFolder.path() + "/.backup"); emit saveTimelinePreview(backupFolder.absoluteFilePath(fileName)); return true; } ClipManager *KdenliveDoc::clipManager() { return m_clipManager; } QString KdenliveDoc::groupsXml() const { return m_clipManager->groupsXml(); } QUrl KdenliveDoc::projectFolder() const { //if (m_projectFolder.isEmpty()) return QUrl(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "//projects/"); return m_projectFolder; } void KdenliveDoc::setProjectFolder(QUrl url) { if (url == m_projectFolder) return; setModified(true); QDir dir(url.toLocalFile()); if (!dir.exists()) { dir.mkpath(dir.absolutePath()); } dir.mkdir(QStringLiteral("titles")); dir.mkdir(QStringLiteral("thumbs")); dir.mkdir(QStringLiteral("proxy")); dir.mkdir(QStringLiteral(".backup")); if (KMessageBox::questionYesNo(QApplication::activeWindow(), i18n("You have changed the project folder. Do you want to copy the cached data from %1 to the new folder %2?", m_projectFolder.path(), url.path())) == KMessageBox::Yes) moveProjectData(url); m_projectFolder = url; updateProjectFolderPlacesEntry(); } void KdenliveDoc::moveProjectData(const QUrl &url) { QList list = pCore->binController()->getControllerList(); QList cacheUrls; for (int i = 0; i < list.count(); ++i) { ClipController *clip = list.at(i); if (clip->clipType() == Text) { // the image for title clip must be moved QUrl oldUrl = clip->clipUrl(); QUrl newUrl = QUrl::fromLocalFile(url.toLocalFile() + QDir::separator() + "titles/" + oldUrl.fileName()); KIO::Job *job = KIO::copy(oldUrl, newUrl); if (job->exec()) clip->setProperty(QStringLiteral("resource"), newUrl.path()); } QString hash = clip->getClipHash(); QUrl oldVideoThumbUrl = QUrl::fromLocalFile(m_projectFolder.path() + QDir::separator() + "thumbs/" + hash + ".png"); if (QFile::exists(oldVideoThumbUrl.path())) { cacheUrls << oldVideoThumbUrl; } QUrl oldAudioThumbUrl = QUrl::fromLocalFile(m_projectFolder.path() + QDir::separator() + "thumbs/" + hash + ".thumb"); if (QFile::exists(oldAudioThumbUrl.path())) { cacheUrls << oldAudioThumbUrl; } QUrl oldVideoProxyUrl = QUrl::fromLocalFile(m_projectFolder.path() + QDir::separator() + "proxy/" + hash + '.' + KdenliveSettings::proxyextension()); if (QFile::exists(oldVideoProxyUrl.path())) { cacheUrls << oldVideoProxyUrl; } } if (!cacheUrls.isEmpty()) { KIO::Job *job = KIO::copy(cacheUrls, QUrl::fromLocalFile(url.path() + QDir::separator() + "thumbs/")); KJobWidgets::setWindow(job, QApplication::activeWindow()); job->exec(); } } const QString &KdenliveDoc::profilePath() const { return m_profile.path; } MltVideoProfile KdenliveDoc::mltProfile() const { return m_profile; } bool KdenliveDoc::profileChanged(const QString &profile) const { return m_profile.toList() != ProfilesDialog::getVideoProfile(profile).toList(); } ProfileInfo KdenliveDoc::getProfileInfo() const { ProfileInfo info; info.profileSize = getRenderSize(); info.profileFps = fps(); return info; } double KdenliveDoc::dar() const { return (double) m_profile.display_aspect_num / m_profile.display_aspect_den; } void KdenliveDoc::setThumbsProgress(const QString &message, int progress) { emit progressInfo(message, progress); } QUndoStack *KdenliveDoc::commandStack() { return m_commandStack; } Render *KdenliveDoc::renderer() { return m_render; } int KdenliveDoc::getFramePos(const QString &duration) { return m_timecode.getFrameCount(duration); } QDomDocument KdenliveDoc::toXml() { return m_document; } Timecode KdenliveDoc::timecode() const { return m_timecode; } QDomNodeList KdenliveDoc::producersList() { return m_document.elementsByTagName(QStringLiteral("producer")); } double KdenliveDoc::projectDuration() const { if (m_render) return GenTime(m_render->getLength(), m_render->fps()).ms() / 1000; else return 0; } double KdenliveDoc::fps() const { return m_render->fps(); } int KdenliveDoc::width() const { return m_width; } int KdenliveDoc::height() const { return m_height; } QUrl KdenliveDoc::url() const { return m_url; } void KdenliveDoc::setUrl(const QUrl &url) { m_url = url; } void KdenliveDoc::slotModified() { setModified(m_commandStack->isClean() == false); } void KdenliveDoc::setModified(bool mod) { // fix mantis#3160: The document may have an empty URL if not saved yet, but should have a m_autosave in any case if (m_autosave && mod && KdenliveSettings::crashrecovery()) { emit startAutoSave(); } if (mod == m_modified) return; m_modified = mod; emit docModified(m_modified); } bool KdenliveDoc::isModified() const { return m_modified; } const QString KdenliveDoc::description() const { if (!m_url.isValid()) return i18n("Untitled") + "[*] / " + m_profile.description; else return m_url.fileName() + " [*]/ " + m_profile.description; } bool KdenliveDoc::addClip(QDomElement elem, const QString &clipId) { const QString producerId = clipId.section('_', 0, 0); elem.setAttribute(QStringLiteral("id"), producerId); if (KdenliveSettings::checkfirstprojectclip() && pCore->bin()->isEmpty()) { elem.setAttribute("checkProfile", 1); } pCore->bin()->createClip(elem); pCore->producerQueue()->getFileProperties(elem, producerId, 150, true); /*QString str; QTextStream stream(&str); elem.save(stream, 4); qDebug()<<"ADDING CLIP COMMAND\n-----------\n"<getClipById(producerId); if (clip == NULL) { QString clipFolder = KRecentDirs::dir(":KdenliveClipFolder"); elem.setAttribute("id", producerId); QString path = elem.attribute("resource"); QString extension; if (elem.attribute("type").toInt() == SlideShow) { QUrl f = QUrl::fromLocalFile(path); extension = f.fileName(); path = f.adjusted(QUrl::RemoveFilename).path(); } 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")) { //qDebug() << "// 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(QApplication::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(QApplication::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 = QFileDialog::getExistingDirectory(QApplication::activeWindow(), i18n("Looking for %1", path), clipFolder); else { // Abort project loading action = res; } } else { int res = KMessageBox::questionYesNoCancel(QApplication::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 = QFileDialog::getOpenFileName(QApplication::activeWindow(), i18n("Looking for %1", path), clipFolder); else { // Abort project loading action = res; } } } if (action == KMessageBox::Yes) { //qDebug() << "// ASKED FOR SRCH CLIP: " << clipId; m_searchFolder = QFileDialog::getExistingDirectory(QApplication::activeWindow(), QString(), clipFolder); if (!m_searchFolder.isEmpty()) newpath = searchFileRecursively(QDir(m_searchFolder), size, hash); } else if (action == KMessageBox::Cancel) { return false; } else if (action == KMessageBox::No) { // Keep clip as placeHolder elem.setAttribute("placeholder", '1'); } if (!newpath.isEmpty()) { //qDebug() << "// 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); } } clip = new DocClipBase(m_clipManager, elem, producerId); m_clipManager->addClip(clip); } return true; */ } QString KdenliveDoc::searchFileRecursively(const QDir &dir, const QString &matchSize, const QString &matchHash) const { QString foundFileName; QByteArray fileData; QByteArray fileHash; QStringList filesAndDirs = dir.entryList(QDir::Files | QDir::Readable); 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) { /* * 1 MB = 1 second per 450 files (or faster) * 10 MB = 9 seconds per 450 files (or faster) */ if (file.size() > 1000000 * 2) { fileData = file.read(1000000); if (file.seek(file.size() - 1000000)) fileData.append(file.readAll()); } else fileData = file.readAll(); file.close(); fileHash = QCryptographicHash::hash(fileData, QCryptographicHash::Md5); if (QString(fileHash.toHex()) == matchHash) return file.fileName(); else qDebug() << filesAndDirs.at(i) << "size match but not hash"; } } ////qDebug() << 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) { foundFileName = searchFileRecursively(dir.absoluteFilePath(filesAndDirs.at(i)), matchSize, matchHash); if (!foundFileName.isEmpty()) break; } return foundFileName; } /* bool KdenliveDoc::addClipInfo(QDomElement elem, QDomElement orig, const QString &clipId) { DocClipBase *clip = m_clipManager->getClipById(clipId); if (clip == NULL) { if (!addClip(elem, clipId, false)) return false; } else { QMap properties; QDomNamedNodeMap attributes = elem.attributes(); for (int i = 0; i < attributes.count(); ++i) { QString attrname = attributes.item(i).nodeName(); if (attrname != "resource") properties.insert(attrname, attributes.item(i).nodeValue()); ////qDebug() << attrname << " = " << attributes.item(i).nodeValue(); } clip->setProperties(properties); emit addProjectClip(clip, false); } if (!orig.isNull()) { QMap meta; for (QDomNode m = orig.firstChild(); !m.isNull(); m = m.nextSibling()) { QString name = m.toElement().attribute("name"); if (name.startsWith(QLatin1String("meta.attr"))) { if (name.endsWith(QLatin1String(".markup"))) name = name.section('.', 0, -2); meta.insert(name.section('.', 2, -1), m.firstChild().nodeValue()); } } if (!meta.isEmpty()) { if (clip == NULL) clip = m_clipManager->getClipById(clipId); if (clip) clip->setMetadata(meta); } } return true; }*/ void KdenliveDoc::deleteClip(const QString &clipId) { ClipController *controller = pCore->binController()->getController(clipId); if (!controller) { // Clip doesn't exist, something is wrong qWarning()<<"// Document error deleting clip: "<clipType(); QString url = controller->clipUrl().toLocalFile(); // Delete clip in bin pCore->bin()->deleteClip(clipId); // Delete controller and Mlt::Producer pCore->binController()->removeBinClip(clipId); // Remove from file watch if (type != Color && type != SlideShow && type != QText && !url.isEmpty()) { m_fileWatcher.removeFile(url); } } ProjectClip *KdenliveDoc::getBinClip(const QString &clipId) { return pCore->bin()->getBinClip(clipId); } QStringList KdenliveDoc::getBinFolderClipIds(const QString &folderId) const { return pCore->bin()->getBinFolderClipIds(folderId); } ClipController *KdenliveDoc::getClipController(const QString &clipId) { return pCore->binController()->getController(clipId); } void KdenliveDoc::slotCreateTextTemplateClip(const QString &group, const QString &groupId, QUrl path) { QString titlesFolder = QDir::cleanPath(projectFolder().path() + QDir::separator() + "titles/"); if (path.isEmpty()) { QPointer d = new QFileDialog(QApplication::activeWindow(), i18n("Enter Template Path"), titlesFolder); d->setMimeTypeFilters(QStringList() << QStringLiteral("application/x-kdenlivetitle")); d->setFileMode(QFileDialog::ExistingFile); if (d->exec() == QDialog::Accepted && !d->selectedUrls().isEmpty()) { path = d->selectedUrls().first(); } delete d; } if (path.isEmpty()) return; //TODO: rewrite with new title system (just set resource) m_clipManager->slotAddTextTemplateClip(i18n("Template title clip"), path, group, groupId); emit selectLastAddedClip(QString::number(m_clipManager->lastClipId())); } void KdenliveDoc::cacheImage(const QString &fileId, const QImage &img) const { img.save(QDir::cleanPath(m_projectFolder.path() +QDir::separator() + "thumbs/" + fileId + ".png")); } void KdenliveDoc::setDocumentProperty(const QString &name, const QString &value) { if (value.isEmpty()) { m_documentProperties.remove(name); return; } m_documentProperties[name] = value; } const QString KdenliveDoc::getDocumentProperty(const QString &name, const QString &defaultValue) const { if (m_documentProperties.contains(name)) return m_documentProperties.value(name); return defaultValue; } QMap KdenliveDoc::getRenderProperties() const { QMap renderProperties; QMapIterator i(m_documentProperties); while (i.hasNext()) { i.next(); if (i.key().startsWith(QLatin1String("render"))) renderProperties.insert(i.key(), i.value()); } return renderProperties; } void KdenliveDoc::saveCustomEffects(const QDomNodeList &customeffects) { QDomElement e; QStringList importedEffects; int maxchild = customeffects.count(); for (int i = 0; i < maxchild; ++i) { e = customeffects.at(i).toElement(); const QString id = e.attribute(QStringLiteral("id")); const QString tag = e.attribute(QStringLiteral("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 = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/effects"; 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(); } } } } } if (!importedEffects.isEmpty()) KMessageBox::informationList(QApplication::activeWindow(), i18n("The following effects were imported from the project:"), importedEffects); if (!importedEffects.isEmpty()) { emit reloadEffects(); } } 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 = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/user-places.xbel"; KBookmarkManager *bookmarkManager = KBookmarkManager::managerForExternalFile(file); if (!bookmarkManager) return; KBookmarkGroup root = bookmarkManager->root(); KBookmark bookmark = root.first(); QString kdenliveName = QCoreApplication::applicationName(); QUrl documentLocation = m_projectFolder; bool exists = false; while (!bookmark.isNull()) { // UDI not empty indicates a device QString udi = bookmark.metaDataItem(QStringLiteral("UDI")); QString appName = bookmark.metaDataItem(QStringLiteral("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, QStringLiteral("folder-favorites")); // Make this user selectable ? bookmark.setMetaDataItem(QStringLiteral("OnlyInApp"), kdenliveName); bookmarkManager->emitChanged(root); } } const QSize KdenliveDoc::getRenderSize() const { QSize size; if (m_render) { size.setWidth(m_render->frameRenderWidth()); size.setHeight(m_render->renderHeight()); } return size; } // static double KdenliveDoc::getDisplayRatio(const QString &path) { QFile file(path); QDomDocument doc; if (!file.open(QIODevice::ReadOnly)) { qWarning() << "ERROR, CANNOT READ: " << path; return 0; } if (!doc.setContent(&file)) { qWarning() << "ERROR, CANNOT READ: " << path; file.close(); return 0; } file.close(); QDomNodeList list = doc.elementsByTagName(QStringLiteral("profile")); if (list.isEmpty()) return 0; QDomElement profile = list.at(0).toElement(); double den = profile.attribute(QStringLiteral("display_aspect_den")).toDouble(); if (den > 0) return profile.attribute(QStringLiteral("display_aspect_num")).toDouble() / den; return 0; } void KdenliveDoc::backupLastSavedVersion(const QString &path) { // Ensure backup folder exists if (path.isEmpty()) return; QFile file(path); QDir backupFolder(m_projectFolder.path() + "/.backup"); QString fileName = QUrl::fromLocalFile(path).fileName().section('.', 0, -2); QFileInfo info(file); fileName.append('-' + m_documentProperties.value(QStringLiteral("documentid"))); fileName.append(info.lastModified().toString(QStringLiteral("-yyyy-MM-dd-hh-mm"))); fileName.append(".kdenlive"); QString backupFile = backupFolder.absoluteFilePath(fileName); if (file.exists()) { // delete previous backup if it was done less than 60 seconds ago QFile::remove(backupFile); if (!QFile::copy(path, backupFile)) { KMessageBox::information(QApplication::activeWindow(), i18n("Cannot create backup copy:\n%1", backupFile)); } } } void KdenliveDoc::cleanupBackupFiles() { QDir backupFolder(m_projectFolder.path() + "/.backup"); QString projectFile = url().fileName().section('.', 0, -2); projectFile.append('-' + m_documentProperties.value(QStringLiteral("documentid"))); projectFile.append("-??"); projectFile.append("??"); projectFile.append("-??"); projectFile.append("-??"); projectFile.append("-??"); projectFile.append("-??.kdenlive"); QStringList filter; filter << projectFile; backupFolder.setNameFilters(filter); QFileInfoList resultList = backupFolder.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) { //qDebug()<<"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; } void KdenliveDoc::slotProxyCurrentItem(bool doProxy, QList clipList) { if (clipList.isEmpty()) clipList = pCore->bin()->selectedClips(); QUndoCommand *command = new QUndoCommand(); if (doProxy) command->setText(i18np("Add proxy clip", "Add proxy clips", clipList.count())); else command->setText(i18np("Remove proxy clip", "Remove proxy clips", clipList.count())); // Make sure the proxy folder exists QString proxydir = projectFolder().path() + QDir::separator() + "proxy/"; QDir dir(projectFolder().path()); dir.mkdir(QStringLiteral("proxy")); // Prepare updated properties QMap newProps; QMap oldProps; if (!doProxy) newProps.insert(QStringLiteral("kdenlive:proxy"), QStringLiteral("-")); // Parse clips for (int i = 0; i < clipList.count(); ++i) { ProjectClip *item = clipList.at(i); ClipType t = item->clipType(); // Only allow proxy on some clip types if ((t == Video || t == AV || t == Unknown || t == Image || t == Playlist) && item->isReady()) { if ((doProxy && item->hasProxy()) || (!doProxy && !item->hasProxy() && pCore->binController()->hasClip(item->clipId()))) continue; if (pCore->producerQueue()->isProcessing(item->clipId())) { continue; } if (doProxy) { newProps.clear(); QString path = proxydir + item->hash() + '.' + (t == Image ? QStringLiteral("png") : getDocumentProperty(QStringLiteral("proxyextension"))); // insert required duration for proxy newProps.insert(QStringLiteral("proxy_out"), item->getProducerProperty(QStringLiteral("out"))); newProps.insert(QStringLiteral("kdenlive:proxy"), path); } else if (!pCore->binController()->hasClip(item->clipId())) { // Force clip reload newProps.insert(QStringLiteral("resource"), item->url().toLocalFile()); } // We need to insert empty proxy so that undo will work //TODO: how to handle clip properties //oldProps = clip->currentProperties(newProps); if (doProxy) oldProps.insert(QStringLiteral("kdenlive:proxy"), QStringLiteral("-")); new EditClipCommand(pCore->bin(), item->clipId(), oldProps, newProps, true, command); } } if (command->childCount() > 0) { m_commandStack->push(command); } else delete command; } //TODO put all file watching stuff in own class void KdenliveDoc::watchFile(const QUrl &url) { m_fileWatcher.addFile(url.toLocalFile()); } void KdenliveDoc::slotClipModified(const QString &path) { QStringList ids = pCore->binController()->getBinIdsByResource(QUrl::fromLocalFile(path)); foreach (const QString &id, ids) { if (!m_modifiedClips.contains(id)) { pCore->bin()->setWaitingStatus(id); } m_modifiedClips[id] = QTime::currentTime(); } if (!m_modifiedTimer.isActive()) m_modifiedTimer.start(); } void KdenliveDoc::slotClipMissing(const QString &path) { qDebug() << "// CLIP: " << path << " WAS MISSING"; QStringList ids = pCore->binController()->getBinIdsByResource(QUrl::fromLocalFile(path)); //TODO handle missing clips by replacing producer with an invalid producer /*foreach (const QString &id, ids) { emit missingClip(id); }*/ } void KdenliveDoc::slotProcessModifiedClips() { if (!m_modifiedClips.isEmpty()) { QMapIterator i(m_modifiedClips); while (i.hasNext()) { i.next(); if (QTime::currentTime().msecsTo(i.value()) <= -1500) { pCore->bin()->reloadClip(i.key()); m_modifiedClips.remove(i.key()); break; } } setModified(true); } if (m_modifiedClips.isEmpty()) m_modifiedTimer.stop(); } QMap KdenliveDoc::documentProperties() { m_documentProperties.insert(QStringLiteral("version"), QString::number(DOCUMENTVERSION)); m_documentProperties.insert(QStringLiteral("kdenliveversion"), QStringLiteral(KDENLIVE_VERSION)); m_documentProperties.insert(QStringLiteral("projectfolder"), m_projectFolder.path()); m_documentProperties.insert(QStringLiteral("profile"), profilePath()); m_documentProperties.insert(QStringLiteral("position"), QString::number(m_render->seekPosition().frames(m_render->fps()))); return m_documentProperties; } void KdenliveDoc::loadDocumentProperties() { QDomNodeList list = m_document.elementsByTagName(QStringLiteral("playlist")); if (!list.isEmpty()) { QDomElement pl = list.at(0).toElement(); if (pl.isNull()) return; QDomNodeList props = pl.elementsByTagName(QStringLiteral("property")); QString name; QDomElement e; for (int i = 0; i < props.count(); i++) { e = props.at(i).toElement(); name = e.attribute(QStringLiteral("name")); if (name.startsWith(QLatin1String("kdenlive:docproperties."))) { name = name.section(QStringLiteral("."), 1); m_documentProperties.insert(name, e.firstChild().nodeValue()); } else if (name.startsWith(QLatin1String("kdenlive:docmetadata."))) { name = name.section(QStringLiteral("."), 1); m_documentMetadata.insert(name, e.firstChild().nodeValue()); } } } QString path = m_documentProperties.value(QStringLiteral("projectfolder")); if (!path.startsWith('/')) { QDir dir = QDir::home(); path = dir.absoluteFilePath(path); } m_projectFolder = QUrl::fromLocalFile(path); list = m_document.elementsByTagName(QStringLiteral("profile")); if (!list.isEmpty()) { m_profile = ProfilesDialog::getVideoProfileFromXml(list.at(0).toElement()); } updateProjectProfile(false); } void KdenliveDoc::updateProjectProfile(bool reloadProducers) { KdenliveSettings::setProject_display_ratio((double) m_profile.display_aspect_num / m_profile.display_aspect_den); double fps = (double) m_profile.frame_rate_num / m_profile.frame_rate_den; KdenliveSettings::setProject_fps(fps); m_width = m_profile.width; m_height = m_profile.height; bool fpsChanged = m_timecode.fps() != fps; m_timecode.setFormat(fps); pCore->producerQueue()->abortOperations(); KdenliveSettings::setCurrent_profile(m_profile.path); pCore->monitorManager()->resetProfiles(m_profile, m_timecode); if (!reloadProducers) return; emit updateFps(fpsChanged); if (fpsChanged) { pCore->bin()->reloadAllProducers(); } } void KdenliveDoc::resetProfile() { m_profile = ProfilesDialog::getVideoProfile(KdenliveSettings::current_profile()); updateProjectProfile(true); emit docModified(true); } void KdenliveDoc::slotSwitchProfile() { QAction *action = qobject_cast(sender()); if (!action) return; QVariantList data = action->data().toList(); QString id = data.takeFirst().toString(); if (!data.isEmpty()) { // we want a profile switch m_profile = MltVideoProfile(data); updateProjectProfile(true); emit docModified(true); } } void KdenliveDoc::switchProfile(MltVideoProfile profile, const QString &id, const QDomElement &xml) { // Request profile update QString matchingProfile = ProfilesDialog::existingProfile(profile); if (matchingProfile.isEmpty() && (profile.width % 8 != 0)) { // Make sure profile width is a multiple of 8, required by some parts of mlt profile.adjustWidth(); matchingProfile = ProfilesDialog::existingProfile(profile); } if (!matchingProfile.isEmpty()) { // We found a known matching profile, switch and inform user QMap< QString, QString > profileProperties = ProfilesDialog::getSettingsFromFile(matchingProfile); profile.path = matchingProfile; profile.description = profileProperties.value("description"); // Build actions for the info message (switch / cancel) QList list; QAction *ac = new QAction(KoIconUtils::themedIcon(QStringLiteral("dialog-ok")), i18n("Switch"), this); QVariantList params; connect(ac, SIGNAL(triggered(bool)), this, SLOT(slotSwitchProfile())); params << id << profile.toList(); ac->setData(params); QAction *ac2 = new QAction(KoIconUtils::themedIcon(QStringLiteral("dialog-cancel")), i18n("Cancel"), this); QVariantList params2; params2 << id; ac2->setData(params2); connect(ac2, SIGNAL(triggered(bool)), this, SLOT(slotSwitchProfile())); list << ac << ac2; pCore->bin()->doDisplayMessage(i18n("Switch to clip profile %1?", profile.descriptiveString()), KMessageWidget::Information, list); } else { // No known profile, ask user if he wants to use clip profile anyway if (KMessageBox::warningContinueCancel(QApplication::activeWindow(), i18n("No profile found for your clip.\nCreate and switch to new profile (%1x%2, %3fps)?", profile.width, profile.height, QString::number((double)profile.frame_rate_num / profile.frame_rate_den, 'f', 2))) == KMessageBox::Continue) { m_profile = profile; m_profile.description = QString("%1x%2 %3fps").arg(profile.width).arg(profile.height).arg(QString::number((double)profile.frame_rate_num / profile.frame_rate_den, 'f', 2)); ProfilesDialog::saveProfile(m_profile); updateProjectProfile(true); emit docModified(true); pCore->producerQueue()->getFileProperties(xml, id, 150, true); } } } void KdenliveDoc::forceProcessing(const QString &id) { pCore->producerQueue()->forceProcessing(id); } void KdenliveDoc::getFileProperties(const QDomElement &xml, const QString &clipId, int imageHeight, bool replaceProducer) { pCore->producerQueue()->getFileProperties(xml, clipId, imageHeight, replaceProducer); } void KdenliveDoc::doAddAction(const QString &name, QAction *a) { pCore->window()->actionCollection()->addAction(name, a); } + +void KdenliveDoc::previewRender() +{ + QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); + QString documentId = m_documentProperties.value(QStringLiteral("documentid")); + m_render->previewRendering(zone(), cacheDir, documentId); + emit progressInfo(i18n("Rendering preview"), 0); +} + +void KdenliveDoc::invalidatePreviews(QList chunks) +{ + QDir dir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); + QString documentId = m_documentProperties.value(QStringLiteral("documentid")); + foreach(int i, chunks) { + QFile::remove(dir.absoluteFilePath(documentId + QString("-%1.mp4").arg(i))); + } +} diff --git a/src/doc/kdenlivedoc.h b/src/doc/kdenlivedoc.h index 80c301b8d..2808600a0 100644 --- a/src/doc/kdenlivedoc.h +++ b/src/doc/kdenlivedoc.h @@ -1,248 +1,250 @@ /*************************************************************************** * Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@kdenlive.org) * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * 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 * ***************************************************************************/ /*! \class KdenliveDoc \brief Represents a kdenlive project file Instances of KdeliveDoc classes are created by void MainWindow::newFile(bool showProjectSettings, bool force) */ #ifndef KDENLIVEDOC_H #define KDENLIVEDOC_H #include #include #include #include #include #include #include #include #include #include "gentime.h" #include "timecode.h" #include "definitions.h" #include "timeline/guide.h" #include "mltcontroller/effectscontroller.h" class Render; class ClipManager; class MainWindow; class TrackInfo; class NotesPlugin; class ProjectClip; class ClipController; class QTextEdit; class QUndoGroup; class QTimer; class QUndoStack; namespace Mlt { class Profile; } class KdenliveDoc: public QObject { Q_OBJECT public: KdenliveDoc(const QUrl &url, const QUrl &projectFolder, QUndoGroup *undoGroup, const QString &profileName, const QMap & properties, const QMap & metadata, const QPoint &tracks, Render *render, NotesPlugin *notes, bool *openBackup, MainWindow *parent = 0); ~KdenliveDoc(); QDomNodeList producersList(); double fps() const; int width() const; int height() const; QUrl url() const; KAutoSaveFile *m_autosave; Timecode timecode() const; QDomDocument toXml(); QUndoStack *commandStack(); Render *renderer(); ClipManager *clipManager(); QString groupsXml() const; /** @brief Adds a clip to the project tree. * @return false if the user aborted the operation, true otherwise */ bool addClip(QDomElement elem, const QString &clipId); /** @brief Updates information about a clip. * @param elem the * @param orig the potential * @param clipId the producer id * @return false if the user aborted the operation (in case the clip wasn't * there yet), true otherwise * * If the clip wasn't added before, it tries to add it to the project. */ //bool addClipInfo(QDomElement elem, QDomElement orig, const QString &clipId); void deleteClip(const QString &clipId); int getFramePos(const QString &duration); /** @brief Get a bin's clip from its id. */ ProjectClip *getBinClip(const QString &clipId); /** @brief Get a list of all clip ids that are inside a folder. */ QStringList getBinFolderClipIds(const QString &folderId) const; ClipController *getClipController(const QString &clipId); /** @brief Informs Kdenlive of the audio thumbnails generation progress. */ void setThumbsProgress(const QString &message, int progress); const QString &profilePath() const; /** @brief Returns current project profile. */ MltVideoProfile mltProfile() const; ProfileInfo getProfileInfo() const; const QString description() const; void setUrl(const QUrl &url); /** @brief Defines whether the document needs to be saved. */ bool isModified() const; /** @brief Returns the project folder, used to store project files. */ QUrl projectFolder() const; void setZoom(int horizontal, int vertical); QPoint zoom() const; double dar() const; double projectDuration() const; /** @brief Returns the project file xml. */ QDomDocument xmlSceneList(const QString &scene); /** @brief Saves the project file xml to a file. */ bool saveSceneList(const QString &path, const QString &scene); void cacheImage(const QString &fileId, const QImage &img) const; void setProjectFolder(QUrl url); void setZone(int start, int end); QPoint zone() const; int setSceneList(); void setDocumentProperty(const QString &name, const QString &value); const QString getDocumentProperty(const QString &name, const QString &defaultValue = QString()) const; /** @brief Gets the list of renderer properties saved into the document. */ QMap getRenderProperties() const; /** @brief Read the display ratio from an xml project file. */ static double getDisplayRatio(const QString &path); /** @brief Backup the project file */ void backupLastSavedVersion(const QString &path); /** @brief Returns the document metadata (author, copyright, ...) */ const QMap metadata() const; /** @brief Set the document metadata (author, copyright, ...) */ void setMetadata(const QMap & meta); /** @brief Get frame size of the renderer (profile)*/ const QSize getRenderSize() const; /** @brief Add url to the file watcher so that we monitor changes */ void watchFile(const QUrl &url); /** @brief Get all document properties that need to be saved */ QMap documentProperties(); bool useProxy() const; bool autoGenerateProxy(int width) const; bool autoGenerateImageProxy(int width) const; QString documentNotes() const; /** @brief Saves effects embedded in project file. */ void saveCustomEffects(const QDomNodeList &customeffects); void resetProfile(); /** @brief Force processing of clip id in producer queue. */ void forceProcessing(const QString &id); void getFileProperties(const QDomElement &xml, const QString &clipId, int imageHeight, bool replaceProducer = true); /** @brief Returns true if the profile file has changed. */ bool profileChanged(const QString &profile) const; void doAddAction(const QString &name, QAction *a); + void previewRender(); + void invalidatePreviews(QList chunks); private: QUrl m_url; QDomDocument m_document; KDirWatch m_fileWatcher; /** Timer used to reload clips when they have been externally modified */ QTimer m_modifiedTimer; /** List of the clip IDs that need to be reloaded after being externally modified */ QMap m_modifiedClips; int m_width; int m_height; Timecode m_timecode; Render *m_render; QTextEdit *m_notesWidget; QUndoStack *m_commandStack; ClipManager *m_clipManager; MltVideoProfile m_profile; QString m_searchFolder; /** @brief Tells whether the current document has been changed after being saved. */ bool m_modified; /** @brief The project folder, used to store project files (titles, effects...). */ QUrl m_projectFolder; QMap m_documentProperties; QMap m_documentMetadata; QString searchFileRecursively(const QDir &dir, const QString &matchSize, const QString &matchHash) const; void moveProjectData(const QUrl &url); /** @brief Creates a new project. */ QDomDocument createEmptyDocument(int videotracks, int audiotracks); QDomDocument createEmptyDocument(const QList &tracks); /** @brief Updates the project folder location entry in the kdenlive file dialogs to point to the current project folder. */ void updateProjectFolderPlacesEntry(); /** @brief Only keep some backup files, delete some */ void cleanupBackupFiles(); /** @brief Load document properties from the xml file */ void loadDocumentProperties(); /** @brief update document properties to reflect a change in the current profile */ void updateProjectProfile(bool reloadProducers = false); public slots: void slotCreateTextTemplateClip(const QString &group, const QString &groupId, QUrl path); /** @brief Sets the document as modified or up to date. * @description If crash recovery is turned on, a timer calls KdenliveDoc::slotAutoSave() \n * Emits docModified conected to MainWindow::slotUpdateDocumentState \n * @param mod (optional) true if the document has to be saved */ void setModified(bool mod = true); void slotProxyCurrentItem(bool doProxy, QList clipList = QList()); /** @brief Saves the current project at the autosave location. * @description The autosave files are in ~/.kde/data/stalefiles/kdenlive/ */ void slotAutoSave(); private slots: void slotClipModified(const QString &path); void slotClipMissing(const QString &path); void slotProcessModifiedClips(); void slotModified(); void slotSetDocumentNotes(const QString ¬es); void switchProfile(MltVideoProfile profile, const QString &id, const QDomElement &xml); void slotSwitchProfile(); signals: void resetProjectList(); void progressInfo(const QString &, int); /** @brief Informs that the document status has been changed. * * If the document has been modified, it's called with true as an argument. */ void docModified(bool); void selectLastAddedClip(const QString &); void guidesUpdated(); /** @brief When creating a backup file, also save a thumbnail of current timeline */ void saveTimelinePreview(const QString &path); /** @brief Trigger the autosave timer start */ void startAutoSave(); /** @brief Current doc created effects, reload list */ void reloadEffects(); /** @brief Fps was changed, update timeline */ void updateFps(bool changed); }; #endif diff --git a/src/kdenliveui.rc b/src/kdenliveui.rc index e4c725bc0..8b00cc91a 100644 --- a/src/kdenliveui.rc +++ b/src/kdenliveui.rc @@ -1,225 +1,226 @@ - + Extra Toolbar Project Extract Audio Clip Jobs Transcode Generators Tracks Clip in Timeline Tool - + Clip Markers Timeline Selection Insertion Removal + Current clip Guides Space Tracks Add Effect Monitor Go To Current Monitor Overlay Monitor config View Save Layout As diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 2596ccaa4..2d7af27d8 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,3418 +1,3430 @@ /*************************************************************************** * Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@kdenlive.org) * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * 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 * ***************************************************************************/ #include "mainwindow.h" #include "mainwindowadaptor.h" #include "core.h" #include "bin/projectclip.h" #include "bin/generators/generators.h" #include "library/librarywidget.h" #include "monitor/scopes/audiographspectrum.h" #include "mltcontroller/clipcontroller.h" #include "kdenlivesettings.h" #include "dialogs/kdenlivesettingsdialog.h" #include "dialogs/clipcreationdialog.h" #include "effectslist/initeffects.h" #include "project/dialogs/projectsettings.h" #include "project/clipmanager.h" #include "monitor/monitor.h" #include "monitor/recmonitor.h" #include "monitor/monitormanager.h" #include "doc/kdenlivedoc.h" #include "timeline/timeline.h" #include "timeline/track.h" #include "timeline/customtrackview.h" #include "effectslist/effectslistview.h" #include "effectslist/effectbasket.h" #include "effectstack/effectstackview2.h" #include "project/transitionsettings.h" #include "mltcontroller/bincontroller.h" #include "mltcontroller/producerqueue.h" #include "dialogs/renderwidget.h" #include "renderer.h" #include "dialogs/wizard.h" #include "project/projectcommands.h" #include "titler/titlewidget.h" #include "timeline/markerdialog.h" #include "timeline/clipitem.h" #include "interfaces.h" #include "project/cliptranscode.h" #include "scopes/scopemanager.h" #include "project/dialogs/archivewidget.h" #include "utils/resourcewidget.h" #include "layoutmanagement.h" #include "hidetitlebars.h" #include "mltconnection.h" #include "project/projectmanager.h" #include "timeline/timelinesearch.h" #include #include "utils/thememanager.h" #include "utils/KoIconUtils.h" #ifdef USE_JOGSHUTTLE #include "jogshuttle/jogmanager.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const char version[] = KDENLIVE_VERSION; namespace Mlt { class Producer; }; EffectsList MainWindow::videoEffects; EffectsList MainWindow::audioEffects; EffectsList MainWindow::customEffects; EffectsList MainWindow::transitions; QMap MainWindow::m_lumacache; QMap MainWindow::m_lumaFiles; /*static bool sortByNames(const QPair &a, const QPair &b) { return a.first < b.first; }*/ // determine the the default KDE style as defined BY THE USER // (as opposed to whatever style KDE considers default) static QString defaultStyle(const char *fallback=Q_NULLPTR) { KSharedConfigPtr kdeGlobals = KSharedConfig::openConfig(QStringLiteral("kdeglobals"), KConfig::NoGlobals); KConfigGroup cg(kdeGlobals, "KDE"); return cg.readEntry("widgetStyle", fallback); } MainWindow::MainWindow(const QString &MltPath, const QUrl &Url, const QString & clipsToLoad, QWidget *parent) : KXmlGuiWindow(parent), m_timelineArea(NULL), m_stopmotion(NULL), m_effectStack(NULL), m_exitCode(EXIT_SUCCESS), m_effectList(NULL), m_transitionList(NULL), m_clipMonitor(NULL), m_projectMonitor(NULL), m_recMonitor(NULL), m_renderWidget(NULL), m_themeInitialized(false), m_isDarkTheme(false) { qRegisterMetaType ("audioShortVector"); qRegisterMetaType< QVector > ("QVector"); qRegisterMetaType ("MessageType"); qRegisterMetaType ("stringMap"); qRegisterMetaType ("audioByteArray"); qRegisterMetaType< QVector > (); qRegisterMetaType ("QDomElement"); qRegisterMetaType ("requestClipInfo"); qRegisterMetaType ("MltVideoProfile"); Core::build(this); // Widget themes for non KDE users KActionMenu *stylesAction= new KActionMenu(i18n("Style"), this); QActionGroup *stylesGroup = new QActionGroup(stylesAction); // GTK theme does not work well with Kdenlive, and does not support color theming, so avoid it QStringList availableStyles = QStyleFactory::keys(); QString desktopStyle = QApplication::style()->objectName(); if (QString::compare(desktopStyle, QLatin1String("GTK+"), Qt::CaseInsensitive) == 0 && KdenliveSettings::widgetstyle().isEmpty()) { if (availableStyles.contains(QLatin1String("breeze"), Qt::CaseInsensitive)) { // Auto switch to Breeze theme KdenliveSettings::setWidgetstyle(QStringLiteral("Breeze")); } else if (availableStyles.contains(QLatin1String("fusion"), Qt::CaseInsensitive)) { KdenliveSettings::setWidgetstyle(QStringLiteral("Fusion")); } } // Add default style action QAction *defaultStyle = new QAction(i18n("Default"), stylesGroup); defaultStyle->setCheckable(true); stylesAction->addAction(defaultStyle); if (KdenliveSettings::widgetstyle().isEmpty()) { defaultStyle->setChecked(true); } foreach(const QString &style, availableStyles) { QAction *a = new QAction(style, stylesGroup); a->setCheckable(true); a->setData(style); if (KdenliveSettings::widgetstyle() == style) { a->setChecked(true); } stylesAction->addAction(a); } connect(stylesGroup, &QActionGroup::triggered, this, &MainWindow::slotChangeStyle); // Color schemes KActionMenu *themeAction= new KActionMenu(i18n("Theme"), this); ThemeManager::instance()->setThemeMenuAction(themeAction); ThemeManager::instance()->setCurrentTheme(KdenliveSettings::colortheme()); connect(ThemeManager::instance(), SIGNAL(signalThemeChanged(const QString &)), this, SLOT(slotThemeChanged(const QString &)), Qt::DirectConnection); if (!KdenliveSettings::widgetstyle().isEmpty() && QString::compare(desktopStyle, KdenliveSettings::widgetstyle(), Qt::CaseInsensitive) != 0) { // User wants a custom widget style, init doChangeStyle(); } else ThemeManager::instance()->slotChangePalette(); new RenderingAdaptor(this); pCore->initialize(); MltConnection::locateMeltAndProfilesPath(MltPath); KdenliveSettings::setCurrent_profile(KdenliveSettings::default_profile()); // If using a custom profile, make sure the file exists or fallback to default if (KdenliveSettings::current_profile().startsWith(QStringLiteral("/")) && !QFile::exists(KdenliveSettings::current_profile())) { KMessageBox::sorry(this, i18n("Cannot find your default profile, switching to ATSC 1080p 25")); KdenliveSettings::setCurrent_profile("atsc_1080p_25"); KdenliveSettings::setDefault_profile("atsc_1080p_25"); } m_commandStack = new QUndoGroup; m_timelineArea = new QTabWidget(this); //m_timelineArea->setTabReorderingEnabled(true); m_timelineArea->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); m_timelineArea->setMinimumHeight(200); // Hide tabbar QTabBar *bar = m_timelineArea->findChild(); bar->setHidden(true); m_gpuAllowed = initEffects::parseEffectFiles(pCore->binController()->mltRepository()); //initEffects::parseCustomEffectsFile(); m_shortcutRemoveFocus = new QShortcut(QKeySequence(QStringLiteral("Esc")), this); connect(m_shortcutRemoveFocus, SIGNAL(activated()), this, SLOT(slotRemoveFocus())); /// Add Widgets setDockNestingEnabled(true); setCentralWidget(m_timelineArea); m_projectBinDock = addDock(i18n("Project Bin"), QStringLiteral("project_bin"), pCore->bin()); QDockWidget * libraryDock = addDock(i18n("Library"), QStringLiteral("library"), pCore->library()); m_clipMonitor = new Monitor(Kdenlive::ClipMonitor, pCore->monitorManager(), this); pCore->bin()->setMonitor(m_clipMonitor); connect(m_clipMonitor, &Monitor::showConfigDialog, this, &MainWindow::slotPreferences); connect(m_clipMonitor, &Monitor::addMarker, this, &MainWindow::slotAddMarkerGuideQuickly); connect(m_clipMonitor, &Monitor::deleteMarker, this, &MainWindow::slotDeleteClipMarker); connect(m_clipMonitor, &Monitor::seekToPreviousSnap, this, &MainWindow::slotSnapRewind); connect(m_clipMonitor, &Monitor::seekToNextSnap, this, &MainWindow::slotSnapForward); connect(pCore->bin(), SIGNAL(clipNeedsReload(QString,bool)),this, SLOT(slotUpdateClip(QString,bool))); connect(pCore->bin(), SIGNAL(findInTimeline(QString)), this, SLOT(slotClipInTimeline(QString))); //TODO deprecated, replace with Bin methods if necessary /*connect(m_projectList, SIGNAL(loadingIsOver()), this, SLOT(slotElapsedTime())); connect(m_projectList, SIGNAL(displayMessage(QString,int,MessageType)), this, SLOT(slotGotProgressInfo(QString,int,MessageType))); connect(m_projectList, SIGNAL(updateRenderStatus()), this, SLOT(slotCheckRenderStatus())); connect(m_projectList, SIGNAL(updateProfile(QString)), this, SLOT(slotUpdateProjectProfile(QString))); connect(m_projectList, SIGNAL(refreshClip(QString,bool)), pCore->monitorManager(), SLOT(slotRefreshCurrentMonitor(QString))); connect(m_clipMonitor, SIGNAL(zoneUpdated(QPoint)), m_projectList, SLOT(slotUpdateClipCut(QPoint)));*/ connect(m_clipMonitor, SIGNAL(extractZone(QString)), pCore->bin(), SLOT(slotStartCutJob(QString))); connect(m_clipMonitor, SIGNAL(passKeyPress(QKeyEvent*)), this, SLOT(triggerKey(QKeyEvent*))); m_projectMonitor = new Monitor(Kdenlive::ProjectMonitor, pCore->monitorManager(), this); connect(m_projectMonitor, SIGNAL(passKeyPress(QKeyEvent*)), this, SLOT(triggerKey(QKeyEvent*))); connect(m_projectMonitor, &Monitor::addMarker, this, &MainWindow::slotAddMarkerGuideQuickly); connect(m_projectMonitor, &Monitor::deleteMarker, this, &MainWindow::slotDeleteClipMarker); connect(m_projectMonitor, &Monitor::seekToPreviousSnap, this, &MainWindow::slotSnapRewind); connect(m_projectMonitor, &Monitor::seekToNextSnap, this, &MainWindow::slotSnapForward); connect(m_projectMonitor, SIGNAL(updateGuide(int, QString)), this, SLOT(slotEditGuide(int, QString))); /* //TODO disabled until ported to qml m_recMonitor = new RecMonitor(Kdenlive::RecordMonitor, pCore->monitorManager(), this); connect(m_recMonitor, SIGNAL(addProjectClip(QUrl)), this, SLOT(slotAddProjectClip(QUrl))); connect(m_recMonitor, SIGNAL(addProjectClipList(QList)), this, SLOT(slotAddProjectClipList(QList))); */ pCore->monitorManager()->initMonitors(m_clipMonitor, m_projectMonitor, m_recMonitor); connect(m_clipMonitor, SIGNAL(addMasterEffect(QString,QDomElement)), pCore->bin(), SLOT(slotEffectDropped(QString,QDomElement))); // Audio spectrum scope m_audioSpectrum = new AudioGraphSpectrum(pCore->monitorManager()); QDockWidget * spectrumDock = addDock(i18n("Audio Spectrum"), QStringLiteral("audiospectrum"), m_audioSpectrum); connect(this, &MainWindow::reloadTheme, m_audioSpectrum, &AudioGraphSpectrum::refreshPixmap); // Close library and audiospectrum on first run libraryDock->close(); spectrumDock->close(); m_effectStack = new EffectStackView2(m_projectMonitor, this); connect(m_effectStack, SIGNAL(startFilterJob(const ItemInfo&,const QString&,QMap&,QMap&,QMap&)), pCore->bin(), SLOT(slotStartFilterJob(const ItemInfo &,const QString&,QMap&,QMap&,QMap&))); connect(pCore->bin(), SIGNAL(masterClipSelected(ClipController *, Monitor *)), m_effectStack, SLOT(slotMasterClipItemSelected(ClipController *, Monitor *))); connect(pCore->bin(), SIGNAL(masterClipUpdated(ClipController *, Monitor *)), m_effectStack, SLOT(slotRefreshMasterClipEffects(ClipController *, Monitor *))); connect(m_effectStack, SIGNAL(addMasterEffect(QString,QDomElement)), pCore->bin(), SLOT(slotEffectDropped(QString,QDomElement))); connect(m_effectStack, SIGNAL(removeMasterEffect(QString,QDomElement)), pCore->bin(), SLOT(slotDeleteEffect(QString,QDomElement))); connect(m_effectStack, SIGNAL(reloadEffects()), this, SLOT(slotReloadEffects())); connect(m_effectStack, SIGNAL(displayMessage(QString,int)), this, SLOT(slotGotProgressInfo(QString,int))); m_effectStackDock = addDock(i18n("Properties"), QStringLiteral("effect_stack"), m_effectStack); m_effectList = new EffectsListView(); m_effectListDock = addDock(i18n("Effects"), QStringLiteral("effect_list"), m_effectList); m_transitionList = new EffectsListView(EffectsListView::TransitionMode); m_transitionListDock = addDock(i18n("Transitions"), QStringLiteral("transition_list"), m_transitionList); setupActions(); // Add monitors here to keep them at the right of the window m_clipMonitorDock = addDock(i18n("Clip Monitor"), QStringLiteral("clip_monitor"), m_clipMonitor); m_projectMonitorDock = addDock(i18n("Project Monitor"), QStringLiteral("project_monitor"), m_projectMonitor); if (m_recMonitor) { m_recMonitorDock = addDock(i18n("Record Monitor"), QStringLiteral("record_monitor"), m_recMonitor); } m_undoView = new QUndoView(); m_undoView->setCleanIcon(KoIconUtils::themedIcon(QStringLiteral("edit-clear"))); m_undoView->setEmptyLabel(i18n("Clean")); m_undoView->setGroup(m_commandStack); m_undoViewDock = addDock(i18n("Undo History"), QStringLiteral("undo_history"), m_undoView); // Color and icon theme stuff addAction(QStringLiteral("themes_menu"), themeAction); connect(m_commandStack, SIGNAL(cleanChanged(bool)), m_saveAction, SLOT(setDisabled(bool))); addAction(QStringLiteral("styles_menu"), stylesAction); // Close non-general docks for the initial layout // only show important ones m_undoViewDock->close(); /// Tabify Widgets /// tabifyDockWidget(m_transitionListDock, m_effectListDock); tabifyDockWidget(pCore->bin()->clipPropertiesDock(), m_effectStackDock); //tabifyDockWidget(m_effectListDock, m_effectStackDock); tabifyDockWidget(m_clipMonitorDock, m_projectMonitorDock); if (m_recMonitor) { tabifyDockWidget(m_clipMonitorDock, m_recMonitorDock); } readOptions(); QAction *action; // Stop motion actions. Beware of the order, we MUST use the same order in stopmotion/stopmotion.cpp m_stopmotion_actions = new KActionCategory(i18n("Stop Motion"), actionCollection()); action = new QAction(KoIconUtils::themedIcon(QStringLiteral("media-record")), i18n("Capture frame"), this); //action->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_stopmotion_actions->addAction(QStringLiteral("stopmotion_capture"), action); action = new QAction(i18n("Switch live / captured frame"), this); //action->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_stopmotion_actions->addAction(QStringLiteral("stopmotion_switch"), action); action = new QAction(KoIconUtils::themedIcon(QStringLiteral("edit-paste")), i18n("Show last frame over video"), this); action->setCheckable(true); action->setChecked(false); m_stopmotion_actions->addAction(QStringLiteral("stopmotion_overlay"), action); // Monitor Record action addAction(QStringLiteral("switch_monitor_rec"), m_clipMonitor->recAction()); // Build effects menu m_effectsMenu = new QMenu(i18n("Add Effect"), this); m_effectActions = new KActionCategory(i18n("Effects"), actionCollection()); m_effectList->reloadEffectList(m_effectsMenu, m_effectActions); m_transitionsMenu = new QMenu(i18n("Add Transition"), this); m_transitionActions = new KActionCategory(i18n("Transitions"), actionCollection()); m_transitionList->reloadEffectList(m_transitionsMenu, m_transitionActions); ScopeManager *scmanager = new ScopeManager(this); new LayoutManagement(this); new HideTitleBars(this); new TimelineSearch(this); m_extraFactory = new KXMLGUIClient(this); buildDynamicActions(); // Add shortcut to action tooltips QList< KActionCollection * > collections = KActionCollection::allCollections(); for (int i = 0; i < collections.count(); ++i) { KActionCollection *coll = collections.at(i); foreach( QAction* tempAction, coll->actions()) { // find the shortcut pattern and delete (note the preceding space in the RegEx) QString strippedTooltip = tempAction->toolTip().remove(QRegExp("\\s\\(.*\\)")); // append shortcut if it exists for action if (tempAction->shortcut() == QKeySequence(0)) tempAction->setToolTip( strippedTooltip); else tempAction->setToolTip( strippedTooltip + " (" + tempAction->shortcut().toString() + ")"); } } // Create Effect Basket (dropdown list of favorites) m_effectBasket = new EffectBasket(m_effectList); connect(m_effectBasket, SIGNAL(addEffect(QDomElement)), this, SLOT(slotAddEffect(QDomElement))); QWidgetAction *widgetlist = new QWidgetAction(this); widgetlist->setDefaultWidget(m_effectBasket); widgetlist->setText(i18n("Favorite Effects")); widgetlist->setToolTip(i18n("Favorite Effects")); widgetlist->setIcon(KoIconUtils::themedIcon("favorite")); QMenu *menu = new QMenu(this); menu->addAction(widgetlist); QToolButton *basketButton = new QToolButton(this); basketButton->setMenu(menu); basketButton->setToolButtonStyle(toolBar()->toolButtonStyle()); basketButton->setDefaultAction(widgetlist); basketButton->setPopupMode(QToolButton::InstantPopup); basketButton->setText(i18n("Favorite Effects")); basketButton->setToolTip(i18n("Favorite Effects")); basketButton->setIcon(KoIconUtils::themedIcon(QStringLiteral("favorite"))); QWidgetAction* toolButtonAction = new QWidgetAction(this); toolButtonAction->setText(i18n("Favorite Effects")); toolButtonAction->setIcon(KoIconUtils::themedIcon("favorite")); toolButtonAction->setDefaultWidget(basketButton); addAction(QStringLiteral("favorite_effects"), toolButtonAction); connect(toolButtonAction, SIGNAL(triggered(bool)), basketButton, SLOT(showMenu())); setupGUI(); /*ScriptingPart* sp = new ScriptingPart(this, QStringList()); guiFactory()->addClient(sp);*/ loadGenerators(); loadDockActions(); loadClipActions(); // Connect monitor overlay info menu. QMenu *monitorOverlay = static_cast(factory()->container(QStringLiteral("monitor_config_overlay"), this)); connect(monitorOverlay, SIGNAL(triggered(QAction*)), this, SLOT(slotSwitchMonitorOverlay(QAction*))); m_projectMonitor->setupMenu(static_cast(factory()->container(QStringLiteral("monitor_go"), this)), monitorOverlay, m_playZone, m_loopZone, NULL, m_loopClip); m_clipMonitor->setupMenu(static_cast(factory()->container(QStringLiteral("monitor_go"), this)), monitorOverlay, m_playZone, m_loopZone, static_cast(factory()->container(QStringLiteral("marker_menu"), this))); QMenu *clipInTimeline = static_cast(factory()->container(QStringLiteral("clip_in_timeline"), this)); clipInTimeline->setIcon(KoIconUtils::themedIcon(QStringLiteral("go-jump"))); pCore->bin()->setupGeneratorMenu(); connect(pCore->monitorManager(), SIGNAL(updateOverlayInfos(int,int)), this, SLOT(slotUpdateMonitorOverlays(int,int))); // Setup and fill effects and transitions menus. QMenu *m = static_cast(factory()->container(QStringLiteral("video_effects_menu"), this)); connect(m, SIGNAL(triggered(QAction*)), this, SLOT(slotAddVideoEffect(QAction*))); connect(m_effectsMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotAddVideoEffect(QAction*))); connect(m_transitionsMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotAddTransition(QAction*))); m_timelineContextMenu = new QMenu(this); m_timelineContextClipMenu = new QMenu(this); m_timelineContextTransitionMenu = new QMenu(this); m_timelineContextMenu->addAction(actionCollection()->action(QStringLiteral("insert_space"))); m_timelineContextMenu->addAction(actionCollection()->action(QStringLiteral("delete_space"))); m_timelineContextMenu->addAction(actionCollection()->action(KStandardAction::name(KStandardAction::Paste))); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("clip_in_project_tree"))); //m_timelineContextClipMenu->addAction(actionCollection()->action("clip_to_project_tree")); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("delete_timeline_clip"))); m_timelineContextClipMenu->addSeparator(); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("group_clip"))); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("ungroup_clip"))); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("split_audio"))); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("set_audio_align_ref"))); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("align_audio"))); m_timelineContextClipMenu->addSeparator(); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("cut_timeline_clip"))); m_timelineContextClipMenu->addAction(actionCollection()->action(KStandardAction::name(KStandardAction::Copy))); m_timelineContextClipMenu->addAction(actionCollection()->action(QStringLiteral("paste_effects"))); m_timelineContextClipMenu->addSeparator(); QMenu *markersMenu = static_cast(factory()->container(QStringLiteral("marker_menu"), this)); m_timelineContextClipMenu->addMenu(markersMenu); m_timelineContextClipMenu->addSeparator(); m_timelineContextClipMenu->addMenu(m_transitionsMenu); m_timelineContextClipMenu->addMenu(m_effectsMenu); m_timelineContextTransitionMenu->addAction(actionCollection()->action(QStringLiteral("delete_timeline_clip"))); m_timelineContextTransitionMenu->addAction(actionCollection()->action(KStandardAction::name(KStandardAction::Copy))); m_timelineContextTransitionMenu->addAction(actionCollection()->action(QStringLiteral("auto_transition"))); connect(m_effectList, SIGNAL(addEffect(QDomElement)), this, SLOT(slotAddEffect(QDomElement))); connect(m_effectList, SIGNAL(reloadEffects()), this, SLOT(slotReloadEffects())); slotConnectMonitors(); // Populate encoding profiles KConfig conf(QStringLiteral("encodingprofiles.rc"), KConfig::CascadeConfig, QStandardPaths::DataLocation); if (KdenliveSettings::proxyparams().isEmpty() || KdenliveSettings::proxyextension().isEmpty()) { KConfigGroup group(&conf, "proxy"); QMap< QString, QString > values = group.entryMap(); QMapIterator i(values); if (i.hasNext()) { i.next(); QString data = i.value(); KdenliveSettings::setProxyparams(data.section(';', 0, 0)); KdenliveSettings::setProxyextension(data.section(';', 1, 1)); } } if (KdenliveSettings::v4l_parameters().isEmpty() || KdenliveSettings::v4l_extension().isEmpty()) { KConfigGroup group(&conf, "video4linux"); QMap< QString, QString > values = group.entryMap(); QMapIterator i(values); if (i.hasNext()) { i.next(); QString data = i.value(); KdenliveSettings::setV4l_parameters(data.section(';', 0, 0)); KdenliveSettings::setV4l_extension(data.section(';', 1, 1)); } } if (KdenliveSettings::grab_parameters().isEmpty() || KdenliveSettings::grab_extension().isEmpty()) { KConfigGroup group(&conf, "screengrab"); QMap< QString, QString > values = group.entryMap(); QMapIterator i(values); if (i.hasNext()) { i.next(); QString data = i.value(); KdenliveSettings::setGrab_parameters(data.section(';', 0, 0)); KdenliveSettings::setGrab_extension(data.section(';', 1, 1)); } } if (KdenliveSettings::decklink_parameters().isEmpty() || KdenliveSettings::decklink_extension().isEmpty()) { KConfigGroup group(&conf, "decklink"); QMap< QString, QString > values = group.entryMap(); QMapIterator i(values); if (i.hasNext()) { i.next(); QString data = i.value(); KdenliveSettings::setDecklink_parameters(data.section(';', 0, 0)); KdenliveSettings::setDecklink_extension(data.section(';', 1, 1)); } } emit GUISetupDone(); pCore->projectManager()->init(Url, clipsToLoad); QTimer::singleShot(0, pCore->projectManager(), SLOT(slotLoadOnOpen())); connect(this, SIGNAL(reloadTheme()), this, SLOT(slotReloadTheme()), Qt::UniqueConnection); #ifdef USE_JOGSHUTTLE new JogManager(this); #endif scmanager->slotCheckActiveScopes(); //KMessageBox::information(this, "Warning, development version for testing only. we are currently working on core functionnalities,\ndo not save any project or your project files might be corrupted."); } void MainWindow::slotThemeChanged(const QString &theme) { disconnect(this, SIGNAL(reloadTheme()), this, SLOT(slotReloadTheme())); KSharedConfigPtr config = KSharedConfig::openConfig(theme); setPalette(KColorScheme::createApplicationPalette(config)); qApp->setPalette(palette()); QPalette plt = palette(); KdenliveSettings::setColortheme(theme); if (m_effectStack) { m_effectStack->updatePalette(); m_effectStack->transitionConfig()->updatePalette(); } if (m_effectList) m_effectList->updatePalette(); if (m_transitionList) m_transitionList->updatePalette(); if (m_clipMonitor) m_clipMonitor->setPalette(plt); if (m_projectMonitor) m_projectMonitor->setPalette(plt); setStatusBarStyleSheet(plt); if (pCore->projectManager() && pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->updatePalette(); } if (m_timelineArea) { m_timelineArea->setPalette(plt); } if (statusBar()) { const QObjectList children = statusBar()->children(); foreach(QObject * child, children) { if (child->isWidgetType()) ((QWidget*)child)->setPalette(plt); const QObjectList subchildren = child->children(); foreach(QObject * subchild, subchildren) { if (subchild->isWidgetType()) ((QWidget*)subchild)->setPalette(plt); } } } #if KXMLGUI_VERSION_MINOR < 23 && KXMLGUI_VERSION_MAJOR == 5 // Not required anymore with auto colored icons since KF5 5.23 QColor background = plt.window().color(); bool useDarkIcons = background.value() < 100; if (m_themeInitialized && useDarkIcons != m_isDarkTheme) { if (pCore->bin()) { pCore->bin()->refreshIcons(); } if (m_clipMonitor) m_clipMonitor->refreshIcons(); if (m_projectMonitor) m_projectMonitor->refreshIcons(); if (pCore->monitorManager()) pCore->monitorManager()->refreshIcons(); if (m_effectStack && m_effectStack->transitionConfig()) m_effectStack->transitionConfig()->refreshIcons(); if (m_effectList) m_effectList->refreshIcons(); if (m_effectStack) m_effectStack->refreshIcons(); if (pCore->projectManager() && pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->refreshIcons(); } foreach(QAction* action, actionCollection()->actions()) { QIcon icon = action->icon(); if (icon.isNull()) continue; QString iconName = icon.name(); QIcon newIcon = KoIconUtils::themedIcon(iconName); if (newIcon.isNull()) continue; action->setIcon(newIcon); } } m_themeInitialized = true; m_isDarkTheme = useDarkIcons; #endif connect(this, SIGNAL(reloadTheme()), this, SLOT(slotReloadTheme()), Qt::UniqueConnection); } bool MainWindow::event(QEvent *e) { switch (e->type()) { case QEvent::ApplicationPaletteChange: emit reloadTheme(); e->accept(); break; default: break; } return KXmlGuiWindow::event(e); } void MainWindow::slotReloadTheme() { ThemeManager::instance()->slotSettingsChanged(); } MainWindow::~MainWindow() { delete m_stopmotion; delete m_audioSpectrum; m_effectStack->slotClipItemSelected(NULL, m_projectMonitor); m_effectStack->slotTransitionItemSelected(NULL, 0, QPoint(), false); if (m_projectMonitor) m_projectMonitor->stop(); if (m_clipMonitor) m_clipMonitor->stop(); delete pCore; delete m_effectStack; delete m_projectMonitor; delete m_clipMonitor; delete m_shortcutRemoveFocus; qDeleteAll(m_transitions); Mlt::Factory::close(); } //virtual bool MainWindow::queryClose() { if (m_renderWidget) { int waitingJobs = m_renderWidget->waitingJobsCount(); if (waitingJobs > 0) { switch (KMessageBox::warningYesNoCancel(this, i18np("You have 1 rendering job waiting in the queue.\nWhat do you want to do with this job?", "You have %1 rendering jobs waiting in the queue.\nWhat do you want to do with these jobs?", waitingJobs), QString(), KGuiItem(i18n("Start them now")), KGuiItem(i18n("Delete them")))) { case KMessageBox::Yes : // create script with waiting jobs and start it if (m_renderWidget->startWaitingRenderJobs() == false) return false; break; case KMessageBox::No : // Don't do anything, jobs will be deleted break; default: return false; } } } saveOptions(); // WARNING: According to KMainWindow::queryClose documentation we are not supposed to close the document here? return pCore->projectManager()->closeCurrentDocument(true, true); } void MainWindow::loadGenerators() { QMenu *addMenu = static_cast(factory()->container(QStringLiteral("generators"), this)); Generators::getGenerators(KdenliveSettings::producerslist(), addMenu); connect(addMenu, SIGNAL(triggered(QAction *)), this, SLOT(buildGenerator(QAction *))); } void MainWindow::buildGenerator(QAction *action) { Generators gen(m_clipMonitor, action->data().toString(), this); if (gen.exec() == QDialog::Accepted) { pCore->bin()->slotAddClipToProject(gen.getSavedClip()); } } void MainWindow::saveProperties(KConfigGroup &config) { // save properties here KXmlGuiWindow::saveProperties(config); //TODO: fix session management if (qApp->isSavingSession()) { if (pCore->projectManager()->current() && !pCore->projectManager()->current()->url().isEmpty()) { config.writeEntry("kdenlive_lastUrl", pCore->projectManager()->current()->url().path()); } } } void MainWindow::readProperties(const KConfigGroup &config) { // read properties here KXmlGuiWindow::readProperties(config); //TODO: fix session management /*if (qApp->isSessionRestored()) { pCore->projectManager()->openFile(QUrl::fromLocalFile(config.readEntry("kdenlive_lastUrl", QString()))); }*/ } void MainWindow::saveNewToolbarConfig() { KXmlGuiWindow::saveNewToolbarConfig(); //TODO for some reason all dynamically inserted actions are removed by the save toolbar // So we currently re-add them manually.... loadDockActions(); loadClipActions(); pCore->bin()->rebuildMenu(); } void MainWindow::slotReloadEffects() { initEffects::parseCustomEffectsFile(); m_effectList->reloadEffectList(m_effectsMenu, m_effectActions); } void MainWindow::configureNotifications() { KNotifyConfigWidget::configure(this); } void MainWindow::slotFullScreen() { KToggleFullScreenAction::setFullScreen(this, actionCollection()->action(QStringLiteral("fullscreen"))->isChecked()); } void MainWindow::slotAddEffect(const QDomElement &effect) { if (effect.isNull()) { //qDebug() << "--- ERROR, TRYING TO APPEND NULL EFFECT"; return; } QDomElement effectToAdd = effect.cloneNode().toElement(); EFFECTMODE status = m_effectStack->effectStatus(); if (status == TIMELINE_TRACK) { pCore->projectManager()->currentTimeline()->projectView()->slotAddTrackEffect(effectToAdd, pCore->projectManager()->currentTimeline()->tracksCount() - m_effectStack->trackIndex()); } else if (status == TIMELINE_CLIP) { pCore->projectManager()->currentTimeline()->projectView()->slotAddEffectToCurrentItem(effectToAdd); } else if (status == MASTER_CLIP) { pCore->bin()->slotEffectDropped(QString(), effectToAdd); } } void MainWindow::slotUpdateClip(const QString &id, bool reload) { ProjectClip *clip = pCore->bin()->getBinClip(id); if (!clip) { return; } pCore->projectManager()->currentTimeline()->projectView()->slotUpdateClip(id, reload); } void MainWindow::slotConnectMonitors() { //connect(m_projectList, SIGNAL(deleteProjectClips(QStringList,QMap)), this, SLOT(slotDeleteProjectClips(QStringList,QMap))); connect(m_projectMonitor->render, SIGNAL(gotFileProperties(requestClipInfo,ClipController *)), pCore->bin(), SLOT(slotProducerReady(requestClipInfo,ClipController *)), Qt::DirectConnection); connect(m_clipMonitor, SIGNAL(refreshClipThumbnail(QString)), pCore->bin(), SLOT(slotRefreshClipThumbnail(QString))); connect(m_projectMonitor, SIGNAL(requestFrameForAnalysis(bool)), this, SLOT(slotMonitorRequestRenderFrame(bool))); connect(m_projectMonitor, &Monitor::createSplitOverlay, this, &MainWindow::createSplitOverlay); connect(m_projectMonitor, &Monitor::removeSplitOverlay, this, &MainWindow::removeSplitOverlay); } void MainWindow::createSplitOverlay(Mlt::Filter *filter) { if (pCore->projectManager()->currentTimeline()) { if (pCore->projectManager()->currentTimeline()->projectView()->createSplitOverlay(filter)) { m_projectMonitor->activateSplit(); } } } void MainWindow::removeSplitOverlay() { if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->removeSplitOverlay(); } } void MainWindow::addAction(const QString &name, QAction *action) { m_actionNames.append(name); actionCollection()->addAction(name, action); actionCollection()->setDefaultShortcut(action, action->shortcut()); //Fix warning about setDefaultShortcut } QAction *MainWindow::addAction(const QString &name, const QString &text, const QObject *receiver, const char *member, const QIcon &icon, const QKeySequence &shortcut) { QAction *action = new QAction(text, this); if (!icon.isNull()) { action->setIcon(icon); } if (!shortcut.isEmpty()) { action->setShortcut(shortcut); } addAction(name, action); connect(action, SIGNAL(triggered(bool)), receiver, member); return action; } void MainWindow::setupActions() { m_statusProgressBar = new QProgressBar(this); m_statusProgressBar->setMinimum(0); m_statusProgressBar->setMaximum(100); m_statusProgressBar->setMaximumWidth(150); m_statusProgressBar->setVisible(false); KToolBar *toolbar = new KToolBar(QStringLiteral("statusToolBar"), this, Qt::BottomToolBarArea); toolbar->setMovable(false); setStatusBarStyleSheet(palette()); QString styleBorderless = QStringLiteral("QToolButton { border-width: 0px;margin: 1px 3px 0px;padding: 0px;}"); - + //create edit mode buttons m_normalEditTool = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-normal-edit")), i18n("Normal mode"), this); m_normalEditTool->setShortcut(i18nc("Normal editing", "n")); toolbar->addAction(m_normalEditTool); m_normalEditTool->setCheckable(true); m_normalEditTool->setChecked(true); m_overwriteEditTool = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-overwrite-edit")), i18n("Overwrite mode"), this); //m_overwriteEditTool->setShortcut(i18nc("Overwrite mode shortcut", "o")); toolbar->addAction(m_overwriteEditTool); m_overwriteEditTool->setCheckable(true); m_overwriteEditTool->setChecked(false); m_insertEditTool = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-insert-edit")), i18n("Insert mode"), this); //m_insertEditTool->setShortcut(i18nc("Insert mode shortcut", "i")); toolbar->addAction(m_insertEditTool); m_insertEditTool->setCheckable(true); m_insertEditTool->setChecked(false); QActionGroup *editGroup = new QActionGroup(this); editGroup->addAction(m_normalEditTool); editGroup->addAction(m_overwriteEditTool); editGroup->addAction(m_insertEditTool); editGroup->setExclusive(true); connect(editGroup, SIGNAL(triggered(QAction*)), this, SLOT(slotChangeEdit(QAction*))); toolbar->addSeparator(); // create tools buttons m_buttonSelectTool = new QAction(KoIconUtils::themedIcon(QStringLiteral("cursor-arrow")), i18n("Selection tool"), this); m_buttonSelectTool->setShortcut(i18nc("Selection tool shortcut", "s")); toolbar->addAction(m_buttonSelectTool); m_buttonSelectTool->setCheckable(true); m_buttonSelectTool->setChecked(true); m_buttonRazorTool = new QAction(KoIconUtils::themedIcon(QStringLiteral("edit-cut")), i18n("Razor tool"), this); m_buttonRazorTool->setShortcut(i18nc("Razor tool shortcut", "x")); toolbar->addAction(m_buttonRazorTool); m_buttonRazorTool->setCheckable(true); m_buttonRazorTool->setChecked(false); m_buttonSpacerTool = new QAction(KoIconUtils::themedIcon(QStringLiteral("distribute-horizontal-x")), i18n("Spacer tool"), this); m_buttonSpacerTool->setShortcut(i18nc("Spacer tool shortcut", "m")); toolbar->addAction(m_buttonSpacerTool); m_buttonSpacerTool->setCheckable(true); m_buttonSpacerTool->setChecked(false); QActionGroup *toolGroup = new QActionGroup(this); toolGroup->addAction(m_buttonSelectTool); toolGroup->addAction(m_buttonRazorTool); toolGroup->addAction(m_buttonSpacerTool); toolGroup->setExclusive(true); toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly); QWidget * actionWidget; int max = toolbar->iconSizeDefault() + 2; actionWidget = toolbar->widgetForAction(m_normalEditTool); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget = toolbar->widgetForAction(m_insertEditTool); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget = toolbar->widgetForAction(m_overwriteEditTool); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget = toolbar->widgetForAction(m_buttonSelectTool); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget = toolbar->widgetForAction(m_buttonRazorTool); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget = toolbar->widgetForAction(m_buttonSpacerTool); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); connect(toolGroup, SIGNAL(triggered(QAction*)), this, SLOT(slotChangeTool(QAction*))); toolbar->addSeparator(); QIcon ic = KoIconUtils::themedIcon(QStringLiteral("zoom-fit-best")); m_buttonFitZoom = new QAction(ic, i18n("Fit zoom to project"), this); toolbar->addAction(m_buttonFitZoom); m_buttonFitZoom->setCheckable(false); m_zoomOut = new QAction(KoIconUtils::themedIcon(QStringLiteral("zoom-out")), i18n("Zoom Out"), this); toolbar->addAction(m_zoomOut); m_zoomOut->setShortcut(Qt::CTRL + Qt::Key_Minus); m_zoomSlider = new QSlider(Qt::Horizontal, this); m_zoomSlider->setMaximum(13); m_zoomSlider->setPageStep(1); m_zoomSlider->setInvertedAppearance(true); m_zoomSlider->setInvertedControls(true); m_zoomSlider->setMaximumWidth(150); m_zoomSlider->setMinimumWidth(100); toolbar->addWidget(m_zoomSlider); m_zoomIn = new QAction(KoIconUtils::themedIcon(QStringLiteral("zoom-in")), i18n("Zoom In"), this); toolbar->addAction(m_zoomIn); m_zoomIn->setShortcut(Qt::CTRL + Qt::Key_Plus); actionWidget = toolbar->widgetForAction(m_buttonFitZoom); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget->setStyleSheet(styleBorderless); actionWidget = toolbar->widgetForAction(m_zoomIn); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget->setStyleSheet(styleBorderless); actionWidget = toolbar->widgetForAction(m_zoomOut); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget->setStyleSheet(styleBorderless); connect(m_zoomSlider, SIGNAL(valueChanged(int)), this, SLOT(slotSetZoom(int))); connect(m_zoomSlider, SIGNAL(sliderMoved(int)), this, SLOT(slotShowZoomSliderToolTip(int))); connect(m_buttonFitZoom, SIGNAL(triggered()), this, SLOT(slotFitZoom())); connect(m_zoomIn, SIGNAL(triggered(bool)), this, SLOT(slotZoomIn())); connect(m_zoomOut, SIGNAL(triggered(bool)), this, SLOT(slotZoomOut())); toolbar->addSeparator(); //create automatic audio split button m_buttonAutomaticSplitAudio = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-split-audio")), i18n("Split audio and video automatically"), this); toolbar->addAction(m_buttonAutomaticSplitAudio); m_buttonAutomaticSplitAudio->setCheckable(true); m_buttonAutomaticSplitAudio->setChecked(KdenliveSettings::splitaudio()); connect(m_buttonAutomaticSplitAudio, &QAction::toggled, this, &MainWindow::slotSwitchSplitAudio); m_buttonVideoThumbs = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-show-videothumb")), i18n("Show video thumbnails"), this); toolbar->addAction(m_buttonVideoThumbs); m_buttonVideoThumbs->setCheckable(true); m_buttonVideoThumbs->setChecked(KdenliveSettings::videothumbnails()); connect(m_buttonVideoThumbs, SIGNAL(triggered()), this, SLOT(slotSwitchVideoThumbs())); m_buttonAudioThumbs = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-show-audiothumb")), i18n("Show audio thumbnails"), this); toolbar->addAction(m_buttonAudioThumbs); m_buttonAudioThumbs->setCheckable(true); m_buttonAudioThumbs->setChecked(KdenliveSettings::audiothumbnails()); connect(m_buttonAudioThumbs, SIGNAL(triggered()), this, SLOT(slotSwitchAudioThumbs())); m_buttonShowMarkers = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-show-markers")), i18n("Show markers comments"), this); toolbar->addAction(m_buttonShowMarkers); m_buttonShowMarkers->setCheckable(true); m_buttonShowMarkers->setChecked(KdenliveSettings::showmarkers()); connect(m_buttonShowMarkers, SIGNAL(triggered()), this, SLOT(slotSwitchMarkersComments())); m_buttonSnap = new QAction(KoIconUtils::themedIcon(QStringLiteral("kdenlive-snap")), i18n("Snap"), this); toolbar->addAction(m_buttonSnap); m_buttonSnap->setCheckable(true); m_buttonSnap->setChecked(KdenliveSettings::snaptopoints()); connect(m_buttonSnap, SIGNAL(triggered()), this, SLOT(slotSwitchSnap())); actionWidget = toolbar->widgetForAction(m_buttonAutomaticSplitAudio); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget = toolbar->widgetForAction(m_buttonVideoThumbs); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget = toolbar->widgetForAction(m_buttonAudioThumbs); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget = toolbar->widgetForAction(m_buttonShowMarkers); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); actionWidget = toolbar->widgetForAction(m_buttonSnap); actionWidget->setMaximumWidth(max); actionWidget->setMaximumHeight(max - 4); m_messageLabel = new StatusBarMessageLabel(this); m_messageLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding); statusBar()->addWidget(m_messageLabel, 10); statusBar()->addWidget(m_statusProgressBar, 0); + QWidget *spacer = new QWidget(this); + spacer->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); + statusBar()->addWidget(spacer, 10); statusBar()->addPermanentWidget(toolbar); m_timeFormatButton = new KSelectAction(QStringLiteral("00:00:00:00 / 00:00:00:00"), this); QFont fixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); m_timeFormatButton->setFont(fixedFont); m_timeFormatButton->addAction(i18n("hh:mm:ss:ff")); m_timeFormatButton->addAction(i18n("Frames")); if (KdenliveSettings::frametimecode()) m_timeFormatButton->setCurrentItem(1); else m_timeFormatButton->setCurrentItem(0); connect(m_timeFormatButton, SIGNAL(triggered(int)), this, SLOT(slotUpdateTimecodeFormat(int))); m_timeFormatButton->setToolBarMode(KSelectAction::MenuMode); toolbar->addAction(m_timeFormatButton); const QFontMetrics metric(fixedFont); int requiredWidth = metric.boundingRect(QStringLiteral("00:00:00:00 / 00:00:00:00")).width() + 20; actionWidget = toolbar->widgetForAction(m_timeFormatButton); actionWidget->setObjectName(QStringLiteral("timecode")); actionWidget->setMinimumWidth(requiredWidth); addAction(QStringLiteral("normal_mode"), m_normalEditTool); addAction(QStringLiteral("overwrite_mode"), m_overwriteEditTool); addAction(QStringLiteral("insert_mode"), m_insertEditTool); addAction(QStringLiteral("select_tool"), m_buttonSelectTool); addAction(QStringLiteral("razor_tool"), m_buttonRazorTool); addAction(QStringLiteral("spacer_tool"), m_buttonSpacerTool); addAction(QStringLiteral("automatic_split_audio"), m_buttonAutomaticSplitAudio); addAction(QStringLiteral("show_video_thumbs"), m_buttonVideoThumbs); addAction(QStringLiteral("show_audio_thumbs"), m_buttonAudioThumbs); addAction(QStringLiteral("show_markers"), m_buttonShowMarkers); addAction(QStringLiteral("snap"), m_buttonSnap); addAction(QStringLiteral("zoom_fit"), m_buttonFitZoom); addAction(QStringLiteral("zoom_in"), m_zoomIn); addAction(QStringLiteral("zoom_out"), m_zoomOut); KNS3::standardAction(i18n("Download New Wipes..."), this, SLOT(slotGetNewLumaStuff()), actionCollection(), "get_new_lumas"); KNS3::standardAction(i18n("Download New Render Profiles..."), this, SLOT(slotGetNewRenderStuff()), actionCollection(), "get_new_profiles"); KNS3::standardAction(i18n("Download New Project Profiles..."), this, SLOT(slotGetNewMltProfileStuff()), actionCollection(), "get_new_mlt_profiles"); KNS3::standardAction(i18n("Download New Title Templates..."), this, SLOT(slotGetNewTitleStuff()), actionCollection(), "get_new_titles"); addAction(QStringLiteral("run_wizard"), i18n("Run Config Wizard"), this, SLOT(slotRunWizard()), KoIconUtils::themedIcon(QStringLiteral("tools-wizard"))); addAction(QStringLiteral("project_settings"), i18n("Project Settings"), this, SLOT(slotEditProjectSettings()), KoIconUtils::themedIcon(QStringLiteral("configure"))); addAction(QStringLiteral("project_render"), i18n("Render"), this, SLOT(slotRenderProject()), KoIconUtils::themedIcon(QStringLiteral("media-record")), Qt::CTRL + Qt::Key_Return); addAction(QStringLiteral("project_clean"), i18n("Clean Project"), this, SLOT(slotCleanProject()), KoIconUtils::themedIcon(QStringLiteral("edit-clear"))); //TODO //addAction("project_adjust_profile", i18n("Adjust Profile to Current Clip"), pCore->bin(), SLOT(adjustProjectProfileToItem())); m_playZone = addAction(QStringLiteral("monitor_play_zone"), i18n("Play Zone"), pCore->monitorManager(), SLOT(slotPlayZone()), KoIconUtils::themedIcon(QStringLiteral("media-playback-start")), Qt::CTRL + Qt::Key_Space); m_loopZone = addAction(QStringLiteral("monitor_loop_zone"), i18n("Loop Zone"), pCore->monitorManager(), SLOT(slotLoopZone()), KoIconUtils::themedIcon(QStringLiteral("media-playback-start")), Qt::ALT + Qt::Key_Space); m_loopClip = addAction(QStringLiteral("monitor_loop_clip"), i18n("Loop selected clip"), m_projectMonitor, SLOT(slotLoopClip()), KoIconUtils::themedIcon(QStringLiteral("media-playback-start"))); m_loopClip->setEnabled(false); addAction(QStringLiteral("dvd_wizard"), i18n("DVD Wizard"), this, SLOT(slotDvdWizard()), KoIconUtils::themedIcon(QStringLiteral("media-optical"))); addAction(QStringLiteral("transcode_clip"), i18n("Transcode Clips"), this, SLOT(slotTranscodeClip()), KoIconUtils::themedIcon(QStringLiteral("edit-copy"))); addAction(QStringLiteral("archive_project"), i18n("Archive Project"), this, SLOT(slotArchiveProject()), KoIconUtils::themedIcon(QStringLiteral("document-save-all"))); addAction(QStringLiteral("switch_monitor"), i18n("Switch monitor"), this, SLOT(slotSwitchMonitors()), QIcon(), Qt::Key_T); addAction(QStringLiteral("expand_timeline_clip"), i18n("Expand Clip"), pCore->projectManager(), SLOT(slotExpandClip()), KoIconUtils::themedIcon(QStringLiteral("document-open"))); QAction *overlayInfo = new QAction(KoIconUtils::themedIcon(QStringLiteral("help-hint")), i18n("Monitor Info Overlay"), this); addAction(QStringLiteral("monitor_overlay"), overlayInfo); overlayInfo->setCheckable(true); overlayInfo->setData(0x01); QAction *overlayTCInfo = new QAction(KoIconUtils::themedIcon(QStringLiteral("help-hint")), i18n("Monitor Overlay Timecode"), this); addAction(QStringLiteral("monitor_overlay_tc"), overlayTCInfo); overlayTCInfo->setCheckable(true); overlayTCInfo->setData(0x02); QAction *overlayMarkerInfo = new QAction(KoIconUtils::themedIcon(QStringLiteral("help-hint")), i18n("Monitor Overlay Markers"), this); addAction(QStringLiteral("monitor_overlay_markers"), overlayMarkerInfo); overlayMarkerInfo->setCheckable(true); overlayMarkerInfo->setData(0x04); QAction *overlaySafeInfo = new QAction(KoIconUtils::themedIcon(QStringLiteral("help-hint")), i18n("Monitor Overlay Safe Zones"), this); addAction(QStringLiteral("monitor_overlay_safezone"), overlaySafeInfo); overlaySafeInfo->setCheckable(true); overlaySafeInfo->setData(0x08); QAction *overlayAudioInfo = new QAction(KoIconUtils::themedIcon(QStringLiteral("help-hint")), i18n("Monitor Overlay Audio Waveform"), this); addAction(QStringLiteral("monitor_overlay_audiothumb"), overlayAudioInfo); overlayAudioInfo->setCheckable(true); overlayAudioInfo->setData(0x10); QAction *dropFrames = new QAction(QIcon(), i18n("Real Time (drop frames)"), this); dropFrames->setCheckable(true); dropFrames->setChecked(KdenliveSettings::monitor_dropframes()); connect(dropFrames, SIGNAL(toggled(bool)), this, SLOT(slotSwitchDropFrames(bool))); KSelectAction *monitorGamma = new KSelectAction(i18n("Monitor Gamma"), this); monitorGamma->addAction(i18n("sRGB (computer)")); monitorGamma->addAction(i18n("Rec. 709 (TV)")); addAction(QStringLiteral("mlt_gamma"), monitorGamma); monitorGamma->setCurrentItem(KdenliveSettings::monitor_gamma()); connect(monitorGamma, SIGNAL(triggered(int)), this, SLOT(slotSetMonitorGamma(int))); addAction(QStringLiteral("insert_project_tree"), i18n("Insert Zone in Project Bin"), this, SLOT(slotInsertZoneToTree()), QIcon(), Qt::CTRL + Qt::Key_I); addAction(QStringLiteral("insert_timeline"), i18n("Insert Zone in Timeline"), this, SLOT(slotInsertZoneToTimeline()), QIcon(), Qt::SHIFT + Qt::CTRL + Qt::Key_I); QAction *resizeStart = new QAction(QIcon(), i18n("Resize Item Start"), this); addAction(QStringLiteral("resize_timeline_clip_start"), resizeStart); resizeStart->setShortcut(Qt::Key_1); connect(resizeStart, SIGNAL(triggered(bool)), this, SLOT(slotResizeItemStart())); QAction *resizeEnd = new QAction(QIcon(), i18n("Resize Item End"), this); addAction(QStringLiteral("resize_timeline_clip_end"), resizeEnd); resizeEnd->setShortcut(Qt::Key_2); connect(resizeEnd, SIGNAL(triggered(bool)), this, SLOT(slotResizeItemEnd())); addAction(QStringLiteral("monitor_seek_snap_backward"), i18n("Go to Previous Snap Point"), this, SLOT(slotSnapRewind()), KoIconUtils::themedIcon(QStringLiteral("media-seek-backward")), Qt::ALT + Qt::Key_Left); addAction(QStringLiteral("seek_clip_start"), i18n("Go to Clip Start"), this, SLOT(slotClipStart()), KoIconUtils::themedIcon(QStringLiteral("media-seek-backward")), Qt::Key_Home); addAction(QStringLiteral("seek_clip_end"), i18n("Go to Clip End"), this, SLOT(slotClipEnd()), KoIconUtils::themedIcon(QStringLiteral("media-seek-forward")), Qt::Key_End); addAction(QStringLiteral("monitor_seek_snap_forward"), i18n("Go to Next Snap Point"), this, SLOT(slotSnapForward()), KoIconUtils::themedIcon(QStringLiteral("media-seek-forward")), Qt::ALT + Qt::Key_Right); addAction(QStringLiteral("delete_timeline_clip"), i18n("Delete Selected Item"), this, SLOT(slotDeleteItem()), KoIconUtils::themedIcon(QStringLiteral("edit-delete")), Qt::Key_Delete); addAction(QStringLiteral("align_playhead"), i18n("Align Playhead to Mouse Position"), this, SLOT(slotAlignPlayheadToMousePos()), QIcon(), Qt::Key_P); QAction *stickTransition = new QAction(i18n("Automatic Transition"), this); stickTransition->setData(QStringLiteral("auto")); stickTransition->setCheckable(true); stickTransition->setEnabled(false); addAction(QStringLiteral("auto_transition"), stickTransition); connect(stickTransition, SIGNAL(triggered(bool)), this, SLOT(slotAutoTransition())); addAction(QStringLiteral("group_clip"), i18n("Group Clips"), this, SLOT(slotGroupClips()), KoIconUtils::themedIcon(QStringLiteral("object-group")), Qt::CTRL + Qt::Key_G); QAction * ungroupClip = addAction(QStringLiteral("ungroup_clip"), i18n("Ungroup Clips"), this, SLOT(slotUnGroupClips()), KoIconUtils::themedIcon(QStringLiteral("object-ungroup")), Qt::CTRL + Qt::SHIFT + Qt::Key_G); ungroupClip->setData("ungroup_clip"); addAction(QStringLiteral("edit_item_duration"), i18n("Edit Duration"), this, SLOT(slotEditItemDuration()), KoIconUtils::themedIcon(QStringLiteral("measure"))); addAction(QStringLiteral("clip_in_project_tree"), i18n("Clip in Project Bin"), this, SLOT(slotClipInProjectTree()), KoIconUtils::themedIcon(QStringLiteral("go-jump-definition"))); addAction(QStringLiteral("overwrite_to_in_point"), i18n("Insert Clip Zone in Timeline (Overwrite)"), this, SLOT(slotInsertClipOverwrite()), QIcon(), Qt::Key_B); addAction(QStringLiteral("insert_to_in_point"), i18n("Insert Clip Zone in Timeline (Insert)"), this, SLOT(slotInsertClipInsert()), QIcon(), Qt::Key_V); addAction(QStringLiteral("remove_extract"), i18n("Extract Timeline Zone"), this, SLOT(slotExtractZone()), QIcon(), Qt::SHIFT + Qt::Key_X); addAction(QStringLiteral("remove_lift"), i18n("Lift Timeline Zone"), this, SLOT(slotLiftZone()), QIcon(), Qt::Key_Z); + addAction(QStringLiteral("prerender_timeline_zone"), i18n("Timeline Preview Render"), this, SLOT(slotPreviewRender()), QIcon()); + addAction(QStringLiteral("select_timeline_clip"), i18n("Select Clip"), this, SLOT(slotSelectTimelineClip()), KoIconUtils::themedIcon(QStringLiteral("edit-select")), Qt::Key_Plus); addAction(QStringLiteral("deselect_timeline_clip"), i18n("Deselect Clip"), this, SLOT(slotDeselectTimelineClip()), KoIconUtils::themedIcon(QStringLiteral("edit-select")), Qt::Key_Minus); addAction(QStringLiteral("select_add_timeline_clip"), i18n("Add Clip To Selection"), this, SLOT(slotSelectAddTimelineClip()), KoIconUtils::themedIcon(QStringLiteral("edit-select")), Qt::ALT + Qt::Key_Plus); addAction(QStringLiteral("select_timeline_transition"), i18n("Select Transition"), this, SLOT(slotSelectTimelineTransition()), KoIconUtils::themedIcon(QStringLiteral("edit-select")), Qt::SHIFT + Qt::Key_Plus); addAction(QStringLiteral("deselect_timeline_transition"), i18n("Deselect Transition"), this, SLOT(slotDeselectTimelineTransition()), KoIconUtils::themedIcon(QStringLiteral("edit-select")), Qt::SHIFT + Qt::Key_Minus); addAction(QStringLiteral("select_add_timeline_transition"), i18n("Add Transition To Selection"), this, SLOT(slotSelectAddTimelineTransition()), KoIconUtils::themedIcon(QStringLiteral("edit-select")), Qt::ALT + Qt::SHIFT + Qt::Key_Plus); addAction(QStringLiteral("cut_timeline_clip"), i18n("Cut Clip"), this, SLOT(slotCutTimelineClip()), KoIconUtils::themedIcon(QStringLiteral("edit-cut")), Qt::SHIFT + Qt::Key_R); addAction(QStringLiteral("add_clip_marker"), i18n("Add Marker"), this, SLOT(slotAddClipMarker()), KoIconUtils::themedIcon(QStringLiteral("bookmark-new"))); addAction(QStringLiteral("delete_clip_marker"), i18n("Delete Marker"), this, SLOT(slotDeleteClipMarker()), KoIconUtils::themedIcon(QStringLiteral("edit-delete"))); addAction(QStringLiteral("delete_all_clip_markers"), i18n("Delete All Markers"), this, SLOT(slotDeleteAllClipMarkers()), KoIconUtils::themedIcon(QStringLiteral("edit-delete"))); QAction * editClipMarker = addAction(QStringLiteral("edit_clip_marker"), i18n("Edit Marker"), this, SLOT(slotEditClipMarker()), KoIconUtils::themedIcon(QStringLiteral("document-properties"))); editClipMarker->setData(QStringLiteral("edit_marker")); addAction(QStringLiteral("add_marker_guide_quickly"), i18n("Add Marker/Guide quickly"), this, SLOT(slotAddMarkerGuideQuickly()), KoIconUtils::themedIcon(QStringLiteral("bookmark-new")), Qt::Key_Asterisk); QAction * splitAudio = addAction(QStringLiteral("split_audio"), i18n("Split Audio"), this, SLOT(slotSplitAudio()), KoIconUtils::themedIcon(QStringLiteral("document-new"))); // "A+V" as data means this action should only be available for clips with audio AND video splitAudio->setData("A+V"); QAction * setAudioAlignReference = addAction(QStringLiteral("set_audio_align_ref"), i18n("Set Audio Reference"), this, SLOT(slotSetAudioAlignReference())); // "A" as data means this action should only be available for clips with audio setAudioAlignReference->setData("A"); QAction * alignAudio = addAction(QStringLiteral("align_audio"), i18n("Align Audio to Reference"), this, SLOT(slotAlignAudio()), QIcon()); // "A" as data means this action should only be available for clips with audio alignAudio->setData("A"); QAction * audioOnly = new QAction(KoIconUtils::themedIcon(QStringLiteral("document-new")), i18n("Audio Only"), this); addAction(QStringLiteral("clip_audio_only"), audioOnly); audioOnly->setData(PlaylistState::AudioOnly); audioOnly->setCheckable(true); QAction * videoOnly = new QAction(KoIconUtils::themedIcon(QStringLiteral("document-new")), i18n("Video Only"), this); addAction(QStringLiteral("clip_video_only"), videoOnly); videoOnly->setData(PlaylistState::VideoOnly); videoOnly->setCheckable(true); QAction * audioAndVideo = new QAction(KoIconUtils::themedIcon(QStringLiteral("document-new")), i18n("Audio and Video"), this); addAction(QStringLiteral("clip_audio_and_video"), audioAndVideo); audioAndVideo->setData(PlaylistState::Original); audioAndVideo->setCheckable(true); QAction * disabledClip = new QAction(KoIconUtils::themedIcon(QStringLiteral("document-new")), i18n("Disabled"), this); addAction(QStringLiteral("clip_disabled"), disabledClip); disabledClip->setData(PlaylistState::Disabled); disabledClip->setCheckable(true); m_clipTypeGroup = new QActionGroup(this); m_clipTypeGroup->addAction(audioOnly); m_clipTypeGroup->addAction(videoOnly); m_clipTypeGroup->addAction(audioAndVideo); m_clipTypeGroup->addAction(disabledClip); connect(m_clipTypeGroup, SIGNAL(triggered(QAction*)), this, SLOT(slotUpdateClipType(QAction*))); m_clipTypeGroup->setEnabled(false); addAction(QStringLiteral("insert_space"), i18n("Insert Space"), this, SLOT(slotInsertSpace())); addAction(QStringLiteral("delete_space"), i18n("Remove Space"), this, SLOT(slotRemoveSpace())); KActionCategory *timelineActions = new KActionCategory(i18n("Tracks"), actionCollection()); QAction *insertTrack = new QAction(QIcon(), i18n("Insert Track"), this); connect(insertTrack, &QAction::triggered, this, &MainWindow::slotInsertTrack); timelineActions->addAction(QStringLiteral("insert_track"), insertTrack); QAction *deleteTrack = new QAction(QIcon(), i18n("Delete Track"), this); connect(deleteTrack, &QAction::triggered, this, &MainWindow::slotDeleteTrack); timelineActions->addAction(QStringLiteral("delete_track"), deleteTrack); deleteTrack->setData("delete_track"); QAction *configTracks = new QAction(KoIconUtils::themedIcon(QStringLiteral("configure")), i18n("Configure Tracks"), this); connect(configTracks, &QAction::triggered, this, &MainWindow::slotConfigTrack); timelineActions->addAction(QStringLiteral("config_tracks"), configTracks); QAction *selectTrack = new QAction(QIcon(), i18n("Select All in Current Track"), this); connect(selectTrack, &QAction::triggered, this, &MainWindow::slotSelectTrack); timelineActions->addAction(QStringLiteral("select_track"), selectTrack); QAction *selectAll = KStandardAction::selectAll(this, SLOT(slotSelectAllTracks()), this); selectAll->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-select-all"))); selectAll->setShortcutContext(Qt::WidgetWithChildrenShortcut); timelineActions->addAction(QStringLiteral("select_all_tracks"), selectAll); kdenliveCategoryMap.insert(QStringLiteral("timeline"), timelineActions); addAction(QStringLiteral("add_guide"), i18n("Add Guide"), this, SLOT(slotAddGuide()), KoIconUtils::themedIcon(QStringLiteral("document-new"))); addAction(QStringLiteral("delete_guide"), i18n("Delete Guide"), this, SLOT(slotDeleteGuide()), KoIconUtils::themedIcon(QStringLiteral("edit-delete"))); addAction(QStringLiteral("edit_guide"), i18n("Edit Guide"), this, SLOT(slotEditGuide()), KoIconUtils::themedIcon(QStringLiteral("document-properties"))); addAction(QStringLiteral("delete_all_guides"), i18n("Delete All Guides"), this, SLOT(slotDeleteAllGuides()), KoIconUtils::themedIcon(QStringLiteral("edit-delete"))); QAction *pasteEffects = addAction(QStringLiteral("paste_effects"), i18n("Paste Effects"), this, SLOT(slotPasteEffects()), KoIconUtils::themedIcon(QStringLiteral("edit-paste"))); pasteEffects->setData("paste_effects"); m_saveAction = KStandardAction::save(pCore->projectManager(), SLOT(saveFile()), actionCollection()); m_saveAction->setIcon(KoIconUtils::themedIcon(QStringLiteral("document-save"))); addAction(QStringLiteral("save_selection"), i18n("Save Selection"), pCore->projectManager(), SLOT(slotSaveSelection()), KoIconUtils::themedIcon(QStringLiteral("document-save"))); QAction *sentToLibrary = addAction(QStringLiteral("send_library"), i18n("Add Selection to Library"), pCore->library(), SLOT(slotAddToLibrary()), KoIconUtils::themedIcon(QStringLiteral("bookmark-new"))); pCore->library()->setupActions(QList () << sentToLibrary); QAction *a = KStandardAction::quit(this, SLOT(close()), actionCollection()); a->setIcon(KoIconUtils::themedIcon(QStringLiteral("application-exit"))); // TODO: make the following connection to slotEditKeys work //KStandardAction::keyBindings(this, SLOT(slotEditKeys()), actionCollection()); a = KStandardAction::preferences(this, SLOT(slotPreferences()), actionCollection()); a->setIcon(KoIconUtils::themedIcon(QStringLiteral("configure"))); a = KStandardAction::configureNotifications(this, SLOT(configureNotifications()), actionCollection()); a->setIcon(KoIconUtils::themedIcon(QStringLiteral("configure"))); a = KStandardAction::copy(this, SLOT(slotCopy()), actionCollection()); a->setIcon(KoIconUtils::themedIcon(QStringLiteral("edit-copy"))); a = KStandardAction::paste(this, SLOT(slotPaste()), actionCollection()); a->setIcon(KoIconUtils::themedIcon(QStringLiteral("edit-paste"))); a = KStandardAction::fullScreen(this, SLOT(slotFullScreen()), this, actionCollection()); a->setIcon(KoIconUtils::themedIcon(QStringLiteral("view-fullscreen"))); QAction *undo = KStandardAction::undo(m_commandStack, SLOT(undo()), actionCollection()); undo->setIcon(KoIconUtils::themedIcon(QStringLiteral("edit-undo"))); undo->setEnabled(false); connect(m_commandStack, SIGNAL(canUndoChanged(bool)), undo, SLOT(setEnabled(bool))); QAction *redo = KStandardAction::redo(m_commandStack, SLOT(redo()), actionCollection()); redo->setIcon(KoIconUtils::themedIcon(QStringLiteral("edit-redo"))); redo->setEnabled(false); connect(m_commandStack, SIGNAL(canRedoChanged(bool)), redo, SLOT(setEnabled(bool))); QMenu *addClips = new QMenu(); QAction *addClip = addAction(QStringLiteral("add_clip"), i18n("Add Clip"), pCore->bin(), SLOT(slotAddClip()), KoIconUtils::themedIcon(QStringLiteral("kdenlive-add-clip"))); addClips->addAction(addClip); QAction *action = addAction(QStringLiteral("add_color_clip"), i18n("Add Color Clip"), pCore->bin(), SLOT(slotCreateProjectClip()), KoIconUtils::themedIcon(QStringLiteral("kdenlive-add-color-clip"))); action->setData((int) Color); addClips->addAction(action); action = addAction(QStringLiteral("add_slide_clip"), i18n("Add Slideshow Clip"), pCore->bin(), SLOT(slotCreateProjectClip()), KoIconUtils::themedIcon(QStringLiteral("kdenlive-add-slide-clip"))); action->setData((int) SlideShow); addClips->addAction(action); action = addAction(QStringLiteral("add_text_clip"), i18n("Add Title Clip"), pCore->bin(), SLOT(slotCreateProjectClip()), KoIconUtils::themedIcon(QStringLiteral("kdenlive-add-text-clip"))); action->setData((int) Text); addClips->addAction(action); action = addAction(QStringLiteral("add_text_template_clip"), i18n("Add Template Title"), pCore->bin(), SLOT(slotCreateProjectClip()), KoIconUtils::themedIcon(QStringLiteral("kdenlive-add-text-clip"))); action->setData((int) TextTemplate); addClips->addAction(action); action = addAction(QStringLiteral("add_qtext_clip"), i18n("Add Simple Text Clip"), pCore->bin(), SLOT(slotCreateProjectClip()), KoIconUtils::themedIcon(QStringLiteral("kdenlive-add-text-clip"))); action->setData((int) QText); addClips->addAction(action); QAction *addFolder = addAction(QStringLiteral("add_folder"), i18n("Create Folder"), pCore->bin(), SLOT(slotAddFolder()), KoIconUtils::themedIcon(QStringLiteral("folder-new"))); addClips->addAction(addAction(QStringLiteral("download_resource"), i18n("Online Resources"), this, SLOT(slotDownloadResources()), KoIconUtils::themedIcon(QStringLiteral("edit-download")))); QAction *clipProperties = addAction(QStringLiteral("clip_properties"), i18n("Clip Properties"), pCore->bin(), SLOT(slotSwitchClipProperties(bool)), KoIconUtils::themedIcon(QStringLiteral("document-edit"))); clipProperties->setCheckable(true); clipProperties->setData("clip_properties"); QAction *openClip = addAction(QStringLiteral("edit_clip"), i18n("Edit Clip"), pCore->bin(), SLOT(slotOpenClip()), KoIconUtils::themedIcon(QStringLiteral("document-open"))); openClip->setData("edit_clip"); openClip->setEnabled(false); QAction *deleteClip = addAction(QStringLiteral("delete_clip"), i18n("Delete Clip"), pCore->bin(), SLOT(slotDeleteClip()), KoIconUtils::themedIcon(QStringLiteral("edit-delete"))); deleteClip->setData("delete_clip"); deleteClip->setEnabled(false); QAction *reloadClip = addAction(QStringLiteral("reload_clip"), i18n("Reload Clip"), pCore->bin(), SLOT(slotReloadClip()), KoIconUtils::themedIcon(QStringLiteral("view-refresh"))); reloadClip->setData("reload_clip"); reloadClip->setEnabled(false); QAction *disableEffects = addAction(QStringLiteral("disable_timeline_effects"), i18n("Disable Timeline Effects"), pCore->projectManager(), SLOT(slotDisableTimelineEffects(bool)), KoIconUtils::themedIcon(QStringLiteral("favorite"))); disableEffects->setData("disable_timeline_effects"); disableEffects->setCheckable(true); disableEffects->setChecked(false); QAction *duplicateClip = addAction(QStringLiteral("duplicate_clip"), i18n("Duplicate Clip"), pCore->bin(), SLOT(slotDuplicateClip()), KoIconUtils::themedIcon(QStringLiteral("edit-copy"))); duplicateClip->setData("duplicate_clip"); duplicateClip->setEnabled(false); QAction *proxyClip = new QAction(i18n("Proxy Clip"), this); addAction(QStringLiteral("proxy_clip"), proxyClip); proxyClip->setData(QStringList() << QString::number((int) AbstractClipJob::PROXYJOB)); proxyClip->setCheckable(true); proxyClip->setChecked(false); //TODO: port stopmotion to new Monitor code //addAction("stopmotion", i18n("Stop Motion Capture"), this, SLOT(slotOpenStopmotion()), KoIconUtils::themedIcon("image-x-generic")); addAction(QStringLiteral("switch_track_lock"), i18n("Toggle Track Lock"), pCore->projectManager(), SLOT(slotSwitchTrackLock()), QIcon(), Qt::SHIFT + Qt::Key_L); addAction(QStringLiteral("switch_all_track_lock"), i18n("Toggle All Track Lock"), pCore->projectManager(), SLOT(slotSwitchAllTrackLock()), QIcon(), Qt::CTRL + Qt::SHIFT + Qt::Key_L); addAction(QStringLiteral("switch_track_target"), i18n("Toggle Track Target"), pCore->projectManager(), SLOT(slotSwitchTrackTarget()), QIcon(), Qt::SHIFT + Qt::Key_T); QHash actions; actions.insert(QStringLiteral("reload"), reloadClip); actions.insert(QStringLiteral("duplicate"), duplicateClip); actions.insert(QStringLiteral("proxy"), proxyClip); actions.insert(QStringLiteral("properties"), clipProperties); actions.insert(QStringLiteral("open"), openClip); actions.insert(QStringLiteral("delete"), deleteClip); actions.insert(QStringLiteral("folder"), addFolder); pCore->bin()->setupMenu(addClips, addClip, actions); // Setup effects and transitions actions. KActionCategory *transitionActions = new KActionCategory(i18n("Transitions"), actionCollection()); //m_transitions = new QAction*[transitions.count()]; for (int i = 0; i < transitions.count(); ++i) { QStringList effectInfo = transitions.effectIdInfo(i); if (effectInfo.isEmpty()) continue; QAction *a = new QAction(effectInfo.at(0), this); a->setData(effectInfo); a->setIconVisibleInMenu(false); m_transitions << a; QString id = effectInfo.at(2); if (id.isEmpty()) id = effectInfo.at(1); transitionActions->addAction("transition_" + id, a); } } void MainWindow::setStatusBarStyleSheet(const QPalette &p) { KColorScheme scheme(p.currentColorGroup(), KColorScheme::Window, KSharedConfig::openConfig(KdenliveSettings::colortheme())); QColor buttonBg = scheme.background(KColorScheme::LinkBackground).color(); QColor buttonBord = scheme.foreground(KColorScheme::LinkText).color(); QColor buttonBord2 = scheme.shade(KColorScheme::LightShade); statusBar()->setStyleSheet(QStringLiteral("QStatusBar QLabel {font-size:%1pt;} QStatusBar::item { border: 0px; font-size:%1pt;padding:0px; }").arg(statusBar()->font().pointSize())); QString style1 = QStringLiteral("QToolBar { border: 0px } QToolButton { border-style: inset; border:1px solid transparent;border-radius: 3px;margin: 0px 3px;padding: 0px;} QToolButton#timecode {padding-right:10px;} QToolButton:hover { background: %3;border-style: inset; border:1px solid %3;border-radius: 3px;} QToolButton:checked { background-color: %1; border-style: inset; border:1px solid %2;border-radius: 3px;}").arg(buttonBg.name(), buttonBord.name(), buttonBord2.name()); statusBar()->setStyleSheet(style1); } void MainWindow::saveOptions() { KdenliveSettings::self()->save(); } void MainWindow::readOptions() { KSharedConfigPtr config = KSharedConfig::openConfig(); pCore->projectManager()->recentFilesAction()->loadEntries(KConfigGroup(config, "Recent Files")); if (KdenliveSettings::defaultprojectfolder().isEmpty()) { QDir dir(QDir::homePath()); if (!dir.mkdir(QStringLiteral("kdenlive"))) { qDebug() << "/// ERROR CREATING PROJECT FOLDER: "; } else { dir.cd("kdenlive"); KdenliveSettings::setDefaultprojectfolder(dir.absolutePath()); } } if (KdenliveSettings::trackheight() == 0) { QFontMetrics metrics(font()); int trackHeight = 2 * metrics.height(); QStyle *style = qApp->style(); trackHeight += style->pixelMetric(QStyle::PM_ToolBarIconSize) + 2 * style->pixelMetric(QStyle::PM_ToolBarItemMargin) + style->pixelMetric(QStyle::PM_ToolBarItemSpacing) + 2; KdenliveSettings::setTrackheight(trackHeight); } if (KdenliveSettings::trackheight() == 0) { KdenliveSettings::setTrackheight(50); } KConfigGroup initialGroup(config, "version"); if (!initialGroup.exists() || KdenliveSettings::ffmpegpath().isEmpty() || KdenliveSettings::ffplaypath().isEmpty()) { // this is our first run, show Wizard QPointer w = new Wizard(false, this); if (w->exec() == QDialog::Accepted && w->isOk()) { w->adjustSettings(); delete w; } else { delete w; ::exit(1); } } initialGroup.writeEntry("version", version); } void MainWindow::slotRunWizard() { QPointer w = new Wizard(false, this); if (w->exec() == QDialog::Accepted && w->isOk()) { w->adjustSettings(); } delete w; } void MainWindow::slotRefreshProfiles() { KdenliveSettingsDialog* d = static_cast (KConfigDialog::exists("settings")); if (d) { d->checkProfile(); } } void MainWindow::slotEditProjectSettings() { KdenliveDoc *project = pCore->projectManager()->current(); QPoint p = pCore->projectManager()->currentTimeline()->getTracksCount(); QPointer w = new ProjectSettings(project, project->metadata(), pCore->projectManager()->currentTimeline()->projectView()->extractTransitionsLumas(), p.x(), p.y(), project->projectFolder().path(), true, !project->isModified(), this); connect(w, SIGNAL(disableProxies()), this, SLOT(slotDisableProxies())); connect(w, SIGNAL(refreshProfiles()), this, SLOT(slotRefreshProfiles())); if (w->exec() == QDialog::Accepted) { QString profile = w->selectedProfile(); project->setProjectFolder(w->selectedFolder()); bool modified = false; if (m_recMonitor) { m_recMonitor->slotUpdateCaptureFolder(project->projectFolder().path() + QDir::separator()); } if (m_renderWidget) { m_renderWidget->setDocumentPath(project->projectFolder().path() + QDir::separator()); } if (KdenliveSettings::videothumbnails() != w->enableVideoThumbs()) { slotSwitchVideoThumbs(); } if (KdenliveSettings::audiothumbnails() != w->enableAudioThumbs()) { slotSwitchAudioThumbs(); } if (project->profilePath() != profile || project->profileChanged(profile)) { KdenliveSettings::setCurrent_profile(profile); pCore->projectManager()->slotResetProfiles(); slotUpdateDocumentState(true); } if (project->getDocumentProperty(QStringLiteral("proxyparams")) != w->proxyParams()) { modified = true; project->setDocumentProperty(QStringLiteral("proxyparams"), w->proxyParams()); if (pCore->binController()->clipCount() > 0 && KMessageBox::questionYesNo(this, i18n("You have changed the proxy parameters. Do you want to recreate all proxy clips for this project?")) == KMessageBox::Yes) { //TODO: rebuild all proxies //m_projectList->rebuildProxies(); } } if (project->getDocumentProperty(QStringLiteral("proxyextension")) != w->proxyExtension()) { modified = true; project->setDocumentProperty(QStringLiteral("proxyextension"), w->proxyExtension()); } if (project->getDocumentProperty(QStringLiteral("generateproxy")) != QString::number((int) w->generateProxy())) { modified = true; project->setDocumentProperty(QStringLiteral("generateproxy"), QString::number((int) w->generateProxy())); } if (project->getDocumentProperty(QStringLiteral("proxyminsize")) != QString::number(w->proxyMinSize())) { modified = true; project->setDocumentProperty(QStringLiteral("proxyminsize"), QString::number(w->proxyMinSize())); } if (project->getDocumentProperty(QStringLiteral("generateimageproxy")) != QString::number((int) w->generateImageProxy())) { modified = true; project->setDocumentProperty(QStringLiteral("generateimageproxy"), QString::number((int) w->generateImageProxy())); } if (project->getDocumentProperty(QStringLiteral("proxyimageminsize")) != QString::number(w->proxyImageMinSize())) { modified = true; project->setDocumentProperty(QStringLiteral("proxyimageminsize"), QString::number(w->proxyImageMinSize())); } if (QString::number((int) w->useProxy()) != project->getDocumentProperty(QStringLiteral("enableproxy"))) { project->setDocumentProperty(QStringLiteral("enableproxy"), QString::number((int) w->useProxy())); modified = true; slotUpdateProxySettings(); } if (w->metadata() != project->metadata()) { project->setMetadata(w->metadata()); } if (modified) project->setModified(); } delete w; } void MainWindow::slotDisableProxies() { pCore->projectManager()->current()->setDocumentProperty(QStringLiteral("enableproxy"), QString::number((int) false)); pCore->projectManager()->current()->setModified(); slotUpdateProxySettings(); } void MainWindow::slotRenderProject() { KdenliveDoc *project = pCore->projectManager()->current(); if (!m_renderWidget) { QString projectfolder = project ? project->projectFolder().path() + QDir::separator() : KdenliveSettings::defaultprojectfolder(); MltVideoProfile profile; if (project) { profile = project->mltProfile(); m_renderWidget = new RenderWidget(projectfolder, project->useProxy(), profile, this); connect(m_renderWidget, SIGNAL(shutdown()), this, SLOT(slotShutdown())); connect(m_renderWidget, SIGNAL(selectedRenderProfile(QMap)), this, SLOT(slotSetDocumentRenderProfile(QMap))); connect(m_renderWidget, SIGNAL(prepareRenderingData(bool,bool,QString)), this, SLOT(slotPrepareRendering(bool,bool,QString))); connect(m_renderWidget, SIGNAL(abortProcess(QString)), this, SIGNAL(abortRenderJob(QString))); connect(m_renderWidget, SIGNAL(openDvdWizard(QString)), this, SLOT(slotDvdWizard(QString))); m_renderWidget->setProfile(project->mltProfile()); m_renderWidget->setGuides(pCore->projectManager()->currentTimeline()->projectView()->guidesData(), project->projectDuration()); m_renderWidget->setDocumentPath(project->projectFolder().path() + QDir::separator()); m_renderWidget->setRenderProfile(project->getRenderProperties()); } } slotCheckRenderStatus(); m_renderWidget->show(); //m_renderWidget->showNormal(); // What are the following lines supposed to do? //pCore->projectManager()->currentTimeline()->tracksNumber(); //m_renderWidget->enableAudio(false); //m_renderWidget->export_audio; } void MainWindow::slotCheckRenderStatus() { // Make sure there are no missing clips //TODO /*if (m_renderWidget) m_renderWidget->missingClips(pCore->bin()->hasMissingClips());*/ } void MainWindow::setRenderingProgress(const QString &url, int progress) { if (m_renderWidget) m_renderWidget->setRenderJob(url, progress); } void MainWindow::setRenderingFinished(const QString &url, int status, const QString &error) { if (m_renderWidget) m_renderWidget->setRenderStatus(url, status, error); } void MainWindow::slotCleanProject() { if (KMessageBox::warningContinueCancel(this, i18n("This will remove all unused clips from your project."), i18n("Clean up project")) == KMessageBox::Cancel) return; pCore->bin()->cleanup(); } void MainWindow::slotUpdateMousePosition(int pos) { if (pCore->projectManager()->current()) { switch (m_timeFormatButton->currentItem()) { case 0: m_timeFormatButton->setText(pCore->projectManager()->current()->timecode().getTimecodeFromFrames(pos) + " / " + pCore->projectManager()->current()->timecode().getTimecodeFromFrames(pCore->projectManager()->currentTimeline()->duration())); break; default: m_timeFormatButton->setText(QString::number(pos) + " / " + QString::number(pCore->projectManager()->currentTimeline()->duration())); } } } void MainWindow::slotUpdateProjectDuration(int pos) { if (pCore->projectManager()->current()) { pCore->projectManager()->currentTimeline()->setDuration(pos); slotUpdateMousePosition(pCore->projectManager()->currentTimeline()->projectView()->getMousePos()); } } void MainWindow::slotUpdateDocumentState(bool modified) { setWindowTitle(pCore->projectManager()->current()->description()); setWindowModified(modified); m_saveAction->setEnabled(modified); } void MainWindow::connectDocument() { KdenliveDoc *project = pCore->projectManager()->current(); Timeline *trackView = pCore->projectManager()->currentTimeline(); connect(project, SIGNAL(startAutoSave()), pCore->projectManager(), SLOT(slotStartAutoSave())); connect(project, SIGNAL(reloadEffects()), this, SLOT(slotReloadEffects())); KdenliveSettings::setProject_fps(project->fps()); m_clipMonitorDock->raise(); m_effectStack->transitionConfig()->updateProjectFormat(); connect(trackView, SIGNAL(configTrack()), this, SLOT(slotConfigTrack())); connect(trackView, SIGNAL(updateTracksInfo()), this, SLOT(slotUpdateTrackInfo())); connect(trackView, SIGNAL(mousePosition(int)), this, SLOT(slotUpdateMousePosition(int))); connect(pCore->producerQueue(), SIGNAL(infoProcessingFinished()), trackView->projectView(), SLOT(slotInfoProcessingFinished()), Qt::DirectConnection); connect(trackView->projectView(), SIGNAL(importKeyframes(GraphicsRectItem,QString,QString)), this, SLOT(slotProcessImportKeyframes(GraphicsRectItem,QString,QString))); connect(m_projectMonitor, &Monitor::multitrackView, trackView, &Timeline::slotMultitrackView); connect(m_projectMonitor, SIGNAL(renderPosition(int)), trackView, SLOT(moveCursorPos(int))); connect(m_projectMonitor, SIGNAL(zoneUpdated(QPoint)), trackView, SLOT(slotSetZone(QPoint))); connect(m_projectMonitor, SIGNAL(zoneUpdated(QPoint)), project, SLOT(setModified())); connect(m_clipMonitor, SIGNAL(zoneUpdated(QPoint)), project, SLOT(setModified())); connect(project, SIGNAL(docModified(bool)), this, SLOT(slotUpdateDocumentState(bool))); connect(trackView->projectView(), SIGNAL(guidesUpdated()), this, SLOT(slotGuidesUpdated())); connect(project, SIGNAL(saveTimelinePreview(QString)), trackView, SLOT(slotSaveTimelinePreview(QString))); connect(trackView, SIGNAL(showTrackEffects(int,TrackInfo)), this, SLOT(slotTrackSelected(int,TrackInfo))); connect(trackView->projectView(), SIGNAL(clipItemSelected(ClipItem*,bool,bool)), this, SLOT(slotTimelineClipSelected(ClipItem*,bool,bool)), Qt::DirectConnection); connect(trackView->projectView(), &CustomTrackView::setActiveKeyframe, m_effectStack, &EffectStackView2::setActiveKeyframe); connect(trackView->projectView(), SIGNAL(transitionItemSelected(Transition*,int,QPoint,bool)), m_effectStack, SLOT(slotTransitionItemSelected(Transition*,int,QPoint,bool)), Qt::DirectConnection); connect(trackView->projectView(), SIGNAL(transitionItemSelected(Transition*,int,QPoint,bool)), this, SLOT(slotActivateTransitionView(Transition*))); connect(trackView->projectView(), SIGNAL(zoomIn()), this, SLOT(slotZoomIn())); connect(trackView->projectView(), SIGNAL(zoomOut()), this, SLOT(slotZoomOut())); connect(trackView, SIGNAL(setZoom(int)), this, SLOT(slotSetZoom(int))); connect(trackView->projectView(), SIGNAL(displayMessage(QString,MessageType)), m_messageLabel, SLOT(setMessage(QString,MessageType))); connect(pCore->bin(), SIGNAL(clipNameChanged(QString)), trackView->projectView(), SLOT(clipNameChanged(QString))); connect(pCore->bin(), SIGNAL(displayMessage(QString,MessageType)), m_messageLabel, SLOT(setMessage(QString,MessageType))); connect(trackView->projectView(), SIGNAL(showClipFrame(const QString&,int)), pCore->bin(), SLOT(selectClipById(const QString&,int))); connect(trackView->projectView(), SIGNAL(playMonitor()), m_projectMonitor, SLOT(slotPlay())); connect(m_projectMonitor, &Monitor::addEffect, trackView->projectView(), &CustomTrackView::slotAddEffectToCurrentItem); connect(trackView->projectView(), SIGNAL(transitionItemSelected(Transition*,int,QPoint,bool)), m_projectMonitor, SLOT(slotSetSelectedClip(Transition*))); connect(pCore->bin(), SIGNAL(gotFilterJobResults(QString,int,int,stringMap,stringMap)), trackView->projectView(), SLOT(slotGotFilterJobResults(QString,int,int,stringMap,stringMap))); //TODO //connect(m_projectList, SIGNAL(addMarkers(QString,QList)), trackView->projectView(), SLOT(slotAddClipMarker(QString,QList))); // Effect stack signals connect(m_effectStack, SIGNAL(updateEffect(ClipItem*,int,QDomElement,QDomElement,int,bool)), trackView->projectView(), SLOT(slotUpdateClipEffect(ClipItem*,int,QDomElement,QDomElement,int,bool))); connect(m_effectStack, SIGNAL(updateClipRegion(ClipItem*,int,QString)), trackView->projectView(), SLOT(slotUpdateClipRegion(ClipItem*,int,QString))); connect(m_effectStack, SIGNAL(removeEffect(ClipItem*,int,QDomElement)), trackView->projectView(), SLOT(slotDeleteEffect(ClipItem*,int,QDomElement))); connect(m_effectStack, SIGNAL(removeEffectGroup(ClipItem*,int,QDomDocument)), trackView->projectView(), SLOT(slotDeleteEffectGroup(ClipItem*,int,QDomDocument))); connect(m_effectStack, SIGNAL(addEffect(ClipItem*,QDomElement,int)), trackView->projectView(), SLOT(slotAddEffect(ClipItem*,QDomElement,int))); connect(m_effectStack, SIGNAL(changeEffectState(ClipItem*,int,QList,bool)), trackView->projectView(), SLOT(slotChangeEffectState(ClipItem*,int,QList,bool))); connect(m_effectStack, SIGNAL(changeEffectPosition(ClipItem*,int,QList,int)), trackView->projectView(), SLOT(slotChangeEffectPosition(ClipItem*,int,QList,int))); connect(m_effectStack, SIGNAL(refreshEffectStack(ClipItem*)), trackView->projectView(), SLOT(slotRefreshEffects(ClipItem*))); connect(m_effectStack, SIGNAL(seekTimeline(int)), trackView->projectView(), SLOT(seekCursorPos(int))); connect(m_effectStack, SIGNAL(importClipKeyframes(GraphicsRectItem, ItemInfo, QDomElement, QMap)), trackView->projectView(), SLOT(slotImportClipKeyframes(GraphicsRectItem, ItemInfo, QDomElement, QMap))); // Transition config signals connect(m_effectStack->transitionConfig(), SIGNAL(transitionUpdated(Transition*,QDomElement)), trackView->projectView() , SLOT(slotTransitionUpdated(Transition*,QDomElement))); connect(m_effectStack->transitionConfig(), SIGNAL(seekTimeline(int)), trackView->projectView() , SLOT(seekCursorPos(int))); connect(trackView->projectView(), SIGNAL(activateDocumentMonitor()), m_projectMonitor, SLOT(slotActivateMonitor())); connect(project, &KdenliveDoc::updateFps, trackView, &Timeline::updateProfile, Qt::DirectConnection); connect(trackView, SIGNAL(zoneMoved(int,int)), this, SLOT(slotZoneMoved(int,int))); trackView->projectView()->setContextMenu(m_timelineContextMenu, m_timelineContextClipMenu, m_timelineContextTransitionMenu, m_clipTypeGroup, static_cast(factory()->container(QStringLiteral("marker_menu"), this))); if (m_renderWidget) { slotCheckRenderStatus(); m_renderWidget->setProfile(project->mltProfile()); m_renderWidget->setGuides(pCore->projectManager()->currentTimeline()->projectView()->guidesData(), project->projectDuration()); m_renderWidget->setDocumentPath(project->projectFolder().path() + QDir::separator()); m_renderWidget->setRenderProfile(project->getRenderProperties()); } m_zoomSlider->setValue(project->zoom().x()); m_commandStack->setActiveStack(project->commandStack()); KdenliveSettings::setProject_display_ratio(project->dar()); setWindowTitle(project->description()); setWindowModified(project->isModified()); m_saveAction->setEnabled(project->isModified()); m_normalEditTool->setChecked(true); connect(m_projectMonitor, SIGNAL(durationChanged(int)), this, SLOT(slotUpdateProjectDuration(int))); pCore->monitorManager()->setDocument(project); trackView->updateProfile(false); if (m_recMonitor) { m_recMonitor->slotUpdateCaptureFolder(project->projectFolder().path() + QDir::separator()); } //Update the mouse position display so it will display in DF/NDF format by default based on the project setting. slotUpdateMousePosition(0); // Update guides info in render widget slotGuidesUpdated(); // Make sure monitor is visible so that it is painted black on startup //show(); //pCore->monitorManager()->activateMonitor(Kdenlive::ClipMonitor, true); // set tool to select tool m_buttonSelectTool->setChecked(true); connect(m_projectMonitorDock, SIGNAL(visibilityChanged(bool)), m_projectMonitor, SLOT(slotRefreshMonitor(bool)), Qt::UniqueConnection); connect(m_clipMonitorDock, SIGNAL(visibilityChanged(bool)), m_clipMonitor, SLOT(slotRefreshMonitor(bool)), Qt::UniqueConnection); } void MainWindow::slotZoneMoved(int start, int end) { pCore->projectManager()->current()->setZone(start, end); m_projectMonitor->slotZoneMoved(start, end); } void MainWindow::slotGuidesUpdated() { QMap guidesData = pCore->projectManager()->currentTimeline()->projectView()->guidesData(); if (m_renderWidget) m_renderWidget->setGuides(guidesData, pCore->projectManager()->current()->projectDuration()); m_projectMonitor->setGuides(guidesData); } void MainWindow::slotEditKeys() { KShortcutsDialog dialog(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsAllowed, this); dialog.addCollection(actionCollection(), i18nc("general keyboard shortcuts", "General")); dialog.configure(); } void MainWindow::slotPreferences(int page, int option) { /* * An instance of your dialog could be already created and could be * cached, in which case you want to display the cached dialog * instead of creating another one */ if (m_stopmotion) { m_stopmotion->slotLive(false); } if (KConfigDialog::showDialog(QStringLiteral("settings"))) { KdenliveSettingsDialog* d = static_cast (KConfigDialog::exists(QStringLiteral("settings"))); if (page != -1) d->showPage(page, option); return; } // KConfigDialog didn't find an instance of this dialog, so lets // create it : // Get the mappable actions in localized form QMap actions; KActionCollection* collection = actionCollection(); QRegExp ampEx("&{1,1}"); foreach (const QString& action_name, m_actionNames) { QString action_text = collection->action(action_name)->text(); action_text.remove(ampEx); actions[action_text] = action_name; } KdenliveSettingsDialog* dialog = new KdenliveSettingsDialog(actions, m_gpuAllowed, this); connect(dialog, SIGNAL(settingsChanged(QString)), this, SLOT(updateConfiguration())); connect(dialog, SIGNAL(settingsChanged(QString)), SIGNAL(configurationChanged())); connect(dialog, SIGNAL(doResetProfile()), pCore->projectManager(), SLOT(slotResetProfiles())); connect(dialog, SIGNAL(restartKdenlive()), this, SLOT(slotRestart())); connect(dialog, SIGNAL(updateLibraryFolder()), pCore, SIGNAL(updateLibraryPath())); if (m_recMonitor) { connect(dialog, SIGNAL(updateCaptureFolder()), this, SLOT(slotUpdateCaptureFolder())); connect(dialog, SIGNAL(updateFullScreenGrab()), m_recMonitor, SLOT(slotUpdateFullScreenGrab())); } dialog->show(); if (page != -1) { dialog->showPage(page, option); } } void MainWindow::slotRestart() { m_exitCode = EXIT_RESTART; QApplication::closeAllWindows(); } void MainWindow::closeEvent(QCloseEvent* event) { KXmlGuiWindow::closeEvent(event); if (event->isAccepted()) { QApplication::exit(m_exitCode); return; } } void MainWindow::slotUpdateCaptureFolder() { if (m_recMonitor) { if (pCore->projectManager()->current()) m_recMonitor->slotUpdateCaptureFolder(pCore->projectManager()->current()->projectFolder().path() + QDir::separator()); else m_recMonitor->slotUpdateCaptureFolder(KdenliveSettings::defaultprojectfolder()); } } void MainWindow::updateConfiguration() { //TODO: we should apply settings to all projects, not only the current one if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->refresh(); pCore->projectManager()->currentTimeline()->projectView()->checkAutoScroll(); pCore->projectManager()->currentTimeline()->checkTrackHeight(); } m_buttonAudioThumbs->setChecked(KdenliveSettings::audiothumbnails()); m_buttonVideoThumbs->setChecked(KdenliveSettings::videothumbnails()); m_buttonShowMarkers->setChecked(KdenliveSettings::showmarkers()); slotSwitchSplitAudio(KdenliveSettings::splitaudio()); // Update list of transcoding profiles buildDynamicActions(); loadClipActions(); } void MainWindow::slotSwitchSplitAudio(bool enable) { KdenliveSettings::setSplitaudio(enable); m_buttonAutomaticSplitAudio->setChecked(KdenliveSettings::splitaudio()); if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->updateHeaders(); } } void MainWindow::slotSwitchVideoThumbs() { KdenliveSettings::setVideothumbnails(!KdenliveSettings::videothumbnails()); if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->slotUpdateAllThumbs(); } m_buttonVideoThumbs->setChecked(KdenliveSettings::videothumbnails()); } void MainWindow::slotSwitchAudioThumbs() { KdenliveSettings::setAudiothumbnails(!KdenliveSettings::audiothumbnails()); pCore->binController()->checkAudioThumbs(); if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->refresh(); pCore->projectManager()->currentTimeline()->projectView()->checkAutoScroll(); } m_buttonAudioThumbs->setChecked(KdenliveSettings::audiothumbnails()); } void MainWindow::slotSwitchMarkersComments() { KdenliveSettings::setShowmarkers(!KdenliveSettings::showmarkers()); if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->refresh(); } m_buttonShowMarkers->setChecked(KdenliveSettings::showmarkers()); } void MainWindow::slotSwitchSnap() { KdenliveSettings::setSnaptopoints(!KdenliveSettings::snaptopoints()); m_buttonSnap->setChecked(KdenliveSettings::snaptopoints()); } void MainWindow::slotDeleteItem() { if (QApplication::focusWidget() && QApplication::focusWidget()->parentWidget() && QApplication::focusWidget()->parentWidget() == pCore->bin()) { pCore->bin()->slotDeleteClip(); } else { QWidget *widget = QApplication::focusWidget(); while (widget && widget != this) { if (widget == m_effectStackDock) { m_effectStack->deleteCurrentEffect(); return; } widget = widget->parentWidget(); } // effect stack has no focus if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->deleteSelectedClips(); } } } void MainWindow::slotAddClipMarker() { KdenliveDoc *project = pCore->projectManager()->current(); ClipController *clip = NULL; GenTime pos; if (m_projectMonitor->isActive()) { if (pCore->projectManager()->currentTimeline()) { ClipItem *item = pCore->projectManager()->currentTimeline()->projectView()->getActiveClipUnderCursor(); if (item) { pos = GenTime((int)((m_projectMonitor->position() - item->startPos() + item->cropStart()).frames(project->fps()) * item->speed() + 0.5), project->fps()); clip = pCore->binController()->getController(item->getBinId()); } } } else { clip = m_clipMonitor->currentController(); pos = m_clipMonitor->position(); } if (!clip) { m_messageLabel->setMessage(i18n("Cannot find clip to add marker"), ErrorMessage); return; } QString id = clip->clipId(); CommentedTime marker(pos, i18n("Marker"), KdenliveSettings::default_marker_type()); QPointer d = new MarkerDialog(clip, marker, project->timecode(), i18n("Add Marker"), this); if (d->exec() == QDialog::Accepted) { pCore->bin()->slotAddClipMarker(id, QList () << d->newMarker()); QString hash = clip->getClipHash(); if (!hash.isEmpty()) project->cacheImage(hash + '#' + QString::number(d->newMarker().time().frames(project->fps())), d->markerImage()); } delete d; } void MainWindow::slotDeleteClipMarker(bool allowGuideDeletion) { ClipController *clip = NULL; GenTime pos; if (m_projectMonitor->isActive()) { if (pCore->projectManager()->currentTimeline()) { ClipItem *item = pCore->projectManager()->currentTimeline()->projectView()->getActiveClipUnderCursor(); if (item) { pos = (m_projectMonitor->position() - item->startPos() + item->cropStart()) / item->speed(); clip = pCore->binController()->getController(item->getBinId()); } } } else { clip = m_clipMonitor->currentController(); pos = m_clipMonitor->position(); } if (!clip) { m_messageLabel->setMessage(i18n("Cannot find clip to remove marker"), ErrorMessage); return; } QString id = clip->clipId(); QString comment = clip->markerComment(pos); if (comment.isEmpty()) { if (allowGuideDeletion && m_projectMonitor->isActive()) { slotDeleteGuide(); } else m_messageLabel->setMessage(i18n("No marker found at cursor time"), ErrorMessage); return; } pCore->bin()->deleteClipMarker(comment, id, pos); } void MainWindow::slotDeleteAllClipMarkers() { ClipController *clip = NULL; if (m_projectMonitor->isActive()) { if (pCore->projectManager()->currentTimeline()) { ClipItem *item = pCore->projectManager()->currentTimeline()->projectView()->getActiveClipUnderCursor(); if (item) { clip = pCore->binController()->getController(item->getBinId()); } } } else { clip = m_clipMonitor->currentController(); } if (!clip) { m_messageLabel->setMessage(i18n("Cannot find clip to remove marker"), ErrorMessage); return; } pCore->bin()->deleteAllClipMarkers(clip->clipId()); } void MainWindow::slotEditClipMarker() { ClipController *clip = NULL; GenTime pos; if (m_projectMonitor->isActive()) { if (pCore->projectManager()->currentTimeline()) { ClipItem *item = pCore->projectManager()->currentTimeline()->projectView()->getActiveClipUnderCursor(); if (item) { pos = (m_projectMonitor->position() - item->startPos() + item->cropStart()) / item->speed(); clip = pCore->binController()->getController(item->getBinId()); } } } else { clip = m_clipMonitor->currentController(); pos = m_clipMonitor->position(); } if (!clip) { m_messageLabel->setMessage(i18n("Cannot find clip to remove marker"), ErrorMessage); return; } QString id = clip->clipId(); CommentedTime oldMarker = clip->markerAt(pos); if (oldMarker == CommentedTime()) { m_messageLabel->setMessage(i18n("No marker found at cursor time"), ErrorMessage); return; } QPointer d = new MarkerDialog(clip, oldMarker, pCore->projectManager()->current()->timecode(), i18n("Edit Marker"), this); if (d->exec() == QDialog::Accepted) { pCore->bin()->slotAddClipMarker(id, QList () <newMarker()); QString hash = clip->getClipHash(); if (!hash.isEmpty()) pCore->projectManager()->current()->cacheImage(hash + '#' + QString::number(d->newMarker().time().frames(pCore->projectManager()->current()->fps())), d->markerImage()); if (d->newMarker().time() != pos) { // remove old marker oldMarker.setMarkerType(-1); pCore->bin()->slotAddClipMarker(id, QList () <projectManager()->currentTimeline() || !pCore->projectManager()->current()) return; if (m_clipMonitor->isActive()) { ClipController *clip = m_clipMonitor->currentController(); GenTime pos = m_clipMonitor->position(); if (!clip) { m_messageLabel->setMessage(i18n("Cannot find clip to add marker"), ErrorMessage); return; } //TODO: allow user to set default marker category CommentedTime marker(pos, pCore->projectManager()->current()->timecode().getDisplayTimecode(pos, false), KdenliveSettings::default_marker_type()); pCore->bin()->slotAddClipMarker(clip->clipId(), QList () <projectManager()->currentTimeline()->projectView()->slotAddGuide(false); } } void MainWindow::slotAddGuide() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->slotAddGuide(); } void MainWindow::slotInsertSpace() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->slotInsertSpace(); } void MainWindow::slotRemoveSpace() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->slotRemoveSpace(); } void MainWindow::slotInsertTrack() { pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); if (pCore->projectManager()->currentTimeline()) { int ix = pCore->projectManager()->currentTimeline()->projectView()->selectedTrack(); pCore->projectManager()->currentTimeline()->projectView()->slotInsertTrack(ix ); } if (pCore->projectManager()->current()) { m_effectStack->transitionConfig()->updateProjectFormat(); } } void MainWindow::slotDeleteTrack() { pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); if (pCore->projectManager()->currentTimeline()) { int ix = pCore->projectManager()->currentTimeline()->projectView()->selectedTrack(); pCore->projectManager()->currentTimeline()->projectView()->slotDeleteTrack(ix); } if (pCore->projectManager()->current()) { m_effectStack->transitionConfig()->updateProjectFormat(); } } void MainWindow::slotConfigTrack() { pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); if (pCore->projectManager()->currentTimeline()) { int ix = pCore->projectManager()->currentTimeline()->projectView()->selectedTrack(); pCore->projectManager()->currentTimeline()->projectView()->slotConfigTracks(ix); } if (pCore->projectManager()->current()) m_effectStack->transitionConfig()->updateProjectFormat(); } void MainWindow::slotSelectTrack() { pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->slotSelectClipsInTrack(); } } void MainWindow::slotSelectAllTracks() { pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->slotSelectAllClips(); } void MainWindow::slotEditGuide(int pos, QString text) { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->slotEditGuide(pos, text); } void MainWindow::slotDeleteGuide() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->slotDeleteGuide(); } void MainWindow::slotDeleteAllGuides() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->slotDeleteAllGuides(); } void MainWindow::slotCutTimelineClip() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->cutSelectedClips(); } void MainWindow::slotInsertClipOverwrite() { if (pCore->projectManager()->currentTimeline()) { QPoint binZone = m_clipMonitor->getZoneInfo(); pCore->projectManager()->currentTimeline()->projectView()->insertZone(TimelineMode::OverwriteEdit, m_clipMonitor->activeClipId(), binZone); } } void MainWindow::slotInsertClipInsert() { if (pCore->projectManager()->currentTimeline()) { QPoint binZone = m_clipMonitor->getZoneInfo(); pCore->projectManager()->currentTimeline()->projectView()->insertZone(TimelineMode::InsertEdit, m_clipMonitor->activeClipId(), binZone); } } void MainWindow::slotExtractZone() { if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->extractZone(QPoint(), true); } } void MainWindow::slotLiftZone() { if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->extractZone(QPoint(),false); } } +void MainWindow::slotPreviewRender() +{ + if (pCore->projectManager()->current()) { + pCore->projectManager()->current()->previewRender(); + } +} + void MainWindow::slotSelectTimelineClip() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->selectClip(true); } void MainWindow::slotSelectTimelineTransition() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->selectTransition(true); } void MainWindow::slotDeselectTimelineClip() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->selectClip(false, true); } void MainWindow::slotDeselectTimelineTransition() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->selectTransition(false, true); } void MainWindow::slotSelectAddTimelineClip() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->selectClip(true, true); } void MainWindow::slotSelectAddTimelineTransition() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->selectTransition(true, true); } void MainWindow::slotGroupClips() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->groupClips(); } void MainWindow::slotUnGroupClips() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->groupClips(false); } void MainWindow::slotEditItemDuration() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->editItemDuration(); } void MainWindow::slotAddProjectClip(const QUrl &url) { pCore->bin()->droppedUrls(QList() << url); } void MainWindow::slotAddProjectClipList(const QList &urls) { pCore->bin()->droppedUrls(urls); } void MainWindow::slotAddTransition(QAction *result) { if (!result) return; QStringList info = result->data().toStringList(); if (info.isEmpty() || info.count() < 2) return; QDomElement transition = transitions.getEffectByTag(info.at(0), info.at(1)); if (pCore->projectManager()->currentTimeline() && !transition.isNull()) { pCore->projectManager()->currentTimeline()->projectView()->slotAddTransitionToSelectedClips(transition.cloneNode().toElement()); } } void MainWindow::slotAddVideoEffect(QAction *result) { if (!result) { return; } const int VideoEffect = 1; const int AudioEffect = 2; QStringList info = result->data().toStringList(); if (info.isEmpty() || info.size() < 3) { return; } QDomElement effect ; if (info.last() == QString::number((int) VideoEffect)) { effect = videoEffects.getEffectByTag(info.at(0), info.at(1)); } else if (info.last() == QString::number((int) AudioEffect)) { effect = audioEffects.getEffectByTag(info.at(0), info.at(1)); } else { effect = customEffects.getEffectByTag(info.at(0), info.at(1)); } if (!effect.isNull()) { slotAddEffect(effect); } else { m_messageLabel->setMessage(i18n("Cannot find effect %1 / %2", info.at(0), info.at(1)), ErrorMessage); } } void MainWindow::slotZoomIn() { m_zoomSlider->setValue(m_zoomSlider->value() - 1); slotShowZoomSliderToolTip(); } void MainWindow::slotZoomOut() { m_zoomSlider->setValue(m_zoomSlider->value() + 1); slotShowZoomSliderToolTip(); } void MainWindow::slotFitZoom() { if (pCore->projectManager()->currentTimeline()) { m_zoomSlider->setValue(pCore->projectManager()->currentTimeline()->fitZoom()); // Make sure to reset scroll bar to start pCore->projectManager()->currentTimeline()->projectView()->scrollToStart(); } } void MainWindow::slotSetZoom(int value) { value = qMax(m_zoomSlider->minimum(), value); value = qMin(m_zoomSlider->maximum(), value); if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->slotChangeZoom(value); } m_zoomOut->setEnabled(value < m_zoomSlider->maximum()); m_zoomIn->setEnabled(value > m_zoomSlider->minimum()); slotUpdateZoomSliderToolTip(value); m_zoomSlider->blockSignals(true); m_zoomSlider->setValue(value); m_zoomSlider->blockSignals(false); } void MainWindow::slotShowZoomSliderToolTip(int zoomlevel) { if (zoomlevel != -1) { slotUpdateZoomSliderToolTip(zoomlevel); } QPoint global = m_zoomSlider->rect().topLeft(); global.ry() += m_zoomSlider->height() / 2; QHelpEvent toolTipEvent(QEvent::ToolTip, QPoint(0, 0), m_zoomSlider->mapToGlobal(global)); QApplication::sendEvent(m_zoomSlider, &toolTipEvent); } void MainWindow::slotUpdateZoomSliderToolTip(int zoomlevel) { m_zoomSlider->setToolTip(i18n("Zoom Level: %1/13", (13 - zoomlevel))); } void MainWindow::slotGotProgressInfo(const QString &message, int progress, MessageType type) { if (type == DefaultMessage) { m_statusProgressBar->setValue(progress); } - m_messageLabel->setMessage(message, type); + m_messageLabel->setMessage(progress < 100 ? message : QString(), type); if (progress >= 0) { if (type == DefaultMessage) { - m_statusProgressBar->setVisible(true); + m_statusProgressBar->setVisible(progress < 100); } } else { m_statusProgressBar->setVisible(false); } } void MainWindow::customEvent(QEvent* e) { if (e->type() == QEvent::User) m_messageLabel->setMessage(static_cast (e)->message(), MltError); } void MainWindow::slotTimelineClipSelected(ClipItem* item, bool reloadStack, bool raise) { m_effectStack->slotClipItemSelected(item, m_projectMonitor, reloadStack); m_projectMonitor->slotSetSelectedClip(item); if (raise) { m_effectStack->raiseWindow(m_effectStackDock); } } void MainWindow::slotTrackSelected(int index, const TrackInfo &info, bool raise) { m_effectStack->slotTrackItemSelected(index, info, m_projectMonitor); if (raise) { m_effectStack->raiseWindow(m_effectStackDock); } } void MainWindow::slotActivateTransitionView(Transition *transition) { if (transition) m_effectStack->raiseWindow(m_effectStackDock); } void MainWindow::slotSnapRewind() { if (m_projectMonitor->isActive()) { if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->slotSeekToPreviousSnap(); } } else { m_clipMonitor->slotSeekToPreviousSnap(); } } void MainWindow::slotSnapForward() { if (m_projectMonitor->isActive()) { if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->slotSeekToNextSnap(); } } else { m_clipMonitor->slotSeekToNextSnap(); } } void MainWindow::slotClipStart() { if (m_projectMonitor->isActive()) { if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->clipStart(); } } } void MainWindow::slotClipEnd() { if (m_projectMonitor->isActive()) { if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->clipEnd(); } } } void MainWindow::slotChangeTool(QAction * action) { if (action == m_buttonSelectTool) slotSetTool(SelectTool); else if (action == m_buttonRazorTool) slotSetTool(RazorTool); else if (action == m_buttonSpacerTool) slotSetTool(SpacerTool); } void MainWindow::slotChangeEdit(QAction * action) { if (!pCore->projectManager()->currentTimeline()) return; if (action == m_overwriteEditTool) pCore->projectManager()->currentTimeline()->projectView()->setEditMode(TimelineMode::OverwriteEdit); else if (action == m_insertEditTool) pCore->projectManager()->currentTimeline()->projectView()->setEditMode(TimelineMode::InsertEdit); else pCore->projectManager()->currentTimeline()->projectView()->setEditMode(TimelineMode::NormalEdit); } void MainWindow::slotSetTool(ProjectTool tool) { if (pCore->projectManager()->current() && pCore->projectManager()->currentTimeline()) { //pCore->projectManager()->current()->setTool(tool); QString message; switch (tool) { case SpacerTool: message = i18n("Ctrl + click to use spacer on current track only"); break; case RazorTool: message = i18n("Click on a clip to cut it, Shift + move to preview cut frame"); break; default: message = i18n("Shift + click to create a selection rectangle, Ctrl + click to add an item to selection"); break; } m_messageLabel->setMessage(message, InformationMessage); pCore->projectManager()->currentTimeline()->projectView()->setTool(tool); } } void MainWindow::slotCopy() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->copyClip(); } void MainWindow::slotPaste() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->pasteClip(); } void MainWindow::slotPasteEffects() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->pasteClipEffects(); } void MainWindow::slotClipInTimeline(const QString &clipId) { if (pCore->projectManager()->currentTimeline()) { QList matching = pCore->projectManager()->currentTimeline()->projectView()->findId(clipId); QMenu *inTimelineMenu = static_cast(factory()->container(QStringLiteral("clip_in_timeline"), this)); QList actionList; for (int i = 0; i < matching.count(); ++i) { QString track = pCore->projectManager()->currentTimeline()->getTrackInfo(matching.at(i).track).trackName; QString start = pCore->projectManager()->current()->timecode().getTimecode(matching.at(i).startPos); int j = 0; QAction *a = new QAction(track + ": " + start, inTimelineMenu); a->setData(QStringList() << track << start); connect(a, SIGNAL(triggered()), this, SLOT(slotSelectClipInTimeline())); while (j < actionList.count()) { if (actionList.at(j)->text() > a->text()) break; j++; } actionList.insert(j, a); } QList list = inTimelineMenu->actions(); unplugActionList("timeline_occurences"); qDeleteAll(list); plugActionList("timeline_occurences", actionList); if (actionList.isEmpty()) { inTimelineMenu->setEnabled(false); } else { inTimelineMenu->setEnabled(true); } } } void MainWindow::slotClipInProjectTree() { if (pCore->projectManager()->currentTimeline()) { int pos = -1; QPoint zone; const QString selectedId = pCore->projectManager()->currentTimeline()->projectView()->getClipUnderCursor(&pos, &zone); if (selectedId.isEmpty()) { return; } m_projectBinDock->raise(); pCore->bin()->selectClipById(selectedId, pos, zone); if (m_projectMonitor->isActive()) { slotSwitchMonitors(); } } } void MainWindow::slotSelectClipInTimeline() { if (pCore->projectManager()->currentTimeline()) { QAction *action = qobject_cast(sender()); QStringList data = action->data().toStringList(); pCore->projectManager()->currentTimeline()->projectView()->selectFound(data.at(0), data.at(1)); } } /** Gets called when the window gets hidden */ void MainWindow::hideEvent(QHideEvent */*event*/) { if (isMinimized() && pCore->monitorManager()) pCore->monitorManager()->pauseActiveMonitor(); } /*void MainWindow::slotSaveZone(Render *render, const QPoint &zone, DocClipBase *baseClip, QUrl path) { QPointer dialog = new QDialog(this); dialog->setWindowTitle("Save clip zone"); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); QVBoxLayout *mainLayout = new QVBoxLayout; dialog->setLayout(mainLayout); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); dialog->connect(buttonBox, SIGNAL(accepted()), dialog, SLOT(accept())); dialog->connect(buttonBox, SIGNAL(rejected()), dialog, SLOT(reject())); QLabel *label1 = new QLabel(i18n("Save clip zone as:"), this); if (path.isEmpty()) { QString tmppath = pCore->projectManager()->current()->projectFolder().path() + QDir::separator(); if (baseClip == NULL) { tmppath.append("untitled.mlt"); } else { tmppath.append((baseClip->name().isEmpty() ? baseClip->fileURL().fileName() : baseClip->name()) + '-' + QString::number(zone.x()).rightJustified(4, '0') + ".mlt"); } path = QUrl(tmppath); } KUrlRequester *url = new KUrlRequester(path, this); url->setFilter("video/mlt-playlist"); QLabel *label2 = new QLabel(i18n("Description:"), this); QLineEdit *edit = new QLineEdit(this); mainLayout->addWidget(label1); mainLayout->addWidget(url); mainLayout->addWidget(label2); mainLayout->addWidget(edit); mainLayout->addWidget(buttonBox); if (dialog->exec() == QDialog::Accepted) { if (QFile::exists(url->url().path())) { if (KMessageBox::questionYesNo(this, i18n("File %1 already exists.\nDo you want to overwrite it?", url->url().path())) == KMessageBox::No) { slotSaveZone(render, zone, baseClip, url->url()); delete dialog; return; } } if (baseClip && !baseClip->fileURL().isEmpty()) { // create zone from clip url, so that we don't have problems with proxy clips QProcess p; QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); env.remove("MLT_PROFILE"); p.setProcessEnvironment(env); p.start(KdenliveSettings::rendererpath(), QStringList() << baseClip->fileURL().toLocalFile() << "in=" + QString::number(zone.x()) << "out=" + QString::number(zone.y()) << "-consumer" << "xml:" + url->url().path()); if (!p.waitForStarted(3000)) { KMessageBox::sorry(this, i18n("Cannot start MLT's renderer:\n%1", KdenliveSettings::rendererpath())); } else if (!p.waitForFinished(5000)) { KMessageBox::sorry(this, i18n("Timeout while creating xml output")); } } else render->saveZone(url->url(), edit->text(), zone); } delete dialog; }*/ void MainWindow::slotResizeItemStart() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->setInPoint(); } void MainWindow::slotResizeItemEnd() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->setOutPoint(); } int MainWindow::getNewStuff(const QString &configFile) { KNS3::Entry::List entries; QPointer dialog = new KNS3::DownloadDialog(configFile); if (dialog->exec()) entries = dialog->changedEntries(); foreach(const KNS3::Entry & entry, entries) { if (entry.status() == KNS3::Entry::Installed) qDebug() << "// Installed files: " << entry.installedFiles(); } delete dialog; return entries.size(); } void MainWindow::slotGetNewTitleStuff() { if (getNewStuff(QStringLiteral("kdenlive_titles.knsrc")) > 0) { // get project title path QString titlePath = pCore->projectManager()->current()->projectFolder().path(); titlePath.append(QStringLiteral("/titles/")); TitleWidget::refreshTitleTemplates(titlePath); } } void MainWindow::slotGetNewLumaStuff() { if (getNewStuff(QStringLiteral("kdenlive_wipes.knsrc")) > 0) { initEffects::refreshLumas(); pCore->projectManager()->currentTimeline()->projectView()->reloadTransitionLumas(); } } void MainWindow::slotGetNewRenderStuff() { if (getNewStuff(QStringLiteral("kdenlive_renderprofiles.knsrc")) > 0) if (m_renderWidget) m_renderWidget->reloadProfiles(); } void MainWindow::slotGetNewMltProfileStuff() { if (getNewStuff(QStringLiteral("kdenlive_projectprofiles.knsrc")) > 0) { // update the list of profiles in settings dialog KdenliveSettingsDialog* d = static_cast (KConfigDialog::exists(QStringLiteral("settings"))); if (d) d->checkProfile(); } } void MainWindow::slotAutoTransition() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->autoTransition(); } void MainWindow::slotSplitAudio() { if (pCore->projectManager()->currentTimeline()) pCore->projectManager()->currentTimeline()->projectView()->splitAudio(); } void MainWindow::slotSetAudioAlignReference() { if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->setAudioAlignReference(); } } void MainWindow::slotAlignAudio() { if (pCore->projectManager()->currentTimeline()) { pCore->projectManager()->currentTimeline()->projectView()->alignAudio(); } } void MainWindow::slotUpdateClipType(QAction *action) { if (pCore->projectManager()->currentTimeline()) { PlaylistState::ClipState state = (PlaylistState::ClipState) action->data().toInt(); pCore->projectManager()->currentTimeline()->projectView()->setClipType(state); } } void MainWindow::slotDvdWizard(const QString &url) { // We must stop the monitors since we create a new on in the dvd wizard QPointer w = new DvdWizard(pCore->monitorManager(), url, this); w->exec(); delete w; pCore->monitorManager()->activateMonitor(Kdenlive::ClipMonitor); } void MainWindow::slotShowTimeline(bool show) { if (show == false) { m_timelineState = saveState(); centralWidget()->setHidden(true); } else { centralWidget()->setHidden(false); restoreState(m_timelineState); } } void MainWindow::loadClipActions() { unplugActionList(QStringLiteral("add_effect")); plugActionList(QStringLiteral("add_effect"), m_effectsMenu->actions()); QList clipJobActions = getExtraActions("clipjobs"); unplugActionList("clip_jobs"); plugActionList("clip_jobs", clipJobActions); QList atcActions = getExtraActions(QStringLiteral("audiotranscoderslist")); unplugActionList(QStringLiteral("audio_transcoders_list")); plugActionList(QStringLiteral("audio_transcoders_list"), atcActions); QList tcActions = getExtraActions(QStringLiteral("transcoderslist")); unplugActionList(QStringLiteral("transcoders_list")); plugActionList(QStringLiteral("transcoders_list"), tcActions); } void MainWindow::loadDockActions() { QList list = kdenliveCategoryMap.value(QStringLiteral("interface"))->actions(); // Sort actions QMap sorted; QStringList sortedList; foreach(QAction *a, list) { sorted.insert(a->text(), a); sortedList << a->text(); } QList orderedList; sortedList.sort(Qt::CaseInsensitive); foreach(const QString &text, sortedList) { orderedList << sorted.value(text); } unplugActionList( "dock_actions" ); plugActionList( "dock_actions", orderedList); } void MainWindow::buildDynamicActions() { KActionCategory *ts = NULL; if (kdenliveCategoryMap.contains(QStringLiteral("clipjobs"))) { ts = kdenliveCategoryMap.take(QStringLiteral("clipjobs")); delete ts; } ts = new KActionCategory(i18n("Clip Jobs"), m_extraFactory->actionCollection()); Mlt::Profile profile; Mlt::Filter *filter; foreach(const QString &stab, QStringList() << "vidstab" << "videostab2" << "videostab") { filter = Mlt::Factory::filter(profile, (char*)stab.toUtf8().constData()); if (filter && filter->is_valid()) { QAction *action = new QAction(i18n("Stabilize") + " (" + stab + ")", m_extraFactory->actionCollection()); action->setData(QStringList() << QString::number((int) AbstractClipJob::FILTERCLIPJOB) << stab); ts->addAction(action->text(), action); connect(action, SIGNAL(triggered(bool)), pCore->bin(), SLOT(slotStartClipJob(bool))); delete filter; break; } delete filter; } filter = Mlt::Factory::filter(profile,(char*)"motion_est"); if (filter) { if (filter->is_valid()) { QAction *action = new QAction(i18n("Automatic scene split"), m_extraFactory->actionCollection()); QStringList stabJob; stabJob << QString::number((int) AbstractClipJob::FILTERCLIPJOB) << QStringLiteral("motion_est"); action->setData(stabJob); ts->addAction(action->text(), action); connect(action, SIGNAL(triggered(bool)), pCore->bin(), SLOT(slotStartClipJob(bool))); } delete filter; } if (KdenliveSettings::producerslist().contains(QStringLiteral("timewarp"))) { QAction *action = new QAction(i18n("Reverse clip"), m_extraFactory->actionCollection()); QStringList stabJob; stabJob << QString::number((int) AbstractClipJob::FILTERCLIPJOB) << QStringLiteral("timewarp"); action->setData(stabJob); ts->addAction(action->text(), action); connect(action, SIGNAL(triggered(bool)), pCore->bin(), SLOT(slotStartClipJob(bool))); } QAction *action = new QAction(i18n("Analyse keyframes"), m_extraFactory->actionCollection()); QStringList stabJob(QString::number((int) AbstractClipJob::ANALYSECLIPJOB)); action->setData(stabJob); ts->addAction(action->text(), action); connect(action, SIGNAL(triggered(bool)), pCore->bin(), SLOT(slotStartClipJob(bool))); kdenliveCategoryMap.insert(QStringLiteral("clipjobs"), ts); if (kdenliveCategoryMap.contains(QStringLiteral("transcoderslist"))) { ts = kdenliveCategoryMap.take(QStringLiteral("transcoderslist")); delete ts; } if (kdenliveCategoryMap.contains(QStringLiteral("audiotranscoderslist"))) { ts = kdenliveCategoryMap.take(QStringLiteral("audiotranscoderslist")); delete ts; } ts = new KActionCategory(i18n("Transcoders"), m_extraFactory->actionCollection()); KActionCategory *ats = new KActionCategory(i18n("Extract Audio"), m_extraFactory->actionCollection()); KSharedConfigPtr config = KSharedConfig::openConfig(QStandardPaths::locate(QStandardPaths::DataLocation, QStringLiteral("kdenlivetranscodingrc")), KConfig::CascadeConfig); KConfigGroup transConfig(config, "Transcoding"); // read the entries QMap< QString, QString > profiles = transConfig.entryMap(); QMapIterator i(profiles); while (i.hasNext()) { i.next(); QStringList data; data << QString::number((int) AbstractClipJob::TRANSCODEJOB); data << i.value().split(';'); QAction *a = new QAction(i.key(), m_extraFactory->actionCollection()); a->setData(data); if (data.count() > 1) a->setToolTip(data.at(1)); // slottranscode connect(a, SIGNAL(triggered(bool)), pCore->bin(), SLOT(slotStartClipJob(bool))); if (data.count() > 3 && data.at(3) == "audio") { // This is an audio transcoding action ats->addAction(i.key(), a); } else { ts->addAction(i.key(), a); } } kdenliveCategoryMap.insert(QStringLiteral("transcoderslist"), ts); kdenliveCategoryMap.insert(QStringLiteral("audiotranscoderslist"), ats); // Populate View menu with show / hide actions for dock widgets KActionCategory *guiActions = NULL; if (kdenliveCategoryMap.contains(QStringLiteral("interface"))) { guiActions = kdenliveCategoryMap.take(QStringLiteral("interface")); delete guiActions; } guiActions = new KActionCategory(i18n("Interface"), actionCollection()); QAction *showTimeline = new QAction(i18n("Timeline"), this); showTimeline->setCheckable(true); showTimeline->setChecked(true); connect(showTimeline, SIGNAL(triggered(bool)), this, SLOT(slotShowTimeline(bool))); guiActions->addAction(showTimeline->text(), showTimeline); actionCollection()->addAction(showTimeline->text(), showTimeline); QList docks = findChildren(); for (int i = 0; i < docks.count(); ++i) { QDockWidget* dock = docks.at(i); QAction * dockInformations = dock->toggleViewAction(); if (!dockInformations) continue; dockInformations->setChecked(!dock->isHidden()); guiActions->addAction(dockInformations->text(), dockInformations); } kdenliveCategoryMap.insert(QStringLiteral("interface"), guiActions); } QList MainWindow::getExtraActions(const QString &name) { if (!kdenliveCategoryMap.contains(name)) return QList (); return kdenliveCategoryMap.value(name)->actions(); } void MainWindow::slotTranscode(const QStringList &urls) { QString params; QString desc; QString condition; if (urls.isEmpty()) { QAction *action = qobject_cast(sender()); QStringList data = action->data().toStringList(); pCore->bin()->startClipJob(data); return; } if (urls.isEmpty()) { m_messageLabel->setMessage(i18n("No clip to transcode"), ErrorMessage); return; } ClipTranscode *d = new ClipTranscode(urls, params, QStringList(), desc); connect(d, SIGNAL(addClip(QUrl)), this, SLOT(slotAddProjectClip(QUrl))); d->show(); } void MainWindow::slotTranscodeClip() { QString allExtensions = ClipCreationDialog::getExtensions().join(QStringLiteral(" ")); const QString dialogFilter = i18n("All Supported Files") + '(' + allExtensions + ");;" + i18n("All Files") + "(*)"; QString clipFolder = KRecentDirs::dir(QStringLiteral(":KdenliveClipFolder")); QStringList urls = QFileDialog::getOpenFileNames(this, i18n("Files to transcode"), clipFolder, dialogFilter); if (urls.isEmpty()) return; slotTranscode(urls); } void MainWindow::slotSetDocumentRenderProfile(const QMap &props) { KdenliveDoc *project = pCore->projectManager()->current(); bool modified = false; QMapIterator i(props); while (i.hasNext()) { i.next(); if (project->getDocumentProperty(i.key()) == i.value()) continue; project->setDocumentProperty(i.key(), i.value()); modified = true; } if (modified) project->setModified(); } void MainWindow::slotPrepareRendering(bool scriptExport, bool zoneOnly, const QString &chapterFile) { KdenliveDoc *project = pCore->projectManager()->current(); if (m_renderWidget == NULL) return; QString scriptPath; QString playlistPath; QString mltSuffix(QStringLiteral(".mlt")); QList playlistPaths; QList trackNames; int tracksCount = 1; bool stemExport = m_renderWidget->isStemAudioExportEnabled(); if (scriptExport) { //QString scriptsFolder = project->projectFolder().path(QUrl::AddTrailingSlash) + "scripts/"; QString path = m_renderWidget->getFreeScriptName(project->url()); QPointer getUrl = new KUrlRequesterDialog(QUrl::fromLocalFile(path), i18n("Create Render Script"), this); getUrl->urlRequester()->setMode(KFile::File); if (getUrl->exec() == QDialog::Rejected) { delete getUrl; return; } scriptPath = getUrl->selectedUrl().path(); delete getUrl; QFile f(scriptPath); if (f.exists()) { if (KMessageBox::warningYesNo(this, i18n("Script file already exists. Do you want to overwrite it?")) != KMessageBox::Yes) return; } playlistPath = scriptPath; } else { QTemporaryFile temp(QDir::tempPath() + QStringLiteral("/kdenlive_rendering_XXXXXX.mlt")); temp.setAutoRemove(false); temp.open(); playlistPath = temp.fileName(); } QString playlistContent = pCore->projectManager()->projectSceneList(); if (!chapterFile.isEmpty()) { int in = 0; int out; if (!zoneOnly) out = (int) GenTime(project->projectDuration()).frames(project->fps()); else { in = pCore->projectManager()->currentTimeline()->inPoint(); out = pCore->projectManager()->currentTimeline()->outPoint(); } QDomDocument doc; QDomElement chapters = doc.createElement(QStringLiteral("chapters")); chapters.setAttribute(QStringLiteral("fps"), project->fps()); doc.appendChild(chapters); QMap guidesData = pCore->projectManager()->currentTimeline()->projectView()->guidesData(); QMapIterator g(guidesData); QLocale locale; while (g.hasNext()) { g.next(); int time = (int) GenTime(g.key()).frames(project->fps()); if (time >= in && time < out) { if (zoneOnly) time = time - in; QDomElement chapter = doc.createElement(QStringLiteral("chapter")); chapters.appendChild(chapter); chapter.setAttribute(QStringLiteral("title"), g.value()); chapter.setAttribute(QStringLiteral("time"), time); } } if (chapters.childNodes().count() > 0) { if (pCore->projectManager()->currentTimeline()->projectView()->hasGuide(out, 0) == -1) { // Always insert a guide in pos 0 QDomElement chapter = doc.createElement(QStringLiteral("chapter")); chapters.insertBefore(chapter, QDomNode()); chapter.setAttribute(QStringLiteral("title"), i18nc("the first in a list of chapters", "Start")); chapter.setAttribute(QStringLiteral("time"), QStringLiteral("0")); } // save chapters file QFile file(chapterFile); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qWarning() << "////// ERROR writing DVD CHAPTER file: " << chapterFile; } else { file.write(doc.toString().toUtf8()); if (file.error() != QFile::NoError) { qWarning() << "////// ERROR writing DVD CHAPTER file: " << chapterFile; } file.close(); } } } // check if audio export is selected bool exportAudio; if (m_renderWidget->automaticAudioExport()) { exportAudio = pCore->projectManager()->currentTimeline()->checkProjectAudio(); } else { exportAudio = m_renderWidget->selectedAudioExport(); } // Set playlist audio volume to 100% QDomDocument doc; doc.setContent(playlistContent); QDomElement tractor = doc.documentElement().firstChildElement(QStringLiteral("tractor")); if (!tractor.isNull()) { QDomNodeList props = tractor.elementsByTagName(QStringLiteral("property")); for (int i = 0; i < props.count(); ++i) { if (props.at(i).toElement().attribute(QStringLiteral("name")) == QLatin1String("meta.volume")) { props.at(i).firstChild().setNodeValue(QStringLiteral("1")); break; } } } // Add autoclose to playlists. QDomNodeList playlists = doc.elementsByTagName("playlist"); for (int i = 0; i < playlists.length();++i) { playlists.item(i).toElement().setAttribute("autoclose", 1); } // Do we want proxy rendering if (project->useProxy() && !m_renderWidget->proxyRendering()) { QString root = doc.documentElement().attribute(QStringLiteral("root")); // replace proxy clips with originals //TODO QMap proxies = pCore->binController()->getProxies(); QDomNodeList producers = doc.elementsByTagName(QStringLiteral("producer")); QString producerResource; QString producerService; QString suffix; QString prefix; for (int n = 0; n < producers.length(); ++n) { QDomElement e = producers.item(n).toElement(); producerResource = EffectsList::property(e, QStringLiteral("resource")); producerService = EffectsList::property(e, QStringLiteral("mlt_service")); if (producerResource.isEmpty()) { continue; } if (producerService == QLatin1String("timewarp")) { // slowmotion producer prefix = producerResource.section(':', 0, 0) + ":"; producerResource = producerResource.section(':', 1); } else { prefix.clear(); } if (producerService == QLatin1String("framebuffer")) { // slowmotion producer suffix = '?' + producerResource.section('?', 1); producerResource = producerResource.section('?', 0, 0); } else { suffix.clear(); } if (!producerResource.startsWith('/')) { producerResource.prepend(root + '/'); } if (!producerResource.isEmpty()) { if (proxies.contains(producerResource)) { QString replacementResource = proxies.value(producerResource); EffectsList::setProperty(e, QStringLiteral("resource"), prefix + replacementResource + suffix); if (producerService == QLatin1String("timewarp")) { EffectsList::setProperty(e, QStringLiteral("warp_resource"), replacementResource); } // We need to delete the "aspect_ratio" property because proxy clips // sometimes have different ratio than original clips EffectsList::removeProperty(e, QStringLiteral("aspect_ratio")); EffectsList::removeMetaProperties(e); } } } } QList docList; // check which audio tracks have to be exported if (stemExport) { Timeline* ct = pCore->projectManager()->currentTimeline(); int allTracksCount = ct->tracksCount(); // reset tracks count (tracks to be rendered) tracksCount = 0; // begin with track 1 (track zero is a hidden black track) for (int i = 1; i < allTracksCount; i++) { Track* track = ct->track(i); // add only tracks to render list that are not muted and have audio if (track && !track->info().isMute && track->hasAudio()) { QDomDocument docCopy = doc.cloneNode(true).toDocument(); QString trackName = track->info().trackName; // save track name trackNames << trackName; qDebug() << "Track-Name: " << trackName; // create stem export doc content QDomNodeList tracks = docCopy.elementsByTagName(QStringLiteral("track")); for (int j = 0; j < allTracksCount; j++) { if (j != i) { // mute other tracks tracks.at(j).toElement().setAttribute(QStringLiteral("hide"), QStringLiteral("both")); } } docList << docCopy; tracksCount++; } } } else { docList << doc; } // create full playlistPaths for (int i = 0; i < tracksCount; i++) { QString plPath(playlistPath); // add track number to path name if (stemExport) { plPath = plPath + "_" + QString(trackNames.at(i)).replace(QLatin1String(" "), QLatin1String("_")); } // add mlt suffix plPath += mltSuffix; playlistPaths << plPath; qDebug() << "playlistPath: " << plPath << endl; // Do save scenelist QFile file(plPath); if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { m_messageLabel->setMessage(i18n("Cannot write to file %1", plPath), ErrorMessage); return; } file.write(docList.at(i).toString().toUtf8()); if (file.error() != QFile::NoError) { m_messageLabel->setMessage(i18n("Cannot write to file %1", plPath), ErrorMessage); file.close(); return; } file.close(); } m_renderWidget->slotExport(scriptExport, pCore->projectManager()->currentTimeline()->inPoint(), pCore->projectManager()->currentTimeline()->outPoint(), project->metadata(), playlistPaths, trackNames, scriptPath, exportAudio); } void MainWindow::slotUpdateTimecodeFormat(int ix) { KdenliveSettings::setFrametimecode(ix == 1); m_clipMonitor->updateTimecodeFormat(); m_projectMonitor->updateTimecodeFormat(); m_effectStack->transitionConfig()->updateTimecodeFormat(); m_effectStack->updateTimecodeFormat(); pCore->bin()->updateTimecodeFormat(); //pCore->projectManager()->currentTimeline()->projectView()->clearSelection(); pCore->projectManager()->currentTimeline()->updateRuler(); slotUpdateMousePosition(pCore->projectManager()->currentTimeline()->projectView()->getMousePos()); } void MainWindow::slotRemoveFocus() { statusBar()->setFocus(); statusBar()->clearFocus(); } void MainWindow::slotShutdown() { pCore->projectManager()->current()->setModified(false); // Call shutdown QDBusConnectionInterface* interface = QDBusConnection::sessionBus().interface(); if (interface && interface->isServiceRegistered(QStringLiteral("org.kde.ksmserver"))) { QDBusInterface smserver(QStringLiteral("org.kde.ksmserver"), QStringLiteral("/KSMServer"), QStringLiteral("org.kde.KSMServerInterface")); smserver.call(QStringLiteral("logout"), 1, 2, 2); } else if (interface && interface->isServiceRegistered(QStringLiteral("org.gnome.SessionManager"))) { QDBusInterface smserver(QStringLiteral("org.gnome.SessionManager"), QStringLiteral("/org/gnome/SessionManager"), QStringLiteral("org.gnome.SessionManager")); smserver.call(QStringLiteral("Shutdown")); } } void MainWindow::slotUpdateTrackInfo() { m_effectStack->transitionConfig()->updateProjectFormat(); } void MainWindow::slotSwitchMonitors() { pCore->monitorManager()->slotSwitchMonitors(!m_clipMonitor->isActive()); if (m_projectMonitor->isActive()) pCore->projectManager()->currentTimeline()->projectView()->setFocus(); else pCore->bin()->focusBinView(); } void MainWindow::slotSwitchMonitorOverlay(QAction *action) { if (pCore->monitorManager()->isActive(Kdenlive::ClipMonitor)) { m_clipMonitor->switchMonitorInfo(action->data().toInt()); } else { m_projectMonitor->switchMonitorInfo(action->data().toInt()); } } void MainWindow::slotSwitchDropFrames(bool drop) { m_clipMonitor->switchDropFrames(drop); m_projectMonitor->switchDropFrames(drop); } void MainWindow::slotSetMonitorGamma(int gamma) { KdenliveSettings::setMonitor_gamma(gamma); m_clipMonitor->updateMonitorGamma(); m_projectMonitor->updateMonitorGamma(); } void MainWindow::slotInsertZoneToTree() { if (!m_clipMonitor->isActive() || m_clipMonitor->currentController() == NULL) return; QPoint info = m_clipMonitor->getZoneInfo(); pCore->bin()->slotAddClipCut(m_clipMonitor->activeClipId(), info.x(), info.y()); } void MainWindow::slotInsertZoneToTimeline() { if (pCore->projectManager()->currentTimeline() == NULL || m_clipMonitor->currentController() == NULL) return; QPoint info = m_clipMonitor->getZoneInfo(); pCore->projectManager()->currentTimeline()->projectView()->insertClipCut(m_clipMonitor->activeClipId(), info.x(), info.y()); } void MainWindow::slotMonitorRequestRenderFrame(bool request) { if (request) { m_projectMonitor->render->sendFrameForAnalysis = true; return; } else { for (int i = 0; i < m_gfxScopesList.count(); ++i) { if (m_gfxScopesList.at(i)->isVisible() && tabifiedDockWidgets(m_gfxScopesList.at(i)).isEmpty() && static_cast(m_gfxScopesList.at(i)->widget())->autoRefreshEnabled()) { request = true; break; } } } #ifdef DEBUG_MAINW qDebug() << "Any scope accepting new frames? " << request; #endif if (!request) { m_projectMonitor->render->sendFrameForAnalysis = false; } } void MainWindow::slotOpenStopmotion() { if (m_stopmotion == NULL) { m_stopmotion = new StopmotionWidget(pCore->monitorManager(), pCore->projectManager()->current()->projectFolder(), m_stopmotion_actions->actions(), this); //TODO //connect(m_stopmotion, SIGNAL(addOrUpdateSequence(QString)), m_projectList, SLOT(slotAddOrUpdateSequence(QString))); //for (int i = 0; i < m_gfxScopesList.count(); ++i) { // Check if we need the renderer to send a new frame for update /*if (!m_scopesList.at(i)->widget()->visibleRegion().isEmpty() && !(static_cast(m_scopesList.at(i)->widget())->autoRefreshEnabled())) request = true;*/ //connect(m_stopmotion, SIGNAL(gotFrame(QImage)), static_cast(m_gfxScopesList.at(i)->widget()), SLOT(slotRenderZoneUpdated(QImage))); //static_cast(m_scopesList.at(i)->widget())->slotMonitorCapture(); //} } m_stopmotion->show(); } void MainWindow::slotUpdateProxySettings() { KdenliveDoc *project = pCore->projectManager()->current(); if (m_renderWidget) m_renderWidget->updateProxyConfig(project->useProxy()); if (KdenliveSettings::enableproxy()) { QDir dir(pCore->projectManager()->current()->projectFolder().path()); dir.mkdir(QStringLiteral("proxy")); } pCore->bin()->refreshProxySettings(); } void MainWindow::slotArchiveProject() { QList list = pCore->binController()->getControllerList(); pCore->binController()->saveDocumentProperties(pCore->projectManager()->currentTimeline()->documentProperties(), pCore->projectManager()->current()->metadata(), pCore->projectManager()->currentTimeline()->projectView()->guidesData()); QDomDocument doc = pCore->projectManager()->current()->xmlSceneList(m_projectMonitor->sceneList()); QPointer d = new ArchiveWidget(pCore->projectManager()->current()->url().fileName(), doc, list, pCore->projectManager()->currentTimeline()->projectView()->extractTransitionsLumas(), this); if (d->exec()) { m_messageLabel->setMessage(i18n("Archiving project"), OperationCompletedMessage); } delete d; } void MainWindow::slotDownloadResources() { QString currentFolder; if (pCore->projectManager()->current()) currentFolder = pCore->projectManager()->current()->projectFolder().path(); else currentFolder = KdenliveSettings::defaultprojectfolder(); ResourceWidget *d = new ResourceWidget(currentFolder); connect(d, SIGNAL(addClip(QUrl)), this, SLOT(slotAddProjectClip(QUrl))); d->show(); } void MainWindow::slotProcessImportKeyframes(GraphicsRectItem type, const QString &tag, const QString& data) { if (type == AVWidget) { // This data should be sent to the effect stack m_effectStack->setKeyframes(tag, data); } else if (type == TransitionWidget) { // This data should be sent to the transition stack m_effectStack->transitionConfig()->setKeyframes(tag, data); } else { // Error } } void MainWindow::slotAlignPlayheadToMousePos() { pCore->monitorManager()->activateMonitor(Kdenlive::ProjectMonitor); pCore->projectManager()->currentTimeline()->projectView()->slotAlignPlayheadToMousePos(); } void MainWindow::triggerKey(QKeyEvent* ev) { // Hack: The QQuickWindow that displays fullscreen monitor does not integrate quith QActions. // so on keypress events we parse keys and check for shortcuts in all existing actions QKeySequence seq; // Remove the Num modifier or some shortcuts like "*" will not work if (ev->modifiers() != Qt::KeypadModifier) { seq = QKeySequence(ev->key() + ev->modifiers()); } else { seq = QKeySequence(ev->key()); } QList< KActionCollection * > collections = KActionCollection::allCollections(); for (int i = 0; i < collections.count(); ++i) { KActionCollection *coll = collections.at(i); foreach( QAction* tempAction, coll->actions()) { if (tempAction->shortcuts().contains(seq)) { // Trigger action tempAction->trigger(); ev->accept(); return; } } } } QDockWidget *MainWindow::addDock(const QString &title, const QString &objectName, QWidget* widget, Qt::DockWidgetArea area) { QDockWidget *dockWidget = new QDockWidget(title, this); dockWidget->setObjectName(objectName); dockWidget->setWidget(widget); addDockWidget(area, dockWidget); return dockWidget; } void MainWindow::slotUpdateMonitorOverlays(int id, int code) { QMenu *monitorOverlay = static_cast(factory()->container(QStringLiteral("monitor_config_overlay"), this)); if (!monitorOverlay) return; QList actions = monitorOverlay->actions(); foreach(QAction *ac, actions) { int data = ac->data().toInt(); if (data == 0x010) { ac->setEnabled(id == Kdenlive::ClipMonitor); } ac->setChecked(code & data); } } void MainWindow::slotChangeStyle(QAction *a) { QString style = a->data().toString(); KdenliveSettings::setWidgetstyle(style); doChangeStyle(); } void MainWindow::doChangeStyle() { QString newStyle = KdenliveSettings::widgetstyle(); if (newStyle.isEmpty() || newStyle == QStringLiteral("Default")) { newStyle = defaultStyle("Breeze"); } QApplication::setStyle(QStyleFactory::create(newStyle)); // Changing widget style resets color theme, so update color theme again ThemeManager::instance()->slotChangePalette(); } bool MainWindow::isTabbedWith(QDockWidget *widget, const QString & otherWidget) { #if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)) QList tabbed = tabifiedDockWidgets(widget); for (int i = 0; i < tabbed.count(); i++) { if (tabbed.at(i)->objectName() == otherWidget) return true; } return false; #else return false; #endif } #ifdef DEBUG_MAINW #undef DEBUG_MAINW #endif diff --git a/src/mainwindow.h b/src/mainwindow.h index 4d5622da6..28456da82 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -1,451 +1,452 @@ /*************************************************************************** * Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@kdenlive.org) * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * 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 * ***************************************************************************/ #ifndef MAINWINDOW_H #define MAINWINDOW_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kdenlivecore_export.h" #include "effectslist/effectslist.h" #include "gentime.h" #include "bin/bin.h" #include "definitions.h" #include "statusbarmessagelabel.h" #include "dvdwizard/dvdwizard.h" #include "stopmotion/stopmotion.h" class KdenliveDoc; class EffectsListView; class EffectStackView; class EffectStackView2; class AudioGraphSpectrum; class Monitor; class RecMonitor; class RenderWidget; class Render; class Transition; class KIconLoader; #define EXIT_RESTART (42) class /*KDENLIVECORE_EXPORT*/ MainWindow : public KXmlGuiWindow { Q_OBJECT public: /** @brief Initialises the main window. * @param MltPath (optional) path to MLT environment * @param Url (optional) file to open * @param clipsToLoad (optional) a comma separated list of clips to import in project * * If Url is present, it will be opened, otherwhise, if openlastproject is * set, latest project will be opened. If no file is open after trying this, * a default new file will be created. */ explicit MainWindow(const QString &MltPath = QString(), const QUrl &Url = QUrl(), const QString & clipsToLoad = QString(), QWidget *parent = 0); virtual ~MainWindow(); static EffectsList videoEffects; static EffectsList audioEffects; static EffectsList customEffects; static EffectsList transitions; /** @brief Cache for luma files thumbnails. */ static QMap m_lumacache; static QMap m_lumaFiles; /** @brief Adds an action to the action collection and stores the name. */ void addAction(const QString &name, QAction *action); /** @brief Adds an action to the action collection and stores the name. */ QAction *addAction(const QString &name, const QString &text, const QObject *receiver, const char *member, const QIcon &icon = QIcon(), const QKeySequence &shortcut = QKeySequence()); /** * @brief Adds a new dock widget to this window. * @param title title of the dock widget * @param objectName objectName of the dock widget (required for storing layouts) * @param widget widget to use in the dock * @param area area to which the dock should be added to * @returns the created dock widget */ QDockWidget *addDock(const QString &title, const QString &objectName, QWidget *widget, Qt::DockWidgetArea area = Qt::TopDockWidgetArea); // TODO make private again QTabWidget* m_timelineArea; StopmotionWidget *m_stopmotion; QUndoGroup *m_commandStack; EffectStackView2 *m_effectStack; QUndoView *m_undoView; StatusBarMessageLabel *m_messageLabel; /** @brief holds info about whether movit is available on this system */ bool m_gpuAllowed; int m_exitCode; QMap kdenliveCategoryMap; QList getExtraActions(const QString &name); /** @brief Returns true if docked widget is tabbed with another widget from its object name */ bool isTabbedWith(QDockWidget *widget, const QString & otherWidget); protected: /** @brief Closes the window. * @return false if the user presses "Cancel" on a confirmation dialog or * the operation requested (starting waiting jobs or saving file) fails, * true otherwise */ virtual bool queryClose(); virtual void closeEvent(QCloseEvent*); /** @brief Reports a message in the status bar when an error occurs. */ virtual void customEvent(QEvent *e); /** @brief Stops the active monitor when the window gets hidden. */ virtual void hideEvent(QHideEvent *e); /** @brief Saves the file and the window properties when saving the session. */ virtual void saveProperties(KConfigGroup &config); /** @brief Restores the window and the file when a session is loaded. */ virtual void readProperties(const KConfigGroup &config); virtual void saveNewToolbarConfig(); private: QProgressBar *m_statusProgressBar; /** @brief Sets up all the actions and attaches them to the collection. */ void setupActions(); KColorSchemeManager *m_colorschemes; QDockWidget *m_projectBinDock; QDockWidget *m_effectListDock; EffectsListView *m_effectList; QDockWidget *m_transitionListDock; EffectsListView *m_transitionList; QDockWidget *m_effectStackDock; QDockWidget *m_clipMonitorDock; Monitor *m_clipMonitor; QDockWidget *m_projectMonitorDock; Monitor *m_projectMonitor; QDockWidget *m_recMonitorDock; RecMonitor *m_recMonitor; AudioGraphSpectrum *m_audioSpectrum; QDockWidget *m_undoViewDock; KSelectAction *m_timeFormatButton; /** This list holds all the scopes used in Kdenlive, allowing to manage some global settings */ QList m_gfxScopesList; KActionCategory *m_effectActions; KActionCategory *m_transitionActions; QMenu *m_effectsMenu; QMenu *m_transitionsMenu; QMenu *m_timelineContextMenu; QMenu *m_timelineContextClipMenu; QMenu *m_timelineContextTransitionMenu; /** Actions used in the stopmotion widget */ KActionCategory *m_stopmotion_actions; /** Action names that can be used in the slotDoAction() slot, with their i18n() names */ QStringList m_actionNames; /** @brief Shortcut to remove the focus from any element. * * It allows to get out of e.g. text input fields and to press another * shortcut. */ QShortcut* m_shortcutRemoveFocus; RenderWidget *m_renderWidget; QList m_transitions; QAction *m_buttonAudioThumbs; QAction *m_buttonVideoThumbs; QAction *m_buttonShowMarkers; QAction *m_buttonFitZoom; QAction *m_buttonAutomaticSplitAudio; QAction *m_normalEditTool; QAction *m_overwriteEditTool; QAction *m_insertEditTool; QAction *m_buttonSelectTool; QAction *m_buttonRazorTool; QAction *m_buttonSpacerTool; QAction *m_buttonSnap; QAction *m_saveAction; QSlider *m_zoomSlider; QAction *m_zoomIn; QAction *m_zoomOut; QAction *m_loopZone; QAction *m_playZone; QAction *m_loopClip; QAction *m_proxyClip; QActionGroup *m_clipTypeGroup; QString m_theme; KIconLoader *m_iconLoader; void readOptions(); void saveOptions(); virtual bool event(QEvent *e); void loadGenerators(); /** @brief Instantiates a "Get Hot New Stuff" dialog. * @param configFile configuration file for KNewStuff * @return number of installed items */ int getNewStuff(const QString &configFile = QString()); QStringList m_pluginFileNames; QByteArray m_timelineState; void buildDynamicActions(); void loadClipActions(); QTime m_timer; KXMLGUIClient *m_extraFactory; bool m_themeInitialized; bool m_isDarkTheme; QListWidget *m_effectBasket; /** @brief Update statusbar stylesheet (in case of color theme change). */ void setStatusBarStyleSheet(const QPalette &p); /** @brief Update widget style. */ void doChangeStyle(); public slots: void slotGotProgressInfo(const QString &message, int progress, MessageType type = DefaultMessage); void slotReloadEffects(); Q_SCRIPTABLE void setRenderingProgress(const QString &url, int progress); Q_SCRIPTABLE void setRenderingFinished(const QString &url, int status, const QString &error); void slotSwitchVideoThumbs(); void slotSwitchAudioThumbs(); void slotPreferences(int page = -1, int option = -1); void connectDocument(); void slotTimelineClipSelected(ClipItem* item, bool reloadStack = true, bool raise = true); /** @brief Reload project profile in config dialog if changed. */ void slotRefreshProfiles(); private slots: /** @brief Shows the shortcut dialog. */ void slotEditKeys(); void loadDockActions(); /** @brief Reflects setting changes to the GUI. */ void updateConfiguration(); void slotConnectMonitors(); void slotUpdateClip(const QString &id, bool reload); void slotUpdateMousePosition(int pos); void slotUpdateProjectDuration(int pos); void slotAddEffect(const QDomElement &effect); void slotEditProjectSettings(); /** @brief Turns automatic splitting of audio and video on/off. */ void slotSwitchSplitAudio(bool enable); void slotSwitchMarkersComments(); void slotSwitchSnap(); void slotRenderProject(); void slotFullScreen(); /** @brief if modified is true adds "modified" to the caption and enables the save button. * (triggered by KdenliveDoc::setModified()) */ void slotUpdateDocumentState(bool modified); /** @brief Sets the timeline zoom slider to @param value. * * Also disables zoomIn and zoomOut actions if they cannot be used at the moment. */ void slotSetZoom(int value); /** @brief Decreases the timeline zoom level by 1. */ void slotZoomIn(); /** @brief Increases the timeline zoom level by 1. */ void slotZoomOut(); /** @brief Makes the timeline zoom level fit the timeline content. */ void slotFitZoom(); /** @brief Updates the zoom slider tooltip to fit @param zoomlevel. */ void slotUpdateZoomSliderToolTip(int zoomlevel); /** @brief Displays the zoom slider tooltip. * @param zoomlevel (optional) The zoom level to show in the tooltip. * * Adopted from Dolphin (src/statusbar/dolphinstatusbar.cpp) */ void slotShowZoomSliderToolTip(int zoomlevel = -1); /** @brief Deletes item in timeline, project tree or effect stack depending on focus. */ void slotDeleteItem(); void slotAddClipMarker(); void slotDeleteClipMarker(bool allowGuideDeletion = false); void slotDeleteAllClipMarkers(); void slotEditClipMarker(); /** @brief Adds marker or auide at the current position without showing the marker dialog. * * Adds a marker if clip monitor is active, otherwise a guide. * The comment is set to the current position (therefore not dialog). * This can be useful to mark something during playback. */ void slotAddMarkerGuideQuickly(); void slotCutTimelineClip(); void slotInsertClipOverwrite(); void slotInsertClipInsert(); void slotExtractZone(); void slotLiftZone(); + void slotPreviewRender(); void slotSelectTimelineClip(); void slotSelectTimelineTransition(); void slotDeselectTimelineClip(); void slotDeselectTimelineTransition(); void slotSelectAddTimelineClip(); void slotSelectAddTimelineTransition(); void slotAddVideoEffect(QAction *result); void slotAddTransition(QAction *result); void slotAddProjectClip(const QUrl &url); void slotAddProjectClipList(const QList &urls); void slotTrackSelected(int index, const TrackInfo &info, bool raise = true); void slotActivateTransitionView(Transition *transition); void slotChangeTool(QAction * action); void slotChangeEdit(QAction * action); void slotSetTool(ProjectTool tool); void slotSnapForward(); void slotSnapRewind(); void slotClipStart(); void slotClipEnd(); void slotSelectClipInTimeline(); void slotClipInTimeline(const QString &clipId); void slotInsertSpace(); void slotRemoveSpace(); void slotAddGuide(); void slotEditGuide(int pos = -1, QString text = QString()); void slotDeleteGuide(); void slotDeleteAllGuides(); void slotGuidesUpdated(); void slotCopy(); void slotPaste(); void slotPasteEffects(); void slotResizeItemStart(); void slotResizeItemEnd(); void configureNotifications(); void slotInsertTrack(); void slotDeleteTrack(); /** @brief Shows the configure tracks dialog and updates transitions afterwards. */ void slotConfigTrack(); /** @brief Select all clips in active track. */ void slotSelectTrack(); /** @brief Select all clips in timeline. */ void slotSelectAllTracks(); void slotGetNewLumaStuff(); void slotGetNewTitleStuff(); void slotGetNewRenderStuff(); void slotGetNewMltProfileStuff(); void slotAutoTransition(); void slotRunWizard(); void slotZoneMoved(int start, int end); void slotDvdWizard(const QString &url = QString()); void slotGroupClips(); void slotUnGroupClips(); void slotEditItemDuration(); void slotClipInProjectTree(); //void slotClipToProjectTree(); void slotSplitAudio(); void slotSetAudioAlignReference(); void slotAlignAudio(); void slotUpdateClipType(QAction *action); void slotShowTimeline(bool show); void slotTranscode(const QStringList &urls = QStringList()); void slotTranscodeClip(); /** @brief Archive project: creates a copy of the project file with all clips in a new folder. */ void slotArchiveProject(); void slotSetDocumentRenderProfile(const QMap &props); void slotPrepareRendering(bool scriptExport, bool zoneOnly, const QString &chapterFile); /** @brief Switches between displaying frames or timecode. * @param ix 0 = display timecode, 1 = display frames. */ void slotUpdateTimecodeFormat(int ix); /** @brief Removes the focus of anything. */ void slotRemoveFocus(); void slotCleanProject(); void slotShutdown(); void slotUpdateTrackInfo(); void slotSwitchMonitors(); void slotSwitchMonitorOverlay(QAction *); void slotSwitchDropFrames(bool drop); void slotSetMonitorGamma(int gamma); void slotCheckRenderStatus(); void slotInsertZoneToTree(); void slotInsertZoneToTimeline(); /** @brief Update the capture folder if user asked a change. */ void slotUpdateCaptureFolder(); /** @brief The monitor informs that it needs (or not) to have frames sent by the renderer. */ void slotMonitorRequestRenderFrame(bool request); /** @brief Open the stopmotion dialog. */ void slotOpenStopmotion(); /** @brief Update project because the use of proxy clips was enabled / disabled. */ void slotUpdateProxySettings(); /** @brief Disable proxies for this project. */ void slotDisableProxies(); /** @brief Open the online services search dialog. */ void slotDownloadResources(); /** @brief Process keyframe data sent from a clip to effect / transition stack. */ void slotProcessImportKeyframes(GraphicsRectItem type, const QString &tag, const QString& data); /** @brief Move playhead to mouse curser position if defined key is pressed */ void slotAlignPlayheadToMousePos(); void slotThemeChanged(const QString &); void slotReloadTheme(); /** @brief Close Kdenlive and try to restart it */ void slotRestart(); void triggerKey(QKeyEvent* ev); /** @brief Update monitor overlay actions on monitor switch */ void slotUpdateMonitorOverlays(int id, int code); /** @brief Update widget style */ void slotChangeStyle(QAction *a); /** @brief Create temporary top track to preview an effect */ void createSplitOverlay(Mlt::Filter *filter); void removeSplitOverlay(); /** @brief Create a generator's setup dialog */ void buildGenerator(QAction *action); signals: Q_SCRIPTABLE void abortRenderJob(const QString &url); void configurationChanged(); void GUISetupDone(); void reloadTheme(); }; #endif diff --git a/src/project/jobs/proxyclipjob.cpp b/src/project/jobs/proxyclipjob.cpp index d34aed126..c22b90525 100644 --- a/src/project/jobs/proxyclipjob.cpp +++ b/src/project/jobs/proxyclipjob.cpp @@ -1,313 +1,312 @@ /*************************************************************************** * * * Copyright (C) 2011 by Jean-Baptiste Mardelle (jb@kdenlive.org) * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * 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 * ***************************************************************************/ #include "proxyclipjob.h" #include "kdenlivesettings.h" #include "doc/kdenlivedoc.h" #include "bin/projectclip.h" #include "bin/bin.h" #include #include #include ProxyJob::ProxyJob(ClipType cType, const QString &id, const QStringList& parameters) : AbstractClipJob(PROXYJOB, cType, id), m_jobDuration(0), m_isFfmpegJob(true) { m_jobStatus = JobWaiting; description = i18n("proxy"); m_dest = parameters.at(0); m_src = parameters.at(1); m_exif = parameters.at(2).toInt(); m_proxyParams = parameters.at(3); m_renderWidth = parameters.at(4).toInt(); m_renderHeight = parameters.at(5).toInt(); replaceClip = true; } void ProxyJob::startJob() { // Special case: playlist clips (.mlt or .kdenlive project files) m_jobDuration = 0; if (clipType == Playlist) { // change FFmpeg params to MLT format m_isFfmpegJob = false; QStringList mltParameters; mltParameters << m_src; mltParameters << QStringLiteral("-consumer") << QStringLiteral("avformat:") + m_dest; QStringList params = m_proxyParams.split(QLatin1Char('-'), QString::SkipEmptyParts); foreach(const QString &s, params) { QString t = s.simplified(); if (t.count(QLatin1Char(' ')) == 0) { t.append(QLatin1String("=1")); } else t.replace(QLatin1Char(' '), QLatin1String("=")); mltParameters << t; } mltParameters.append(QStringLiteral("real_time=-%1").arg(KdenliveSettings::mltthreads())); //TODO: currently, when rendering an xml file through melt, the display ration is lost, so we enforce it manualy double display_ratio; if (m_src.startsWith(QLatin1String("consumer:"))) display_ratio = KdenliveDoc::getDisplayRatio(m_src.section(QStringLiteral(":"), 1)); else display_ratio = KdenliveDoc::getDisplayRatio(m_src); mltParameters << QStringLiteral("aspect=") + QLocale().toString(display_ratio); // Ask for progress reporting mltParameters << QStringLiteral("progress=1"); m_jobProcess = new QProcess; m_jobProcess->setProcessChannelMode(QProcess::MergedChannels); m_jobProcess->start(KdenliveSettings::rendererpath(), mltParameters); m_jobProcess->waitForStarted(); } else if (clipType == Image) { m_isFfmpegJob = false; // Image proxy QImage i(m_src); if (i.isNull()) { m_errorMessage.append(i18n("Cannot load image %1.", m_src)); setStatus(JobCrashed); return; } QImage proxy; // Images are scaled to profile size. //TODO: Make it be configurable? if (i.width() > i.height()) proxy = i.scaledToWidth(m_renderWidth); else proxy = i.scaledToHeight(m_renderHeight); if (m_exif > 1) { // Rotate image according to exif data QImage processed; QMatrix matrix; switch ( m_exif ) { case 2: matrix.scale( -1, 1 ); break; case 3: matrix.rotate( 180 ); break; case 4: matrix.scale( 1, -1 ); break; case 5: matrix.rotate( 270 ); matrix.scale( -1, 1 ); break; case 6: matrix.rotate( 90 ); break; case 7: matrix.rotate( 90 ); matrix.scale( -1, 1 ); break; case 8: matrix.rotate( 270 ); break; } processed = proxy.transformed( matrix ); processed.save(m_dest); } else { proxy.save(m_dest); } setStatus(JobDone); return; } else { m_isFfmpegJob = true; QStringList parameters; if (m_proxyParams.contains(QStringLiteral("-noautorotate"))) { // The noautorotate flag must be passed before input source parameters << QStringLiteral("-noautorotate"); } parameters << QStringLiteral("-i") << m_src; QString params = m_proxyParams; foreach(const QString &s, params.split(QLatin1Char(' '))) { if (s != QLatin1String("-noautorotate")) { parameters << s; } } // Make sure we don't block when proxy file already exists parameters << QStringLiteral("-y"); parameters << m_dest; m_jobProcess = new QProcess; m_jobProcess->setProcessChannelMode(QProcess::MergedChannels); m_jobProcess->start(KdenliveSettings::ffmpegpath(), parameters, QIODevice::ReadOnly); m_jobProcess->waitForStarted(); } while (m_jobProcess->state() != QProcess::NotRunning) { processLogInfo(); if (m_jobStatus == JobAborted) { emit cancelRunningJob(m_clipId, cancelProperties()); m_jobProcess->close(); m_jobProcess->waitForFinished(); QFile::remove(m_dest); } m_jobProcess->waitForFinished(400); } - if (m_jobStatus != JobAborted) { int result = m_jobProcess->exitStatus(); if (result == QProcess::NormalExit) { if (QFileInfo(m_dest).size() == 0) { // File was not created processLogInfo(); m_errorMessage.append(i18n("Failed to create proxy clip.")); setStatus(JobCrashed); } else setStatus(JobDone); } else if (result == QProcess::CrashExit) { // Proxy process crashed QFile::remove(m_dest); setStatus(JobCrashed); } } delete m_jobProcess; return; } void ProxyJob::processLogInfo() { if (!m_jobProcess || m_jobStatus == JobAborted) return; QString log = QString::fromUtf8(m_jobProcess->readAll()); if (!log.isEmpty()) m_logDetails.append(log + QLatin1Char('\n')); else return; int progress; if (m_isFfmpegJob) { // Parse FFmpeg output if (m_jobDuration == 0) { if (log.contains(QLatin1String("Duration:"))) { QString data = log.section(QStringLiteral("Duration:"), 1, 1).section(QLatin1Char(','), 0, 0).simplified(); QStringList numbers = data.split(QLatin1Char(':')); m_jobDuration = (int) (numbers.at(0).toInt() * 3600 + numbers.at(1).toInt() * 60 + numbers.at(2).toDouble()); } } else if (log.contains(QLatin1String("time="))) { QString time = log.section(QStringLiteral("time="), 1, 1).simplified().section(QLatin1Char(' '), 0, 0); if (time.contains(QLatin1Char(':'))) { QStringList numbers = time.split(QLatin1Char(':')); progress = numbers.at(0).toInt() * 3600 + numbers.at(1).toInt() * 60 + numbers.at(2).toDouble(); } else progress = (int) time.toDouble(); emit jobProgress(m_clipId, (int) (100.0 * progress / m_jobDuration), jobType); } } else { // Parse MLT output if (log.contains(QLatin1String("percentage:"))) { progress = log.section(QStringLiteral("percentage:"), 1).simplified().section(QLatin1Char(' '), 0, 0).toInt(); emit jobProgress(m_clipId, progress, jobType); } } } ProxyJob::~ProxyJob() { } const QString ProxyJob::destination() const { return m_dest; } stringMap ProxyJob::cancelProperties() { QMap props; props.insert(QStringLiteral("kdenlive:proxy"), QStringLiteral("-")); return props; } const QString ProxyJob::statusMessage() { QString statusInfo; switch (m_jobStatus) { case JobWorking: statusInfo = i18n("Creating proxy"); break; case JobWaiting: statusInfo = i18n("Waiting - proxy"); break; default: break; } return statusInfo; } // static QList ProxyJob::filterClips(QList clips) { QList result; for (int i = 0; i < clips.count(); i++) { ProjectClip *clip = clips.at(i); ClipType type = clip->clipType(); if (type != AV && type != Video && type != Playlist && type != Image) { // Clip will not be processed by this job continue; } result << clip; } return result; } // static QHash ProxyJob::prepareJob(Bin *bin, QList clips) { QHash jobs; QSize renderSize = bin->getRenderSize(); QString params = bin->getDocumentProperty(QStringLiteral("proxyparams")).simplified(); for (int i = 0; i < clips.count(); i++) { ProjectClip *item = clips.at(i); QString id = item->clipId(); QString path = item->getProducerProperty(QStringLiteral("kdenlive:proxy")); if (path.isEmpty()) { item->setJobStatus(AbstractClipJob::PROXYJOB, JobCrashed, -1, i18n("Failed to create proxy, empty path.")); continue; } if (QFileInfo(path).size() > 0) { // Proxy already created item->setJobStatus(AbstractClipJob::PROXYJOB, JobDone); bin->gotProxy(id); continue; } QString sourcePath = item->url().toLocalFile(); if (item->clipType() == Playlist) { // Special case: playlists use the special 'consumer' producer to support resizing sourcePath.prepend("consumer:"); } QStringList parameters; parameters << path << sourcePath << item->getProducerProperty(QStringLiteral("_exif_orientation")) << params << QString::number(renderSize.width()) << QString::number(renderSize.height()); ProxyJob *job = new ProxyJob(item->clipType(), id, parameters); jobs.insert(item, job); } return jobs; } diff --git a/src/renderer.cpp b/src/renderer.cpp index 180cc515b..2c0d0099c 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -1,2316 +1,2379 @@ /*************************************************************************** krender.cpp - description ------------------- begin : Fri Nov 22 2002 copyright : (C) 2002 by Jason Wood email : jasonwood@blueyonder.co.uk copyright : (C) 2005 Lucio Flavio Correa email : lucio.correa@gmail.com copyright : (C) Marco Gittler email : g.marco@freenet.de copyright : (C) 2006 Jean-Baptiste Mardelle email : jb@kdenlive.org ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "renderer.h" #include "kdenlivesettings.h" #include "doc/kthumb.h" #include "definitions.h" #include "project/dialogs/slideshowclip.h" #include "dialogs/profilesdialog.h" #include "mltcontroller/bincontroller.h" #include "bin/projectclip.h" #include "timeline/clip.h" #include "monitor/glwidget.h" #include "mltcontroller/clipcontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SEEK_INACTIVE (-1) Render::Render(Kdenlive::MonitorId rendererName, BinController *binController, GLWidget *qmlView, QWidget *parent) : AbstractRender(rendererName, parent), requestedSeekPosition(SEEK_INACTIVE), showFrameSemaphore(1), externalConsumer(false), m_name(rendererName), m_mltConsumer(NULL), m_mltProducer(NULL), m_showFrameEvent(NULL), m_pauseEvent(NULL), m_binController(binController), m_qmlView(qmlView), m_isZoneMode(false), m_isLoopMode(false), m_blackClip(NULL), m_isActive(false), - m_isRefreshing(false) + m_isRefreshing(false), + m_abortPreview(false) { qRegisterMetaType ("stringMap"); analyseAudio = KdenliveSettings::monitor_audio(); //buildConsumer(); if (m_qmlView) { m_blackClip = new Mlt::Producer(*m_qmlView->profile(), "colour:black"); m_blackClip->set("id", "black"); m_blackClip->set("mlt_type", "producer"); m_mltProducer = m_blackClip->cut(0, 1); m_qmlView->setProducer(m_mltProducer); m_mltConsumer = qmlView->consumer(); } /*m_mltConsumer->connect(*m_mltProducer); m_mltProducer->set_speed(0.0);*/ m_refreshTimer.setSingleShot(true); m_refreshTimer.setInterval(50); connect(&m_refreshTimer, SIGNAL(timeout()), this, SLOT(refresh())); connect(this, SIGNAL(checkSeeking()), this, SLOT(slotCheckSeeking())); if (m_name == Kdenlive::ProjectMonitor) { connect(m_binController, SIGNAL(prepareTimelineReplacement(QString)), this, SIGNAL(prepareTimelineReplacement(QString)), Qt::DirectConnection); connect(m_binController, SIGNAL(replaceTimelineProducer(QString)), this, SIGNAL(replaceTimelineProducer(QString)), Qt::DirectConnection); connect(m_binController, SIGNAL(updateTimelineProducer(QString)), this, SIGNAL(updateTimelineProducer(QString))); connect(m_binController, SIGNAL(setDocumentNotes(QString)), this, SIGNAL(setDocumentNotes(QString))); } } Render::~Render() { + m_abortPreview = true; closeMlt(); } void Render::closeMlt() { delete m_showFrameEvent; delete m_pauseEvent; delete m_mltConsumer; delete m_mltProducer; delete m_blackClip; + m_previewThread.waitForFinished(); } void Render::slotSwitchFullscreen() { if (m_mltConsumer) m_mltConsumer->set("full_screen", 1); } void Render::prepareProfileReset(double fps) { m_refreshTimer.stop(); m_fps = fps; } void Render::finishProfileReset() { delete m_blackClip; m_blackClip = new Mlt::Producer(*m_qmlView->profile(), "colour:black"); m_blackClip->set("id", "black"); m_blackClip->set("mlt_type", "producer"); } void Render::seek(const GenTime &time) { if (!m_mltProducer || !m_isActive) return; int pos = time.frames(m_fps); seek(pos); } void Render::seek(int time) { resetZoneMode(); time = qBound(0, time, m_mltProducer->get_length() - 1); if (requestedSeekPosition == SEEK_INACTIVE) { requestedSeekPosition = time; if (m_mltProducer->get_speed() != 0) { m_mltConsumer->purge(); } m_mltProducer->seek(time); if (!externalConsumer) { m_isRefreshing = true; m_mltConsumer->set("refresh", 1); } } else { requestedSeekPosition = time; } } int Render::frameRenderWidth() const { return m_qmlView->profile()->width(); } int Render::renderWidth() const { return (int)(m_qmlView->profile()->height() * m_qmlView->profile()->dar() + 0.5); } int Render::renderHeight() const { return m_qmlView->profile()->height(); } QImage Render::extractFrame(int frame_position, const QString &path, int width, int height) { if (width == -1) { width = frameRenderWidth(); height = renderHeight(); } else if (width % 2 == 1) width++; if (!path.isEmpty()) { Mlt::Producer *producer = new Mlt::Producer(*m_qmlView->profile(), path.toUtf8().constData()); if (producer) { if (producer->is_valid()) { QImage img = KThumb::getFrame(producer, frame_position, width, height); delete producer; return img; } else delete producer; } } if (!m_mltProducer || !path.isEmpty()) { QImage pix(width, height, QImage::Format_RGB32); pix.fill(Qt::black); return pix; } Mlt::Frame *frame = NULL; if (KdenliveSettings::gpu_accel()) { QString service = m_mltProducer->get("mlt_service"); //TODO: create duplicate prod from xml data Mlt::Producer *tmpProd = new Mlt::Producer(*m_qmlView->profile(), service.toUtf8().constData(), path.toUtf8().constData()); Mlt::Filter scaler(*m_qmlView->profile(), "swscale"); Mlt::Filter converter(*m_qmlView->profile(), "avcolor_space"); tmpProd->attach(scaler); tmpProd->attach(converter); tmpProd->seek(m_mltProducer->position()); frame = tmpProd->get_frame(); delete tmpProd; } else { frame = m_mltProducer->get_frame(); } QImage img = KThumb::getFrame(frame, width, height); delete frame; return img; } int Render::getLength() { if (m_mltProducer) { // //qDebug()<<"////// LENGTH: "<get_producer()); return mlt_producer_get_playtime(m_mltProducer->get_producer()); } return 0; } bool Render::isValid(const QUrl &url) { Mlt::Producer producer(*m_qmlView->profile(), url.toLocalFile().toUtf8().constData()); if (producer.is_blank()) return false; return true; } double Render::dar() const { return m_qmlView->profile()->dar(); } double Render::sar() const { return m_qmlView->profile()->sar(); } #if 0 /** Create the producer from the MLT XML QDomDocument */ void Render::initSceneList() { //qDebug() << "-------- INIT SCENE LIST ------_"; QDomDocument doc; QDomElement mlt = doc.createElement("mlt"); doc.appendChild(mlt); QDomElement prod = doc.createElement("producer"); prod.setAttribute("resource", "colour"); prod.setAttribute("colour", "red"); prod.setAttribute("id", "black"); prod.setAttribute("in", "0"); prod.setAttribute("out", "0"); QDomElement tractor = doc.createElement("tractor"); QDomElement multitrack = doc.createElement("multitrack"); QDomElement playlist1 = doc.createElement("playlist"); playlist1.appendChild(prod); multitrack.appendChild(playlist1); QDomElement playlist2 = doc.createElement("playlist"); multitrack.appendChild(playlist2); QDomElement playlist3 = doc.createElement("playlist"); multitrack.appendChild(playlist3); QDomElement playlist4 = doc.createElement("playlist"); multitrack.appendChild(playlist4); QDomElement playlist5 = doc.createElement("playlist"); multitrack.appendChild(playlist5); tractor.appendChild(multitrack); mlt.appendChild(tractor); // //qDebug()<");*/ setSceneList(doc, 0); } #endif void Render::loadUrl(const QString &url) { Mlt::Producer *producer = new Mlt::Producer(*m_qmlView->profile(), url.toUtf8().constData()); setProducer(producer, 0, true); } bool Render::updateProducer(Mlt::Producer *producer) { if (m_mltProducer) { if (strcmp(m_mltProducer->get("resource"), "") == 0) { // We need to make some cleanup Mlt::Tractor trac(*m_mltProducer); for (int i = 0; i < trac.count(); i++) { trac.set_track(*m_blackClip, i); } } delete m_mltProducer; m_mltProducer = NULL; } if (m_mltConsumer) { if (!m_mltConsumer->is_stopped()) { m_mltConsumer->stop(); } } if (!producer || !producer->is_valid()) { return false; } m_fps = producer->get_fps(); m_mltProducer = producer; if (m_qmlView) { m_qmlView->setProducer(producer); m_mltConsumer = m_qmlView->consumer(); } return true; } bool Render::setProducer(Mlt::Producer *producer, int position, bool isActive) { m_refreshTimer.stop(); requestedSeekPosition = SEEK_INACTIVE; QMutexLocker locker(&m_mutex); QString currentId; int consumerPosition = 0; if (!producer && m_mltProducer && m_mltProducer->parent().get("id") == QLatin1String("black")) { // Black clip already displayed no need to refresh return true; } if (m_mltProducer) { currentId = m_mltProducer->get("id"); m_mltProducer->set_speed(0); if (QString(m_mltProducer->get("resource")) == QLatin1String("")) { // We need to make some cleanup Mlt::Tractor trac(*m_mltProducer); for (int i = 0; i < trac.count(); i++) { trac.set_track(*m_blackClip, i); } } delete m_mltProducer; m_mltProducer = NULL; } if (m_mltConsumer) { if (!m_mltConsumer->is_stopped()) { isActive = true; m_mltConsumer->stop(); } consumerPosition = m_mltConsumer->position(); } blockSignals(true); if (!producer || !producer->is_valid()) { producer = m_blackClip->cut(0,1); } emit stopped(); if (position == -1 && producer->get("id") == currentId) position = consumerPosition; if (position != -1) producer->seek(position); m_fps = producer->get_fps(); blockSignals(false); m_mltProducer = producer; m_mltProducer->set_speed(0); if (m_qmlView) { m_qmlView->setProducer(producer); m_mltConsumer = m_qmlView->consumer(); //m_mltConsumer->set("refresh", 1); } //m_mltConsumer->connect(*producer); if (isActive) { startConsumer(); } emit durationChanged(m_mltProducer->get_playtime() - 1, m_mltProducer->get_in()); position = m_mltProducer->position(); emit rendererPosition(position); return true; } void Render::startConsumer() { if (m_mltConsumer->is_stopped() && m_mltConsumer->start() == -1) { // ARGH CONSUMER BROKEN!!!! KMessageBox::error(qApp->activeWindow(), i18n("Could not create the video preview window.\nThere is something wrong with your Kdenlive install or your driver settings, please fix it.")); if (m_showFrameEvent) delete m_showFrameEvent; m_showFrameEvent = NULL; if (m_pauseEvent) delete m_pauseEvent; m_pauseEvent = NULL; delete m_mltConsumer; m_mltConsumer = NULL; return; } m_isRefreshing = true; m_mltConsumer->set("refresh", 1); m_isActive = true; } int Render::setSceneList(const QDomDocument &list, int position) { return setSceneList(list.toString(), position); } int Render::setSceneList(QString playlist, int position) { requestedSeekPosition = SEEK_INACTIVE; m_refreshTimer.stop(); QMutexLocker locker(&m_mutex); //if (m_winid == -1) return -1; int error = 0; //qDebug() << "////// RENDER, SET SCENE LIST:\n" << playlist <<"\n..........:::."; // Remove previous profile info QDomDocument doc; doc.setContent(playlist); QDomElement profile = doc.documentElement().firstChildElement(QStringLiteral("profile")); doc.documentElement().removeChild(profile); playlist = doc.toString(); if (m_mltConsumer) { if (!m_mltConsumer->is_stopped()) { m_mltConsumer->stop(); } } else { qWarning() << "/////// ERROR, TRYING TO USE NULL MLT CONSUMER"; error = -1; } if (m_mltProducer) { m_mltProducer->set_speed(0); qDeleteAll(m_slowmotionProducers); m_slowmotionProducers.clear(); delete m_mltProducer; m_mltProducer = NULL; emit stopped(); } m_binController->destroyBin(); blockSignals(true); m_locale = QLocale(); m_locale.setNumberOptions(QLocale::OmitGroupSeparator); m_mltProducer = new Mlt::Producer(*m_qmlView->profile(), "xml-string", playlist.toUtf8().constData()); //m_mltProducer = new Mlt::Producer(*m_qmlView->profile(), "xml-nogl-string", playlist.toUtf8().constData()); if (!m_mltProducer || !m_mltProducer->is_valid()) { qDebug() << " WARNING - - - - -INVALID PLAYLIST: " << playlist.toUtf8().constData(); m_mltProducer = m_blackClip->cut(0, 1); error = -1; } m_mltProducer->set("eof", "pause"); checkMaxThreads(); m_mltProducer->optimise(); m_fps = m_mltProducer->get_fps(); if (position != 0) { // Seek to correct place after opening project. m_mltProducer->seek(position); } // init MLT's document root, useful to find full urls m_binController->setDocumentRoot(doc.documentElement().attribute(QStringLiteral("root"))); // Fill Bin's playlist Mlt::Service service(m_mltProducer->parent().get_service()); if (service.type() != tractor_type) { qWarning() << "// TRACTOR PROBLEM"; } blockSignals(false); Mlt::Tractor tractor(service); Mlt::Properties retainList((mlt_properties) tractor.get_data("xml_retain")); if (retainList.is_valid() && retainList.get_data(m_binController->binPlaylistId().toUtf8().constData())) { Mlt::Playlist playlist((mlt_playlist) retainList.get_data(m_binController->binPlaylistId().toUtf8().constData())); if (playlist.is_valid() && playlist.type() == playlist_type) { // Load bin clips m_binController->initializeBin(playlist); } } // No Playlist found, create new one if (m_qmlView) { m_binController->createIfNeeded(m_qmlView->profile()); QString retain = QStringLiteral("xml_retain %1").arg(m_binController->binPlaylistId()); tractor.set(retain.toUtf8().constData(), m_binController->service(), 0); //if (!m_binController->hasClip("black")) m_binController->addClipToBin("black", *m_blackClip); m_qmlView->setProducer(m_mltProducer); m_mltConsumer = m_qmlView->consumer(); } //qDebug() << "// NEW SCENE LIST DURATION SET TO: " << m_mltProducer->get_playtime(); //m_mltConsumer->connect(*m_mltProducer); m_mltProducer->set_speed(0); fillSlowMotionProducers(); emit durationChanged(m_mltProducer->get_playtime() - 1); // Fill bin QStringList ids = m_binController->getClipIds(); foreach(const QString &id, ids) { if (id == QLatin1String("black")) { //TODO: delegate handling of black clip to bincontroller //delete m_blackClip; //m_blackClip = &original->parent(); } else { // pass basic info, the others (folder, etc) will be taken from the producer itself requestClipInfo info; info.imageHeight = 0; info.clipId = id; info.replaceProducer = true; emit gotFileProperties(info, m_binController->getController(id)); } //delete original; } ////qDebug()<<"// SETSCN LST, POS: "<parent().get_service()); if (service.type() != tractor_type) { qWarning() << "// TRACTOR PROBLEM"<parent().get("mlt_service"); return; } Mlt::Tractor tractor(service); int mltMaxThreads = mlt_service_cache_get_size(service.get_service(), "producer_avformat"); int requestedThreads = tractor.count() + m_qmlView->realTime() + 2; if (requestedThreads > mltMaxThreads) { mlt_service_cache_set_size(service.get_service(), "producer_avformat", requestedThreads); //qDebug()<<"// MLT threads updated to: "<profile(), "xml:kdenlive_playlist"); //qDebug()<<" ++ + READY TO SAVE: "<profile()->width()<<" / "<profile()->description(); if (!xmlConsumer.is_valid()) return QString(); m_mltProducer->optimise(); xmlConsumer.set("terminate_on_pause", 1); xmlConsumer.set("store", "kdenlive"); Mlt::Producer prod(m_mltProducer->get_producer()); if (!prod.is_valid()) return QString(); xmlConsumer.connect(prod); xmlConsumer.run(); playlist = QString::fromUtf8(xmlConsumer.get("kdenlive_playlist")); return playlist; } bool Render::saveSceneList(QString path, QDomElement kdenliveData) { QFile file(path); QDomDocument doc; doc.setContent(sceneList(), false); if (doc.isNull()) return false; QDomElement root = doc.documentElement(); if (!kdenliveData.isNull() && !root.isNull()) { // add Kdenlive specific tags root.appendChild(doc.importNode(kdenliveData, true)); } if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { qWarning() << "////// ERROR writing to file: " << path; return false; } file.write(doc.toString().toUtf8()); if (file.error() != QFile::NoError) { file.close(); return false; } file.close(); return true; } void Render::saveZone(QPoint zone) { QString clipFolder = KRecentDirs::dir(QStringLiteral(":KdenliveClipFolder")); if (clipFolder.isEmpty()) { clipFolder = QDir::homePath(); } QString url = QFileDialog::getSaveFileName(qApp->activeWindow(), i18n("Save Zone"), clipFolder, i18n("MLT playlist (*.mlt)")); Mlt::Consumer xmlConsumer(*m_qmlView->profile(), ("xml:" + url).toUtf8().constData()); xmlConsumer.set("terminate_on_pause", 1); m_mltProducer->optimise(); qDebug()<<" - - -- - SAVE ZONE SEVICE: "<get("mlt_type"); if (QString(m_mltProducer->get("mlt_type")) != QLatin1String("producer")) { // TODO: broken QString scene = sceneList(); Mlt::Producer duplicate(*m_mltProducer->profile(), "xml-string", scene.toUtf8().constData()); duplicate.set_in_and_out(zone.x(), zone.y()); qDebug()<<"/// CUT: "<get_producer()); Mlt::Producer *prod2 = prod.cut(zone.x(), zone.y()); Mlt::Playlist list(*m_mltProducer->profile()); list.insert_at(0, *prod2, 0); //list.set("title", desc.toUtf8().constData()); xmlConsumer.connect(list); xmlConsumer.run(); delete prod2; } } double Render::fps() const { return m_fps; } int Render::volume() const { if (!m_mltConsumer || !m_mltProducer) return -1; if (m_mltConsumer->get("mlt_service") == QStringLiteral("multi")) { return ((int) 100 * m_mltConsumer->get_double("0.volume")); } return ((int) 100 * m_mltConsumer->get_double("volume")); } void Render::start() { m_refreshTimer.stop(); QMutexLocker locker(&m_mutex); /*if (m_winid == -1) { //qDebug() << "----- BROKEN MONITOR: " << m_name << ", RESTART"; return; }*/ if (!m_mltConsumer) { //qDebug()<<" / - - - STARTED BEFORE CONSUMER!!!"; return; } if (m_mltConsumer->is_stopped()) { if (m_mltConsumer->start() == -1) { //KMessageBox::error(qApp->activeWindow(), i18n("Could not create the video preview window.\nThere is something wrong with your Kdenlive install or your driver settings, please fix it.")); qWarning() << "/ / / / CANNOT START MONITOR"; } else { m_mltConsumer->purge(); m_isRefreshing = true; m_mltConsumer->set("refresh", 1); } } } void Render::stop() { requestedSeekPosition = SEEK_INACTIVE; m_refreshTimer.stop(); QMutexLocker locker(&m_mutex); m_isActive = false; if (m_mltProducer) { if (m_isZoneMode) resetZoneMode(); m_mltProducer->set_speed(0.0); } if (m_mltConsumer) { m_mltConsumer->purge(); if (!m_mltConsumer->is_stopped()) { m_mltConsumer->stop(); } } m_isRefreshing = false; } void Render::stop(const GenTime & startTime) { requestedSeekPosition = SEEK_INACTIVE; m_refreshTimer.stop(); QMutexLocker locker(&m_mutex); m_isActive = false; if (m_mltProducer) { if (m_isZoneMode) resetZoneMode(); m_mltProducer->set_speed(0.0); m_mltProducer->seek((int) startTime.frames(m_fps)); } if (m_mltConsumer) { m_mltConsumer->purge(); } m_isRefreshing = false; } /*void Render::pause() { requestedSeekPosition = SEEK_INACTIVE; if (!m_mltProducer || !m_mltConsumer || !m_isActive) return; m_mltProducer->set_speed(0.0); //if (!m_mltConsumer->is_stopped()) m_mltConsumer->stop(); //m_mltProducer->seek(m_mltConsumer->position()); }*/ void Render::setActiveMonitor() { if (!m_isActive) emit activateMonitor(m_name); } void Render::switchPlay(bool play) { QMutexLocker locker(&m_mutex); requestedSeekPosition = SEEK_INACTIVE; if (!m_mltProducer || !m_mltConsumer || !m_isActive) return; if (m_isZoneMode) resetZoneMode(); if (play) { if (m_name == Kdenlive::ClipMonitor && m_mltConsumer->position() == m_mltProducer->get_out()) m_mltProducer->seek(0); if (m_mltConsumer->get_int("real_time") != m_qmlView->realTime()) { m_mltConsumer->set("real_time", m_qmlView->realTime()); m_mltConsumer->set("buffer", 25); m_mltConsumer->set("prefill", 1); // Changes to real_time require a consumer restart if running. if (!m_mltConsumer->is_stopped()) { m_mltConsumer->stop(); } } m_mltConsumer->start(); m_isRefreshing = true; m_mltConsumer->set("refresh", 1); m_mltProducer->set_speed(1.0); } else { m_mltConsumer->purge(); m_mltProducer->set_speed(0.0); m_mltConsumer->set("buffer", 0); m_mltConsumer->set("prefill", 0); m_mltConsumer->set("real_time", -1); m_mltProducer->seek(m_mltConsumer->position() + 1); m_mltConsumer->start(); } } void Render::play(double speed) { requestedSeekPosition = SEEK_INACTIVE; if (!m_mltProducer || !m_isActive) return; double current_speed = m_mltProducer->get_speed(); if (current_speed == speed) return; if (m_isZoneMode) resetZoneMode(); m_mltProducer->set_speed(speed); if (m_mltConsumer->is_stopped() && speed != 0.0) { m_mltConsumer->start(); } if (current_speed == 0.0 && speed != 0.0) { m_isRefreshing = true; m_mltConsumer->set("refresh", 1); } } void Render::play(const GenTime & startTime) { requestedSeekPosition = SEEK_INACTIVE; if (!m_mltProducer || !m_mltConsumer || !m_isActive) return; m_mltProducer->seek((int)(startTime.frames(m_fps))); m_mltProducer->set_speed(1.0); m_isRefreshing = true; m_mltConsumer->set("refresh", 1); } void Render::loopZone(const GenTime & startTime, const GenTime & stopTime) { requestedSeekPosition = SEEK_INACTIVE; if (!m_mltProducer || !m_mltConsumer || !m_isActive) return; //m_mltProducer->set("eof", "loop"); m_isLoopMode = true; m_loopStart = startTime; playZone(startTime, stopTime); } bool Render::playZone(const GenTime & startTime, const GenTime & stopTime) { requestedSeekPosition = SEEK_INACTIVE; if (!m_mltProducer || !m_mltConsumer || !m_isActive) return false; m_mltProducer->seek((int)(startTime.frames(m_fps))); m_mltProducer->set_speed(0); m_mltConsumer->purge(); m_mltProducer->set("out", (int)(stopTime.frames(m_fps))); m_mltProducer->set_speed(1.0); if (m_mltConsumer->is_stopped()) m_mltConsumer->start(); m_isRefreshing = true; m_mltConsumer->set("refresh", 1); m_isZoneMode = true; return true; } void Render::resetZoneMode() { if (!m_isZoneMode && !m_isLoopMode) return; m_mltProducer->set("out", m_mltProducer->get_length()); m_isZoneMode = false; m_isLoopMode = false; } void Render::seekToFrame(int pos) { if (!m_mltProducer || !m_isActive) return; pos = qBound(0, pos - m_mltProducer->get_in(), m_mltProducer->get_length()); seek(pos); } void Render::seekToFrameDiff(int diff) { if (!m_mltProducer || !m_isActive) return; if (requestedSeekPosition == SEEK_INACTIVE) { seek(m_mltConsumer->position() + diff); } else { seek(requestedSeekPosition + diff); } } void Render::doRefresh() { if (m_mltProducer && (playSpeed() == 0) && m_isActive) { if (m_isRefreshing) m_refreshTimer.start(); else refresh(); } } void Render::refresh() { m_refreshTimer.stop(); if (!m_mltProducer || !m_isActive) return; QMutexLocker locker(&m_mutex); if (m_mltConsumer) { m_isRefreshing = true; if (m_mltConsumer->is_stopped()) m_mltConsumer->start(); m_mltConsumer->purge(); m_isRefreshing = true; m_mltConsumer->set("refresh", 1); } } void Render::setDropFrames(bool drop) { QMutexLocker locker(&m_mutex); if (m_mltConsumer) { int dropFrames = m_qmlView->realTime(); if (drop == false) dropFrames = -dropFrames; //m_mltConsumer->stop(); m_mltConsumer->set("real_time", dropFrames); if (m_mltConsumer->start() == -1) { qWarning() << "ERROR, Cannot start monitor"; } } } void Render::setConsumerProperty(const QString &name, const QString &value) { QMutexLocker locker(&m_mutex); if (m_mltConsumer) { //m_mltConsumer->stop(); m_mltConsumer->set(name.toUtf8().constData(), value.toUtf8().constData()); if (m_isActive && m_mltConsumer->start() == -1) { qWarning() << "ERROR, Cannot start monitor"; } } } bool Render::isPlaying() const { if (!m_mltConsumer || m_mltConsumer->is_stopped()) return false; return playSpeed() != 0; } double Render::playSpeed() const { if (m_mltProducer) return m_mltProducer->get_speed(); return 0.0; } GenTime Render::seekPosition() const { if (m_mltConsumer) return GenTime((int) m_mltConsumer->position(), m_fps); else return GenTime(); } int Render::seekFramePosition() const { if (m_mltConsumer) return (int) m_mltConsumer->position(); return 0; } void Render::emitFrameUpdated(Mlt::Frame& frame) { Q_UNUSED(frame) return; /*TODO: fix movit crash mlt_image_format format = mlt_image_rgb24; int width = 0; int height = 0; //frame.set("rescale.interp", "bilinear"); //frame.set("deinterlace_method", "onefield"); //frame.set("top_field_first", -1); const uchar* image = frame.get_image(format, width, height); QImage qimage(width, height, QImage::Format_RGB888); //Format_ARGB32_Premultiplied); memcpy(qimage.scanLine(0), image, width * height * 3); emit frameUpdated(qimage); */ } int Render::getCurrentSeekPosition() const { if (requestedSeekPosition != SEEK_INACTIVE) return requestedSeekPosition; return (int) m_mltConsumer->position(); } bool Render::checkFrameNumber(int pos) { if (pos == requestedSeekPosition) { requestedSeekPosition = SEEK_INACTIVE; } if (requestedSeekPosition != SEEK_INACTIVE) { double speed = m_mltProducer->get_speed(); m_mltProducer->set_speed(0); m_mltProducer->seek(requestedSeekPosition); if (speed == 0) { m_mltConsumer->set("refresh", 1); } else m_mltProducer->set_speed(speed); } else { m_isRefreshing = false; if (m_isZoneMode) { if (pos >= m_mltProducer->get_int("out") - 1) { if (m_isLoopMode) { m_mltConsumer->purge(); m_mltProducer->seek((int)(m_loopStart.frames(m_fps))); m_mltProducer->set_speed(1.0); m_mltConsumer->set("refresh", 1); } else { if (m_mltProducer->get_speed() == 0) return false; } } } } return true; } void Render::emitFrameUpdated(QImage img) { emit frameUpdated(img); } void Render::slotCheckSeeking() { if (requestedSeekPosition != SEEK_INACTIVE) { m_mltProducer->seek(requestedSeekPosition); requestedSeekPosition = SEEK_INACTIVE; } } void Render::showAudio(Mlt::Frame& frame) { if (!frame.is_valid() || frame.get_int("test_audio") != 0) { return; } mlt_audio_format audio_format = mlt_audio_s16; //FIXME: should not be hardcoded.. int freq = 48000; int num_channels = 2; int samples = 0; qint16* data = (qint16*)frame.get_audio(audio_format, freq, num_channels, samples); if (!data) { return; } // Data format: [ c00 c10 c01 c11 c02 c12 c03 c13 ... c0{samples-1} c1{samples-1} for 2 channels. // So the vector is of size samples*channels. audioShortVector sampleVector(samples*num_channels); memcpy(sampleVector.data(), data, samples*num_channels*sizeof(qint16)); if (samples > 0) { emit audioSamplesSignal(sampleVector, freq, num_channels, samples); } } /* * MLT playlist direct manipulation. */ void Render::mltCheckLength(Mlt::Tractor *tractor) { int trackNb = tractor->count(); int duration = 0; if (m_isZoneMode) resetZoneMode(); if (trackNb == 1) { QScopedPointer trackProducer(tractor->track(0)); duration = trackProducer->get_playtime(); m_mltProducer->set("out", duration); emit durationChanged(duration); return; } while (trackNb > 1) { QScopedPointer trackProducer(tractor->track(trackNb - 1)); int trackDuration = trackProducer->get_playtime(); if (trackDuration > duration) duration = trackDuration; trackNb--; } QScopedPointer blackTrackProducer(tractor->track(0)); if (blackTrackProducer->get_playtime() - 1 != duration) { Mlt::Playlist blackTrackPlaylist((mlt_playlist) blackTrackProducer->get_service()); QScopedPointer blackclip(blackTrackPlaylist.get_clip(0)); if (!blackclip || blackclip->is_blank() || blackTrackPlaylist.count() != 1) { blackTrackPlaylist.clear(); m_blackClip->set("length", duration + 1); m_blackClip->set("out", duration); Mlt::Producer *black2 = m_blackClip->cut(0, duration); blackTrackPlaylist.insert_at(0, black2, 1); delete black2; } else { if (duration > blackclip->parent().get_length()) { blackclip->parent().set("length", duration + 1); blackclip->parent().set("out", duration); blackclip->set("length", duration + 1); } blackTrackPlaylist.resize_clip(0, 0, duration); } if (m_mltConsumer->position() > duration) { m_mltConsumer->purge(); m_mltProducer->seek(duration); } m_mltProducer->set("out", duration); emit durationChanged(duration); } } Mlt::Producer *Render::getTrackProducer(const QString &id, int track, bool, bool) { Mlt::Service service(m_mltProducer->parent().get_service()); if (service.type() != tractor_type) { qWarning() << "// TRACTOR PROBLEM"; return NULL; } Mlt::Tractor tractor(service); // WARNING: Kdenlive's track numbering is 0 for top track, while in MLT 0 is black track and 1 is the bottom track so we MUST reverse track number // TODO: memleak Mlt::Producer destTrackProducer(tractor.track(tractor.count() - track - 1)); Mlt::Playlist destTrackPlaylist((mlt_playlist) destTrackProducer.get_service()); return getProducerForTrack(destTrackPlaylist, id); } Mlt::Producer *Render::getProducerForTrack(Mlt::Playlist &trackPlaylist, const QString &clipId) { //TODO: find a better way to check if a producer is already inserted in a track ? QString trackName = trackPlaylist.get("id"); QString clipIdWithTrack = clipId + "_" + trackName; Mlt::Producer *prod = NULL; for (int i = 0; i < trackPlaylist.count(); i++) { if (trackPlaylist.is_blank(i)) continue; QScopedPointer p(trackPlaylist.get_clip(i)); QString id = p->parent().get("id"); if (id == clipIdWithTrack) { // This producer already exists in the track, reuse it prod = &p->parent(); break; } } if (prod == NULL) prod = m_binController->getBinProducer(clipId); return prod; } Mlt::Tractor *Render::lockService() { // we are going to replace some clips, purge consumer if (!m_mltProducer) return NULL; QMutexLocker locker(&m_mutex); if (m_mltConsumer) { m_mltConsumer->purge(); } Mlt::Service service(m_mltProducer->parent().get_service()); if (service.type() != tractor_type) { return NULL; } service.lock(); return new Mlt::Tractor(service); } void Render::unlockService(Mlt::Tractor *tractor) { if (tractor) { delete tractor; } if (!m_mltProducer) return; Mlt::Service service(m_mltProducer->parent().get_service()); if (service.type() != tractor_type) { qWarning() << "// TRACTOR PROBLEM"; return; } service.unlock(); } void Render::mltInsertSpace(QMap trackClipStartList, QMap trackTransitionStartList, int track, const GenTime &duration, const GenTime &timeOffset) { if (!m_mltProducer) { //qDebug() << "PLAYLIST NOT INITIALISED //////"; return; } Mlt::Producer parentProd(m_mltProducer->parent()); if (parentProd.get_producer() == NULL) { //qDebug() << "PLAYLIST BROKEN, CANNOT INSERT CLIP //////"; return; } ////qDebug()<<"// CLP STRT LST: "< 0) { trackPlaylist.insert_blank(clipIndex, diff - 1); } else { if (!trackPlaylist.is_blank(clipIndex)) clipIndex --; if (!trackPlaylist.is_blank(clipIndex)) { //qDebug() << "//// ERROR TRYING TO DELETE SPACE FROM " << insertPos; } int position = trackPlaylist.clip_start(clipIndex); int blankDuration = trackPlaylist.clip_length(clipIndex); if (blankDuration + diff == 0) { trackPlaylist.remove(clipIndex); } else trackPlaylist.remove_region(position, -diff); } trackPlaylist.consolidate_blanks(0); } // now move transitions mlt_service serv = m_mltProducer->parent().get_service(); mlt_service nextservice = mlt_service_get_producer(serv); mlt_properties properties = MLT_SERVICE_PROPERTIES(nextservice); QString mlt_type = mlt_properties_get(properties, "mlt_type"); QString resource = mlt_properties_get(properties, "mlt_service"); while (mlt_type == QLatin1String("transition")) { mlt_transition tr = (mlt_transition) nextservice; int currentTrack = mlt_transition_get_b_track(tr); int currentIn = (int) mlt_transition_get_in(tr); int currentOut = (int) mlt_transition_get_out(tr); insertPos = trackTransitionStartList.value(track); if (insertPos != -1) { insertPos += offset; if (track == currentTrack && currentOut > insertPos && resource != QLatin1String("mix")) { mlt_transition_set_in_and_out(tr, currentIn + diff, currentOut + diff); } } nextservice = mlt_service_producer(nextservice); if (nextservice == NULL) break; properties = MLT_SERVICE_PROPERTIES(nextservice); mlt_type = mlt_properties_get(properties, "mlt_type"); resource = mlt_properties_get(properties, "mlt_service"); } } else { for (int trackNb = tractor.count() - 1; trackNb >= 1; --trackNb) { Mlt::Producer trackProducer(tractor.track(trackNb)); Mlt::Playlist trackPlaylist((mlt_playlist) trackProducer.get_service()); //int clipNb = trackPlaylist.count(); insertPos = trackClipStartList.value(trackNb); if (insertPos != -1) { insertPos += offset; /* //qDebug()<<"-------------\nTRACK "< 0) { trackPlaylist.insert_blank(clipIndex, diff - 1); } else { if (!trackPlaylist.is_blank(clipIndex)) { clipIndex --; } if (!trackPlaylist.is_blank(clipIndex)) { //qDebug() << "//// ERROR TRYING TO DELETE SPACE FROM " << insertPos; } int position = trackPlaylist.clip_start(clipIndex); int blankDuration = trackPlaylist.clip_length(clipIndex); if (diff + blankDuration == 0) { trackPlaylist.remove(clipIndex); } else trackPlaylist.remove_region(position, - diff); } trackPlaylist.consolidate_blanks(0); } } // now move transitions mlt_service serv = m_mltProducer->parent().get_service(); mlt_service nextservice = mlt_service_get_producer(serv); mlt_properties properties = MLT_SERVICE_PROPERTIES(nextservice); QString mlt_type = mlt_properties_get(properties, "mlt_type"); QString resource = mlt_properties_get(properties, "mlt_service"); while (mlt_type == QLatin1String("transition")) { mlt_transition tr = (mlt_transition) nextservice; int currentIn = (int) mlt_transition_get_in(tr); int currentOut = (int) mlt_transition_get_out(tr); int currentTrack = mlt_transition_get_b_track(tr); insertPos = trackTransitionStartList.value(currentTrack); if (insertPos != -1) { insertPos += offset; if (currentOut > insertPos && resource != QLatin1String("mix")) { mlt_transition_set_in_and_out(tr, currentIn + diff, currentOut + diff); } } nextservice = mlt_service_producer(nextservice); if (nextservice == NULL) break; properties = MLT_SERVICE_PROPERTIES(nextservice); mlt_type = mlt_properties_get(properties, "mlt_type"); resource = mlt_properties_get(properties, "mlt_service"); } } service.unlock(); mltCheckLength(&tractor); m_isRefreshing = true; m_mltConsumer->set("refresh", 1); } bool Render::mltRemoveTrackEffect(int track, int index, bool updateIndex) { Mlt::Service service(m_mltProducer->parent().get_service()); bool success = false; Mlt::Tractor tractor(service); //TODO: memleak Mlt::Producer trackProducer(tractor.track(track)); Mlt::Playlist trackPlaylist((mlt_playlist) trackProducer.get_service()); Mlt::Service clipService(trackPlaylist.get_service()); service.lock(); int ct = 0; Mlt::Filter *filter = clipService.filter(ct); while (filter) { if ((index == -1 && strcmp(filter->get("kdenlive_id"), "")) || filter->get_int("kdenlive_ix") == index) { if (clipService.detach(*filter) == 0) { delete filter; success = true; } } else if (updateIndex) { // Adjust the other effects index if (filter->get_int("kdenlive_ix") > index) filter->set("kdenlive_ix", filter->get_int("kdenlive_ix") - 1); ct++; } else ct++; filter = clipService.filter(ct); } service.unlock(); refresh(); return success; } bool Render::mltRemoveEffect(int track, GenTime position, int index, bool updateIndex, bool doRefresh) { if (position < GenTime()) { // Remove track effect return mltRemoveTrackEffect(track, index, updateIndex); } Mlt::Service service(m_mltProducer->parent().get_service()); bool success = false; Mlt::Tractor tractor(service); //TODO: memleak Mlt::Producer trackProducer(tractor.track(track)); Mlt::Playlist trackPlaylist((mlt_playlist) trackProducer.get_service()); int clipIndex = trackPlaylist.get_clip_index_at((int) position.frames(m_fps)); QScopedPointer clip(trackPlaylist.get_clip(clipIndex)); if (!clip) { qDebug() << " / / / CANNOT FIND CLIP TO REMOVE EFFECT"; return false; } Mlt::Service clipService(clip->get_service()); success = removeFilterFromService(clipService, index, updateIndex); int duration = clip->get_playtime(); if (doRefresh) { // Check if clip is visible in monitor int diff = trackPlaylist.clip_start(clipIndex) + duration - m_mltProducer->position(); if (diff < 0 || diff > duration) doRefresh = false; } if (doRefresh) refresh(); return success; } //static bool Render::removeFilterFromService(Mlt::Service service, int effectIndex, bool updateIndex) { service.lock(); bool success = false; int ct = 0; Mlt::Filter *filter = service.filter(ct); while (filter) { if ((effectIndex == -1 && strcmp(filter->get("kdenlive_id"), "")) || filter->get_int("kdenlive_ix") == effectIndex) { if (service.detach(*filter) == 0) { delete filter; success = true; } } else if (updateIndex) { // Adjust the other effects index if (filter->get_int("kdenlive_ix") > effectIndex) filter->set("kdenlive_ix", filter->get_int("kdenlive_ix") - 1); ct++; } else ct++; filter = service.filter(ct); } service.unlock(); return success; } bool Render::mltAddTrackEffect(int track, EffectsParameterList params) { Mlt::Service service(m_mltProducer->parent().get_service()); Mlt::Tractor tractor(service); QScopedPointer tk(tractor.track(track)); Mlt::Producer trackProducer(tk.data()); Mlt::Playlist trackPlaylist((mlt_playlist) trackProducer.get_service()); Mlt::Service trackService(trackProducer.get_service()); //trackPlaylist return mltAddEffect(trackService, params, trackProducer.get_playtime() - 1, true); } bool Render::mltAddEffect(int track, GenTime position, EffectsParameterList params, bool doRefresh) { Mlt::Service service(m_mltProducer->parent().get_service()); Mlt::Tractor tractor(service); QScopedPointer tk(tractor.track(track)); Mlt::Producer trackProducer(tk.data()); Mlt::Playlist trackPlaylist((mlt_playlist) trackProducer.get_service()); int clipIndex = trackPlaylist.get_clip_index_at((int) position.frames(m_fps)); QScopedPointer clip(trackPlaylist.get_clip(clipIndex)); if (!clip) { return false; } Mlt::Service clipService(clip->get_service()); int duration = clip->get_playtime(); if (doRefresh) { // Check if clip is visible in monitor int diff = trackPlaylist.clip_start(clipIndex) + duration - m_mltProducer->position(); if (diff < 0 || diff > duration) doRefresh = false; } return mltAddEffect(clipService, params, duration, doRefresh); } bool Render::mltAddEffect(Mlt::Service service, EffectsParameterList params, int duration, bool doRefresh) { bool updateIndex = false; const int filter_ix = params.paramValue(QStringLiteral("kdenlive_ix")).toInt(); int ct = 0; service.lock(); Mlt::Filter *filter = service.filter(ct); while (filter) { if (filter->get_int("kdenlive_ix") == filter_ix) { // A filter at that position already existed, so we will increase all indexes later updateIndex = true; break; } ct++; filter = service.filter(ct); } if (params.paramValue(QStringLiteral("id")) == QLatin1String("speed")) { // special case, speed effect is not really inserted, we just update the other effects index (kdenlive_ix) ct = 0; filter = service.filter(ct); while (filter) { if (filter->get_int("kdenlive_ix") >= filter_ix) { if (updateIndex) filter->set("kdenlive_ix", filter->get_int("kdenlive_ix") + 1); } ct++; filter = service.filter(ct); } service.unlock(); if (doRefresh) refresh(); return true; } // temporarily remove all effects after insert point QList filtersList; ct = 0; filter = service.filter(ct); while (filter) { if (filter->get_int("kdenlive_ix") >= filter_ix) { filtersList.append(filter); service.detach(*filter); } else ct++; filter = service.filter(ct); } bool success = addFilterToService(service, params, duration); // re-add following filters for (int i = 0; i < filtersList.count(); ++i) { Mlt::Filter *filter = filtersList.at(i); if (updateIndex) filter->set("kdenlive_ix", filter->get_int("kdenlive_ix") + 1); service.attach(*filter); } qDeleteAll(filtersList); service.unlock(); if (doRefresh) refresh(); return success; } // static bool Render::addFilterToService(Mlt::Service service, EffectsParameterList params, int duration) { // create filter QString tag = params.paramValue(QStringLiteral("tag")); QLocale locale; ////qDebug() << " / / INSERTING EFFECT: " << tag << ", REGI: " << region; QString kfr = params.paramValue(QStringLiteral("keyframes")); if (!kfr.isEmpty()) { QStringList keyFrames = kfr.split(';', QString::SkipEmptyParts); char *starttag = qstrdup(params.paramValue(QStringLiteral("starttag"), QStringLiteral("start")).toUtf8().constData()); char *endtag = qstrdup(params.paramValue(QStringLiteral("endtag"), QStringLiteral("end")).toUtf8().constData()); ////qDebug() << "// ADDING KEYFRAME TAGS: " << starttag << ", " << endtag; //double max = params.paramValue("max").toDouble(); double min = params.paramValue(QStringLiteral("min")).toDouble(); double factor = params.paramValue(QStringLiteral("factor"), QStringLiteral("1")).toDouble(); double paramOffset = params.paramValue(QStringLiteral("offset"), QStringLiteral("0")).toDouble(); params.removeParam(QStringLiteral("starttag")); params.removeParam(QStringLiteral("endtag")); params.removeParam(QStringLiteral("keyframes")); params.removeParam(QStringLiteral("min")); params.removeParam(QStringLiteral("max")); params.removeParam(QStringLiteral("factor")); params.removeParam(QStringLiteral("offset")); // Special case, only one keyframe, means we want a constant value if (keyFrames.count() == 1) { Mlt::Filter *filter = new Mlt::Filter(*service.profile(), qstrdup(tag.toUtf8().constData())); if (filter && filter->is_valid()) { filter->set("kdenlive_id", qstrdup(params.paramValue(QStringLiteral("id")).toUtf8().constData())); int x1 = keyFrames.at(0).section('=', 0, 0).toInt(); double y1 = keyFrames.at(0).section('=', 1, 1).toDouble(); for (int j = 0; j < params.count(); ++j) { filter->set(params.at(j).name().toUtf8().constData(), params.at(j).value().toUtf8().constData()); } filter->set("in", x1); ////qDebug() << "// ADDING KEYFRAME vals: " << min<<" / "<set(starttag, locale.toString(((min + y1) - paramOffset) / factor).toUtf8().data()); service.attach(*filter); delete filter; } else { delete[] starttag; delete[] endtag; //qDebug() << "filter is NULL"; service.unlock(); return false; } } else for (int i = 0; i < keyFrames.size() - 1; ++i) { Mlt::Filter *filter = new Mlt::Filter(*service.profile(), qstrdup(tag.toUtf8().constData())); if (filter && filter->is_valid()) { filter->set("kdenlive_id", qstrdup(params.paramValue(QStringLiteral("id")).toUtf8().constData())); int x1 = keyFrames.at(i).section('=', 0, 0).toInt(); double y1 = keyFrames.at(i).section('=', 1, 1).toDouble(); int x2 = keyFrames.at(i + 1).section('=', 0, 0).toInt(); double y2 = keyFrames.at(i + 1).section('=', 1, 1).toDouble(); if (x2 == -1) x2 = duration; // non-overlapping sections if (i > 0) { y1 += (y2 - y1) / (x2 - x1); ++x1; } for (int j = 0; j < params.count(); ++j) { filter->set(params.at(j).name().toUtf8().constData(), params.at(j).value().toUtf8().constData()); } filter->set("in", x1); filter->set("out", x2); ////qDebug() << "// ADDING KEYFRAME vals: " << min<<" / "<set(starttag, locale.toString(((min + y1) - paramOffset) / factor).toUtf8().data()); filter->set(endtag, locale.toString(((min + y2) - paramOffset) / factor).toUtf8().data()); service.attach(*filter); delete filter; } else { delete[] starttag; delete[] endtag; //qDebug() << "filter is NULL"; service.unlock(); return false; } } delete[] starttag; delete[] endtag; } else { Mlt::Filter *filter; QString prefix; filter = new Mlt::Filter(*service.profile(), qstrdup(tag.toUtf8().constData())); if (filter && filter->is_valid()) { filter->set("kdenlive_id", qstrdup(params.paramValue(QStringLiteral("id")).toUtf8().constData())); } else { //qDebug() << "filter is NULL"; service.unlock(); return false; } params.removeParam(QStringLiteral("kdenlive_id")); if (params.paramValue(QStringLiteral("kdenlive:sync_in_out")) == QLatin1String("1")) { // This effect must sync in / out with parent clip //params.removeParam(QStringLiteral("_sync_in_out")); filter->set_in_and_out(service.get_int("in"), service.get_int("out")); } for (int j = 0; j < params.count(); ++j) { filter->set((prefix + params.at(j).name()).toUtf8().constData(), params.at(j).value().toUtf8().constData()); } if (tag == QLatin1String("sox")) { QString effectArgs = params.paramValue(QStringLiteral("id")).section('_', 1); params.removeParam(QStringLiteral("id")); params.removeParam(QStringLiteral("kdenlive_ix")); params.removeParam(QStringLiteral("tag")); params.removeParam(QStringLiteral("disable")); params.removeParam(QStringLiteral("region")); for (int j = 0; j < params.count(); ++j) { effectArgs.append(' ' + params.at(j).value()); } ////qDebug() << "SOX EFFECTS: " << effectArgs.simplified(); filter->set("effect", effectArgs.simplified().toUtf8().constData()); } // attach filter to the clip service.attach(*filter); delete filter; } return true; } bool Render::mltEditTrackEffect(int track, EffectsParameterList params) { Mlt::Service service(m_mltProducer->parent().get_service()); Mlt::Tractor tractor(service); //TODO: memleak Mlt::Producer trackProducer(tractor.track(track)); Mlt::Playlist trackPlaylist((mlt_playlist) trackProducer.get_service()); Mlt::Service clipService(trackPlaylist.get_service()); int ct = 0; QString index = params.paramValue(QStringLiteral("kdenlive_ix")); QString tag = params.paramValue(QStringLiteral("tag")); Mlt::Filter *filter = clipService.filter(ct); while (filter) { if (filter->get_int("kdenlive_ix") == index.toInt()) { break; } delete filter; ct++; filter = clipService.filter(ct); } if (!filter) { //qDebug() << "WARINIG, FILTER FOR EDITING NOT FOUND, ADDING IT! " << index << ", " << tag; // filter was not found, it was probably a disabled filter, so add it to the correct place... bool success = false;//mltAddTrackEffect(track, params); return success; } QString prefix; QString ser = filter->get("mlt_service"); if (ser == QLatin1String("region")) prefix = QStringLiteral("filter0."); service.lock(); for (int j = 0; j < params.count(); ++j) { filter->set((prefix + params.at(j).name()).toUtf8().constData(), params.at(j).value().toUtf8().constData()); } service.unlock(); refresh(); return true; } bool Render::mltEditEffect(int track, const GenTime &position, EffectsParameterList params, bool replaceEffect) { int index = params.paramValue(QStringLiteral("kdenlive_ix")).toInt(); QString tag = params.paramValue(QStringLiteral("tag")); if (!params.paramValue(QStringLiteral("keyframes")).isEmpty() || replaceEffect || tag.startsWith(QLatin1String("ladspa")) || tag == QLatin1String("sox") || tag == QLatin1String("autotrack_rectangle")) { // This is a keyframe effect, to edit it, we remove it and re-add it. if (mltRemoveEffect(track, position, index, false)) { if (position < GenTime()) return mltAddTrackEffect(track, params); else return mltAddEffect(track, position, params); } } if (position < GenTime()) { return mltEditTrackEffect(track, params); } // find filter Mlt::Service service(m_mltProducer->parent().get_service()); Mlt::Tractor tractor(service); //TODO: memleak Mlt::Producer trackProducer(tractor.track(track)); Mlt::Playlist trackPlaylist((mlt_playlist) trackProducer.get_service()); int clipIndex = trackPlaylist.get_clip_index_at((int) position.frames(m_fps)); QScopedPointer clip(trackPlaylist.get_clip(clipIndex)); if (!clip) { //qDebug() << "WARINIG, CANNOT FIND CLIP ON track: " << track << ", AT POS: " << position.frames(m_fps); return false; } int duration = clip->get_playtime(); bool needRefresh = true; // Check if clip is visible in monitor int diff = trackPlaylist.clip_start(clipIndex) + duration - m_mltProducer->position(); if (diff < 0 || diff > duration) needRefresh = false; int ct = 0; Mlt::Filter *filter = clip->filter(ct); while (filter) { if (filter->get_int("kdenlive_ix") == index) { break; } delete filter; ct++; filter = clip->filter(ct); } if (!filter) { qDebug() << "WARINIG, FILTER FOR EDITING NOT FOUND, ADDING IT! " << index << ", " << tag; // filter was not found, it was probably a disabled filter, so add it to the correct place... bool success = mltAddEffect(track, position, params); return success; } ct = 0; QString ser = filter->get("mlt_service"); QList filtersList; service.lock(); if (ser != tag) { // Effect service changes, delete effect and re-add it clip->detach(*filter); delete filter; // Delete all effects after deleted one filter = clip->filter(ct); while (filter) { if (filter->get_int("kdenlive_ix") > index) { filtersList.append(filter); clip->detach(*filter); } else ct++; filter = clip->filter(ct); } // re-add filter addFilterToService(*clip, params, clip->get_playtime()); service.unlock(); if (needRefresh) refresh(); return true; } if (params.hasParam(QStringLiteral("kdenlive:sync_in_out"))) { if (params.paramValue(QStringLiteral("kdenlive:sync_in_out")) == QLatin1String("1")) { // This effect must sync in / out with parent clip //params.removeParam(QStringLiteral("sync_in_out")); filter->set_in_and_out(clip->get_in(), clip->get_out()); } else { // Reset in/out properties filter->set("in", (char*)NULL); filter->set("out", (char*)NULL); } } for (int j = 0; j < params.count(); ++j) { filter->set(params.at(j).name().toUtf8().constData(), params.at(j).value().toUtf8().constData()); } for (int j = 0; j < filtersList.count(); ++j) { clip->attach(*(filtersList.at(j))); } qDeleteAll(filtersList); service.unlock(); if (needRefresh) doRefresh(); return true; } bool Render::mltEnableEffects(int track, const GenTime &position, const QList &effectIndexes, bool disable) { if (position < GenTime()) { return mltEnableTrackEffects(track, effectIndexes, disable); } // find filter Mlt::Service service(m_mltProducer->parent().get_service()); Mlt::Tractor tractor(service); //TODO: memleak Mlt::Producer trackProducer(tractor.track(track)); Mlt::Playlist trackPlaylist((mlt_playlist) trackProducer.get_service()); int clipIndex = trackPlaylist.get_clip_index_at((int) position.frames(m_fps)); QScopedPointer clip(trackPlaylist.get_clip(clipIndex)); if (!clip) { //qDebug() << "WARINIG, CANNOT FIND CLIP ON track: " << track << ", AT POS: " << position.frames(m_fps); return false; } int duration = clip->get_playtime(); bool doRefresh = true; // Check if clip is visible in monitor int diff = trackPlaylist.clip_start(clipIndex) + duration - m_mltProducer->position(); if (diff < 0 || diff > duration) doRefresh = false; int ct = 0; Mlt::Filter *filter = clip->filter(ct); service.lock(); while (filter) { if (effectIndexes.contains(filter->get_int("kdenlive_ix"))) { filter->set("disable", (int) disable); } ct++; filter = clip->filter(ct); } service.unlock(); if (doRefresh) refresh(); return true; } bool Render::mltEnableTrackEffects(int track, const QList &effectIndexes, bool disable) { Mlt::Service service(m_mltProducer->parent().get_service()); Mlt::Tractor tractor(service); //TODO: memleak Mlt::Producer trackProducer(tractor.track(track)); Mlt::Playlist trackPlaylist((mlt_playlist) trackProducer.get_service()); Mlt::Service clipService(trackPlaylist.get_service()); int ct = 0; Mlt::Filter *filter = clipService.filter(ct); service.lock(); while (filter) { if (effectIndexes.contains(filter->get_int("kdenlive_ix"))) { filter->set("disable", (int) disable); } ct++; filter = clipService.filter(ct); } service.unlock(); refresh(); return true; } void Render::mltUpdateEffectPosition(int track, const GenTime &position, int oldPos, int newPos) { Mlt::Service service(m_mltProducer->parent().get_service()); Mlt::Tractor tractor(service); //TODO: memleak Mlt::Producer trackProducer(tractor.track(track)); Mlt::Playlist trackPlaylist((mlt_playlist) trackProducer.get_service()); int clipIndex = trackPlaylist.get_clip_index_at((int) position.frames(m_fps)); QScopedPointer clip(trackPlaylist.get_clip(clipIndex)); if (!clip) { //qDebug() << "WARINIG, CANNOT FIND CLIP ON track: " << track << ", AT POS: " << position.frames(m_fps); return; } Mlt::Service clipService(clip->get_service()); int duration = clip->get_playtime(); bool doRefresh = true; // Check if clip is visible in monitor int diff = trackPlaylist.clip_start(clipIndex) + duration - m_mltProducer->position(); if (diff < 0 || diff > duration) doRefresh = false; int ct = 0; Mlt::Filter *filter = clipService.filter(ct); while (filter) { int pos = filter->get_int("kdenlive_ix"); if (pos == oldPos) { filter->set("kdenlive_ix", newPos); } else ct++; filter = clipService.filter(ct); } if (doRefresh) refresh(); } void Render::mltMoveEffect(int track, const GenTime &position, int oldPos, int newPos) { if (position < GenTime()) { mltMoveTrackEffect(track, oldPos, newPos); return; } Mlt::Service service(m_mltProducer->parent().get_service()); Mlt::Tractor tractor(service); //TODO: memleak Mlt::Producer trackProducer(tractor.track(track)); Mlt::Playlist trackPlaylist((mlt_playlist) trackProducer.get_service()); int clipIndex = trackPlaylist.get_clip_index_at((int) position.frames(m_fps)); QScopedPointer clip(trackPlaylist.get_clip(clipIndex)); if (!clip) { //qDebug() << "WARINIG, CANNOT FIND CLIP ON track: " << track << ", AT POS: " << position.frames(m_fps); return; } Mlt::Service clipService(clip->get_service()); int duration = clip->get_playtime(); bool doRefresh = true; // Check if clip is visible in monitor int diff = trackPlaylist.clip_start(clipIndex) + duration - m_mltProducer->position(); if (diff < 0 || diff > duration) doRefresh = false; int ct = 0; QList filtersList; Mlt::Filter *filter = clipService.filter(ct); if (newPos > oldPos) { bool found = false; while (filter) { if (!found && filter->get_int("kdenlive_ix") == oldPos) { filter->set("kdenlive_ix", newPos); filtersList.append(filter); clipService.detach(*filter); filter = clipService.filter(ct); while (filter && filter->get_int("kdenlive_ix") <= newPos) { filter->set("kdenlive_ix", filter->get_int("kdenlive_ix") - 1); ct++; filter = clipService.filter(ct); } found = true; } if (filter && filter->get_int("kdenlive_ix") > newPos) { filtersList.append(filter); clipService.detach(*filter); } else ct++; filter = clipService.filter(ct); } } else { while (filter) { if (filter->get_int("kdenlive_ix") == oldPos) { filter->set("kdenlive_ix", newPos); filtersList.append(filter); clipService.detach(*filter); } else ct++; filter = clipService.filter(ct); } ct = 0; filter = clipService.filter(ct); while (filter) { int pos = filter->get_int("kdenlive_ix"); if (pos >= newPos) { if (pos < oldPos) filter->set("kdenlive_ix", pos + 1); filtersList.append(filter); clipService.detach(*filter); } else ct++; filter = clipService.filter(ct); } } for (int i = 0; i < filtersList.count(); ++i) { clipService.attach(*(filtersList.at(i))); } qDeleteAll(filtersList); if (doRefresh) refresh(); } void Render::mltMoveTrackEffect(int track, int oldPos, int newPos) { Mlt::Service service(m_mltProducer->parent().get_service()); Mlt::Tractor tractor(service); //TODO: memleak Mlt::Producer trackProducer(tractor.track(track)); Mlt::Playlist trackPlaylist((mlt_playlist) trackProducer.get_service()); Mlt::Service clipService(trackPlaylist.get_service()); int ct = 0; QList filtersList; Mlt::Filter *filter = clipService.filter(ct); if (newPos > oldPos) { bool found = false; while (filter) { if (!found && filter->get_int("kdenlive_ix") == oldPos) { filter->set("kdenlive_ix", newPos); filtersList.append(filter); clipService.detach(*filter); filter = clipService.filter(ct); while (filter && filter->get_int("kdenlive_ix") <= newPos) { filter->set("kdenlive_ix", filter->get_int("kdenlive_ix") - 1); ct++; filter = clipService.filter(ct); } found = true; } if (filter && filter->get_int("kdenlive_ix") > newPos) { filtersList.append(filter); clipService.detach(*filter); } else ct++; filter = clipService.filter(ct); } } else { while (filter) { if (filter->get_int("kdenlive_ix") == oldPos) { filter->set("kdenlive_ix", newPos); filtersList.append(filter); clipService.detach(*filter); } else ct++; filter = clipService.filter(ct); } ct = 0; filter = clipService.filter(ct); while (filter) { int pos = filter->get_int("kdenlive_ix"); if (pos >= newPos) { if (pos < oldPos) filter->set("kdenlive_ix", pos + 1); filtersList.append(filter); clipService.detach(*filter); } else ct++; filter = clipService.filter(ct); } } for (int i = 0; i < filtersList.count(); ++i) { clipService.attach(*(filtersList.at(i))); } qDeleteAll(filtersList); refresh(); } bool Render::mltResizeClipCrop(ItemInfo info, GenTime newCropStart) { Mlt::Service service(m_mltProducer->parent().get_service()); int newCropFrame = (int) newCropStart.frames(m_fps); Mlt::Tractor tractor(service); Mlt::Producer trackProducer(tractor.track(info.track)); Mlt::Playlist trackPlaylist((mlt_playlist) trackProducer.get_service()); if (trackPlaylist.is_blank_at(info.startPos.frames(m_fps))) { //qDebug() << "//////// ERROR RSIZING BLANK CLIP!!!!!!!!!!!"; return false; } service.lock(); int clipIndex = trackPlaylist.get_clip_index_at(info.startPos.frames(m_fps)); QScopedPointer clip(trackPlaylist.get_clip(clipIndex)); if (clip == NULL) { //qDebug() << "//////// ERROR RSIZING NULL CLIP!!!!!!!!!!!"; service.unlock(); return false; } int previousStart = clip->get_in(); int previousOut = clip->get_out(); if (previousStart == newCropFrame) { //qDebug() << "//////// No ReSIZING Required"; service.unlock(); return true; } int frameOffset = newCropFrame - previousStart; trackPlaylist.resize_clip(clipIndex, newCropFrame, previousOut + frameOffset); service.unlock(); m_isRefreshing = true; m_mltConsumer->set("refresh", 1); return true; } QList Render::checkTrackSequence(int track) { QList list; Mlt::Service service(m_mltProducer->parent().get_service()); if (service.type() != tractor_type) { qWarning() << "// TRACTOR PROBLEM"; return list; } Mlt::Tractor tractor(service); service.lock(); Mlt::Producer trackProducer(tractor.track(track)); Mlt::Playlist trackPlaylist((mlt_playlist) trackProducer.get_service()); int clipNb = trackPlaylist.count(); ////qDebug() << "// PARSING SCENE TRACK: " << t << ", CLIPS: " << clipNb; for (int i = 0; i < clipNb; ++i) { QScopedPointer c(trackPlaylist.get_clip(i)); int pos = trackPlaylist.clip_start(i); if (!list.contains(pos)) list.append(pos); pos += c->get_playtime(); if (!list.contains(pos)) list.append(pos); } return list; } void Render::cloneProperties(Mlt::Properties &dest, Mlt::Properties &source) { int count = source.count(); int i = 0; for ( i = 0; i < count; i ++ ) { char *value = source.get(i); if ( value != NULL ) { char *name = source.get_name( i ); if (name != NULL && name[0] != '_') dest.set(name, value); } } } void Render::fillSlowMotionProducers() { if (m_mltProducer == NULL) return; Mlt::Service service(m_mltProducer->parent().get_service()); if (service.type() != tractor_type) return; Mlt::Tractor tractor(service); int trackNb = tractor.count(); for (int t = 1; t < trackNb; ++t) { Mlt::Producer *tt = tractor.track(t); Mlt::Producer trackProducer(tt); delete tt; Mlt::Playlist trackPlaylist((mlt_playlist) trackProducer.get_service()); if (!trackPlaylist.is_valid()) continue; int clipNb = trackPlaylist.count(); for (int i = 0; i < clipNb; ++i) { QScopedPointer c(trackPlaylist.get_clip(i)); Mlt::Producer *nprod = new Mlt::Producer(c->get_parent()); if (nprod) { QString id = nprod->parent().get("id"); if (id.startsWith(QLatin1String("slowmotion:")) && !nprod->is_blank()) { // this is a slowmotion producer, add it to the list QString url = QString::fromUtf8(nprod->get("resource")); int strobe = nprod->get_int("strobe"); if (strobe > 1) url.append("&strobe=" + QString::number(strobe)); if (!m_slowmotionProducers.contains(url)) { m_slowmotionProducers.insert(url, nprod); } } else delete nprod; } } } } //Updates all transitions QList Render::mltInsertTrack(int ix, const QString &name, bool videoTrack, int lowestVideoTrack) { QList transitionInfos; // Track add / delete was only added recently in MLT (pre 0.9.8 release). #if (LIBMLT_VERSION_INT < 0x0908) Q_UNUSED(ix) Q_UNUSED(name) Q_UNUSED(videoTrack) qDebug()<<"Track insertion requires a more recent MLT version"; return transitionInfos; #else Mlt::Service service(m_mltProducer->parent().get_service()); if (service.type() != tractor_type) { qWarning() << "// TRACTOR PROBLEM"; return QList (); } blockSignals(true); service.lock(); Mlt::Tractor tractor(service); Mlt::Playlist playlist(*service.profile()); playlist.set("kdenlive:track_name", name.toUtf8().constData()); int ct = tractor.count(); if (ix > ct) { //qDebug() << "// ERROR, TRYING TO insert TRACK " << ix << ", max: " << ct; ix = ct; } tractor.insert_track(playlist, ix); Mlt::Producer newProd(tractor.track(ix)); if (!videoTrack) { newProd.set("kdenlive:audio_track", 1); newProd.set("hide", 1); } checkMaxThreads(); tractor.refresh(); Mlt::Field *field = tractor.field(); // Add audio mix transition to last track Mlt::Transition mix(*m_qmlView->profile(), "mix"); mix.set("a_track", 0); mix.set("b_track", ix); mix.set("always_active", 1); mix.set("internal_added", 237); mix.set("combine", 1); field->plant_transition(mix, 0, ix); if (videoTrack) { if (ix <= lowestVideoTrack) { // Track was inserted as lowest video track, it should not have a composite, but previous lowest should ix = lowestVideoTrack + 1; } Mlt::Transition composite(*m_qmlView->profile(), KdenliveSettings::gpu_accel() ? "movit.overlay" : "frei0r.cairoblend"); if (composite.is_valid()) { composite.set("a_track", ix - 1); composite.set("b_track", ix); composite.set("internal_added", 237); field->plant_transition(composite, ix - 1, ix); } } /* // re-add transitions for (int i = trList.count() - 1; i >= 0; --i) { field->plant_transition(*trList.at(i), trList.at(i)->get_a_track(), trList.at(i)->get_b_track()); } qDeleteAll(trList); */ service.unlock(); blockSignals(false); return transitionInfos; #endif } void Render::sendFrameUpdate() { if (m_mltProducer) { Mlt::Frame * frame = m_mltProducer->get_frame(); emitFrameUpdated(*frame); delete frame; } } Mlt::Producer* Render::getProducer() { return m_mltProducer; } const QString Render::activeClipId() { if (m_mltProducer) return m_mltProducer->get("id"); return QString(); } //static bool Render::getBlackMagicDeviceList(KComboBox *devicelist, bool force) { if (!force && !KdenliveSettings::decklink_device_found()) return false; Mlt::Profile profile; Mlt::Producer bm(profile, "decklink"); int found_devices = 0; if (bm.is_valid()) { bm.set("list_devices", 1); found_devices = bm.get_int("devices"); } else KdenliveSettings::setDecklink_device_found(false); if (found_devices <= 0) { devicelist->setEnabled(false); return false; } KdenliveSettings::setDecklink_device_found(true); for (int i = 0; i < found_devices; ++i) { char *tmp = qstrdup(QStringLiteral("device.%1").arg(i).toUtf8().constData()); devicelist->addItem(bm.get(tmp)); delete[] tmp; } return true; } bool Render::getBlackMagicOutputDeviceList(KComboBox *devicelist, bool force) { if (!force && !KdenliveSettings::decklink_device_found()) return false; Mlt::Profile profile; Mlt::Consumer bm(profile, "decklink"); int found_devices = 0; if (bm.is_valid()) { bm.set("list_devices", 1);; found_devices = bm.get_int("devices"); } else KdenliveSettings::setDecklink_device_found(false); if (found_devices <= 0) { devicelist->setEnabled(false); return false; } KdenliveSettings::setDecklink_device_found(true); for (int i = 0; i < found_devices; ++i) { char *tmp = qstrdup(QStringLiteral("device.%1").arg(i).toUtf8().constData()); devicelist->addItem(bm.get(tmp)); delete[] tmp; } return true; } //static bool Render::checkX11Grab() { if (KdenliveSettings::rendererpath().isEmpty() || KdenliveSettings::ffmpegpath().isEmpty()) return false; QProcess p; QStringList args; args << QStringLiteral("avformat:f-list"); p.start(KdenliveSettings::rendererpath(), args); if (!p.waitForStarted()) return false; if (!p.waitForFinished()) return false; QByteArray result = p.readAllStandardError(); return result.contains("x11grab"); } double Render::getMltVersionInfo(const QString &tag) { double version = 0; Mlt::Properties *metadata = m_binController->mltRepository()->metadata(producer_type, tag.toUtf8().data()); if (metadata && metadata->is_valid()) { version = metadata->get_double("version"); } if (metadata) delete metadata; return version; } Mlt::Producer *Render::getBinProducer(const QString &id) { return m_binController->getBinProducer(id); } Mlt::Producer *Render::getBinVideoProducer(const QString &id) { return m_binController->getBinVideoProducer(id); } void Render::loadExtraProducer(const QString &id, Mlt::Producer *prod) { m_binController->loadExtraProducer(id, prod); } const QString Render::getBinProperty(const QString &name) { return m_binController->getProperty(name); } void Render::setVolume(double volume) { if (m_mltConsumer) { if (m_mltConsumer->get("mlt_service") == QStringLiteral("multi")) { m_mltConsumer->set("0.volume", volume); } else { m_mltConsumer->set("volume", volume); } } } bool Render::storeSlowmotionProducer(const QString &url, Mlt::Producer *prod, bool replace) { if (!m_slowmotionProducers.contains(url)) { m_slowmotionProducers.insert(url, prod); return true; } else if (replace) { Mlt::Producer *old = m_slowmotionProducers.take(url); delete old; m_slowmotionProducers.insert(url, prod); return true; } return false; } Mlt::Producer *Render::getSlowmotionProducer(const QString &url) { if (m_slowmotionProducers.contains(url)) { return m_slowmotionProducers.value(url); } return NULL; } void Render::updateSlowMotionProducers(const QString &id, QMap passProperties) { QMapIterator i(m_slowmotionProducers); Mlt::Producer *prod; while (i.hasNext()) { i.next(); prod = i.value(); QString currentId = prod->get("id"); if (currentId.startsWith("slowmotion:" + id + ":")) { QMapIterator j(passProperties); while (j.hasNext()) { j.next(); prod->set(j.key().toUtf8().constData(), j.value().toUtf8().constData()); } } } } + +void Render::previewRendering(QPoint zone, const QString &cacheDir, const QString &documentId) +{ + if (m_previewThread.isRunning()) { + qDebug()<<"/ / /Already processing a preview render, abort"; + return; + } + QDir dir(cacheDir); + dir.mkpath(QStringLiteral(".")); + // Data is rendered in 200 frames chunks + int startChunk = zone.x() / 200; + int endChunk = rintl(zone.y() / 200); + // Save temporary scenelist + QString sceneListFile = dir.absoluteFilePath(documentId + ".mlt"); + Mlt::Consumer xmlConsumer(*m_qmlView->profile(), "xml", sceneListFile.toUtf8().constData()); + if (!xmlConsumer.is_valid()) + return; + m_mltProducer->optimise(); + xmlConsumer.set("terminate_on_pause", 1); + Mlt::Producer prod(m_mltProducer->get_producer()); + if (!prod.is_valid()) + return; + xmlConsumer.connect(prod); + xmlConsumer.run(); + m_previewThread = QtConcurrent::run(this, &Render::doPreviewRender, startChunk, endChunk, dir, documentId, sceneListFile); +} + +void Render::doPreviewRender(int start, int end, QDir folder, QString id, QString scene) +{ + int progress; + for (int i = start; i <= end; ++i) { + if (m_abortPreview) + break; + QString fileName = id + QString("-%1.mp4").arg(i); + if (end > start) { + progress = (double) (i - start + 1) / (end +1 - start) * 100; + } else { + progress = 100; + } + if (folder.exists(fileName)) { + // This chunk already exists + emit previewRender(i * 200, folder.absoluteFilePath(fileName), progress); + continue; + } + // Build rendering process + QStringList args; + args << scene; + args << "in=" + QString::number(i * 200); + args << "out=" + QString::number(i * 200 + 199); + args << "-consumer" << "avformat:" + folder.absoluteFilePath(fileName); + int result = QProcess::execute(KdenliveSettings::rendererpath(), args); + if (result < 0) { + // Something is wrong, abort + break; + } + emit previewRender(i * 200, folder.absoluteFilePath(fileName), progress); + } + QFile::remove(scene); + m_abortPreview = false; +} diff --git a/src/renderer.h b/src/renderer.h index 58b6b3603..ea4ae69ba 100644 --- a/src/renderer.h +++ b/src/renderer.h @@ -1,413 +1,417 @@ /*************************************************************************** krender.h - description ------------------- begin : Fri Nov 22 2002 copyright : (C) 2002 by Jason Wood (jasonwood@blueyonder.co.uk) copyright : (C) 2010 by Jean-Baptiste Mardelle (jb@kdenlive.org) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ /** * @class Render * @brief Client side of the interface to a renderer. * * REFACTORING NOTE -- There is most likely no point in trying to refactor * the renderer, it is better re-written directly (see refactoring branch) * since there is a lot of code duplication, no documentation, and several * hacks that have emerged from the previous two problems. * * From Kdenlive's point of view, you treat the Render object as the renderer, * and simply use it as if it was local. Calls are asynchronous - you send a * call out, and then receive the return value through the relevant signal that * get's emitted once the call completes. */ #ifndef RENDERER_H #define RENDERER_H #include "gentime.h" #include "definitions.h" #include "monitor/abstractmonitor.h" #include "mltcontroller/effectscontroller.h" #include #include #include #include #include #include #include #include #include #include #include +#include class KComboBox; class BinController; class ClipController; class GLWidget; namespace Mlt { class Consumer; class Playlist; class Properties; class Tractor; class Transition; class Frame; class Field; class Producer; class Filter; class Profile; class Service; class Event; } class MltErrorEvent : public QEvent { public: explicit MltErrorEvent(const QString &message) : QEvent(QEvent::User), m_message(message) { } QString message() const { return m_message; } private: QString m_message; }; class Render: public AbstractRender { Q_OBJECT public: enum FailStates { OK = 0, APP_NOEXIST }; /** @brief Build a MLT Renderer * @param rendererName A unique identifier for this renderer * @param winid The parent widget identifier (required for SDL display). Set to 0 for OpenGL rendering * @param profile The MLT profile used for the renderer (default one will be used if empty). */ Render(Kdenlive::MonitorId rendererName, BinController *binController, GLWidget *qmlView, QWidget *parent = 0); /** @brief Destroy the MLT Renderer. */ virtual ~Render(); /** @brief Seeks the renderer clip to the given time. */ void seek(const GenTime &time); void seekToFrameDiff(int diff); /** @brief Sets the current MLT producer playlist. * @param list The xml describing the playlist * @param position (optional) time to seek to */ int setSceneList(const QDomDocument &list, int position = 0); /** @brief Sets the current MLT producer playlist. * @param list new playlist * @param position (optional) time to seek to * @return 0 when it has success, different from 0 otherwise * * Creates the producer from the text playlist. */ int setSceneList(QString playlist, int position = 0); bool updateProducer(Mlt::Producer *producer); bool setProducer(Mlt::Producer *producer, int position, bool isActive); /** @brief Get the current MLT producer playlist. * @return A string describing the playlist */ const QString sceneList(); bool saveSceneList(QString path, QDomElement kdenliveData = QDomElement()); /** @brief Tells the renderer to play the scene at the specified speed, * @param speed speed to play the scene to * * The speed is relative to normal playback, e.g. 1.0 is normal speed, 0.0 * is paused, -1.0 means play backwards. It does not specify start/stop */ void play(double speed); void switchPlay(bool play); /** @brief Stops playing. * @param startTime time to seek to */ void stop(const GenTime &startTime); int volume() const; QImage extractFrame(int frame_position, const QString &path = QString(), int width = -1, int height = -1); /** @brief Plays the scene starting from a specific time. * @param startTime time to start playing the scene from */ void play(const GenTime & startTime); bool playZone(const GenTime & startTime, const GenTime & stopTime); void loopZone(const GenTime & startTime, const GenTime & stopTime); /** @brief Return true if we are currently playing */ bool isPlaying() const; /** @brief Returns the speed at which the renderer is currently playing. * * It returns 0.0 when the renderer is not playing anything. */ double playSpeed() const; /** @brief Returns the current seek position of the renderer. */ GenTime seekPosition() const; int seekFramePosition() const; void emitFrameUpdated(Mlt::Frame&); double fps() const; /** @brief Returns the width of a frame for this profile. */ int frameRenderWidth() const; /** @brief Returns the display width of a frame for this profile. */ int renderWidth() const; /** @brief Returns the height of a frame for this profile. */ int renderHeight() const; /** @brief Returns display aspect ratio. */ double dar() const; /** @brief Returns sample aspect ratio. */ double sar() const; /** @brief Start the MLT monitor consumer. */ void startConsumer(); /* * Playlist manipulation. */ void mltCheckLength(Mlt::Tractor *tractor); Mlt::Producer *getSlowmotionProducer(const QString &url); void mltInsertSpace(QMap trackClipStartList, QMap trackTransitionStartList, int track, const GenTime &duration, const GenTime &timeOffset); int mltGetSpaceLength(const GenTime &pos, int track, bool fromBlankStart); bool mltResizeClipCrop(ItemInfo info, GenTime newCropStart); /** @brief Deletes an effect from a clip in MLT's playlist. */ bool mltRemoveEffect(int track, GenTime position, int index, bool updateIndex, bool doRefresh = true); static bool removeFilterFromService(Mlt::Service service, int effectIndex, bool updateIndex); bool mltRemoveTrackEffect(int track, int index, bool updateIndex); /** @brief Adds an effect to a clip in MLT's playlist. */ bool mltAddEffect(int track, GenTime position, EffectsParameterList params, bool doRefresh = true); static bool addFilterToService(Mlt::Service service, EffectsParameterList params, int duration); bool mltAddEffect(Mlt::Service service, EffectsParameterList params, int duration, bool doRefresh); bool mltAddTrackEffect(int track, EffectsParameterList params); /** @brief Enable / disable clip effects. * @param track The track where the clip is * @param position The start position of the clip * @param effectIndexes The list of effect indexes to enable / disable * @param disable True if effects should be disabled, false otherwise */ bool mltEnableEffects(int track, const GenTime &position, const QList &effectIndexes, bool disable); /** @brief Enable / disable track effects. * @param track The track where the effect is * @param effectIndexes The list of effect indexes to enable / disable * @param disable True if effects should be disabled, false otherwise */ bool mltEnableTrackEffects(int track, const QList &effectIndexes, bool disable); /** @brief Edits an effect parameters in MLT's playlist. */ bool mltEditEffect(int track, const GenTime &position, EffectsParameterList params, bool replaceEffect); bool mltEditTrackEffect(int track, EffectsParameterList params); /** @brief Updates the "kdenlive_ix" (index) value of an effect. */ void mltUpdateEffectPosition(int track, const GenTime &position, int oldPos, int newPos); /** @brief Changes the order of effects in MLT's playlist. * * It switches effects from oldPos and newPos, updating the "kdenlive_ix" * (index) value. */ void mltMoveEffect(int track, const GenTime &position, int oldPos, int newPos); void mltMoveTrackEffect(int track, int oldPos, int newPos); QList mltInsertTrack(int ix, const QString &name, bool videoTrack, int lowestVideoTrack); //const QList producersList(); void setDropFrames(bool show); /** @brief Sets an MLT consumer property. */ void setConsumerProperty(const QString &name, const QString &value); void showAudio(Mlt::Frame&); QList checkTrackSequence(int); void sendFrameUpdate(); /** @brief Returns a pointer to the main producer. */ Mlt::Producer *getProducer(); /** @brief Returns a pointer to the bin's playlist. */ /** @brief Lock the MLT service */ Mlt::Tractor *lockService(); /** @brief Unlock the MLT service */ void unlockService(Mlt::Tractor *tractor); const QString activeClipId(); /** @brief Fill a combobox with the found blackmagic devices */ static bool getBlackMagicDeviceList(KComboBox *devicelist, bool force = false); static bool getBlackMagicOutputDeviceList(KComboBox *devicelist, bool force = false); /** @brief Get current seek pos requested or SEEK_INACTIVE if we are not currently seeking */ int requestedSeekPosition; /** @brief Get current seek pos requested of current producer pos if not seeking */ int getCurrentSeekPosition() const; /** @brief Create a producer from url and load it in the monitor */ void loadUrl(const QString &url); /** @brief Check if the installed FFmpeg / Libav supports x11grab */ static bool checkX11Grab(); /** @brief Get a track producer from a clip's id * Deprecated, track producers are now handled in timeline/track.cpp */ Q_DECL_DEPRECATED Mlt::Producer *getTrackProducer(const QString &id, int track, bool audioOnly = false, bool videoOnly = false); /** @brief Ask to set this monitor as active */ void setActiveMonitor(); QSemaphore showFrameSemaphore; bool externalConsumer; /** @brief Returns the current version of an MLT's producer module */ double getMltVersionInfo(const QString &tag); /** @brief Get a clip's master producer */ Mlt::Producer *getBinProducer(const QString &id); /** @brief Get a clip's video only producer */ Mlt::Producer *getBinVideoProducer(const QString &id); /** @brief Load extra producers (video only, slowmotion) from timeline */ void loadExtraProducer(const QString &id, Mlt::Producer *prod); /** @brief Get a property from the bin's playlist */ const QString getBinProperty(const QString &name); void setVolume(double volume); /** @brief Stop all activities in preparation for a change in profile */ void prepareProfileReset(double fps); void finishProfileReset(); void updateSlowMotionProducers(const QString &id, QMap passProperties); + void previewRendering(QPoint zone, const QString &cacheDir, const QString &documentId); private: /** @brief The name of this renderer. * * Useful to identify the renderers by what they do - e.g. background * rendering, workspace monitor, etc. */ Kdenlive::MonitorId m_name; Mlt::Consumer * m_mltConsumer; Mlt::Producer * m_mltProducer; Mlt::Event *m_showFrameEvent; Mlt::Event *m_pauseEvent; + QFuture m_previewThread; BinController *m_binController; GLWidget *m_qmlView; double m_fps; /** @brief True if we are playing a zone. * * It's determined by the "in" and "out" properties being temporarily * changed. */ bool m_isZoneMode; bool m_isLoopMode; GenTime m_loopStart; Mlt::Producer *m_blackClip; QTimer m_refreshTimer; QMutex m_mutex; QMutex m_infoMutex; QLocale m_locale; /** @brief True if this monitor is active. */ bool m_isActive; /** @brief True if the consumer is currently refreshing itself. */ bool m_isRefreshing; - + bool m_abortPreview; void closeMlt(); QMap m_slowmotionProducers; - /** @brief Build the MLT Consumer object with initial settings. * @param profileName The MLT profile to use for the consumer */ //void buildConsumer(); /** @brief Restore normal mode */ void resetZoneMode(); void fillSlowMotionProducers(); /** @brief Make sure we inform MLT if we need a lot of threads for avformat producer */ void checkMaxThreads(); /** @brief Clone serialisable properties only */ void cloneProperties(Mlt::Properties &dest, Mlt::Properties &source); /** @brief Get a track producer from a clip's id */ Mlt::Producer *getProducerForTrack(Mlt::Playlist &trackPlaylist, const QString &clipId); - + private slots: /** @brief Refreshes the monitor display. */ void refresh(); void slotCheckSeeking(); + void doPreviewRender(int start, int end, QDir folder, QString id, QString scene); signals: /** @brief The renderer stopped, either playing or rendering. */ void stopped(); /** @brief The renderer started playing. */ void playing(double); /** @brief The renderer started rendering. */ void rendering(const GenTime &); /** @brief An error occurred within this renderer. */ void error(const QString &, const QString &); void durationChanged(int, int offset = 0); void rendererPosition(int); void rendererStopped(int); /** @brief A clip has changed, we must reload timeline producers. */ void replaceTimelineProducer(const QString&); void updateTimelineProducer(const QString&); /** @brief Load project notes. */ void setDocumentNotes(const QString&); /** @brief The renderer received a reply to a getFileProperties request. */ void gotFileProperties(requestClipInfo,ClipController *); /** @brief A frame's image has to be shown. * * Used in Mac OS X. */ void showImageSignal(QImage); void showAudioSignal(const QVector &); void checkSeeking(); /** @brief Activate current monitor. */ void activateMonitor(Kdenlive::MonitorId); void mltFrameReceived(Mlt::Frame *); /** @brief We want to replace a clip with another, but before we need to change clip producer id so that there is no interference*/ void prepareTimelineReplacement(const QString &); + void previewRender(int frame, const QString &file, int progress); public slots: /** @brief Starts the consumer. */ void start(); /** @brief Stops the consumer. */ void stop(); int getLength(); /** @brief Checks if the file is readable by MLT. */ bool isValid(const QUrl &url); void slotSwitchFullscreen(); void seekToFrame(int pos); /** @brief Starts a timer to query for a refresh. */ void doRefresh(); void emitFrameUpdated(QImage img); /** @brief Save a part of current timeline to an xml file. */ void saveZone(QPoint zone); /** @brief Renderer moved to a new frame, check seeking */ bool checkFrameNumber(int pos); /** @brief Keep a reference to slowmo producer. Returns false is producer is already stored */ bool storeSlowmotionProducer(const QString &url, Mlt::Producer *prod, bool replace = false); void seek(int time); }; #endif diff --git a/src/timeline/customruler.cpp b/src/timeline/customruler.cpp index b96495747..d5bd868ef 100644 --- a/src/timeline/customruler.cpp +++ b/src/timeline/customruler.cpp @@ -1,498 +1,513 @@ /*************************************************************************** * Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@kdenlive.org) * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * 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 * ***************************************************************************/ #include "customruler.h" #include "kdenlivesettings.h" #include #include #include #include #include #include #include #include #include static int MAX_HEIGHT; // Width of a frame in pixels static int FRAME_SIZE; // Height of the timecode text static int LABEL_SIZE; // Width of a letter, used for cursor width static int FONT_WIDTH; static int BIG_MARK_X; static int MIDDLE_MARK_X; static int LITTLE_MARK_X; static int littleMarkDistance; static int mediumMarkDistance; static int bigMarkDistance; #define SEEK_INACTIVE (-1) #include "definitions.h" const int CustomRuler::comboScale[] = { 1, 2, 5, 10, 25, 50, 125, 250, 500, 750, 1500, 3000, 6000, 12000}; CustomRuler::CustomRuler(const Timecode &tc, CustomTrackView *parent) : QWidget(parent), m_timecode(tc), m_view(parent), m_duration(0), m_offset(0), m_headPosition(SEEK_INACTIVE), m_clickedGuide(-1), m_rate(-1), m_mouseMove(NO_MOVE) { setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); QFontMetricsF fontMetrics(font()); // Define size variables LABEL_SIZE = fontMetrics.ascent(); FONT_WIDTH = fontMetrics.averageCharWidth(); setMinimumHeight(LABEL_SIZE * 2); setMaximumHeight(LABEL_SIZE * 2); MAX_HEIGHT = height(); BIG_MARK_X = LABEL_SIZE + 1; int mark_length = MAX_HEIGHT - BIG_MARK_X; MIDDLE_MARK_X = BIG_MARK_X + mark_length / 2; LITTLE_MARK_X = BIG_MARK_X + mark_length / 3; updateFrameSize(); m_scale = 3; m_zoneStart = 0; m_zoneEnd = 100; m_contextMenu = new QMenu(this); QAction *addGuide = m_contextMenu->addAction(QIcon::fromTheme(QStringLiteral("document-new")), i18n("Add Guide")); connect(addGuide, SIGNAL(triggered()), m_view, SLOT(slotAddGuide())); m_editGuide = m_contextMenu->addAction(QIcon::fromTheme(QStringLiteral("document-properties")), i18n("Edit Guide")); connect(m_editGuide, SIGNAL(triggered()), this, SLOT(slotEditGuide())); m_deleteGuide = m_contextMenu->addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Delete Guide")); connect(m_deleteGuide , SIGNAL(triggered()), this, SLOT(slotDeleteGuide())); QAction *delAllGuides = m_contextMenu->addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18n("Delete All Guides")); connect(delAllGuides, SIGNAL(triggered()), m_view, SLOT(slotDeleteAllGuides())); m_goMenu = m_contextMenu->addMenu(i18n("Go To")); connect(m_goMenu, SIGNAL(triggered(QAction*)), this, SLOT(slotGoToGuide(QAction*))); setMouseTracking(true); m_zoneBG = palette().color(QPalette::Highlight); m_zoneBG.setAlpha(KdenliveSettings::useTimelineZoneToEdit() ? 180 : 60); } void CustomRuler::updateProjectFps(const Timecode &t) { m_timecode = t; mediumMarkDistance = FRAME_SIZE * m_timecode.fps(); bigMarkDistance = FRAME_SIZE * m_timecode.fps() * 60; setPixelPerMark(m_rate); update(); } void CustomRuler::updateFrameSize() { FRAME_SIZE = m_view->getFrameWidth(); littleMarkDistance = FRAME_SIZE; mediumMarkDistance = FRAME_SIZE * m_timecode.fps(); bigMarkDistance = FRAME_SIZE * m_timecode.fps() * 60; updateProjectFps(m_timecode); if (m_rate > 0) setPixelPerMark(m_rate); } void CustomRuler::slotEditGuide() { m_view->slotEditGuide(m_clickedGuide); } void CustomRuler::slotDeleteGuide() { m_view->slotDeleteGuide(m_clickedGuide); } void CustomRuler::slotGoToGuide(QAction *act) { m_view->seekCursorPos(act->data().toInt()); m_view->initCursorPos(act->data().toInt()); } void CustomRuler::setZone(const QPoint &p) { m_zoneStart = p.x(); m_zoneEnd = p.y(); update(); } void CustomRuler::mouseReleaseEvent(QMouseEvent * /*event*/) { if (m_moveCursor == RULER_START || m_moveCursor == RULER_END || m_moveCursor == RULER_MIDDLE) { emit zoneMoved(m_zoneStart, m_zoneEnd); m_view->setDocumentModified(); } m_mouseMove = NO_MOVE; } // virtual void CustomRuler::mousePressEvent(QMouseEvent * event) { int pos = (int)((event->x() + offset())); if (event->button() == Qt::RightButton) { m_clickedGuide = m_view->hasGuide((int)(pos / m_factor), (int)(5 / m_factor + 1)); m_editGuide->setEnabled(m_clickedGuide > 0); m_deleteGuide->setEnabled(m_clickedGuide > 0); m_view->buildGuidesMenu(m_goMenu); m_contextMenu->exec(event->globalPos()); return; } setFocus(Qt::MouseFocusReason); m_view->activateMonitor(); m_moveCursor = RULER_CURSOR; if (event->y() > 10) { if (qAbs(pos - m_zoneStart * m_factor) < 4) m_moveCursor = RULER_START; else if (qAbs(pos - (m_zoneStart + (m_zoneEnd - m_zoneStart) / 2.0) * m_factor) < 4) m_moveCursor = RULER_MIDDLE; else if (qAbs(pos - (m_zoneEnd + 1)* m_factor) < 4) m_moveCursor = RULER_END; m_view->updateSnapPoints(NULL); } if (m_moveCursor == RULER_CURSOR) { m_view->seekCursorPos((int) pos / m_factor); m_clickPoint = event->pos(); m_startRate = m_rate; } } // virtual void CustomRuler::mouseMoveEvent(QMouseEvent * event) { int mappedXPos = (int)((event->x() + offset()) / m_factor); emit mousePosition(mappedXPos); if (event->buttons() == Qt::LeftButton) { int pos; if (m_moveCursor == RULER_START || m_moveCursor == RULER_END) { pos = m_view->getSnapPointForPos(mappedXPos); } else pos = mappedXPos; int zoneStart = m_zoneStart; int zoneEnd = m_zoneEnd; if (pos < 0) pos = 0; if (m_moveCursor == RULER_CURSOR) { QPoint diff = event->pos() - m_clickPoint; if (m_mouseMove == NO_MOVE) { if (qAbs(diff.x()) >= QApplication::startDragDistance()) { m_mouseMove = HORIZONTAL_MOVE; } else if (KdenliveSettings::verticalzoom() && qAbs(diff.y()) >= QApplication::startDragDistance()) { m_mouseMove = VERTICAL_MOVE; } else return; } if (m_mouseMove == HORIZONTAL_MOVE) { if (pos != m_headPosition && pos != m_view->cursorPos()) { int x = m_headPosition == SEEK_INACTIVE ? pos : m_headPosition; m_headPosition = pos; int min = qMin(x, m_headPosition); int max = qMax(x, m_headPosition); update(min * m_factor - offset() - 3, BIG_MARK_X, (max - min) * m_factor + 6, MAX_HEIGHT - BIG_MARK_X); emit seekCursorPos(pos); m_view->slotCheckPositionScrolling(); } } else { int verticalDiff = m_startRate - (diff.y()) / 7; if (verticalDiff != m_rate) emit adjustZoom(verticalDiff); } return; } else if (m_moveCursor == RULER_START) m_zoneStart = qMin(pos, m_zoneEnd); else if (m_moveCursor == RULER_END) m_zoneEnd = qMax(pos, m_zoneStart); else if (m_moveCursor == RULER_MIDDLE) { int move = pos - (m_zoneStart + (m_zoneEnd - m_zoneStart) / 2); if (move + m_zoneStart < 0) move = - m_zoneStart; m_zoneStart += move; m_zoneEnd += move; } int min = qMin(m_zoneStart, zoneStart); int max = qMax(m_zoneEnd, zoneEnd); update(min * m_factor - m_offset - 2, 0, (max - min + 1) * m_factor + 4, height()); } else { int pos = (int)((event->x() + m_offset)); if (event->y() <= 10) { setCursor(Qt::ArrowCursor); } else if (qAbs(pos - m_zoneStart * m_factor) < 4) { setCursor(QCursor(Qt::SizeHorCursor)); if (KdenliveSettings::frametimecode()) setToolTip(i18n("Zone start: %1", m_zoneStart)); else setToolTip(i18n("Zone start: %1", m_timecode.getTimecodeFromFrames(m_zoneStart))); } else if (qAbs(pos - (m_zoneEnd + 1) * m_factor) < 4) { setCursor(QCursor(Qt::SizeHorCursor)); if (KdenliveSettings::frametimecode()) setToolTip(i18n("Zone end: %1", m_zoneEnd)); else setToolTip(i18n("Zone end: %1", m_timecode.getTimecodeFromFrames(m_zoneEnd))); } else if (qAbs(pos - (m_zoneStart + (m_zoneEnd - m_zoneStart) / 2.0) * m_factor) < 4) { setCursor(Qt::SizeHorCursor); if (KdenliveSettings::frametimecode()) setToolTip(i18n("Zone duration: %1", m_zoneEnd - m_zoneStart)); else setToolTip(i18n("Zone duration: %1", m_timecode.getTimecodeFromFrames(m_zoneEnd - m_zoneStart))); } else { setCursor(Qt::ArrowCursor); if (KdenliveSettings::frametimecode()) setToolTip(i18n("Position: %1", (int)(pos / m_factor))); else setToolTip(i18n("Position: %1", m_timecode.getTimecodeFromFrames(pos / m_factor))); } } } // virtual void CustomRuler::wheelEvent(QWheelEvent * e) { int delta = 1; m_view->activateMonitor(); if (e->modifiers() == Qt::ControlModifier) delta = m_timecode.fps(); if (e->delta() < 0) delta = 0 - delta; m_view->moveCursorPos(delta); } int CustomRuler::inPoint() const { return m_zoneStart; } int CustomRuler::outPoint() const { return m_zoneEnd; } void CustomRuler::slotMoveRuler(int newPos) { if (m_offset != newPos) { m_offset = newPos; update(); } } int CustomRuler::offset() const { return m_offset; } void CustomRuler::slotCursorMoved(int oldpos, int newpos) { int min = qMin(oldpos, newpos); int max = qMax(oldpos, newpos); m_headPosition = newpos; update(min * m_factor - m_offset - FONT_WIDTH, BIG_MARK_X, (max - min) * m_factor + FONT_WIDTH * 2 + 2, MAX_HEIGHT - BIG_MARK_X); } void CustomRuler::updateRuler(int pos) { int x = m_headPosition; m_headPosition = pos; if (x == SEEK_INACTIVE) x = pos; int min = qMin(x, m_headPosition); int max = qMax(x, m_headPosition); update(min * m_factor - offset() - 3, BIG_MARK_X, (max - min) * m_factor + 6, MAX_HEIGHT - BIG_MARK_X); } void CustomRuler::setPixelPerMark(int rate, bool force) { if (rate < 0 || (rate == m_rate && !force)) return; int scale = comboScale[rate]; m_rate = rate; m_factor = 1.0 / (double) scale * FRAME_SIZE; m_scale = 1.0 / (double) scale; double fend = m_scale * littleMarkDistance; int textFactor = 1; int timeLabelSize = QWidget::fontMetrics().boundingRect(QStringLiteral("00:00:00:000")).width(); if (timeLabelSize > littleMarkDistance) { textFactor = timeLabelSize / littleMarkDistance + 1; } if (rate > 8) { mediumMarkDistance = (double) FRAME_SIZE * m_timecode.fps() * 60; bigMarkDistance = (double) FRAME_SIZE * m_timecode.fps() * 300; } else if (rate > 6) { mediumMarkDistance = (double) FRAME_SIZE * m_timecode.fps() * 10; bigMarkDistance = (double) FRAME_SIZE * m_timecode.fps() * 30; } else if (rate > 3) { mediumMarkDistance = (double) FRAME_SIZE * m_timecode.fps(); bigMarkDistance = (double) FRAME_SIZE * m_timecode.fps() * 5; } else { mediumMarkDistance = (double) FRAME_SIZE * m_timecode.fps(); bigMarkDistance = (double) FRAME_SIZE * m_timecode.fps() * 60; } m_textSpacing = fend * textFactor; if (m_textSpacing < timeLabelSize) { int roundedFps = (int) (m_timecode.fps() + 0.5); int factor = timeLabelSize / m_textSpacing; if (factor < 2) { m_textSpacing *= 2; } else if (factor < 5) { m_textSpacing *= 5; } else if (factor < 10) { m_textSpacing *= 10; } else if (factor < roundedFps) { m_textSpacing *= roundedFps; } else if (factor < 2 * roundedFps) { m_textSpacing *= 2 * roundedFps; } else if (factor < 5 * roundedFps) { m_textSpacing *= 5 * roundedFps; } else if (factor < 10 * roundedFps) { m_textSpacing *= 10 * roundedFps; } else if (factor < 20 * roundedFps) { m_textSpacing *= 20 * roundedFps; } else if (factor < 60 * roundedFps) { m_textSpacing *= 60 * roundedFps; } else if (factor < 120 * roundedFps) { m_textSpacing *= 120 * roundedFps; } else if (factor < 150 * roundedFps) { m_textSpacing *= 150 * roundedFps; } else if (factor < 300 * roundedFps) { m_textSpacing *= 300 * roundedFps; } else { factor /= (300 * roundedFps); m_textSpacing *= (factor + 1) * (300 * roundedFps); } } update(); } void CustomRuler::setDuration(int d) { int oldduration = m_duration; m_duration = d; update(qMin(oldduration, m_duration) * m_factor - 1 - offset(), 0, qAbs(oldduration - m_duration) * m_factor + 2, height()); } // virtual void CustomRuler::paintEvent(QPaintEvent *e) { QStylePainter p(this); const QRect &paintRect = e->rect(); p.setClipRect(paintRect); p.fillRect(paintRect, palette().midlight().color()); // Draw zone background const int zoneStart = (int)(m_zoneStart * m_factor); const int zoneEnd = (int)((m_zoneEnd + 1)* m_factor); p.fillRect(zoneStart - m_offset, LABEL_SIZE + 2, zoneEnd - zoneStart, MAX_HEIGHT - LABEL_SIZE - 2, m_zoneBG); double f, fend; const int offsetmax = ((paintRect.right() + m_offset) / FRAME_SIZE + 1) * FRAME_SIZE; int offsetmin; p.setPen(palette().text().color()); // draw time labels if (paintRect.y() < LABEL_SIZE) { offsetmin = (paintRect.left() + m_offset) / m_textSpacing; offsetmin = offsetmin * m_textSpacing; for (f = offsetmin; f < offsetmax; f += m_textSpacing) { QString lab; if (KdenliveSettings::frametimecode()) lab = QString::number((int)(f / m_factor + 0.5)); else lab = m_timecode.getTimecodeFromFrames((int)(f / m_factor + 0.5)); p.drawText(f - m_offset + 2, LABEL_SIZE, lab); } } p.setPen(palette().dark().color()); offsetmin = (paintRect.left() + m_offset) / littleMarkDistance; offsetmin = offsetmin * littleMarkDistance; // draw the little marks fend = m_scale * littleMarkDistance; if (fend > 5) { QLineF l(offsetmin - m_offset, LITTLE_MARK_X, offsetmin - m_offset, MAX_HEIGHT); for (f = offsetmin; f < offsetmax; f += fend) { l.translate(fend, 0); p.drawLine(l); } } offsetmin = (paintRect.left() + m_offset) / mediumMarkDistance; offsetmin = offsetmin * mediumMarkDistance; // draw medium marks fend = m_scale * mediumMarkDistance; if (fend > 5) { QLineF l(offsetmin - m_offset - fend, MIDDLE_MARK_X, offsetmin - m_offset - fend, MAX_HEIGHT); for (f = offsetmin - fend; f < offsetmax + fend; f += fend) { l.translate(fend, 0); p.drawLine(l); } } offsetmin = (paintRect.left() + m_offset) / bigMarkDistance; offsetmin = offsetmin * bigMarkDistance; // draw big marks fend = m_scale * bigMarkDistance; if (fend > 5) { QLineF l(offsetmin - m_offset, BIG_MARK_X, offsetmin - m_offset, MAX_HEIGHT); for (f = offsetmin; f < offsetmax; f += fend) { l.translate(fend, 0); p.drawLine(l); } } // draw zone cursors if (zoneStart > 0) { QPolygon pa(4); pa.setPoints(4, zoneStart - m_offset + FONT_WIDTH / 2, LABEL_SIZE + 2, zoneStart - m_offset, LABEL_SIZE + 2, zoneStart - m_offset, MAX_HEIGHT - 1, zoneStart - m_offset + FONT_WIDTH / 2, MAX_HEIGHT - 1); p.drawPolyline(pa); } if (zoneEnd > 0) { QColor center(Qt::white); center.setAlpha(150); QRect rec(zoneStart - m_offset + (zoneEnd - zoneStart) / 2 - 4, LABEL_SIZE + 2, 8, MAX_HEIGHT - LABEL_SIZE - 3); p.fillRect(rec, center); p.drawRect(rec); QPolygon pa(4); pa.setPoints(4, zoneEnd - m_offset - FONT_WIDTH / 2, LABEL_SIZE + 2, zoneEnd - m_offset, LABEL_SIZE + 2, zoneEnd - m_offset, MAX_HEIGHT - 1, zoneEnd - m_offset - FONT_WIDTH / 2, MAX_HEIGHT - 1); p.drawPolyline(pa); } + // draw Rendering preview zones + QColor preview(Qt::yellow); + foreach(int frame, m_renderingPreviews) { + QRect rec(frame * m_factor - m_offset, MAX_HEIGHT - 2, 199 * m_factor, 2); + p.fillRect(rec, preview); + } + // draw pointer const int value = m_view->cursorPos() * m_factor - m_offset; QPolygon pa(3); pa.setPoints(3, value - FONT_WIDTH, LABEL_SIZE + 3, value + FONT_WIDTH, LABEL_SIZE + 3, value, MAX_HEIGHT); p.setBrush(palette().brush(QPalette::Text)); p.setPen(Qt::NoPen); p.drawPolygon(pa); if (m_headPosition == m_view->cursorPos()) { m_headPosition = SEEK_INACTIVE; } if (m_headPosition != SEEK_INACTIVE) { p.fillRect(m_headPosition * m_factor - m_offset - 1, BIG_MARK_X + 1, 3, MAX_HEIGHT - BIG_MARK_X - 1, palette().linkVisited()); } } void CustomRuler::activateZone() { m_zoneBG.setAlpha(KdenliveSettings::useTimelineZoneToEdit() ? 180 : 60); update(); } +void CustomRuler::updatePreview(int frame, bool rendered) +{ + if (rendered) + m_renderingPreviews << frame; + else + m_renderingPreviews.removeAll(frame); + update(frame * m_factor - offset(), MAX_HEIGHT - 2, (199) * m_factor, 2); +} diff --git a/src/timeline/customruler.h b/src/timeline/customruler.h index e858293f8..9a466ef34 100644 --- a/src/timeline/customruler.h +++ b/src/timeline/customruler.h @@ -1,104 +1,105 @@ /*************************************************************************** * Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@kdenlive.org) * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * 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 * ***************************************************************************/ /** * @class CustomRuler * @author Jean-Baptiste Mardelle * @brief Manages the timeline ruler. */ #ifndef CUSTOMRULER_H #define CUSTOMRULER_H #include #include "timeline/customtrackview.h" #include "timecode.h" enum RULER_MOVE { RULER_CURSOR = 0, RULER_START = 1, RULER_MIDDLE = 2, RULER_END = 3 }; enum MOUSE_MOVE { NO_MOVE = 0, HORIZONTAL_MOVE = 1, VERTICAL_MOVE = 2 }; class CustomRuler : public QWidget { Q_OBJECT public: CustomRuler(const Timecode &tc, CustomTrackView *parent); void setPixelPerMark(int rate, bool force = false); static const int comboScale[]; int outPoint() const; int inPoint() const; void setDuration(int d); void setZone(const QPoint &p); int offset() const; void updateProjectFps(const Timecode &t); void updateFrameSize(); void activateZone(); + void updatePreview(int frame, bool rendered = true); protected: void paintEvent(QPaintEvent * /*e*/); void wheelEvent(QWheelEvent * e); void mousePressEvent(QMouseEvent * event); void mouseReleaseEvent(QMouseEvent * event); void mouseMoveEvent(QMouseEvent * event); private: Timecode m_timecode; CustomTrackView *m_view; int m_zoneStart; int m_zoneEnd; int m_duration; double m_textSpacing; double m_factor; double m_scale; int m_offset; /** @brief the position of the seek point */ int m_headPosition; QColor m_zoneBG; RULER_MOVE m_moveCursor; QMenu *m_contextMenu; QAction *m_editGuide; QAction *m_deleteGuide; int m_clickedGuide; /** Used for zooming through vertical move */ QPoint m_clickPoint; int m_rate; int m_startRate; MOUSE_MOVE m_mouseMove; QMenu *m_goMenu; - + QList m_renderingPreviews; public slots: void slotMoveRuler(int newPos); void slotCursorMoved(int oldpos, int newpos); void updateRuler(int pos); private slots: void slotEditGuide(); void slotDeleteGuide(); void slotGoToGuide(QAction *act); signals: void zoneMoved(int, int); void adjustZoom(int); void mousePosition(int); void seekCursorPos(int); }; #endif diff --git a/src/timeline/timeline.cpp b/src/timeline/timeline.cpp index f5d504908..e14ef221c 100644 --- a/src/timeline/timeline.cpp +++ b/src/timeline/timeline.cpp @@ -1,1717 +1,1770 @@ /*************************************************************************** * Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@kdenlive.org) * * Copyright (C) 2015 by Vincent Pinon (vpinon@kde.org) * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * 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 * ***************************************************************************/ #include "timeline.h" #include "track.h" #include "clip.h" #include "renderer.h" #include "headertrack.h" #include "clipitem.h" #include "transition.h" #include "transitionhandler.h" #include "timelinecommands.h" #include "customruler.h" #include "customtrackview.h" #include "dialogs/profilesdialog.h" #include "mltcontroller/clipcontroller.h" #include "bin/projectclip.h" #include "kdenlivesettings.h" #include "mainwindow.h" #include "doc/kdenlivedoc.h" #include "utils/KoIconUtils.h" #include "project/clipmanager.h" #include "effectslist/initeffects.h" #include "mltcontroller/effectscontroller.h" #include #include #include #include #include #include Timeline::Timeline(KdenliveDoc *doc, const QList &actions, bool *ok, QWidget *parent) : QWidget(parent), multitrackView(false) , videoTarget(-1) , audioTarget(-1) , m_hasOverlayTrack(false) , m_overlayTrack(NULL) , m_scale(1.0) , m_doc(doc) , m_verticalZoom(1) { m_trackActions << actions; setupUi(this); // ruler_frame->setMaximumHeight(); // size_frame->setMaximumHeight(); m_scene = new CustomTrackScene(this); m_trackview = new CustomTrackView(doc, this, m_scene, parent); if (m_doc->setSceneList() == -1) *ok = false; else *ok = true; Mlt::Service s(m_doc->renderer()->getProducer()->parent().get_service()); m_tractor = new Mlt::Tractor(s); m_ruler = new CustomRuler(doc->timecode(), m_trackview); connect(m_ruler, SIGNAL(zoneMoved(int,int)), this, SIGNAL(zoneMoved(int,int))); connect(m_ruler, SIGNAL(adjustZoom(int)), this, SIGNAL(setZoom(int))); connect(m_ruler, SIGNAL(mousePosition(int)), this, SIGNAL(mousePosition(int))); connect(m_ruler, SIGNAL(seekCursorPos(int)), m_doc->renderer(), SLOT(seek(int)), Qt::QueuedConnection); QHBoxLayout *layout = new QHBoxLayout; layout->setContentsMargins(m_trackview->frameWidth(), 0, 0, 0); layout->setSpacing(0); ruler_frame->setLayout(layout); ruler_frame->setMaximumHeight(m_ruler->height()); layout->addWidget(m_ruler); QHBoxLayout *sizeLayout = new QHBoxLayout; sizeLayout->setContentsMargins(0, 0, 0, 0); sizeLayout->setSpacing(0); size_frame->setLayout(sizeLayout); size_frame->setMaximumHeight(m_ruler->height()); QToolButton *butSmall = new QToolButton(this); butSmall->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-zoom-small"))); butSmall->setToolTip(i18n("Smaller tracks")); butSmall->setAutoRaise(true); connect(butSmall, SIGNAL(clicked()), this, SLOT(slotVerticalZoomDown())); sizeLayout->addWidget(butSmall); QToolButton *butLarge = new QToolButton(this); butLarge->setIcon(KoIconUtils::themedIcon(QStringLiteral("kdenlive-zoom-large"))); butLarge->setToolTip(i18n("Bigger tracks")); butLarge->setAutoRaise(true); connect(butLarge, SIGNAL(clicked()), this, SLOT(slotVerticalZoomUp())); sizeLayout->addWidget(butLarge); QToolButton *enableZone = new QToolButton(this); QAction *ac = new QAction(KoIconUtils::themedIcon(QStringLiteral("measure")), i18n("Use Timeline Zone for Insert"), this); ac->setShortcut(Qt::Key_G); enableZone->setAutoRaise(true); ac->setCheckable(true); ac->setChecked(KdenliveSettings::useTimelineZoneToEdit()); enableZone->setDefaultAction(ac); connect(ac, &QAction::toggled, this, &Timeline::slotEnableZone); sizeLayout->addWidget(enableZone); m_doc->doAddAction(QStringLiteral("use_timeline_zone_in_edit"), ac); QHBoxLayout *tracksLayout = new QHBoxLayout; tracksLayout->setContentsMargins(0, 0, 0, 0); tracksLayout->setSpacing(0); tracks_frame->setLayout(tracksLayout); headers_area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); headers_area->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); QVBoxLayout *headersLayout = new QVBoxLayout; headersLayout->setContentsMargins(0, m_trackview->frameWidth(), 0, 0); headersLayout->setSpacing(0); headers_container->setLayout(headersLayout); connect(headers_area->verticalScrollBar(), SIGNAL(valueChanged(int)), m_trackview->verticalScrollBar(), SLOT(setValue(int))); tracksLayout->addWidget(m_trackview); connect(m_trackview->verticalScrollBar(), SIGNAL(valueChanged(int)), headers_area->verticalScrollBar(), SLOT(setValue(int))); connect(m_trackview, SIGNAL(tracksChanged()), this, SLOT(slotReloadTracks())); connect(m_trackview, SIGNAL(updateTrackHeaders()), this, SLOT(slotRepaintTracks())); connect(m_trackview, SIGNAL(showTrackEffects(int,TrackInfo)), this, SIGNAL(showTrackEffects(int,TrackInfo))); connect(m_trackview, SIGNAL(updateTrackEffectState(int)), this, SLOT(slotUpdateTrackEffectState(int))); transitionHandler = new TransitionHandler(m_tractor); connect(transitionHandler, &TransitionHandler::refresh, m_doc->renderer(), &Render::doRefresh); connect(m_trackview, SIGNAL(cursorMoved(int,int)), m_ruler, SLOT(slotCursorMoved(int,int))); connect(m_trackview, SIGNAL(updateRuler(int)), m_ruler, SLOT(updateRuler(int)), Qt::DirectConnection); connect(m_trackview->horizontalScrollBar(), SIGNAL(valueChanged(int)), m_ruler, SLOT(slotMoveRuler(int))); connect(m_trackview->horizontalScrollBar(), SIGNAL(rangeChanged(int,int)), this, SLOT(slotUpdateVerticalScroll(int,int))); connect(m_trackview, SIGNAL(mousePosition(int)), this, SIGNAL(mousePosition(int))); + connect(m_doc->renderer(), &Render::previewRender, this, &Timeline::gotPreviewRender); } Timeline::~Timeline() { delete m_ruler; delete m_trackview; delete m_scene; delete transitionHandler; delete m_tractor; qDeleteAll<>(m_tracks); m_tracks.clear(); } void Timeline::loadTimeline() { parseDocument(m_doc->toXml()); m_trackview->slotUpdateAllThumbs(); m_trackview->slotSelectTrack(m_trackview->getNextVideoTrack(1)); slotChangeZoom(m_doc->zoom().x(), m_doc->zoom().y()); slotSetZone(m_doc->zone(), false); } QMap Timeline::documentProperties() { QMap props = m_doc->documentProperties(); props.insert(QStringLiteral("audiotargettrack"), QString::number(audioTarget)); props.insert(QStringLiteral("videotargettrack"), QString::number(videoTarget)); return props; } Track* Timeline::track(int i) { if (i < 0 || i >= m_tracks.count()) return NULL; return m_tracks.at(i); } int Timeline::tracksCount() const { return m_tractor->count() - (m_hasOverlayTrack ? 1 : 0); } int Timeline::visibleTracksCount() const { return m_tractor->count() - 1 - (m_hasOverlayTrack ? 1 : 0); } //virtual void Timeline::keyPressEvent(QKeyEvent * event) { if (event->key() == Qt::Key_Up) { m_trackview->slotTrackUp(); event->accept(); } else if (event->key() == Qt::Key_Down) { m_trackview->slotTrackDown(); event->accept(); } else QWidget::keyPressEvent(event); } int Timeline::duration() const { return m_trackview->duration(); } bool Timeline::checkProjectAudio() { bool hasAudio = false; int max = m_tracks.count(); for (int i = 0; i < max; i++) { Track *sourceTrack = track(i); QScopedPointer track(m_tractor->track(i + 1)); int state = track->get_int("hide"); if (sourceTrack && sourceTrack->hasAudio() && !(state & 2)) { hasAudio = true; break; } } return hasAudio; } int Timeline::inPoint() const { return m_ruler->inPoint(); } int Timeline::outPoint() const { return m_ruler->outPoint(); } void Timeline::slotSetZone(const QPoint &p, bool updateDocumentProperties) { m_ruler->setZone(p); if (updateDocumentProperties) m_doc->setZone(p.x(), p.y()); } void Timeline::setDuration(int dur) { m_trackview->setDuration(dur); m_ruler->setDuration(dur); } int Timeline::getTracks() { int duration = 1; qDeleteAll<>(m_tracks); m_tracks.clear(); QVBoxLayout *headerLayout = qobject_cast< QVBoxLayout* >(headers_container->layout()); QLayoutItem *child; while ((child = headerLayout->takeAt(0)) != 0) { delete child; } int clipsCount = 0; for (int i = 0; i < m_tractor->count(); ++i) { QScopedPointer track(m_tractor->track(i)); QString playlist_name = track->get("id"); if (playlist_name == QLatin1String("black_track")) continue; clipsCount += track->count(); } emit startLoadingBin(clipsCount); emit resetUsageCount(); checkTrackHeight(false); int height = KdenliveSettings::trackheight() * m_scene->scale().y() - 1; int headerWidth = 0; int offset = 0; for (int i = 0; i < m_tractor->count(); ++i) { QScopedPointer track(m_tractor->track(i)); QString playlist_name = track->get("id"); if (playlist_name == QLatin1String("playlistmain")) continue; bool isBackgroundBlackTrack = playlist_name == QLatin1String("black_track"); // check track effects Mlt::Playlist playlist(*track); int trackduration = 0; int audio = 0; Track *tk = NULL; if (!isBackgroundBlackTrack) { audio = playlist.get_int("kdenlive:audio_track"); tk = new Track(i, m_trackActions, playlist, audio == 1 ? AudioTrack : VideoTrack, this); m_tracks.append(tk); trackduration = loadTrack(i, offset, playlist); QFrame *frame = new QFrame(headers_container); frame->setFrameStyle(QFrame::HLine); frame->setFixedHeight(1); headerLayout->insertWidget(0, frame); } else { // Black track tk = new Track(i, m_trackActions, playlist, audio == 1 ? AudioTrack : VideoTrack, this); m_tracks.append(tk); } offset += track->count(); if (audio == 0 && !isBackgroundBlackTrack) { // Check if we have a composite transition for this track QScopedPointer transition(transitionHandler->getTransition(KdenliveSettings::gpu_accel() ? "movit.overlay" : "frei0r.cairoblend", i, -1, true)); if (!transition) { tk->trackHeader->disableComposite(); } } if (!isBackgroundBlackTrack) { tk->trackHeader->setTrackHeight(height); int currentWidth = tk->trackHeader->minimumWidth(); if (currentWidth > headerWidth) headerWidth = currentWidth; headerLayout->insertWidget(0, tk->trackHeader); if (trackduration > duration) duration = trackduration; tk->trackHeader->setSelectedIndex(m_trackview->selectedTrack()); connect(tk->trackHeader, &HeaderTrack::switchTrackComposite, this, &Timeline::slotSwitchTrackComposite); connect(tk->trackHeader, SIGNAL(switchTrackVideo(int,bool)), m_trackview, SLOT(slotSwitchTrackVideo(int,bool))); connect(tk->trackHeader, SIGNAL(switchTrackAudio(int,bool)), m_trackview, SLOT(slotSwitchTrackAudio(int,bool))); connect(tk->trackHeader, SIGNAL(switchTrackLock(int,bool)), m_trackview, SLOT(slotSwitchTrackLock(int,bool))); connect(tk->trackHeader, SIGNAL(selectTrack(int,bool)), m_trackview, SLOT(slotSelectTrack(int,bool))); connect(tk->trackHeader, SIGNAL(renameTrack(int,QString)), this, SLOT(slotRenameTrack(int,QString))); connect(tk->trackHeader, SIGNAL(configTrack()), this, SIGNAL(configTrack())); connect(tk->trackHeader, SIGNAL(addTrackEffect(QDomElement,int)), m_trackview, SLOT(slotAddTrackEffect(QDomElement,int))); if (playlist.filter_count()) { getEffects(playlist, NULL, i); slotUpdateTrackEffectState(i); } connect(tk, &Track::newTrackDuration, this, &Timeline::checkDuration, Qt::DirectConnection); connect(tk, SIGNAL(storeSlowMotion(QString,Mlt::Producer *)), m_doc->renderer(), SLOT(storeSlowmotionProducer(QString,Mlt::Producer *))); + connect(tk, &Track::invalidatePreview, this, &Timeline::invalidatePreview); } } headers_container->setFixedWidth(headerWidth); if (audioTarget > -1) { m_tracks.at(audioTarget)->trackHeader->switchTarget(true); } if (videoTarget > -1) { m_tracks.at(videoTarget)->trackHeader->switchTarget(true); } updatePalette(); refreshTrackActions(); return duration; } void Timeline::checkDuration(int duration) { Q_UNUSED(duration) m_doc->renderer()->mltCheckLength(m_tractor); return; /*FIXME for (int i = 1; i < m_tractor->count(); ++i) { QScopedPointer tk(m_tractor->track(i)); int len = tk->get_playtime() - 1; if (len > duration) duration = len; } QScopedPointer tk1(m_tractor->track(0)); Mlt::Service s(tk1->get_service()); Mlt::Playlist blackTrack(s); if (blackTrack.get_playtime() - 1 != duration) { QScopedPointer blackClip(blackTrack.get_clip(0)); if (blackClip->parent().get_length() <= duration) { blackClip->parent().set("length", duration + 1); blackClip->parent().set("out", duration); blackClip->set("length", duration + 1); } blackTrack.resize_clip(0, 0, duration); } //TODO: rewind consumer if beyond duration / emit durationChanged */ } void Timeline::getTransitions() { QLocale locale; locale.setNumberOptions(QLocale::OmitGroupSeparator); mlt_service service = mlt_service_get_producer(m_tractor->get_service()); QScopedPointer field(m_tractor->field()); while (service) { Mlt::Properties prop(MLT_SERVICE_PROPERTIES(service)); if (QString(prop.get("mlt_type")) != QLatin1String("transition")) break; //skip automatic mix if (prop.get_int("internal_added") == 237) { QString trans = prop.get("mlt_service"); if (trans == QLatin1String("movit.overlay") || trans == QLatin1String("frei0r.cairoblend")) { int ix = prop.get_int("b_track"); if (ix >= 0 && ix < m_tracks.count()) { TrackInfo info = track(ix)->info(); info.composite = !prop.get_int("disable"); track(ix)->setInfo(info); } else qWarning() << "Wrong composite track index: " << ix; } else if(trans == QLatin1String("mix")) { } service = mlt_service_producer(service); continue; } int a_track = prop.get_int("a_track"); int b_track = prop.get_int("b_track"); ItemInfo transitionInfo; transitionInfo.startPos = GenTime(prop.get_int("in"), m_doc->fps()); transitionInfo.endPos = GenTime(prop.get_int("out") + 1, m_doc->fps()); transitionInfo.track = b_track; // When adding composite transition, check if it is a wipe transition if (prop.get("kdenlive_id") == NULL && QString(prop.get("mlt_service")) == QLatin1String("composite") && isSlide(prop.get("geometry"))) prop.set("kdenlive_id", "slide"); QDomElement base = MainWindow::transitions.getEffectByTag(prop.get("mlt_service"), prop.get("kdenlive_id")).cloneNode().toElement(); //check invalid parameters if (a_track > m_tractor->count() - 1) { m_documentErrors.append(i18n("Transition %1 had an invalid track: %2 > %3", prop.get("id"), a_track, m_tractor->count() - 1) + '\n'); prop.set("a_track", m_tractor->count() - 1); } if (b_track > m_tractor->count() - 1) { m_documentErrors.append(i18n("Transition %1 had an invalid track: %2 > %3", prop.get("id"), b_track, m_tractor->count() - 1) + '\n'); prop.set("b_track", m_tractor->count() - 1); } if (a_track == b_track || b_track <= 0 || transitionInfo.startPos >= transitionInfo.endPos || base.isNull() //|| !m_trackview->canBePastedTo(transitionInfo, TransitionWidget) ) { // invalid transition, remove it m_documentErrors.append(i18n("Removed invalid transition: %1", prop.get("id")) + '\n'); mlt_service disconnect = service; service = mlt_service_producer(service); mlt_field_disconnect_service(field->get_field(), disconnect); } else { QDomNodeList params = base.elementsByTagName(QStringLiteral("parameter")); for (int i = 0; i < params.count(); ++i) { QDomElement e = params.item(i).toElement(); QString paramName = e.hasAttribute(QStringLiteral("tag")) ? e.attribute(QStringLiteral("tag")) : e.attribute(QStringLiteral("name")); QString value = prop.get(paramName.toUtf8().constData()); // if transition parameter has an "optional" attribute, it means that it can contain an empty value if (value.isEmpty() && !e.hasAttribute(QStringLiteral("optional"))) continue; if (e.hasAttribute("factor") || e.hasAttribute("offset")) adjustDouble(e, value); else e.setAttribute(QStringLiteral("value"), value); } Transition *tr = new Transition(transitionInfo, a_track, m_doc->fps(), base, QString(prop.get("automatic")) == QLatin1String("1")); connect(tr, &AbstractClipItem::selectItem, m_trackview, &CustomTrackView::slotSelectItem); tr->setPos(transitionInfo.startPos.frames(m_doc->fps()), KdenliveSettings::trackheight() * (visibleTracksCount() - transitionInfo.track) + 1 + tr->itemOffset()); if (QString(prop.get("force_track")) == QLatin1String("1")) tr->setForcedTrack(true, a_track); if (isTrackLocked(b_track)) tr->setItemLocked(true); m_scene->addItem(tr); service = mlt_service_producer(service); } } } // static bool Timeline::isSlide(QString geometry) { if (geometry.count(';') != 1) return false; geometry.remove(QChar('%'), Qt::CaseInsensitive); geometry.replace(QChar('x'), QChar(':'), Qt::CaseInsensitive); geometry.replace(QChar(','), QChar(':'), Qt::CaseInsensitive); geometry.replace(QChar('/'), QChar(':'), Qt::CaseInsensitive); QString start = geometry.section('=', 0, 0).section(':', 0, -2) + ':'; start.append(geometry.section('=', 1, 1).section(':', 0, -2)); QStringList numbers = start.split(':', QString::SkipEmptyParts); for (int i = 0; i < numbers.size(); ++i) { int checkNumber = qAbs(numbers.at(i).toInt()); if (checkNumber != 0 && checkNumber != 100) { return false; } } return true; } void Timeline::adjustDouble(QDomElement &e, const QString &value) { QLocale locale; locale.setNumberOptions(QLocale::OmitGroupSeparator); QString factor = e.attribute(QStringLiteral("factor"), QStringLiteral("1")); double offset = locale.toDouble(e.attribute(QStringLiteral("offset"), QStringLiteral("0"))); double fact = 1; if (factor.contains('%')) fact = EffectsController::getStringEval(m_doc->getProfileInfo(), factor); else fact = locale.toDouble(factor); QString type = e.attribute(QStringLiteral("type")); if (type == QLatin1String("double") || type == QLatin1String("constant")) { double val = locale.toDouble(value); e.setAttribute(QStringLiteral("value"), locale.toString(offset + val * fact)); } else if (type == QLatin1String("simplekeyframe")) { QStringList keys = value.split(QLatin1Char(';')); for (int j = 0; j < keys.count(); ++j) { QString pos = keys.at(j).section(QLatin1Char('='), 0, 0); double val = locale.toDouble(keys.at(j).section(QLatin1Char('='), 1, 1)) * fact + offset; keys[j] = pos + '=' + locale.toString(val); } e.setAttribute(QStringLiteral("value"), keys.join(QLatin1Char(';'))); } else { e.setAttribute(QStringLiteral("value"), value); } } void Timeline::parseDocument(const QDomDocument &doc) { //int cursorPos = 0; m_documentErrors.clear(); m_replacementProducerIds.clear(); // parse project tracks QDomElement mlt = doc.firstChildElement(QStringLiteral("mlt")); m_trackview->setDuration(getTracks()); getTransitions(); // Rebuild groups QDomDocument groupsDoc; groupsDoc.setContent(m_doc->renderer()->getBinProperty(QStringLiteral("kdenlive:clipgroups"))); QDomNodeList groups = groupsDoc.elementsByTagName(QStringLiteral("group")); m_trackview->loadGroups(groups); // Load custom effects QDomDocument effectsDoc; effectsDoc.setContent(m_doc->renderer()->getBinProperty(QStringLiteral("kdenlive:customeffects"))); QDomNodeList effects = effectsDoc.elementsByTagName(QStringLiteral("effect")); if (!effects.isEmpty()) { m_doc->saveCustomEffects(effects); } if (!m_documentErrors.isNull()) KMessageBox::sorry(this, m_documentErrors); if (mlt.hasAttribute(QStringLiteral("upgraded")) || mlt.hasAttribute(QStringLiteral("modified"))) { // Our document was upgraded, create a backup copy just in case QString baseFile = m_doc->url().path().section(QStringLiteral(".kdenlive"), 0, 0); int ct = 0; QString backupFile = baseFile + "_backup" + QString::number(ct) + ".kdenlive"; while (QFile::exists(backupFile)) { ct++; backupFile = baseFile + "_backup" + QString::number(ct) + ".kdenlive"; } QString message; if (mlt.hasAttribute(QStringLiteral("upgraded"))) message = i18n("Your project file was upgraded to the latest Kdenlive document version.\nTo make sure you don't lose data, a backup copy called %1 was created.", backupFile); else message = i18n("Your project file was modified by Kdenlive.\nTo make sure you don't lose data, a backup copy called %1 was created.", backupFile); KIO::FileCopyJob *copyjob = KIO::file_copy(m_doc->url(), QUrl::fromLocalFile(backupFile)); if (copyjob->exec()) KMessageBox::information(this, message); else KMessageBox::information(this, i18n("Your project file was upgraded to the latest Kdenlive document version, but it was not possible to create the backup copy %1.", backupFile)); } } void Timeline::slotDeleteClip(const QString &clipId, QUndoCommand *deleteCommand) { m_trackview->deleteClip(clipId, deleteCommand); } void Timeline::setCursorPos(int pos) { m_trackview->setCursorPos(pos); } void Timeline::moveCursorPos(int pos) { m_trackview->setCursorPos(pos); } void Timeline::slotChangeZoom(int horizontal, int vertical) { m_ruler->setPixelPerMark(horizontal); m_scale = (double) m_trackview->getFrameWidth() / m_ruler->comboScale[horizontal]; if (vertical == -1) { // user called zoom m_doc->setZoom(horizontal, m_verticalZoom); m_trackview->setScale(m_scale, m_scene->scale().y()); } else { m_verticalZoom = vertical; if (m_verticalZoom == 0) m_trackview->setScale(m_scale, 0.5); else m_trackview->setScale(m_scale, m_verticalZoom); adjustTrackHeaders(); } } int Timeline::fitZoom() const { int zoom = (int)((duration() + 20 / m_scale) * m_trackview->getFrameWidth() / m_trackview->width()); int i; for (i = 0; i < 13; ++i) if (m_ruler->comboScale[i] > zoom) break; return i; } KdenliveDoc *Timeline::document() { return m_doc; } void Timeline::refresh() { m_trackview->viewport()->update(); } void Timeline::slotRepaintTracks() { for (int i = 1; i < m_tracks.count(); i++) { m_tracks.at(i)->trackHeader->setSelectedIndex(m_trackview->selectedTrack()); } } void Timeline::blockTrackSignals(bool block) { for (int i = 1; i < m_tracks.count(); i++) { m_tracks.at(i)->blockSignals(block); } } void Timeline::slotReloadTracks() { emit updateTracksInfo(); } TrackInfo Timeline::getTrackInfo(int ix) { if (ix < 0 || ix > m_tracks.count()) { qWarning()<<"/// ARGH, requested info for track: "<info(); } bool Timeline::isLastClip(ItemInfo info) { Track *tk = track(info.track); if (tk == NULL) { return true; } return tk->isLastClip(info.endPos.seconds()); } void Timeline::setTrackInfo(int ix, TrackInfo info) { if (ix < 0 || ix > m_tracks.count()) { qWarning() << "Set Track effect outisde of range"; return; } Track *tk = track(ix); tk->setInfo(info); } QList Timeline::getTracksInfo() { QList tracks; for (int i = 0; i < tracksCount(); i++) { tracks << track(i)->info(); } return tracks; } QStringList Timeline::getTrackNames() { QStringList trackNames; for (int i = 0; i < tracksCount(); i++) { trackNames << track(i)->info().trackName; } return trackNames; } void Timeline::lockTrack(int ix, bool lock) { Track *tk = track(ix); if (tk == NULL) { qWarning() << "Set Track effect outisde of range: "<lockTrack(lock); } bool Timeline::isTrackLocked(int ix) { Track *tk = track(ix); if (tk == NULL) { qWarning() << "Set Track effect outisde of range: "<getIntProperty(QStringLiteral("kdenlive:locked_track")); return locked == 1; } void Timeline::updateTrackState(int ix, int state) { int currentState = 0; QScopedPointer track(m_tractor->track(ix)); currentState = track->get_int("hide"); if (state == currentState) return; if (state == 0) { // Show all if (currentState & 1) { switchTrackVideo(ix, false); } if (currentState & 2) { switchTrackAudio(ix, false); } } else if (state == 1) { // Mute video if (currentState & 2) { switchTrackAudio(ix, false); } switchTrackVideo(ix, true); } else if (state == 2) { // Mute audio if (currentState & 1) { switchTrackVideo(ix, false); } switchTrackAudio(ix, true); } else { switchTrackVideo(ix, true); switchTrackAudio(ix, true); } } void Timeline::switchTrackVideo(int ix, bool hide) { Track* tk = track(ix); if (tk == NULL) { qWarning() << "Set Track effect outisde of range: "<state(); if (hide && (state & 1)) { // Video is already muted return; } int newstate = 0; if (hide) { if (state & 2) { newstate = 3; } else { newstate = 1; } } else { if (state & 2) { newstate = 2; } else { newstate = 0; } } tk->setState(newstate); refreshTractor(); } void Timeline::slotSwitchTrackComposite(int trackIndex, bool enable) { if (trackIndex < 1 || trackIndex > m_tracks.count()) return; QScopedPointer transition(transitionHandler->getTransition(KdenliveSettings::gpu_accel() ? "movit.overlay" : "frei0r.cairoblend", trackIndex, -1, true)); if (transition) { transition->set("disable", enable); // When turning a track composite on/off, we need to re-plug transitions correctly updateComposites(); m_doc->renderer()->doRefresh(); m_doc->setModified(); //TODO: create undo/redo command for this } else { Track* tk = track(trackIndex); tk->trackHeader->setComposite(false); qWarning() << "Composite transition not found"; } } void Timeline::updateComposites() { int lowest = getLowestVideoTrack(); if (lowest >= 0) { transitionHandler->rebuildComposites(lowest); } } void Timeline::refreshTractor() { m_tractor->multitrack()->refresh(); m_tractor->refresh(); } void Timeline::switchTrackAudio(int ix, bool mute) { Track* tk = track(ix); if (tk == NULL) { qWarning() << "Set Track effect outisde of range: "<state(); if (mute && (state & 2)) { // audio is already muted return; } if (mute && state < 2 ) { // We mute a track with sound /*if (ix == getLowestNonMutedAudioTrack())*/ } else if (!mute && state > 1 ) { // We un-mute a previously muted track /*if (ix < getLowestNonMutedAudioTrack())*/ } int newstate; if (mute) { if (state & 1) newstate = 3; else newstate = 2; } else if (state & 1) { newstate = 1; } else { newstate = 0; } tk->setState(newstate); //if (audioMixingBroken) fixAudioMixing(); m_tractor->multitrack()->refresh(); m_tractor->refresh(); } int Timeline::getLowestVideoTrack() { for (int i = 1; i < m_tractor->count(); ++i) { QScopedPointer track(m_tractor->track(i)); Mlt::Playlist playlist(*track); if (playlist.get_int("kdenlive:audio_track") != 1) return i; } return -1; } void Timeline::fixAudioMixing() { QScopedPointer field(m_tractor->field()); field->lock(); mlt_service nextservice = mlt_service_get_producer(field->get_service()); mlt_properties properties = MLT_SERVICE_PROPERTIES(nextservice); QString mlt_type = mlt_properties_get(properties, "mlt_type"); QString resource = mlt_properties_get(properties, "mlt_service"); // Delete all audio mixing transitions while (mlt_type == QLatin1String("transition")) { if (resource == QLatin1String("mix")) { Mlt::Transition transition((mlt_transition) nextservice); nextservice = mlt_service_producer(nextservice); field->disconnect_service(transition); } else nextservice = mlt_service_producer(nextservice); if (nextservice == NULL) break; properties = MLT_SERVICE_PROPERTIES(nextservice); mlt_type = mlt_properties_get(properties, "mlt_type"); resource = mlt_properties_get(properties, "mlt_service"); } // Re-add correct audio transitions for (int i = 1; i < m_tractor->count(); i++) { //bool muted = getTrackInfo(i).isMute; //if (muted) continue; /*int a_track = qMax(lowestTrack, i - 1); bool a_muted = getTrackInfo(a_track).isMute; while (a_muted && a_track > lowestTrack) { a_track = qMax(lowestTrack, a_track - 1); a_muted = getTrackInfo(a_track).isMute; } if (a_muted) continue;*/ Mlt::Transition *transition = new Mlt::Transition(*m_tractor->profile(), "mix"); transition->set("always_active", 1); transition->set("combine", 1); transition->set("a_track", 0); transition->set("b_track", i); transition->set("internal_added", 237); field->plant_transition(*transition, 0, i); } field->unlock(); } void Timeline::updatePalette() { headers_container->setStyleSheet(QLatin1String("")); setPalette(qApp->palette()); QPalette p = qApp->palette(); KColorScheme scheme(p.currentColorGroup(), KColorScheme::View, KSharedConfig::openConfig(KdenliveSettings::colortheme())); QColor col = scheme.background().color(); QColor col2 = scheme.foreground().color(); headers_container->setStyleSheet(QStringLiteral("QLineEdit { background-color: transparent;color: %1;} QLineEdit:hover{ background-color: %2;} QLineEdit:focus { background-color: %2;} ").arg(col2.name(), col.name())); m_trackview->updatePalette(); if (!m_tracks.isEmpty()) { int ix = m_trackview->selectedTrack(); for (int i = 0; i < m_tracks.count(); i++) { if (m_tracks.at(i)->trackHeader) { m_tracks.at(i)->trackHeader->refreshPalette(); if (i == ix) m_tracks.at(ix)->trackHeader->setSelectedIndex(ix); } } } m_ruler->activateZone(); } void Timeline::updateHeaders() { if (!m_tracks.isEmpty()) { for (int i = 0; i < m_tracks.count(); i++) { if (m_tracks.at(i)->trackHeader) { m_tracks.at(i)->trackHeader->updateLed(); } } } } void Timeline::refreshIcons() { QList allMenus = this->findChildren(); for (int i = 0; i < allMenus.count(); i++) { QAction *m = allMenus.at(i); QIcon ic = m->icon(); if (ic.isNull() || ic.name().isEmpty()) continue; QIcon newIcon = KoIconUtils::themedIcon(ic.name()); m->setIcon(newIcon); } QList allButtons = this->findChildren(); for (int i = 0; i < allButtons.count(); i++) { KDualAction *m = allButtons.at(i); QIcon ic = m->activeIcon(); if (ic.isNull() || ic.name().isEmpty()) continue; QIcon newIcon = KoIconUtils::themedIcon(ic.name()); m->setActiveIcon(newIcon); ic = m->inactiveIcon(); if (ic.isNull() || ic.name().isEmpty()) continue; newIcon = KoIconUtils::themedIcon(ic.name()); m->setInactiveIcon(newIcon); } } void Timeline::adjustTrackHeaders() { if (m_tracks.isEmpty()) return; int height = KdenliveSettings::trackheight() * m_scene->scale().y() - 1; for (int i = 1; i < m_tracks.count(); i++) { m_tracks.at(i)->trackHeader->adjustSize(height); } } void Timeline::reloadTrack(int ix, int start, int end) { // Get playlist Mlt::Playlist pl = m_tracks.at(ix)->playlist(); if (end == -1) end = pl.get_length(); // Remove current clips int startIndex = pl.get_clip_index_at(start); int endIndex = pl.get_clip_index_at(end); double startY = m_trackview->getPositionFromTrack(ix) + 2; QRectF r(start, startY, end - start, 2); QList selection = m_scene->items(r); QList toDelete; for (int i = 0; i < selection.count(); i++) { if (selection.at(i)->type() == AVWidget) toDelete << selection.at(i); } qDeleteAll(toDelete); // Reload items loadTrack(ix, 0, pl, startIndex, endIndex, false); } int Timeline::loadTrack(int ix, int offset, Mlt::Playlist &playlist, int start, int end, bool updateReferences) { // parse track Mlt::ClipInfo *info = new Mlt::ClipInfo(); double fps = m_doc->fps(); if (end == -1) end = playlist.count(); bool locked = playlist.get_int("kdenlive:locked_track") == 1; for(int i = start; i < end; ++i) { emit loadingBin(offset + i + 1); if (playlist.is_blank(i)) { continue; } playlist.clip_info(i, info); Mlt::Producer *clip = info->cut; // Found a clip int in = info->frame_in; int out = info->frame_out; QString idString = info->producer->get("id"); if (in > out || m_invalidProducers.contains(idString)) { QString trackName = playlist.get("kdenlive:track_name"); m_documentErrors.append(i18n("Invalid clip removed from track %1 at %2\n", trackName.isEmpty() ? QString::number(ix) : trackName, info->start)); playlist.remove(i); --i; continue; } QString id = idString; Track::SlowmoInfo slowInfo; slowInfo.speed = 1.0; slowInfo.strobe = 1; slowInfo.state = PlaylistState::Original; bool hasSpeedEffect = false; if (idString.endsWith(QLatin1String("_video"))) { // Video only producer, store it in BinController m_doc->renderer()->loadExtraProducer(idString, new Mlt::Producer(clip->parent())); } if (idString.startsWith(QLatin1String("slowmotion"))) { hasSpeedEffect = true; QLocale locale; locale.setNumberOptions(QLocale::OmitGroupSeparator); id = idString.section(':', 1, 1); slowInfo.speed = locale.toDouble(idString.section(':', 2, 2)); slowInfo.strobe = idString.section(':', 3, 3).toInt(); if (slowInfo.strobe == 0) slowInfo.strobe = 1; slowInfo.state = (PlaylistState::ClipState) idString.section(':', 4, 4).toInt(); // Slowmotion producer, store it for reuse Mlt::Producer *parentProd = new Mlt::Producer(clip->parent()); QString url = parentProd->get("warp_resource"); if (!m_doc->renderer()->storeSlowmotionProducer(slowInfo.toString(locale) + url, parentProd)) { delete parentProd; } } id = id.section('_', 0, 0); int length = out - in + 1; ProjectClip *binclip = m_doc->getBinClip(id); if (binclip == NULL) { // Is this a disabled clip id = info->producer->get("kdenlive:binid"); binclip = m_doc->getBinClip(id); } if (binclip == NULL) { // Warning, unknown clip found, timeline corruption!! //TODO: fix this qDebug()<<"* * * * *UNKNOWN CLIP, WE ARE DEAD: "<addRef(); ItemInfo clipinfo; clipinfo.startPos = GenTime(info->start, fps); clipinfo.endPos = GenTime(info->start + length, fps); clipinfo.cropStart = GenTime(in, fps); clipinfo.cropDuration = GenTime(length, fps); clipinfo.track = ix; //qDebug()<<"// Loading clip: "<getFrameWidth(), true); connect(item, &AbstractClipItem::selectItem, m_trackview, &CustomTrackView::slotSelectItem); item->setPos(clipinfo.startPos.frames(fps), KdenliveSettings::trackheight() * (visibleTracksCount() - clipinfo.track) + 1 + item->itemOffset()); //qDebug()<<" * * Loaded clip on tk: "<updateState(idString, info->producer->get_int("audio_index"), info->producer->get_int("video_index")); m_scene->addItem(item); if (locked) item->setItemLocked(true); if (hasSpeedEffect) { QDomElement speedeffect = MainWindow::videoEffects.getEffectByTag(QString(), QStringLiteral("speed")).cloneNode().toElement(); EffectsList::setParameter(speedeffect, QStringLiteral("speed"), QString::number((int)(100 * slowInfo.speed + 0.5))); EffectsList::setParameter(speedeffect, QStringLiteral("strobe"), QString::number(slowInfo.strobe)); item->addEffect(m_doc->getProfileInfo(), speedeffect, false); } // parse clip effects getEffects(*clip, item); } delete info; return playlist.get_length(); } void Timeline::loadGuides(QMap guidesData) { QMapIterator i(guidesData); while (i.hasNext()) { i.next(); const GenTime pos = GenTime(i.key()); m_trackview->addGuide(pos, i.value(), true); } } void Timeline::getEffects(Mlt::Service &service, ClipItem *clip, int track) { int effectNb = clip == NULL ? 0 : clip->effectsCount(); QLocale locale; locale.setNumberOptions(QLocale::OmitGroupSeparator); for (int ix = 0; ix < service.filter_count(); ++ix) { QScopedPointer effect(service.filter(ix)); QDomElement clipeffect = getEffectByTag(effect->get("tag"), effect->get("kdenlive_id")); if (clipeffect.isNull()) { m_documentErrors.append(i18n("Effect %1:%2 not found in MLT, it was removed from this project\n", effect->get("tag"), effect->get("kdenlive_id"))); service.detach(*effect); --ix; continue; } effectNb++; QDomElement currenteffect = clipeffect.cloneNode().toElement(); currenteffect.setAttribute(QStringLiteral("kdenlive_ix"), QString::number(effectNb)); currenteffect.setAttribute(QStringLiteral("kdenlive_info"), effect->get("kdenlive_info")); currenteffect.setAttribute(QStringLiteral("disable"), effect->get("disable")); QDomNodeList clipeffectparams = currenteffect.childNodes(); QDomNodeList params = currenteffect.elementsByTagName(QStringLiteral("parameter")); ProfileInfo info = m_doc->getProfileInfo(); for (int i = 0; i < params.count(); ++i) { QDomElement e = params.item(i).toElement(); if (e.attribute(QStringLiteral("type")) == QLatin1String("keyframe")) e.setAttribute(QStringLiteral("keyframes"), getKeyframes(service, ix, e)); else setParam(info, e, effect->get(e.attribute(QStringLiteral("name")).toUtf8().constData())); } if (effect->get_out()) { // no keyframes but in/out points //EffectsList::setParameter(currenteffect, QStringLiteral("in"), effect->get("in")); //EffectsList::setParameter(currenteffect, QStringLiteral("out"), effect->get("out")); currenteffect.setAttribute(QStringLiteral("in"), effect->get_in()); currenteffect.setAttribute(QStringLiteral("out"), effect->get_out()); } QString sync = effect->get("kdenlive:sync_in_out"); if (!sync.isEmpty()) { currenteffect.setAttribute(QStringLiteral("kdenlive:sync_in_out"), sync); } if (QString(effect->get("tag")) == QLatin1String("region")) getSubfilters(effect.data(), currenteffect); if (clip) { clip->addEffect(m_doc->getProfileInfo(), currenteffect, false); } else { addTrackEffect(track, currenteffect); } } } QString Timeline::getKeyframes(Mlt::Service service, int &ix, QDomElement e) { QLocale locale; locale.setNumberOptions(QLocale::OmitGroupSeparator); QString starttag = e.attribute(QStringLiteral("starttag"), QStringLiteral("start")); QString endtag = e.attribute(QStringLiteral("endtag"), QStringLiteral("end")); double fact, offset = locale.toDouble(e.attribute(QStringLiteral("offset"), QStringLiteral("0"))); QString factor = e.attribute(QStringLiteral("factor"), QStringLiteral("1")); if (factor.contains('%')) fact = EffectsController::getStringEval(m_doc->getProfileInfo(), factor); else fact = locale.toDouble(factor); // retrieve keyframes QScopedPointer effect(service.filter(ix)); int effectNb = effect->get_int("kdenlive_ix"); QString keyframes = QString::number(effect->get_in()) + '=' + locale.toString(offset + fact * effect->get_double(starttag.toUtf8().constData())) + ';'; for (;ix < service.filter_count(); ++ix) { QScopedPointer eff2(service.filter(ix)); if (eff2->get_int("kdenlive_ix") != effectNb) break; if (eff2->get_in() < eff2->get_out()) { keyframes.append(QString::number(eff2->get_out()) + '=' + locale.toString(offset + fact * eff2->get_double(endtag.toUtf8().constData())) + ';'); } } --ix; return keyframes; } void Timeline::getSubfilters(Mlt::Filter *effect, QDomElement ¤teffect) { for (int i = 0; ; ++i) { QString name = "filter" + QString::number(i); if (!effect->get(name.toUtf8().constData())) break; //identify effect QString tag = effect->get(name.append(".tag").toUtf8().constData()); QString id = effect->get(name.append(".kdenlive_id").toUtf8().constData()); QDomElement subclipeffect = getEffectByTag(tag, id); if (subclipeffect.isNull()) { qWarning() << "Region sub-effect not found"; continue; } //load effect subclipeffect = subclipeffect.cloneNode().toElement(); subclipeffect.setAttribute(QStringLiteral("region_ix"), i); //get effect parameters (prefixed by subfilter name) QDomNodeList params = subclipeffect.elementsByTagName(QStringLiteral("parameter")); ProfileInfo info = m_doc->getProfileInfo(); for (int i = 0; i < params.count(); ++i) { QDomElement param = params.item(i).toElement(); setParam(info, param, effect->get((name + "." + param.attribute(QStringLiteral("name"))).toUtf8().constData())); } currenteffect.appendChild(currenteffect.ownerDocument().importNode(subclipeffect, true)); } } //static void Timeline::setParam(ProfileInfo info, QDomElement param, QString value) { QLocale locale; locale.setNumberOptions(QLocale::OmitGroupSeparator); //get Kdenlive scaling parameters double offset = locale.toDouble(param.attribute(QStringLiteral("offset"), QStringLiteral("0"))); double fact; QString factor = param.attribute(QStringLiteral("factor"), QStringLiteral("1")); if (factor.contains('%')) { fact = EffectsController::getStringEval(info, factor); } else { fact = locale.toDouble(factor); } //adjust parameter if necessary QString type = param.attribute(QStringLiteral("type")); if (type == QLatin1String("simplekeyframe")) { QStringList kfrs = value.split(';'); for (int l = 0; l < kfrs.count(); ++l) { QString fr = kfrs.at(l).section('=', 0, 0); double val = locale.toDouble(kfrs.at(l).section('=', 1, 1)); if (fact != 1) { // Add 0.5 since we are converting to integer below so that 0.8 is converted to 1 and not 0 val = val * fact + 0.5; } kfrs[l] = fr + '=' + QString::number((int) (val + offset)); } param.setAttribute(QStringLiteral("keyframes"), kfrs.join(QStringLiteral(";"))); } else if (type == QLatin1String("double") || type == QLatin1String("constant")) { param.setAttribute(QStringLiteral("value"), locale.toDouble(value) * fact + offset); } else { param.setAttribute(QStringLiteral("value"), value); } } QDomElement Timeline::getEffectByTag(const QString &effecttag, const QString &effectid) { QDomElement clipeffect = MainWindow::customEffects.getEffectByTag(QString(), effectid); if (clipeffect.isNull()) { clipeffect = MainWindow::videoEffects.getEffectByTag(effecttag, effectid); } if (clipeffect.isNull()) { clipeffect = MainWindow::audioEffects.getEffectByTag(effecttag, effectid); } return clipeffect; } QGraphicsScene *Timeline::projectScene() { return m_scene; } CustomTrackView *Timeline::projectView() { return m_trackview; } void Timeline::setEditMode(const QString & editMode) { m_editMode = editMode; } const QString & Timeline::editMode() const { return m_editMode; } void Timeline::slotVerticalZoomDown() { if (m_verticalZoom == 0) return; m_verticalZoom--; m_doc->setZoom(m_doc->zoom().x(), m_verticalZoom); if (m_verticalZoom == 0) m_trackview->setScale(m_scene->scale().x(), 0.5); else m_trackview->setScale(m_scene->scale().x(), 1); adjustTrackHeaders(); m_trackview->verticalScrollBar()->setValue(headers_area->verticalScrollBar()->value()); } void Timeline::slotVerticalZoomUp() { if (m_verticalZoom == 2) return; m_verticalZoom++; m_doc->setZoom(m_doc->zoom().x(), m_verticalZoom); if (m_verticalZoom == 2) m_trackview->setScale(m_scene->scale().x(), 2); else m_trackview->setScale(m_scene->scale().x(), 1); adjustTrackHeaders(); m_trackview->verticalScrollBar()->setValue(headers_area->verticalScrollBar()->value()); } void Timeline::slotRenameTrack(int ix, const QString &name) { QString currentName = track(ix)->getProperty(QStringLiteral("kdenlive:track_name")); if (currentName == name) return; ConfigTracksCommand *configTracks = new ConfigTracksCommand(this, ix, currentName, name); m_doc->commandStack()->push(configTracks); } void Timeline::renameTrack(int ix, const QString &name) { if (ix < 1) return; Track *tk = track(ix); if (!tk) return; tk->setProperty(QStringLiteral("kdenlive:track_name"), name); tk->trackHeader->renameTrack(name); slotReloadTracks(); } void Timeline::slotUpdateVerticalScroll(int /*min*/, int max) { int height = 0; if (max > 0) height = m_trackview->horizontalScrollBar()->height() - 1; headers_container->layout()->setContentsMargins(0, m_trackview->frameWidth(), 0, height); } void Timeline::updateRuler() { m_ruler->update(); } void Timeline::slotShowTrackEffects(int ix) { m_trackview->clearSelection(); emit showTrackEffects(ix, getTrackInfo(ix)); } void Timeline::slotUpdateTrackEffectState(int ix) { if (ix < 1) return; Track *tk = track(ix); if (!tk) return; tk->trackHeader->updateEffectLabel(tk->effectsList.effectNames()); } void Timeline::slotSaveTimelinePreview(const QString &path) { QImage img(width(), height(), QImage::Format_ARGB32_Premultiplied); img.fill(palette().base().color().rgb()); QPainter painter(&img); render(&painter); painter.end(); img = img.scaledToWidth(600, Qt::SmoothTransformation); img.save(path); } void Timeline::updateProfile(bool fpsChanged) { m_ruler->updateFrameSize(); m_ruler->updateProjectFps(m_doc->timecode()); m_ruler->setPixelPerMark(m_doc->zoom().x(), true); slotChangeZoom(m_doc->zoom().x(), m_doc->zoom().y()); slotSetZone(m_doc->zone(), false); m_trackview->updateSceneFrameWidth(fpsChanged); } void Timeline::checkTrackHeight(bool force) { if (m_trackview->checkTrackHeight(force)) { m_doc->clipManager()->clearCache(); m_ruler->updateFrameSize(); m_trackview->updateSceneFrameWidth(); slotChangeZoom(m_doc->zoom().x(), m_doc->zoom().y()); slotSetZone(m_doc->zone(), false); } } bool Timeline::moveClip(int startTrack, qreal startPos, int endTrack, qreal endPos, PlaylistState::ClipState state, TimelineMode::EditMode mode, bool duplicate) { if (startTrack == endTrack) { return track(startTrack)->move(startPos, endPos, mode); } Track *sourceTrack = track(startTrack); int pos = sourceTrack->frame(startPos); int clipIndex = sourceTrack->playlist().get_clip_index_at(pos); sourceTrack->playlist().lock(); Mlt::Producer *clipProducer = sourceTrack->playlist().replace_with_blank(clipIndex); sourceTrack->playlist().consolidate_blanks(); if (!clipProducer || clipProducer->is_blank()) { qDebug() << "// Cannot get clip at index: "<playlist().unlock(); return false; } sourceTrack->playlist().unlock(); Track *destTrack = track(endTrack); bool success = destTrack->add(endPos, clipProducer, GenTime(clipProducer->get_in(), destTrack->fps()).seconds(), GenTime(clipProducer->get_out() + 1, destTrack->fps()).seconds(), state, duplicate, mode); delete clipProducer; return success; } void Timeline::addTrackEffect(int trackIndex, QDomElement effect) { if (trackIndex < 0 || trackIndex >= m_tracks.count()) { qWarning() << "Set Track effect outisde of range"; return; } Track *sourceTrack = track(trackIndex); effect.setAttribute(QStringLiteral("kdenlive_ix"), sourceTrack->effectsList.count() + 1); // Init parameter value & keyframes if required QDomNodeList params = effect.elementsByTagName(QStringLiteral("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(QStringLiteral("default")).contains('%')) { double evaluatedValue = EffectsController::getStringEval(m_doc->getProfileInfo(), e.attribute(QStringLiteral("default"))); e.setAttribute(QStringLiteral("default"), evaluatedValue); if (e.hasAttribute(QStringLiteral("value")) && e.attribute(QStringLiteral("value")).startsWith('%')) { e.setAttribute(QStringLiteral("value"), evaluatedValue); } } if (!e.isNull() && (e.attribute(QStringLiteral("type")) == QLatin1String("keyframe") || e.attribute(QStringLiteral("type")) == QLatin1String("simplekeyframe"))) { QString def = e.attribute(QStringLiteral("default")); // Effect has a keyframe type parameter, we need to set the values if (e.attribute(QStringLiteral("keyframes")).isEmpty()) { e.setAttribute(QStringLiteral("keyframes"), "0:" + def + ';'); //qDebug() << "///// EFFECT KEYFRAMES INITED: " << e.attribute("keyframes"); //break; } } if (effect.attribute(QStringLiteral("id")) == QLatin1String("crop")) { // default use_profile to 1 for clips with proxies to avoid problems when rendering if (e.attribute(QStringLiteral("name")) == QLatin1String("use_profile") && m_doc->useProxy()) e.setAttribute(QStringLiteral("value"), QStringLiteral("1")); } } sourceTrack->effectsList.append(effect); } void Timeline::removeTrackEffect(int trackIndex, const QDomElement &effect) { if (trackIndex < 0 || trackIndex >= m_tracks.count()) { qWarning() << "Set Track effect outisde of range"; return; } int toRemove = effect.attribute(QStringLiteral("kdenlive_ix")).toInt(); Track *sourceTrack = track(trackIndex); int max = sourceTrack->effectsList.count(); for (int i = 0; i < max; ++i) { int index = sourceTrack->effectsList.at(i).attribute(QStringLiteral("kdenlive_ix")).toInt(); if (toRemove == index) { sourceTrack->effectsList.removeAt(toRemove); break; } } } void Timeline::setTrackEffect(int trackIndex, int effectIndex, QDomElement effect) { if (trackIndex < 0 || trackIndex >= m_tracks.count()) { qWarning() << "Set Track effect outisde of range"; return; } Track *sourceTrack = track(trackIndex); int max = sourceTrack->effectsList.count(); if (effectIndex <= 0 || effectIndex > (max) || effect.isNull()) { //qDebug() << "Invalid effect index: " << effectIndex; return; } sourceTrack->effectsList.removeAt(effect.attribute(QStringLiteral("kdenlive_ix")).toInt()); effect.setAttribute(QStringLiteral("kdenlive_ix"), effectIndex); sourceTrack->effectsList.insert(effect); } void Timeline::enableTrackEffects(int trackIndex, const QList &effectIndexes, bool disable) { if (trackIndex < 0 || trackIndex >= m_tracks.count()) { qWarning() << "Set Track effect outisde of range"; return; } Track *sourceTrack = track(trackIndex); EffectsList list = sourceTrack->effectsList; QDomElement effect; for (int i = 0; i < effectIndexes.count(); ++i) { effect = list.itemFromIndex(effectIndexes.at(i)); if (!effect.isNull()) effect.setAttribute(QStringLiteral("disable"), (int) disable); } } const EffectsList Timeline::getTrackEffects(int trackIndex) { if (trackIndex < 0 || trackIndex >= m_tracks.count()) { qWarning() << "Set Track effect outisde of range"; return EffectsList(); } Track *sourceTrack = track(trackIndex); return sourceTrack->effectsList; } QDomElement Timeline::getTrackEffect(int trackIndex, int effectIndex) { if (trackIndex < 0 || trackIndex >= m_tracks.count()) { qWarning() << "Set Track effect outisde of range"; return QDomElement(); } Track *sourceTrack = track(trackIndex); EffectsList list = sourceTrack->effectsList; if (effectIndex > list.count() || effectIndex < 1 || list.itemFromIndex(effectIndex).isNull()) return QDomElement(); return list.itemFromIndex(effectIndex).cloneNode().toElement(); } int Timeline::hasTrackEffect(int trackIndex, const QString &tag, const QString &id) { if (trackIndex < 0 || trackIndex >= m_tracks.count()) { qWarning() << "Set Track effect outisde of range"; return -1; } Track *sourceTrack = track(trackIndex); EffectsList list = sourceTrack->effectsList; return list.hasEffect(tag, id); } MltVideoProfile Timeline::mltProfile() const { return ProfilesDialog::getVideoProfile(*m_tractor->profile()); } double Timeline::fps() const { return m_doc->fps(); } QPoint Timeline::getTracksCount() { int audioTracks = 0; int videoTracks = 0; int max = m_tracks.count(); for (int i = 0; i < max; i++) { Track *tk = track(i); if (tk->type == AudioTrack) audioTracks++; else videoTracks++; } return QPoint(videoTracks, audioTracks); } int Timeline::getTrackSpaceLength(int trackIndex, int pos, bool fromBlankStart) { if (trackIndex < 0 || trackIndex >= m_tracks.count()) { qWarning() << "Set Track effect outisde of range"; return 0; } return track(trackIndex)->getBlankLength(pos, fromBlankStart); } void Timeline::updateClipProperties(const QString &id, QMap properties) { for (int i = 0; i< m_tracks.count(); i++) { track(i)->updateClipProperties(id, properties); } } int Timeline::changeClipSpeed(ItemInfo info, ItemInfo speedIndependantInfo, PlaylistState::ClipState state, double speed, int strobe, Mlt::Producer *originalProd, bool removeEffect) { QLocale locale; QString url = QString::fromUtf8(originalProd->get("resource")); Track::SlowmoInfo slowInfo; slowInfo.speed = speed; slowInfo.strobe = strobe; slowInfo.state = state; url.prepend(slowInfo.toString(locale)); //if (strobe > 1) url.append("&strobe=" + QString::number(strobe)); Mlt::Producer *prod; if (removeEffect) { // We want to remove framebuffer producer, so pass original prod = originalProd; } else { // Pass slowmotion producer prod = m_doc->renderer()->getSlowmotionProducer(url); } QString id = originalProd->get("id"); id = id.section(QStringLiteral("_"), 0, 0); Mlt::Properties passProperties; Mlt::Properties original(originalProd->get_properties()); passProperties.pass_list(original, ClipController::getPassPropertiesList(false)); return track(info.track)->changeClipSpeed(info, speedIndependantInfo, state, speed, strobe, prod, id, passProperties); } void Timeline::duplicateClipOnPlaylist(int tk, qreal startPos, int offset, Mlt::Producer *prod) { Track *sourceTrack = track(tk); int pos = sourceTrack->frame(startPos); int clipIndex = sourceTrack->playlist().get_clip_index_at(pos); if (sourceTrack->playlist().is_blank(clipIndex)) { qDebug()<<"// ERROR FINDING CLIP on TK: "<playlist().get_clip(clipIndex); Clip clp(clipProducer->parent()); Mlt::Producer *cln = clp.clone(); // Clip effects must be moved from clip to the playlist entry, so first delete them from parent clip Clip(*cln).deleteEffects(); cln->set_in_and_out(clipProducer->get_in(), clipProducer->get_out()); Mlt::Playlist trackPlaylist((mlt_playlist) prod->get_service()); trackPlaylist.lock(); int newIdx = trackPlaylist.insert_at(pos - offset, cln, 1); // Re-add source effects in playlist Mlt::Producer *inPlaylist = trackPlaylist.get_clip(newIdx); if (inPlaylist) { Clip(*inPlaylist).addEffects(*clipProducer); delete inPlaylist; } trackPlaylist.unlock(); delete clipProducer; delete cln; delete prod; } int Timeline::getSpaceLength(const GenTime &pos, int tk, bool fromBlankStart) { Track *sourceTrack = track(tk); if (!sourceTrack) return 0; int insertPos = pos.frames(m_doc->fps()); return sourceTrack->spaceLength(insertPos, fromBlankStart); } void Timeline::disableTimelineEffects(bool disable) { for (int i = 0; i< tracksCount(); i++) { track(i)->disableEffects(disable); } } void Timeline::importPlaylist(ItemInfo info, QMap processedUrl, QMap idMaps, QDomDocument doc, QUndoCommand *command) { projectView()->importPlaylist(info, processedUrl, idMaps, doc, command); } void Timeline::refreshTrackActions() { int tracks = tracksCount(); if (tracks > 3) { return; } foreach(QAction *action, m_trackActions) { if (action->data().toString() == "delete_track") { action->setEnabled(tracks > 2); } } } void Timeline::slotMultitrackView(bool enable) { multitrackView = enable; transitionHandler->enableMultiTrack(enable); } void Timeline::connectOverlayTrack(bool enable) { if (!m_hasOverlayTrack) return; m_tractor->lock(); if (enable) { // Re-add overlaytrack m_tractor->insert_track(*m_overlayTrack, tracksCount() + 1); delete m_overlayTrack; m_overlayTrack = NULL; } else { m_overlayTrack = m_tractor->track(tracksCount()); m_tractor->remove_track(tracksCount()); } m_tractor->unlock(); } void Timeline::removeSplitOverlay() { if (!m_hasOverlayTrack) return; m_tractor->lock(); m_tractor->remove_track(tracksCount()); m_hasOverlayTrack = false; m_tractor->unlock(); } bool Timeline::createOverlay(Mlt::Filter *filter, int tk, int startPos) { Track *sourceTrack = track(tk); if (!sourceTrack) return false; m_tractor->lock(); int clipIndex = sourceTrack->playlist().get_clip_index_at(startPos); Mlt::Producer *clipProducer = sourceTrack->playlist().get_clip(clipIndex); Clip clp(clipProducer->parent()); Mlt::Producer *cln = clp.clone(); Clip(*cln).deleteEffects(); cln->set_in_and_out(clipProducer->get_in(), clipProducer->get_out()); Mlt::Playlist overlay(*m_tractor->profile()); Mlt::Tractor trac(*m_tractor->profile()); trac.set_track(*clipProducer, 0); trac.set_track(*cln, 1); cln->attach(*filter); QString splitTransition = KdenliveSettings::gpu_accel() ? "movit.overlay" : "frei0r.cairoblend"; Mlt::Transition t(*m_tractor->profile(), splitTransition.toUtf8().constData()); t.set("always_active", 1); trac.plant_transition(t, 0, 1); delete cln; delete clipProducer; overlay.insert_blank(0, startPos); Mlt::Producer split(trac.get_producer()); overlay.insert_at(startPos, &split, 1); int trackIndex = tracksCount(); m_tractor->insert_track(overlay, trackIndex); Mlt::Producer *overlayTrack = m_tractor->track(trackIndex); overlayTrack->set("hide", 2); delete overlayTrack; m_hasOverlayTrack = true; m_tractor->unlock(); return true; } void Timeline::switchTrackTarget() { if (!KdenliveSettings::splitaudio()) { // This feature is only available on split mode return; } Track *current = m_tracks.at(m_trackview->selectedTrack()); TrackType trackType = current->info().type; if (trackType == VideoTrack) { if (m_trackview->selectedTrack() == videoTarget) { // Switch off current->trackHeader->switchTarget(false); videoTarget = -1; } else { if (videoTarget > -1) m_tracks.at(videoTarget)->trackHeader->switchTarget(false); current->trackHeader->switchTarget(true); videoTarget = m_trackview->selectedTrack(); } } else if (trackType == AudioTrack) { if (m_trackview->selectedTrack() == audioTarget) { // Switch off current->trackHeader->switchTarget(false); audioTarget = -1; } else { if (audioTarget > -1) m_tracks.at(audioTarget)->trackHeader->switchTarget(false); current->trackHeader->switchTarget(true); audioTarget = m_trackview->selectedTrack(); } } } void Timeline::slotEnableZone(bool enable) { KdenliveSettings::setUseTimelineZoneToEdit(enable); m_ruler->activateZone(); } + +void Timeline::gotPreviewRender(int frame, const QString &file, int progress) +{ + m_ruler->updatePreview(frame); + if (!m_hasOverlayTrack) { + // Create overlay track + Mlt::Playlist overlay(*m_tractor->profile()); + int trackIndex = tracksCount(); + m_tractor->lock(); + m_tractor->insert_track(overlay, trackIndex); + m_tractor->unlock(); + m_hasOverlayTrack = true; + } + Mlt::Producer *overlayTrack = m_tractor->track(tracksCount()); + m_tractor->lock(); + Mlt::Playlist trackPlaylist((mlt_playlist) overlayTrack->get_service()); + delete overlayTrack; + if (trackPlaylist.is_blank_at(frame)) { + Mlt::Producer prod(*m_tractor->profile(), 0, file.toUtf8().constData()); + prod.set("mlt_service", "avformat-novalidate"); + trackPlaylist.insert_at(frame, &prod, 1); + } + m_tractor->unlock(); + m_doc->progressInfo(i18n("Rendering preview"), progress); + //m_doc->updatePreview(progress); +} + +void Timeline::invalidatePreview(int startFrame, int length) +{ + if (!m_hasOverlayTrack) + return; + int start = startFrame / 200; + int end = lrintf((startFrame + length) / 200); + Mlt::Producer *overlayTrack = m_tractor->track(tracksCount()); + m_tractor->lock(); + Mlt::Playlist trackPlaylist((mlt_playlist) overlayTrack->get_service()); + delete overlayTrack; + QList list; + for (int i = start; i <=end; i++) { + int ix = trackPlaylist.get_clip_index_at(200 * i); + if (trackPlaylist.is_blank(i)) + continue; + list << i; + Mlt::Producer *prod = trackPlaylist.replace_with_blank(ix); + delete prod; + m_ruler->updatePreview(i * 200, false); + } + trackPlaylist.consolidate_blanks(); + m_tractor->unlock(); + m_doc->invalidatePreviews(list); +} diff --git a/src/timeline/timeline.h b/src/timeline/timeline.h index c1f6279ac..b0ed868bb 100644 --- a/src/timeline/timeline.h +++ b/src/timeline/timeline.h @@ -1,256 +1,258 @@ /*************************************************************************** * Copyright (C) 2007 by Jean-Baptiste Mardelle (jb@kdenlive.org) * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * 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 * ***************************************************************************/ /** * @class Timeline * @brief Manages the timline * @author Jean-Baptiste Mardelle */ #ifndef TRACKVIEW_H #define TRACKVIEW_H #include "timeline/customtrackscene.h" #include "effectslist/effectslist.h" #include "ui_timeline_ui.h" #include "definitions.h" #include #include #include #include class Track; class ClipItem; class CustomTrackView; class KdenliveDoc; class TransitionHandler; class CustomRuler; class QUndoCommand; class Timeline : public QWidget, public Ui::TimeLine_UI { Q_OBJECT public: explicit Timeline(KdenliveDoc *doc, const QList & actions, bool *ok, QWidget *parent = 0); virtual ~ Timeline(); /** @brief is multitrack view (split screen for tracks) enabled */ bool multitrackView; int videoTarget; int audioTarget; Track* track(int i); /** @brief Number of tracks in the MLT playlist. */ int tracksCount() const; /** @brief Number of visible tracks (= tracksCount() - 1 ) because black trck is not visible to user. */ int visibleTracksCount() const; void setEditMode(const QString & editMode); const QString & editMode() const; QGraphicsScene *projectScene(); CustomTrackView *projectView(); int duration() const; KdenliveDoc *document(); void refresh() ; int outPoint() const; int inPoint() const; int fitZoom() const; /** @brief This object handles all transition operation. */ TransitionHandler *transitionHandler; void lockTrack(int ix, bool lock); bool isTrackLocked(int ix); /** @brief Dis / enable video for a track. */ void switchTrackVideo(int ix, bool hide); /** @brief Dis / enable audio for a track. */ void switchTrackAudio(int ix, bool mute); /** @brief Adjust audio transitions depending on tracks muted state. */ void fixAudioMixing(); /** @brief Updates (redraws) the ruler. * * Used to change from displaying frames to timecode or vice versa. */ void updateRuler(); /** @brief Parse tracks to see if project has audio in it. * * Parses all tracks to check if there is audio data. */ bool checkProjectAudio(); /** @brief Load guides from data */ void loadGuides(QMap guidesData); void checkTrackHeight(bool force = false); void updatePalette(); void refreshIcons(); /** @brief Returns a kdenlive effect xml description from an effect tag / id */ static QDomElement getEffectByTag(const QString &effecttag, const QString &effectid); /** @brief Move a clip between tracks */ bool moveClip(int startTrack, qreal startPos, int endTrack, qreal endPos, PlaylistState::ClipState state, TimelineMode::EditMode mode, bool duplicate); void renameTrack(int ix, const QString &name); void updateTrackState(int ix, int state); /** @brief Returns info about a track. * @param ix The track number in MLT's coordinates (0 = black track, 1 = bottom audio, etc) * deprecated use string version with track name instead */ TrackInfo getTrackInfo(int ix); void setTrackInfo(int trackIndex, TrackInfo info); QList getTracksInfo(); QStringList getTrackNames(); void addTrackEffect(int trackIndex, QDomElement effect); void removeTrackEffect(int trackIndex, const QDomElement &effect); void setTrackEffect(int trackIndex, int effectIndex, QDomElement effect); void enableTrackEffects(int trackIndex, const QList &effectIndexes, bool disable); const EffectsList getTrackEffects(int trackIndex); QDomElement getTrackEffect(int trackIndex, int effectIndex); int hasTrackEffect(int trackIndex, const QString &tag, const QString &id); MltVideoProfile mltProfile() const; double fps() const; QPoint getTracksCount(); /** @brief Check if we have a blank space on selected track. * Returns -1 if track is shorter, 0 if not blank and > 0 for blank length */ int getTrackSpaceLength(int trackIndex, int pos, bool fromBlankStart); void updateClipProperties(const QString &id, QMap properties); int changeClipSpeed(ItemInfo info, ItemInfo speedIndependantInfo, PlaylistState::ClipState state, double speed, int strobe, Mlt::Producer *originalProd, bool removeEffect = false); /** @brief Set an effect's XML accordingly to MLT::filter values. */ static void setParam(ProfileInfo info, QDomElement param, QString value); int getTracks(); void getTransitions(); void refreshTractor(); void duplicateClipOnPlaylist(int tk, qreal startPos, int offset, Mlt::Producer *prod); int getSpaceLength(const GenTime &pos, int tk, bool fromBlankStart); void blockTrackSignals(bool block); /** @brief Load document */ void loadTimeline(); /** @brief Dis/enable all effects in timeline*/ void disableTimelineEffects(bool disable); QString getKeyframes(Mlt::Service service, int &ix, QDomElement e); void getSubfilters(Mlt::Filter *effect, QDomElement ¤teffect); static bool isSlide(QString geometry); /** @brief Import amultitrack MLT playlist in timeline */ void importPlaylist(ItemInfo info, QMap processedUrl, QMap idMaps, QDomDocument doc, QUndoCommand *command); /** @brief Creates an overlay track with a filtered clip */ bool createOverlay(Mlt::Filter *filter, int tk, int startPos); void removeSplitOverlay(); /** @brief Temporarily add/remove track before saving */ void connectOverlayTrack(bool enable); /** @brief Update composite transitions's tracks */ void updateComposites(); /** @brief Switch current track target state */ void switchTrackTarget(); /** @brief Refresh Header Leds */ void updateHeaders(); /** @brief Returns true if position is on the last clip */ bool isLastClip(ItemInfo info); /** @brief find lowest video track in timeline. */ int getLowestVideoTrack(); /** @brief Returns the document properties with some added values from timeline. */ QMap documentProperties(); void reloadTrack(int ix, int start = 0, int end = -1); protected: void keyPressEvent(QKeyEvent * event); public slots: void slotDeleteClip(const QString &clipId, QUndoCommand *deleteCommand); void slotChangeZoom(int horizontal, int vertical = -1); void setDuration(int dur); void slotSetZone(const QPoint &p, bool updateDocumentProperties = true); /** @brief Save a snapshot image of current timeline view */ void slotSaveTimelinePreview(const QString &path); void checkDuration(int duration); void slotShowTrackEffects(int); void updateProfile(bool fpsChanged); /** @brief Enable/disable multitrack view (split monitor in 4) */ void slotMultitrackView(bool enable); private: Mlt::Tractor *m_tractor; QList m_tracks; /** @brief number of special overlay tracks to preview effects */ bool m_hasOverlayTrack; Mlt::Producer *m_overlayTrack; CustomRuler *m_ruler; CustomTrackView *m_trackview; QList m_invalidProducers; double m_scale; QString m_editMode; CustomTrackScene *m_scene; /** @brief A list of producer ids to be replaced when opening a corrupted document*/ QMap m_replacementProducerIds; KdenliveDoc *m_doc; int m_verticalZoom; QString m_documentErrors; QList m_trackActions; void adjustTrackHeaders(); void parseDocument(const QDomDocument &doc); int loadTrack(int ix, int offset, Mlt::Playlist &playlist, int start = 0, int end = -1, bool updateReferences = true); void getEffects(Mlt::Service &service, ClipItem *clip, int track = 0); void adjustDouble(QDomElement &e, const QString &value); /** @brief Adjust kdenlive effect xml parameters to the MLT value*/ void adjustparameterValue(QDomNodeList clipeffectparams, const QString ¶mname, const QString ¶mvalue); /** @brief Enable/disable track actions depending on number of tracks */ void refreshTrackActions(); private slots: void slotSwitchTrackComposite(int trackIndex, bool enable); void setCursorPos(int pos); void moveCursorPos(int pos); /** @brief The tracks count or a track name changed, rebuild and notify */ void slotReloadTracks(); void slotVerticalZoomDown(); void slotVerticalZoomUp(); /** @brief Changes the name of a track. * @param ix Number of the track * @param name New name */ void slotRenameTrack(int ix, const QString &name); void slotRepaintTracks(); /** @brief Adjusts the margins of the header area. * * Avoid a shift between header area and trackview if * the horizontal scrollbar is visible and the position * of the vertical scrollbar is maximal */ void slotUpdateVerticalScroll(int min, int max); /** @brief Update the track label showing applied effects.*/ void slotUpdateTrackEffectState(int); /** @brief Toggle use of timeline zone for editing.*/ void slotEnableZone(bool enable); + void gotPreviewRender(int frame, const QString &file, int progress); + void invalidatePreview(int startFrame, int length); signals: void mousePosition(int); void cursorMoved(); void zoneMoved(int, int); void configTrack(); void updateTracksInfo(); void setZoom(int); void showTrackEffects(int, const TrackInfo&); /** @brief Indicate how many clips we are going to load */ void startLoadingBin(int); /** @brief Indicate which clip we are currently loading */ void loadingBin(int); /** @brief We are about to reload timeline, reset bin clip usage */ void resetUsageCount(); }; #endif diff --git a/src/timeline/track.cpp b/src/timeline/track.cpp index 3bbc235df..b49bbd053 100644 --- a/src/timeline/track.cpp +++ b/src/timeline/track.cpp @@ -1,834 +1,872 @@ /* * Kdenlive timeline track handling MLT playlist * Copyright 2015 Kdenlive team * Author: Vincent Pinon * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "track.h" #include "headertrack.h" #include "kdenlivesettings.h" #include "clip.h" #include #include #include Track::Track(int index, const QList &actions, Mlt::Playlist &playlist, TrackType type, QWidget *parent) : effectsList(EffectsList(true)), type(type), trackHeader(NULL), m_index(index), m_playlist(playlist) { QString playlist_name = playlist.get("id"); if (playlist_name != "black_track") { trackHeader = new HeaderTrack(info(), actions, this, parent); } } Track::~Track() { if (trackHeader) trackHeader->deleteLater(); } // members access Mlt::Playlist & Track::playlist() { return m_playlist; } qreal Track::fps() { return m_playlist.get_fps(); } int Track::frame(qreal t) { return round(t * fps()); } qreal Track::length() { return m_playlist.get_playtime() / fps(); } // basic clip operations bool Track::add(qreal t, Mlt::Producer *parent, qreal tcut, qreal dtcut, PlaylistState::ClipState state, bool duplicate, TimelineMode::EditMode mode) { Mlt::Producer *cut = NULL; if (parent == NULL || !parent->is_valid()) { return false; } if (state == PlaylistState::Disabled) { QScopedPointer prodCopy(Clip(*parent).clone()); prodCopy->set("video_index", -1); prodCopy->set("audio_index", -1); prodCopy->set("kdenlive:binid", parent->get("id")); cut = prodCopy->cut(frame(tcut), frame(dtcut) - 1); } else if (duplicate && state != PlaylistState::VideoOnly) { QScopedPointer newProd(clipProducer(parent, state)); cut = newProd->cut(frame(tcut), frame(dtcut) - 1); } else { cut = parent->cut(frame(tcut), frame(dtcut) - 1); } if (parent->is_cut()) { Clip(*cut).addEffects(*parent); } m_playlist.lock(); bool result = doAdd(t, cut, mode); m_playlist.unlock(); delete cut; return result; } bool Track::doAdd(qreal t, Mlt::Producer *cut, TimelineMode::EditMode mode) { int pos = frame(t); int len = cut->get_out() - cut->get_in() + 1; if (pos < m_playlist.get_playtime() && mode > 0) { if (mode == TimelineMode::OverwriteEdit) { m_playlist.remove_region(pos, len); } else if (mode == TimelineMode::InsertEdit) { m_playlist.split_at(pos); } //m_playlist.insert_blank(m_playlist.get_clip_index_at(pos), len); } m_playlist.consolidate_blanks(); if (m_playlist.insert_at(pos, cut, 1) == m_playlist.count() - 1) { emit newTrackDuration(m_playlist.get_playtime()); } + emit invalidatePreview(pos, len); return true; } bool Track::move(qreal start, qreal end, TimelineMode::EditMode mode) { int pos = frame(start); m_playlist.lock(); int clipIndex = m_playlist.get_clip_index_at(pos); bool durationChanged = false; if (clipIndex == m_playlist.count() - 1) { durationChanged = true; } QScopedPointer clipProducer(m_playlist.replace_with_blank(clipIndex)); if (!clipProducer || clipProducer->is_blank()) { qDebug() << "// Cannot get clip at index: "<= m_playlist.get_playtime()) { // Clip is inserted at the end of track, duration change event handled in doAdd() durationChanged = false; } bool result = doAdd(end, clipProducer.data(), mode); m_playlist.unlock(); if (durationChanged) { emit newTrackDuration(m_playlist.get_playtime()); } + emit invalidatePreview(pos, clipProducer->get_playtime()); return result; } bool Track::isLastClip(qreal t) { int clipIndex = m_playlist.get_clip_index_at(frame(t)); if (clipIndex >= m_playlist.count() - 1) { return true; } return false; } bool Track::del(qreal t) { m_playlist.lock(); bool durationChanged = false; - int ix = m_playlist.get_clip_index_at(frame(t)); + int pos = frame(t); + int ix = m_playlist.get_clip_index_at(pos); if (ix == m_playlist.count() - 1) { durationChanged = true; } + int length = 0; Mlt::Producer *clip = m_playlist.replace_with_blank(ix); - if (clip) + if (clip) { + length = clip->get_playtime(); delete clip; + } else { - qWarning("Error deleting clip at %d, tk: %d", frame(t), m_index); + qWarning("Error deleting clip at %d, tk: %d", pos, m_index); m_playlist.unlock(); return false; } m_playlist.consolidate_blanks(); m_playlist.unlock(); if (durationChanged) { emit newTrackDuration(m_playlist.get_playtime()); } + emit invalidatePreview(pos, length); return true; } bool Track::del(qreal t, qreal dt) { m_playlist.lock(); m_playlist.insert_blank(m_playlist.remove_region(frame(t), frame(dt) + 1), frame(dt)); m_playlist.consolidate_blanks(); m_playlist.unlock(); + emit invalidatePreview(frame(t), frame(dt)); return true; } bool Track::resize(qreal t, qreal dt, bool end) { m_playlist.lock(); - int index = m_playlist.get_clip_index_at(frame(t)); + int startFrame = frame(t); + int index = m_playlist.get_clip_index_at(startFrame); int length = frame(dt); + int updateLength = length; QScopedPointer clip(m_playlist.get_clip(index)); if (clip == NULL || clip->is_blank()) { qWarning("Can't resize clip at %f", t); m_playlist.unlock(); return false; } int in = clip->get_in(); int out = clip->get_out(); - if (end) out += length; else in += length; + if (end) { + // Resizing clip end + startFrame += out - in; + out += length; + } else { + // Resizing clip start + in += length; + } //image or color clips are not bounded if (in < 0) {out -= in; in = 0;} if (clip->get_length() < out + 1) { clip->parent().set("length", out + 2); clip->set("length", out + 2); } if (m_playlist.resize_clip(index, in, out)) { qWarning("MLT resize failed : clip %d from %d to %d", index, in, out); m_playlist.unlock(); return false; } //adjust adjacent blank if (end) { ++index; if (index > m_playlist.count() - 1) { m_playlist.unlock(); // this is the last clip in track, check tracks length to adjust black track and project duration emit newTrackDuration(m_playlist.get_playtime()); + if (updateLength > 0) + emit invalidatePreview(startFrame, updateLength); + else + emit invalidatePreview(startFrame + updateLength, -updateLength); return true; } length = -length; } if (length > 0) { // reducing clip m_playlist.insert_blank(index, length - 1); } else { if (!end) --index; if (!m_playlist.is_blank(index)) { qWarning("Resizing over non-blank clip %d!", index); } out = m_playlist.clip_length(index) + length - 1; if (out >= 0) { if (m_playlist.resize_clip(index, 0, out)) { qWarning("Error resizing blank %d", index); } } else { if (m_playlist.remove(index)) { qWarning("Error removing blank %d", index); } } } m_playlist.consolidate_blanks(); m_playlist.unlock(); + if (updateLength > 0) + emit invalidatePreview(startFrame, updateLength); + else + emit invalidatePreview(startFrame + updateLength, -updateLength); return true; } bool Track::cut(qreal t) { int pos = frame(t); m_playlist.lock(); int index = m_playlist.get_clip_index_at(pos); if (m_playlist.is_blank(index)) { return false; } if (m_playlist.split(index, pos - m_playlist.clip_start(index) - 1)) { qWarning("MLT split failed"); m_playlist.unlock(); return false; } m_playlist.unlock(); QScopedPointer clip1(m_playlist.get_clip(index + 1)); QScopedPointer clip2(m_playlist.get_clip(index)); Clip (*clip1).addEffects(*clip2, true); // adjust filters in/out Clip (*clip2).adjustEffectsLength(); return true; } bool Track::needsDuplicate(const QString &service) const { return (service.contains(QStringLiteral("avformat")) || service.contains(QStringLiteral("consumer")) || service.contains(QStringLiteral("xml"))); } void Track::lockTrack(bool locked) { if (!trackHeader) return; setProperty(QStringLiteral("kdenlive:locked_track"), locked ? 1 : 0); trackHeader->setLock(locked); } void Track::replaceId(const QString &id) { QString idForAudioTrack = id + QLatin1Char('_') + m_playlist.get("id") + "_audio"; QString idForVideoTrack = id + "_video"; QString idForTrack = id + QLatin1Char('_') + m_playlist.get("id"); //TODO: slowmotion for (int i = 0; i < m_playlist.count(); i++) { if (m_playlist.is_blank(i)) continue; QScopedPointer p(m_playlist.get_clip(i)); QString current = p->parent().get("id"); if (current == id || current == idForTrack || current == idForAudioTrack || current == idForVideoTrack || current.startsWith("slowmotion:" + id + ":")) { current.prepend("#"); p->parent().set("id", current.toUtf8().constData()); } } } QList Track::getSlowmotionInfos(const QString &id) { QList list; QLocale locale; for (int i = 0; i < m_playlist.count(); i++) { if (m_playlist.is_blank(i)) continue; QScopedPointer p(m_playlist.get_clip(i)); QString current = p->parent().get("id"); if (!current.startsWith(QLatin1String("#"))) { continue; } current.remove(0, 1); if (current.startsWith("slowmotion:" + id + ":")) { Track::SlowmoInfo info; info.readFromString(current.section(":", 2), locale); list << info; } } return list; } bool Track::replaceAll(const QString &id, Mlt::Producer *original, Mlt::Producer *videoOnlyProducer, QMap newSlowMos) { bool found = false; QString idForAudioTrack; QString idForVideoTrack; QString service = original->parent().get("mlt_service"); QString idForTrack = original->parent().get("id"); QLocale locale; + QList updateList; if (needsDuplicate(service)) { // We have to use the track clip duplication functions, because of audio glitches in MLT's multitrack idForAudioTrack = idForTrack + QLatin1Char('_') + m_playlist.get("id") + "_audio"; idForVideoTrack = idForTrack + "_video"; idForTrack.append(QLatin1Char('_') + m_playlist.get("id")); } Mlt::Producer *trackProducer = NULL; Mlt::Producer *audioTrackProducer = NULL; for (int i = 0; i < m_playlist.count(); i++) { if (m_playlist.is_blank(i)) continue; QScopedPointer p(m_playlist.get_clip(i)); QString current = p->parent().get("id"); if (!current.startsWith(QLatin1String("#"))) { continue; } current.remove(0, 1); Mlt::Producer *cut = NULL; + updateList << QPoint(m_playlist.clip_start(i), m_playlist.clip_length(i)); if (current.startsWith("slowmotion:" + id + ":")) { // Slowmotion producer, just update resource Mlt::Producer *slowProd = newSlowMos.value(current.section(QStringLiteral(":"), 2)); if (!slowProd || !slowProd->is_valid()) { qDebug()<<"/// WARNING, couldn't find replacement slowmo for "<cut(p->get_in(), p->get_out()); } if (!cut && idForAudioTrack.isEmpty()) { if (current == idForTrack) { // No duplication required cut = original->cut(p->get_in(), p->get_out()); } else { continue; } } if (!cut && p->parent().get_int("audio_index") == -1 && current == id) { // No audio - no duplication required cut = original->cut(p->get_in(), p->get_out()); } else if (!cut && current == idForTrack) { // Use duplicate if (trackProducer == NULL) { trackProducer = Clip(*original).clone(); trackProducer->set("id", idForTrack.toUtf8().constData()); } cut = trackProducer->cut(p->get_in(), p->get_out()); } else if (!cut && current == idForAudioTrack) { if (audioTrackProducer == NULL) { audioTrackProducer = clipProducer(original, PlaylistState::AudioOnly, true); } cut = audioTrackProducer->cut(p->get_in(), p->get_out()); } else if (!cut && current == idForVideoTrack) { cut = videoOnlyProducer->cut(p->get_in(), p->get_out()); } if (cut) { Clip(*cut).addEffects(*p); m_playlist.remove(i); m_playlist.insert(*cut, i); m_playlist.consolidate_blanks(); found = true; delete cut; } } + foreach(const QPoint &pt, updateList) { + emit invalidatePreview(pt.x(), pt.y()); + } return found; } //TODO: cut: checkSlowMotionProducer bool Track::replace(qreal t, Mlt::Producer *prod, PlaylistState::ClipState state) { m_playlist.lock(); int index = m_playlist.get_clip_index_at(frame(t)); Mlt::Producer *cut; QScopedPointer orig(m_playlist.replace_with_blank(index)); QString service = prod->get("mlt_service"); if (state == PlaylistState::Disabled) { QScopedPointer prodCopy(Clip(*prod).clone()); prodCopy->set("video_index", -1); prodCopy->set("audio_index", -1); prodCopy->set("kdenlive:binid", prod->get("id")); cut = prodCopy->cut(orig->get_in(), orig->get_out()); } else if (state != PlaylistState::VideoOnly && service != QLatin1String("timewarp")) { // Get track duplicate Mlt::Producer *copyProd = clipProducer(prod, state); cut = copyProd->cut(orig->get_in(), orig->get_out()); delete copyProd; } else { cut = prod->cut(orig->get_in(), orig->get_out()); } Clip(*cut).addEffects(*orig); bool ok = m_playlist.insert_at(frame(t), cut, 1) >= 0; delete cut; m_playlist.unlock(); + emit invalidatePreview(frame(t), orig->get_playtime()); return ok; } void Track::updateEffects(const QString &id, Mlt::Producer *original) { QString idForAudioTrack; QString idForVideoTrack; QString service = original->parent().get("mlt_service"); QString idForTrack = original->parent().get("id"); - + QList updateList; if (needsDuplicate(service)) { // We have to use the track clip duplication functions, because of audio glitches in MLT's multitrack idForAudioTrack = idForTrack + QLatin1Char('_') + m_playlist.get("id") + "_audio"; idForVideoTrack = idForTrack + "_video"; idForTrack.append(QLatin1Char('_') + m_playlist.get("id")); } for (int i = 0; i < m_playlist.count(); i++) { if (m_playlist.is_blank(i)) continue; QScopedPointer p(m_playlist.get_clip(i)); Mlt::Producer origin = p->parent(); QString current = origin.get("id"); if (current.startsWith(QLatin1String("slowmotion:"))) { if (current.section(QStringLiteral(":"), 1, 1) == id) { Clip(origin).replaceEffects(*original); + updateList << QPoint(m_playlist.clip_start(i), m_playlist.clip_length(i)); } } else if (current == id) { // we are directly using original producer, no need to update effects + updateList << QPoint(m_playlist.clip_start(i), m_playlist.clip_length(i)); continue; } else if (current.section(QStringLiteral("_"), 0, 0) == id) { + updateList << QPoint(m_playlist.clip_start(i), m_playlist.clip_length(i)); Clip(origin).replaceEffects(*original); } } + foreach(const QPoint &pt, updateList) { + emit invalidatePreview(pt.x(), pt.y()); + } } /*Mlt::Producer &Track::find(const QByteArray &name, const QByteArray &value, int startindex) { for (int i = startindex; i < m_playlist.count(); i++) { if (m_playlist.is_blank(i)) continue; QScopedPointer p(m_playlist.get_clip(i)); if (value == p->parent().get(name.constData())) { return p->parent(); } } return Mlt::0; }*/ Mlt::Producer *Track::clipProducer(Mlt::Producer *parent, PlaylistState::ClipState state, bool forceCreation) { QString service = parent->parent().get("mlt_service"); QString originalId = parent->parent().get("id"); if (!needsDuplicate(service) || state == PlaylistState::VideoOnly || originalId.endsWith(QLatin1String("_video"))) { // Don't clone producer for track if it has no audio return new Mlt::Producer(*parent); } originalId = originalId.section(QStringLiteral("_"), 0, 0); QString idForTrack = originalId + QLatin1Char('_') + m_playlist.get("id"); if (state == PlaylistState::AudioOnly) { idForTrack.append("_audio"); } if (!forceCreation) { for (int i = 0; i < m_playlist.count(); i++) { if (m_playlist.is_blank(i)) continue; QScopedPointer p(m_playlist.get_clip(i)); if (QString(p->parent().get("id")) == idForTrack) { return new Mlt::Producer(p->parent()); } } } Mlt::Producer *prod = Clip(parent->parent()).clone(); prod->set("id", idForTrack.toUtf8().constData()); if (state == PlaylistState::AudioOnly) { prod->set("video_index", -1); } return prod; } bool Track::hasAudio() { for (int i = 0; i < m_playlist.count(); i++) { if (m_playlist.is_blank(i)) continue; QScopedPointer p(m_playlist.get_clip(i)); QString service = p->get("mlt_service"); if (service == QLatin1String("xml") || service == QLatin1String("consumer") || p->get_int("audio_index") > -1) { return true; } } return false; } void Track::setProperty(const QString &name, const QString &value) { m_playlist.set(name.toUtf8().constData(), value.toUtf8().constData()); } void Track::setProperty(const QString &name, int value) { m_playlist.set(name.toUtf8().constData(), value); } const QString Track::getProperty(const QString &name) { return QString(m_playlist.get(name.toUtf8().constData())); } int Track::getIntProperty(const QString &name) { return m_playlist.get_int(name.toUtf8().constData()); } TrackInfo Track::info() { TrackInfo info; info.trackName = m_playlist.get("kdenlive:track_name"); info.isLocked= m_playlist.get_int("kdenlive:locked_track"); int currentState = m_playlist.parent().get_int("hide"); info.isMute = currentState & 2; info.isBlind = currentState & 1; info.type = type; info.effectsList = effectsList; info.composite = m_playlist.get_int("kdenlive:composite"); return info; } void Track::setInfo(TrackInfo info) { if (!trackHeader) return; m_playlist.set("kdenlive:track_name", info.trackName.toUtf8().constData()); m_playlist.set("kdenlive:locked_track", info.isLocked ? 1 : 0); m_playlist.set("kdenlive:composite", info.composite ? 1 : 0); int state = 0; if (info.isMute) { if (info.isBlind) state = 3; else state = 2; } else if (info.isBlind) state = 1; m_playlist.parent().set("hide", state); type = info.type; trackHeader->updateStatus(info); } int Track::state() { return m_playlist.parent().get_int("hide"); } void Track::setState(int state) { m_playlist.parent().set("hide", state); } int Track::getBlankLength(int pos, bool fromBlankStart) { int clipIndex = m_playlist.get_clip_index_at(pos); if (clipIndex == m_playlist.count()) { // We are after the end of the playlist return -1; } if (!m_playlist.is_blank(clipIndex)) return 0; if (fromBlankStart) return m_playlist.clip_length(clipIndex); return m_playlist.clip_length(clipIndex) + m_playlist.clip_start(clipIndex) - pos; } void Track::updateClipProperties(const QString &id, QMap properties) { QString idForTrack = id + QLatin1Char('_') + m_playlist.get("id"); QString idForVideoTrack = id + "_video"; QString idForAudioTrack = idForTrack + "_audio"; // slowmotion producers are updated in renderer for (int i = 0; i < m_playlist.count(); i++) { if (m_playlist.is_blank(i)) continue; QScopedPointer p(m_playlist.get_clip(i)); QString current = p->parent().get("id"); QStringList processed; if (!processed.contains(current) && (current == idForTrack || current == idForAudioTrack || current == idForVideoTrack)) { QMapIterator i(properties); while (i.hasNext()) { i.next(); p->parent().set(i.key().toUtf8().constData(), i.value().toUtf8().constData()); } processed << current; } } } Mlt::Producer *Track::buildSlowMoProducer(Mlt::Properties passProps, const QString &url, const QString &id, Track::SlowmoInfo info) { QLocale locale; Mlt::Producer *prod = new Mlt::Producer(*m_playlist.profile(), 0, ("timewarp:" + url).toUtf8().constData()); if (!prod->is_valid()) { qDebug()<<"++++ FAILED TO CREATE SLOWMO PROD"; return NULL; } QString producerid = "slowmotion:" + id + ':' + info.toString(locale); prod->set("id", producerid.toUtf8().constData()); // copy producer props for (int i = 0; i < passProps.count(); i++) { prod->set(passProps.get_name(i), passProps.get(i)); } // set clip state switch ((int) info.state) { case PlaylistState::VideoOnly: prod->set("audio_index", -1); break; case PlaylistState::AudioOnly: prod->set("video_index", -1); break; default: break; } QString slowmoId = info.toString(locale); slowmoId.append(prod->get("warp_resource")); emit storeSlowMotion(slowmoId, prod); return prod; } int Track::changeClipSpeed(ItemInfo info, ItemInfo speedIndependantInfo, PlaylistState::ClipState state, double speed, int strobe, Mlt::Producer *prod, const QString &id, Mlt::Properties passProps, bool removeEffect) { + //TODO: invalidate preview rendering int newLength = 0; int startPos = info.startPos.frames(fps()); int clipIndex = m_playlist.get_clip_index_at(startPos); int clipLength = m_playlist.clip_length(clipIndex); m_playlist.lock(); QScopedPointer original(m_playlist.get_clip(clipIndex)); if (original == NULL) { qDebug()<<"// No clip to apply effect"; m_playlist.unlock(); return -1; } if (!original->is_valid() || original->is_blank()) { // invalid clip qDebug()<<"// Invalid clip to apply effect"; m_playlist.unlock(); return -1; } Mlt::Producer clipparent = original->parent(); if (!clipparent.is_valid() || clipparent.is_blank()) { // invalid clip qDebug()<<"// Invalid parent to apply effect"; m_playlist.unlock(); return -1; } QLocale locale; if (speed <= 0 && speed > -1) speed = 1.0; QString serv = clipparent.get("mlt_service"); QString url; if (serv == QLatin1String("timewarp")) { url = QString::fromUtf8(clipparent.get("warp_resource")); } else { url = QString::fromUtf8(clipparent.get("resource")); } url.prepend(locale.toString(speed) + ':'); Track::SlowmoInfo slowInfo; slowInfo.speed = speed; slowInfo.strobe = strobe; slowInfo.state = state; if (serv.contains(QStringLiteral("avformat"))) { if (speed != 1.0 || strobe > 1) { if (!prod || !prod->is_valid()) { prod = buildSlowMoProducer(passProps, url, id, slowInfo); if (prod == NULL) { // error, abort qDebug()<<"++++ FAILED TO CREATE SLOWMO PROD"; m_playlist.unlock(); return -1; } } QScopedPointer clip(m_playlist.replace_with_blank(clipIndex)); m_playlist.consolidate_blanks(0); // Check that the blank space is long enough for our new duration clipIndex = m_playlist.get_clip_index_at(startPos); int blankEnd = m_playlist.clip_start(clipIndex) + m_playlist.clip_length(clipIndex); Mlt::Producer *cut; if (clipIndex + 1 < m_playlist.count() && (startPos + clipLength / speed > blankEnd)) { GenTime maxLength = GenTime(blankEnd, fps()) - info.startPos; cut = prod->cut((int)(info.cropStart.frames(fps()) / speed), (int)(info.cropStart.frames(fps()) / speed + maxLength.frames(fps()) - 1)); } else cut = prod->cut((int)(info.cropStart.frames(fps()) / speed), (int)((info.cropStart.frames(fps()) + clipLength) / speed - 1)); // move all effects to the correct producer Clip(*cut).addEffects(*clip); m_playlist.insert_at(startPos, cut, 1); delete cut; clipIndex = m_playlist.get_clip_index_at(startPos); newLength = m_playlist.clip_length(clipIndex); } else if (speed == 1.0 && strobe < 2) { QScopedPointer clip(m_playlist.replace_with_blank(clipIndex)); m_playlist.consolidate_blanks(0); // Check that the blank space is long enough for our new duration clipIndex = m_playlist.get_clip_index_at(startPos); int blankEnd = m_playlist.clip_start(clipIndex) + m_playlist.clip_length(clipIndex); Mlt::Producer *cut; if (!prod || !prod->is_valid()) { prod = buildSlowMoProducer(passProps, url, id, slowInfo); if (prod == NULL) { // error, abort qDebug()<<"++++ FAILED TO CREATE SLOWMO PROD"; m_playlist.unlock(); return -1; } } int originalStart = (int)(speedIndependantInfo.cropStart.frames(fps())); if (clipIndex + 1 < m_playlist.count() && (info.startPos + speedIndependantInfo.cropDuration).frames(fps()) > blankEnd) { GenTime maxLength = GenTime(blankEnd, fps()) - info.startPos; cut = prod->cut(originalStart, (int)(originalStart + maxLength.frames(fps()) - 1)); } else { cut = prod->cut(originalStart, (int)(originalStart + speedIndependantInfo.cropDuration.frames(fps())) - 1); } // move all effects to the correct producer Clip(*cut).addEffects(*clip); m_playlist.insert_at(startPos, cut, 1); delete cut; clipIndex = m_playlist.get_clip_index_at(startPos); newLength = m_playlist.clip_length(clipIndex); } } else if (serv == QLatin1String("timewarp")) { if (!prod || !prod->is_valid()) { prod = buildSlowMoProducer(passProps, url, id, slowInfo); if (prod == NULL) { // error, abort qDebug()<<"++++ FAILED TO CREATE SLOWMO PROD"; m_playlist.unlock(); return -1; } } if (removeEffect) { prod = clipProducer(prod, state); } QScopedPointer clip(m_playlist.replace_with_blank(clipIndex)); m_playlist.consolidate_blanks(0); int duration; int originalStart; if (speed == 1.0) { duration = speedIndependantInfo.cropDuration.frames(fps()); originalStart = speedIndependantInfo.cropStart.frames(fps()); } else { duration = (int) (speedIndependantInfo.cropDuration.frames(fps()) / speed + 0.5); originalStart = (int)(speedIndependantInfo.cropStart.frames(fps()) / speed + 0.5); } //qDebug()<<"/ / /UPDATE SPEED: "< blankEnd)) { GenTime maxLength = GenTime(blankEnd, fps()) - info.startPos; cut = prod->cut(originalStart, (int)(originalStart + maxLength.frames(fps()) - 1)); } else { cut = prod->cut(originalStart, originalStart + duration - 1); } // move all effects to the correct producer Clip(*cut).addEffects(*clip); m_playlist.insert_at(startPos, cut, 1); delete cut; clipIndex = m_playlist.get_clip_index_at(startPos); newLength = m_playlist.clip_length(clipIndex); if (removeEffect) delete prod; } //Do not delete prod, it is now stored in the slowmotion producers list m_playlist.unlock(); if (clipIndex + 1 == m_playlist.count()) { // We changed the speed of last clip in playlist, check track length emit newTrackDuration(m_playlist.get_playtime()); } return newLength; } int Track::index() const { return m_index; } int Track::spaceLength(int pos, bool fromBlankStart) { int clipIndex = m_playlist.get_clip_index_at(pos); if (clipIndex == m_playlist.count()) { // We are after the end of the playlist return -1; } if (!m_playlist.is_blank(clipIndex)) return 0; if (fromBlankStart) return m_playlist.clip_length(clipIndex); return m_playlist.clip_length(clipIndex) + m_playlist.clip_start(clipIndex) - pos; } void Track::disableEffects(bool disable) { for (int i = 0; i < m_playlist.count(); i++) { QScopedPointer original(m_playlist.get_clip(i)); if (original == NULL || !original->is_valid() || original->is_blank()) { // invalid clip continue; } Clip(*original).disableEffects(disable); } } diff --git a/src/timeline/track.h b/src/timeline/track.h index 8914876c0..aec88d483 100644 --- a/src/timeline/track.h +++ b/src/timeline/track.h @@ -1,214 +1,215 @@ /* * Kdenlive timeline track handling MLT playlist * Copyright 2015 Kdenlive team * Author: Vincent Pinon * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifndef TRACK_H #define TRACK_H #include "definitions.h" #include #include #include class HeaderTrack; /** @brief Kdenlive timeline track, to access MLT playlist operations * The track as seen in the video editor is actually a playlist * in the MLT framework behind the scene. * When one adds, deletes, moves and resizes clips on the track, * it corresponds to playlist operations, simple or elaborate. * The Track class handles the edit time to MLT frame & index * correspondance, and builds complex operations on top of basic * commands. */ class Track : public QObject { Q_OBJECT public: /** @brief Track constructor * @param playlist is the MLT object used for monitor/render * @param fps is the read speed (frames per seconds) */ explicit Track(int index, const QList &actions, Mlt::Playlist &playlist, TrackType type, QWidget *parent = 0); ~Track(); struct SlowmoInfo { double speed; int strobe; PlaylistState::ClipState state; QString toString(QLocale locale) { QStringList str; str << locale.toString(speed) << QString::number(strobe) << QString::number((int) state); return str.join(":"); } void readFromString(const QString &str, QLocale locale) { speed = locale.toDouble(str.section(":", 0, 0)); strobe = str.section(":", 1, 1).toInt(); if (str.count(QLatin1Char(':')) == 1) { state = PlaylistState::Original; } else { state = (PlaylistState::ClipState) str.section(":", 2, 2).toInt(); } } }; /// Property access function Mlt::Playlist & playlist(); qreal fps(); /** List containing track effects */ EffectsList effectsList; /** Track type (audio / video) */ TrackType type; /** @brief The track header widget */ HeaderTrack *trackHeader; /** @brief convertion utility function * @param time (in seconds) * @return frame number */ int frame(qreal t); /** @brief get the playlist duration * @return play time in seconds */ qreal length(); /** @brief Returns MLT's track index */ int index() const; /** @brief add a clip * @param t is the time position to start the cut (in seconds) * @param cut is a MLT Producer cut (resource + in/out timecodes) * @param duplicate when true, we will create a copy of the clip if necessary * @param mode allow insert in non-blanks by replacing (mode=1) or pushing (mode=2) content * The playlist must be locked / unlocked before and after calling doAdd * @return true if success */ bool doAdd(qreal t, Mlt::Producer *cut, TimelineMode::EditMode mode); bool add(qreal t, Mlt::Producer *parent, qreal tcut, qreal dtcut, PlaylistState::ClipState state, bool duplicate, TimelineMode::EditMode mode); /** @brief Move a clip in the track * @param start where clip is present (in seconds); * @param end wher the clip should be moved * @param mode allow insert in non-blanks by replacing (mode=1) or pushing (mode=2) content * @return true if success */ bool move(qreal start, qreal end, TimelineMode::EditMode mode = TimelineMode::NormalEdit); /** @brief delete a clip * @param time where clip is present (in seconds); * @return true if success */ bool del(qreal t); /** delete a region * @param t is the start, * @param dt is the duration (in seconds) * @return true if success */ bool del(qreal t, qreal dt); /** @brief change the clip length from start or end * @param told is the current edge position, * @param tnew is the target edge position (in seconds) * @param end precises if we move the end of the left clip (\em true) * or the start of the right clip (\em false) * @return true if success */ bool resize(qreal told, qreal tnew, bool end); /** @brief split the clip at given position * @param t is the cut time in playlist * @return true if success */ bool cut(qreal t); /** @brief prepends a dash to the clip's id to prepare for replacement */ void replaceId(const QString &id); /** @brief replace all occurences of a clip in the track with another resource * @param id is the clip id * @param original is the original replacement clip * @param videoOnlyProducer is the video only (without sound) replacement clip * @param newSlowMos the slowmotion producers required for replacement * @return true if success */ bool replaceAll(const QString &id, Mlt::Producer *original, Mlt::Producer *videoOnlyProducer, QMap newSlowMos); void updateEffects(const QString &id, Mlt::Producer *original); /** @brief replace an instance of a clip with another resource * @param t is the clip time in playlist * @param prod is the replacement clip * @return true if success */ bool replace(qreal t, Mlt::Producer *prod, PlaylistState::ClipState state = PlaylistState::Original); /** @brief look for a clip having a given property value * @param name is the property name * @param value is the searched value * @param startindex is a playlist index to start the search from * @return pointer to the first matching producer */ //Mlt::Producer &find(const QByteArray &name, const QByteArray &value, int startindex = 0); /** @brief get a producer clone for the track and pick an extract * MLT (libav*) can't mix audio of a clip with itself, so we duplicate the producer for each track * @param parent is the source media * @param state is for Normal, Audio only or Video only * @param forceCreation if true, we do not attempt to re-use existing track producer but recreate it * @return producer cut for this track */ Mlt::Producer *clipProducer(Mlt::Producer *parent, PlaylistState::ClipState state, bool forceCreation = false); /** @brief Changes the speed of a clip in MLT's playlist. * * It creates a new "framebuffer" producer, which must have its "resource" * property set to "video.mpg?0.6", where "video.mpg" is the path to the * clip and "0.6" is the speed in percentage. The newly created producer * will have its "id" property set to "slowmotion:parentid:speed", where * "parentid" is the id of the original clip in the ClipManager list and * "speed" is the current speed. * If removeEffect is true, we revert to original avformat producer */ int changeClipSpeed(ItemInfo info, ItemInfo speedIndependantInfo, PlaylistState::ClipState state, double speed, int strobe, Mlt::Producer *prod, const QString &id, Mlt::Properties passProps, bool removeEffect = false); Mlt::Producer *buildSlowMoProducer(Mlt::Properties passProps, const QString &url, const QString &id, Track::SlowmoInfo info); /** @brief Returns true if there is a clip with audio on this track */ bool hasAudio(); void setProperty(const QString &name, const QString &value); void setProperty(const QString &name, int value); const QString getProperty(const QString &name); int getIntProperty(const QString &name); TrackInfo info(); void setInfo(TrackInfo info); void lockTrack(bool locked); int state(); void setState(int state); /** @brief Check if we have a blank space at pos and its length. * Returns -1 if track is shorter, 0 if not blank and > 0 for blank length */ int getBlankLength(int pos, bool fromBlankStart); /** @brief Update producer properties on all instances of this clip. */ void updateClipProperties(const QString &id, QMap properties); /** @brief Returns a list of speed info for all slowmotion producer used on this track for an id. */ QList getSlowmotionInfos(const QString &id); /** @brief Returns the length of blank space from a position pos. */ int spaceLength(int pos, bool fromBlankStart); /** @brief Dis/enable all effects on this track. */ void disableEffects(bool disable); /** @brief Returns true if position is on last clip or beyond track length. */ bool isLastClip(qreal t); signals: /** @brief notify track length change to update background * @param duration is the new length */ void newTrackDuration(int duration); void storeSlowMotion(const QString &url, Mlt::Producer *prod); + void invalidatePreview(int position, int length); private: /** Position in MLT's tractor */ int m_index; /** MLT playlist behind the scene */ Mlt::Playlist m_playlist; /** @brief Returns true is this MLT service needs duplication to work on multiple tracks */ bool needsDuplicate(const QString &service) const; }; #endif // TRACK_H