File indexing completed on 2024-12-22 04:40:09
0001 /* 0002 SPDX-FileCopyrightText: 2017-2022 Mladen Milinkovic <max@smoothware.net> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "vobsubinputprocessdialog.h" 0008 #include "ui_vobsubinputprocessdialog.h" 0009 0010 #include "core/richtext/richdocument.h" 0011 0012 #include <functional> 0013 0014 #include <QDebug> 0015 #include <QPainter> 0016 #include <QKeyEvent> 0017 0018 #include <KMessageBox> 0019 0020 #include <QStringBuilder> 0021 #include <QDataStream> 0022 #include <QFile> 0023 #include <QSaveFile> 0024 #include <QStringView> 0025 0026 using namespace SubtitleComposer; 0027 0028 // Private helper classes 0029 class VobSubInputProcessDialog::Frame : public QSharedData 0030 { 0031 public: 0032 Frame() {} 0033 Frame(const Frame &other) : QSharedData(other) {} 0034 ~Frame() {} 0035 0036 bool processPieces(); 0037 0038 static QMap<qint32, qint32> spaceStats; 0039 0040 quint32 index; 0041 QImage subImage; 0042 Time subShowTime; 0043 Time subHideTime; 0044 QList<PiecePtr> pieces; 0045 }; 0046 0047 class VobSubInputProcessDialog::Piece : public QSharedData 0048 { 0049 public: 0050 Piece() 0051 : line(nullptr), 0052 top(0), 0053 left(0), 0054 bottom(0), 0055 right(0), 0056 symbolCount(1) { } 0057 Piece(int x, int y) 0058 : line(nullptr), 0059 top(y), 0060 left(x), 0061 bottom(y), 0062 right(x), 0063 symbolCount(1) { } 0064 Piece(const Piece &other) 0065 : QSharedData(other), 0066 line(other.line), 0067 top(other.top), 0068 left(other.left), 0069 bottom(other.bottom), 0070 right(other.right), 0071 symbolCount(other.symbolCount), 0072 pixels(other.pixels) { } 0073 ~Piece() { } 0074 0075 inline int height() { 0076 return bottom - top + 1; 0077 } 0078 0079 inline bool operator<(const Piece &other) const; 0080 inline bool operator==(const Piece &other) const; 0081 inline Piece & operator+=(const Piece &other); 0082 0083 0084 inline void normalize(); 0085 0086 LinePtr line; 0087 qint32 top, left, bottom, right; 0088 qint32 symbolCount; 0089 RichString text; 0090 QVector<QPoint> pixels; 0091 }; 0092 0093 class VobSubInputProcessDialog::Line : public QSharedData { 0094 public: 0095 Line(int top, int bottom) 0096 : top(top), 0097 bottom(bottom) { } 0098 Line(const Line &other) 0099 : QSharedData(other), 0100 top(other.top), 0101 bottom(other.bottom) { } 0102 0103 inline int height() { return bottom - top; } 0104 0105 inline bool contains(PiecePtr piece) { return top <= piece->bottom && piece->top <= bottom; } 0106 inline bool intersects(LinePtr line) { return top <= line->bottom && line->top <= bottom; } 0107 inline void extend(int top, int bottom) { 0108 if(top < this->top) 0109 this->top = top; 0110 if(bottom > this->bottom) 0111 this->bottom = bottom; 0112 } 0113 0114 qint32 top, bottom; 0115 qint16 baseline; 0116 }; 0117 0118 QMap<qint32, qint32> VobSubInputProcessDialog::Frame::spaceStats; 0119 0120 bool 0121 VobSubInputProcessDialog::Frame::processPieces() 0122 { 0123 QImage pieceBitmap = subImage; 0124 const int width = pieceBitmap.width(); 0125 const int height = pieceBitmap.height(); 0126 PiecePtr piece; 0127 0128 QVector<int> ignoredColors = {pieceBitmap.pixelIndex(0, 0)}; 0129 int maxAlpha = 0; 0130 for(int i = 0; i < pieceBitmap.colorCount(); i++) { 0131 const int alpha = qAlpha(pieceBitmap.color(i)); 0132 if(maxAlpha < alpha) 0133 maxAlpha = alpha; 0134 } 0135 for(int i = 0; i < pieceBitmap.colorCount(); i++) { 0136 if(i == ignoredColors.at(0)) 0137 continue; 0138 const QRgb color = pieceBitmap.color(i); 0139 if(qAlpha(color) < maxAlpha || qGray(color) <= 127) 0140 ignoredColors.append(i); 0141 } 0142 0143 pieces.clear(); 0144 0145 // build piece by searching non-diagonal adjacent pixels, assigned pixels are 0146 // removed from pieceBitmap 0147 std::function<void(int,int)> cutPiece = [&](int x, int y){ 0148 if(piece->top > y) 0149 piece->top = y; 0150 if(piece->bottom < y) 0151 piece->bottom = y; 0152 0153 if(piece->left > x) 0154 piece->left = x; 0155 if(piece->right < x) 0156 piece->right = x; 0157 0158 piece->pixels.append(QPoint(x, y)); 0159 pieceBitmap.setPixel(x, y, ignoredColors.at(0)); 0160 0161 if(x < width - 1 && !ignoredColors.contains(pieceBitmap.pixelIndex(x + 1, y))) 0162 cutPiece(x + 1, y); 0163 if(x > 0 && !ignoredColors.contains(pieceBitmap.pixelIndex(x - 1, y))) 0164 cutPiece(x - 1, y); 0165 if(y < height - 1 && !ignoredColors.contains(pieceBitmap.pixelIndex(x, y + 1))) 0166 cutPiece(x, y + 1); 0167 if(y > 0 && !ignoredColors.contains(pieceBitmap.pixelIndex(x, y - 1))) 0168 cutPiece(x, y - 1); 0169 }; 0170 0171 // search pieces from top left 0172 for(int y = 0; y < height; y++) { 0173 for(int x = 0; x < width; x++) { 0174 if(!ignoredColors.contains(pieceBitmap.pixelIndex(x, y))) { 0175 piece = new Piece(x, y); 0176 cutPiece(x, y); 0177 pieces.append(piece); 0178 } 0179 } 0180 } 0181 0182 if(pieces.empty()) 0183 return false; 0184 0185 // figure out where the lines are 0186 int maxLineHeight = 0; 0187 QVector<LinePtr> lines; 0188 foreach(piece, pieces) { 0189 foreach(LinePtr line, lines) { 0190 if(line->contains(piece)) { 0191 piece->line = line; 0192 line->extend(piece->top, piece->bottom); 0193 break; 0194 } 0195 } 0196 if(!piece->line) { 0197 piece->line = new Line(piece->top, piece->bottom); 0198 lines.append(piece->line); 0199 } 0200 if(maxLineHeight < piece->line->height()) 0201 maxLineHeight = piece->line->height(); 0202 } 0203 0204 // fix accents of characters going into their own line, merge short lines 0205 // that are close to next line with next line 0206 LinePtr lastLine; 0207 foreach(LinePtr line, lines) { 0208 if(lastLine && line->top - lastLine->bottom < maxLineHeight / 3 && lastLine->height() < maxLineHeight / 3) { 0209 foreach(piece, pieces) { 0210 if(piece->line == lastLine) 0211 piece->line = line; 0212 } 0213 } 0214 lastLine = line; 0215 } 0216 0217 // find out where the symbol baseline is, using most frequent bottom coordinate, 0218 // otherwise comma and apostrophe could be recognized as same character 0219 QHash<LinePtr, QHash<qint16, qint16>> bottomCount; 0220 foreach(piece, pieces) 0221 bottomCount[piece->line][piece->bottom]++; 0222 foreach(LinePtr line, lines) { 0223 qint16 max = 0; 0224 for(auto i = bottomCount[line].cbegin(); i != bottomCount[line].cend(); ++i) { 0225 if(i.value() > max) { 0226 max = i.value(); 0227 line->baseline = i.key(); 0228 } 0229 } 0230 } 0231 foreach(piece, pieces) { 0232 if(piece->bottom < piece->line->baseline) 0233 piece->bottom = piece->line->baseline; 0234 } 0235 0236 // sort pieces, line by line, left to right, comparison is done in Piece::operator<() 0237 std::sort(pieces.begin(), pieces.end(), [](const PiecePtr &a, const PiecePtr &b)->bool{ 0238 return *a < *b; 0239 }); 0240 0241 PiecePtr prevPiece; 0242 foreach(piece, pieces) { 0243 if(prevPiece && prevPiece->line == piece->line) 0244 spaceStats[piece->left - prevPiece->right]++; 0245 prevPiece = piece; 0246 } 0247 0248 return true; 0249 } 0250 0251 inline bool 0252 VobSubInputProcessDialog::Piece::operator<(const Piece &other) const 0253 { 0254 if(line->top < other.line->top) 0255 return true; 0256 if(line->intersects(other.line) && left < other.left) 0257 return true; 0258 return false; 0259 } 0260 0261 inline bool 0262 VobSubInputProcessDialog::Piece::operator==(const Piece &other) const 0263 { 0264 if(bottom - top != other.bottom - other.top) 0265 return false; 0266 if(right - left != other.right - other.left) 0267 return false; 0268 if(symbolCount != other.symbolCount) 0269 return false; 0270 // we assume pixel vectors contain QPoint elements ordered exactly the same 0271 return pixels == other.pixels; 0272 } 0273 0274 inline VobSubInputProcessDialog::Piece & 0275 VobSubInputProcessDialog::Piece::operator+=(const Piece &other) 0276 { 0277 if(top > other.top) 0278 top = other.top; 0279 if(bottom < other.bottom) 0280 bottom = other.bottom; 0281 0282 if(left > other.left) 0283 left = other.left; 0284 if(right < other.right) 0285 right = other.right; 0286 0287 pixels.append(other.pixels); 0288 0289 return *this; 0290 } 0291 0292 // write to QDataStream 0293 inline QDataStream & 0294 operator<<(QDataStream &stream, const SubtitleComposer::VobSubInputProcessDialog::Line &line) { 0295 stream << line.top << line.bottom; 0296 return stream; 0297 } 0298 0299 inline QDataStream & 0300 operator<<(QDataStream &stream, const SubtitleComposer::VobSubInputProcessDialog::Piece &piece) { 0301 stream << *piece.line; 0302 stream << piece.top << piece.left << piece.bottom << piece.right; 0303 stream << piece.symbolCount; 0304 stream << piece.text; 0305 stream << piece.pixels; 0306 return stream; 0307 } 0308 0309 // read from QDataStream 0310 inline QDataStream & 0311 operator>>(QDataStream &stream, SubtitleComposer::VobSubInputProcessDialog::Line &line) { 0312 stream >> line.top >> line.bottom; 0313 return stream; 0314 } 0315 0316 inline QDataStream & 0317 operator>>(QDataStream &stream, SubtitleComposer::VobSubInputProcessDialog::Piece &piece) { 0318 piece.line = new VobSubInputProcessDialog::Line(0, 0); 0319 stream >> *piece.line; 0320 stream >> piece.top >> piece.left >> piece.bottom >> piece.right; 0321 stream >> piece.symbolCount; 0322 stream >> piece.text; 0323 stream >> piece.pixels; 0324 return stream; 0325 } 0326 0327 inline void 0328 VobSubInputProcessDialog::Piece::normalize() 0329 { 0330 if(top == 0 && left == 0) 0331 return; 0332 0333 for(auto i = pixels.begin(); i != pixels.end(); ++i) { 0334 i->rx() -= left; 0335 i->ry() -= top; 0336 } 0337 0338 right -= left; 0339 bottom -= top; 0340 top = left = 0; 0341 } 0342 0343 inline uint 0344 qHash(const VobSubInputProcessDialog::Piece &piece) 0345 { 0346 // ignore top and left since this is used on normalized pieces 0347 return 1000 * piece.right * piece.bottom + piece.pixels.length(); 0348 } 0349 0350 0351 0352 0353 // VobSubInputProcessDialog 0354 VobSubInputProcessDialog::VobSubInputProcessDialog(Subtitle *subtitle, QWidget *parent) : 0355 QDialog(parent), 0356 ui(new Ui::VobSubInputProcessDialog), 0357 m_subtitle(subtitle), 0358 m_recognizedPiecesMaxSymbolLength(0) 0359 { 0360 ui->setupUi(this); 0361 0362 connect(ui->btnOk, &QPushButton::clicked, this, &VobSubInputProcessDialog::onOkClicked); 0363 connect(ui->btnAbort, &QPushButton::clicked, this, &VobSubInputProcessDialog::onAbortClicked); 0364 0365 connect(ui->styleBold, &QPushButton::toggled, this, [this](bool checked){ 0366 QFont font = ui->lineEdit->font(); 0367 font.setBold(checked); 0368 ui->lineEdit->setFont(font); 0369 }); 0370 connect(ui->styleItalic, &QPushButton::toggled, this, [this](bool checked){ 0371 QFont font = ui->lineEdit->font(); 0372 font.setItalic(checked); 0373 ui->lineEdit->setFont(font); 0374 }); 0375 connect(ui->styleUnderline, &QPushButton::toggled, this, [this](bool checked){ 0376 QFont font = ui->lineEdit->font(); 0377 font.setUnderline(checked); 0378 ui->lineEdit->setFont(font); 0379 }); 0380 0381 connect(ui->symbolCount, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &VobSubInputProcessDialog::onSymbolCountChanged); 0382 0383 connect(ui->btnPrevSymbol, &QPushButton::clicked, this, &VobSubInputProcessDialog::onPrevSymbolClicked); 0384 connect(ui->btnNextSymbol, &QPushButton::clicked, this, &VobSubInputProcessDialog::onNextSymbolClicked); 0385 connect(ui->btnPrevImage, &QPushButton::clicked, this, &VobSubInputProcessDialog::onPrevImageClicked); 0386 connect(ui->btnNextImage, &QPushButton::clicked, this, &VobSubInputProcessDialog::onNextImageClicked); 0387 0388 ui->lineEdit->installEventFilter(this); 0389 ui->lineEdit->setFocus(); 0390 } 0391 0392 VobSubInputProcessDialog::~VobSubInputProcessDialog() 0393 { 0394 delete ui; 0395 } 0396 0397 bool 0398 VobSubInputProcessDialog::symFileOpen(const QString &filename) 0399 { 0400 QFile file(filename); 0401 if(!file.open(QIODevice::ReadOnly)) 0402 return false; 0403 0404 QTextStream stream(&file); 0405 if(stream.readLine() != QStringLiteral("SubtitleComposer Symbol Matrix v1.0")) 0406 return false; 0407 0408 m_recognizedPieces.clear(); 0409 m_recognizedPiecesMaxSymbolLength = 0; 0410 0411 RichString text; 0412 Piece piece; 0413 QString line; 0414 QChar ch; 0415 while(stream.readLineInto(&line)) { 0416 if(line.startsWith(QStringLiteral(".s "))) { 0417 text.setRichString(QStringView(line).mid(3).trimmed().toString()); 0418 } else if(line.startsWith(QStringLiteral(".d "))) { 0419 QTextStream data(QStringView(line).mid(3).trimmed().toUtf8()); 0420 0421 // read piece data 0422 data >> piece.right; 0423 do { data >> ch; } while(ch != QLatin1Char(',')); 0424 data >> piece.bottom; 0425 do { data >> ch; } while(ch != QLatin1Char(',')); 0426 data >> piece.symbolCount; 0427 0428 // skip to point data 0429 do { data >> ch; } while(ch != QLatin1Char(':')); 0430 data.skipWhiteSpace(); 0431 0432 // read point data 0433 piece.pixels.clear(); 0434 const QByteArray pixelData(qUncompress(QByteArray::fromBase64(data.readAll().toUtf8(), QByteArray::Base64Encoding | QByteArray::OmitTrailingEquals))); 0435 QDataStream pixelDataStream(pixelData); 0436 while(!pixelDataStream.atEnd()) { 0437 int x, y; 0438 pixelDataStream >> x >> y; 0439 piece.pixels.append(QPoint(x, y)); 0440 } 0441 0442 // save piece 0443 if(piece.symbolCount > m_recognizedPiecesMaxSymbolLength) 0444 m_recognizedPiecesMaxSymbolLength = piece.symbolCount; 0445 m_recognizedPieces[piece] = text; 0446 0447 text.clear(); 0448 } 0449 } 0450 0451 file.close(); 0452 0453 return true; 0454 } 0455 0456 bool 0457 VobSubInputProcessDialog::symFileSave(const QString &filename) 0458 { 0459 QSaveFile file(filename); 0460 if(!file.open(QIODevice::WriteOnly)) 0461 return false; 0462 0463 QTextStream stream(&file); 0464 stream << QStringLiteral("SubtitleComposer Symbol Matrix v1.0\n"); 0465 for(auto i = m_recognizedPieces.cbegin(); i != m_recognizedPieces.cend(); ++i) { 0466 if(!i.value().length()) 0467 continue; 0468 Piece piece = i.key(); 0469 stream << "\n.s " << i.value().richString(); 0470 stream << QString::asprintf("\n.d %d, %d, %d: ", i.key().right, i.key().bottom, i.key().symbolCount); 0471 QByteArray pixelData; 0472 QDataStream pixelDataStream(&pixelData, QIODevice::WriteOnly); 0473 foreach(QPoint p, i.key().pixels) 0474 pixelDataStream << p.x() << p.y(); 0475 stream << qCompress(pixelData).toBase64(QByteArray::Base64Encoding | QByteArray::OmitTrailingEquals) << '\n'; 0476 } 0477 return file.commit(); 0478 } 0479 0480 /*virtual*/ bool 0481 VobSubInputProcessDialog::eventFilter(QObject *obj, QEvent *event) /*override*/ 0482 { 0483 if(event->type() == QEvent::FocusOut) { 0484 ui->lineEdit->setFocus(); 0485 return true; 0486 } 0487 0488 if(event->type() == QEvent::KeyPress) { 0489 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event); 0490 switch(keyEvent->key()) { 0491 case Qt::Key_Up: 0492 ui->symbolCount->setValue(ui->symbolCount->value() + ui->symbolCount->singleStep()); 0493 return true; 0494 0495 case Qt::Key_Down: 0496 ui->symbolCount->setValue(ui->symbolCount->value() - ui->symbolCount->singleStep()); 0497 return true; 0498 0499 case Qt::Key_Left: 0500 if((keyEvent->modifiers() & Qt::ControlModifier) == 0) 0501 break; 0502 if((keyEvent->modifiers() & Qt::ShiftModifier) == 0) 0503 QMetaObject::invokeMethod(this, "onPrevSymbolClicked", Qt::QueuedConnection); 0504 else 0505 QMetaObject::invokeMethod(this, "onPrevImageClicked", Qt::QueuedConnection); 0506 return true; 0507 0508 case Qt::Key_Right: 0509 if((keyEvent->modifiers() & Qt::ControlModifier) == 0) 0510 break; 0511 if((keyEvent->modifiers() & Qt::ShiftModifier) == 0) 0512 QMetaObject::invokeMethod(this, "onNextSymbolClicked", Qt::QueuedConnection); 0513 else 0514 QMetaObject::invokeMethod(this, "onNextImageClicked", Qt::QueuedConnection); 0515 return true; 0516 0517 case Qt::Key_Space: 0518 case Qt::Key_Escape: 0519 return true; 0520 0521 case Qt::Key_B: 0522 if((keyEvent->modifiers() & Qt::ControlModifier) == 0) 0523 break; 0524 ui->styleBold->toggle(); 0525 return true; 0526 0527 case Qt::Key_I: 0528 if((keyEvent->modifiers() & Qt::ControlModifier) == 0) 0529 break; 0530 ui->styleItalic->toggle(); 0531 return true; 0532 0533 case Qt::Key_U: 0534 if((keyEvent->modifiers() & Qt::ControlModifier) == 0) 0535 break; 0536 ui->styleUnderline->toggle(); 0537 return true; 0538 } 0539 } 0540 0541 return QDialog::eventFilter(obj, event); 0542 } 0543 0544 void 0545 VobSubInputProcessDialog::processFrames(StreamProcessor *streamProcessor) 0546 { 0547 connect(streamProcessor, &StreamProcessor::streamError, this, &VobSubInputProcessDialog::onStreamError); 0548 connect(streamProcessor, &StreamProcessor::streamFinished, this, &VobSubInputProcessDialog::onStreamFinished); 0549 connect(streamProcessor, &StreamProcessor::imageDataAvailable, this, &VobSubInputProcessDialog::onStreamData, Qt::BlockingQueuedConnection); 0550 0551 streamProcessor->start(); 0552 0553 Frame::spaceStats.clear(); 0554 0555 ui->progressBar->setMinimum(0); 0556 ui->progressBar->setValue(0); 0557 0558 ui->grpText->setDisabled(true); 0559 ui->grpNavButtons->setDisabled(true); 0560 } 0561 0562 void 0563 VobSubInputProcessDialog::onStreamData(const QImage &image, quint64 msecStart, quint64 msecDuration) 0564 { 0565 FramePtr frame(new Frame()); 0566 frame->subShowTime.setMillisTime(double(msecStart)); 0567 frame->subHideTime.setMillisTime(double(msecStart + msecDuration)); 0568 frame->subImage = image; 0569 0570 ui->subtitleView->setPixmap(QPixmap::fromImage(frame->subImage)); 0571 QCoreApplication::processEvents(); 0572 0573 if(frame->processPieces()) { 0574 frame->index = m_frames.length(); 0575 ui->progressBar->setMaximum(m_frames.length()); 0576 m_frames.append(frame); 0577 } 0578 } 0579 0580 void 0581 VobSubInputProcessDialog::onStreamError(int /*code*/, const QString &message, const QString &debug) 0582 { 0583 QString text = message % QStringLiteral("\n") % debug; 0584 KMessageBox::error(this, text, i18n("VobSub Error")); 0585 } 0586 0587 void 0588 VobSubInputProcessDialog::onStreamFinished() 0589 { 0590 m_frameCurrent = m_frames.begin() - 1; 0591 0592 // average word length in english is 5.1 chars 0593 const double avgWordLength = 4; 0594 0595 if(!Frame::spaceStats.empty()) { 0596 auto itChar = Frame::spaceStats.begin(); // shorter spaces on start 0597 auto itWord = std::prev(Frame::spaceStats.end()); // longer spaces near end 0598 qint64 charSpacingSum = itChar.key() * itChar.value(); 0599 quint64 charSpacingCount = itChar.value(); 0600 qint64 wordSpacingSum = itWord.key() * itWord.value(); 0601 quint64 wordSpacingCount = itWord.value(); 0602 0603 while(itChar != itWord) { 0604 if(charSpacingCount < avgWordLength * wordSpacingCount) { 0605 // sum up chars 0606 ++itChar; 0607 charSpacingSum += itChar.key() * itChar.value(); 0608 charSpacingCount += itChar.value(); 0609 } else { 0610 // sum up words 0611 --itWord; 0612 wordSpacingSum += itWord.key() * itWord.value(); 0613 wordSpacingCount += itWord.value(); 0614 } 0615 } 0616 m_spaceWidth = wordSpacingSum / wordSpacingCount; 0617 } else { 0618 m_spaceWidth = 100; 0619 } 0620 0621 ui->grpText->setDisabled(true); 0622 ui->grpNavButtons->setDisabled(true); 0623 QMetaObject::invokeMethod(this, "processNextImage", Qt::QueuedConnection); 0624 } 0625 0626 void 0627 VobSubInputProcessDialog::processNextImage() 0628 { 0629 if(++m_frameCurrent == m_frames.end()) { 0630 accept(); 0631 return; 0632 } 0633 0634 ui->progressBar->setValue((*m_frameCurrent)->index + 1); 0635 0636 ui->subtitleView->setPixmap(QPixmap::fromImage((*m_frameCurrent)->subImage)); 0637 0638 m_pieces = (*m_frameCurrent)->pieces; 0639 m_pieceCurrent = m_pieces.begin(); 0640 0641 recognizePiece(); 0642 } 0643 0644 void 0645 VobSubInputProcessDialog::processCurrentPiece() 0646 { 0647 if(m_pieceCurrent == m_pieces.end()) 0648 return; 0649 0650 ui->grpText->setDisabled(false); 0651 ui->grpNavButtons->setDisabled(false); 0652 0653 QPixmap pixmap = QPixmap::fromImage((*m_frameCurrent)->subImage); 0654 QPainter p(&pixmap); 0655 0656 QList<PiecePtr>::iterator i = m_pieceCurrent; 0657 p.setPen(QColor(255, 255, 255, 64)); 0658 p.drawLine(0, (*i)->line->baseline, pixmap.width(), (*i)->line->baseline); 0659 0660 p.setPen(QColor(255, 0, 0, 200)); 0661 QRect rcVisible(QPoint((*i)->left, (*i)->top), QPoint((*i)->right, (*i)->bottom)); 0662 int n = (*i)->symbolCount; 0663 for(; n-- && i != m_pieces.end(); ++i) { 0664 rcVisible |= QRect(QPoint((*i)->left, (*i)->top), QPoint((*i)->right, (*i)->bottom)); 0665 foreach(QPoint pix, (*i)->pixels) 0666 p.drawPoint(pix); 0667 } 0668 rcVisible.adjust((ui->subtitleView->minimumWidth() - rcVisible.width()) / -2, (ui->subtitleView->minimumHeight() - rcVisible.height()) / -2, 0, 0); 0669 rcVisible.setBottomRight(QPoint(pixmap.width(), pixmap.height())); 0670 ui->subtitleView->setPixmap(pixmap.copy(rcVisible)); 0671 0672 ui->lineEdit->setFocus(); 0673 0674 ui->symbolCount->setMaximum(m_pieces.end() - m_pieceCurrent); 0675 } 0676 0677 void 0678 VobSubInputProcessDialog::processNextPiece() 0679 { 0680 m_pieceCurrent += (*m_pieceCurrent)->symbolCount; 0681 0682 ui->lineEdit->clear(); 0683 ui->symbolCount->setValue(1); 0684 0685 if(m_pieceCurrent == m_pieces.end()) { 0686 RichString subText; 0687 PiecePtr piecePrev; 0688 foreach(PiecePtr piece, m_pieces) { 0689 if(piecePrev) { 0690 if(!piecePrev->line->intersects(piece->line)) 0691 subText.append(QChar(QChar::LineFeed)); 0692 else if(piece->left - piecePrev->right > m_spaceWidth) 0693 subText.append(QChar(QChar::Space)); 0694 } 0695 0696 subText += piece->text; 0697 piecePrev = piece; 0698 } 0699 0700 SubtitleLine *l = m_subtitle->line((*m_frameCurrent)->index); 0701 if(!l) { 0702 l = new SubtitleLine((*m_frameCurrent)->subShowTime, (*m_frameCurrent)->subHideTime); 0703 m_subtitle->insertLine(l); 0704 } 0705 l->primaryDoc()->setRichText(subText); 0706 0707 ui->grpText->setDisabled(true); 0708 ui->grpNavButtons->setDisabled(true); 0709 QMetaObject::invokeMethod(this, "processNextImage", Qt::QueuedConnection); 0710 return; 0711 } 0712 0713 recognizePiece(); 0714 } 0715 0716 void 0717 VobSubInputProcessDialog::recognizePiece() 0718 { 0719 for(int len = m_recognizedPiecesMaxSymbolLength; len > 0; len--) { 0720 PiecePtr normal = currentNormalizedPiece(len); 0721 if(len != normal->symbolCount) 0722 continue; 0723 if(m_recognizedPieces.contains(*normal)) { 0724 const RichString text = m_recognizedPieces.value(*normal); 0725 (*m_pieceCurrent)->text = text; 0726 currentSymbolCountSet(len); 0727 processNextPiece(); 0728 return; 0729 } 0730 } 0731 0732 processCurrentPiece(); 0733 } 0734 0735 RichString 0736 VobSubInputProcessDialog::currentText() 0737 { 0738 int style = 0; 0739 if(ui->styleBold->isChecked()) 0740 style |= RichString::Bold; 0741 if(ui->styleItalic->isChecked()) 0742 style |= RichString::Italic; 0743 if(ui->styleUnderline->isChecked()) 0744 style |= RichString::Underline; 0745 return RichString(ui->lineEdit->text(), style); 0746 } 0747 0748 VobSubInputProcessDialog::PiecePtr 0749 VobSubInputProcessDialog::currentNormalizedPiece(int symbolCount) 0750 { 0751 PiecePtr normal(new Piece(**m_pieceCurrent)); 0752 normal->symbolCount = 1; 0753 for(auto piece = m_pieceCurrent; --symbolCount && ++piece != m_pieces.end(); ) { 0754 *normal += **piece; 0755 normal->symbolCount++; 0756 } 0757 0758 normal->normalize(); 0759 0760 return normal; 0761 } 0762 0763 void 0764 VobSubInputProcessDialog::currentSymbolCountSet(int symbolCount) 0765 { 0766 if(m_pieceCurrent == m_pieces.end()) 0767 return; 0768 0769 int n = (*m_pieceCurrent)->symbolCount; 0770 QList<PiecePtr>::iterator piece = m_pieceCurrent; 0771 while(--n && ++piece != m_pieces.end()) 0772 (*piece)->symbolCount = 1; 0773 0774 piece = m_pieceCurrent; 0775 (*piece)->symbolCount = symbolCount; 0776 while(--symbolCount && ++piece != m_pieces.end()) 0777 (*piece)->symbolCount = 0; 0778 } 0779 0780 void 0781 VobSubInputProcessDialog::onSymbolCountChanged(int symbolCount) 0782 { 0783 currentSymbolCountSet(symbolCount); 0784 0785 processCurrentPiece(); 0786 } 0787 0788 void 0789 VobSubInputProcessDialog::onOkClicked() 0790 { 0791 if((*m_pieceCurrent)->symbolCount > m_recognizedPiecesMaxSymbolLength) 0792 m_recognizedPiecesMaxSymbolLength = (*m_pieceCurrent)->symbolCount; 0793 0794 (*m_pieceCurrent)->text = currentText(); 0795 0796 PiecePtr normal = currentNormalizedPiece((*m_pieceCurrent)->symbolCount); 0797 m_recognizedPieces[*normal] = (*m_pieceCurrent)->text; 0798 0799 processNextPiece(); 0800 } 0801 0802 void 0803 VobSubInputProcessDialog::onAbortClicked() 0804 { 0805 reject(); 0806 } 0807 0808 void 0809 VobSubInputProcessDialog::onPrevImageClicked() 0810 { 0811 if(m_frameCurrent == m_frames.begin()) 0812 return; 0813 0814 --m_frameCurrent; 0815 0816 ui->progressBar->setValue((*m_frameCurrent)->index + 1); 0817 0818 m_pieces = (*m_frameCurrent)->pieces; 0819 m_pieceCurrent = m_pieces.end(); 0820 0821 onPrevSymbolClicked(); 0822 } 0823 0824 void 0825 VobSubInputProcessDialog::onNextImageClicked() 0826 { 0827 if(m_frameCurrent == m_frames.end() - 1) 0828 return; 0829 0830 ++m_frameCurrent; 0831 0832 ui->progressBar->setValue((*m_frameCurrent)->index + 1); 0833 0834 m_pieces = (*m_frameCurrent)->pieces; 0835 m_pieceCurrent = m_pieces.begin() - 1; 0836 0837 onNextSymbolClicked(); 0838 } 0839 0840 void 0841 VobSubInputProcessDialog::onPrevSymbolClicked() 0842 { 0843 do { 0844 if(m_pieceCurrent == m_pieces.begin()) 0845 return onPrevImageClicked(); 0846 --m_pieceCurrent; 0847 } while((*m_pieceCurrent)->symbolCount == 0); 0848 0849 updateCurrentPiece(); 0850 } 0851 0852 void 0853 VobSubInputProcessDialog::onNextSymbolClicked() 0854 { 0855 do { 0856 if(m_pieceCurrent >= m_pieces.end() - 1) 0857 return onNextImageClicked(); 0858 ++m_pieceCurrent; 0859 } while((*m_pieceCurrent)->symbolCount == 0); 0860 0861 updateCurrentPiece(); 0862 } 0863 0864 void 0865 VobSubInputProcessDialog::updateCurrentPiece() 0866 { 0867 processCurrentPiece(); 0868 0869 ui->lineEdit->setText((*m_pieceCurrent)->text.string()); 0870 ui->lineEdit->selectAll(); 0871 0872 int style = (*m_pieceCurrent)->text.styleFlagsAt(0); 0873 ui->styleBold->setChecked((style & RichString::Bold) != 0); 0874 ui->styleItalic->setChecked((style & RichString::Italic) != 0); 0875 ui->styleUnderline->setChecked((style & RichString::Underline) != 0); 0876 0877 ui->symbolCount->setValue((*m_pieceCurrent)->symbolCount); 0878 }