1 #include <QFontMetrics>
2 #include <QRegularExpression>
13 #define LOC QString("Subtitles: ")
14 #define LOC_WARN QString("Subtitles Warning: ")
16 #ifdef DEBUG_SUBTITLES
19 std::chrono::milliseconds time = frame->
m_timecode;
20 return QString(
"%1(%2)")
39 std::chrono::milliseconds expireTime,
40 int whichImageCache = -1) :
61 QRect rect, Qt::Alignment align,
63 int whichImageCache, std::chrono::milliseconds expireTime) :
72 int whichImageCache, std::chrono::milliseconds expireTime) :
74 SubWrapper(area, expireTime, whichImageCache) {}
81 std::chrono::milliseconds expireTime) :
149 int pixelSize,
int zoom,
int stretch);
151 const QString &family,
155 std::chrono::milliseconds start,
156 std::chrono::milliseconds duration);
158 static QString
MakePrefix(
const QString &family,
161 void Load(
const QString &family,
174 static QSet<QString>
Diff(
const QString &family,
219 return QString(
"#%1%2%3")
220 .arg(color.red(), 2, 16, QLatin1Char(
'0'))
221 .arg(color.green(), 2, 16, QLatin1Char(
'0'))
222 .arg(color.blue(), 2, 16, QLatin1Char(
'0'));
228 result = QString(
"face=%1 pixelsize=%2 color=%3 "
229 "italics=%4 weight=%5 underline=%6")
231 .arg(f->
GetFace()->pixelSize())
233 .arg(
static_cast<int>(f->
GetFace()->italic()))
235 .arg(
static_cast<int>(f->
GetFace()->underline()));
241 result += QString(
" shadow=%1 shadowoffset=%2 "
242 "shadowcolor=%3 shadowalpha=%4")
243 .arg(QString::number(
static_cast<int>(f->
hasShadow())),
244 QString(
"(%1,%2)").arg(offset.x()).arg(offset.y()),
246 QString::number(alpha));
248 result += QString(
" outline=%1 outlinecolor=%2 "
249 "outlinesize=%3 outlinealpha=%4")
260 int pixelSize,
int zoom,
int stretch)
262 int origPixelSize = pixelSize;
263 float scale = zoom / 100.0;
265 scale = scale * 32 / 42;
267 scale = scale * 42 / 32;
279 result->
GetFace()->setPixelSize(pixelSize);
281 result->
GetFace()->setStretch(stretch);
302 int off = lroundf(scale * pixelSize / 20);
303 offset = QPoint(off, off);
318 offset.
setX(lroundf(offset.x() * scale));
319 offset.
setY(lroundf(offset.y() * scale));
321 result->
SetShadow(shadow, offset, color, alpha);
337 off = lroundf(scale * pixelSize / 20);
347 off = lroundf(point.x() * scale);
349 result->
SetOutline(outline, color, off, alpha);
351 LOG(VB_VBI, LOG_DEBUG,
352 QString(
"GetFont(family=%1, prefix=%2, orig pixelSize=%3, "
353 "new pixelSize=%4 zoom=%5) = %6")
354 .arg(family,
prefix).arg(origPixelSize).arg(pixelSize)
362 for (
int i = 0; i <
m_cleanup.size(); ++i)
374 return family +
"_" + QString::number(attr.
m_fontTag & 0x7);
389 font->GetFace()->setFamily(
"FreeMono");
390 QBrush brush(Qt::black);
391 bg->SetFillBrush(brush);
392 bg->SetLinePen(QPen(brush, 0));
396 static const std::array<const std::string,8> s_cc708Fonts {
406 font->GetFace()->setFamily(QString::fromStdString(s_cc708Fonts[attr.
m_fontTag & 0x7]));
410 font->GetFace()->setFamily(
"Droid Sans");
411 QBrush brush(Qt::black);
412 bg->SetFillBrush(brush);
413 bg->SetLinePen(QPen(brush, 0));
417 font->GetFace()->setFamily(
"FreeMono");
420 QBrush brush(Qt::black);
421 bg->SetFillBrush(brush);
422 bg->SetLinePen(QPen(brush, 0));
424 font->GetFace()->setPixelSize(10);
436 return color == Qt::white ? Qt::black : Qt::white;
448 face->setItalic(!face->italic());
449 face->setPixelSize(face->pixelSize() + 1);
450 face->setUnderline(!face->underline());
451 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
452 face->setWeight((face->weight() + 1) % 32);
459 offset.setX(offset.x() + 1);
465 size + 1, 255 - alpha);
468 Qt::SolidPattern : Qt::NoBrush);
475 auto *baseParent =
new MythUIType(
nullptr,
"base");
480 &providerBaseFont, &providerBaseShape);
483 auto *negParent =
new MythUIType(
nullptr,
"base");
495 if (!posResult || !negResult)
496 LOG(VB_VBI, LOG_INFO,
497 QString(
"Couldn't load theme file %1").arg(
kSubFileName));
501 resultFont = providerBaseFont;
507 resultBG = providerBaseShape;
517 resultFont->
GetFace()->setCapitalization(QFont::SmallCaps);
520 LOG(VB_VBI, LOG_DEBUG,
521 QString(
"providerBaseFont = %1").arg(
fontToString(providerBaseFont)));
522 LOG(VB_VBI, LOG_DEBUG,
524 LOG(VB_VBI, LOG_DEBUG,
525 QString(
"resultFont = %1").arg(
fontToString(resultFont)));
526 LOG(VB_VBI, LOG_DEBUG,
534 resultFont->
GetShadow(offset, color, alpha);
551 QSet<QString> result;
552 QFont *face1 = font1->
GetFace();
553 QFont *face2 = font2->
GetFace();
554 if (face1->italic() != face2->italic())
556 if (face1->weight() != face2->weight())
558 if (face1->underline() != face2->underline())
560 if (face1->pixelSize() != face2->pixelSize())
573 font1->
GetShadow(offset1, color1, alpha1);
574 font2->
GetShadow(offset2, color2, alpha2);
575 if (offset1 != offset2)
577 if (color1 != color2)
579 if (alpha1 != alpha2)
593 if (color1 != color2)
597 if (alpha1 != alpha2)
604 for (
auto i = result.constBegin(); i != result.constEnd(); ++i)
605 values +=
" " + (*i);
606 LOG(VB_VBI, LOG_INFO,
607 QString(
"Subtitle family %1 allows provider to change:%2")
615 const QString &family,
619 std::chrono::milliseconds start,
620 std::chrono::milliseconds duration)
627 auto *result =
new SubShape(parent, name, area, whichImageCache,
635 result->SetFillBrush(brush);
636 result->SetLinePen(QPen(brush, 0));
643 LOG(VB_VBI, LOG_DEBUG,
644 QString(
"GetBackground(prefix=%1) = "
645 "{type=%2 alpha=%3 brushstyle=%4 brushcolor=%5}")
646 .arg(
prefix, result->m_type, QString::number(result->GetAlpha()),
647 QString::number(result->m_fillBrush.style()),
670 int &left,
int &right)
const
677 LOG(VB_VBI, LOG_INFO,
678 QString(
"Attempting to split chunk '%1'").arg(
m_text));
679 int lastSpace =
m_text.lastIndexOf(
' ', -2);
682 LOG(VB_VBI, LOG_INFO,
683 QString(
"Failed to split chunk '%1'").arg(
m_text));
688 newChunk.
m_text =
m_text.mid(lastSpace + 1).trimmed() +
' ';
690 LOG(VB_VBI, LOG_INFO,
691 QString(
"Split chunk into '%1' + '%2'").arg(
m_text, newChunk.
m_text));
698 str += QString(
"fg=%1.%2 ")
701 str += QString(
"bg=%1.%2 ")
704 str += QString(
"edge=%1.%2 ")
707 str += QString(
"off=%1 pensize=%2 ")
710 str += QString(
"it=%1 ul=%2 bf=%3 ")
715 str += QString(
" text='%1'").arg(
m_text);
720 int &x,
int y,
int height)
731 while (count <
m_text.length() &&
m_text.at(count) ==
' ')
735 int x_adjust = count * font.horizontalAdvance(
" ");
737 int rightPadding = 0;
738 CalcPadding(isFirst, isLast, leftPadding, rightPadding);
743 QRect bgrect(x - leftPadding, y,
744 chunk_sz.width() + leftPadding + rightPadding,
748 bgrect.setLeft(bgrect.left() + x_adjust);
750 .arg(chunk_sz.width()).arg(height).arg(x).arg(y);
756 .arg(chunk_sz.width()).arg(height).arg(x).arg(y);
758 chunk_sz.width() - x_adjust + rightPadding, height);
760 x += chunk_sz.width();
769 int rightPadding = 0;
770 QList<FormattedTextChunk>::const_iterator it;
772 for (it =
chunks.constBegin(); it !=
chunks.constEnd(); ++it)
774 bool isLast = (it + 1 ==
chunks.constEnd());
775 QSize
tmp = (*it).CalcSize(layoutSpacing);
776 height = std::max(height,
tmp.height());
777 width +=
tmp.width();
778 (*it).CalcPadding(isFirst, isLast, leftPadding, rightPadding);
779 if (it ==
chunks.constBegin())
780 width += leftPadding;
783 return {width + rightPadding, height};
800 int anchor_width = 0;
801 int anchor_height = 0;
802 for (
const auto & line : std::as_const(
m_lines))
805 anchor_width = std::max(anchor_width, sz.width());
806 anchor_height += sz.height();
813 anchor_x -= anchor_width / 2;
815 anchor_x -= anchor_width;
817 anchor_y -= anchor_height / 2;
819 anchor_y -= anchor_height;
822 anchor_y = std::max(0, std::min(anchor_y,
m_safeArea.height() - anchor_height));
823 anchor_x = std::max(0, std::min(anchor_x,
m_safeArea.width() - anchor_width));
825 m_bounds = QRect(anchor_x, anchor_y, anchor_width, anchor_height);
830 for (
int i = 0; i <
m_lines.size(); i++)
833 m_lines[i].m_xIndent = anchor_x;
838 while (!
m_lines[i].chunks.isEmpty() &&
839 m_lines[i].chunks.first().m_text.trimmed().isEmpty())
842 m_lines[i].chunks.first().CalcSize().width();
843 m_lines[i].chunks.removeFirst();
846 while (!
m_lines[i].chunks.isEmpty() &&
847 m_lines[i].chunks.last().m_text.trimmed().isEmpty())
849 m_lines[i].chunks.removeLast();
854 if (!
m_lines[i].chunks.isEmpty())
856 QString *str = &
m_lines[i].chunks.last().m_text;
857 int idx = str->length() - 1;
858 while (idx >= 0 && str->at(idx) ==
' ')
860 str->truncate(idx + 1);
868 for (
int i = 0; i <
m_lines.size(); i++)
872 int height =
m_lines[i].CalcSize().height();
873 QList<FormattedTextChunk>::iterator chunk;
875 for (chunk =
m_lines[i].chunks.begin();
876 chunk !=
m_lines[i].chunks.end();
879 bool isLast = (chunk + 1 ==
m_lines[i].chunks.end());
880 (*chunk).PreRender(isFirst, isLast, x, y, height);
891 QList<MythUIType *> textList;
892 QList<MythUIType *> shapeList;
895 QList<FormattedTextChunk>::const_iterator chunk;
896 for (chunk = line.chunks.constBegin();
897 chunk != line.chunks.constEnd();
909 if ((*chunk).m_textRect.width() > 0) {
912 Qt::AlignLeft|Qt::AlignTop,
918 if ((*chunk).m_bgShapeRect.width() > 0) {
920 GetBackground(
nullptr,
921 (*chunk).m_bgShapeName,
922 m_base, (*chunk).m_format,
928 shapeList += bgshape;
933 while (!shapeList.isEmpty())
935 while (!textList.isEmpty())
942 for (
const auto & ftl : std::as_const(
m_lines))
946 line.fill(
' ', ftl.m_origX);
947 QList<FormattedTextChunk>::const_iterator chunk;
948 for (chunk = ftl.chunks.constBegin();
949 chunk != ftl.chunks.constEnd();
952 const QString &text = (*chunk).m_text;
954 bool isBlank = !attr.
m_underline && text.trimmed().isEmpty();
964 line += QString(
"<font color=\"%1\">")
971 line += QString(
"</font>");
980 if (!line.trimmed().isEmpty())
1010 bool isItalic =
false;
1011 bool isBold =
false;
1012 bool isUnderline =
false;
1013 QColor color(Qt::white);
1014 static const QRegularExpression htmlTag {
1015 "</?.+>", QRegularExpression::InvertedGreedinessOption };
1016 QString htmlPrefix(
"<font color=\"");
1017 QString htmlSuffix(
"\">");
1018 for (
const QString& subtitle : std::as_const(subs))
1021 QString text(subtitle);
1022 while (!text.isEmpty())
1024 auto match = htmlTag.match(text);
1025 int pos = match.capturedStart();
1032 text = (pos < 0 ?
"" : text.mid(pos));
1033 LOG(VB_VBI, LOG_INFO, QString(
"Adding SRT chunk: %1")
1038 int htmlLen = match.capturedLength();
1039 QString html = text.left(htmlLen).toLower();
1040 text = text.mid(htmlLen);
1043 else if (html ==
"</i>")
1045 else if (html.startsWith(htmlPrefix) &&
1046 html.endsWith(htmlSuffix))
1048 int colorLen = html.length() -
1049 (htmlPrefix.length() + htmlSuffix.length());
1050 QString colorString(
1051 html.mid(htmlPrefix.length(), colorLen));
1052 QColor newColor(colorString);
1053 if (newColor.isValid())
1059 LOG(VB_VBI, LOG_INFO,
1060 QString(
"Ignoring invalid SRT color specification: "
1061 "'%1'").arg(colorString));
1064 else if (html ==
"</font>")
1066 else if (html ==
"<b>")
1068 else if (html ==
"</b>")
1070 else if (html ==
"<u>")
1072 else if (html ==
"</u>")
1073 isUnderline =
false;
1076 LOG(VB_VBI, LOG_INFO,
1077 QString(
"Ignoring unknown SRT formatting: '%1'")
1081 LOG(VB_VBI, LOG_INFO,
1082 QString(
"SRT formatting change '%1', "
1083 "new ital=%2 bold=%3 uline=%4 color=%5)")
1084 .arg(html).arg(isItalic).arg(isBold).arg(isUnderline)
1095 for (
int i = 0; i <
m_lines.size(); i++)
1097 int width =
m_lines[i].CalcSize().width();
1100 while (width > maxWidth &&
m_lines[i].chunks.size() > 1)
1102 width -=
m_lines[i].chunks.back().CalcSize().width();
1108 LOG(VB_VBI, LOG_INFO,
1109 QString(
"Wrapping chunk to next line: '%1'")
1110 .arg(
m_lines[i+1].chunks[0].m_text));
1114 bool isSplitPossible =
true;
1115 while (width > maxWidth && isSplitPossible)
1118 isSplitPossible =
m_lines[i].chunks.back().Split(newChunk);
1119 if (isSplitPossible)
1125 m_lines[i+1].chunks.prepend(newChunk);
1126 width =
m_lines[i].CalcSize().width();
1138 bool &isItalic,
bool &isUnderline)
1143 if (text.length() >= 1 && text[0] >= QChar(0x7000))
1145 int op = text[0].unicode() - 0x7000;
1146 isUnderline = ((op & 0x1) != 0);
1162 color = (op & 0xf) >> 1;
1171 static const QRegularExpression kControlCharsRE {
"[\\x{7000}-\\x{7fff}]" };
1172 int nextControl = text.indexOf(kControlCharsRE);
1173 if (nextControl < 0)
1180 result = text.left(nextControl);
1181 text = text.mid(nextControl);
1193 int totalHeight = 0;
1197 QVector<int> heights(
m_lines.size());
1198 QVector<int> spaceBefore(
m_lines.size());
1200 for (
int i = 0; i <
m_lines.size(); i++)
1206 int height =
m_lines[i].CalcSize().height();
1207 heights[i] = height;
1208 spaceBefore[i] = y -
prevY;
1209 totalSpace += (y -
prevY);
1211 totalHeight += height;
1214 int overage = std::min(totalHeight - safeHeight, totalSpace);
1218 if (overage > 0 && totalSpace > 0)
1220 float shrink = (totalSpace - overage) / (
float)totalSpace;
1222 for (
int i = 0; i <
m_lines.size(); i++)
1224 m_lines[i].m_yIndent =
prevY + spaceBefore[i] * shrink;
1230 int shift = std::min(firstY, std::max(0,
prevY - safeHeight));
1232 for (
int i = 0; i <
m_lines.size(); i++)
1233 m_lines[i].m_yIndent -= shift;
1240 static const std::array<const QColor,8> kClr
1242 Qt::white, Qt::green, Qt::blue, Qt::cyan,
1243 Qt::red, Qt::yellow, Qt::magenta, Qt::white,
1261 QFontMetrics fm(*font);
1262 fontwidth = fm.averageCharWidth();
1269 for (; i !=
buffers.cend(); ++i)
1273 bool isItalic =
false;
1274 bool isUnderline =
false;
1275 const bool isBold =
false;
1276 QString text(
cc->m_text);
1278 int orig_x =
cc->m_x;
1286 x = xmid + (orig_x - xscale / 2) * fontwidth;
1291 x = (orig_x + 3) *
m_safeArea.width() / xscale;
1294 int orig_y =
cc->m_y;
1296 if (orig_y < yscale / 2)
1299 y = (orig_y *
m_safeArea.height() * zoom / (yscale * 100));
1305 ((yscale - orig_y - 0.5) *
m_safeArea.height() * zoom /
1310 while (!text.isNull())
1312 QString captionText =
1315 kClr[std::min(std::max(0, color), 7)]);
1318 LOG(VB_VBI, LOG_INFO,
1319 QString(
"Adding cc608 chunk (%1,%2): %3")
1335 shape->SetFillBrush(fill);
1344 const std::vector<CC708String*> &list,
1347 LOG(VB_VBI, LOG_DEBUG,
LOC +
1348 QString(
"Display Win %1, Anchor_id %2, x_anch %3, y_anch %4, "
1357 (aspect > 1.4F) ? 210.0F : 160.0F;
1359 float xmult = (float)
m_safeArea.width() / xrange;
1360 float ymult = (float)
m_safeArea.height() / yrange;
1368 for (
auto *str708 : list)
1371 m_lines.resize(str708->m_y + 1);
1373 m_lines[str708->m_y].chunks += chunk;
1374 LOG(VB_VBI, LOG_INFO, QString(
"Adding cc708 chunk: win %1 row %2: %3")
1382 const QString &
Name,
int FontStretch)
1385 m_fontStretch(FontStretch),
1392 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"SeekingDone signal received.");
1436 if (!forced_only ||
m_family.isEmpty()) {
1517 AVSubtitleRect *hl_button = dvdButton->rects[0];
1518 uint h = hl_button->h;
1519 uint w = hl_button->w;
1520 QRect rect = QRect(hl_button->x, hl_button->y, w, h);
1521 QImage bg_image(hl_button->data[0], w, h, w, QImage::Format_Indexed8);
1522 auto *bgpalette = (uint32_t *)(hl_button->data[1]);
1524 QVector<uint32_t> bg_palette(4);
1525 for (
int i = 0; i < 4; i++)
1526 bg_palette[i] = bgpalette[i];
1527 bg_image.setColorTable(bg_palette);
1530 const QRect fg_rect(buttonPos.translated(-hl_button->x, -hl_button->y));
1531 QImage fg_image = bg_image.copy(fg_rect);
1532 QVector<uint32_t> fg_palette(4);
1533 auto *fgpalette = (uint32_t *)(dvdButton->rects[1]->data[1]);
1536 for (
int i = 0; i < 4; i++)
1537 fg_palette[i] = fgpalette[i];
1538 fg_image.setColorTable(fg_palette);
1541 bg_image = bg_image.convertToFormat(QImage::Format_ARGB32);
1542 fg_image = fg_image.convertToFormat(QImage::Format_ARGB32);
1545 for (
int x=fg_rect.x(); x < fg_rect.x()+fg_rect.width(); ++x)
1547 if ((x < 0) || (x > hl_button->w))
1549 for (
int y=fg_rect.y(); y < fg_rect.y()+fg_rect.height(); ++y)
1551 if ((y < 0) || (y > hl_button->h))
1553 bg_image.setPixel(x, y, fg_image.pixel(x-fg_rect.x(),y-fg_rect.y()));
1587 QList<MythUIType *>::iterator it;
1588 for (it = list.begin(); it != list.end(); ++it)
1591 auto *wrapper =
dynamic_cast<SubWrapper *
>(child);
1595 if (whichImageCache != -1 && (mask & (1UL << whichImageCache)))
1635 int outlineSize = 0;
1636 int shadowWidth = 0;
1637 int shadowHeight = 0;
1640 mythfont->
GetOutline(color, outlineSize, alpha);
1641 MythPoint outline(outlineSize, outlineSize);
1643 outlineSize = outline.x();
1648 mythfont->
GetShadow(shadowOffset, color, alpha);
1650 shadowWidth = abs(shadowOffset.x());
1651 shadowHeight = abs(shadowOffset.y());
1653 shadowWidth = std::max(shadowWidth, outlineSize);
1654 shadowHeight = std::max(shadowHeight, outlineSize);
1656 return {shadowWidth + outlineSize, shadowHeight + outlineSize};
1661 float layoutSpacing)
const
1664 QFont *font = mythfont->
GetFace();
1665 QFontMetrics fm(*font);
1666 int width = fm.horizontalAdvance(text);
1668 if (layoutSpacing > 0 && !text.trimmed().isEmpty())
1669 height = std::max(height, (
int)(font->pixelSize() * layoutSpacing));
1671 return {width, height};
1678 bool isFirst,
bool isLast,
1679 int &left,
int &right)
const
1682 QFont *font = mythfont->
GetFace();
1683 QFontMetrics fm(*font);
1684 int basicPadding = fm.maxWidth() *
PAD_WIDTH;
1685 left = isFirst ? basicPadding : 0;
1687 (isLast ? basicPadding : 0);
1704 return mythfont->
face().family();
1722 LOG(VB_GENERAL, LOG_WARNING,
LOC +
"Failed to get subtitle reader.");
1724 LOG(VB_GENERAL, LOG_WARNING,
LOC +
"Failed to get CEA-608 reader.");
1726 LOG(VB_GENERAL, LOG_WARNING,
LOC +
"Failed to get CEA-708 reader.");
1733 QList<MythUIType *>::iterator it;
1734 QList<MythUIType *>::iterator itNext;
1738 std::chrono::milliseconds now =
1739 currentFrame ? currentFrame->
m_timecode : std::chrono::milliseconds::max();
1746 auto *wrapper =
dynamic_cast<SubWrapper *
>(child);
1751 std::chrono::milliseconds expireTime = wrapper->
GetExpireTime();
1752 if (expireTime > 0ms && expireTime < now)
1760 if (expireTime > 0ms && needRescale)
1762 auto *image =
dynamic_cast<SubImage *
>(child);
1766 QSize size = image->GetImage()->size();
1768 image->GetImage()->Resize(size);
1818 auto *wrapper =
dynamic_cast<SubWrapper *
>(img);
1820 visible = visible.united(wrapper->GetOrigArea());
1823 if (visible.isEmpty())
1826 QRect bounding = visible.boundingRect();
1827 bounding = bounding.translated(
m_safeArea.topLeft());
1829 int left =
m_safeArea.left() - bounding.left();
1837 auto *wrapper =
dynamic_cast<SubWrapper *
>(img);
1849 QMutexLocker lock(&(subs->
m_lock));
1859 if (!currentFrame || !videoOut)
1889 AVSEEK_FLAG_BACKWARD);
1894 #ifdef DEBUG_SUBTITLES
1897 LOG(VB_PLAYBACK, LOG_DEBUG,
1898 LOC + QString(
"time %1, no subtitle available after seek")
1909 AVSubtitle subtitle = subs->
m_buffers.front();
1910 if (subtitle.end_display_time < currentFrame->
m_timecode.count())
1913 #ifdef DEBUG_SUBTITLES
1916 LOG(VB_PLAYBACK, LOG_DEBUG,
1917 LOC + QString(
"time %1, drop %2")
1932 #ifdef DEBUG_SUBTITLES
1935 LOG(VB_PLAYBACK, LOG_DEBUG,
1936 LOC + QString(
"time %1, no subtitle available")
1949 [[maybe_unused]]
bool assForceNext {
false};
1952 AVSubtitle subtitle = subs->
m_buffers.front();
1953 if (subtitle.start_display_time > currentFrame->
m_timecode.count())
1955 #ifdef DEBUG_SUBTITLES
1958 LOG(VB_PLAYBACK, LOG_DEBUG,
1959 LOC + QString(
"time %1, next %2")
1971 assForceNext =
true;
1973 auto displayfor = std::chrono::milliseconds(subtitle.end_display_time -
1974 subtitle.start_display_time);
1975 #ifdef DEBUG_SUBTITLES
1978 LOG(VB_PLAYBACK, LOG_DEBUG,
1979 LOC + QString(
"time %1, show %2")
1983 if (displayfor == 0ms)
1985 displayfor = (displayfor < 50ms) ? 50ms : displayfor;
1986 std::chrono::milliseconds late = currentFrame->
m_timecode -
1987 std::chrono::milliseconds(subtitle.start_display_time);
1991 for (std::size_t i = 0; i < subtitle.num_rects; ++i)
1993 AVSubtitleRect* rect = subtitle.rects[i];
1995 bool displaysub =
true;
1997 subs->
m_buffers.front().end_display_time <
2003 if (displaysub && rect->type == SUBTITLE_BITMAP)
2005 QRect display(rect->display_x, rect->display_y,
2006 rect->display_w, rect->display_h);
2012 int right = rect->x + rect->w;
2013 int bottom = rect->y + rect->h;
2015 (currentFrame->
m_width < right) ||
2016 !display.width() || !display.height())
2018 int sd_height = 576;
2022 int height = ((currentFrame->
m_height <= sd_height) &&
2023 (bottom <= sd_height)) ? sd_height :
2024 ((currentFrame->
m_height <= 720) && bottom <= 720)
2026 int width = ((currentFrame->
m_width <= 720) &&
2027 (right <= 720)) ? 720 :
2028 ((currentFrame->
m_width <= 1280) &&
2029 (right <= 1280)) ? 1280 : 1920;
2030 display = QRect(0, 0, width, height);
2035 int uh = display.height() / 2 - rect->y;
2036 std::chrono::milliseconds displayuntil = currentFrame->
m_timecode + displayfor;
2039 bbox = QRect(0, 0, rect->w, uh);
2041 rect->flags & AV_SUBTITLE_FLAG_FORCED,
2042 QString(
"avsub%1t").arg(i),
2043 displayuntil, late);
2047 int lh = rect->h - uh;
2050 bbox = QRect(0, uh, rect->w, lh);
2052 rect->flags & AV_SUBTITLE_FLAG_FORCED,
2053 QString(
"avsub%1b").arg(i),
2054 displayuntil, late);
2058 else if (displaysub && rect->type == SUBTITLE_ASS)
2061 AddAssEvent(rect->ass, subtitle.start_display_time, subtitle.end_display_time);
2073 QRect &bbox,
bool top,
2074 QRect &display,
int forced,
2075 const QString& imagename,
2076 std::chrono::milliseconds displayuntil,
2077 std::chrono::milliseconds late)
2082 bool prev_empty =
false;
2085 int xmin = bbox.right();
2086 int xmax = bbox.left();
2087 int ymin = bbox.bottom();
2088 int ymax = bbox.top();
2089 int ylast = bbox.top();
2090 int ysplit = bbox.bottom();
2093 for (
int y = bbox.top(); y <= bbox.bottom(); ++y)
2104 for (
int x = bbox.left(); x <= bbox.right(); ++x)
2106 const uint8_t color =
2107 rect->data[0][y * rect->linesize[0] + x];
2108 const uint32_t
pixel = *((uint32_t *)rect->data[1] + color);
2109 if (
pixel & 0xff000000)
2126 else if (!prev_empty)
2143 if (ymax == bbox.bottom())
2152 bbox.setRight(xmax);
2154 bbox.setBottom(ymax);
2160 QRect orig_rect(bbox.left(), bbox.top(), bbox.width(), bbox.height());
2162 QImage qImage(bbox.width(), bbox.height(), QImage::Format_ARGB32);
2163 for (
int y = 0; y < bbox.height(); ++y)
2165 int ysrc = y + bbox.top();
2166 for (
int x = 0; x < bbox.width(); ++x)
2168 int xsrc = x + bbox.left();
2169 const uint8_t color =
2170 rect->data[0][ysrc * rect->linesize[0] + xsrc];
2171 const uint32_t
pixel = *((uint32_t *)rect->data[1] + color);
2172 qImage.setPixel(x, y,
pixel);
2177 bbox.translate(rect->x, rect->y);
2186 if (scaled.size() != orig_rect.size())
2187 qImage = qImage.scaled(scaled.width(), scaled.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
2223 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"Display %1AV sub until %2ms")
2224 .arg(forced ?
"FORCED " :
"").arg(displayuntil.count()));
2226 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"AV Sub was %1ms late").arg(late.count()));
2229 return (ysplit + 1);
2237 std::chrono::milliseconds duration = 0ms;
2259 std::chrono::milliseconds start,
2260 std::chrono::milliseconds duration)
2263 duration,
this, subs);
2288 QMutexLocker locker(&textlist->
m_lock);
2310 for (
auto & window : cc708service->
m_windows)
2311 window.SetChanged();
2314 uint64_t clearMask = 0;
2315 QList<FormattedTextSubtitle *> addList;
2324 clearMask |= (1UL << i);
2329 QMutexLocker locker(&win.
m_lock);
2330 std::vector<CC708String*> list = win.
GetStrings();
2335 addList.append(fsub);
2343 if (!addList.empty())
2354 if (scaled.size() != pos.size())
2356 img = img.scaled(scaled.width(), scaled.height(),
2357 Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
2378 uint64_t verbose_mask = VB_GENERAL;
2379 LogLevel_t verbose_level = LOG_INFO;
2384 verbose_level = LOG_EMERG;
2387 verbose_level = LOG_ERR;
2390 verbose_level = LOG_WARNING;
2393 verbose_level = LOG_INFO;
2397 verbose_level = LOG_DEBUG;
2406 static QMutex s_stringLock;
2407 s_stringLock.lock();
2409 QString str = QString::vasprintf(fmt, vl);
2410 LOG(verbose_mask, verbose_level, QString(
"libass: %1").arg(str));
2411 s_stringLock.unlock();
2426 ass_set_extract_fonts(
m_assLibrary,
static_cast<int>(
true));
2427 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"Initialised libass object.");
2442 const char *psz_font =
"/system/fonts/DroidSans.ttf";
2443 const char *psz_font_family =
"Droid Sans";
2445 const char *psz_font =
nullptr;
2446 const char *psz_font_family =
"sans-serif";
2448 ass_set_fonts(
m_assRenderer, psz_font, psz_font_family, 1,
nullptr, 1);
2450 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"Initialised libass renderer.");
2469 for (
uint i = 0; i < count; ++i)
2475 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"Retrieved font '%1'")
2512 if (header.isNull())
2516 header =
parser->GetSubHeader();
2518 if (!header.isNull())
2519 ass_process_codec_private(
m_assTrack, header.data(), header.size());
2535 ass_process_chunk(
m_assTrack, event, strlen(event), starttime, endtime-starttime);
2562 if (!changed && !
force)
2570 if (images->w == 0 || images->h == 0)
2572 images = images->next;
2576 uint8_t alpha = images->color & 0xFF;
2577 uint8_t blue = images->color >> 8 & 0xFF;
2578 uint8_t green = images->color >> 16 & 0xFF;
2579 uint8_t red = images->color >> 24 & 0xFF;
2583 images = images->next;
2587 QSize img_size(images->w, images->h);
2588 QRect img_rect(images->dst_x,images->dst_y,
2589 images->w, images->h);
2590 QImage qImage(img_size, QImage::Format_ARGB32);
2591 qImage.fill(0x00000000);
2593 unsigned char *src = images->bitmap;
2594 for (
int y = 0; y < images->h; ++y)
2596 for (
int x = 0; x < images->w; ++x)
2598 uint8_t value = src[x];
2601 uint32_t
pixel = (value * (255 - alpha) / 255 << 24) |
2602 (red << 16) | (green << 8) | blue;
2603 qImage.setPixel(x, y,
pixel);
2606 src += images->stride;
2615 QString name = QString(
"asssub%1").arg(count);
2626 images = images->next;
2630 #endif // USING_LIBASS