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);
1055 else if (html ==
"</i>")
1059 else if (html.startsWith(htmlPrefix) &&
1060 html.endsWith(htmlSuffix))
1062 int colorLen = html.length() -
1063 (htmlPrefix.length() + htmlSuffix.length());
1064 QString colorString(
1065 html.mid(htmlPrefix.length(), colorLen));
1066 QColor newColor(colorString);
1067 if (newColor.isValid())
1073 LOG(VB_VBI, LOG_INFO,
1074 QString(
"Ignoring invalid SRT color specification: "
1075 "'%1'").arg(colorString));
1078 else if (html ==
"</font>")
1082 else if (html ==
"<b>")
1086 else if (html ==
"</b>")
1090 else if (html ==
"<u>")
1094 else if (html ==
"</u>")
1096 isUnderline =
false;
1100 LOG(VB_VBI, LOG_INFO,
1101 QString(
"Ignoring unknown SRT formatting: '%1'")
1105 LOG(VB_VBI, LOG_INFO,
1106 QString(
"SRT formatting change '%1', "
1107 "new ital=%2 bold=%3 uline=%4 color=%5)")
1108 .arg(html).arg(isItalic).arg(isBold).arg(isUnderline)
1119 for (
int i = 0; i <
m_lines.size(); i++)
1121 int width =
m_lines[i].CalcSize().width();
1124 while (width > maxWidth &&
m_lines[i].chunks.size() > 1)
1126 width -=
m_lines[i].chunks.back().CalcSize().width();
1132 LOG(VB_VBI, LOG_INFO,
1133 QString(
"Wrapping chunk to next line: '%1'")
1134 .arg(
m_lines[i+1].chunks[0].m_text));
1138 bool isSplitPossible =
true;
1139 while (width > maxWidth && isSplitPossible)
1142 isSplitPossible =
m_lines[i].chunks.back().Split(newChunk);
1143 if (isSplitPossible)
1149 m_lines[i+1].chunks.prepend(newChunk);
1150 width =
m_lines[i].CalcSize().width();
1162 bool &isItalic,
bool &isUnderline)
1167 if (text.length() >= 1 && text[0] >= QChar(0x7000))
1169 int op = text[0].unicode() - 0x7000;
1170 isUnderline = ((op & 0x1) != 0);
1186 color = (op & 0xf) >> 1;
1195 static const QRegularExpression kControlCharsRE {
"[\\x{7000}-\\x{7fff}]" };
1196 int nextControl = text.indexOf(kControlCharsRE);
1197 if (nextControl < 0)
1204 result = text.left(nextControl);
1205 text = text.mid(nextControl);
1217 int totalHeight = 0;
1221 QVector<int> heights(
m_lines.size());
1222 QVector<int> spaceBefore(
m_lines.size());
1224 for (
int i = 0; i <
m_lines.size(); i++)
1230 int height =
m_lines[i].CalcSize().height();
1231 heights[i] = height;
1232 spaceBefore[i] = y -
prevY;
1233 totalSpace += (y -
prevY);
1235 totalHeight += height;
1238 int overage = std::min(totalHeight - safeHeight, totalSpace);
1242 if (overage > 0 && totalSpace > 0)
1244 float shrink = (totalSpace - overage) / (
float)totalSpace;
1246 for (
int i = 0; i <
m_lines.size(); i++)
1248 m_lines[i].m_yIndent =
prevY + (spaceBefore[i] * shrink);
1254 int shift = std::min(firstY, std::max(0,
prevY - safeHeight));
1256 for (
int i = 0; i <
m_lines.size(); i++)
1257 m_lines[i].m_yIndent -= shift;
1264 static const std::array<const QColor,8> kClr
1266 Qt::white, Qt::green, Qt::blue, Qt::cyan,
1267 Qt::red, Qt::yellow, Qt::magenta, Qt::white,
1285 QFontMetrics fm(*font);
1286 fontwidth = fm.averageCharWidth();
1293 for (; i !=
buffers.cend(); ++i)
1297 bool isItalic =
false;
1298 bool isUnderline =
false;
1299 const bool isBold =
false;
1300 QString text(cc->
m_text);
1302 int orig_x = cc->
m_x;
1310 x = xmid + ((orig_x - (xscale / 2)) * fontwidth);
1315 x = (orig_x + 3) *
m_safeArea.width() / xscale;
1318 int orig_y = cc->
m_y;
1320 if (orig_y < yscale / 2)
1323 y = (orig_y *
m_safeArea.height() * zoom / (yscale * 100));
1329 ((yscale - orig_y - 0.5) *
m_safeArea.height() * zoom /
1334 while (!text.isNull())
1336 QString captionText =
1342 LOG(VB_VBI, LOG_INFO,
1343 QString(
"Adding cc608 chunk (%1,%2): %3")
1359 shape->SetFillBrush(fill);
1368 const std::vector<CC708String*> &list,
1371 LOG(VB_VBI, LOG_DEBUG,
LOC +
1372 QString(
"Display Win %1, Anchor_id %2, x_anch %3, y_anch %4, "
1380 float xrange { 160.0F };
1383 else if (aspect > 1.4F)
1386 float xmult = (float)
m_safeArea.width() / xrange;
1387 float ymult = (float)
m_safeArea.height() / yrange;
1395 for (
auto *str708 : list)
1398 m_lines.resize(str708->m_y + 1);
1400 m_lines[str708->m_y].chunks += chunk;
1401 LOG(VB_VBI, LOG_INFO, QString(
"Adding cc708 chunk: win %1 row %2: %3")
1409 const QString &
Name,
int FontStretch)
1412 m_fontStretch(FontStretch),
1419 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"SeekingDone signal received.");
1429 CleanupAssLibrary();
1463 if (!forced_only ||
m_family.isEmpty()) {
1506 ass_flush_events(m_assTrack);
1544 AVSubtitleRect *hl_button = dvdButton->rects[0];
1545 uint h = hl_button->h;
1546 uint w = hl_button->w;
1547 QRect rect = QRect(hl_button->x, hl_button->y, w, h);
1548 QImage bg_image(hl_button->data[0], w, h, w, QImage::Format_Indexed8);
1549 auto *bgpalette = (uint32_t *)(hl_button->data[1]);
1551 QVector<uint32_t> bg_palette(4);
1552 for (
int i = 0; i < 4; i++)
1553 bg_palette[i] = bgpalette[i];
1554 bg_image.setColorTable(bg_palette);
1557 const QRect fg_rect(buttonPos.translated(-hl_button->x, -hl_button->y));
1558 QImage fg_image = bg_image.copy(fg_rect);
1559 QVector<uint32_t> fg_palette(4);
1560 auto *fgpalette = (uint32_t *)(dvdButton->rects[1]->data[1]);
1563 for (
int i = 0; i < 4; i++)
1564 fg_palette[i] = fgpalette[i];
1565 fg_image.setColorTable(fg_palette);
1568 bg_image = bg_image.convertToFormat(QImage::Format_ARGB32);
1569 fg_image = fg_image.convertToFormat(QImage::Format_ARGB32);
1572 for (
int x=fg_rect.x(); x < fg_rect.x()+fg_rect.width(); ++x)
1574 if ((x < 0) || (x > hl_button->w))
1576 for (
int y=fg_rect.y(); y < fg_rect.y()+fg_rect.height(); ++y)
1578 if ((y < 0) || (y > hl_button->h))
1580 bg_image.setPixel(x, y, fg_image.pixel(x-fg_rect.x(),y-fg_rect.y()));
1615 auto *wrapper =
dynamic_cast<SubWrapper *
>(child);
1619 if (whichImageCache != -1 && (mask & (1UL << whichImageCache)))
1659 int outlineSize = 0;
1660 int shadowWidth = 0;
1661 int shadowHeight = 0;
1664 mythfont->
GetOutline(color, outlineSize, alpha);
1665 MythPoint outline(outlineSize, outlineSize);
1667 outlineSize = outline.x();
1672 mythfont->
GetShadow(shadowOffset, color, alpha);
1674 shadowWidth = abs(shadowOffset.x());
1675 shadowHeight = abs(shadowOffset.y());
1677 shadowWidth = std::max(shadowWidth, outlineSize);
1678 shadowHeight = std::max(shadowHeight, outlineSize);
1680 return {shadowWidth + outlineSize, shadowHeight + outlineSize};
1685 float layoutSpacing)
const
1688 QFont *font = mythfont->
GetFace();
1689 QFontMetrics fm(*font);
1690 int width = fm.horizontalAdvance(text);
1692 if (layoutSpacing > 0 && !text.trimmed().isEmpty())
1693 height = std::max(height, (
int)(font->pixelSize() * layoutSpacing));
1695 return {width, height};
1702 bool isFirst,
bool isLast,
1703 int &left,
int &right)
const
1706 QFont *font = mythfont->
GetFace();
1707 QFontMetrics fm(*font);
1708 int basicPadding = fm.maxWidth() *
PAD_WIDTH;
1709 left = isFirst ? basicPadding : 0;
1711 (isLast ? basicPadding : 0);
1728 return mythfont->
face().family();
1746 LOG(VB_GENERAL, LOG_WARNING,
LOC +
"Failed to get subtitle reader.");
1748 LOG(VB_GENERAL, LOG_WARNING,
LOC +
"Failed to get CEA-608 reader.");
1750 LOG(VB_GENERAL, LOG_WARNING,
LOC +
"Failed to get CEA-708 reader.");
1759 std::chrono::milliseconds now =
1760 currentFrame ? currentFrame->
m_timecode : std::chrono::milliseconds::max();
1765 auto *wrapper =
dynamic_cast<SubWrapper *
>(child);
1770 std::chrono::milliseconds expireTime = wrapper->
GetExpireTime();
1771 if (expireTime > 0ms && expireTime < now)
1779 if (expireTime > 0ms && needRescale)
1781 auto *image =
dynamic_cast<SubImage *
>(child);
1785 QSize size = image->GetImage()->size();
1787 image->GetImage()->Resize(size);
1835 const auto *wrapper =
dynamic_cast<const SubWrapper *
>(img);
1836 if (wrapper && img->IsVisible())
1837 visible = visible.united(wrapper->GetOrigArea());
1840 if (visible.isEmpty())
1843 QRect bounding = visible.boundingRect();
1844 bounding = bounding.translated(
m_safeArea.topLeft());
1846 int left =
m_safeArea.left() - bounding.left();
1852 auto *wrapper =
dynamic_cast<SubWrapper *
>(img);
1853 if (wrapper && img->IsVisible())
1854 img->SetArea(
MythRect(wrapper->GetOrigArea().translated(left, top)));
1864 QMutexLocker lock(&(subs->
m_lock));
1874 if (!currentFrame || !videoOut)
1904 AVSEEK_FLAG_BACKWARD);
1909#ifdef DEBUG_SUBTITLES
1912 LOG(VB_PLAYBACK, LOG_DEBUG,
1913 LOC + QString(
"time %1, no subtitle available after seek")
1924 AVSubtitle subtitle = subs->
m_buffers.front();
1925 if (subtitle.end_display_time < currentFrame->
m_timecode.count())
1928#ifdef DEBUG_SUBTITLES
1931 LOG(VB_PLAYBACK, LOG_DEBUG,
1932 LOC + QString(
"time %1, drop %2")
1947#ifdef DEBUG_SUBTITLES
1950 LOG(VB_PLAYBACK, LOG_DEBUG,
1951 LOC + QString(
"time %1, no subtitle available")
1964 [[maybe_unused]]
bool assForceNext {
false};
1967 AVSubtitle subtitle = subs->
m_buffers.front();
1968 if (subtitle.start_display_time > currentFrame->
m_timecode.count())
1970#ifdef DEBUG_SUBTITLES
1973 LOG(VB_PLAYBACK, LOG_DEBUG,
1974 LOC + QString(
"time %1, next %2")
1986 assForceNext =
true;
1988 auto displayfor = std::chrono::milliseconds(subtitle.end_display_time -
1989 subtitle.start_display_time);
1990#ifdef DEBUG_SUBTITLES
1993 LOG(VB_PLAYBACK, LOG_DEBUG,
1994 LOC + QString(
"time %1, show %2")
1998 if (displayfor == 0ms)
2000 displayfor = (displayfor < 50ms) ? 50ms : displayfor;
2001 std::chrono::milliseconds late = currentFrame->
m_timecode -
2002 std::chrono::milliseconds(subtitle.start_display_time);
2006 for (std::size_t i = 0; i < subtitle.num_rects; ++i)
2008 AVSubtitleRect* rect = subtitle.rects[i];
2010 bool displaysub =
true;
2012 subs->
m_buffers.front().end_display_time <
2018 if (displaysub && rect->type == SUBTITLE_BITMAP)
2020 QRect display(rect->display_x, rect->display_y,
2021 rect->display_w, rect->display_h);
2027 int right = rect->x + rect->w;
2028 int bottom = rect->y + rect->h;
2030 (currentFrame->
m_width < right) ||
2031 !display.width() || !display.height())
2033 int sd_height = 576;
2038 int height { 1080 };
2039 if ((currentFrame->
m_height <= sd_height) &&
2040 (bottom <= sd_height))
2042 else if ((currentFrame->
m_height <= 720) && bottom <= 720)
2046 if ((currentFrame->
m_width <= 720) && (right <= 720))
2048 else if ((currentFrame->
m_width <= 1280) &&
2051 display = QRect(0, 0, width, height);
2056 int uh = (display.height() / 2) - rect->y;
2057 std::chrono::milliseconds displayuntil = currentFrame->
m_timecode + displayfor;
2060 bbox = QRect(0, 0, rect->w, uh);
2062 rect->flags & AV_SUBTITLE_FLAG_FORCED,
2063 QString(
"avsub%1t").arg(i),
2064 displayuntil, late);
2070 int lh = rect->h - uh;
2073 bbox = QRect(0, uh, rect->w, lh);
2075 rect->flags & AV_SUBTITLE_FLAG_FORCED,
2076 QString(
"avsub%1b").arg(i),
2077 displayuntil, late);
2081 else if (displaysub && rect->type == SUBTITLE_ASS)
2084 AddAssEvent(rect->ass, subtitle.start_display_time, subtitle.end_display_time);
2091 RenderAssTrack(currentFrame->
m_timecode, assForceNext);
2096 QRect &bbox,
bool top,
2097 QRect &display,
int forced,
2098 const QString& imagename,
2099 std::chrono::milliseconds displayuntil,
2100 std::chrono::milliseconds late)
2105 bool prev_empty =
false;
2108 int xmin = bbox.right();
2109 int xmax = bbox.left();
2110 int ymin = bbox.bottom();
2111 int ymax = bbox.top();
2112 int ylast = bbox.top();
2113 int ysplit = bbox.bottom();
2116 for (
int y = bbox.top(); y <= bbox.bottom(); ++y)
2127 for (
int x = bbox.left(); x <= bbox.right(); ++x)
2129 const uint8_t color =
2130 rect->data[0][(y * rect->linesize[0]) + x];
2131 const uint32_t
pixel = *((uint32_t *)rect->data[1] + color);
2132 if (
pixel & 0xff000000)
2135 xmin = std::min(x, xmin);
2136 xmax = std::max(x, xmax);
2142 ymin = std::min(y, ymin);
2143 ymax = std::max(y, ymax);
2145 else if (!prev_empty)
2162 if (ymax == bbox.bottom())
2171 bbox.setRight(xmax);
2173 bbox.setBottom(ymax);
2179 QRect orig_rect(bbox.left(), bbox.top(), bbox.width(), bbox.height());
2181 QImage qImage(bbox.width(), bbox.height(), QImage::Format_ARGB32);
2182 for (
int y = 0; y < bbox.height(); ++y)
2184 int ysrc = y + bbox.top();
2185 for (
int x = 0; x < bbox.width(); ++x)
2187 int xsrc = x + bbox.left();
2188 const uint8_t color =
2189 rect->data[0][(ysrc * rect->linesize[0]) + xsrc];
2190 const uint32_t
pixel = *((uint32_t *)rect->data[1] + color);
2191 qImage.setPixel(x, y,
pixel);
2196 bbox.translate(rect->x, rect->y);
2205 if (scaled.size() != orig_rect.size())
2206 qImage = qImage.scaled(scaled.width(), scaled.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
2242 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"Display %1AV sub until %2ms")
2243 .arg(forced ?
"FORCED " :
"").arg(displayuntil.count()));
2245 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"AV Sub was %1ms late").arg(late.count()));
2248 return (ysplit + 1);
2256 std::chrono::milliseconds duration = 0ms;
2278 std::chrono::milliseconds start,
2279 std::chrono::milliseconds duration)
2282 duration,
this, subs);
2307 QMutexLocker locker(&textlist->
m_lock);
2329 for (
auto & window : cc708service->
m_windows)
2330 window.SetChanged();
2333 uint64_t clearMask = 0;
2334 QList<FormattedTextSubtitle *> addList;
2343 clearMask |= (1UL << i);
2348 QMutexLocker locker(&win.
m_lock);
2349 std::vector<CC708String*> list = win.
GetStrings();
2354 addList.append(fsub);
2362 if (!addList.empty())
2373 if (scaled.size() != pos.size())
2375 img = img.scaled(scaled.width(), scaled.height(),
2376 Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
2395static void myth_libass_log(
int level,
const char *fmt, va_list vl,
void *)
2397 uint64_t verbose_mask = VB_GENERAL;
2398 LogLevel_t verbose_level = LOG_INFO;
2403 verbose_level = LOG_EMERG;
2406 verbose_level = LOG_ERR;
2409 verbose_level = LOG_WARNING;
2412 verbose_level = LOG_INFO;
2416 verbose_level = LOG_DEBUG;
2425 static QMutex s_stringLock;
2426 s_stringLock.lock();
2428 QString str = QString::vasprintf(fmt, vl);
2429 LOG(verbose_mask, verbose_level, QString(
"libass: %1").arg(str));
2430 s_stringLock.unlock();
2433bool SubtitleScreen::InitialiseAssLibrary(
void)
2435 if (m_assLibrary && m_assRenderer)
2440 m_assLibrary = ass_library_init();
2444 ass_set_message_cb(m_assLibrary, myth_libass_log,
nullptr);
2445 ass_set_extract_fonts(m_assLibrary,
static_cast<int>(
true));
2446 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"Initialised libass object.");
2453 m_assRenderer = ass_renderer_init(m_assLibrary);
2461 const char *psz_font =
"/system/fonts/DroidSans.ttf";
2462 const char *psz_font_family =
"Droid Sans";
2464 const char *psz_font =
nullptr;
2465 const char *psz_font_family =
"sans-serif";
2467 ass_set_fonts(m_assRenderer, psz_font, psz_font_family, 1,
nullptr, 1);
2468 ass_set_hinting(m_assRenderer, ASS_HINTING_LIGHT);
2469 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"Initialised libass renderer.");
2475void SubtitleScreen::LoadAssFonts(
void)
2481 if (m_assFontCount == count)
2484 ass_clear_fonts(m_assLibrary);
2488 for (
uint i = 0; i < count; ++i)
2493 ass_add_font(m_assLibrary,
filename.data(), font.data(), font.size());
2494 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"Retrieved font '%1'")
2500void SubtitleScreen::CleanupAssLibrary(
void)
2505 ass_renderer_done(m_assRenderer);
2506 m_assRenderer =
nullptr;
2510 ass_clear_fonts(m_assLibrary);
2512 ass_library_done(m_assLibrary);
2514 m_assLibrary =
nullptr;
2517void SubtitleScreen::InitialiseAssTrack(
int tracknum)
2519 if (!InitialiseAssLibrary() || !
m_player)
2522 if (tracknum == m_assTrackNum && m_assTrack)
2527 m_assTrack = ass_new_track(m_assLibrary);
2528 m_assTrackNum = tracknum;
2531 if (header.isNull())
2535 header =
parser->GetSubHeader();
2537 if (!header.isNull())
2538 ass_process_codec_private(m_assTrack, header.data(), header.size());
2541 ResizeAssRenderer();
2544void SubtitleScreen::CleanupAssTrack(
void)
2547 ass_free_track(m_assTrack);
2548 m_assTrack =
nullptr;
2551void SubtitleScreen::AddAssEvent(
char *event, uint32_t starttime, uint32_t endtime)
2553 if (m_assTrack && event)
2554 ass_process_chunk(m_assTrack, event, strlen(event), starttime, endtime-starttime);
2557void SubtitleScreen::ResizeAssRenderer(
void)
2560 ass_set_margins(m_assRenderer, 0, 0, 0, 0);
2561 ass_set_use_margins(m_assRenderer,
static_cast<int>(
true));
2562 ass_set_font_scale(m_assRenderer, 1.0);
2565void SubtitleScreen::RenderAssTrack(std::chrono::milliseconds timecode,
bool force)
2567 if (!
m_player || !m_assRenderer || !m_assTrack)
2577 ResizeAssRenderer();
2580 ASS_Image *images = ass_render_frame(m_assRenderer, m_assTrack, timecode.count(), &changed);
2581 if (!changed && !
force)
2589 if (images->w == 0 || images->h == 0)
2591 images = images->next;
2595 uint8_t alpha = images->color & 0xFF;
2596 uint8_t blue = images->color >> 8 & 0xFF;
2597 uint8_t green = images->color >> 16 & 0xFF;
2598 uint8_t red = images->color >> 24 & 0xFF;
2602 images = images->next;
2606 QSize img_size(images->w, images->h);
2607 QRect img_rect(images->dst_x,images->dst_y,
2608 images->w, images->h);
2609 QImage qImage(img_size, QImage::Format_ARGB32);
2610 qImage.fill(0x00000000);
2612 unsigned char *src = images->bitmap;
2613 for (
int y = 0; y < images->h; ++y)
2615 for (
int x = 0; x < images->w; ++x)
2617 uint8_t value = src[x];
2620 uint32_t
pixel = (value * (255 - alpha) / 255 << 24) |
2621 (red << 16) | (green << 8) | blue;
2622 qImage.setPixel(x, y,
pixel);
2625 src += images->stride;
2634 QString name = QString(
"asssub%1").arg(count);
2645 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 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