Files
kdenlive/tests/filetest.cpp
2025-05-16 12:49:01 +02:00

476 lines
22 KiB
C++

/*
SPDX-FileCopyrightText: 2018-2022 Jean-Baptiste Mardelle <jb@kdenlive.org>
SPDX-FileCopyrightText: 2022 Eric Jiang
SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/
#include "test_utils.hpp"
// test specific headers
#include "bin/binplaylist.hpp"
#include "doc/kdenlivedoc.h"
#include "timeline2/model/builders/meltBuilder.hpp"
#include "xml/xml.hpp"
#include <QTemporaryFile>
#include <QUndoGroup>
using namespace fakeit;
TEST_CASE("Save File", "[SF]")
{
auto binModel = pCore->projectItemModel();
binModel->clean();
std::shared_ptr<DocUndoStack> undoStack = std::make_shared<DocUndoStack>(nullptr);
SECTION("Simple insert and save")
{
// Create document
pCore->setCurrentProfile(QStringLiteral("dv_pal"));
KdenliveDoc document(undoStack);
pCore->projectManager()->testSetDocument(&document);
QDateTime documentDate = QDateTime::currentDateTime();
KdenliveTests::updateTimeline(false, QString(), QString(), documentDate, 0);
auto timeline = document.getTimeline(document.uuid());
pCore->projectManager()->testSetActiveTimeline(timeline);
KdenliveTests::resetNextId();
QDir dir = QDir::temp();
QString binId = KdenliveTests::createProducerWithSound(pCore->getProjectProfile(), binModel);
QString binId2 = KdenliveTests::createProducer(pCore->getProjectProfile(), "red", binModel, 20, false);
int tid1 = timeline->getTrackIndexFromPosition(2);
// Setup timeline audio drop info
QMap<int, QString> audioInfo;
audioInfo.insert(1, QStringLiteral("stream1"));
KdenliveTests::setAudioTargets(timeline, audioInfo);
KdenliveTests::setVideoTargets(timeline, tid1);
// Insert 2 clips (length=20, pos = 80 / 100)
int cid1 = -1;
REQUIRE(timeline->requestClipInsertion(binId2, tid1, 80, cid1, true, true, false));
int l = timeline->getClipPlaytime(cid1);
int cid2 = -1;
REQUIRE(timeline->requestClipInsertion(binId2, tid1, 80 + l, cid2, true, true, false));
// Resize first clip (length=100)
REQUIRE(timeline->requestItemResize(cid1, 100, false) == 100);
auto state = [&]() {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipTrackId(cid2) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == 0);
REQUIRE(timeline->getClipPosition(cid2) == 100);
REQUIRE(timeline->getClipPlaytime(cid1) == 100);
REQUIRE(timeline->getClipPlaytime(cid2) == 20);
};
state();
REQUIRE(timeline->checkConsistency());
pCore->projectManager()->testSaveFileAs(dir.absoluteFilePath(QStringLiteral("test.kdenlive")));
// Undo resize
undoStack->undo();
// Undo first insert
undoStack->undo();
// Undo second insert
undoStack->undo();
pCore->projectManager()->closeCurrentDocument(false, false);
}
SECTION("Reopen and check in/out points")
{
// Create new document
// We mock the project class so that the undoStack function returns our undoStack, and our mocked document
KdenliveTests::resetNextId();
QString saveFile = QDir::temp().absoluteFilePath(QStringLiteral("test.kdenlive"));
QUrl openURL = QUrl::fromLocalFile(saveFile);
QUndoGroup *undoGroup = new QUndoGroup();
undoGroup->addStack(undoStack.get());
DocOpenResult openResults = KdenliveDoc::Open(openURL, QDir::temp().path(), undoGroup, false, nullptr);
REQUIRE(openResults.isSuccessful() == true);
std::unique_ptr<KdenliveDoc> openedDoc = openResults.getDocument();
pCore->projectManager()->testSetDocument(openedDoc.get());
const QUuid uuid = openedDoc->uuid();
QDateTime documentDate = QFileInfo(openURL.toLocalFile()).lastModified();
KdenliveTests::updateTimeline(false, QString(), QString(), documentDate, 0);
pCore->projectManager()->testSetActiveTimeline();
auto timeline = openedDoc->getTimeline(uuid);
const QString hash = openedDoc->getSequenceProperty(uuid, QStringLiteral("timelineHash"));
REQUIRE(timeline->getTracksCount() == 4);
REQUIRE(timeline->checkConsistency());
int tid1 = timeline->getTrackIndexFromPosition(2);
int cid1 = timeline->getClipByStartPosition(tid1, 0);
int cid2 = timeline->getClipByStartPosition(tid1, 100);
REQUIRE(cid1 > -1);
REQUIRE(cid2 > -1);
auto state = [&]() {
REQUIRE(timeline->checkConsistency());
REQUIRE(timeline->getTrackClipsCount(tid1) == 2);
REQUIRE(timeline->getClipTrackId(cid1) == tid1);
REQUIRE(timeline->getClipTrackId(cid2) == tid1);
REQUIRE(timeline->getClipPosition(cid1) == 0);
REQUIRE(timeline->getClipPosition(cid2) == 100);
REQUIRE(timeline->getClipPlaytime(cid1) == 100);
REQUIRE(timeline->getClipPlaytime(cid2) == 20);
};
state();
QByteArray updatedHex = timeline->timelineHash().toHex();
REQUIRE(updatedHex == hash);
pCore->projectManager()->closeCurrentDocument(false, false);
QDir dir = QDir::temp();
QFile::remove(dir.absoluteFilePath(QStringLiteral("test.kdenlive")));
}
SECTION("Open a file with AV clips")
{
// Create new document
QString path = sourcesPath + "/dataset/av.kdenlive";
QUrl openURL = QUrl::fromLocalFile(path);
QUndoGroup *undoGroup = new QUndoGroup();
undoGroup->addStack(undoStack.get());
DocOpenResult openResults = KdenliveDoc::Open(openURL, QDir::temp().path(), undoGroup, false, nullptr);
REQUIRE(openResults.isSuccessful() == true);
std::unique_ptr<KdenliveDoc> openedDoc = openResults.getDocument();
pCore->projectManager()->testSetDocument(openedDoc.get());
const QUuid uuid = openedDoc->uuid();
QDateTime documentDate = QFileInfo(openURL.toLocalFile()).lastModified();
KdenliveTests::updateTimeline(false, QString(), QString(), documentDate, 0);
auto timeline = openedDoc->getTimeline(uuid);
pCore->projectManager()->testSetActiveTimeline(timeline);
REQUIRE(timeline->checkConsistency());
int tid1 = timeline->getTrackIndexFromPosition(0);
int tid2 = timeline->getTrackIndexFromPosition(1);
int tid3 = timeline->getTrackIndexFromPosition(2);
int tid4 = timeline->getTrackIndexFromPosition(3);
// Check we have audio and video tracks
REQUIRE(timeline->isAudioTrack(tid1));
REQUIRE(timeline->isAudioTrack(tid2));
REQUIRE(timeline->isAudioTrack(tid3) == false);
REQUIRE(timeline->isAudioTrack(tid4) == false);
int cid1 = timeline->getClipByStartPosition(tid1, 0);
int cid2 = timeline->getClipByStartPosition(tid2, 0);
int cid3 = timeline->getClipByStartPosition(tid3, 0);
int cid4 = timeline->getClipByStartPosition(tid4, 0);
// Check we have our clips
REQUIRE(cid1 == -1);
REQUIRE(cid2 > -1);
REQUIRE(cid3 > -1);
REQUIRE(cid4 == -1);
REQUIRE(timeline->getClipPlaytime(cid2) == 500);
REQUIRE(timeline->getClipPlaytime(cid3) == 500);
pCore->projectManager()->closeCurrentDocument(false, false);
}
}
TEST_CASE("Check File Corruption", "[CFC]")
{
auto binModel = pCore->projectItemModel();
binModel->clean();
std::shared_ptr<DocUndoStack> undoStack = std::make_shared<DocUndoStack>(nullptr);
SECTION("Open a file and check id consistency")
{
// open
QString path = sourcesPath + "/dataset/clip-ids.kdenlive";
QUrl openURL = QUrl::fromLocalFile(path);
QUndoGroup *undoGroup = new QUndoGroup();
undoGroup->addStack(undoStack.get());
DocOpenResult openResults = KdenliveDoc::Open(openURL, QDir::temp().path(), undoGroup, false, nullptr);
REQUIRE(openResults.isSuccessful() == true);
std::unique_ptr<KdenliveDoc> openedDoc = openResults.getDocument();
// Mock<KdenliveDoc> docMock(*openedDoc.get());
// When(Method(docMock, getCacheDir)).AlwaysReturn(QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)));
// KdenliveDoc &mockedDoc = docMock.get();
pCore->projectManager()->testSetDocument(openedDoc.get());
const QUuid uuid = openedDoc->uuid();
QDateTime documentDate = QFileInfo(openURL.toLocalFile()).lastModified();
KdenliveTests::updateTimeline(false, QString(), QString(), documentDate, 0);
QMap<QUuid, QString> allSequences = binModel->getAllSequenceClips();
const QString firstSeqId = allSequences.value(uuid);
pCore->projectManager()->openTimeline(firstSeqId, -1, uuid);
std::shared_ptr<TimelineItemModel> timeline = openedDoc->getTimeline(uuid);
pCore->projectManager()->testSetActiveTimeline(timeline);
// Now reopen all timeline sequences
QList<QUuid> allUuids = allSequences.keys();
allUuids.removeAll(uuid);
for (auto &u : allUuids) {
const QString id = allSequences.value(u);
pCore->projectManager()->openTimeline(id, -1, u);
}
// std::shared_ptr<TimelineItemModel> timeline = openedDoc->getTimeline(uuid);
// pCore->projectManager()->testSetActiveDocument(&mockedDoc);
// auto timeline = mockedDoc.getTimeline(uuid);
REQUIRE(timeline->checkConsistency());
int tid1 = timeline->getTrackIndexFromPosition(0);
int tid2 = timeline->getTrackIndexFromPosition(1);
int tid3 = timeline->getTrackIndexFromPosition(2);
int tid4 = timeline->getTrackIndexFromPosition(3);
// Check we have audio and video tracks
REQUIRE(timeline->isAudioTrack(tid1));
REQUIRE(timeline->isAudioTrack(tid2));
REQUIRE(timeline->isAudioTrack(tid3) == false);
REQUIRE(timeline->isAudioTrack(tid4) == false);
int cid1 = timeline->getClipByStartPosition(tid4, 16); // Blue
int cid2 = timeline->getClipByStartPosition(tid4, 21); // Green
int cid3 = timeline->getClipByStartPosition(tid4, 26); // Red
int cid4 = timeline->getClipByStartPosition(tid4, 31); // Yellow
int cid1b = timeline->getClipByStartPosition(tid3, 11); // Blue
int cid2b = timeline->getClipByStartPosition(tid3, 16); // Green
int cid3b = timeline->getClipByStartPosition(tid3, 21); // Red
int cid4b = timeline->getClipByStartPosition(tid3, 26); // Yellow
// Check we have our clips
REQUIRE(cid1 > -1);
REQUIRE(cid2 > -1);
REQUIRE(cid3 > -1);
REQUIRE(cid4 > -1);
REQUIRE(cid1b > -1);
REQUIRE(cid2b > -1);
REQUIRE(cid3b > -1);
REQUIRE(cid4b > -1);
REQUIRE(timeline->getClipName(cid1) == QLatin1String("blue.mpg"));
REQUIRE(timeline->getClipName(cid1b) == QLatin1String("blue.mpg"));
REQUIRE(timeline->getClipName(cid2) == QLatin1String("green.mpg"));
REQUIRE(timeline->getClipName(cid2b) == QLatin1String("green.mpg"));
REQUIRE(timeline->getClipName(cid3) == QLatin1String("red.mpg"));
REQUIRE(timeline->getClipName(cid3b) == QLatin1String("red.mpg"));
REQUIRE(timeline->getClipName(cid4) == QLatin1String("yellow.mpg"));
REQUIRE(timeline->getClipName(cid4b) == QLatin1String("yellow.mpg"));
pCore->projectManager()->closeCurrentDocument(false, false);
}
}
TEST_CASE("Non-BMP Unicode", "[NONBMP]")
{
auto binModel = pCore->projectItemModel();
binModel->clean();
// A Kdenlive bug (bugzilla bug 435768) caused characters outside the Basic
// Multilingual Plane to be lost when the file is loaded. This string
// contains the onigiri emoji which should survive a save+open round trip.
// If Kdenlive drops the emoji, then the result is just "testtest" which can
// be checked for.
const QString emojiTestString = QString::fromUtf8("test\xF0\x9F\x8D\x99test");
std::shared_ptr<DocUndoStack> undoStack = std::make_shared<DocUndoStack>(nullptr);
QTemporaryFile saveFile(QDir::temp().filePath("kdenlive_test_XXXXXX.kdenlive"));
qDebug() << "Choosing temp file with template" << (QDir::temp().filePath("kdenlive_test_XXXXXX.kdenlive"));
REQUIRE(saveFile.open());
saveFile.close();
qDebug() << "New temporary file:" << saveFile.fileName();
SECTION("Save title with special chars")
{
// Create document
KdenliveDoc document(undoStack);
pCore->projectManager()->testSetDocument(&document);
QDateTime documentDate = QDateTime::currentDateTime();
KdenliveTests::updateTimeline(false, QString(), QString(), documentDate, 0);
auto timeline = document.getTimeline(document.uuid());
pCore->projectManager()->testSetActiveTimeline(timeline);
QDir dir = QDir::temp();
// create a simple title with the non-BMP test string
const QString titleXml =
QStringLiteral("<kdenlivetitle duration=\"150\" LC_NUMERIC=\"C\" width=\"1920\" height=\"1080\" out=\"149\">\n <item type=\"QGraphicsTextItem\" "
"z-index=\"0\">\n <position x=\"777\" y=\"482\">\n <transform>1,0,0,0,1,0,0,0,1</transform>\n </position>\n <content "
"shadow=\"0;#64000000;3;3;3\" font-underline=\"0\" box-height=\"138\" font-outline-color=\"0,0,0,255\" font=\"DejaVu Sans\" "
"letter-spacing=\"0\" font-pixel-size=\"120\" font-italic=\"0\" typewriter=\"0;2;1;0;0\" alignment=\"0\" font-weight=\"63\" "
"font-outline=\"3\" box-width=\"573.25\" font-color=\"252,233,79,255\">") +
emojiTestString +
QStringLiteral("</content>\n </item>\n <startviewport rect=\"0,0,1920,1080\"/>\n <endviewport rect=\"0,0,1920,1080\"/>\n <background "
"color=\"0,0,0,0\"/>\n</kdenlivetitle>\n");
QString binId2 = KdenliveTests::createTextProducer(pCore->getProjectProfile(), binModel, titleXml, emojiTestString, 150);
TrackModel::construct(timeline, -1, -1, QString(), true);
TrackModel::construct(timeline, -1, -1, QString(), true);
int tid1 = timeline->getTrackIndexFromPosition(2);
// Setup timeline audio drop info
QMap<int, QString> audioInfo;
audioInfo.insert(1, QStringLiteral("stream1"));
KdenliveTests::setAudioTargets(timeline, audioInfo);
KdenliveTests::setVideoTargets(timeline, tid1);
pCore->projectManager()->testSaveFileAs(saveFile.fileName());
// open the file and check that it contains emojiTestString
QFile file(saveFile.fileName());
REQUIRE(file.open(QIODevice::ReadOnly));
QByteArray contents = file.readAll();
if (contents.contains(emojiTestString.toUtf8())) {
qDebug() << "File contains test string";
} else {
qDebug() << "File does not contain test string:" << contents;
}
REQUIRE(contents.contains(emojiTestString.toUtf8()));
// try opening the file as a Kdenlivedoc and check that the title hasn't
// lost the emoji
QUrl openURL = QUrl::fromLocalFile(saveFile.fileName());
QUndoGroup *undoGroup = new QUndoGroup();
undoGroup->addStack(undoStack.get());
DocOpenResult openResults = KdenliveDoc::Open(openURL, QDir::temp().path(),
undoGroup, false, nullptr);
REQUIRE(openResults.isSuccessful() == true);
std::unique_ptr<KdenliveDoc> openedDoc = openResults.getDocument();
QDomDocument newDoc = KdenliveTests::getDocument(openedDoc.get());
auto producers = newDoc.elementsByTagName(QStringLiteral("producer"));
QDomElement textTitle;
for (int i = 0; i < producers.size(); i++) {
auto kdenliveid = KdenliveTests::getProperty(producers.at(i).toElement(), QStringLiteral("kdenlive:id"));
if (kdenliveid != nullptr && kdenliveid->text() == binId2) {
textTitle = producers.at(i).toElement();
break;
}
}
auto clipname = KdenliveTests::getProperty(textTitle, QStringLiteral("kdenlive:clipname"));
REQUIRE(clipname != nullptr);
CHECK(clipname->text() == emojiTestString);
auto xmldata = KdenliveTests::getProperty(textTitle, QStringLiteral("xmldata"));
REQUIRE(xmldata != nullptr);
CHECK(clipname->text().contains(emojiTestString));
pCore->projectManager()->closeCurrentDocument(false, false);
}
SECTION("Save project and check profile")
{
// Create document
pCore->setCurrentProfile(QStringLiteral("atsc_1080p_25"));
KdenliveDoc document(undoStack);
// We mock the project class so that the undoStack function returns our undoStack, and our mocked document
pCore->projectManager()->testSetDocument(&document);
QDateTime documentDate = QDateTime::currentDateTime();
KdenliveTests::updateTimeline(false, QString(), QString(), documentDate, 0);
auto timeline = document.getTimeline(document.uuid());
pCore->projectManager()->testSetActiveTimeline(timeline);
QDir dir = QDir::temp();
int tid1 = timeline->getTrackIndexFromPosition(2);
// Setup timeline audio drop info
QMap<int, QString> audioInfo;
audioInfo.insert(1, QStringLiteral("stream1"));
KdenliveTests::setAudioTargets(timeline, audioInfo);
KdenliveTests::setVideoTargets(timeline, tid1);
pCore->projectManager()->testSaveFileAs(saveFile.fileName());
// open the file and check that it contains the correct profile info
QFile file(saveFile.fileName());
REQUIRE(file.open(QIODevice::ReadOnly));
QByteArray contents = file.readAll();
QString contentCheck("<property name=\"kdenlive:docproperties.profile\">atsc_1080p_25</property>");
if (contents.contains(contentCheck.toUtf8())) {
qDebug() << "File contains test string";
} else {
qDebug() << "File does not contain test string:" << contents;
}
REQUIRE(contents.contains(contentCheck.toUtf8()));
pCore->projectManager()->closeCurrentDocument(false, false);
}
}
TEST_CASE("Opening Mix", "[OPENMIX]")
{
auto binModel = pCore->projectItemModel();
binModel->clean();
// Check that mixes (and reverse mixes) load correctly
const QString emojiTestString = QString::fromUtf8("test\xF0\x9F\x8D\x99test");
std::shared_ptr<DocUndoStack> undoStack = std::make_shared<DocUndoStack>(nullptr);
SECTION("Load file with a mix")
{
// We mock the project class so that the undoStack function returns our undoStack, and our mocked document
QUrl openURL = QUrl::fromLocalFile(sourcesPath + "/dataset/test-mix.kdenlive");
QUndoGroup *undoGroup = new QUndoGroup();
undoGroup->addStack(undoStack.get());
DocOpenResult openResults = KdenliveDoc::Open(openURL, QDir::temp().path(), undoGroup, false, nullptr);
REQUIRE(openResults.isSuccessful() == true);
std::unique_ptr<KdenliveDoc> openedDoc = openResults.getDocument();
pCore->projectManager()->testSetDocument(openedDoc.get());
const QUuid uuid = openedDoc->uuid();
QDateTime documentDate = QFileInfo(openURL.toLocalFile()).lastModified();
KdenliveTests::updateTimeline(false, QString(), QString(), documentDate, 0);
auto timeline = openedDoc->getTimeline(uuid);
pCore->projectManager()->testSetActiveTimeline(timeline);
REQUIRE(timeline->getTracksCount() == 4);
int mixtrackId = timeline->getTrackIndexFromPosition(2);
REQUIRE(KdenliveTests::getTrackById_const(timeline, mixtrackId)->mixCount() == 2);
int mixtrackId2 = timeline->getTrackIndexFromPosition(3);
REQUIRE(KdenliveTests::getTrackById_const(timeline, mixtrackId2)->mixCount() == 1);
QDomDocument newDoc = KdenliveTests::getDocument(openedDoc.get());
auto producers = newDoc.elementsByTagName(QStringLiteral("producer"));
pCore->projectManager()->closeCurrentDocument(false, false);
}
}
TEST_CASE("Opening File With Keyframes", "[OPENKFRS]")
{
auto binModel = pCore->projectItemModel();
binModel->clean();
std::shared_ptr<DocUndoStack> undoStack = std::make_shared<DocUndoStack>(nullptr);
SECTION("Open a file with Keyframes")
{
// Create new document
QString path = sourcesPath + "/dataset/test-keyframes.kdenlive";
QUrl openURL = QUrl::fromLocalFile(path);
QUndoGroup *undoGroup = new QUndoGroup();
undoGroup->addStack(undoStack.get());
DocOpenResult openResults = KdenliveDoc::Open(openURL, QDir::temp().path(), undoGroup, false, nullptr);
REQUIRE(openResults.isSuccessful() == true);
std::unique_ptr<KdenliveDoc> openedDoc = openResults.getDocument();
pCore->projectManager()->testSetDocument(openedDoc.get());
const QUuid uuid = openedDoc->uuid();
QDateTime documentDate = QFileInfo(openURL.toLocalFile()).lastModified();
KdenliveTests::updateTimeline(false, QString(), QString(), documentDate, 0);
pCore->projectManager()->testSetActiveTimeline();
auto timeline = openedDoc->getTimeline(uuid);
REQUIRE(timeline->checkConsistency());
int tid1 = timeline->getTrackIndexFromPosition(0);
int tid2 = timeline->getTrackIndexFromPosition(1);
int tid3 = timeline->getTrackIndexFromPosition(2);
int tid4 = timeline->getTrackIndexFromPosition(3);
// Check we have audio and video tracks
REQUIRE(timeline->isAudioTrack(tid1));
REQUIRE(timeline->isAudioTrack(tid2));
REQUIRE(timeline->isAudioTrack(tid3) == false);
REQUIRE(timeline->isAudioTrack(tid4) == false);
int cid1 = timeline->getClipByStartPosition(tid1, 0);
int cid2 = timeline->getClipByStartPosition(tid2, 0);
int cid3 = timeline->getClipByStartPosition(tid3, 0);
int cid4 = timeline->getClipByStartPosition(tid4, 0);
// Check we have our clips
REQUIRE(cid1 == -1);
REQUIRE(cid2 > -1);
REQUIRE(cid3 > -1);
REQUIRE(cid4 == -1);
pCore->projectManager()->closeCurrentDocument(false, false);
}
}