4#include <QRegularExpression>
16#define LOC QString("Subtitles: ")
17#define LOC_WARN QString("Subtitles Warning: ")
22 std::chrono::milliseconds time = frame->
m_timecode;
23 return QString(
"%1(%2)")
42 std::chrono::milliseconds expireTime,
43 int whichImageCache = -1) :
64 QRect rect, Qt::Alignment align,
66 int whichImageCache, std::chrono::milliseconds expireTime) :
75 int whichImageCache, std::chrono::milliseconds expireTime) :
77 SubWrapper(area, expireTime, whichImageCache) {}
84 std::chrono::milliseconds expireTime) :
152 int pixelSize,
int zoom,
int stretch);
154 const QString &family,
158 std::chrono::milliseconds start,
159 std::chrono::milliseconds duration);
161 static QString
MakePrefix(
const QString &family,
164 void Load(
const QString &family,
177 static QSet<QString>
Diff(
const QString &family,
222 return QString(
"#%1%2%3")
223 .arg(color.red(), 2, 16, QLatin1Char(
'0'))
224 .arg(color.green(), 2, 16, QLatin1Char(
'0'))
225 .arg(color.blue(), 2, 16, QLatin1Char(
'0'));
231 result = QString(
"face=%1 pixelsize=%2 color=%3 "
232 "italics=%4 weight=%5 underline=%6")
234 .arg(f->
GetFace()->pixelSize())
236 .arg(
static_cast<int>(f->
GetFace()->italic()))
238 .arg(
static_cast<int>(f->
GetFace()->underline()));
244 result += QString(
" shadow=%1 shadowoffset=%2 "
245 "shadowcolor=%3 shadowalpha=%4")
246 .arg(QString::number(
static_cast<int>(f->
hasShadow())),
247 QString(
"(%1,%2)").arg(offset.x()).arg(offset.y()),
249 QString::number(alpha));
251 result += QString(
" outline=%1 outlinecolor=%2 "
252 "outlinesize=%3 outlinealpha=%4")
263 int pixelSize,
int zoom,
int stretch)
265 int origPixelSize = pixelSize;
266 float scale = zoom / 100.0F;
268 scale = scale * 32 / 42;
270 scale = scale * 42 / 32;
282 result->
GetFace()->setPixelSize(pixelSize);
284 result->
GetFace()->setStretch(stretch);
305 int off = lroundf(scale * pixelSize / 20);
306 offset = QPoint(off, off);
325 offset.
setX(lroundf(offset.x() * scale));
326 offset.
setY(lroundf(offset.y() * scale));
328 result->
SetShadow(shadow, offset, color, alpha);
344 off = lroundf(scale * pixelSize / 20);
356 off = lroundf(point.x() * scale);
358 result->
SetOutline(outline, color, off, alpha);
360 LOG(VB_VBI, LOG_DEBUG,
361 QString(
"GetFont(family=%1, prefix=%2, orig pixelSize=%3, "
362 "new pixelSize=%4 zoom=%5) = %6")
363 .arg(family,
prefix).arg(origPixelSize).arg(pixelSize)
371 for (
int i = 0; i <
m_cleanup.size(); ++i)
383 return family +
"_" + QString::number(attr.
m_fontTag & 0x7);
398 font->GetFace()->setFamily(
"FreeMono");
399 QBrush brush(Qt::black);
400 bg->SetFillBrush(brush);
401 bg->SetLinePen(QPen(brush, 0));
405 static const std::array<const std::string,8> s_cc708Fonts {
415 font->GetFace()->setFamily(QString::fromStdString(s_cc708Fonts[attr.
m_fontTag & 0x7]));
419 font->GetFace()->setFamily(
"Droid Sans");
420 QBrush brush(Qt::black);
421 bg->SetFillBrush(brush);
422 bg->SetLinePen(QPen(brush, 0));
426 font->GetFace()->setFamily(
"FreeMono");
429 QBrush brush(Qt::black);
430 bg->SetFillBrush(brush);
431 bg->SetLinePen(QPen(brush, 0));
433 font->GetFace()->setPixelSize(10);
445 return color == Qt::white ? Qt::black : Qt::white;
457 face->setItalic(!face->italic());
458 face->setPixelSize(face->pixelSize() + 1);
459 face->setUnderline(!face->underline());
460#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
461 face->setWeight((face->weight() + 1) % 32);
468 offset.setX(offset.x() + 1);
474 size + 1, 255 - alpha);
477 Qt::SolidPattern : Qt::NoBrush);
484 auto *baseParent =
new MythUIType(
nullptr,
"base");
489 &providerBaseFont, &providerBaseShape);
492 auto *negParent =
new MythUIType(
nullptr,
"base");
504 if (!posResult || !negResult)
505 LOG(VB_VBI, LOG_INFO,
506 QString(
"Couldn't load theme file %1").arg(
kSubFileName));
510 resultFont = providerBaseFont;
516 resultBG = providerBaseShape;
526 resultFont->
GetFace()->setCapitalization(QFont::SmallCaps);
529 LOG(VB_VBI, LOG_DEBUG,
530 QString(
"providerBaseFont = %1").arg(
fontToString(providerBaseFont)));
531 LOG(VB_VBI, LOG_DEBUG,
533 LOG(VB_VBI, LOG_DEBUG,
534 QString(
"resultFont = %1").arg(
fontToString(resultFont)));
535 LOG(VB_VBI, LOG_DEBUG,
543 resultFont->
GetShadow(offset, color, alpha);
560 QSet<QString> result;
561 QFont *face1 = font1->
GetFace();
562 QFont *face2 = font2->
GetFace();
563 if (face1->italic() != face2->italic())
565 if (face1->weight() != face2->weight())
567 if (face1->underline() != face2->underline())
569 if (face1->pixelSize() != face2->pixelSize())
582 font1->
GetShadow(offset1, color1, alpha1);
583 font2->
GetShadow(offset2, color2, alpha2);
584 if (offset1 != offset2)
586 if (color1 != color2)
588 if (alpha1 != alpha2)
602 if (color1 != color2)
606 if (alpha1 != alpha2)
613 for (
auto i = result.constBegin(); i != result.constEnd(); ++i)
614 values +=
" " + (*i);
615 LOG(VB_VBI, LOG_INFO,
616 QString(
"Subtitle family %1 allows provider to change:%2")
624 const QString &family,
628 std::chrono::milliseconds start,
629 std::chrono::milliseconds duration)
636 auto *result =
new SubShape(parent, name, area, whichImageCache,
644 result->SetFillBrush(brush);
645 result->SetLinePen(QPen(brush, 0));
652 LOG(VB_VBI, LOG_DEBUG,
653 QString(
"GetBackground(prefix=%1) = "
654 "{type=%2 alpha=%3 brushstyle=%4 brushcolor=%5}")
655 .arg(
prefix, result->m_type, QString::number(result->GetAlpha()),
656 QString::number(result->m_fillBrush.style()),
679 int &left,
int &right)
const
686 LOG(VB_VBI, LOG_INFO,
687 QString(
"Attempting to split chunk '%1'").arg(
m_text));
688 int lastSpace =
m_text.lastIndexOf(
' ', -2);
691 LOG(VB_VBI, LOG_INFO,
692 QString(
"Failed to split chunk '%1'").arg(
m_text));
697 newChunk.
m_text =
m_text.mid(lastSpace + 1).trimmed() +
' ';
699 LOG(VB_VBI, LOG_INFO,
700 QString(
"Split chunk into '%1' + '%2'").arg(
m_text, newChunk.
m_text));
707 str += QString(
"fg=%1.%2 ")
710 str += QString(
"bg=%1.%2 ")
713 str += QString(
"edge=%1.%2 ")
716 str += QString(
"off=%1 pensize=%2 ")
719 str += QString(
"it=%1 ul=%2 bf=%3 ")
724 str += QString(
" text='%1'").arg(
m_text);
729 int &x,
int y,
int height)
740 while (count <
m_text.length() &&
m_text.at(count) ==
' ')
744 int x_adjust = count * font.horizontalAdvance(
" ");
746 int rightPadding = 0;
747 CalcPadding(isFirst, isLast, leftPadding, rightPadding);
752 QRect bgrect(x - leftPadding, y,
753 chunk_sz.width() + leftPadding + rightPadding,
757 bgrect.setLeft(bgrect.left() + x_adjust);
759 .arg(chunk_sz.width()).arg(height).arg(x).arg(y);
765 .arg(chunk_sz.width()).arg(height).arg(x).arg(y);
767 chunk_sz.width() - x_adjust + rightPadding, height);
769 x += chunk_sz.width();
778 int rightPadding = 0;
779 QList<FormattedTextChunk>::const_iterator it;
781 for (it =
chunks.constBegin(); it !=
chunks.constEnd(); ++it)
783 bool isLast = (it + 1 ==
chunks.constEnd());
784 QSize
tmp = (*it).CalcSize(layoutSpacing);
785 height = std::max(height,
tmp.height());
786 width +=
tmp.width();
787 (*it).CalcPadding(isFirst, isLast, leftPadding, rightPadding);
788 if (it ==
chunks.constBegin())
789 width += leftPadding;
792 return {width + rightPadding, height};
809 int anchor_width = 0;
810 int anchor_height = 0;
811 for (
const auto & line : std::as_const(
m_lines))
814 anchor_width = std::max(anchor_width, sz.width());
815 anchor_height += sz.height();
822 anchor_x -= anchor_width / 2;
824 anchor_x -= anchor_width;
826 anchor_y -= anchor_height / 2;
828 anchor_y -= anchor_height;
834 m_bounds = QRect(anchor_x, anchor_y, anchor_width, anchor_height);
839 for (
int i = 0; i <
m_lines.size(); i++)
842 m_lines[i].m_xIndent = anchor_x;
847 while (!
m_lines[i].chunks.isEmpty() &&
848 m_lines[i].chunks.first().m_text.trimmed().isEmpty())
851 m_lines[i].chunks.first().CalcSize().width();
852 m_lines[i].chunks.removeFirst();
855 while (!
m_lines[i].chunks.isEmpty() &&
856 m_lines[i].chunks.last().m_text.trimmed().isEmpty())
858 m_lines[i].chunks.removeLast();
863 if (!
m_lines[i].chunks.isEmpty())
865 QString *str = &
m_lines[i].chunks.last().m_text;
866 int idx = str->length() - 1;
867 while (idx >= 0 && str->at(idx) ==
' ')
869 str->truncate(idx + 1);
877 for (
int i = 0; i <
m_lines.size(); i++)
881 int height =
m_lines[i].CalcSize().height();
882 QList<FormattedTextChunk>::iterator chunk;
884 for (chunk =
m_lines[i].chunks.begin();
885 chunk !=
m_lines[i].chunks.end();
888 bool isLast = (chunk + 1 ==
m_lines[i].chunks.end());
889 (*chunk).PreRender(isFirst, isLast, x, y, height);
900 QList<MythUIType *> textList;
901 QList<MythUIType *> shapeList;
904 QList<FormattedTextChunk>::const_iterator chunk;
905 for (chunk = line.chunks.constBegin();
906 chunk != line.chunks.constEnd();
918 if ((*chunk).m_textRect.width() > 0) {
921 Qt::AlignLeft|Qt::AlignTop,
927 if ((*chunk).m_bgShapeRect.width() > 0) {
929 GetBackground(
nullptr,
930 (*chunk).m_bgShapeName,
931 m_base, (*chunk).m_format,
937 shapeList += bgshape;
942 while (!shapeList.isEmpty())
944 while (!textList.isEmpty())
951 for (
const auto & ftl : std::as_const(
m_lines))
955 line.fill(
' ', ftl.m_origX);
956 QList<FormattedTextChunk>::const_iterator chunk;
957 for (chunk = ftl.chunks.constBegin();
958 chunk != ftl.chunks.constEnd();
961 const QString &text = (*chunk).m_text;
973 line += QString(
"<font color=\"%1\">")
980 line += QString(
"</font>");
989 if (!line.trimmed().isEmpty())
1019 bool isItalic =
false;
1020 bool isBold =
false;
1021 bool isUnderline =
false;
1022 QColor color(Qt::white);
1023 static const QRegularExpression htmlTag {
1024 "</?.+>", QRegularExpression::InvertedGreedinessOption };
1025 QString htmlPrefix(
"<font color=\"");
1026 QString htmlSuffix(
"\">");
1027 for (
const QString& subtitle : std::as_const(subs))
1030 QString text(subtitle);
1031 while (!text.isEmpty())
1033 auto match = htmlTag.match(text);
1034 int pos = match.capturedStart();
1041 text = (pos < 0 ?
"" : text.mid(pos));
1042 LOG(VB_VBI, LOG_INFO, QString(
"Adding SRT chunk: %1")
1047 int htmlLen = match.capturedLength();
1048 QString html = text.left(htmlLen).toLower();
1049 text = text.mid(htmlLen);
1052 else if (html ==
"</i>")
1054 else if (html.startsWith(htmlPrefix) &&
1055 html.endsWith(htmlSuffix))
1057 int colorLen = html.length() -
1058 (htmlPrefix.length() + htmlSuffix.length());
1059 QString colorString(
1060 html.mid(htmlPrefix.length(), colorLen));
1061 QColor newColor(colorString);
1062 if (newColor.isValid())
1068 LOG(VB_VBI, LOG_INFO,
1069 QString(
"Ignoring invalid SRT color specification: "
1070 "'%1'").arg(colorString));
1073 else if (html ==
"</font>")
1077 else if (html ==
"<b>")
1081 else if (html ==
"</b>")
1085 else if (html ==
"<u>")
1089 else if (html ==
"</u>")
1091 isUnderline =
false;
1095 LOG(VB_VBI, LOG_INFO,
1096 QString(
"Ignoring unknown SRT formatting: '%1'")
1100 LOG(VB_VBI, LOG_INFO,
1101 QString(
"SRT formatting change '%1', "
1102 "new ital=%2 bold=%3 uline=%4 color=%5)")
1103 .arg(html).arg(isItalic).arg(isBold).arg(isUnderline)
1114 for (
int i = 0; i <
m_lines.size(); i++)
1116 int width =
m_lines[i].CalcSize().width();
1119 while (width > maxWidth &&
m_lines[i].chunks.size() > 1)
1121 width -=
m_lines[i].chunks.back().CalcSize().width();
1127 LOG(VB_VBI, LOG_INFO,
1128 QString(
"Wrapping chunk to next line: '%1'")
1129 .arg(
m_lines[i+1].chunks[0].m_text));
1133 bool isSplitPossible =
true;
1134 while (width > maxWidth && isSplitPossible)
1137 isSplitPossible =
m_lines[i].chunks.back().Split(newChunk);
1138 if (isSplitPossible)
1144 m_lines[i+1].chunks.prepend(newChunk);
1145 width =
m_lines[i].CalcSize().width();
1157 bool &isItalic,
bool &isUnderline)
1162 if (text.length() >= 1 && text[0] >= QChar(0x7000))
1164 int op = text[0].unicode() - 0x7000;
1165 isUnderline = ((op & 0x1) != 0);
1181 color = (op & 0xf) >> 1;
1190 static const QRegularExpression kControlCharsRE {
"[\\x{7000}-\\x{7fff}]" };
1191 int nextControl = text.indexOf(kControlCharsRE);
1192 if (nextControl < 0)
1199 result = text.left(nextControl);
1200 text = text.mid(nextControl);
1212 int totalHeight = 0;
1216 QVector<int> heights(
m_lines.size());
1217 QVector<int> spaceBefore(
m_lines.size());
1219 for (
int i = 0; i <
m_lines.size(); i++)
1225 int height =
m_lines[i].CalcSize().height();
1226 heights[i] = height;
1227 spaceBefore[i] = y -
prevY;
1228 totalSpace += (y -
prevY);
1230 totalHeight += height;
1233 int overage = std::min(totalHeight - safeHeight, totalSpace);
1237 if (overage > 0 && totalSpace > 0)
1239 float shrink = (totalSpace - overage) / (
float)totalSpace;
1241 for (
int i = 0; i <
m_lines.size(); i++)
1243 m_lines[i].m_yIndent =
prevY + (spaceBefore[i] * shrink);
1249 int shift = std::min(firstY, std::max(0,
prevY - safeHeight));
1251 for (
int i = 0; i <
m_lines.size(); i++)
1252 m_lines[i].m_yIndent -= shift;
1259 static const std::array<const QColor,8> kClr
1261 Qt::white, Qt::green, Qt::blue, Qt::cyan,
1262 Qt::red, Qt::yellow, Qt::magenta, Qt::white,
1280 QFontMetrics fm(*font);
1281 fontwidth = fm.averageCharWidth();
1288 for (; i !=
buffers.cend(); ++i)
1292 bool isItalic =
false;
1293 bool isUnderline =
false;
1294 const bool isBold =
false;
1295 QString text(cc->
m_text);
1297 int orig_x = cc->
m_x;
1305 x = xmid + ((orig_x - xscale / 2) * fontwidth);
1310 x = (orig_x + 3) *
m_safeArea.width() / xscale;
1313 int orig_y = cc->
m_y;
1315 if (orig_y < yscale / 2)
1318 y = (orig_y *
m_safeArea.height() * zoom / (yscale * 100));
1324 ((yscale - orig_y - 0.5) *
m_safeArea.height() * zoom /
1329 while (!text.isNull())
1331 QString captionText =
1337 LOG(VB_VBI, LOG_INFO,
1338 QString(
"Adding cc608 chunk (%1,%2): %3")
1354 shape->SetFillBrush(fill);
1363 const std::vector<CC708String*> &list,
1366 LOG(VB_VBI, LOG_DEBUG,
LOC +
1367 QString(
"Display Win %1, Anchor_id %2, x_anch %3, y_anch %4, "
1375 float xrange { 160.0F };
1378 else if (aspect > 1.4F)
1381 float xmult = (float)
m_safeArea.width() / xrange;
1382 float ymult = (float)
m_safeArea.height() / yrange;
1390 for (
auto *str708 : list)
1393 m_lines.resize(str708->m_y + 1);
1395 m_lines[str708->m_y].chunks += chunk;
1396 LOG(VB_VBI, LOG_INFO, QString(
"Adding cc708 chunk: win %1 row %2: %3")
1404 const QString &
Name,
int FontStretch)
1407 m_fontStretch(FontStretch),
1414 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"SeekingDone signal received.");
1424 CleanupAssLibrary();
1458 if (!forced_only ||
m_family.isEmpty()) {
1501 ass_flush_events(m_assTrack);
1539 AVSubtitleRect *hl_button = dvdButton->rects[0];
1540 uint h = hl_button->h;
1541 uint w = hl_button->w;
1542 QRect rect = QRect(hl_button->x, hl_button->y, w, h);
1543 QImage bg_image(hl_button->data[0], w, h, w, QImage::Format_Indexed8);
1544 auto *bgpalette = (uint32_t *)(hl_button->data[1]);
1546 QVector<uint32_t> bg_palette(4);
1547 for (
int i = 0; i < 4; i++)
1548 bg_palette[i] = bgpalette[i];
1549 bg_image.setColorTable(bg_palette);
1552 const QRect fg_rect(buttonPos.translated(-hl_button->x, -hl_button->y));
1553 QImage fg_image = bg_image.copy(fg_rect);
1554 QVector<uint32_t> fg_palette(4);
1555 auto *fgpalette = (uint32_t *)(dvdButton->rects[1]->data[1]);
1558 for (
int i = 0; i < 4; i++)
1559 fg_palette[i] = fgpalette[i];
1560 fg_image.setColorTable(fg_palette);
1563 bg_image = bg_image.convertToFormat(QImage::Format_ARGB32);
1564 fg_image = fg_image.convertToFormat(QImage::Format_ARGB32);
1567 for (
int x=fg_rect.x(); x < fg_rect.x()+fg_rect.width(); ++x)
1569 if ((x < 0) || (x > hl_button->w))
1571 for (
int y=fg_rect.y(); y < fg_rect.y()+fg_rect.height(); ++y)
1573 if ((y < 0) || (y > hl_button->h))
1575 bg_image.setPixel(x, y, fg_image.pixel(x-fg_rect.x(),y-fg_rect.y()));
1609 QList<MythUIType *>::iterator it;
1610 for (it = list.begin(); it != list.end(); ++it)
1613 auto *wrapper =
dynamic_cast<SubWrapper *
>(child);
1617 if (whichImageCache != -1 && (mask & (1UL << whichImageCache)))
1657 int outlineSize = 0;
1658 int shadowWidth = 0;
1659 int shadowHeight = 0;
1662 mythfont->
GetOutline(color, outlineSize, alpha);
1663 MythPoint outline(outlineSize, outlineSize);
1665 outlineSize = outline.x();
1670 mythfont->
GetShadow(shadowOffset, color, alpha);
1672 shadowWidth = abs(shadowOffset.x());
1673 shadowHeight = abs(shadowOffset.y());
1675 shadowWidth = std::max(shadowWidth, outlineSize);
1676 shadowHeight = std::max(shadowHeight, outlineSize);
1678 return {shadowWidth + outlineSize, shadowHeight + outlineSize};
1683 float layoutSpacing)
const
1686 QFont *font = mythfont->
GetFace();
1687 QFontMetrics fm(*font);
1688 int width = fm.horizontalAdvance(text);
1690 if (layoutSpacing > 0 && !text.trimmed().isEmpty())
1691 height = std::max(height, (
int)(font->pixelSize() * layoutSpacing));
1693 return {width, height};
1700 bool isFirst,
bool isLast,
1701 int &left,
int &right)
const
1704 QFont *font = mythfont->
GetFace();
1705 QFontMetrics fm(*font);
1706 int basicPadding = fm.maxWidth() *
PAD_WIDTH;
1707 left = isFirst ? basicPadding : 0;
1709 (isLast ? basicPadding : 0);
1726 return mythfont->
face().family();
1744 LOG(VB_GENERAL, LOG_WARNING,
LOC +
"Failed to get subtitle reader.");
1746 LOG(VB_GENERAL, LOG_WARNING,
LOC +
"Failed to get CEA-608 reader.");
1748 LOG(VB_GENERAL, LOG_WARNING,
LOC +
"Failed to get CEA-708 reader.");
1755 QList<MythUIType *>::iterator it;
1756 QList<MythUIType *>::iterator itNext;
1760 std::chrono::milliseconds now =
1761 currentFrame ? currentFrame->
m_timecode : std::chrono::milliseconds::max();
1768 auto *wrapper =
dynamic_cast<SubWrapper *
>(child);
1773 std::chrono::milliseconds expireTime = wrapper->
GetExpireTime();
1774 if (expireTime > 0ms && expireTime < now)
1782 if (expireTime > 0ms && needRescale)
1784 auto *image =
dynamic_cast<SubImage *
>(child);
1788 QSize size = image->GetImage()->size();
1790 image->GetImage()->Resize(size);
1840 auto *wrapper =
dynamic_cast<SubWrapper *
>(img);
1842 visible = visible.united(wrapper->GetOrigArea());
1845 if (visible.isEmpty())
1848 QRect bounding = visible.boundingRect();
1849 bounding = bounding.translated(
m_safeArea.topLeft());
1851 int left =
m_safeArea.left() - bounding.left();
1859 auto *wrapper =
dynamic_cast<SubWrapper *
>(img);
1871 QMutexLocker lock(&(subs->
m_lock));
1881 if (!currentFrame || !videoOut)
1911 AVSEEK_FLAG_BACKWARD);
1916#ifdef DEBUG_SUBTITLES
1919 LOG(VB_PLAYBACK, LOG_DEBUG,
1920 LOC + QString(
"time %1, no subtitle available after seek")
1931 AVSubtitle subtitle = subs->
m_buffers.front();
1932 if (subtitle.end_display_time < currentFrame->
m_timecode.count())
1935#ifdef DEBUG_SUBTITLES
1938 LOG(VB_PLAYBACK, LOG_DEBUG,
1939 LOC + QString(
"time %1, drop %2")
1954#ifdef DEBUG_SUBTITLES
1957 LOG(VB_PLAYBACK, LOG_DEBUG,
1958 LOC + QString(
"time %1, no subtitle available")
1971 [[maybe_unused]]
bool assForceNext {
false};
1974 AVSubtitle subtitle = subs->
m_buffers.front();
1975 if (subtitle.start_display_time > currentFrame->
m_timecode.count())
1977#ifdef DEBUG_SUBTITLES
1980 LOG(VB_PLAYBACK, LOG_DEBUG,
1981 LOC + QString(
"time %1, next %2")
1993 assForceNext =
true;
1995 auto displayfor = std::chrono::milliseconds(subtitle.end_display_time -
1996 subtitle.start_display_time);
1997#ifdef DEBUG_SUBTITLES
2000 LOG(VB_PLAYBACK, LOG_DEBUG,
2001 LOC + QString(
"time %1, show %2")
2005 if (displayfor == 0ms)
2007 displayfor = (displayfor < 50ms) ? 50ms : displayfor;
2008 std::chrono::milliseconds late = currentFrame->
m_timecode -
2009 std::chrono::milliseconds(subtitle.start_display_time);
2013 for (std::size_t i = 0; i < subtitle.num_rects; ++i)
2015 AVSubtitleRect* rect = subtitle.rects[i];
2017 bool displaysub =
true;
2019 subs->
m_buffers.front().end_display_time <
2025 if (displaysub && rect->type == SUBTITLE_BITMAP)
2027 QRect display(rect->display_x, rect->display_y,
2028 rect->display_w, rect->display_h);
2034 int right = rect->x + rect->w;
2035 int bottom = rect->y + rect->h;
2037 (currentFrame->
m_width < right) ||
2038 !display.width() || !display.height())
2040 int sd_height = 576;
2045 int height { 1080 };
2046 if ((currentFrame->
m_height <= sd_height) &&
2047 (bottom <= sd_height))
2049 else if ((currentFrame->
m_height <= 720) && bottom <= 720)
2053 if ((currentFrame->
m_width <= 720) && (right <= 720))
2055 else if ((currentFrame->
m_width <= 1280) &&
2058 display = QRect(0, 0, width, height);
2063 int uh = (display.height() / 2) - rect->y;
2064 std::chrono::milliseconds displayuntil = currentFrame->
m_timecode + displayfor;
2067 bbox = QRect(0, 0, rect->w, uh);
2069 rect->flags & AV_SUBTITLE_FLAG_FORCED,
2070 QString(
"avsub%1t").arg(i),
2071 displayuntil, late);
2077 int lh = rect->h - uh;
2080 bbox = QRect(0, uh, rect->w, lh);
2082 rect->flags & AV_SUBTITLE_FLAG_FORCED,
2083 QString(
"avsub%1b").arg(i),
2084 displayuntil, late);
2088 else if (displaysub && rect->type == SUBTITLE_ASS)
2091 AddAssEvent(rect->ass, subtitle.start_display_time, subtitle.end_display_time);
2098 RenderAssTrack(currentFrame->
m_timecode, assForceNext);
2103 QRect &bbox,
bool top,
2104 QRect &display,
int forced,
2105 const QString& imagename,
2106 std::chrono::milliseconds displayuntil,
2107 std::chrono::milliseconds late)
2112 bool prev_empty =
false;
2115 int xmin = bbox.right();
2116 int xmax = bbox.left();
2117 int ymin = bbox.bottom();
2118 int ymax = bbox.top();
2119 int ylast = bbox.top();
2120 int ysplit = bbox.bottom();
2123 for (
int y = bbox.top(); y <= bbox.bottom(); ++y)
2134 for (
int x = bbox.left(); x <= bbox.right(); ++x)
2136 const uint8_t color =
2137 rect->data[0][(y * rect->linesize[0]) + x];
2138 const uint32_t
pixel = *((uint32_t *)rect->data[1] + color);
2139 if (
pixel & 0xff000000)
2142 xmin = std::min(x, xmin);
2143 xmax = std::max(x, xmax);
2149 ymin = std::min(y, ymin);
2150 ymax = std::max(y, ymax);
2152 else if (!prev_empty)
2169 if (ymax == bbox.bottom())
2178 bbox.setRight(xmax);
2180 bbox.setBottom(ymax);
2186 QRect orig_rect(bbox.left(), bbox.top(), bbox.width(), bbox.height());
2188 QImage qImage(bbox.width(), bbox.height(), QImage::Format_ARGB32);
2189 for (
int y = 0; y < bbox.height(); ++y)
2191 int ysrc = y + bbox.top();
2192 for (
int x = 0; x < bbox.width(); ++x)
2194 int xsrc = x + bbox.left();
2195 const uint8_t color =
2196 rect->data[0][(ysrc * rect->linesize[0]) + xsrc];
2197 const uint32_t
pixel = *((uint32_t *)rect->data[1] + color);
2198 qImage.setPixel(x, y,
pixel);
2203 bbox.translate(rect->x, rect->y);
2212 if (scaled.size() != orig_rect.size())
2213 qImage = qImage.scaled(scaled.width(), scaled.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
2249 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"Display %1AV sub until %2ms")
2250 .arg(forced ?
"FORCED " :
"").arg(displayuntil.count()));
2252 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"AV Sub was %1ms late").arg(late.count()));
2255 return (ysplit + 1);
2263 std::chrono::milliseconds duration = 0ms;
2285 std::chrono::milliseconds start,
2286 std::chrono::milliseconds duration)
2289 duration,
this, subs);
2314 QMutexLocker locker(&textlist->
m_lock);
2336 for (
auto & window : cc708service->
m_windows)
2337 window.SetChanged();
2340 uint64_t clearMask = 0;
2341 QList<FormattedTextSubtitle *> addList;
2350 clearMask |= (1UL << i);
2355 QMutexLocker locker(&win.
m_lock);
2356 std::vector<CC708String*> list = win.
GetStrings();
2361 addList.append(fsub);
2369 if (!addList.empty())
2380 if (scaled.size() != pos.size())
2382 img = img.scaled(scaled.width(), scaled.height(),
2383 Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
2402static void myth_libass_log(
int level,
const char *fmt, va_list vl,
void *)
2404 uint64_t verbose_mask = VB_GENERAL;
2405 LogLevel_t verbose_level = LOG_INFO;
2410 verbose_level = LOG_EMERG;
2413 verbose_level = LOG_ERR;
2416 verbose_level = LOG_WARNING;
2419 verbose_level = LOG_INFO;
2423 verbose_level = LOG_DEBUG;
2432 static QMutex s_stringLock;
2433 s_stringLock.lock();
2435 QString str = QString::vasprintf(fmt, vl);
2436 LOG(verbose_mask, verbose_level, QString(
"libass: %1").arg(str));
2437 s_stringLock.unlock();
2440bool SubtitleScreen::InitialiseAssLibrary(
void)
2442 if (m_assLibrary && m_assRenderer)
2447 m_assLibrary = ass_library_init();
2451 ass_set_message_cb(m_assLibrary, myth_libass_log,
nullptr);
2452 ass_set_extract_fonts(m_assLibrary,
static_cast<int>(
true));
2453 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"Initialised libass object.");
2460 m_assRenderer = ass_renderer_init(m_assLibrary);
2468 const char *psz_font =
"/system/fonts/DroidSans.ttf";
2469 const char *psz_font_family =
"Droid Sans";
2471 const char *psz_font =
nullptr;
2472 const char *psz_font_family =
"sans-serif";
2474 ass_set_fonts(m_assRenderer, psz_font, psz_font_family, 1,
nullptr, 1);
2475 ass_set_hinting(m_assRenderer, ASS_HINTING_LIGHT);
2476 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"Initialised libass renderer.");
2482void SubtitleScreen::LoadAssFonts(
void)
2488 if (m_assFontCount == count)
2491 ass_clear_fonts(m_assLibrary);
2495 for (
uint i = 0; i < count; ++i)
2500 ass_add_font(m_assLibrary,
filename.data(), font.data(), font.size());
2501 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"Retrieved font '%1'")
2507void SubtitleScreen::CleanupAssLibrary(
void)
2512 ass_renderer_done(m_assRenderer);
2513 m_assRenderer =
nullptr;
2517 ass_clear_fonts(m_assLibrary);
2519 ass_library_done(m_assLibrary);
2521 m_assLibrary =
nullptr;
2524void SubtitleScreen::InitialiseAssTrack(
int tracknum)
2526 if (!InitialiseAssLibrary() || !
m_player)
2529 if (tracknum == m_assTrackNum && m_assTrack)
2534 m_assTrack = ass_new_track(m_assLibrary);
2535 m_assTrackNum = tracknum;
2538 if (header.isNull())
2542 header =
parser->GetSubHeader();
2544 if (!header.isNull())
2545 ass_process_codec_private(m_assTrack, header.data(), header.size());
2548 ResizeAssRenderer();
2551void SubtitleScreen::CleanupAssTrack(
void)
2554 ass_free_track(m_assTrack);
2555 m_assTrack =
nullptr;
2558void SubtitleScreen::AddAssEvent(
char *event, uint32_t starttime, uint32_t endtime)
2560 if (m_assTrack && event)
2561 ass_process_chunk(m_assTrack, event, strlen(event), starttime, endtime-starttime);
2564void SubtitleScreen::ResizeAssRenderer(
void)
2567 ass_set_margins(m_assRenderer, 0, 0, 0, 0);
2568 ass_set_use_margins(m_assRenderer,
static_cast<int>(
true));
2569 ass_set_font_scale(m_assRenderer, 1.0);
2572void SubtitleScreen::RenderAssTrack(std::chrono::milliseconds timecode,
bool force)
2574 if (!
m_player || !m_assRenderer || !m_assTrack)
2584 ResizeAssRenderer();
2587 ASS_Image *images = ass_render_frame(m_assRenderer, m_assTrack, timecode.count(), &changed);
2588 if (!changed && !
force)
2596 if (images->w == 0 || images->h == 0)
2598 images = images->next;
2602 uint8_t alpha = images->color & 0xFF;
2603 uint8_t blue = images->color >> 8 & 0xFF;
2604 uint8_t green = images->color >> 16 & 0xFF;
2605 uint8_t red = images->color >> 24 & 0xFF;
2609 images = images->next;
2613 QSize img_size(images->w, images->h);
2614 QRect img_rect(images->dst_x,images->dst_y,
2615 images->w, images->h);
2616 QImage qImage(img_size, QImage::Format_ARGB32);
2617 qImage.fill(0x00000000);
2619 unsigned char *src = images->bitmap;
2620 for (
int y = 0; y < images->h; ++y)
2622 for (
int x = 0; x < images->w; ++x)
2624 uint8_t value = src[x];
2627 uint32_t
pixel = (value * (255 - alpha) / 255 << 24) |
2628 (red << 16) | (green << 8) | blue;
2629 qImage.setPixel(x, y,
pixel);
2632 src += images->stride;
2641 QString name = QString(
"asssub%1").arg(count);
2652 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)
bool IsVisible(bool recurse=false) const
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 guint32 * 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