feat(translate): keep translated subtitles next to the video so they load on reopen (#14)
Co-authored-by: Tri Vo Khac <git@trivk.dev>
This commit was merged in pull request #14.
This commit is contained in:
1 parent
b34a01a92f
commit
76b12d6632
13 files changed
+716
-65
No files matched your search
@@ -0,0 +1,209 @@
|
||||
#include "translationstore.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QTemporaryDir>
|
||||
#include <QTest>
|
||||
|
||||
using namespace Qt::Literals::StringLiterals;
|
||||
|
||||
namespace
|
||||
{
|
||||
void writeText(const QString &path, const QByteArray &data)
|
||||
{
|
||||
QFile file(path);
|
||||
QVERIFY(file.open(QIODevice::WriteOnly));
|
||||
file.write(data);
|
||||
}
|
||||
|
||||
QByteArray readText(const QString &path)
|
||||
{
|
||||
QFile file(path);
|
||||
return file.open(QIODevice::ReadOnly) ? file.readAll() : QByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
class TranslationStoreTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
QTemporaryDir m_dir;
|
||||
QString m_videos;
|
||||
QString m_cache;
|
||||
QString m_video;
|
||||
QUrl m_videoUrl;
|
||||
|
||||
// The translation as TranslationService leaves it in the cache before publishing.
|
||||
QString cached(const QString &key, const QByteArray &data)
|
||||
{
|
||||
const QString path = TranslationStore::cachePath(m_cache, m_videoUrl, key, u"vi"_s);
|
||||
writeText(path, data);
|
||||
return path;
|
||||
}
|
||||
|
||||
private Q_SLOTS:
|
||||
void init()
|
||||
{
|
||||
QVERIFY(m_dir.isValid());
|
||||
QDir root(m_dir.path());
|
||||
root.removeRecursively();
|
||||
QVERIFY(root.mkpath(u"videos"_s));
|
||||
QVERIFY(root.mkpath(u"cache"_s));
|
||||
m_videos = m_dir.filePath(u"videos"_s);
|
||||
m_cache = m_dir.filePath(u"cache"_s);
|
||||
m_video = m_videos + u"/Show.S01E01.mkv"_s;
|
||||
writeText(m_video, "video");
|
||||
m_videoUrl = QUrl::fromLocalFile(m_video);
|
||||
}
|
||||
|
||||
void namesFiles()
|
||||
{
|
||||
QCOMPARE(TranslationStore::languageSuffix(u"Vietnamese"_s), u"vi"_s);
|
||||
QCOMPARE(TranslationStore::languageSuffix(u"vietnamese"_s), u"vi"_s);
|
||||
QCOMPARE(TranslationStore::languageSuffix(u"Klingon!"_s), u"klingon"_s);
|
||||
QCOMPARE(TranslationStore::sidecarPaths(u"/films/Show.S01E01.mkv"_s, u"vi"_s),
|
||||
QStringList({u"/films/Show.S01E01.vi.srt"_s, u"/films/Show.S01E01.qtpotato.vi.srt"_s}));
|
||||
QCOMPARE(TranslationStore::cachePath(u"/cache"_s, QUrl::fromLocalFile(u"/films/Show.S01E01.mkv"_s), u"abc"_s, u"vi"_s),
|
||||
u"/cache/Show.S01E01.abc.vi.srt"_s);
|
||||
QCOMPARE(TranslationStore::cachePath(u"/cache"_s, QUrl(u"https://example.com/live.m3u8"_s), u"abc"_s, u"vi"_s),
|
||||
u"/cache/subtitles.abc.vi.srt"_s);
|
||||
QCOMPARE(TranslationStore::videoKey(QUrl(u"https://example.com/live.m3u8"_s)), u"https://example.com/live.m3u8"_s);
|
||||
}
|
||||
|
||||
void writesNextToVideo()
|
||||
{
|
||||
TranslationStore::Registry registry;
|
||||
const QString cache = cached(u"k1"_s, "one");
|
||||
const auto result = TranslationStore::publish(registry, m_videoUrl, u"vi"_s, u"k1"_s, "one", cache);
|
||||
|
||||
const QString sidecar = m_videos + u"/Show.S01E01.vi.srt"_s;
|
||||
QCOMPARE(result.path, sidecar);
|
||||
QVERIFY(result.nextToVideo);
|
||||
QCOMPARE(readText(sidecar), "one");
|
||||
QVERIFY(TranslationStore::isOwned(registry, sidecar));
|
||||
QCOMPARE(TranslationStore::savedTranslation(registry, m_videoUrl, u"vi"_s), sidecar);
|
||||
QCOMPARE(TranslationStore::savedTranslation(registry, m_videoUrl, u"ja"_s), QString());
|
||||
QCOMPARE(registry.find(TranslationStore::videoKey(m_videoUrl), u"vi"_s)->configKey, u"k1"_s);
|
||||
}
|
||||
|
||||
// Different settings give a new translation, which replaces QtPotato's own file.
|
||||
void replacesOwnFileWithNewSettings()
|
||||
{
|
||||
TranslationStore::Registry registry;
|
||||
TranslationStore::publish(registry, m_videoUrl, u"vi"_s, u"k1"_s, "one", cached(u"k1"_s, "one"));
|
||||
const auto result = TranslationStore::publish(registry, m_videoUrl, u"vi"_s, u"k2"_s, "two", cached(u"k2"_s, "two"));
|
||||
|
||||
QCOMPARE(result.path, m_videos + u"/Show.S01E01.vi.srt"_s);
|
||||
QCOMPARE(readText(result.path), "two");
|
||||
QCOMPARE(registry.records.size(), 1);
|
||||
QCOMPARE(registry.records.constFirst().configKey, u"k2"_s);
|
||||
QVERIFY(!QFile::exists(m_videos + u"/Show.S01E01.qtpotato.vi.srt"_s));
|
||||
}
|
||||
|
||||
void keepsUserSubtitle()
|
||||
{
|
||||
const QString userFile = m_videos + u"/Show.S01E01.vi.srt"_s;
|
||||
writeText(userFile, "mine");
|
||||
TranslationStore::Registry registry;
|
||||
QVERIFY(!TranslationStore::isOwned(registry, userFile));
|
||||
|
||||
const auto result = TranslationStore::publish(registry, m_videoUrl, u"vi"_s, u"k1"_s, "one", cached(u"k1"_s, "one"));
|
||||
QCOMPARE(result.path, m_videos + u"/Show.S01E01.qtpotato.vi.srt"_s);
|
||||
QCOMPARE(readText(userFile), "mine");
|
||||
QCOMPARE(readText(result.path), "one");
|
||||
}
|
||||
|
||||
// A file QtPotato wrote and the user then edited counts as the user's.
|
||||
void keepsEditedTranslation()
|
||||
{
|
||||
TranslationStore::Registry registry;
|
||||
const auto first = TranslationStore::publish(registry, m_videoUrl, u"vi"_s, u"k1"_s, "one", cached(u"k1"_s, "one"));
|
||||
writeText(first.path, "one, fixed by hand");
|
||||
QVERIFY(!TranslationStore::isOwned(registry, first.path));
|
||||
|
||||
const auto second = TranslationStore::publish(registry, m_videoUrl, u"vi"_s, u"k2"_s, "two", cached(u"k2"_s, "two"));
|
||||
QCOMPARE(second.path, m_videos + u"/Show.S01E01.qtpotato.vi.srt"_s);
|
||||
QCOMPARE(readText(first.path), "one, fixed by hand");
|
||||
}
|
||||
|
||||
// Once on ".qtpotato.vi.srt", stay there instead of leaving a second copy behind.
|
||||
void staysOnPreviousFile()
|
||||
{
|
||||
const QString userFile = m_videos + u"/Show.S01E01.vi.srt"_s;
|
||||
writeText(userFile, "mine");
|
||||
TranslationStore::Registry registry;
|
||||
const auto first = TranslationStore::publish(registry, m_videoUrl, u"vi"_s, u"k1"_s, "one", cached(u"k1"_s, "one"));
|
||||
QFile::remove(userFile);
|
||||
|
||||
const auto second = TranslationStore::publish(registry, m_videoUrl, u"vi"_s, u"k2"_s, "two", cached(u"k2"_s, "two"));
|
||||
QCOMPARE(second.path, first.path);
|
||||
QCOMPARE(readText(second.path), "two");
|
||||
QVERIFY(!QFile::exists(userFile));
|
||||
}
|
||||
|
||||
void fallsBackToCacheWhenFolderIsReadOnly()
|
||||
{
|
||||
QFile::setPermissions(m_videos, QFileDevice::ReadOwner | QFileDevice::ExeOwner);
|
||||
auto restore = qScopeGuard([this] {
|
||||
QFile::setPermissions(m_videos, QFileDevice::ReadOwner | QFileDevice::WriteOwner | QFileDevice::ExeOwner);
|
||||
});
|
||||
if (QFileInfo(m_videos).isWritable()) {
|
||||
QSKIP("Permissions are not enforced here (running as root?)");
|
||||
}
|
||||
|
||||
TranslationStore::Registry registry;
|
||||
const QString cache = cached(u"k1"_s, "one");
|
||||
const auto result = TranslationStore::publish(registry, m_videoUrl, u"vi"_s, u"k1"_s, "one", cache);
|
||||
QCOMPARE(result.path, cache);
|
||||
QVERIFY(!result.nextToVideo);
|
||||
QCOMPARE(TranslationStore::savedTranslation(registry, m_videoUrl, u"vi"_s), cache);
|
||||
}
|
||||
|
||||
void fallsBackToCacheForStreams()
|
||||
{
|
||||
const QUrl stream(u"https://example.com/live.m3u8"_s);
|
||||
const QString cache = TranslationStore::cachePath(m_cache, stream, u"k1"_s, u"vi"_s);
|
||||
writeText(cache, "one");
|
||||
TranslationStore::Registry registry;
|
||||
const auto result = TranslationStore::publish(registry, stream, u"vi"_s, u"k1"_s, "one", cache);
|
||||
QCOMPARE(result.path, cache);
|
||||
QCOMPARE(TranslationStore::savedTranslation(registry, stream, u"vi"_s), cache);
|
||||
}
|
||||
|
||||
void glossaryRewriteKeepsOwnership()
|
||||
{
|
||||
TranslationStore::Registry registry;
|
||||
const auto result = TranslationStore::publish(registry, m_videoUrl, u"vi"_s, u"k1"_s, "one", cached(u"k1"_s, "one"));
|
||||
writeText(result.path, "one, new glossary");
|
||||
TranslationStore::recordRewrite(registry, result.path, "one, new glossary");
|
||||
QVERIFY(TranslationStore::isOwned(registry, result.path));
|
||||
}
|
||||
|
||||
void savesAndLoadsRegistry()
|
||||
{
|
||||
const QString file = m_dir.filePath(u"data/translations.json"_s);
|
||||
TranslationStore::Registry registry;
|
||||
const auto result = TranslationStore::publish(registry, m_videoUrl, u"vi"_s, u"k1"_s, "one", cached(u"k1"_s, "one"));
|
||||
// A record whose file is gone is not worth keeping.
|
||||
registry.put({u"/gone.mkv"_s, u"vi"_s, u"/gone.vi.srt"_s, u"k0"_s, u"00"_s});
|
||||
QVERIFY(TranslationStore::save(file, registry));
|
||||
|
||||
const TranslationStore::Registry loaded = TranslationStore::load(file);
|
||||
QCOMPARE(loaded.records.size(), 1);
|
||||
QVERIFY(TranslationStore::isOwned(loaded, result.path));
|
||||
QCOMPARE(TranslationStore::savedTranslation(loaded, m_videoUrl, u"vi"_s), result.path);
|
||||
QVERIFY(TranslationStore::load(m_dir.filePath(u"missing.json"_s)).records.isEmpty());
|
||||
}
|
||||
|
||||
// The registry is no longer right about a file that disappeared.
|
||||
void forgetsDeletedTranslation()
|
||||
{
|
||||
TranslationStore::Registry registry;
|
||||
const auto result = TranslationStore::publish(registry, m_videoUrl, u"vi"_s, u"k1"_s, "one", cached(u"k1"_s, "one"));
|
||||
QFile::remove(result.path);
|
||||
QCOMPARE(TranslationStore::savedTranslation(registry, m_videoUrl, u"vi"_s), QString());
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_GUILESS_MAIN(TranslationStoreTest)
|
||||
#include "translationstoretest.moc"
|
||||
Reference in new issue
Block a user