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 result.reserve(
m_lines.size());
953 for (
const auto & ftl : std::as_const(
m_lines))
957 line.fill(
' ', ftl.m_origX);
958 QList<FormattedTextChunk>::const_iterator chunk;
959 for (chunk = ftl.chunks.constBegin();
960 chunk != ftl.chunks.constEnd();
963 const QString &text = (*chunk).m_text;
975 line += QString(
"<font color=\"%1\">")
982 line += QString(
"</font>");
991 if (!line.trimmed().isEmpty())
1021 bool isItalic =
false;
1022 bool isBold =
false;
1023 bool isUnderline =
false;
1024 QColor color(Qt::white);
1025 static const QRegularExpression htmlTag {
1026 "</?.+>", QRegularExpression::InvertedGreedinessOption };
1027 QString htmlPrefix(
"<font color=\"");
1028 QString htmlSuffix(
"\">");
1029 for (
const QString& subtitle : std::as_const(subs))
1032 QString text(subtitle);
1033 while (!text.isEmpty())
1035 auto match = htmlTag.match(text);
1036 int pos = match.capturedStart();
1043 text = (pos < 0 ?
"" : text.mid(pos));
1044 LOG(VB_VBI, LOG_INFO, QString(
"Adding SRT chunk: %1")
1049 int htmlLen = match.capturedLength();
1050 QString html = text.left(htmlLen).toLower();
1051 text = text.mid(htmlLen);
1056 else if (html ==
"</i>")
1060 else if (html.startsWith(htmlPrefix) &&
1061 html.endsWith(htmlSuffix))
1063 int colorLen = html.length() -
1064 (htmlPrefix.length() + htmlSuffix.length());
1065 QString colorString(
1066 html.mid(htmlPrefix.length(), colorLen));
1067 QColor newColor(colorString);
1068 if (newColor.isValid())
1074 LOG(VB_VBI, LOG_INFO,
1075 QString(
"Ignoring invalid SRT color specification: "
1076 "'%1'").arg(colorString));
1079 else if (html ==
"</font>")
1083 else if (html ==
"<b>")
1087 else if (html ==
"</b>")
1091 else if (html ==
"<u>")
1095 else if (html ==
"</u>")
1097 isUnderline =
false;
1101 LOG(VB_VBI, LOG_INFO,
1102 QString(
"Ignoring unknown SRT formatting: '%1'")
1106 LOG(VB_VBI, LOG_INFO,
1107 QString(
"SRT formatting change '%1', "
1108 "new ital=%2 bold=%3 uline=%4 color=%5)")
1109 .arg(html).arg(isItalic).arg(isBold).arg(isUnderline)
1120 for (
int i = 0; i <
m_lines.size(); i++)
1122 int width =
m_lines[i].CalcSize().width();
1125 while (width > maxWidth &&
m_lines[i].chunks.size() > 1)
1127 width -=
m_lines[i].chunks.back().CalcSize().width();
1133 LOG(VB_VBI, LOG_INFO,
1134 QString(
"Wrapping chunk to next line: '%1'")
1135 .arg(
m_lines[i+1].chunks[0].m_text));
1139 bool isSplitPossible =
true;
1140 while (width > maxWidth && isSplitPossible)
1143 isSplitPossible =
m_lines[i].chunks.back().Split(newChunk);
1144 if (isSplitPossible)
1150 m_lines[i+1].chunks.prepend(newChunk);
1151 width =
m_lines[i].CalcSize().width();
1163 bool &isItalic,
bool &isUnderline)
1168 if (text.length() >= 1 && text[0] >= QChar(0x7000))
1170 int op = text[0].unicode() - 0x7000;
1171 isUnderline = ((op & 0x1) != 0);
1187 color = (op & 0xf) >> 1;
1196 static const QRegularExpression kControlCharsRE {
"[\\x{7000}-\\x{7fff}]" };
1197 int nextControl = text.indexOf(kControlCharsRE);
1198 if (nextControl < 0)
1205 result = text.left(nextControl);
1206 text = text.mid(nextControl);
1218 int totalHeight = 0;
1222 QVector<int> heights(
m_lines.size());
1223 QVector<int> spaceBefore(
m_lines.size());
1225 for (
int i = 0; i <
m_lines.size(); i++)
1231 int height =
m_lines[i].CalcSize().height();
1232 heights[i] = height;
1233 spaceBefore[i] = y -
prevY;
1234 totalSpace += (y -
prevY);
1236 totalHeight += height;
1239 int overage = std::min(totalHeight - safeHeight, totalSpace);
1243 if (overage > 0 && totalSpace > 0)
1245 float shrink = (totalSpace - overage) / (
float)totalSpace;
1247 for (
int i = 0; i <
m_lines.size(); i++)
1249 m_lines[i].m_yIndent =
prevY + (spaceBefore[i] * shrink);
1255 int shift = std::min(firstY, std::max(0,
prevY - safeHeight));
1257 for (
int i = 0; i <
m_lines.size(); i++)
1258 m_lines[i].m_yIndent -= shift;
1265 static const std::array<const QColor,8> kClr
1267 Qt::white, Qt::green, Qt::blue, Qt::cyan,
1268 Qt::red, Qt::yellow, Qt::magenta, Qt::white,
1286 QFontMetrics fm(*font);
1287 fontwidth = fm.averageCharWidth();
1294 for (; i !=
buffers.cend(); ++i)
1298 bool isItalic =
false;
1299 bool isUnderline =
false;
1300 const bool isBold =
false;
1301 QString text(cc->
m_text);
1303 int orig_x = cc->
m_x;
1311 x = xmid + ((orig_x - (xscale / 2)) * fontwidth);
1316 x = (orig_x + 3) *
m_safeArea.width() / xscale;
1319 int orig_y = cc->
m_y;
1321 if (orig_y < yscale / 2)
1324 y = (orig_y *
m_safeArea.height() * zoom / (yscale * 100));
1330 ((yscale - orig_y - 0.5) *
m_safeArea.height() * zoom /
1335 while (!text.isNull())
1337 QString captionText =
1343 LOG(VB_VBI, LOG_INFO,
1344 QString(
"Adding cc608 chunk (%1,%2): %3")
1360 shape->SetFillBrush(fill);
1369 const std::vector<CC708String*> &list,
1372 LOG(VB_VBI, LOG_DEBUG,
LOC +
1373 QString(
"Display Win %1, Anchor_id %2, x_anch %3, y_anch %4, "
1381 float xrange { 160.0F };
1384 else if (aspect > 1.4F)
1387 float xmult = (float)
m_safeArea.width() / xrange;
1388 float ymult = (float)
m_safeArea.height() / yrange;
1396 for (
auto *str708 : list)
1399 m_lines.resize(str708->m_y + 1);
1401 m_lines[str708->m_y].chunks += chunk;
1402 LOG(VB_VBI, LOG_INFO, QString(
"Adding cc708 chunk: win %1 row %2: %3")
1410 const QString &
Name,
int FontStretch)
1413 m_fontStretch(FontStretch),
1420 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"SeekingDone signal received.");
1430 CleanupAssLibrary();
1464 if (!forced_only ||
m_family.isEmpty()) {
1507 ass_flush_events(m_assTrack);
1545 AVSubtitleRect *hl_button = dvdButton->rects[0];
1546 uint h = hl_button->h;
1547 uint w = hl_button->w;
1548 QRect rect = QRect(hl_button->x, hl_button->y, w, h);
1549 QImage bg_image(hl_button->data[0], w, h, w, QImage::Format_Indexed8);
1550 auto *bgpalette = (uint32_t *)(hl_button->data[1]);
1552 QVector<uint32_t> bg_palette(4);
1553 for (
int i = 0; i < 4; i++)
1554 bg_palette[i] = bgpalette[i];
1555 bg_image.setColorTable(bg_palette);
1558 const QRect fg_rect(buttonPos.translated(-hl_button->x, -hl_button->y));
1559 QImage fg_image = bg_image.copy(fg_rect);
1560 QVector<uint32_t> fg_palette(4);
1561 auto *fgpalette = (uint32_t *)(dvdButton->rects[1]->data[1]);
1564 for (
int i = 0; i < 4; i++)
1565 fg_palette[i] = fgpalette[i];
1566 fg_image.setColorTable(fg_palette);
1569 bg_image = bg_image.convertToFormat(QImage::Format_ARGB32);
1570 fg_image = fg_image.convertToFormat(QImage::Format_ARGB32);
1573 for (
int x=fg_rect.x(); x < fg_rect.x()+fg_rect.width(); ++x)
1575 if ((x < 0) || (x > hl_button->w))
1577 for (
int y=fg_rect.y(); y < fg_rect.y()+fg_rect.height(); ++y)
1579 if ((y < 0) || (y > hl_button->h))
1581 bg_image.setPixel(x, y, fg_image.pixel(x-fg_rect.x(),y-fg_rect.y()));
1616 auto *wrapper =
dynamic_cast<SubWrapper *
>(child);
1620 if (whichImageCache != -1 && (mask & (1UL << whichImageCache)))
1660 int outlineSize = 0;
1661 int shadowWidth = 0;
1662 int shadowHeight = 0;
1665 mythfont->
GetOutline(color, outlineSize, alpha);
1666 MythPoint outline(outlineSize, outlineSize);
1668 outlineSize = outline.x();
1673 mythfont->
GetShadow(shadowOffset, color, alpha);
1675 shadowWidth = abs(shadowOffset.x());
1676 shadowHeight = abs(shadowOffset.y());
1678 shadowWidth = std::max(shadowWidth, outlineSize);
1679 shadowHeight = std::max(shadowHeight, outlineSize);
1681 return {shadowWidth + outlineSize, shadowHeight + outlineSize};
1686 float layoutSpacing)
const
1689 QFont *font = mythfont->
GetFace();
1690 QFontMetrics fm(*font);
1691 int width = fm.horizontalAdvance(text);
1693 if (layoutSpacing > 0 && !text.trimmed().isEmpty())
1694 height = std::max(height, (
int)(font->pixelSize() * layoutSpacing));
1696 return {width, height};
1703 bool isFirst,
bool isLast,
1704 int &left,
int &right)
const
1707 QFont *font = mythfont->
GetFace();
1708 QFontMetrics fm(*font);
1709 int basicPadding = fm.maxWidth() *
PAD_WIDTH;
1710 left = isFirst ? basicPadding : 0;
1712 (isLast ? basicPadding : 0);
1729 return mythfont->
face().family();
1747 LOG(VB_GENERAL, LOG_WARNING,
LOC +
"Failed to get subtitle reader.");
1749 LOG(VB_GENERAL, LOG_WARNING,
LOC +
"Failed to get CEA-608 reader.");
1751 LOG(VB_GENERAL, LOG_WARNING,
LOC +
"Failed to get CEA-708 reader.");
1760 std::chrono::milliseconds now =
1761 currentFrame ? currentFrame->
m_timecode : std::chrono::milliseconds::max();
1766 auto *wrapper =
dynamic_cast<SubWrapper *
>(child);
1771 std::chrono::milliseconds expireTime = wrapper->
GetExpireTime();
1772 if (expireTime > 0ms && expireTime < now)
1780 if (expireTime > 0ms && needRescale)
1782 auto *image =
dynamic_cast<SubImage *
>(child);
1786 QSize size = image->GetImage()->size();
1788 image->GetImage()->Resize(size);
1836 const auto *wrapper =
dynamic_cast<const SubWrapper *
>(img);
1837 if (wrapper && img->IsVisible())
1838 visible = visible.united(wrapper->GetOrigArea());
1841 if (visible.isEmpty())
1844 QRect bounding = visible.boundingRect();
1845 bounding = bounding.translated(
m_safeArea.topLeft());
1847 int left =
m_safeArea.left() - bounding.left();
1853 auto *wrapper =
dynamic_cast<SubWrapper *
>(img);
1854 if (wrapper && img->IsVisible())
1855 img->SetArea(
MythRect(wrapper->GetOrigArea().translated(left, top)));
1865 QMutexLocker lock(&(subs->
m_lock));
1875 if (!currentFrame || !videoOut)
1905 AVSEEK_FLAG_BACKWARD);
1910#ifdef DEBUG_SUBTITLES
1913 LOG(VB_PLAYBACK, LOG_DEBUG,
1914 LOC + QString(
"time %1, no subtitle available after seek")
1925 AVSubtitle subtitle = subs->
m_buffers.front();
1926 if (subtitle.end_display_time < currentFrame->
m_timecode.count())
1929#ifdef DEBUG_SUBTITLES
1932 LOG(VB_PLAYBACK, LOG_DEBUG,
1933 LOC + QString(
"time %1, drop %2")
1948#ifdef DEBUG_SUBTITLES
1951 LOG(VB_PLAYBACK, LOG_DEBUG,
1952 LOC + QString(
"time %1, no subtitle available")
1965 [[maybe_unused]]
bool assForceNext {
false};
1968 AVSubtitle subtitle = subs->
m_buffers.front();
1969 if (subtitle.start_display_time > currentFrame->
m_timecode.count())
1971#ifdef DEBUG_SUBTITLES
1974 LOG(VB_PLAYBACK, LOG_DEBUG,
1975 LOC + QString(
"time %1, next %2")
1987 assForceNext =
true;
1989 auto displayfor = std::chrono::milliseconds(subtitle.end_display_time -
1990 subtitle.start_display_time);
1991#ifdef DEBUG_SUBTITLES
1994 LOG(VB_PLAYBACK, LOG_DEBUG,
1995 LOC + QString(
"time %1, show %2")
1999 if (displayfor == 0ms)
2001 displayfor = (displayfor < 50ms) ? 50ms : displayfor;
2002 std::chrono::milliseconds late = currentFrame->
m_timecode -
2003 std::chrono::milliseconds(subtitle.start_display_time);
2007 for (std::size_t i = 0; i < subtitle.num_rects; ++i)
2009 AVSubtitleRect* rect = subtitle.rects[i];
2011 bool displaysub =
true;
2013 subs->
m_buffers.front().end_display_time <
2019 if (displaysub && rect->type == SUBTITLE_BITMAP)
2021 QRect display(rect->display_x, rect->display_y,
2022 rect->display_w, rect->display_h);
2028 int right = rect->x + rect->w;
2029 int bottom = rect->y + rect->h;
2031 (currentFrame->
m_width < right) ||
2032 !display.width() || !display.height())
2034 int sd_height = 576;
2039 int height { 1080 };
2040 if ((currentFrame->
m_height <= sd_height) &&
2041 (bottom <= sd_height))
2043 else if ((currentFrame->
m_height <= 720) && bottom <= 720)
2047 if ((currentFrame->
m_width <= 720) && (right <= 720))
2049 else if ((currentFrame->
m_width <= 1280) &&
2052 display = QRect(0, 0, width, height);
2057 int uh = (display.height() / 2) - rect->y;
2058 std::chrono::milliseconds displayuntil = currentFrame->
m_timecode + displayfor;
2061 bbox = QRect(0, 0, rect->w, uh);
2063 rect->flags & AV_SUBTITLE_FLAG_FORCED,
2064 QString(
"avsub%1t").arg(i),
2065 displayuntil, late);
2071 int lh = rect->h - uh;
2074 bbox = QRect(0, uh, rect->w, lh);
2076 rect->flags & AV_SUBTITLE_FLAG_FORCED,
2077 QString(
"avsub%1b").arg(i),
2078 displayuntil, late);
2082 else if (displaysub && rect->type == SUBTITLE_ASS)
2085 AddAssEvent(rect->ass, subtitle.start_display_time, subtitle.end_display_time);
2092 RenderAssTrack(currentFrame->
m_timecode, assForceNext);
2097 QRect &bbox,
bool top,
2098 QRect &display,
int forced,
2099 const QString& imagename,
2100 std::chrono::milliseconds displayuntil,
2101 std::chrono::milliseconds late)
2106 bool prev_empty =
false;
2109 int xmin = bbox.right();
2110 int xmax = bbox.left();
2111 int ymin = bbox.bottom();
2112 int ymax = bbox.top();
2113 int ylast = bbox.top();
2114 int ysplit = bbox.bottom();
2117 for (
int y = bbox.top(); y <= bbox.bottom(); ++y)
2128 for (
int x = bbox.left(); x <= bbox.right(); ++x)
2130 const uint8_t color =
2131 rect->data[0][(y * rect->linesize[0]) + x];
2132 const uint32_t
pixel = *((uint32_t *)rect->data[1] + color);
2133 if (
pixel & 0xff000000)
2136 xmin = std::min(x, xmin);
2137 xmax = std::max(x, xmax);
2143 ymin = std::min(y, ymin);
2144 ymax = std::max(y, ymax);
2146 else if (!prev_empty)
2163 if (ymax == bbox.bottom())
2172 bbox.setRight(xmax);
2174 bbox.setBottom(ymax);
2180 QRect orig_rect(bbox.left(), bbox.top(), bbox.width(), bbox.height());
2182 QImage qImage(bbox.width(), bbox.height(), QImage::Format_ARGB32);
2183 for (
int y = 0; y < bbox.height(); ++y)
2185 int ysrc = y + bbox.top();
2186 for (
int x = 0; x < bbox.width(); ++x)
2188 int xsrc = x + bbox.left();
2189 const uint8_t color =
2190 rect->data[0][(ysrc * rect->linesize[0]) + xsrc];
2191 const uint32_t
pixel = *((uint32_t *)rect->data[1] + color);
2192 qImage.setPixel(x, y,
pixel);
2197 bbox.translate(rect->x, rect->y);
2206 if (scaled.size() != orig_rect.size())
2207 qImage = qImage.scaled(scaled.width(), scaled.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
2243 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"Display %1AV sub until %2ms")
2244 .arg(forced ?
"FORCED " :
"").arg(displayuntil.count()));
2246 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"AV Sub was %1ms late").arg(late.count()));
2249 return (ysplit + 1);
2257 std::chrono::milliseconds duration = 0ms;
2279 std::chrono::milliseconds start,
2280 std::chrono::milliseconds duration)
2283 duration,
this, subs);
2308 QMutexLocker locker(&textlist->
m_lock);
2330 for (
auto & window : cc708service->
m_windows)
2331 window.SetChanged();
2334 uint64_t clearMask = 0;
2335 QList<FormattedTextSubtitle *> addList;
2344 clearMask |= (1UL << i);
2349 QMutexLocker locker(&win.
m_lock);
2350 std::vector<CC708String*> list = win.
GetStrings();
2355 addList.append(fsub);
2363 if (!addList.empty())
2374 if (scaled.size() != pos.size())
2376 img = img.scaled(scaled.width(), scaled.height(),
2377 Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
2396static void myth_libass_log(
int level,
const char *fmt, va_list vl,
void *)
2398 uint64_t verbose_mask = VB_GENERAL;
2399 LogLevel_t verbose_level = LOG_INFO;
2404 verbose_level = LOG_EMERG;
2407 verbose_level = LOG_ERR;
2410 verbose_level = LOG_WARNING;
2413 verbose_level = LOG_INFO;
2417 verbose_level = LOG_DEBUG;
2426 static QMutex s_stringLock;
2427 s_stringLock.lock();
2429 QString str = QString::vasprintf(fmt, vl);
2430 LOG(verbose_mask, verbose_level, QString(
"libass: %1").arg(str));
2431 s_stringLock.unlock();
2434bool SubtitleScreen::InitialiseAssLibrary(
void)
2436 if (m_assLibrary && m_assRenderer)
2441 m_assLibrary = ass_library_init();
2445 ass_set_message_cb(m_assLibrary, myth_libass_log,
nullptr);
2446 ass_set_extract_fonts(m_assLibrary,
static_cast<int>(
true));
2447 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"Initialised libass object.");
2454 m_assRenderer = ass_renderer_init(m_assLibrary);
2462 const char *psz_font =
"/system/fonts/DroidSans.ttf";
2463 const char *psz_font_family =
"Droid Sans";
2465 const char *psz_font =
nullptr;
2466 const char *psz_font_family =
"sans-serif";
2468 ass_set_fonts(m_assRenderer, psz_font, psz_font_family, 1,
nullptr, 1);
2469 ass_set_hinting(m_assRenderer, ASS_HINTING_LIGHT);
2470 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"Initialised libass renderer.");
2476void SubtitleScreen::LoadAssFonts(
void)
2482 if (m_assFontCount == count)
2485 ass_clear_fonts(m_assLibrary);
2489 for (
uint i = 0; i < count; ++i)
2494 ass_add_font(m_assLibrary,
filename.data(), font.data(), font.size());
2495 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"Retrieved font '%1'")
2501void SubtitleScreen::CleanupAssLibrary(
void)
2506 ass_renderer_done(m_assRenderer);
2507 m_assRenderer =
nullptr;
2511 ass_clear_fonts(m_assLibrary);
2513 ass_library_done(m_assLibrary);
2515 m_assLibrary =
nullptr;
2518void SubtitleScreen::InitialiseAssTrack(
int tracknum)
2520 if (!InitialiseAssLibrary() || !
m_player)
2523 if (tracknum == m_assTrackNum && m_assTrack)
2528 m_assTrack = ass_new_track(m_assLibrary);
2529 m_assTrackNum = tracknum;
2532 if (header.isNull())
2536 header =
parser->GetSubHeader();
2538 if (!header.isNull())
2539 ass_process_codec_private(m_assTrack, header.data(), header.size());
2542 ResizeAssRenderer();
2545void SubtitleScreen::CleanupAssTrack(
void)
2548 ass_free_track(m_assTrack);
2549 m_assTrack =
nullptr;
2552void SubtitleScreen::AddAssEvent(
char *event, uint32_t starttime, uint32_t endtime)
2554 if (m_assTrack && event)
2555 ass_process_chunk(m_assTrack, event, strlen(event), starttime, endtime-starttime);
2558void SubtitleScreen::ResizeAssRenderer(
void)
2561 ass_set_margins(m_assRenderer, 0, 0, 0, 0);
2562 ass_set_use_margins(m_assRenderer,
static_cast<int>(
true));
2563 ass_set_font_scale(m_assRenderer, 1.0);
2566void SubtitleScreen::RenderAssTrack(std::chrono::milliseconds timecode,
bool force)
2568 if (!
m_player || !m_assRenderer || !m_assTrack)
2578 ResizeAssRenderer();
2581 ASS_Image *images = ass_render_frame(m_assRenderer, m_assTrack, timecode.count(), &changed);
2582 if (!changed && !
force)
2590 if (images->w == 0 || images->h == 0)
2592 images = images->next;
2596 uint8_t alpha = images->color & 0xFF;
2597 uint8_t blue = images->color >> 8 & 0xFF;
2598 uint8_t green = images->color >> 16 & 0xFF;
2599 uint8_t red = images->color >> 24 & 0xFF;
2603 images = images->next;
2607 QSize img_size(images->w, images->h);
2608 QRect img_rect(images->dst_x,images->dst_y,
2609 images->w, images->h);
2610 QImage qImage(img_size, QImage::Format_ARGB32);
2611 qImage.fill(0x00000000);
2613 unsigned char *src = images->bitmap;
2614 for (
int y = 0; y < images->h; ++y)
2616 for (
int x = 0; x < images->w; ++x)
2618 uint8_t value = src[x];
2621 uint32_t
pixel = (value * (255 - alpha) / 255 << 24) |
2622 (red << 16) | (green << 8) | blue;
2623 qImage.setPixel(x, y,
pixel);
2626 src += images->stride;
2635 QString name = QString(
"asssub%1").arg(count);
2646 images = images->next;
2652#include "moc_subtitlescreen.cpp"
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 std::vector< 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