177 lines
8.0 KiB
C++
177 lines
8.0 KiB
C++
#pragma once
|
|
|
|
#include "llmclient.h"
|
|
#include "subtitleextractor.h"
|
|
#include "glossarystore.h"
|
|
|
|
#include <QCoroTask>
|
|
|
|
#include <QObject>
|
|
#include <QStringList>
|
|
#include <QUrl>
|
|
#include <QVariantList>
|
|
#include <QVariantMap>
|
|
#include <QtQml/qqmlregistration.h>
|
|
|
|
#include <atomic>
|
|
#include <memory>
|
|
#include <optional>
|
|
|
|
class QNetworkAccessManager;
|
|
|
|
// Translates a whole subtitle track with an LLM, batch by batch, caches the result as an
|
|
// .srt file and saves a copy next to the video (see TranslationStore).
|
|
class TranslationService : public QObject
|
|
{
|
|
Q_OBJECT
|
|
QML_ELEMENT
|
|
|
|
Q_PROPERTY(bool busy READ isBusy NOTIFY busyChanged)
|
|
Q_PROPERTY(double progress READ progress NOTIFY progressChanged)
|
|
Q_PROPERTY(QString statusText READ statusText NOTIFY statusTextChanged)
|
|
Q_PROPERTY(QString errorText READ errorText NOTIFY errorTextChanged)
|
|
Q_PROPERTY(QStringList availableModels READ availableModels NOTIFY availableModelsChanged)
|
|
Q_PROPERTY(QVariantList translationPresets READ translationPresets CONSTANT)
|
|
Q_PROPERTY(bool fetchingModels READ isFetchingModels NOTIFY fetchingModelsChanged)
|
|
Q_PROPERTY(QVariantList glossaryEntries READ glossaryEntries NOTIFY glossaryChanged)
|
|
Q_PROPERTY(QString glossaryDirectory READ glossaryDirectory NOTIFY glossaryChanged)
|
|
Q_PROPERTY(QString glossaryEpisode READ glossaryEpisode NOTIFY glossaryChanged)
|
|
Q_PROPERTY(QStringList glossaryEpisodes READ glossaryEpisodes NOTIFY glossaryChanged)
|
|
Q_PROPERTY(QString glossaryError READ glossaryError NOTIFY glossaryErrorChanged)
|
|
// The video playing. Its translations are shown when ready; others are only saved.
|
|
// Opening another video stops a single translation but not a playlist one.
|
|
Q_PROPERTY(QUrl currentVideo READ currentVideo WRITE setCurrentVideo NOTIFY currentVideoChanged)
|
|
|
|
public:
|
|
explicit TranslationService(QObject *parent = nullptr);
|
|
~TranslationService() override;
|
|
|
|
bool isBusy() const;
|
|
double progress() const;
|
|
QString statusText() const;
|
|
QString errorText() const;
|
|
QStringList availableModels() const;
|
|
QVariantList translationPresets() const;
|
|
bool isFetchingModels() const;
|
|
QVariantList glossaryEntries() const;
|
|
QString glossaryDirectory() const;
|
|
QString glossaryEpisode() const;
|
|
QStringList glossaryEpisodes() const;
|
|
QString glossaryError() const;
|
|
QUrl currentVideo() const;
|
|
void setCurrentVideo(const QUrl &url);
|
|
|
|
// \a track is an entry of MpvItem::subtitleTracks.
|
|
Q_INVOKABLE void translate(const QUrl &videoUrl, const QVariantMap &track, const QString &mediaTitle);
|
|
// Translates every video in \a urls that has no saved translation yet, one after
|
|
// another, starting at the current video. The current one uses \a currentTrack when
|
|
// set; the others use the embedded subtitle findTranslationSource() picks.
|
|
Q_INVOKABLE void translatePlaylist(const QList<QUrl> &urls, const QVariantMap ¤tTrack, const QString ¤tTitle);
|
|
Q_INVOKABLE void cancel();
|
|
// Asks the API configured in the settings for its models.
|
|
Q_INVOKABLE void fetchModels();
|
|
Q_INVOKABLE void clearError();
|
|
Q_INVOKABLE void forgetTranslation();
|
|
// Path of the translation of \a videoUrl into the target language set in Settings,
|
|
// or an empty string when there is none.
|
|
Q_INVOKABLE QString savedTranslation(const QUrl &videoUrl) const;
|
|
Q_INVOKABLE void loadGlossary(const QUrl &videoUrl);
|
|
Q_INVOKABLE bool saveGlossary(const QVariantList &entries, const QStringList &episodes = {});
|
|
Q_INVOKABLE void clearGlossary();
|
|
// File operations use the dialog draft; import only commits when Save is clicked.
|
|
Q_INVOKABLE QVariantMap importGlossaryFile(const QUrl &url, const QVariantList &entries, const QStringList &episodes);
|
|
Q_INVOKABLE QVariantMap exportGlossaryFile(const QUrl &url, const QVariantList &entries, const QStringList &episodes);
|
|
Q_INVOKABLE QStringList sortedGlossaryEpisodes(const QStringList &episodes) const;
|
|
|
|
Q_SIGNALS:
|
|
void busyChanged();
|
|
void progressChanged();
|
|
void statusTextChanged();
|
|
void errorTextChanged();
|
|
void availableModelsChanged();
|
|
void fetchingModelsChanged();
|
|
void glossaryChanged();
|
|
void glossaryErrorChanged();
|
|
void translated(const QUrl &subtitleUrl, bool fromCache);
|
|
void currentVideoChanged();
|
|
// A playlist translation ended without being cancelled. Problems are in errorText.
|
|
void playlistTranslated(int translatedCount, int alreadyTranslatedCount, int problemCount);
|
|
|
|
private:
|
|
struct Job;
|
|
struct Batches;
|
|
// Where translateVideo() left a translation, or why it could not make one.
|
|
struct Outcome {
|
|
QString cacheFile;
|
|
QString suffix;
|
|
QString configKey;
|
|
bool fromCache = false;
|
|
QString error;
|
|
// The error is about this video's subtitles; other videos may still work.
|
|
bool skippable = false;
|
|
|
|
static Outcome ready(const QString &cacheFile, const QString &suffix, const QString &configKey, bool fromCache)
|
|
{
|
|
Outcome outcome;
|
|
outcome.cacheFile = cacheFile;
|
|
outcome.suffix = suffix;
|
|
outcome.configKey = configKey;
|
|
outcome.fromCache = fromCache;
|
|
return outcome;
|
|
}
|
|
static Outcome failed(const QString &error, bool skippable = false)
|
|
{
|
|
Outcome outcome;
|
|
outcome.error = error;
|
|
outcome.skippable = skippable;
|
|
return outcome;
|
|
}
|
|
};
|
|
|
|
QCoro::Task<> run(std::shared_ptr<Job> job, QUrl videoUrl, QVariantMap track);
|
|
QCoro::Task<> runPlaylist(std::shared_ptr<Job> job, QList<QUrl> urls, QVariantMap currentTrack, QString currentTitle);
|
|
// Reads the settings and the API key for \a job; nothing (after failing it) when they are not usable.
|
|
QCoro::Task<std::optional<LlmClient::Config>> startJob(std::shared_ptr<Job> job);
|
|
QCoro::Task<Outcome> translateVideo(std::shared_ptr<Job> job, LlmClient::Config config, QUrl videoUrl, QVariantMap track);
|
|
QCoro::Task<> runFetchModels();
|
|
// Translates \a lines, retrying failures and asking again for lines the model skipped.
|
|
QCoro::Task<LlmClient::BatchResult> translateLines(std::shared_ptr<Job> job, QStringList lines, QStringList context);
|
|
// Takes batches from \a batches until none are left; several run side by side.
|
|
QCoro::Task<> translateBatches(std::shared_ptr<Job> job, std::shared_ptr<Batches> batches);
|
|
QCoro::Task<Subtitles::ExtractResult> loadCues(std::shared_ptr<Job> job, QUrl videoUrl, QVariantMap track);
|
|
|
|
// Reads the settings and the API key. The second value is an error message when
|
|
// something is missing.
|
|
QCoro::Task<std::pair<LlmClient::Config, QString>> loadConfig();
|
|
void fail(const std::shared_ptr<Job> &job, const QString &error);
|
|
// Saves the translation where players find it and, for the current video, shows it.
|
|
// Returns an error message on failure.
|
|
QString publish(const std::shared_ptr<Job> &job, const QUrl &videoUrl, const Outcome &outcome);
|
|
void endJob(const std::shared_ptr<Job> &job);
|
|
// In a playlist, prefixes the episode number and counts finished episodes in the progress.
|
|
void setJobStatus(const Job &job, const QString &text);
|
|
void setJobProgress(const Job &job, double episodeProgress);
|
|
void setLinesStatus(const Job &job, qsizetype done, qsizetype total);
|
|
|
|
void setBusy(bool busy);
|
|
void setProgress(double progress);
|
|
void setStatusText(const QString &text);
|
|
void setErrorText(const QString &text);
|
|
void setFetchingModels(bool fetching);
|
|
void applyGlossaryEdits(const GlossaryStore::Glossary &previous, const GlossaryStore::Glossary &updated);
|
|
|
|
QNetworkAccessManager *m_network;
|
|
std::shared_ptr<Job> m_job;
|
|
bool m_busy = false;
|
|
double m_progress = 0.0;
|
|
QString m_statusText;
|
|
QString m_errorText;
|
|
QString m_glossaryError;
|
|
QStringList m_availableModels;
|
|
bool m_fetchingModels = false;
|
|
GlossaryStore::Glossary m_glossary;
|
|
QString m_glossaryEpisode;
|
|
QString m_lastTranslationPath;
|
|
QUrl m_currentVideo;
|
|
};
|