3 #include <QFontMetrics>
4 #include <QRegularExpression>
15 #define LOC QString("Subtitles: ")
16 #define LOC_WARN QString("Subtitles Warning: ")
18 #ifdef DEBUG_SUBTITLES
21 std::chrono::milliseconds time = frame->
m_timecode;
22 return QString(
"%1(%2)")
41 std::chrono::milliseconds expireTime,
42 int whichImageCache = -1) :
63 QRect rect, Qt::Alignment align,
65 int whichImageCache, std::chrono::milliseconds expireTime) :
74 int whichImageCache, std::chrono::milliseconds expireTime) :
76 SubWrapper(area, expireTime, whichImageCache) {}
83 std::chrono::milliseconds expireTime) :
151 int pixelSize,
int zoom,
int stretch);
153 const QString &family,
157 std::chrono::milliseconds start,
158 std::chrono::milliseconds duration);
160 static QString
MakePrefix(
const QString &family,
163 void Load(
const QString &family,
176 static QSet<QString>
Diff(
const QString &family,
221 return QString(
"#%1%2%3")
222 .arg(color.red(), 2, 16, QLatin1Char(
'0'))
223 .arg(color.green(), 2, 16, QLatin1Char(
'0'))
224 .arg(color.blue(), 2, 16, QLatin1Char(
'0'));
230 result = QString(
"face=%1 pixelsize=%2 color=%3 "
231 "italics=%4 weight=%5 underline=%6")
233 .arg(f->
GetFace()->pixelSize())
235 .arg(
static_cast<int>(f->
GetFace()->italic()))
237 .arg(
static_cast<int>(f->
GetFace()->underline()));
243 result += QString(
" shadow=%1 shadowoffset=%2 "
244 "shadowcolor=%3 shadowalpha=%4")
245 .arg(QString::number(
static_cast<int>(f->
hasShadow())),
246 QString(
"(%1,%2)").arg(offset.x()).arg(offset.y()),
248 QString::number(alpha));
250 result += QString(
" outline=%1 outlinecolor=%2 "
251 "outlinesize=%3 outlinealpha=%4")
262 int pixelSize,
int zoom,
int stretch)
264 int origPixelSize = pixelSize;
265 float scale = zoom / 100.0;
267 scale = scale * 32 / 42;
269 scale = scale * 42 / 32;
281 result->
GetFace()->setPixelSize(pixelSize);
283 result->
GetFace()->setStretch(stretch);
304 int off = lroundf(scale * pixelSize / 20);
305 offset = QPoint(off, off);
324 offset.
setX(lroundf(offset.x() * scale));
325 offset.
setY(lroundf(offset.y() * scale));
327 result->
SetShadow(shadow, offset, color, alpha);
343 off = lroundf(scale * pixelSize / 20);
355 off = lroundf(point.x() * scale);
357 result->
SetOutline(outline, color, off, alpha);
359 LOG(VB_VBI, LOG_DEBUG,
360 QString(
"GetFont(family=%1, prefix=%2, orig pixelSize=%3, "
361 "new pixelSize=%4 zoom=%5) = %6")
362 .arg(family,
prefix).arg(origPixelSize).arg(pixelSize)
370 for (
int i = 0; i <
m_cleanup.size(); ++i)
382 return family +
"_" + QString::number(attr.
m_fontTag & 0x7);
397 font->GetFace()->setFamily(
"FreeMono");
398 QBrush brush(Qt::black);
399 bg->SetFillBrush(brush);
400 bg->SetLinePen(QPen(brush, 0));
404 static const std::array<const std::string,8> s_cc708Fonts {
414 font->GetFace()->setFamily(QString::fromStdString(s_cc708Fonts[attr.
m_fontTag & 0x7]));
418 font->GetFace()->setFamily(
"Droid Sans");
419 QBrush brush(Qt::black);
420 bg->SetFillBrush(brush);
421 bg->SetLinePen(QPen(brush, 0));
425 font->GetFace()->setFamily(
"FreeMono");
428 QBrush brush(Qt::black);
429 bg->SetFillBrush(brush);
430 bg->SetLinePen(QPen(brush, 0));
432 font->GetFace()->setPixelSize(10);
444 return color == Qt::white ? Qt::black : Qt::white;
456 face->setItalic(!face->italic());
457 face->setPixelSize(face->pixelSize() + 1);
458 face->setUnderline(!face->underline());
459 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
460 face->setWeight((face->weight() + 1) % 32);
467 offset.setX(offset.x() + 1);
473 size + 1, 255 - alpha);
476 Qt::SolidPattern : Qt::NoBrush);
483 auto *baseParent =
new MythUIType(
nullptr,
"base");
488 &providerBaseFont, &providerBaseShape);
491 auto *negParent =
new MythUIType(
nullptr,
"base");
503 if (!posResult || !negResult)
504 LOG(VB_VBI, LOG_INFO,
505 QString(
"Couldn't load theme file %1").arg(
kSubFileName));
509 resultFont = providerBaseFont;
515 resultBG = providerBaseShape;
525 resultFont->
GetFace()->setCapitalization(QFont::SmallCaps);
528 LOG(VB_VBI, LOG_DEBUG,
529 QString(
"providerBaseFont = %1").arg(
fontToString(providerBaseFont)));
530 LOG(VB_VBI, LOG_DEBUG,
532 LOG(VB_VBI, LOG_DEBUG,
533 QString(
"resultFont = %1").arg(
fontToString(resultFont)));
534 LOG(VB_VBI, LOG_DEBUG,
542 resultFont->
GetShadow(offset, color, alpha);
559 QSet<QString> result;
560 QFont *face1 = font1->
GetFace();
561 QFont *face2 = font2->
GetFace();
562 if (face1->italic() != face2->italic())
564 if (face1->weight() != face2->weight())
566 if (face1->underline() != face2->underline())
568 if (face1->pixelSize() != face2->pixelSize())
581 font1->
GetShadow(offset1, color1, alpha1);
582 font2->
GetShadow(offset2, color2, alpha2);
583 if (offset1 != offset2)
585 if (color1 != color2)
587 if (alpha1 != alpha2)
601 if (color1 != color2)
605 if (alpha1 != alpha2)
612 for (
auto i = result.constBegin(); i != result.constEnd(); ++i)
613 values +=
" " + (*i);
614 LOG(VB_VBI, LOG_INFO,
615 QString(
"Subtitle family %1 allows provider to change:%2")
623 const QString &family,
627 std::chrono::milliseconds start,
628 std::chrono::milliseconds duration)
635 auto *result =
new SubShape(parent, name, area, whichImageCache,
643 result->SetFillBrush(brush);
644 result->SetLinePen(QPen(brush, 0));
651 LOG(VB_VBI, LOG_DEBUG,
652 QString(
"GetBackground(prefix=%1) = "
653 "{type=%2 alpha=%3 brushstyle=%4 brushcolor=%5}")
654 .arg(
prefix, result->m_type, QString::number(result->GetAlpha()),
655 QString::number(result->m_fillBrush.style()),
678 int &left,
int &right)
const
685 LOG(VB_VBI, LOG_INFO,
686 QString(
"Attempting to split chunk '%1'").arg(
m_text));
687 int lastSpace =
m_text.lastIndexOf(
' ', -2);
690 LOG(VB_VBI, LOG_INFO,
691 QString(
"Failed to split chunk '%1'").arg(
m_text));
696 newChunk.
m_text =
m_text.mid(lastSpace + 1).trimmed() +
' ';
698 LOG(VB_VBI, LOG_INFO,
699 QString(
"Split chunk into '%1' + '%2'").arg(
m_text, newChunk.
m_text));
706 str += QString(
"fg=%1.%2 ")
709 str += QString(
"bg=%1.%2 ")
712 str += QString(
"edge=%1.%2 ")
715 str += QString(
"off=%1 pensize=%2 ")
718 str += QString(
"it=%1 ul=%2 bf=%3 ")
723 str += QString(
" text='%1'").arg(
m_text);
728 int &x,
int y,
int height)
739 while (count <
m_text.length() &&
m_text.at(count) ==
' ')
743 int x_adjust = count * font.horizontalAdvance(
" ");
745 int rightPadding = 0;
746 CalcPadding(isFirst, isLast, leftPadding, rightPadding);
751 QRect bgrect(x - leftPadding, y,
752 chunk_sz.width() + leftPadding + rightPadding,
756 bgrect.setLeft(bgrect.left() + x_adjust);
758 .arg(chunk_sz.width()).arg(height).arg(x).arg(y);
764 .arg(chunk_sz.width()).arg(height).arg(x).arg(y);
766 chunk_sz.width() - x_adjust + rightPadding, height);
768 x += chunk_sz.width();
777 int rightPadding = 0;
778 QList<FormattedTextChunk>::const_iterator it;
780 for (it =
chunks.constBegin(); it !=
chunks.constEnd(); ++it)
782 bool isLast = (it + 1 ==
chunks.constEnd());
783 QSize
tmp = (*it).CalcSize(layoutSpacing);
784 height = std::max(height,
tmp.height());
785 width +=
tmp.width();
786 (*it).CalcPadding(isFirst, isLast, leftPadding, rightPadding);
787 if (it ==
chunks.constBegin())
788 width += leftPadding;
791 return {width + rightPadding, height};
808 int anchor_width = 0;
809 int anchor_height = 0;
810 for (
const auto & line : std::as_const(
m_lines))
813 anchor_width = std::max(anchor_width, sz.width());
814 anchor_height += sz.height();
821 anchor_x -= anchor_width / 2;
823 anchor_x -= anchor_width;
825 anchor_y -= anchor_height / 2;
827 anchor_y -= anchor_height;
833 m_bounds = QRect(anchor_x, anchor_y, anchor_width, anchor_height);
838 for (
int i = 0; i <
m_lines.size(); i++)
841 m_lines[i].m_xIndent = anchor_x;
846 while (!
m_lines[i].chunks.isEmpty() &&
847 m_lines[i].chunks.first().m_text.trimmed().isEmpty())
850 m_lines[i].chunks.first().CalcSize().width();
851 m_lines[i].chunks.removeFirst();
854 while (!
m_lines[i].chunks.isEmpty() &&
855 m_lines[i].chunks.last().m_text.trimmed().isEmpty())
857 m_lines[i].chunks.removeLast();
862 if (!
m_lines[i].chunks.isEmpty())
864 QString *str = &
m_lines[i].chunks.last().m_text;
865 int idx = str->length() - 1;
866 while (idx >= 0 && str->at(idx) ==
' ')
868 str->truncate(idx + 1);
876 for (
int i = 0; i <
m_lines.size(); i++)
880 int height =
m_lines[i].CalcSize().height();
881 QList<FormattedTextChunk>::iterator chunk;
883 for (chunk =
m_lines[i].chunks.begin();
884 chunk !=
m_lines[i].chunks.end();
887 bool isLast = (chunk + 1 ==
m_lines[i].chunks.end());
888 (*chunk).PreRender(isFirst, isLast, x, y, height);
899 QList<MythUIType *> textList;
900 QList<MythUIType *> shapeList;
903 QList<FormattedTextChunk>::const_iterator chunk;
904 for (chunk = line.chunks.constBegin();
905 chunk != line.chunks.constEnd();
917 if ((*chunk).m_textRect.width() > 0) {
920 Qt::AlignLeft|Qt::AlignTop,
926 if ((*chunk).m_bgShapeRect.width() > 0) {
928 GetBackground(
nullptr,
929 (*chunk).m_bgShapeName,
930 m_base, (*chunk).m_format,
936 shapeList += bgshape;
941 while (!shapeList.isEmpty())
943 while (!textList.isEmpty())
950 for (
const auto & ftl : std::as_const(
m_lines))
954 line.fill(
' ', ftl.m_origX);
955 QList<FormattedTextChunk>::const_iterator chunk;
956 for (chunk = ftl.chunks.constBegin();
957 chunk != ftl.chunks.constEnd();
960 const QString &text = (*chunk).m_text;
972 line += QString(
"<font color=\"%1\">")
979 line += QString(
"</font>");
988 if (!line.trimmed().isEmpty())
1018 bool isItalic =
false;
1019 bool isBold =
false;
1020 bool isUnderline =
false;
1021 QColor color(Qt::white);
1022 static const QRegularExpression htmlTag {
1023 "</?.+>", QRegularExpression::InvertedGreedinessOption };
1024 QString htmlPrefix(
"<font color=\"");
1025 QString htmlSuffix(
"\">");
1026 for (
const QString& subtitle : std::as_const(subs))
1029 QString text(subtitle);
1030 while (!text.isEmpty())
1032 auto match = htmlTag.match(text);
1033 int pos = match.capturedStart();
1040 text = (pos < 0 ?
"" : text.mid(pos));
1041 LOG(VB_VBI, LOG_INFO, QString(
"Adding SRT chunk: %1")
1046 int htmlLen = match.capturedLength();
1047 QString html = text.left(htmlLen).toLower();
1048 text = text.mid(htmlLen);
1051 else if (html ==
"</i>")
1053 else if (html.startsWith(htmlPrefix) &&
1054 html.endsWith(htmlSuffix))
1056 int colorLen = html.length() -
1057 (htmlPrefix.length() + htmlSuffix.length());
1058 QString colorString(
1059 html.mid(htmlPrefix.length(), colorLen));
1060 QColor newColor(colorString);
1061 if (newColor.isValid())
1067 LOG(VB_VBI, LOG_INFO,
1068 QString(
"Ignoring invalid SRT color specification: "
1069 "'%1'").arg(colorString));
1072 else if (html ==
"</font>")
1076 else if (html ==
"<b>")
1080 else if (html ==
"</b>")
1084 else if (html ==
"<u>")
1088 else if (html ==
"</u>")
1090 isUnderline =
false;
1094 LOG(VB_VBI, LOG_INFO,
1095 QString(
"Ignoring unknown SRT formatting: '%1'")
1099 LOG(VB_VBI, LOG_INFO,
1100 QString(
"SRT formatting change '%1', "
1101 "new ital=%2 bold=%3 uline=%4 color=%5)")
1102 .arg(html).arg(isItalic).arg(isBold).arg(isUnderline)
1113 for (
int i = 0; i <
m_lines.size(); i++)
1115 int width =
m_lines[i].CalcSize().width();
1118 while (width > maxWidth &&
m_lines[i].chunks.size() > 1)
1120 width -=
m_lines[i].chunks.back().CalcSize().width();
1126 LOG(VB_VBI, LOG_INFO,
1127 QString(
"Wrapping chunk to next line: '%1'")
1128 .arg(
m_lines[i+1].chunks[0].m_text));
1132 bool isSplitPossible =
true;
1133 while (width > maxWidth && isSplitPossible)
1136 isSplitPossible =
m_lines[i].chunks.back().Split(newChunk);
1137 if (isSplitPossible)
1143 m_lines[i+1].chunks.prepend(newChunk);
1144 width =
m_lines[i].CalcSize().width();
1156 bool &isItalic,
bool &isUnderline)
1161 if (text.length() >= 1 && text[0] >= QChar(0x7000))
1163 int op = text[0].unicode() - 0x7000;
1164 isUnderline = ((op & 0x1) != 0);
1180 color = (op & 0xf) >> 1;
1189 static const QRegularExpression kControlCharsRE {
"[\\x{7000}-\\x{7fff}]" };
1190 int nextControl = text.indexOf(kControlCharsRE);
1191 if (nextControl < 0)
1198 result = text.left(nextControl);
1199 text = text.mid(nextControl);
1211 int totalHeight = 0;
1215 QVector<int> heights(
m_lines.size());
1216 QVector<int> spaceBefore(
m_lines.size());
1218 for (
int i = 0; i <
m_lines.size(); i++)
1224 int height =
m_lines[i].CalcSize().height();
1225 heights[i] = height;
1226 spaceBefore[i] = y -
prevY;
1227 totalSpace += (y -
prevY);
1229 totalHeight += height;
1232 int overage = std::min(totalHeight - safeHeight, totalSpace);
1236 if (overage > 0 && totalSpace > 0)
1238 float shrink = (totalSpace - overage) / (
float)totalSpace;
1240 for (
int i = 0; i <
m_lines.size(); i++)
1242 m_lines[i].m_yIndent =
prevY + spaceBefore[i] * shrink;
1248 int shift = std::min(firstY, std::max(0,
prevY - safeHeight));
1250 for (
int i = 0; i <
m_lines.size(); i++)
1251 m_lines[i].m_yIndent -= shift;
1258 static const std::array<const QColor,8> kClr
1260 Qt::white, Qt::green, Qt::blue, Qt::cyan,
1261 Qt::red, Qt::yellow, Qt::magenta, Qt::white,
1279 QFontMetrics fm(*font);
1280 fontwidth = fm.averageCharWidth();
1287 for (; i !=
buffers.cend(); ++i)
1291 bool isItalic =
false;
1292 bool isUnderline =
false;
1293 const bool isBold =
false;
1294 QString text(
cc->m_text);
1296 int orig_x =
cc->m_x;
1304 x = xmid + (orig_x - xscale / 2) * fontwidth;
1309 x = (orig_x + 3) *
m_safeArea.width() / xscale;
1312 int orig_y =
cc->m_y;
1314 if (orig_y < yscale / 2)
1317 y = (orig_y *
m_safeArea.height() * zoom / (yscale * 100));
1323 ((yscale - orig_y - 0.5) *
m_safeArea.height() * zoom /
1328 while (!text.isNull())
1330 QString captionText =
1336 LOG(VB_VBI, LOG_INFO,
1337 QString(
"Adding cc608 chunk (%1,%2): %3")
1353 shape->SetFillBrush(fill);
1362 const std::vector<CC708String*> &list,
1365 LOG(VB_VBI, LOG_DEBUG,
LOC +
1366 QString(
"Display Win %1, Anchor_id %2, x_anch %3, y_anch %4, "
1374 float xrange { 160.0F };
1377 else if (aspect > 1.4F)
1380 float xmult = (float)
m_safeArea.width() / xrange;
1381 float ymult = (float)
m_safeArea.height() / yrange;
1389 for (
auto *str708 : list)
1392 m_lines.resize(str708->m_y + 1);
1394 m_lines[str708->m_y].chunks += chunk;
1395 LOG(VB_VBI, LOG_INFO, QString(
"Adding cc708 chunk: win %1 row %2: %3")
1403 const QString &
Name,
int FontStretch)
1406 m_fontStretch(FontStretch),
1413 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"SeekingDone signal received.");
1457 if (!forced_only ||
m_family.isEmpty()) {
1538 AVSubtitleRect *hl_button = dvdButton->rects[0];
1539 uint h = hl_button->h;
1540 uint w = hl_button->w;
1541 QRect rect = QRect(hl_button->x, hl_button->y, w, h);
1542 QImage bg_image(hl_button->data[0], w, h, w, QImage::Format_Indexed8);
1543 auto *bgpalette = (uint32_t *)(hl_button->data[1]);
1545 QVector<uint32_t> bg_palette(4);
1546 for (
int i = 0; i < 4; i++)
1547 bg_palette[i] = bgpalette[i];
1548 bg_image.setColorTable(bg_palette);
1551 const QRect fg_rect(buttonPos.translated(-hl_button->x, -hl_button->y));
1552 QImage fg_image = bg_image.copy(fg_rect);
1553 QVector<uint32_t> fg_palette(4);
1554 auto *fgpalette = (uint32_t *)(dvdButton->rects[1]->data[1]);
1557 for (
int i = 0; i < 4; i++)
1558 fg_palette[i] = fgpalette[i];
1559 fg_image.setColorTable(fg_palette);
1562 bg_image = bg_image.convertToFormat(QImage::Format_ARGB32);
1563 fg_image = fg_image.convertToFormat(QImage::Format_ARGB32);
1566 for (
int x=fg_rect.x(); x < fg_rect.x()+fg_rect.width(); ++x)
1568 if ((x < 0) || (x > hl_button->w))
1570 for (
int y=fg_rect.y(); y < fg_rect.y()+fg_rect.height(); ++y)
1572 if ((y < 0) || (y > hl_button->h))
1574 bg_image.setPixel(x, y, fg_image.pixel(x-fg_rect.x(),y-fg_rect.y()));
1608 QList<MythUIType *>::iterator it;
1609 for (it = list.begin(); it != list.end(); ++it)
1612 auto *wrapper =
dynamic_cast<SubWrapper *
>(child);
1616 if (whichImageCache != -1 && (mask & (1UL << whichImageCache)))
1656 int outlineSize = 0;
1657 int shadowWidth = 0;
1658 int shadowHeight = 0;
1661 mythfont->
GetOutline(color, outlineSize, alpha);
1662 MythPoint outline(outlineSize, outlineSize);
1664 outlineSize = outline.x();
1669 mythfont->
GetShadow(shadowOffset, color, alpha);
1671 shadowWidth = abs(shadowOffset.x());
1672 shadowHeight = abs(shadowOffset.y());
1674 shadowWidth = std::max(shadowWidth, outlineSize);
1675 shadowHeight = std::max(shadowHeight, outlineSize);
1677 return {shadowWidth + outlineSize, shadowHeight + outlineSize};
1682 float layoutSpacing)
const
1685 QFont *font = mythfont->
GetFace();
1686 QFontMetrics fm(*font);
1687 int width = fm.horizontalAdvance(text);
1689 if (layoutSpacing > 0 && !text.trimmed().isEmpty())
1690 height = std::max(height, (
int)(font->pixelSize() * layoutSpacing));
1692 return {width, height};
1699 bool isFirst,
bool isLast,
1700 int &left,
int &right)
const
1703 QFont *font = mythfont->
GetFace();
1704 QFontMetrics fm(*font);
1705 int basicPadding = fm.maxWidth() *
PAD_WIDTH;
1706 left = isFirst ? basicPadding : 0;
1708 (isLast ? basicPadding : 0);
1725 return mythfont->
face().family();
1743 LOG(VB_GENERAL, LOG_WARNING,
LOC +
"Failed to get subtitle reader.");
1745 LOG(VB_GENERAL, LOG_WARNING,
LOC +
"Failed to get CEA-608 reader.");
1747 LOG(VB_GENERAL, LOG_WARNING,
LOC +
"Failed to get CEA-708 reader.");
1754 QList<MythUIType *>::iterator it;
1755 QList<MythUIType *>::iterator itNext;
1759 std::chrono::milliseconds now =
1760 currentFrame ? currentFrame->
m_timecode : std::chrono::milliseconds::max();
1767 auto *wrapper =
dynamic_cast<SubWrapper *
>(child);
1772 std::chrono::milliseconds expireTime = wrapper->
GetExpireTime();
1773 if (expireTime > 0ms && expireTime < now)
1781 if (expireTime > 0ms && needRescale)
1783 auto *image =
dynamic_cast<SubImage *
>(child);
1787 QSize size = image->GetImage()->size();
1789 image->GetImage()->Resize(size);
1839 auto *wrapper =
dynamic_cast<SubWrapper *
>(img);
1841 visible = visible.united(wrapper->GetOrigArea());
1844 if (visible.isEmpty())
1847 QRect bounding = visible.boundingRect();
1848 bounding = bounding.translated(
m_safeArea.topLeft());
1850 int left =
m_safeArea.left() - bounding.left();
1858 auto *wrapper =
dynamic_cast<SubWrapper *
>(img);
1870 QMutexLocker lock(&(subs->
m_lock));
1880 if (!currentFrame || !videoOut)
1910 AVSEEK_FLAG_BACKWARD);
1915 #ifdef DEBUG_SUBTITLES
1918 LOG(VB_PLAYBACK, LOG_DEBUG,
1919 LOC + QString(
"time %1, no subtitle available after seek")
1930 AVSubtitle subtitle = subs->
m_buffers.front();
1931 if (subtitle.end_display_time < currentFrame->
m_timecode.count())
1934 #ifdef DEBUG_SUBTITLES
1937 LOG(VB_PLAYBACK, LOG_DEBUG,
1938 LOC + QString(
"time %1, drop %2")
1953 #ifdef DEBUG_SUBTITLES
1956 LOG(VB_PLAYBACK, LOG_DEBUG,
1957 LOC + QString(
"time %1, no subtitle available")
1970 [[maybe_unused]]
bool assForceNext {
false};
1973 AVSubtitle subtitle = subs->
m_buffers.front();
1974 if (subtitle.start_display_time > currentFrame->
m_timecode.count())
1976 #ifdef DEBUG_SUBTITLES
1979 LOG(VB_PLAYBACK, LOG_DEBUG,
1980 LOC + QString(
"time %1, next %2")
1992 assForceNext =
true;
1994 auto displayfor = std::chrono::milliseconds(subtitle.end_display_time -
1995 subtitle.start_display_time);
1996 #ifdef DEBUG_SUBTITLES
1999 LOG(VB_PLAYBACK, LOG_DEBUG,
2000 LOC + QString(
"time %1, show %2")
2004 if (displayfor == 0ms)
2006 displayfor = (displayfor < 50ms) ? 50ms : displayfor;
2007 std::chrono::milliseconds late = currentFrame->
m_timecode -
2008 std::chrono::milliseconds(subtitle.start_display_time);
2012 for (std::size_t i = 0; i < subtitle.num_rects; ++i)
2014 AVSubtitleRect* rect = subtitle.rects[i];
2016 bool displaysub =
true;
2018 subs->
m_buffers.front().end_display_time <
2024 if (displaysub && rect->type == SUBTITLE_BITMAP)
2026 QRect display(rect->display_x, rect->display_y,
2027 rect->display_w, rect->display_h);
2033 int right = rect->x + rect->w;
2034 int bottom = rect->y + rect->h;
2036 (currentFrame->
m_width < right) ||
2037 !display.width() || !display.height())
2039 int sd_height = 576;
2044 int height { 1080 };
2045 if ((currentFrame->
m_height <= sd_height) &&
2046 (bottom <= sd_height))
2048 else if ((currentFrame->
m_height <= 720) && bottom <= 720)
2052 if ((currentFrame->
m_width <= 720) && (right <= 720))
2054 else if ((currentFrame->
m_width <= 1280) &&
2057 display = QRect(0, 0, width, height);
2062 int uh = (display.height() / 2) - rect->y;
2063 std::chrono::milliseconds displayuntil = currentFrame->
m_timecode + displayfor;
2066 bbox = QRect(0, 0, rect->w, uh);
2068 rect->flags & AV_SUBTITLE_FLAG_FORCED,
2069 QString(
"avsub%1t").arg(i),
2070 displayuntil, late);
2076 int lh = rect->h - uh;
2079 bbox = QRect(0, uh, rect->w, lh);
2081 rect->flags & AV_SUBTITLE_FLAG_FORCED,
2082 QString(
"avsub%1b").arg(i),
2083 displayuntil, late);
2087 else if (displaysub && rect->type == SUBTITLE_ASS)
2090 AddAssEvent(rect->ass, subtitle.start_display_time, subtitle.end_display_time);
2102 QRect &bbox,
bool top,
2103 QRect &display,
int forced,
2104 const QString& imagename,
2105 std::chrono::milliseconds displayuntil,
2106 std::chrono::milliseconds late)
2111 bool prev_empty =
false;
2114 int xmin = bbox.right();
2115 int xmax = bbox.left();
2116 int ymin = bbox.bottom();
2117 int ymax = bbox.top();
2118 int ylast = bbox.top();
2119 int ysplit = bbox.bottom();
2122 for (
int y = bbox.top(); y <= bbox.bottom(); ++y)
2133 for (
int x = bbox.left(); x <= bbox.right(); ++x)
2135 const uint8_t color =
2136 rect->data[0][(y * rect->linesize[0]) + x];
2137 const uint32_t
pixel = *((uint32_t *)rect->data[1] + color);
2138 if (
pixel & 0xff000000)
2141 xmin = std::min(x, xmin);
2142 xmax = std::max(x, xmax);
2148 ymin = std::min(y, ymin);
2149 ymax = std::max(y, ymax);
2151 else if (!prev_empty)
2168 if (ymax == bbox.bottom())
2177 bbox.setRight(xmax);
2179 bbox.setBottom(ymax);
2185 QRect orig_rect(bbox.left(), bbox.top(), bbox.width(), bbox.height());
2187 QImage qImage(bbox.width(), bbox.height(), QImage::Format_ARGB32);
2188 for (
int y = 0; y < bbox.height(); ++y)
2190 int ysrc = y + bbox.top();
2191 for (
int x = 0; x < bbox.width(); ++x)
2193 int xsrc = x + bbox.left();
2194 const uint8_t color =
2195 rect->data[0][(ysrc * rect->linesize[0]) + xsrc];
2196 const uint32_t
pixel = *((uint32_t *)rect->data[1] + color);
2197 qImage.setPixel(x, y,
pixel);
2202 bbox.translate(rect->x, rect->y);
2211 if (scaled.size() != orig_rect.size())
2212 qImage = qImage.scaled(scaled.width(), scaled.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
2248 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"Display %1AV sub until %2ms")
2249 .arg(forced ?
"FORCED " :
"").arg(displayuntil.count()));
2251 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"AV Sub was %1ms late").arg(late.count()));
2254 return (ysplit + 1);
2262 std::chrono::milliseconds duration = 0ms;
2284 std::chrono::milliseconds start,
2285 std::chrono::milliseconds duration)
2288 duration,
this, subs);
2313 QMutexLocker locker(&textlist->
m_lock);
2335 for (
auto & window : cc708service->
m_windows)
2336 window.SetChanged();
2339 uint64_t clearMask = 0;
2340 QList<FormattedTextSubtitle *> addList;
2349 clearMask |= (1UL << i);
2354 QMutexLocker locker(&win.
m_lock);
2355 std::vector<CC708String*> list = win.
GetStrings();
2360 addList.append(fsub);
2368 if (!addList.empty())
2379 if (scaled.size() != pos.size())
2381 img = img.scaled(scaled.width(), scaled.height(),
2382 Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
2403 uint64_t verbose_mask = VB_GENERAL;
2404 LogLevel_t verbose_level = LOG_INFO;
2409 verbose_level = LOG_EMERG;
2412 verbose_level = LOG_ERR;
2415 verbose_level = LOG_WARNING;
2418 verbose_level = LOG_INFO;
2422 verbose_level = LOG_DEBUG;
2431 static QMutex s_stringLock;
2432 s_stringLock.lock();
2434 QString str = QString::vasprintf(fmt, vl);
2435 LOG(verbose_mask, verbose_level, QString(
"libass: %1").arg(str));
2436 s_stringLock.unlock();
2451 ass_set_extract_fonts(
m_assLibrary,
static_cast<int>(
true));
2452 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"Initialised libass object.");
2467 const char *psz_font =
"/system/fonts/DroidSans.ttf";
2468 const char *psz_font_family =
"Droid Sans";
2470 const char *psz_font =
nullptr;
2471 const char *psz_font_family =
"sans-serif";
2473 ass_set_fonts(
m_assRenderer, psz_font, psz_font_family, 1,
nullptr, 1);
2475 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"Initialised libass renderer.");
2494 for (
uint i = 0; i < count; ++i)
2500 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"Retrieved font '%1'")
2537 if (header.isNull())
2541 header =
parser->GetSubHeader();
2543 if (!header.isNull())
2544 ass_process_codec_private(
m_assTrack, header.data(), header.size());
2560 ass_process_chunk(
m_assTrack, event, strlen(event), starttime, endtime-starttime);
2587 if (!changed && !
force)
2595 if (images->w == 0 || images->h == 0)
2597 images = images->next;
2601 uint8_t alpha = images->color & 0xFF;
2602 uint8_t blue = images->color >> 8 & 0xFF;
2603 uint8_t green = images->color >> 16 & 0xFF;
2604 uint8_t red = images->color >> 24 & 0xFF;
2608 images = images->next;
2612 QSize img_size(images->w, images->h);
2613 QRect img_rect(images->dst_x,images->dst_y,
2614 images->w, images->h);
2615 QImage qImage(img_size, QImage::Format_ARGB32);
2616 qImage.fill(0x00000000);
2618 unsigned char *src = images->bitmap;
2619 for (
int y = 0; y < images->h; ++y)
2621 for (
int x = 0; x < images->w; ++x)
2623 uint8_t value = src[x];
2626 uint32_t
pixel = (value * (255 - alpha) / 255 << 24) |
2627 (red << 16) | (green << 8) | blue;
2628 qImage.setPixel(x, y,
pixel);
2631 src += images->stride;
2640 QString name = QString(
"asssub%1").arg(count);
2651 images = images->next;
2655 #endif // USING_LIBASS