1 #include <QFontMetrics>
12 #define LOC QString("Subtitles: ")
13 #define LOC_WARN QString("Subtitles Warning: ")
15 #ifdef DEBUG_SUBTITLES
18 std::chrono::milliseconds time = frame->
m_timecode;
19 return QString(
"%1(%2)")
38 std::chrono::milliseconds expireTime,
39 int whichImageCache = -1) :
60 QRect rect, Qt::Alignment align,
62 int whichImageCache, std::chrono::milliseconds expireTime) :
71 int whichImageCache, std::chrono::milliseconds expireTime) :
73 SubWrapper(area, expireTime, whichImageCache) {}
80 std::chrono::milliseconds expireTime) :
148 int pixelSize,
int zoom,
int stretch);
150 const QString &family,
154 std::chrono::milliseconds start,
155 std::chrono::milliseconds duration);
157 static QString
MakePrefix(
const QString &family,
160 void Load(
const QString &family,
173 static QSet<QString>
Diff(
const QString &family,
218 return QString(
"#%1%2%3")
219 .arg(color.red(), 2, 16, QLatin1Char(
'0'))
220 .arg(color.green(), 2, 16, QLatin1Char(
'0'))
221 .arg(color.blue(), 2, 16, QLatin1Char(
'0'));
227 result = QString(
"face=%1 pixelsize=%2 color=%3 "
228 "italics=%4 weight=%5 underline=%6")
230 .arg(f->
GetFace()->pixelSize())
232 .arg(
static_cast<int>(f->
GetFace()->italic()))
234 .arg(
static_cast<int>(f->
GetFace()->underline()));
240 result += QString(
" shadow=%1 shadowoffset=%2 "
241 "shadowcolor=%3 shadowalpha=%4")
242 .arg(QString::number(
static_cast<int>(f->
hasShadow())),
243 QString(
"(%1,%2)").arg(offset.x()).arg(offset.y()),
245 QString::number(alpha));
247 result += QString(
" outline=%1 outlinecolor=%2 "
248 "outlinesize=%3 outlinealpha=%4")
259 int pixelSize,
int zoom,
int stretch)
261 int origPixelSize = pixelSize;
262 float scale = zoom / 100.0;
264 scale = scale * 32 / 42;
266 scale = scale * 42 / 32;
278 result->
GetFace()->setPixelSize(pixelSize);
280 result->
GetFace()->setStretch(stretch);
301 int off = lroundf(scale * pixelSize / 20);
302 offset = QPoint(off, off);
317 offset.
setX(lroundf(offset.x() * scale));
318 offset.
setY(lroundf(offset.y() * scale));
320 result->
SetShadow(shadow, offset, color, alpha);
336 off = lroundf(scale * pixelSize / 20);
346 off = lroundf(point.x() * scale);
348 result->
SetOutline(outline, color, off, alpha);
350 LOG(VB_VBI, LOG_DEBUG,
351 QString(
"GetFont(family=%1, prefix=%2, orig pixelSize=%3, "
352 "new pixelSize=%4 zoom=%5) = %6")
353 .arg(family,
prefix).arg(origPixelSize).arg(pixelSize)
361 for (
int i = 0; i <
m_cleanup.size(); ++i)
373 return family +
"_" + QString::number(attr.
m_fontTag & 0x7);
388 font->GetFace()->setFamily(
"FreeMono");
389 QBrush brush(Qt::black);
390 bg->SetFillBrush(brush);
391 bg->SetLinePen(QPen(brush, 0));
395 static const std::array<const std::string,8> s_cc708Fonts {
405 font->GetFace()->setFamily(QString::fromStdString(s_cc708Fonts[attr.
m_fontTag & 0x7]));
409 font->GetFace()->setFamily(
"Droid Sans");
410 QBrush brush(Qt::black);
411 bg->SetFillBrush(brush);
412 bg->SetLinePen(QPen(brush, 0));
416 font->GetFace()->setFamily(
"FreeMono");
419 QBrush brush(Qt::black);
420 bg->SetFillBrush(brush);
421 bg->SetLinePen(QPen(brush, 0));
423 font->GetFace()->setPixelSize(10);
435 return color == Qt::white ? Qt::black : Qt::white;
447 face->setItalic(!face->italic());
448 face->setPixelSize(face->pixelSize() + 1);
449 face->setUnderline(!face->underline());
450 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
451 face->setWeight((face->weight() + 1) % 32);
458 offset.setX(offset.x() + 1);
464 size + 1, 255 - alpha);
467 Qt::SolidPattern : Qt::NoBrush);
474 auto *baseParent =
new MythUIType(
nullptr,
"base");
479 &providerBaseFont, &providerBaseShape);
482 auto *negParent =
new MythUIType(
nullptr,
"base");
494 if (!posResult || !negResult)
495 LOG(VB_VBI, LOG_INFO,
496 QString(
"Couldn't load theme file %1").arg(
kSubFileName));
500 resultFont = providerBaseFont;
506 resultBG = providerBaseShape;
516 resultFont->
GetFace()->setCapitalization(QFont::SmallCaps);
519 LOG(VB_VBI, LOG_DEBUG,
520 QString(
"providerBaseFont = %1").arg(
fontToString(providerBaseFont)));
521 LOG(VB_VBI, LOG_DEBUG,
523 LOG(VB_VBI, LOG_DEBUG,
524 QString(
"resultFont = %1").arg(
fontToString(resultFont)));
525 LOG(VB_VBI, LOG_DEBUG,
533 resultFont->
GetShadow(offset, color, alpha);
550 QSet<QString> result;
551 QFont *face1 = font1->
GetFace();
552 QFont *face2 = font2->
GetFace();
553 if (face1->italic() != face2->italic())
555 if (face1->weight() != face2->weight())
557 if (face1->underline() != face2->underline())
559 if (face1->pixelSize() != face2->pixelSize())
572 font1->
GetShadow(offset1, color1, alpha1);
573 font2->
GetShadow(offset2, color2, alpha2);
574 if (offset1 != offset2)
576 if (color1 != color2)
578 if (alpha1 != alpha2)
592 if (color1 != color2)
596 if (alpha1 != alpha2)
603 for (
auto i = result.constBegin(); i != result.constEnd(); ++i)
604 values +=
" " + (*i);
605 LOG(VB_VBI, LOG_INFO,
606 QString(
"Subtitle family %1 allows provider to change:%2")
614 const QString &family,
618 std::chrono::milliseconds start,
619 std::chrono::milliseconds duration)
626 auto *result =
new SubShape(parent, name, area, whichImageCache,
634 result->SetFillBrush(brush);
635 result->SetLinePen(QPen(brush, 0));
642 LOG(VB_VBI, LOG_DEBUG,
643 QString(
"GetBackground(prefix=%1) = "
644 "{type=%2 alpha=%3 brushstyle=%4 brushcolor=%5}")
645 .arg(
prefix, result->m_type, QString::number(result->GetAlpha()),
646 QString::number(result->m_fillBrush.style()),
669 int &left,
int &right)
const
676 LOG(VB_VBI, LOG_INFO,
677 QString(
"Attempting to split chunk '%1'").arg(
m_text));
678 int lastSpace =
m_text.lastIndexOf(
' ', -2);
681 LOG(VB_VBI, LOG_INFO,
682 QString(
"Failed to split chunk '%1'").arg(
m_text));
687 newChunk.
m_text =
m_text.mid(lastSpace + 1).trimmed() +
' ';
689 LOG(VB_VBI, LOG_INFO,
690 QString(
"Split chunk into '%1' + '%2'").arg(
m_text, newChunk.
m_text));
697 str += QString(
"fg=%1.%2 ")
700 str += QString(
"bg=%1.%2 ")
703 str += QString(
"edge=%1.%2 ")
706 str += QString(
"off=%1 pensize=%2 ")
709 str += QString(
"it=%1 ul=%2 bf=%3 ")
714 str += QString(
" text='%1'").arg(
m_text);
719 int &x,
int y,
int height)
730 while (count <
m_text.length() &&
m_text.at(count) ==
' ')
734 int x_adjust = count * font.horizontalAdvance(
" ");
736 int rightPadding = 0;
737 CalcPadding(isFirst, isLast, leftPadding, rightPadding);
742 QRect bgrect(x - leftPadding, y,
743 chunk_sz.width() + leftPadding + rightPadding,
747 bgrect.setLeft(bgrect.left() + x_adjust);
749 .arg(chunk_sz.width()).arg(height).arg(x).arg(y);
755 .arg(chunk_sz.width()).arg(height).arg(x).arg(y);
757 chunk_sz.width() - x_adjust + rightPadding, height);
759 x += chunk_sz.width();
768 int rightPadding = 0;
769 QList<FormattedTextChunk>::const_iterator it;
771 for (it =
chunks.constBegin(); it !=
chunks.constEnd(); ++it)
773 bool isLast = (it + 1 ==
chunks.constEnd());
774 QSize
tmp = (*it).CalcSize(layoutSpacing);
775 height = std::max(height,
tmp.height());
776 width +=
tmp.width();
777 (*it).CalcPadding(isFirst, isLast, leftPadding, rightPadding);
778 if (it ==
chunks.constBegin())
779 width += leftPadding;
782 return {width + rightPadding, height};
799 int anchor_width = 0;
800 int anchor_height = 0;
801 for (
const auto & line : qAsConst(
m_lines))
804 anchor_width = std::max(anchor_width, sz.width());
805 anchor_height += sz.height();
812 anchor_x -= anchor_width / 2;
814 anchor_x -= anchor_width;
816 anchor_y -= anchor_height / 2;
818 anchor_y -= anchor_height;
821 anchor_y = std::max(0, std::min(anchor_y,
m_safeArea.height() - anchor_height));
822 anchor_x = std::max(0, std::min(anchor_x,
m_safeArea.width() - anchor_width));
824 m_bounds = QRect(anchor_x, anchor_y, anchor_width, anchor_height);
829 for (
int i = 0; i <
m_lines.size(); i++)
832 m_lines[i].m_xIndent = anchor_x;
837 while (!
m_lines[i].chunks.isEmpty() &&
838 m_lines[i].chunks.first().m_text.trimmed().isEmpty())
841 m_lines[i].chunks.first().CalcSize().width();
842 m_lines[i].chunks.removeFirst();
845 while (!
m_lines[i].chunks.isEmpty() &&
846 m_lines[i].chunks.last().m_text.trimmed().isEmpty())
848 m_lines[i].chunks.removeLast();
853 if (!
m_lines[i].chunks.isEmpty())
855 QString *str = &
m_lines[i].chunks.last().m_text;
856 int idx = str->length() - 1;
857 while (idx >= 0 && str->at(idx) ==
' ')
859 str->truncate(idx + 1);
867 for (
int i = 0; i <
m_lines.size(); i++)
871 int height =
m_lines[i].CalcSize().height();
872 QList<FormattedTextChunk>::iterator chunk;
874 for (chunk =
m_lines[i].chunks.begin();
875 chunk !=
m_lines[i].chunks.end();
878 bool isLast = (chunk + 1 ==
m_lines[i].chunks.end());
879 (*chunk).PreRender(isFirst, isLast, x, y, height);
890 QList<MythUIType *> textList;
891 QList<MythUIType *> shapeList;
894 QList<FormattedTextChunk>::const_iterator chunk;
895 for (chunk = line.chunks.constBegin();
896 chunk != line.chunks.constEnd();
908 if ((*chunk).m_textRect.width() > 0) {
911 Qt::AlignLeft|Qt::AlignTop,
917 if ((*chunk).m_bgShapeRect.width() > 0) {
919 GetBackground(
nullptr,
920 (*chunk).m_bgShapeName,
921 m_base, (*chunk).m_format,
927 shapeList += bgshape;
932 while (!shapeList.isEmpty())
934 while (!textList.isEmpty())
941 for (
const auto & ftl : qAsConst(
m_lines))
945 line.fill(
' ', ftl.m_origX);
946 QList<FormattedTextChunk>::const_iterator chunk;
947 for (chunk = ftl.chunks.constBegin();
948 chunk != ftl.chunks.constEnd();
951 const QString &text = (*chunk).m_text;
953 bool isBlank = !attr.
m_underline && text.trimmed().isEmpty();
963 line += QString(
"<font color=\"%1\">")
970 line += QString(
"</font>");
979 if (!line.trimmed().isEmpty())
1009 bool isItalic =
false;
1010 bool isBold =
false;
1011 bool isUnderline =
false;
1012 QColor color(Qt::white);
1013 static const QRegularExpression htmlTag {
1014 "</?.+>", QRegularExpression::InvertedGreedinessOption };
1015 QString htmlPrefix(
"<font color=\"");
1016 QString htmlSuffix(
"\">");
1017 for (
const QString& subtitle : qAsConst(subs))
1020 QString text(subtitle);
1021 while (!text.isEmpty())
1023 auto match = htmlTag.match(text);
1024 int pos = match.capturedStart();
1031 text = (pos < 0 ?
"" : text.mid(pos));
1032 LOG(VB_VBI, LOG_INFO, QString(
"Adding SRT chunk: %1")
1037 int htmlLen = match.capturedLength();
1038 QString html = text.left(htmlLen).toLower();
1039 text = text.mid(htmlLen);
1042 else if (html ==
"</i>")
1044 else if (html.startsWith(htmlPrefix) &&
1045 html.endsWith(htmlSuffix))
1047 int colorLen = html.length() -
1048 (htmlPrefix.length() + htmlSuffix.length());
1049 QString colorString(
1050 html.mid(htmlPrefix.length(), colorLen));
1051 QColor newColor(colorString);
1052 if (newColor.isValid())
1058 LOG(VB_VBI, LOG_INFO,
1059 QString(
"Ignoring invalid SRT color specification: "
1060 "'%1'").arg(colorString));
1063 else if (html ==
"</font>")
1065 else if (html ==
"<b>")
1067 else if (html ==
"</b>")
1069 else if (html ==
"<u>")
1071 else if (html ==
"</u>")
1072 isUnderline =
false;
1075 LOG(VB_VBI, LOG_INFO,
1076 QString(
"Ignoring unknown SRT formatting: '%1'")
1080 LOG(VB_VBI, LOG_INFO,
1081 QString(
"SRT formatting change '%1', "
1082 "new ital=%2 bold=%3 uline=%4 color=%5)")
1083 .arg(html).arg(isItalic).arg(isBold).arg(isUnderline)
1094 for (
int i = 0; i <
m_lines.size(); i++)
1096 int width =
m_lines[i].CalcSize().width();
1099 while (width > maxWidth &&
m_lines[i].chunks.size() > 1)
1101 width -=
m_lines[i].chunks.back().CalcSize().width();
1107 LOG(VB_VBI, LOG_INFO,
1108 QString(
"Wrapping chunk to next line: '%1'")
1109 .arg(
m_lines[i+1].chunks[0].m_text));
1113 bool isSplitPossible =
true;
1114 while (width > maxWidth && isSplitPossible)
1117 isSplitPossible =
m_lines[i].chunks.back().Split(newChunk);
1118 if (isSplitPossible)
1124 m_lines[i+1].chunks.prepend(newChunk);
1125 width =
m_lines[i].CalcSize().width();
1137 bool &isItalic,
bool &isUnderline)
1142 if (text.length() >= 1 && text[0] >= QChar(0x7000))
1144 int op = text[0].unicode() - 0x7000;
1145 isUnderline = ((op & 0x1) != 0);
1161 color = (op & 0xf) >> 1;
1170 static const QRegularExpression kControlCharsRE {
"[\\x{7000}-\\x{7fff}]" };
1171 int nextControl = text.indexOf(kControlCharsRE);
1172 if (nextControl < 0)
1179 result = text.left(nextControl);
1180 text = text.mid(nextControl);
1192 int totalHeight = 0;
1196 QVector<int> heights(
m_lines.size());
1197 QVector<int> spaceBefore(
m_lines.size());
1199 for (
int i = 0; i <
m_lines.size(); i++)
1205 int height =
m_lines[i].CalcSize().height();
1206 heights[i] = height;
1207 spaceBefore[i] = y -
prevY;
1208 totalSpace += (y -
prevY);
1210 totalHeight += height;
1213 int overage = std::min(totalHeight - safeHeight, totalSpace);
1217 if (overage > 0 && totalSpace > 0)
1219 float shrink = (totalSpace - overage) / (
float)totalSpace;
1221 for (
int i = 0; i <
m_lines.size(); i++)
1223 m_lines[i].m_yIndent =
prevY + spaceBefore[i] * shrink;
1229 int shift = std::min(firstY, std::max(0,
prevY - safeHeight));
1231 for (
int i = 0; i <
m_lines.size(); i++)
1232 m_lines[i].m_yIndent -= shift;
1239 static const std::array<const QColor,8> kClr
1241 Qt::white, Qt::green, Qt::blue, Qt::cyan,
1242 Qt::red, Qt::yellow, Qt::magenta, Qt::white,
1260 QFontMetrics fm(*font);
1261 fontwidth = fm.averageCharWidth();
1268 for (; i !=
buffers.cend(); ++i)
1272 bool isItalic =
false;
1273 bool isUnderline =
false;
1274 const bool isBold =
false;
1275 QString text(
cc->m_text);
1277 int orig_x =
cc->m_x;
1285 x = xmid + (orig_x - xscale / 2) * fontwidth;
1290 x = (orig_x + 3) *
m_safeArea.width() / xscale;
1293 int orig_y =
cc->m_y;
1295 if (orig_y < yscale / 2)
1298 y = (orig_y *
m_safeArea.height() * zoom / (yscale * 100));
1304 ((yscale - orig_y - 0.5) *
m_safeArea.height() * zoom /
1309 while (!text.isNull())
1311 QString captionText =
1314 kClr[std::min(std::max(0, color), 7)]);
1317 LOG(VB_VBI, LOG_INFO,
1318 QString(
"Adding cc608 chunk (%1,%2): %3")
1334 shape->SetFillBrush(fill);
1343 const std::vector<CC708String*> &list,
1346 LOG(VB_VBI, LOG_DEBUG,
LOC +
1347 QString(
"Display Win %1, Anchor_id %2, x_anch %3, y_anch %4, "
1356 (aspect > 1.4F) ? 210.0F : 160.0F;
1358 float xmult = (float)
m_safeArea.width() / xrange;
1359 float ymult = (float)
m_safeArea.height() / yrange;
1367 for (
auto *str708 : list)
1370 m_lines.resize(str708->m_y + 1);
1372 m_lines[str708->m_y].chunks += chunk;
1373 LOG(VB_VBI, LOG_INFO, QString(
"Adding cc708 chunk: win %1 row %2: %3")
1381 const QString &
Name,
int FontStretch)
1384 m_fontStretch(FontStretch),
1391 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"SeekingDone signal received.");
1435 if (!forced_only ||
m_family.isEmpty()) {
1516 AVSubtitleRect *hl_button = dvdButton->rects[0];
1517 uint h = hl_button->h;
1518 uint w = hl_button->w;
1519 QRect rect = QRect(hl_button->x, hl_button->y, w, h);
1520 QImage bg_image(hl_button->data[0], w, h, w, QImage::Format_Indexed8);
1521 auto *bgpalette = (uint32_t *)(hl_button->data[1]);
1523 QVector<uint32_t> bg_palette(4);
1524 for (
int i = 0; i < 4; i++)
1525 bg_palette[i] = bgpalette[i];
1526 bg_image.setColorTable(bg_palette);
1529 const QRect fg_rect(buttonPos.translated(-hl_button->x, -hl_button->y));
1530 QImage fg_image = bg_image.copy(fg_rect);
1531 QVector<uint32_t> fg_palette(4);
1532 auto *fgpalette = (uint32_t *)(dvdButton->rects[1]->data[1]);
1535 for (
int i = 0; i < 4; i++)
1536 fg_palette[i] = fgpalette[i];
1537 fg_image.setColorTable(fg_palette);
1540 bg_image = bg_image.convertToFormat(QImage::Format_ARGB32);
1541 fg_image = fg_image.convertToFormat(QImage::Format_ARGB32);
1544 for (
int x=fg_rect.x(); x < fg_rect.x()+fg_rect.width(); ++x)
1546 if ((x < 0) || (x > hl_button->w))
1548 for (
int y=fg_rect.y(); y < fg_rect.y()+fg_rect.height(); ++y)
1550 if ((y < 0) || (y > hl_button->h))
1552 bg_image.setPixel(x, y, fg_image.pixel(x-fg_rect.x(),y-fg_rect.y()));
1586 QList<MythUIType *>::iterator it;
1587 for (it = list.begin(); it != list.end(); ++it)
1590 auto *wrapper =
dynamic_cast<SubWrapper *
>(child);
1594 if (whichImageCache != -1 && (mask & (1UL << whichImageCache)))
1634 int outlineSize = 0;
1635 int shadowWidth = 0;
1636 int shadowHeight = 0;
1639 mythfont->
GetOutline(color, outlineSize, alpha);
1640 MythPoint outline(outlineSize, outlineSize);
1642 outlineSize = outline.x();
1647 mythfont->
GetShadow(shadowOffset, color, alpha);
1649 shadowWidth = abs(shadowOffset.x());
1650 shadowHeight = abs(shadowOffset.y());
1652 shadowWidth = std::max(shadowWidth, outlineSize);
1653 shadowHeight = std::max(shadowHeight, outlineSize);
1655 return {shadowWidth + outlineSize, shadowHeight + outlineSize};
1660 float layoutSpacing)
const
1663 QFont *font = mythfont->
GetFace();
1664 QFontMetrics fm(*font);
1665 int width = fm.horizontalAdvance(text);
1667 if (layoutSpacing > 0 && !text.trimmed().isEmpty())
1668 height = std::max(height, (
int)(font->pixelSize() * layoutSpacing));
1670 return {width, height};
1677 bool isFirst,
bool isLast,
1678 int &left,
int &right)
const
1681 QFont *font = mythfont->
GetFace();
1682 QFontMetrics fm(*font);
1683 int basicPadding = fm.maxWidth() *
PAD_WIDTH;
1684 left = isFirst ? basicPadding : 0;
1686 (isLast ? basicPadding : 0);
1703 return mythfont->
face().family();
1721 LOG(VB_GENERAL, LOG_WARNING,
LOC +
"Failed to get subtitle reader.");
1723 LOG(VB_GENERAL, LOG_WARNING,
LOC +
"Failed to get CEA-608 reader.");
1725 LOG(VB_GENERAL, LOG_WARNING,
LOC +
"Failed to get CEA-708 reader.");
1732 QList<MythUIType *>::iterator it;
1733 QList<MythUIType *>::iterator itNext;
1737 std::chrono::milliseconds now =
1738 currentFrame ? currentFrame->
m_timecode : std::chrono::milliseconds::max();
1745 auto *wrapper =
dynamic_cast<SubWrapper *
>(child);
1750 std::chrono::milliseconds expireTime = wrapper->
GetExpireTime();
1751 if (expireTime > 0ms && expireTime < now)
1759 if (expireTime > 0ms && needRescale)
1761 auto *image =
dynamic_cast<SubImage *
>(child);
1765 QSize size = image->GetImage()->size();
1767 image->GetImage()->Resize(size);
1817 auto *wrapper =
dynamic_cast<SubWrapper *
>(img);
1819 visible = visible.united(wrapper->GetOrigArea());
1822 if (visible.isEmpty())
1825 QRect bounding = visible.boundingRect();
1826 bounding = bounding.translated(
m_safeArea.topLeft());
1828 int left =
m_safeArea.left() - bounding.left();
1836 auto *wrapper =
dynamic_cast<SubWrapper *
>(img);
1848 QMutexLocker lock(&(subs->
m_lock));
1858 if (!currentFrame || !videoOut)
1888 AVSEEK_FLAG_BACKWARD);
1893 #ifdef DEBUG_SUBTITLES
1896 LOG(VB_PLAYBACK, LOG_DEBUG,
1897 LOC + QString(
"time %1, no subtitle available after seek")
1908 AVSubtitle subtitle = subs->
m_buffers.front();
1909 if (subtitle.end_display_time < currentFrame->
m_timecode.count())
1912 #ifdef DEBUG_SUBTITLES
1915 LOG(VB_PLAYBACK, LOG_DEBUG,
1916 LOC + QString(
"time %1, drop %2")
1931 #ifdef DEBUG_SUBTITLES
1934 LOG(VB_PLAYBACK, LOG_DEBUG,
1935 LOC + QString(
"time %1, no subtitle available")
1948 [[maybe_unused]]
bool assForceNext {
false};
1951 AVSubtitle subtitle = subs->
m_buffers.front();
1952 if (subtitle.start_display_time > currentFrame->
m_timecode.count())
1954 #ifdef DEBUG_SUBTITLES
1957 LOG(VB_PLAYBACK, LOG_DEBUG,
1958 LOC + QString(
"time %1, next %2")
1970 assForceNext =
true;
1972 auto displayfor = std::chrono::milliseconds(subtitle.end_display_time -
1973 subtitle.start_display_time);
1974 #ifdef DEBUG_SUBTITLES
1977 LOG(VB_PLAYBACK, LOG_DEBUG,
1978 LOC + QString(
"time %1, show %2")
1982 if (displayfor == 0ms)
1984 displayfor = (displayfor < 50ms) ? 50ms : displayfor;
1985 std::chrono::milliseconds late = currentFrame->
m_timecode -
1986 std::chrono::milliseconds(subtitle.start_display_time);
1990 for (std::size_t i = 0; i < subtitle.num_rects; ++i)
1992 AVSubtitleRect* rect = subtitle.rects[i];
1994 bool displaysub =
true;
1996 subs->
m_buffers.front().end_display_time <
2002 if (displaysub && rect->type == SUBTITLE_BITMAP)
2004 QRect display(rect->display_x, rect->display_y,
2005 rect->display_w, rect->display_h);
2011 int right = rect->x + rect->w;
2012 int bottom = rect->y + rect->h;
2014 (currentFrame->
m_width < right) ||
2015 !display.width() || !display.height())
2017 int sd_height = 576;
2021 int height = ((currentFrame->
m_height <= sd_height) &&
2022 (bottom <= sd_height)) ? sd_height :
2023 ((currentFrame->
m_height <= 720) && bottom <= 720)
2025 int width = ((currentFrame->
m_width <= 720) &&
2026 (right <= 720)) ? 720 :
2027 ((currentFrame->
m_width <= 1280) &&
2028 (right <= 1280)) ? 1280 : 1920;
2029 display = QRect(0, 0, width, height);
2034 int uh = display.height() / 2 - rect->y;
2035 std::chrono::milliseconds displayuntil = currentFrame->
m_timecode + displayfor;
2038 bbox = QRect(0, 0, rect->w, uh);
2040 rect->flags & AV_SUBTITLE_FLAG_FORCED,
2041 QString(
"avsub%1t").arg(i),
2042 displayuntil, late);
2046 int lh = rect->h - uh;
2049 bbox = QRect(0, uh, rect->w, lh);
2051 rect->flags & AV_SUBTITLE_FLAG_FORCED,
2052 QString(
"avsub%1b").arg(i),
2053 displayuntil, late);
2057 else if (displaysub && rect->type == SUBTITLE_ASS)
2060 AddAssEvent(rect->ass, subtitle.start_display_time, subtitle.end_display_time);
2072 QRect &bbox,
bool top,
2073 QRect &display,
int forced,
2074 const QString& imagename,
2075 std::chrono::milliseconds displayuntil,
2076 std::chrono::milliseconds late)
2081 bool prev_empty =
false;
2084 int xmin = bbox.right();
2085 int xmax = bbox.left();
2086 int ymin = bbox.bottom();
2087 int ymax = bbox.top();
2088 int ylast = bbox.top();
2089 int ysplit = bbox.bottom();
2092 for (
int y = bbox.top(); y <= bbox.bottom(); ++y)
2103 for (
int x = bbox.left(); x <= bbox.right(); ++x)
2105 const uint8_t color =
2106 rect->data[0][y * rect->linesize[0] + x];
2107 const uint32_t
pixel = *((uint32_t *)rect->data[1] + color);
2108 if (
pixel & 0xff000000)
2125 else if (!prev_empty)
2142 if (ymax == bbox.bottom())
2151 bbox.setRight(xmax);
2153 bbox.setBottom(ymax);
2159 QRect orig_rect(bbox.left(), bbox.top(), bbox.width(), bbox.height());
2161 QImage qImage(bbox.width(), bbox.height(), QImage::Format_ARGB32);
2162 for (
int y = 0; y < bbox.height(); ++y)
2164 int ysrc = y + bbox.top();
2165 for (
int x = 0; x < bbox.width(); ++x)
2167 int xsrc = x + bbox.left();
2168 const uint8_t color =
2169 rect->data[0][ysrc * rect->linesize[0] + xsrc];
2170 const uint32_t
pixel = *((uint32_t *)rect->data[1] + color);
2171 qImage.setPixel(x, y,
pixel);
2176 bbox.translate(rect->x, rect->y);
2185 if (scaled.size() != orig_rect.size())
2186 qImage = qImage.scaled(scaled.width(), scaled.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
2222 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"Display %1AV sub until %2ms")
2223 .arg(forced ?
"FORCED " :
"").arg(displayuntil.count()));
2225 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"AV Sub was %1ms late").arg(late.count()));
2228 return (ysplit + 1);
2236 std::chrono::milliseconds duration = 0ms;
2258 std::chrono::milliseconds start,
2259 std::chrono::milliseconds duration)
2262 duration,
this, subs);
2287 QMutexLocker locker(&textlist->
m_lock);
2309 for (
auto & window : cc708service->
m_windows)
2310 window.SetChanged();
2313 uint64_t clearMask = 0;
2314 QList<FormattedTextSubtitle *> addList;
2323 clearMask |= (1UL << i);
2328 QMutexLocker locker(&win.
m_lock);
2329 std::vector<CC708String*> list = win.
GetStrings();
2334 addList.append(fsub);
2342 if (!addList.empty())
2353 if (scaled.size() != pos.size())
2355 img = img.scaled(scaled.width(), scaled.height(),
2356 Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
2377 uint64_t verbose_mask = VB_GENERAL;
2378 LogLevel_t verbose_level = LOG_INFO;
2383 verbose_level = LOG_EMERG;
2386 verbose_level = LOG_ERR;
2389 verbose_level = LOG_WARNING;
2392 verbose_level = LOG_INFO;
2396 verbose_level = LOG_DEBUG;
2405 static QMutex s_stringLock;
2406 s_stringLock.lock();
2408 QString str = QString::vasprintf(fmt, vl);
2409 LOG(verbose_mask, verbose_level, QString(
"libass: %1").arg(str));
2410 s_stringLock.unlock();
2425 ass_set_extract_fonts(
m_assLibrary,
static_cast<int>(
true));
2426 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"Initialised libass object.");
2441 const char *psz_font =
"/system/fonts/DroidSans.ttf";
2442 const char *psz_font_family =
"Droid Sans";
2444 const char *psz_font =
nullptr;
2445 const char *psz_font_family =
"sans-serif";
2447 ass_set_fonts(
m_assRenderer, psz_font, psz_font_family, 1,
nullptr, 1);
2449 LOG(VB_PLAYBACK, LOG_INFO,
LOC +
"Initialised libass renderer.");
2468 for (
uint i = 0; i < count; ++i)
2474 LOG(VB_PLAYBACK, LOG_INFO,
LOC + QString(
"Retrieved font '%1'")
2511 if (header.isNull())
2515 header =
parser->GetSubHeader();
2517 if (!header.isNull())
2518 ass_process_codec_private(
m_assTrack, header.data(), header.size());
2534 ass_process_chunk(
m_assTrack, event, strlen(event), starttime, endtime-starttime);
2561 if (!changed && !
force)
2569 if (images->w == 0 || images->h == 0)
2571 images = images->next;
2575 uint8_t alpha = images->color & 0xFF;
2576 uint8_t blue = images->color >> 8 & 0xFF;
2577 uint8_t green = images->color >> 16 & 0xFF;
2578 uint8_t red = images->color >> 24 & 0xFF;
2582 images = images->next;
2586 QSize img_size(images->w, images->h);
2587 QRect img_rect(images->dst_x,images->dst_y,
2588 images->w, images->h);
2589 QImage qImage(img_size, QImage::Format_ARGB32);
2590 qImage.fill(0x00000000);
2592 unsigned char *src = images->bitmap;
2593 for (
int y = 0; y < images->h; ++y)
2595 for (
int x = 0; x < images->w; ++x)
2597 uint8_t value = src[x];
2600 uint32_t
pixel = (value * (255 - alpha) / 255 << 24) |
2601 (red << 16) | (green << 8) | blue;
2602 qImage.setPixel(x, y,
pixel);
2605 src += images->stride;
2614 QString name = QString(
"asssub%1").arg(count);
2625 images = images->next;
2629 #endif // USING_LIBASS