File indexing completed on 2024-12-22 04:40:08

0001 /*
0002     SPDX-FileCopyrightText: 2007-2009 Sergio Pistone <sergio_pistone@yahoo.com.ar>
0003     SPDX-FileCopyrightText: 2010-2022 Mladen Milinkovic <max@smoothware.net>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #ifndef SUBSTATIONALPHAINPUTFORMAT_H
0009 #define SUBSTATIONALPHAINPUTFORMAT_H
0010 
0011 #include "core/richtext/richdocument.h"
0012 #include "helpers/common.h"
0013 #include "formats/inputformat.h"
0014 
0015 #include <QRegularExpression>
0016 #include <QStringBuilder>
0017 #include <QDebug>
0018 
0019 namespace SubtitleComposer {
0020 class SubStationAlphaInputFormat : public InputFormat
0021 {
0022     friend class FormatManager;
0023     friend class AdvancedSubStationAlphaInputFormat;
0024 
0025 protected:
0026     RichString toRichString(const QString &string) const
0027     {
0028         staticRE$(reCommands, "\\{([^\\}]+)\\}", REu);
0029 
0030         RichString ret;
0031 
0032         int currentStyle = 0;
0033         QRgb currentColor = 0;
0034 
0035         QRegularExpressionMatchIterator itCommands = reCommands.globalMatch(string);
0036         int offset = 0;
0037         while(itCommands.hasNext()) {
0038             QRegularExpressionMatch mCommands = itCommands.next();
0039             int newStyleFlags = currentStyle;
0040             QRgb newColor = currentColor;
0041 
0042             QString commands(mCommands.captured(1));
0043             QStringList commandsList(commands.split('\\'));
0044             for(QStringList::ConstIterator it = commandsList.constBegin(), end = commandsList.constEnd(); it != end; ++it) {
0045                 if(it->isEmpty()) {
0046                     continue;
0047                 } else if(*it == QLatin1String("i0")) {
0048                     newStyleFlags &= ~RichString::Italic;
0049                 } else if(*it == QLatin1String("b0")) {
0050                     newStyleFlags &= ~RichString::Bold;
0051                 } else if(*it == QLatin1String("u0")) {
0052                     newStyleFlags &= ~RichString::Underline;
0053                 } else if(*it == QLatin1String("i1")) {
0054                     newStyleFlags |= RichString::Italic;
0055                 } else if(it->at(0) == 'b') {
0056                     // it's usually followed 1, but can be weight of the font: 400, 700, ...
0057                     newStyleFlags |= RichString::Bold;
0058                 } else if(*it == QLatin1String("u1")) {
0059                     newStyleFlags |= RichString::Underline;
0060                 } else if(it->at(0) == 'c') {
0061                     QString val = ($("000000") + it->mid(3).chopped(1)).right(6);
0062                     if(val == QLatin1String("000000")) {
0063                         newStyleFlags &= ~RichString::Color;
0064                         newColor = 0;
0065                     } else {
0066                         newStyleFlags |= RichString::Color;
0067                         newColor = QColor(QChar('#') % val.mid(4, 2) % val.mid(2, 2) % val.mid(0, 2)).rgb();
0068                     }
0069                 }
0070             }
0071 
0072             const QString text = string.mid(offset, mCommands.capturedStart() - offset)
0073                     .replace(QLatin1String("\\N"), QLatin1String("\n"), Qt::CaseInsensitive);
0074             ret.append(RichString(text, currentStyle, currentColor));
0075 
0076             currentStyle = newStyleFlags;
0077             currentColor = newColor;
0078             offset = mCommands.capturedEnd();
0079         }
0080         const QString text = string.mid(offset)
0081                 .replace(QLatin1String("\\N"), QLatin1String("\n"), Qt::CaseInsensitive);
0082         ret.append(RichString(text, currentStyle, currentColor));
0083 
0084         return ret;
0085     }
0086 
0087     bool parseSubtitles(Subtitle &subtitle, const QString &data) const override
0088     {
0089         staticRE$(reScriptInfo, "^ *\\[Script Info\\] *[\r\n]+", REu);
0090         if(!reScriptInfo.globalMatch(data).hasNext())
0091             return false;
0092 
0093         staticRE$(reStyles, "[\r\n]+ *\\[[vV]4\\+? Styles\\] *[\r\n]+", REu);
0094         QRegularExpressionMatchIterator itStyles = reStyles.globalMatch(data);
0095         if(!itStyles.hasNext())
0096             return false;
0097         const int stylesStart = itStyles.next().capturedStart();
0098 
0099         FormatData formatData = createFormatData();
0100 
0101         formatData.setValue($("ScriptInfo"), data.mid(0, stylesStart));
0102 
0103         staticRE$(reEvents, "[\r\n]+ *\\[Events\\] *[\r\n]+", REu);
0104         QRegularExpressionMatchIterator itEvents = reEvents.globalMatch(data, stylesStart);
0105         if(!itEvents.hasNext())
0106             return false;
0107         int eventsStart = itEvents.next().capturedStart();
0108 
0109         formatData.setValue($("Styles"), data.mid(stylesStart, eventsStart - stylesStart));
0110 
0111         staticRE$(reFormat, " *Format: *(\\w+,? *)+[\r\n]+", REu);
0112         QRegularExpressionMatchIterator itFormat = reFormat.globalMatch(data, eventsStart);
0113         if(!itFormat.hasNext())
0114             return false;
0115 
0116         setFormatData(subtitle, &formatData);
0117         formatData.clear();
0118 
0119         staticRE$(reDialogue, " *Dialogue: *[^,]+, *([^,]+), *([^,]+), *[^,]*, *[^,]*, *[^,]*, *[^,]*, *[^,]*, *[^,]*, *([^\r\n]*)[\r\n]+", REu);
0120         staticRE$(reDialogueData, " *(Dialogue: *[^,]+, *)[^,]+(, *)[^,]+(, *[^,]+, *[^,]*, *[^,]*, *[^,]*, *[^,]*, *[^,]*, *).*", REu);
0121         staticRE$(reTime, "(\\d+):(\\d+):(\\d+).(\\d+)", REu);
0122 
0123         do {
0124             QRegularExpressionMatch mFormat = itFormat.next();
0125             QRegularExpressionMatchIterator itDialogue = reDialogue.globalMatch(data, mFormat.capturedEnd());
0126             while(itDialogue.hasNext()) {
0127                 QRegularExpressionMatch mDialogue = itDialogue.next();
0128 
0129                 QRegularExpressionMatchIterator itTime = reTime.globalMatch(mDialogue.captured(1));
0130                 if(!itTime.hasNext()) {
0131                     qWarning() << "SubStationAlpha failed to match showTime";
0132                     break;
0133                 }
0134                 QRegularExpressionMatch mTime = itTime.next();
0135                 Time showTime(mTime.captured(1).toInt(), mTime.captured(2).toInt(), mTime.captured(3).toInt(), mTime.captured(4).toInt() * 10);
0136 
0137                 itTime = reTime.globalMatch(mDialogue.captured(2));
0138                 if(!itTime.hasNext()) {
0139                     qWarning() << "SubStationAlpha failed to match hideTime";
0140                     break;
0141                 }
0142                 mTime = itTime.next();
0143                 Time hideTime(mTime.captured(1).toInt(), mTime.captured(2).toInt(), mTime.captured(3).toInt(), mTime.captured(4).toInt() * 10);
0144 
0145                 SubtitleLine *line = new SubtitleLine(showTime, hideTime);
0146                 line->primaryDoc()->setRichText(toRichString(mDialogue.captured(3)), true);
0147 
0148                 formatData.setValue($("Dialogue"), mDialogue.captured(0).replace(reDialogueData, $("\\1%1\\2%2\\3%3\n")));
0149                 setFormatData(line, &formatData);
0150 
0151                 subtitle.insertLine(line);
0152             }
0153         } while(itFormat.hasNext());
0154 
0155         return true;
0156     }
0157 
0158     SubStationAlphaInputFormat(
0159             const QString &name = $("SubStation Alpha"),
0160             const QStringList &extensions = QStringList($("ssa")))
0161         : InputFormat(name, extensions)
0162     {}
0163 };
0164 
0165 class AdvancedSubStationAlphaInputFormat : public SubStationAlphaInputFormat
0166 {
0167     friend class FormatManager;
0168 
0169 protected:
0170     AdvancedSubStationAlphaInputFormat()
0171         : SubStationAlphaInputFormat($("Advanced SubStation Alpha"), QStringList($("ass")))
0172     {}
0173 };
0174 
0175 }
0176 
0177 #endif