4#include <QRegularExpression>
6#include "libmythbase/mythconfig.h"
17#define LOC QString("Subtitles: ")
18#define LOC_WARN QString("Subtitles Warning: ")
23 std::chrono::milliseconds time = frame->
m_timecode;
24 return QString(
"%1(%2)")
43 std::chrono::milliseconds expireTime,
44 int whichImageCache = -1) :
65 QRect rect, Qt::Alignment align,
67 int whichImageCache, std::chrono::milliseconds expireTime) :
76 int whichImageCache, std::chrono::milliseconds expireTime) :
78 SubWrapper(area, expireTime, whichImageCache) {}
85 std::chrono::milliseconds expireTime) :
153 int pixelSize,
int zoom,
int stretch);
155 const QString &family,
159 std::chrono::milliseconds start,
160 std::chrono::milliseconds duration);
162 static QString
MakePrefix(
const QString &family,
165 void Load(
const QString &family,
178 static QSet<QString>
Diff(
const QString &family,
223 return QString(
"#%1%2%3")
224 .arg(color.red(), 2, 16, QLatin1Char(
'0'))
225 .arg(color.green(), 2, 16, QLatin1Char(
'0'))
226 .arg(color.blue(), 2, 16, QLatin1Char(
'0'));
232 result = QString(
"face=%1 pixelsize=%2 color=%3 "
233 "italics=%4 weight=%5 underline=%6")
235 .arg(f->
GetFace()->pixelSize())
237 .arg(
static_cast<int>(f->
GetFace()->italic()))
239 .arg(
static_cast<int>(f->
GetFace()->underline()));
245 result += QString(
" shadow=%1 shadowoffset=%2 "
246 "shadowcolor=%3 shadowalpha=%4")
247 .arg(QString::number(
static_cast<int>(f->
hasShadow())),
248 QString(
"(%1,%2)").arg(offset.x()).arg(offset.y()),
250 QString::number(alpha));
252 result += QString(
" outline=%1 outlinecolor=%2 "
253 "outlinesize=%3 outlinealpha=%4")
264 int pixelSize,
int zoom,
int stretch)
266 int origPixelSize = pixelSize;
267 float scale = zoom / 100.0F;
269 scale = scale * 32 / 42;
271 scale = scale * 42 / 32;
283 result->
GetFace()->setPixelSize(pixelSize);
285 result->
GetFace()->setStretch(stretch);
306 int off = lroundf(scale * pixelSize / 20);
307 offset = QPoint(off, off);
326 offset.
setX(lroundf(offset.x() * scale));
327 offset.
setY(lroundf(offset.y() * scale));
329 result->
SetShadow(shadow, offset, color, alpha);
345 off = lroundf(scale * pixelSize / 20);
357 off = lroundf(point.x() * scale);
359 result->
SetOutline(outline, color, off, alpha);
361 LOG(VB_VBI, LOG_DEBUG,
362 QString(
"GetFont(family=%1, prefix=%2, orig pixelSize=%3, "
363 "new pixelSize=%4 zoom=%5) = %6")
364 .arg(family,
prefix).arg(origPixelSize).arg(pixelSize)
372 for (
int i = 0; i <
m_cleanup.size(); ++i)
384 return family +
"_" + QString::number(attr.
m_fontTag & 0x7);
399 font->GetFace()->setFamily(
"FreeMono");
400 QBrush brush(Qt::black);
401 bg->SetFillBrush(brush);
402 bg->SetLinePen(QPen(brush, 0));
406 static const std::array<const std::string,8> s_cc708Fonts {
416 font->GetFace()->setFamily(QString::fromStdString(s_cc708Fonts[attr.
m_fontTag & 0x7]));
420 font->GetFace()->setFamily(
"Droid Sans");
421 QBrush brush(Qt::black);
422 bg->SetFillBrush(brush);
423 bg->SetLinePen(QPen(brush, 0));
427 font->GetFace()->setFamily(
"FreeMono");
430 QBrush brush(Qt::black);
431 bg->SetFillBrush(brush);
432 bg->SetLinePen(QPen(brush, 0));
434 font->GetFace()->setPixelSize(10);
446 return color == Qt::white ? Qt::black : Qt::white;
458 face->setItalic(!face->italic());
459 face->setPixelSize(face->pixelSize() + 1);
460 face->setUnderline(!face->underline());
461#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
462 face->setWeight((face->weight() + 1) % 32);
469 offset.setX(offset.x() + 1);
475 size + 1, 255 - alpha);
478 Qt::SolidPattern : Qt::NoBrush);
485 auto *baseParent =
new MythUIType(
nullptr,
"base");
490 &providerBaseFont, &providerBaseShape);
493 auto *negParent =
new MythUIType(
nullptr,
"base");
505 if (!posResult || !negResult)
506 LOG(VB_VBI, LOG_INFO,
507 QString(
"Couldn't load theme file %1").arg(
kSubFileName));
511 resultFont = providerBaseFont;
517 resultBG = providerBaseShape;
527 resultFont->
GetFace()->setCapitalization(QFont::SmallCaps);
530 LOG(VB_VBI, LOG_DEBUG,
531 QString(
"providerBaseFont = %1").arg(
fontToString(providerBaseFont)));
532 LOG(VB_VBI, LOG_DEBUG,
534 LOG(VB_VBI, LOG_DEBUG,
535 QString(
"resultFont = %1").arg(
fontToString(resultFont)));
536 LOG(VB_VBI, LOG_DEBUG,
544 resultFont->
GetShadow(offset, color, alpha);
561 QSet<QString> result;
562 QFont *face1 = font1->
GetFace();
563 QFont *face2 = font2->
GetFace();
564 if (face1->italic() != face2->italic())
566 if (face1->weight() != face2->weight())
568 if (face1->underline() != face2->underline())
570 if (face1->pixelSize() != face2->pixelSize())
583 font1->
GetShadow(offset1, color1, alpha1);
584 font2->
GetShadow(offset2, color2, alpha2);
585 if (offset1 != offset2)
587 if (color1 != color2)
589 if (alpha1 != alpha2)
603 if (color1 != color2)
607 if (alpha1 != alpha2)
614 for (
auto i = result.constBegin(); i != result.constEnd(); ++i)
615 values +=
" " + (*i);
616 LOG(VB_VBI, LOG_INFO,
617 QString(
"Subtitle family %1 allows provider to change:%2")
625 const QString &family,
629 std::chrono::milliseconds start,
630 std::chrono::milliseconds duration)
637 auto *result =
new SubShape(parent, name, area, whichImageCache,
645 result->SetFillBrush(brush);
646 result->SetLinePen(QPen(brush, 0));
653 LOG(VB_VBI, LOG_DEBUG,
654 QString(
"GetBackground(prefix=%1) = "
655 "{type=%2 alpha=%3 brushstyle=%4 brushcolor=%5}")
656 .arg(
prefix, result->m_type, QString::number(result->GetAlpha()),
657 QString::number(result->m_fillBrush.style()),
680 int &left,
int &right)
const
687 LOG(VB_VBI, LOG_INFO,
688 QString(
"Attempting to split chunk '%1'").arg(
m_text));
689 int lastSpace =
m_text.lastIndexOf(
' ', -2);
692 LOG(VB_VBI, LOG_INFO,
693 QString(
"Failed to split chunk '%1'").arg(
m_text));
698 newChunk.
m_text =
m_text.mid(lastSpace + 1).trimmed() +
' ';
700 LOG(VB_VBI, LOG_INFO,
701 QString(
"Split chunk into '%1' + '%2'").arg(
m_text, newChunk.
m_text));
708 str += QString(
"fg=%1.%2 ")
711 str += QString(
"bg=%1.%2 ")
714 str += QString(
"edge=%1.%2 ")
717 str += QString(
"off=%1 pensize=%2 ")
720 str += QString(
"it=%1 ul=%2 bf=%3 ")
725 str += QString(
" text='%1'").arg(
m_text);
730 int &x,
int y,
int height)
741 while (count <
m_text.length() &&
m_text.at(count) ==
' ')
745 int x_adjust = count * font.horizontalAdvance(
" ");
747 int rightPadding = 0;
748 CalcPadding(isFirst, isLast, leftPadding, rightPadding);
753 QRect bgrect(x - leftPadding, y,
754 chunk_sz.width() + leftPadding + rightPadding,
758 bgrect.setLeft(bgrect.left() + x_adjust);
760 .arg(chunk_sz.width()).arg(height).arg(x).arg(y);
766 .arg(chunk_sz.width()).arg(height).arg(x).arg(y);
768 chunk_sz.width() - x_adjust + rightPadding, height);
770 x += chunk_sz.width();
779 int rightPadding = 0;
780 QList<FormattedTextChunk>::const_iterator it;
782 for (it =
chunks.constBegin(); it !=
chunks.constEnd(); ++it)
784 bool isLast = (it + 1 ==
chunks.constEnd());
785 QSize
tmp = (*it).CalcSize(layoutSpacing);
786 height = std::max(height,
tmp.height());
787 width +=
tmp.width();
788 (*it).CalcPadding(isFirst, isLast, leftPadding, rightPadding);
789 if (it ==
chunks.constBegin())
790 width += leftPadding;
793 return {width + rightPadding, height};
810 int anchor_width = 0;
811 int anchor_height = 0;
812 for (
const auto & line : std::as_const(
m_lines))
815 anchor_width = std::max(anchor_width, sz.width());
816 anchor_height += sz.height();
823 anchor_x -= anchor_width / 2;
825 anchor_x -= anchor_width;
827 anchor_y -= anchor_height / 2;
829 anchor_y -= anchor_height;
835 m_bounds = QRect(anchor_x, anchor_y, anchor_width, anchor_height);
840 for (
int i = 0; i <
m_lines.size(); i++)
843 m_lines[i].m_xIndent = anchor_x;
848 while (!
m_lines[i].chunks.isEmpty() &&
849 m_lines[i].chunks.first().m_text.trimmed().isEmpty())
852 m_lines[i].chunks.first().CalcSize().width();
853 m_lines[i].chunks.removeFirst();
856 while (!
m_lines[i].chunks.isEmpty() &&
857 m_lines[i].chunks.last().m_text.trimmed().isEmpty())
859 m_lines[i].chunks.removeLast();
864 if (!
m_lines[i].chunks.isEmpty())
866 QString *str = &
m_lines[i].chunks.last().m_text;
867 int idx = str->length() - 1;
868 while (idx >= 0 && str->at(idx) ==
' ')
870 str->truncate(idx + 1);
878 for (
int i = 0; i <
m_lines.size(); i++)
882 int height =
m_lines[i].CalcSize().height();
883 QList<FormattedTextChunk>::iterator chunk;
885 for (chunk =
m_lines[i].chunks.begin();
886 chunk !=
m_lines[i].chunks.end();
889 bool isLast = (chunk + 1 ==
m_lines[i].chunks.end());
890 (*chunk).PreRender(isFirst, isLast, x, y, height);
901 QList<MythUIType *> textList;
902 QList<MythUIType *> shapeList;
905 QList<FormattedTextChunk>::const_iterator chunk;
906 for (chunk = line.chunks.constBegin();
907 chunk != line.chunks.constEnd();
919 if ((*chunk).m_textRect.width() > 0) {
922 Qt::AlignLeft|Qt::AlignTop,
928 if ((*chunk).m_bgShapeRect.width() > 0) {
930 GetBackground(
nullptr,
931 (*chunk).m_bgShapeName,
932 m_base, (*chunk).m_format,
938 shapeList += bgshape;
943 while (!shapeList.isEmpty())
945 while (!textList.isEmpty())
952 for (
const auto & ftl : std::as_const(
m_lines))
956 line.fill(
' ', ftl.m_origX);
957 QList<FormattedTextChunk>::const_iterator chunk;
958 for (chunk = ftl.chunks.constBegin();
959 chunk != ftl.chunks.constEnd();
962 const QString &text = (*chunk).m_text;
974 line += QString(
"<font color=\"%1\">")
981 line += QString(
"</font>");
990 if (!line.trimmed().isEmpty())
1020 bool isItalic =
false;
1021 bool isBold =
false;
1022 bool isUnderline =
false;
1023 QColor color(Qt::white);
1024 static const QRegularExpression htmlTag {
1025 "</?.+>", QRegularExpression::InvertedGreedinessOption };
1026 QString htmlPrefix(
"<font color=\"");
1027 QString htmlSuffix(
"\">");
1028 for (
const QString& subtitle : std::as_const(subs))
1031 QString text(subtitle);
1032 while (!text.isEmpty())
1034 auto match = htmlTag.match(text);
1035 int pos = match.capturedStart();
1042 text = (pos < 0 ?
"" : text.mid(pos));
1043 LOG(VB_VBI, LOG_INFO, QString(
"Adding SRT chunk: %1")
1048 int htmlLen = match.capturedLength();
1049 QString html = text.left(htmlLen).toLower();
1050 text = text.mid(htmlLen);
1053 else if (html ==
"</i>")
1055 else if (html.startsWith(htmlPrefix) &&
1056 html.endsWith(htmlSuffix))
1058 int colorLen = html.length() -
1059 (htmlPrefix.length() + htmlSuffix.length());
1060 QString colorString(
1061 html.mid(htmlPrefix.length(), colorLen));
1062 QColor newColor(colorString);
1063 if (newColor.isValid())
1069 LOG(VB_VBI, LOG_INFO,
1070 QString(
"Ignoring invalid SRT color specification: "
1071 "'%1'").arg(colorString));
1074 else if (html ==
"</font>")
1078 else if (html ==
"<b>")
1082 else if (html ==
"</b>")
1086 else if (html ==
"<u>")
1090 else if (html ==
"</u>")
1092 isUnderline =
false;
1096 LOG(VB_VBI, LOG_INFO,
1097 QString(
"Ignoring unknown SRT formatting: '%1'")
1101 LOG(VB_VBI, LOG_INFO,
1102 QString(
"SRT formatting change '%1', "
1103 "new ital=%2 bold=%3 uline=%4 color=%5)")
1104 .arg(html).arg(isItalic).arg(isBold).arg(isUnderline)
1115 for (
int i = 0; i <
m_lines.size(); i++)
1117 int width =
m_lines[i].CalcSize().width();
1120 while (width > maxWidth &&
m_lines[i].chunks.size() > 1)
1122 width -=
m_lines[i].chunks.back().CalcSize().width();
1128 LOG(VB_VBI, LOG_INFO,
1129 QString(
"Wrapping chunk to next line: '%1'")
1130 .arg(
m_lines[i+1].chunks[0].m_text));
1134 bool isSplitPossible =
true;
1135 while (width > maxWidth && isSplitPossible)
1138 isSplitPossible =
m_lines[i].chunks.back().Split(newChunk);
1139 if (isSplitPossible)
1145 m_lines[i+1].chunks.prepend(newChunk);
1146 width =
m_lines[i].CalcSize().width();
1158 bool &isItalic,
bool &isUnderline)
1163 if (text.length() >= 1 && text[0] >= QChar(0x7000))
1165 int op = text[0].unicode() - 0x7000;
1166 isUnderline = ((op & 0x1) != 0);
1182 color = (op & 0xf) >> 1;
1191 static const QRegularExpression kControlCharsRE {
"[\\x{7000}-\\x{7fff}]" };
1192 int nextControl = text.indexOf(kControlCharsRE);
1193 if (nextControl < 0)
1200 result = text.left(nextControl);
1201 text = text.mid(nextControl);
1213 int totalHeight = 0;
1217 QVector<int> heights(
m_lines.size());
1218 QVector<int> spaceBefore(
m_lines.size());
1220 for (
int i = 0; i <
m_lines.size(); i++)
1226 int height =
m_lines[i].CalcSize().height();
1227 heights[i] = height;
1228 spaceBefore[i] = y -
prevY;
1229 totalSpace += (y -
prevY);
1231 totalHeight += height;
1234 int overage = std::min(totalHeight - safeHeight, totalSpace);
1238 if (overage > 0 && totalSpace > 0)
1240 float shrink = (totalSpace - overage) / (
float)totalSpace;
1242 for (
int i = 0; i <
m_lines.size(); i++)
1244 m_lines[i].m_yIndent =
prevY + (spaceBefore[i] * shrink);
1250 int shift = std::min(firstY, std::max(0,
prevY - safeHeight));
1252 for (
int i = 0; i <
m_lines.size(); i++)
1253 m_lines[i].m_yIndent -= shift;
1260 static const std::array<const QColor,8> kClr
1262 Qt::white, Qt::green, Qt::blue, Qt::cyan,
1263 Qt::red, Qt::yellow, Qt::magenta, Qt::white,
1281 QFontMetrics fm(*font);
1282 fontwidth = fm.averageCharWidth();
1289 for (; i !=
buffers.cend(); ++i)
1293 bool isItalic =
false;
1294 bool isUnderline =
false;
1295 const bool isBold =
false;
1296 QString text(cc->
m_text);
1298 int orig_x = cc->
m_x;
1306 x = xmid + ((orig_x - xscale / 2) * fontwidth);
1311 x = (orig_x + 3) *
m_safeArea.width() / xscale;
1314 int orig_y = cc->
m_y;
1316 if (orig_y < yscale / 2)
1319 y = (orig_y *
m_safeArea.height() * zoom / (yscale * 100));
1325 ((yscale - orig_y - 0.5) *
m_safeArea.height() * zoom /
1330 while (!text.isNull())
1332 QString captionText =
1338 LOG(VB_VBI, LOG_INFO,
1339 QString(
"Adding cc608 chunk (%1,%2): %3")
1355 shape->SetFillBrush(fill);
1364 const std::vector<CC708String*> &list,
1367 LOG(VB_VBI, LOG_DEBUG,
LOC +
1368 QString(
"Display Win %1, Anchor_id %2, x_anch %3, y_anch %4, "
1376 float xrange { 160.0F };
1379 else if (aspect > 1.4F)
1382 float xmult = (float)
m_safeArea.width() / xrange;
1383 float ymult = (float)
m_safeArea.height() / yrange;
1391 for (
auto *str708 : list)
1394 m_lines.resize(str708->m_y + 1);
1396 m_lines[str708->m_y].chunks += chunk;
1397 LOG(VB_VBI, LOG_INFO, QString(
"Adding cc708 chunk: win %1 row %2: %3")
1405 const QString &
Name,
int FontStretch)
1408 m_fontStretch(FontStretch),
1415 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"SeekingDone signal received.");
1425 CleanupAssLibrary();
1459 if (!forced_only ||
m_family.isEmpty()) {
1502 ass_flush_events(m_assTrack);
1540 AVSubtitleRect *hl_button = dvdButton->rects[0];
1541 uint h = hl_button->h;
1542 uint w = hl_button->w;
1543 QRect rect = QRect(hl_button->x, hl_button->y, w, h);
1544 QImage bg_image(hl_button->data[0], w, h, w, QImage::Format_Indexed8);
1545 auto *bgpalette = (uint32_t *)(hl_button->data[1]);
1547 QVector<uint32_t> bg_palette(4);
1548 for (
int i = 0; i < 4; i++)
1549 bg_palette[i] = bgpalette[i];
1550 bg_image.setColorTable(bg_palette);
1553 const QRect fg_rect(buttonPos.translated(-hl_button->x, -hl_button->y));
1554 QImage fg_image = bg_image.copy(fg_rect);
1555 QVector<uint32_t> fg_palette(4);
1556 auto *fgpalette = (uint32_t *)(dvdButton->rects[1]->data[1]);
1559 for (
int i = 0; i < 4; i++)
1560 fg_palette[i] = fgpalette[i];
1561 fg_image.setColorTable(fg_palette);
1564 bg_image = bg_image.convertToFormat(QImage::Format_ARGB32);
1565 fg_image = fg_image.convertToFormat(QImage::Format_ARGB32);
1568 for (
int x=fg_rect.x(); x < fg_rect.x()+fg_rect.width(); ++x)
1570 if ((x < 0) || (x > hl_button->w))
1572 for (
int y=fg_rect.y(); y < fg_rect.y()+fg_rect.height(); ++y)
1574 if ((y < 0) || (y > hl_button->h))
1576 bg_image.setPixel(x, y, fg_image.pixel(x-fg_rect.x(),y-fg_rect.y()));
1610 QList<MythUIType *>::iterator it;
1611 for (it = list.begin(); it != list.end(); ++it)
1614 auto *wrapper =
dynamic_cast<SubWrapper *
>(child);
1618 if (whichImageCache != -1 && (mask & (1UL << whichImageCache)))
1658 int outlineSize = 0;
1659 int shadowWidth = 0;
1660 int shadowHeight = 0;
1663 mythfont->
GetOutline(color, outlineSize, alpha);
1664 MythPoint outline(outlineSize, outlineSize);
1666 outlineSize = outline.x();
1671 mythfont->
GetShadow(shadowOffset, color, alpha);
1673 shadowWidth = abs(shadowOffset.x());
1674 shadowHeight = abs(shadowOffset.y());
1676 shadowWidth = std::max(shadowWidth, outlineSize);
1677 shadowHeight = std::max(shadowHeight, outlineSize);
1679 return {shadowWidth + outlineSize, shadowHeight + outlineSize};
1684 float layoutSpacing)
const
1687 QFont *font = mythfont->
GetFace();
1688 QFontMetrics fm(*font);
1689 int width = fm.horizontalAdvance(text);
1691 if (layoutSpacing > 0 && !text.trimmed().isEmpty())
1692 height = std::max(height, (
int)(font->pixelSize() * layoutSpacing));
1694 return {width, height};
1701 bool isFirst,
bool isLast,
1702 int &left,
int &right)
const
1705 QFont *font = mythfont->
GetFace();
1706 QFontMetrics fm(*font);
1707 int basicPadding = fm.maxWidth() *
PAD_WIDTH;
1708 left = isFirst ? basicPadding : 0;
1710 (isLast ? basicPadding : 0);
1727 return mythfont->
face().family();
1745 LOG(VB_GENERAL, LOG_WARNING,
LOC +
"Failed to get subtitle reader.");
1747 LOG(VB_GENERAL, LOG_WARNING,
LOC +
"Failed to get CEA-608 reader.");
1749 LOG(VB_GENERAL, LOG_WARNING,
LOC +
"Failed to get CEA-708 reader.");
1756 QList<MythUIType *>::iterator it;
1757 QList<MythUIType *>::iterator itNext;
1761 std::chrono::milliseconds now =
1762 currentFrame ? currentFrame->
m_timecode : std::chrono::milliseconds::max();
1769 auto *wrapper =
dynamic_cast<SubWrapper *
>(child);
1774 std::chrono::milliseconds expireTime = wrapper->
GetExpireTime();
1775 if (expireTime > 0ms && expireTime < now)
1783 if (expireTime > 0ms && needRescale)
1785 auto *image =
dynamic_cast<SubImage *
>(child);
1789 QSize size = image->GetImage()->size();
1791 image->GetImage()->Resize(size);
1839 const auto *wrapper =
dynamic_cast<const SubWrapper *
>(img);
1840 if (wrapper && img->IsVisible())
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();
1856 auto *wrapper =
dynamic_cast<SubWrapper *
>(img);
1857 if (wrapper && img->IsVisible())
1858 img->SetArea(
MythRect(wrapper->GetOrigArea().translated(left, top)));
1868 QMutexLocker lock(&(subs->
m_lock));
1878 if (!currentFrame || !videoOut)
1908 AVSEEK_FLAG_BACKWARD);
1913#ifdef DEBUG_SUBTITLES
1916 LOG(VB_PLAYBACK, LOG_DEBUG,
1917 LOC + QString(
"time %1, no subtitle available after seek")
1928 AVSubtitle subtitle = subs->
m_buffers.front();
1929 if (subtitle.end_display_time < currentFrame->
m_timecode.count())
1932#ifdef DEBUG_SUBTITLES
1935 LOG(VB_PLAYBACK, LOG_DEBUG,
1936 LOC + QString(
"time %1, drop %2")
1951#ifdef DEBUG_SUBTITLES
1954 LOG(VB_PLAYBACK, LOG_DEBUG,
1955 LOC + QString(
"time %1, no subtitle available")
1968 [[maybe_unused]]
bool assForceNext {
false};
1971 AVSubtitle subtitle = subs->
m_buffers.front();
1972 if (subtitle.start_display_time > currentFrame->
m_timecode.count())
1974#ifdef DEBUG_SUBTITLES
1977 LOG(VB_PLAYBACK, LOG_DEBUG,
1978 LOC + QString(
"time %1, next %2")
1990 assForceNext =
true;
1992 auto displayfor = std::chrono::milliseconds(subtitle.end_display_time -
1993 subtitle.start_display_time);
1994#ifdef DEBUG_SUBTITLES
1997 LOG(VB_PLAYBACK, LOG_DEBUG,
1998 LOC + QString(
"time %1, show %2")
2002 if (displayfor == 0ms)
2004 displayfor = (displayfor < 50ms) ? 50ms : displayfor;
2005 std::chrono::milliseconds late = currentFrame->
m_timecode -
2006 std::chrono::milliseconds(subtitle.start_display_time);
2010 for (std::size_t i = 0; i < subtitle.num_rects; ++i)
2012 AVSubtitleRect* rect = subtitle.rects[i];
2014 bool displaysub =
true;
2016 subs->
m_buffers.front().end_display_time <
2022 if (displaysub && rect->type == SUBTITLE_BITMAP)
2024 QRect display(rect->display_x, rect->display_y,
2025 rect->display_w, rect->display_h);
2031 int right = rect->x + rect->w;
2032 int bottom = rect->y + rect->h;
2034 (currentFrame->
m_width < right) ||
2035 !display.width() || !display.height())
2037 int sd_height = 576;
2042 int height { 1080 };
2043 if ((currentFrame->
m_height <= sd_height) &&
2044 (bottom <= sd_height))
2046 else if ((currentFrame->
m_height <= 720) && bottom <= 720)
2050 if ((currentFrame->
m_width <= 720) && (right <= 720))
2052 else if ((currentFrame->
m_width <= 1280) &&
2055 display = QRect(0, 0, width, height);
2060 int uh = (display.height() / 2) - rect->y;
2061 std::chrono::milliseconds displayuntil = currentFrame->
m_timecode + displayfor;
2064 bbox = QRect(0, 0, rect->w, uh);
2066 rect->flags & AV_SUBTITLE_FLAG_FORCED,
2067 QString(
"avsub%1t").arg(i),
2068 displayuntil, late);
2074 int lh = rect->h - uh;
2077 bbox = QRect(0, uh, rect->w, lh);
2079 rect->flags & AV_SUBTITLE_FLAG_FORCED,
2080 QString(
"avsub%1b").arg(i),
2081 displayuntil, late);
2085 else if (displaysub && rect->type == SUBTITLE_ASS)
2088 AddAssEvent(rect->ass, subtitle.start_display_time, subtitle.end_display_time);
2095 RenderAssTrack(currentFrame->
m_timecode, assForceNext);
2100 QRect &bbox,
bool top,
2101 QRect &display,
int forced,
2102 const QString& imagename,
2103 std::chrono::milliseconds displayuntil,
2104 std::chrono::milliseconds late)
2109 bool prev_empty =
false;
2112 int xmin = bbox.right();
2113 int xmax = bbox.left();
2114 int ymin = bbox.bottom();
2115 int ymax = bbox.top();
2116 int ylast = bbox.top();
2117 int ysplit = bbox.bottom();
2120 for (
int y = bbox.top(); y <= bbox.bottom(); ++y)
2131 for (
int x = bbox.left(); x <= bbox.right(); ++x)
2133 const uint8_t color =
2134 rect->data[0][(y * rect->linesize[0]) + x];
2135 const uint32_t
pixel = *((uint32_t *)rect->data[1] + color);
2136 if (
pixel & 0xff000000)
2139 xmin = std::min(x, xmin);
2140 xmax = std::max(x, xmax);
2146 ymin = std::min(y, ymin);
2147 ymax = std::max(y, ymax);
2149 else if (!prev_empty)
2166 if (ymax == bbox.bottom())
2175 bbox.setRight(xmax);
2177 bbox.setBottom(ymax);
2183 QRect orig_rect(bbox.left(), bbox.top(), bbox.width(), bbox.height());
2185 QImage qImage(bbox.width(), bbox.height(), QImage::Format_ARGB32);
2186 for (
int y = 0; y < bbox.height(); ++y)
2188 int ysrc = y + bbox.top();
2189 for (
int x = 0; x < bbox.width(); ++x)
2191 int xsrc = x + bbox.left();
2192 const uint8_t color =
2193 rect->data[0][(ysrc * rect->linesize[0]) + xsrc];
2194 const uint32_t
pixel = *((uint32_t *)rect->data[1] + color);
2195 qImage.setPixel(x, y,
pixel);
2200 bbox.translate(rect->x, rect->y);
2209 if (scaled.size() != orig_rect.size())
2210 qImage = qImage.scaled(scaled.width(), scaled.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
2246 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"Display %1AV sub until %2ms")
2247 .arg(forced ?
"FORCED " :
"").arg(displayuntil.count()));
2249 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"AV Sub was %1ms late").arg(late.count()));
2252 return (ysplit + 1);
2260 std::chrono::milliseconds duration = 0ms;
2282 std::chrono::milliseconds start,
2283 std::chrono::milliseconds duration)
2286 duration,
this, subs);
2311 QMutexLocker locker(&textlist->
m_lock);
2333 for (
auto & window : cc708service->
m_windows)
2334 window.SetChanged();
2337 uint64_t clearMask = 0;
2338 QList<FormattedTextSubtitle *> addList;
2347 clearMask |= (1UL << i);
2352 QMutexLocker locker(&win.
m_lock);
2353 std::vector<CC708String*> list = win.
GetStrings();
2358 addList.append(fsub);
2366 if (!addList.empty())
2377 if (scaled.size() != pos.size())
2379 img = img.scaled(scaled.width(), scaled.height(),
2380 Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
2399static void myth_libass_log(
int level,
const char *fmt, va_list vl,
void *)
2401 uint64_t verbose_mask = VB_GENERAL;
2402 LogLevel_t verbose_level = LOG_INFO;
2407 verbose_level = LOG_EMERG;
2410 verbose_level = LOG_ERR;
2413 verbose_level = LOG_WARNING;
2416 verbose_level = LOG_INFO;
2420 verbose_level = LOG_DEBUG;
2429 static QMutex s_stringLock;
2430 s_stringLock.lock();
2432 QString str = QString::vasprintf(fmt, vl);
2433 LOG(verbose_mask, verbose_level, QString(
"libass: %1").arg(str));
2434 s_stringLock.unlock();
2437bool SubtitleScreen::InitialiseAssLibrary(
void)
2439 if (m_assLibrary && m_assRenderer)
2444 m_assLibrary = ass_library_init();
2448 ass_set_message_cb(m_assLibrary, myth_libass_log,
nullptr);
2449 ass_set_extract_fonts(m_assLibrary,
static_cast<int>(
true));
2450 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"Initialised libass object.");
2457 m_assRenderer = ass_renderer_init(m_assLibrary);
2465 const char *psz_font =
"/system/fonts/DroidSans.ttf";
2466 const char *psz_font_family =
"Droid Sans";
2468 const char *psz_font =
nullptr;
2469 const char *psz_font_family =
"sans-serif";
2471 ass_set_fonts(m_assRenderer, psz_font, psz_font_family, 1,
nullptr, 1);
2472 ass_set_hinting(m_assRenderer, ASS_HINTING_LIGHT);
2473 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"Initialised libass renderer.");
2479void SubtitleScreen::LoadAssFonts(
void)
2485 if (m_assFontCount == count)
2488 ass_clear_fonts(m_assLibrary);
2492 for (
uint i = 0; i < count; ++i)
2497 ass_add_font(m_assLibrary,
filename.data(), font.data(), font.size());
2498 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"Retrieved font '%1'")
2504void SubtitleScreen::CleanupAssLibrary(
void)
2509 ass_renderer_done(m_assRenderer);
2510 m_assRenderer =
nullptr;
2514 ass_clear_fonts(m_assLibrary);
2516 ass_library_done(m_assLibrary);
2518 m_assLibrary =
nullptr;
2521void SubtitleScreen::InitialiseAssTrack(
int tracknum)
2523 if (!InitialiseAssLibrary() || !
m_player)
2526 if (tracknum == m_assTrackNum && m_assTrack)
2531 m_assTrack = ass_new_track(m_assLibrary);
2532 m_assTrackNum = tracknum;
2535 if (header.isNull())
2539 header =
parser->GetSubHeader();
2541 if (!header.isNull())
2542 ass_process_codec_private(m_assTrack, header.data(), header.size());
2545 ResizeAssRenderer();
2548void SubtitleScreen::CleanupAssTrack(
void)
2551 ass_free_track(m_assTrack);
2552 m_assTrack =
nullptr;
2555void SubtitleScreen::AddAssEvent(
char *event, uint32_t starttime, uint32_t endtime)
2557 if (m_assTrack && event)
2558 ass_process_chunk(m_assTrack, event, strlen(event), starttime, endtime-starttime);
2561void SubtitleScreen::ResizeAssRenderer(
void)
2564 ass_set_margins(m_assRenderer, 0, 0, 0, 0);
2565 ass_set_use_margins(m_assRenderer,
static_cast<int>(
true));
2566 ass_set_font_scale(m_assRenderer, 1.0);
2569void SubtitleScreen::RenderAssTrack(std::chrono::milliseconds timecode,
bool force)
2571 if (!
m_player || !m_assRenderer || !m_assTrack)
2581 ResizeAssRenderer();
2584 ASS_Image *images = ass_render_frame(m_assRenderer, m_assTrack, timecode.count(), &changed);
2585 if (!changed && !
force)
2593 if (images->w == 0 || images->h == 0)
2595 images = images->next;
2599 uint8_t alpha = images->color & 0xFF;
2600 uint8_t blue = images->color >> 8 & 0xFF;
2601 uint8_t green = images->color >> 16 & 0xFF;
2602 uint8_t red = images->color >> 24 & 0xFF;
2606 images = images->next;
2610 QSize img_size(images->w, images->h);
2611 QRect img_rect(images->dst_x,images->dst_y,
2612 images->w, images->h);
2613 QImage qImage(img_size, QImage::Format_ARGB32);
2614 qImage.fill(0x00000000);
2616 unsigned char *src = images->bitmap;
2617 for (
int y = 0; y < images->h; ++y)
2619 for (
int x = 0; x < images->w; ++x)
2621 uint8_t value = src[x];
2624 uint32_t
pixel = (value * (255 - alpha) / 255 << 24) |
2625 (red << 16) | (green << 8) | blue;
2626 qImage.setPixel(x, y,
pixel);
2629 src += images->stride;
2638 QString name = QString(
"asssub%1").arg(count);
2649 images = images->next;
const uint k708AttrEdgeRightDropShadow
const uint k708AttrEdgeLeftDropShadow
const uint k708AttrEdgeUniform
const uint k708AttrSizeSmall
const uint k708AttrEdgeDepressed
const uint k708AttrFontSmallCaps
const uint k708AttrSizeLarge
const uint k708AttrEdgeRaised
MythDeque< AVSubtitle > m_buffers
std::vector< CC608Text * > m_buffers
CC608Buffer * GetOutputText(bool &changed, int &streamIdx)
void ClearBuffers(bool input, bool output, int outputStreamIdx=-1)
void SetEnabled(bool enable)
QColor GetBGColor(void) const
uint GetFGAlpha(void) const
QColor GetEdgeColor(void) const
QColor GetFGColor(void) const
uint GetBGAlpha(void) const
void SetEnabled(bool enable)
CC708Service * GetCurrentService(void)
std::array< CC708Window, k708MaxWindows > m_windows
bool GetExists(void) const
bool GetChanged(void) const
bool GetVisible(void) const
std::vector< CC708String * > GetStrings(void) const
static void DisposeStrings(std::vector< CC708String * > &strings)
virtual uint GetTrackCount(uint Type)
virtual void GetAttachmentData(uint, QByteArray &, QByteArray &)
virtual QByteArray GetSubHeader(uint)
MythFontProperties * m_textFont
QSize CalcSize(float layoutSpacing=0.0F) const
CC708CharacterAttribute m_format
const SubtitleScreen * m_parent
void CalcPadding(bool isFirst, bool isLast, int &left, int &right) const
bool Split(FormattedTextChunk &newChunk)
bool PreRender(bool isFirst, bool isLast, int &x, int y, int height)
QString ToLogString(void) const
QSize CalcSize(float layoutSpacing=0.0F) const
QList< FormattedTextChunk > chunks
void Init(const std::vector< CC608Text * > &buffers)
void Layout(void) override
void Init(const CC708Window &win, const std::vector< CC708String * > &list, float aspect)
void Init(const QStringList &subs)
void WrapLongLines(void) override
virtual void Layout(void)
SubtitleScreen * m_subScreen
virtual void WrapLongLines(void)
virtual int CacheNum(void) const
QStringList ToSRT(void) const
virtual void PreRender(void)
QVector< FormattedTextLine > m_lines
std::chrono::milliseconds m_start
std::chrono::milliseconds m_duration
void SaveSetting(const QString &key, int newValue)
int GetNumSetting(const QString &key, int defaultval=0)
bool hasOutline(void) const
bool hasShadow(void) const
void SetOutline(bool on, const QColor &color, int size, int alpha)
void GetShadow(QPoint &offset, QColor &color, int &alpha) const
void SetShadow(bool on, QPoint offset, const QColor &color, int alpha)
void GetOutline(QColor &color, int &size, int &alpha) const
void SetColor(const QColor &color)
void Assign(const QImage &img)
int DecrRef(void) override
Decrements reference count and deletes on 0.
MythImage * GetFormatImage()
Returns a blank reference counted image in the format required for the Draw functions for this painte...
virtual CC708Reader * GetCC708Reader(uint=0)
DecoderBase * GetDecoder(void)
Returns the stream decoder currently in use.
MythVideoOutput * GetVideoOutput(void)
float GetVideoAspect(void) const
virtual CC608Reader * GetCC608Reader(uint=0)
virtual SubtitleReader * GetSubReader(uint=0)
float GetFrameRate(void) const
Wrapper around QPoint allowing us to handle percentage and other relative values for positioning in m...
void setY(const QString &sY)
void setX(const QString &sX)
Wrapper around QRect allowing us to handle percentage and other relative values for areas in mythui.
Screen in which all other widgets are contained and rendered.
Image widget, displays a single image or multiple images in sequence.
QHash< int, MythImage * > m_images
void SetImage(MythImage *img)
Should not be used unless absolutely necessary since it bypasses the image caching and threaded loade...
A widget for rendering primitive shapes and lines.
void SetFillBrush(QBrush fill)
Simplified text widget, displays a text string.
The base class on which all widgets and screens are based.
bool AddFont(const QString &text, MythFontProperties *fontProp)
void AddChild(MythUIType *child)
Add a child UIType.
virtual void SetVisible(bool visible)
virtual void SetArea(const MythRect &rect)
virtual void Pulse(void)
Pulse is called 70 times a second to trigger a single frame of an animation.
void DeleteAllChildren(void)
Delete all child widgets.
void DeleteChild(const QString &name)
Delete a named child of this UIType.
QList< MythUIType * > m_childrenList
QRect GetDisplayVideoRect(void) const
std::chrono::milliseconds m_timecode
virtual void GetOSDBounds(QRect &Total, QRect &Visible, float &VisibleAspect, float &FontScaling, float ThemeAspect) const
QRect GetImageRect(QRect Rect, QRect *DisplayRect=nullptr)
translates caption/dvd button rectangle into 'screen' space
QRect GetSafeRect()
Returns a QRect describing an area of the screen on which it is 'safe' to render the On Screen Displa...
virtual MythVideoFrame * GetLastShownFrame()
Returns frame from the head of the ready to be displayed queue, if StartDisplayingFrame has been call...
SubImage(MythUIType *parent, const QString &name, const MythRect &area, std::chrono::milliseconds expireTime)
MythImage * GetImage(void)
SubShape(MythUIType *parent, const QString &name, const MythRect &area, int whichImageCache, std::chrono::milliseconds expireTime)
SubSimpleText(const QString &text, const MythFontProperties &font, QRect rect, Qt::Alignment align, MythUIType *parent, const QString &name, int whichImageCache, std::chrono::milliseconds expireTime)
const std::chrono::milliseconds m_swExpireTime
const MythRect m_swOrigArea
int GetWhichImageCache(void) const
std::chrono::milliseconds GetExpireTime(void) const
MythRect GetOrigArea(void) const
const int m_swWhichImageCache
SubWrapper(const MythRect &rect, std::chrono::milliseconds expireTime, int whichImageCache=-1)
void ClearRawTextSubtitles(void)
TextSubtitleParser * GetParser(void)
static void FreeAVSubtitle(AVSubtitle &sub)
QStringList GetRawTextSubtitles(std::chrono::milliseconds &duration)
void EnableRawTextSubtitles(bool enable)
void ClearAVSubtitles(void)
AVSubtitles * GetAVSubtitles(void)
void EnableAVSubtitles(bool enable)
void EnableTextSubtitles(bool enable)
void SeekFrame(int64_t ts, int flags)
int ReadNextSubtitle(void)
void Pulse(void) override
Pulse is called 70 times a second to trigger a single frame of an animation.
void DisplayCC608Subtitles(void)
~SubtitleScreen() override
static int GetTeletextBackgroundAlpha(void)
class SubtitleFormat * GetSubtitleFormat(void)
class SubtitleFormat * m_format
MythFontProperties * GetFont(const CC708CharacterAttribute &attr) const
void AddScaledImage(QImage &img, QRect &pos)
void ClearAllSubtitles(void)
void DisableForcedSubtitles(void)
CC608Reader * m_cc608reader
bool Create(void) override
std::chrono::milliseconds GetDelay(void) const
SubtitleScreen(MythPlayer *Player, MythPainter *Painter, const QString &Name, int FontStretch)
static QString GetTeletextFontName(void)
std::chrono::milliseconds m_textFontDurationExtensionMs
void Clear708Cache(uint64_t mask)
void SetElementDeleted(void)
std::chrono::milliseconds m_textFontDelayMsPrev
void DisplayAVSubtitles(void)
std::chrono::milliseconds m_textFontDurationExtensionMsPrev
void ClearNonDisplayedSubtitles(void)
void DisplayCC708Subtitles(void)
void DrawTextSubtitles(const QStringList &subs, std::chrono::milliseconds start, std::chrono::milliseconds duration)
void ResetElementState(void)
SubtitleReader * m_subreader
std::chrono::milliseconds m_textFontMinDurationMsPrev
void SetElementResized(void)
void SetZoom(int percent)
int DisplayScaledAVSubtitles(const AVSubtitleRect *rect, QRect &bbox, bool top, QRect &display, int forced, const QString &imagename, std::chrono::milliseconds displayuntil, std::chrono::milliseconds late)
QSize CalcTextSize(const QString &text, const CC708CharacterAttribute &format, float layoutSpacing) const
void DisplayDVDButton(AVSubtitle *dvdButton, QRect &buttonPos)
std::chrono::milliseconds m_textFontMinDurationMs
void SetDelay(std::chrono::milliseconds ms)
void DisplayRawTextSubtitles(void)
void SetElementAdded(void)
void EnableSubtitles(int type, bool forced_only=false)
void SetFontSize(int pixelSize)
std::chrono::milliseconds m_textFontDelayMs
void OptimiseDisplayedArea(void)
CC708Reader * m_cc708reader
void CalcPadding(const CC708CharacterAttribute &format, bool isFirst, bool isLast, int &left, int &right) const
QList< FormattedTextSubtitle * > m_qInited
void ClearDisplayedSubtitles(void)
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
static uint32_t * pixel
--------------------------------------------------—**
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
QString formatTime(std::chrono::milliseconds msecs, QString fmt)
Format a milliseconds time value.
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
bool isBlank(unsigned char median, float stddev, unsigned char maxmedian, float maxstddev)
static eu8 clamp(eu8 value, eu8 low, eu8 high)
static const QString kSubFamilyTeletext("teletext")
static const QString kSubFamilyAV("AV")
static const QString kSubFamily608("608")
static QSize CalcShadowOffsetPadding(MythFontProperties *mythfont)
static const QString kSubWindowName("osd_subtitle")
static const float PAD_HEIGHT
static const QString kSubFamily708("708")
static const QString kSubAttrShadowcolor("shadowcolor")
static const QString kSubAttrItalics("italics")
static const QString kSubAttrOutlinesize("outlinesize")
static const QString kSubAttrBGfill("bgfill")
static const QString kSubAttrColor("color")
static const QString kSubAttrPixelsize("pixelsize")
static const QString kSubAttrShadowalpha("shadowalpha")
static const QString kSubAttrOutlinealpha("outlinealpha")
static const QString kSubAttrUnderline("underline")
static const float PAD_WIDTH
static const QString kSubAttrBold("bold")
static const QString kSubFamilyText("text")
static QColor differentColor(const QColor &color)
static QString srtColorString(const QColor &color)
static QString extract_cc608(QString &text, int &color, bool &isItalic, bool &isUnderline)
Extract everything from the text buffer up until the next format control character.
static QString fontToString(MythFontProperties *f)
static const QString kSubAttrShadowoffset("shadowoffset")
static const QString kSubFileName("osd_subtitle.xml")
static const QString kSubAttrOutline("outline")
static const QString kSubAttrShadow("shadow")
static const QString kSubAttrOutlinecolor("outlinecolor")
static const QString kSubProvider("provider")
static const float LINE_SPACING
@ kDisplayRawTextSubtitle