File indexing completed on 2024-04-14 04:47:53

0001 /*
0002     SPDX-FileCopyrightText: 2022 Jean-Baptiste Mardelle <jb@kdenlive.org>
0003     SPDX-FileCopyrightText: 2022 Eric Jiang
0004     SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 #include "catch.hpp"
0007 #include "test_utils.hpp"
0008 // test specific headers
0009 #include "core.h"
0010 #include "definitions.h"
0011 #include "doc/docundostack.hpp"
0012 #include "doc/kdenlivedoc.h"
0013 
0014 using namespace fakeit;
0015 
0016 TEST_CASE("Read subtitle file", "[Subtitles]")
0017 {
0018     // Create timeline
0019     auto binModel = pCore->projectItemModel();
0020     binModel->clean();
0021     std::shared_ptr<DocUndoStack> undoStack = std::make_shared<DocUndoStack>(nullptr);
0022 
0023     // Here we do some trickery to enable testing.
0024     // We mock the project class so that the undoStack function returns our undoStack
0025     KdenliveDoc document(undoStack);
0026     Mock<KdenliveDoc> docMock(document);
0027     KdenliveDoc &mockedDoc = docMock.get();
0028 
0029     // We mock the project class so that the undoStack function returns our undoStack, and our mocked document
0030     Mock<ProjectManager> pmMock;
0031     When(Method(pmMock, undoStack)).AlwaysReturn(undoStack);
0032     When(Method(pmMock, cacheDir)).AlwaysReturn(QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)));
0033     When(Method(pmMock, current)).AlwaysReturn(&mockedDoc);
0034     ProjectManager &mocked = pmMock.get();
0035     pCore->m_projectManager = &mocked;
0036 
0037     mocked.m_project = &mockedDoc;
0038     QDateTime documentDate = QDateTime::currentDateTime();
0039     mocked.updateTimeline(false, QString(), QString(), documentDate, 0);
0040     auto timeline = mockedDoc.getTimeline(mockedDoc.uuid());
0041     mocked.m_activeTimelineModel = timeline;
0042     mocked.testSetActiveDocument(&mockedDoc, timeline);
0043     QString documentId = QString::number(QDateTime::currentMSecsSinceEpoch());
0044     mockedDoc.setDocumentProperty(QStringLiteral("documentid"), documentId);
0045 
0046     // Initialize subtitle model
0047     std::shared_ptr<SubtitleModel> subtitleModel = timeline->createSubtitleModel();
0048 
0049     SECTION("Load a subtitle file")
0050     {
0051         QString subtitleFile = sourcesPath + "/dataset/01.srt";
0052         bool ok;
0053         QByteArray guessedEncoding = SubtitleModel::guessFileEncoding(subtitleFile, &ok);
0054         CHECK(guessedEncoding == "UTF-8");
0055         subtitleModel->importSubtitle(subtitleFile, 0, false, 30.00, 30.00, guessedEncoding);
0056         // Ensure the 3 dialogues are loaded
0057         REQUIRE(subtitleModel->rowCount() == 3);
0058         QList<SubtitledTime> allSubs = subtitleModel->getAllSubtitles();
0059         QList<GenTime> sTime;
0060         QList<GenTime> controleTime;
0061         controleTime << GenTime(140, 25) << GenTime(265, 25) << GenTime(503, 25) << GenTime(628, 25) << GenTime(628, 25) << GenTime(875, 25);
0062         QStringList subtitlesText;
0063         QStringList control = {QStringLiteral("J'hésite à vérifier"), QStringLiteral("Ce test de sous-titres"), QStringLiteral("!! Quand même !!")};
0064         for (const auto &s : qAsConst(allSubs)) {
0065             subtitlesText << s.subtitle();
0066             sTime << s.start();
0067             sTime << s.end();
0068         }
0069         // Ensure the texts are read correctly
0070         REQUIRE(subtitlesText == control);
0071         // Ensure timeing is correct
0072         REQUIRE(sTime == controleTime);
0073         subtitleModel->removeAllSubtitles();
0074         REQUIRE(subtitleModel->rowCount() == 0);
0075     }
0076 
0077     // TODO: qt6 fix
0078 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0079     SECTION("Load a non-UTF-8 subtitle")
0080     {
0081         QString subtitleFile = sourcesPath + "/dataset/01-iso-8859-1.srt";
0082         bool ok;
0083         QByteArray guessedEncoding = SubtitleModel::guessFileEncoding(subtitleFile, &ok);
0084         qDebug() << "Guessed encoding: " << guessedEncoding;
0085         subtitleModel->importSubtitle(subtitleFile, 0, false, 30.00, 30.00, guessedEncoding);
0086         // Ensure the 3 dialogues are loaded
0087         REQUIRE(subtitleModel->rowCount() == 3);
0088         QList<SubtitledTime> allSubs = subtitleModel->getAllSubtitles();
0089         QStringList subtitlesText;
0090         QStringList control = {QStringLiteral("J'hésite à vérifier"), QStringLiteral("Ce test de sous-titres"), QStringLiteral("!! Quand même !!")};
0091         for (const auto &s : qAsConst(allSubs)) {
0092             subtitlesText << s.subtitle();
0093         }
0094         // Ensure that non-ASCII characters are read correctly
0095         CHECK(subtitlesText == control);
0096         subtitleModel->removeAllSubtitles();
0097         REQUIRE(subtitleModel->rowCount() == 0);
0098     }
0099 #endif
0100 
0101     SECTION("Load ASS file with commas")
0102     {
0103         QString subtitleFile = sourcesPath + "/dataset/subs-with-commas.ass";
0104         bool ok;
0105         QByteArray guessedEncoding = SubtitleModel::guessFileEncoding(subtitleFile, &ok);
0106         qDebug() << "Guessed encoding: " << guessedEncoding;
0107         subtitleModel->importSubtitle(subtitleFile, 0, false, 30.00, 30.00, guessedEncoding);
0108         // Ensure all 2 lines are loaded
0109         REQUIRE(subtitleModel->rowCount() == 2);
0110         QList<SubtitledTime> allSubs = subtitleModel->getAllSubtitles();
0111         QStringList subtitlesText;
0112         QStringList control = {QStringLiteral("Line with one comma, second part."), QStringLiteral("Line with two commas, second part, third part.")};
0113         for (const auto &s : qAsConst(allSubs)) {
0114             subtitlesText << s.subtitle();
0115         }
0116         // Ensure that non-ASCII characters are read correctly
0117         CHECK(subtitlesText == control);
0118         subtitleModel->removeAllSubtitles();
0119         REQUIRE(subtitleModel->rowCount() == 0);
0120     }
0121 
0122     SECTION("Load a broken subtitle file")
0123     {
0124         QString subtitleFile = sourcesPath + "/dataset/02.srt";
0125         subtitleModel->importSubtitle(subtitleFile);
0126         // Ensure the 2 dialogues are loaded
0127         REQUIRE(subtitleModel->rowCount() == 2);
0128         QList<SubtitledTime> allSubs = subtitleModel->getAllSubtitles();
0129         QList<GenTime> sTime;
0130         QList<GenTime> controleTime;
0131         controleTime << GenTime(140, 25) << GenTime(265, 25) << GenTime(628, 25) << GenTime(875, 25);
0132         QStringList subtitlesText;
0133         QStringList control = {QStringLiteral("J'hésite à vérifier"), QStringLiteral("!! Quand même !!")};
0134         for (const auto &s : allSubs) {
0135             subtitlesText << s.subtitle();
0136             sTime << s.start();
0137             sTime << s.end();
0138         }
0139         // Ensure the texts are read correctly
0140         REQUIRE(subtitlesText == control);
0141         // Ensure timeing is correct
0142         REQUIRE(sTime == controleTime);
0143         subtitleModel->removeAllSubtitles();
0144         REQUIRE(subtitleModel->rowCount() == 0);
0145     }
0146 
0147     SECTION("Preserve multiple spaces in subtitles")
0148     {
0149         QString subtitleFile = sourcesPath + "/dataset/multiple-spaces.srt";
0150         subtitleModel->importSubtitle(subtitleFile);
0151         const QList<SubtitledTime> allSubs = subtitleModel->getAllSubtitles();
0152         CHECK(allSubs.at(0).subtitle().toStdString() == "three   spaces");
0153         subtitleModel->removeAllSubtitles();
0154         REQUIRE(subtitleModel->rowCount() == 0);
0155     }
0156 
0157     SECTION("Load SBV subtitle file")
0158     {
0159         QString subtitleFile = sourcesPath + "/dataset/01.sbv";
0160         subtitleModel->importSubtitle(subtitleFile);
0161         // Ensure the 3 dialogues are loaded
0162         REQUIRE(subtitleModel->rowCount() == 3);
0163         subtitleModel->removeAllSubtitles();
0164         REQUIRE(subtitleModel->rowCount() == 0);
0165     }
0166 
0167     SECTION("Load VTT subtitle file")
0168     {
0169         QString subtitleFile = sourcesPath + "/dataset/01.vtt";
0170         subtitleModel->importSubtitle(subtitleFile);
0171         // Ensure the 2 dialogues are loaded
0172         REQUIRE(subtitleModel->rowCount() == 2);
0173         subtitleModel->removeAllSubtitles();
0174         REQUIRE(subtitleModel->rowCount() == 0);
0175     }
0176 
0177     SECTION("Load SRT subtitle file with two dots")
0178     {
0179         QString subtitleFile = sourcesPath + "/dataset/subs-with-two-dots.srt";
0180         subtitleModel->importSubtitle(subtitleFile);
0181         // Ensure the 2 dialogues are loaded
0182         REQUIRE(subtitleModel->rowCount() == 2);
0183         subtitleModel->removeAllSubtitles();
0184         REQUIRE(subtitleModel->rowCount() == 0);
0185     }
0186 
0187     SECTION("Ensure 2 subtitles cannot be place at same frame position")
0188     {
0189         // In our current implementation, having 2 subtitles at same start time is not allowed
0190         int subId = TimelineModel::getNextId();
0191         int subId2 = TimelineModel::getNextId();
0192         int subId3 = TimelineModel::getNextId();
0193         double fps = pCore->getCurrentFps();
0194         REQUIRE(subtitleModel->addSubtitle(subId, GenTime(50, fps), GenTime(70, fps), QStringLiteral("Hello"), false, false));
0195         REQUIRE(subtitleModel->addSubtitle(subId2, GenTime(50, fps), GenTime(90, fps), QStringLiteral("Hello2"), false, false) == false);
0196         REQUIRE(subtitleModel->addSubtitle(subId3, GenTime(100, fps), GenTime(140, fps), QStringLiteral("Second"), false, false));
0197         REQUIRE(subtitleModel->rowCount() == 2);
0198         REQUIRE(subtitleModel->moveSubtitle(subId, GenTime(100, fps), false, false) == false);
0199         REQUIRE(subtitleModel->moveSubtitle(subId, GenTime(300, fps), false, false));
0200         subtitleModel->removeAllSubtitles();
0201         REQUIRE(subtitleModel->rowCount() == 0);
0202     }
0203 
0204     SECTION("Ensure we cannot cut overlapping subtitles (it would create 2 subtitles at same frame position")
0205     {
0206         // In our current implementation, having 2 subtitles at same start time is not allowed
0207         int subId = TimelineModel::getNextId();
0208         int subId2 = TimelineModel::getNextId();
0209         double fps = pCore->getCurrentFps();
0210         REQUIRE(subtitleModel->addSubtitle(subId, GenTime(50, fps), GenTime(70, fps), QStringLiteral("Hello"), false, false));
0211         REQUIRE(subtitleModel->addSubtitle(subId2, GenTime(60, fps), GenTime(90, fps), QStringLiteral("Hello2"), false, false));
0212         REQUIRE(subtitleModel->rowCount() == 2);
0213         REQUIRE(timeline->requestClipsGroup({subId, subId2}));
0214         REQUIRE_FALSE(TimelineFunctions::requestClipCut(timeline, subId, 65));
0215         subtitleModel->removeAllSubtitles();
0216         REQUIRE(subtitleModel->rowCount() == 0);
0217     }
0218 
0219     binModel->clean();
0220     pCore->m_projectManager = nullptr;
0221 }