diff --git a/src/Lib/Ass/AssFactory.cc b/src/Lib/Ass/AssFactory.cc index 334de5b8b3e1fc18514958faf63b3684aac84cc0..354c50d8ce4b7fca4b3bfb6d68924c93cfb3fd32 100644 --- a/src/Lib/Ass/AssFactory.cc +++ b/src/Lib/Ass/AssFactory.cc @@ -6,22 +6,19 @@ using namespace Vivy::Ass; bool AssFactory::initFromStorage() noexcept { - QTextStream in(&diskStorage); QString currentSection{}; quint64 lineIndex = 0; QStringList stylesContent{}; QStringList eventsContent{}; - while (!in.atEnd()) { - const QString line = in.readLine().trimmed(); - - // Dectect comment + auto processLine = [&](const QString line) { + // Detect comment if (line.startsWith(";") || line.isEmpty()) { lineIndex++; - continue; + return; } - // Dectect sections + // Detect sections else if (line.startsWith("[") && line.endsWith("]")) { currentSection = line.mid(1, line.size() - 2); logDebug() << "Parsing section " << VIVY_LOG_QUOTED(currentSection); @@ -57,7 +54,23 @@ AssFactory::initFromStorage() noexcept } lineIndex++; - } + }; + + if (internalAss.isArray()) { + QJsonArray arr = internalAss.toArray(); + auto i = arr.constBegin(); + while (i != arr.constEnd()) { + processLine(i->toString()); + ++i; + } + } else if (diskStorage.exists()) { + QTextStream in(&diskStorage); + + while (!in.atEnd()) { + processLine(in.readLine().trimmed()); + } + } else + return false; // Construct the styles and the lines try { @@ -109,10 +122,24 @@ AssFactory::checkValidity() const noexcept AssFactory::AssFactory(const QString &fileName) : diskStorage(fileName) + , internalAss() { if (!diskStorage.open(QIODevice::ReadOnly | QIODevice::Text)) throw std::runtime_error("failed to open file for reading"); + init(); +} + +AssFactory::AssFactory(const QJsonValue &internal) + : diskStorage("") + , internalAss(internal) +{ + init(); +} + +void +AssFactory::init() +{ if (!initFromStorage()) throw std::runtime_error("failed to init ass factory from file"); @@ -142,6 +169,32 @@ AssFactory::getInfoSection() const noexcept return assInfo; } +QStringList +AssFactory::getAssHeaderAsText() const noexcept +{ + QStringList ret = QStringList("[" + sectionScriptInfo + "]"); + + auto it = assInfo.constBegin(); + while (it != assInfo.constEnd()) { + ret += it.key() + ": " + it.value().toString(); + ++it; + } + + // TODO: add styles as text + ret.append(""); + ret.append("[" + sectionStyles + "]"); + //ret.append("Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding"); + for (int i = 0; i < assStyles.size(); ++i) { + ret.append(assStyles.at(i)->getRawText()); + } + + ret.append(""); + ret.append("[" + sectionEvents + "]"); + //ret.append("Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text"); + + return ret; +} + StyleWeakPtr AssFactory::getStyle(const QString &name) const noexcept { diff --git a/src/Lib/Ass/AssFactory.hh b/src/Lib/Ass/AssFactory.hh index 8e1812889b07cf4fb7183943662d6a6fb56d0b54..9e0c49c513a95ec82cb91b5b0256e893e1c67f1d 100644 --- a/src/Lib/Ass/AssFactory.hh +++ b/src/Lib/Ass/AssFactory.hh @@ -23,6 +23,8 @@ public: private: QFile diskStorage; + QJsonValue internalAss; + SectionContent assInfo{}; QVector<LinePtr> assLines{}; QVector<StylePtr> assStyles{}; @@ -44,14 +46,18 @@ private: bool initFromStorage() noexcept; bool checkValidity() const noexcept; + void init(); + public: explicit AssFactory(const QString &); + explicit AssFactory(const QJsonValue &); ~AssFactory() noexcept = default; bool hasStyle(const QString &) const noexcept; StyleWeakPtr getStyle(const QString &) const noexcept; SectionContent getInfoSection() const noexcept; + QStringList getAssHeaderAsText() const noexcept; void getStyles(QVector<StylePtr> &) const noexcept; void getLines(QVector<LinePtr> &) const noexcept; }; diff --git a/src/Lib/Ass/Line.cc b/src/Lib/Ass/Line.cc index 5188678d6740670cc6ff68299fd5a5b8d6f1553f..af9448cb6ca28372faf56104ff0fad8025d07471 100644 --- a/src/Lib/Ass/Line.cc +++ b/src/Lib/Ass/Line.cc @@ -180,7 +180,10 @@ Line::getContentAsText() const noexcept QString Line::getLineAsText() const noexcept { - return QString::asprintf("%s: %d,%s,%s,", "Dialogue", layer, "0:00:00.00", "0:00:00.00") + + // FIXME: asprintf bad, use QString("%x").arg()? + return QString::asprintf("%s: %d,%s,%s,", "Dialogue", layer, + Utils::Time::fromUInt(start).toString().toStdString().c_str(), + Utils::Time::fromUInt(end).toString().toStdString().c_str()) + styleProperties.name + QString::asprintf(",%s,%d,%d,%d,%s,", "", styleProperties.marginL, styleProperties.marginR, styleProperties.marginV, "karaoke") + diff --git a/src/Lib/Ass/Style.cc b/src/Lib/Ass/Style.cc index 0a78af9537991cd7d4f8d123ea79b3034967bcc8..c6114cfc31a49dd72e66771bb9200aab4e299dc2 100644 --- a/src/Lib/Ass/Style.cc +++ b/src/Lib/Ass/Style.cc @@ -196,3 +196,21 @@ Style::getProperties() const noexcept ret.setObject(object); return ret; } + +#define PRINT_STYLE_BOOL(b) b ? -1 : 0 +QString +Style::getRawText() const noexcept +{ + // FIXME: asprintf bad, use QString("%x").arg()? + return QString::asprintf( + "Style: %s,%s,%d,%s,%s,%s,%s,%d,%d,%d,%d,%0.2f,%0.2f,%0.2f,%0.2f,%0.2f,%0.2f,%0.2f,%d,%d,%d,%d,%d", + styleName.toStdString().c_str(), fontName.toStdString().c_str(), fontSize, + Color::toString(primaryColor).toStdString().c_str(), + Color::toString(secondaryColor).toStdString().c_str(), + Color::toString(outlineColor).toStdString().c_str(), + Color::toString(backColor).toStdString().c_str(), PRINT_STYLE_BOOL(bold), + PRINT_STYLE_BOOL(italic), PRINT_STYLE_BOOL(underline), PRINT_STYLE_BOOL(strikeOut), + double(scaleX), double(scaleY), double(spacing), double(angle), double(borderStyle), + double(outline), double(shadow), alignment, marginL, marginR, marginV, encoding); +} +#undef PRINT_STYLE_BOOL diff --git a/src/Lib/Ass/Style.hh b/src/Lib/Ass/Style.hh index 6712368c054861e3f03368a3f27c4002f7166da6..d4b3bfb62c679466988af0831261edd8da927535 100644 --- a/src/Lib/Ass/Style.hh +++ b/src/Lib/Ass/Style.hh @@ -47,5 +47,6 @@ public: QString getElementName() const noexcept; QJsonDocument getProperties() const noexcept; + QString getRawText() const noexcept; }; } diff --git a/src/Lib/Ass/StyleProperties.hh b/src/Lib/Ass/StyleProperties.hh index d01680a297640f9f2bbdbbd42a5884ea59584264..0efc92abddef11e533d97b18790a50866a00e944 100644 --- a/src/Lib/Ass/StyleProperties.hh +++ b/src/Lib/Ass/StyleProperties.hh @@ -8,7 +8,7 @@ namespace Vivy::Ass // Overrides some properties of the Style of an Ass::Line, Ass::Char, Ass::Syl struct StyleProperties final { - QString name{}; + QString name{ "Default" }; // Colors QColor primaryColor{ Color::defaultValue }, secondaryColor{ Color::defaultValue }, diff --git a/src/Lib/Document/CRTPSubDocument.hh b/src/Lib/Document/CRTPSubDocument.hh index 7aea6cf7871c4a7caf0167ac79f998ad16e38de6..96a2607b12abe7a5299250fe90b955aa9ba75629 100644 --- a/src/Lib/Document/CRTPSubDocument.hh +++ b/src/Lib/Document/CRTPSubDocument.hh @@ -129,7 +129,27 @@ class AssSubDocument final : public CRTPSubDocument<AssDocumentType, AssSubDocum static constexpr inline bool hasContext = false; const QStringList &suffixList = Vivy::Utils::assFileSuffix; +public: + static std::unique_ptr<AssSubDocument> fromInternal(const QJsonValue &internal) noexcept + { + auto ret = std::unique_ptr<AssSubDocument>(new AssSubDocument()); + ret->logDebug() << "Init ASS subdocument from internal"; + + try { + ret->initFromInternal(internal); // May throw + } catch (const std::runtime_error &e) { + ret->logDebug() << "Failed to init ASS subdocument from internal: " << e.what(); + ret.reset(); + // FIXME: currently terminate, but should be changed to stop the loading of the document + throw e; + } + + return ret; + } + +private: void initFromPath(const QString &); + void initFromInternal(const QJsonValue &); explicit AssSubDocument() noexcept = default; friend CRTPSubDocument<AssDocumentType, AssSubDocument, void>; @@ -143,9 +163,14 @@ public: const QVector<Ass::LinePtr> &getLines() const noexcept; const QVector<Ass::StylePtr> &getStyles() const noexcept; + void setFilePath(QString filepath) noexcept; + void setInternalAss(bool b) noexcept; + bool haveInternalAss() const noexcept; + private: QVector<Ass::StylePtr> styles; QVector<Ass::LinePtr> lines; Ass::AssFactory *assFactory; + bool internalAss{ false }; }; } diff --git a/src/Lib/Document/CRTPSubDocument/AssSubDocument.cc b/src/Lib/Document/CRTPSubDocument/AssSubDocument.cc index 9f55a6f3a722b98accb463def72b8ba59e1a7af2..96366e4a6ac7b2d7b0a4ddea82b3eb9323014f37 100644 --- a/src/Lib/Document/CRTPSubDocument/AssSubDocument.cc +++ b/src/Lib/Document/CRTPSubDocument/AssSubDocument.cc @@ -15,6 +15,16 @@ AssSubDocument::initFromPath(const QString &path) assFactory->getLines(lines); } +void +AssSubDocument::initFromInternal(const QJsonValue &internal) +{ + styles.clear(); + lines.clear(); + assFactory = new Ass::AssFactory(internal); + assFactory->getStyles(styles); + assFactory->getLines(lines); +} + QString AssSubDocument::getElementName() const noexcept { @@ -53,14 +63,36 @@ AssSubDocument::getFactory() noexcept return assFactory; } +bool +AssSubDocument::haveInternalAss() const noexcept +{ + return internalAss; +} + +void +AssSubDocument::setInternalAss(bool b) noexcept +{ + internalAss = b; +} + QJsonArray AssSubDocument::getInternalAss() noexcept { QJsonArray ret; + QStringList info = assFactory->getAssHeaderAsText(); + for (int i = 0; i < info.size(); ++i) + ret.append(info.at(i)); + for (int i = 0; i < lines.size(); i++) { ret.append(lines[i]->getLineAsText()); } return ret; } + +void +AssSubDocument::setFilePath(QString filepath) noexcept +{ + filePath = filepath; +} diff --git a/src/Lib/Document/VivyDocument.cc b/src/Lib/Document/VivyDocument.cc index baf0113ea79a285a7a690b4ddcd3e874d9f38e8d..8dc4064c017be92d214df16c30e65e9320c3f269 100644 --- a/src/Lib/Document/VivyDocument.cc +++ b/src/Lib/Document/VivyDocument.cc @@ -89,6 +89,26 @@ VivyDocument::loadSaveJsonDocumentFile_ALPHA(VivyDocument *const self, const QJs if (!self->loadSubDocument(ass.toString(), Capabilities::AssAble)) throw std::runtime_error("Failed to load ASS sub document"); } + + /* + * TODO: for now, if there is an internal ass we use it and ignore the SubAss file + * In the future, both should be loaded to allow the user to switch between the 2 + * (e.g. a "reference" ass and the vivy ass) + */ + if (QJsonValue haveInternalAss = json[KeySubDocuments][KeyHaveInternalAss]; + !haveInternalAss.isUndefined() && haveInternalAss.toBool()) { + // ASS is inside Vivy document + if (QJsonValue internalAssSource = json[KeySubDocuments][KeyInternalAssSource]; + !internalAssSource.isUndefined() && internalAssSource.isArray()) { + if (!self->loadSubDocument(internalAssSource, Capabilities::AssAble)) { + throw std::runtime_error("Failed to load ASS sub document"); + } + } + } else if (QJsonValue ass = json[KeySubDocuments][KeySubAss]; !ass.isUndefined()) { + // ASS in its own ASS file + if (!self->loadSubDocument(ass.toString(), Capabilities::AssAble)) + throw std::runtime_error("Failed to load ASS sub document"); + } } void @@ -130,13 +150,12 @@ VivyDocument::getSaveJsonDocumentFile() const subDocumentJson.insert(KeySubVideo, videoDocument->getFilePath()); if (documentType & Capabilities::AssAble) { + subDocumentJson.insert(KeySubAss, assDocument->getFilePath()); + subDocumentJson.insert(KeyHaveInternalAss, assDocument->haveInternalAss()); + // ASS is inside Vivy document - if ((assDocument->getFilePath() == getName())) + if (assDocument->haveInternalAss()) subDocumentJson.insert(KeyInternalAssSource, assDocument->getInternalAss()); - - // ASS is in its own ASS file - else - subDocumentJson.insert(KeySubAss, assDocument->getFilePath()); } json.insert(KeySubDocuments, subDocumentJson); @@ -203,6 +222,26 @@ VivyDocument::loadSubDocument(const QString &subName, VivyDocument::Capabilities return true; } +bool +VivyDocument::loadSubDocument(const QJsonValue &internalAss, + VivyDocument::Capabilities asType) noexcept +{ + logDebug() << "ASS sub-doc: Trying to open file internal ASS"; + + if (asType == Capabilities::AssAble) { + if ([[maybe_unused]] auto doc = assDocument.get()) { + // Here we may want to do some confirmation / cleanup + assDocument.reset(); + } + + assDocument = AssSubDocument::fromInternal(internalAss); + if (assDocument) + addDocumentType(AssAble); + } + + return true; +} + bool VivyDocument::detectDocumentType(const QFileInfo &file, Capabilities *const ableType) noexcept { diff --git a/src/Lib/Document/VivyDocument.hh b/src/Lib/Document/VivyDocument.hh index 37448b56bc833252086505dfb6f850700ab961cb..e2f4d25bb9cf61dafe438881ae73cd37228070f2 100644 --- a/src/Lib/Document/VivyDocument.hh +++ b/src/Lib/Document/VivyDocument.hh @@ -26,6 +26,7 @@ class VivyDocument final : public AbstractDocument { DCL_VIVY_SAVE_KEY(SubAss) DCL_VIVY_SAVE_KEY(SubVideo) DCL_VIVY_SAVE_KEY(InternalAssSource) + DCL_VIVY_SAVE_KEY(HaveInternalAss) DCL_VIVY_SAVE_KEY(DocumentVersion) using LoadFunctionForVersion = @@ -82,6 +83,7 @@ public: void save() override; bool loadSubDocument(const QString &) noexcept; bool loadSubDocument(const QString &, Capabilities) noexcept; + bool loadSubDocument(const QJsonValue &, VivyDocument::Capabilities) noexcept; // Getters std::shared_ptr<AudioSubDocument> getAudioSubDocument() const noexcept; diff --git a/src/Lib/Utils.cc b/src/Lib/Utils.cc index 5bfeaab70b7818aa03d88bf61f8e7204c7989e2a..408a13a9d0edb749fffcd470e016b64d27d6a6f0 100644 --- a/src/Lib/Utils.cc +++ b/src/Lib/Utils.cc @@ -146,6 +146,25 @@ Utils::Time::fromString(const QString &str) throw std::runtime_error("The string is not of the format `H:MM:SS.cs`"); } +Utils::Time +Utils::Time::fromUInt(const quint64 &t) +{ + quint64 rem = t; + Time ret; + + ret.hour = rem / (100 * 60 * 60); + rem = rem % (100 * 60 * 60); + + ret.minute = rem / (100 * 60); + rem = rem % (100 * 60); + + ret.second = rem / (100); + + ret.centisecond = rem % 100; + + return ret; +} + quint64 Utils::Time::toUInt() const noexcept { @@ -156,8 +175,10 @@ Utils::Time::toUInt() const noexcept QString Utils::Time::toString() const noexcept { - return QString::number(hour) + ":" + QString::number(minute) + ":" + QString::number(second) + - "." + QString::number(centisecond); + return QString("%1").arg(hour, 2, 10, QChar('0')) + ":" + + QString("%1").arg(minute, 2, 10, QChar('0')) + ":" + + QString("%1").arg(second, 2, 10, QChar('0')) + "." + + QString("%1").arg(centisecond, 2, 10, QChar('0')); } QString diff --git a/src/Lib/Utils.hh b/src/Lib/Utils.hh index 097b16ad332a0335926063f990090d6372ae40ff..d80030d0aae5ea7e1043a540ab64c297de89b429 100644 --- a/src/Lib/Utils.hh +++ b/src/Lib/Utils.hh @@ -217,6 +217,7 @@ struct Time final { quint64 centisecond; static Time fromString(const QString &); + static Time fromUInt(const quint64 &); QString toString() const noexcept; quint64 toUInt() const noexcept; diff --git a/src/UI/MainWindow.cc b/src/UI/MainWindow.cc index a877dcc60bd6118a41e895cb8e170e9e12c227ac..a2bf30110b1979e046f8c7877c37948fc31280da 100644 --- a/src/UI/MainWindow.cc +++ b/src/UI/MainWindow.cc @@ -47,6 +47,7 @@ MainWindow::MainWindow() noexcept DCL_MENU(edit, "&Edit"); DCL_MENU(viewTmp, "&View"); viewMenu = viewTmpMenu; // Save the view menu + DCL_MENU(ass, "&ASS"); DCL_MENU(simulate, "&Simulate"); DCL_MENU(help, "&Help"); @@ -60,6 +61,8 @@ MainWindow::MainWindow() noexcept DCL_ACTION(loadSubDocumentVideo, "&Load video", "Load a vide file as a sub-document", file); DCL_ACTION(loadSubDocumentAss, "&Load ASS", "Load an ASS file as a sub-document", file); + DCL_ACTION(setAssInternal, "&Internal ASS", "Set the ASS subdocument as internal", ass); + DCL_ACTION(openDialogHelp, "&About", "Open the help dialog", help); ACTION_ADD_ICON(newDocument, VIVY_ICON_NEW); @@ -205,6 +208,23 @@ MainWindow::openProperties(int index) noexcept current->openProperties(); } +void +MainWindow::setAssInternal() noexcept +{ + try { + const auto document = dynamic_cast<VivyDocument *>(getCurrentDocument()); + if (document) { + if (auto p = document->getAssSubDocument()) { + p->setInternalAss(true); + } + } + } + + catch (const std::runtime_error &e) { + logError() << "Failed to set ASS as internal: " << e.what(); + } +} + void MainWindow::openDialogHelp() noexcept { diff --git a/src/UI/MainWindow.hh b/src/UI/MainWindow.hh index 690ed2f9b0f3f49b53d5779c7c198bd34a2f49a7..bc59b3ce87f516f6e5079a7cd4c31addcf4028f8 100644 --- a/src/UI/MainWindow.hh +++ b/src/UI/MainWindow.hh @@ -117,6 +117,8 @@ private slots: void saveFileAs() noexcept; void renameFile() noexcept; + void setAssInternal() noexcept; + void openDialogHelp() noexcept; void documentViewActionsChanged() noexcept;