00001 #include <QFontMetrics>
00002
00003 #include "mythlogging.h"
00004 #include "mythfontproperties.h"
00005 #include "mythuisimpletext.h"
00006 #include "mythuishape.h"
00007 #include "mythuiimage.h"
00008 #include "mythpainter.h"
00009 #include "subtitlescreen.h"
00010
00011 #define LOC QString("Subtitles: ")
00012 #define LOC_WARN QString("Subtitles Warning: ")
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040
00041
00042
00043
00044
00045
00046
00047
00048
00049
00050
00051
00052
00053
00054
00055
00056
00057
00058
00059
00060
00061
00062
00063
00064
00065
00066 class SubtitleFormat
00067 {
00068 public:
00069 SubtitleFormat(void) {}
00070 ~SubtitleFormat(void);
00071 MythFontProperties *GetFont(const QString &family,
00072 const CC708CharacterAttribute &attr,
00073 int pixelSize, bool isTeletext,
00074 int zoom, int stretch);
00075 MythUIShape *GetBackground(MythUIType *parent, const QString &name,
00076 const QString &family,
00077 const CC708CharacterAttribute &attr);
00078 static QString MakePrefix(const QString &family,
00079 const CC708CharacterAttribute &attr);
00080 bool IsUnlocked(const QString &prefix, const QString &property) const
00081 {
00082 return m_changeMap[prefix].contains(property);
00083 }
00084 private:
00085 void Load(const QString &family,
00086 const CC708CharacterAttribute &attr);
00087 static void CreateProviderDefault(const QString &family,
00088 const CC708CharacterAttribute &attr,
00089 MythUIType *parent,
00090 bool isComplement,
00091 MythFontProperties **font,
00092 MythUIShape **bg);
00093 static void Complement(MythFontProperties *font, MythUIShape *bg);
00094 static QSet<QString> Diff(const QString &family,
00095 const CC708CharacterAttribute &attr,
00096 MythFontProperties *font1,
00097 MythFontProperties *font2,
00098 MythUIShape *bg1,
00099 MythUIShape *bg2);
00100
00101 QHash<QString, MythFontProperties *> m_fontMap;
00102 QHash<QString, MythUIShape *> m_shapeMap;
00103 QHash<QString, QSet<QString> > m_changeMap;
00104 QHash<QString, int> m_pixelSizeMap;
00105 QVector<MythUIType *> m_cleanup;
00106 };
00107
00108 static const QString kSubProvider("provider");
00109 static const QString kSubFileName ("osd_subtitle.xml");
00110 static const QString kSubWindowName("osd_subtitle");
00111 static const QString kSubFamily608 ("608");
00112 static const QString kSubFamily708 ("708");
00113 static const QString kSubFamilyText ("text");
00114 static const QString kSubFamilyAV ("AV");
00115 static const QString kSubFamilyTeletext("teletext");
00116
00117 static const QString kSubAttrItalics ("italics");
00118 static const QString kSubAttrBold ("bold");
00119 static const QString kSubAttrUnderline("underline");
00120 static const QString kSubAttrPixelsize("pixelsize");
00121 static const QString kSubAttrColor ("color");
00122 static const QString kSubAttrBGfill ("bgfill");
00123 static const QString kSubAttrShadow ("shadow");
00124 static const QString kSubAttrShadowoffset("shadowoffset");
00125 static const QString kSubAttrShadowcolor ("shadowcolor");
00126 static const QString kSubAttrShadowalpha ("shadowalpha");
00127 static const QString kSubAttrOutline ("outline");
00128 static const QString kSubAttrOutlinecolor("outlinecolor");
00129 static const QString kSubAttrOutlinesize ("outlinesize");
00130 static const QString kSubAttrOutlinealpha("outlinealpha");
00131
00132 static QString srtColorString(QColor color);
00133 static QString fontToString(MythFontProperties *f)
00134 {
00135 QString result;
00136 result = QString("face=%1 pixelsize=%2 color=%3 "
00137 "italics=%4 weight=%5 underline=%6")
00138 .arg(f->GetFace()->family())
00139 .arg(f->GetFace()->pixelSize())
00140 .arg(srtColorString(f->color()))
00141 .arg(f->GetFace()->italic())
00142 .arg(f->GetFace()->weight())
00143 .arg(f->GetFace()->underline());
00144 QPoint offset;
00145 QColor color;
00146 int alpha;
00147 int size;
00148 f->GetShadow(offset, color, alpha);
00149 result += QString(" shadow=%1 shadowoffset=%2 "
00150 "shadowcolor=%3 shadowalpha=%4")
00151 .arg(f->hasShadow())
00152 .arg(QString("(%1,%2)").arg(offset.x()).arg(offset.y()))
00153 .arg(srtColorString(color))
00154 .arg(alpha);
00155 f->GetOutline(color, size, alpha);
00156 result += QString(" outline=%1 outlinecolor=%2 "
00157 "outlinesize=%3 outlinealpha=%4")
00158 .arg(f->hasOutline())
00159 .arg(srtColorString(color))
00160 .arg(size)
00161 .arg(alpha);
00162 return result;
00163 }
00164
00165 SubtitleFormat::~SubtitleFormat(void)
00166 {
00167 for (int i = 0; i < m_cleanup.size(); ++i)
00168 {
00169 m_cleanup[i]->DeleteAllChildren();
00170 delete m_cleanup[i];
00171 m_cleanup[i] = NULL;
00172 }
00173 }
00174
00175 QString SubtitleFormat::MakePrefix(const QString &family,
00176 const CC708CharacterAttribute &attr)
00177 {
00178 if (family == kSubFamily708)
00179 return family + "_" + QString::number(attr.font_tag & 0x7);
00180 else
00181 return family;
00182 }
00183
00184 void SubtitleFormat::CreateProviderDefault(const QString &family,
00185 const CC708CharacterAttribute &attr,
00186 MythUIType *parent,
00187 bool isComplement,
00188 MythFontProperties **returnFont,
00189 MythUIShape **returnBg)
00190 {
00191 MythFontProperties *font = new MythFontProperties();
00192 MythUIShape *bg = new MythUIShape(parent, kSubProvider);
00193 if (family == kSubFamily608)
00194 {
00195 font->GetFace()->setFamily("FreeMono");
00196 bg->SetFillBrush(QBrush(Qt::black));
00197 }
00198 else if (family == kSubFamily708)
00199 {
00200 static const char *cc708Fonts[] = {
00201 "FreeMono",
00202 "FreeMono",
00203 "DejaVu Serif",
00204 "Droid Sans Mono",
00205 "Liberation Sans",
00206 "Purisa",
00207 "URW Chancery L",
00208 "Impact"
00209 };
00210 font->GetFace()->setFamily(cc708Fonts[attr.font_tag & 0x7]);
00211 }
00212 else if (family == kSubFamilyText)
00213 {
00214 font->GetFace()->setFamily("Droid Sans");
00215 bg->SetFillBrush(QBrush(Qt::black));
00216 }
00217 else if (family == kSubFamilyTeletext)
00218 {
00219 font->GetFace()->setFamily("FreeMono");
00220 }
00221 font->GetFace()->setPixelSize(10);
00222
00223 if (isComplement)
00224 Complement(font, bg);
00225 parent->AddFont(kSubProvider, font);
00226
00227 *returnFont = font;
00228 *returnBg = bg;
00229 }
00230
00231 static QColor differentColor(const QColor &color)
00232 {
00233 return color == Qt::white ? Qt::black : Qt::white;
00234 }
00235
00236
00237
00238 void SubtitleFormat::Complement(MythFontProperties *font, MythUIShape *bg)
00239 {
00240 QPoint offset;
00241 QColor color;
00242 int alpha;
00243 int size;
00244 QFont *face = font->GetFace();
00245 face->setItalic(!face->italic());
00246 face->setPixelSize(face->pixelSize() + 1);
00247 face->setUnderline(!face->underline());
00248 face->setWeight((face->weight() + 1) % 32);
00249 font->SetColor(differentColor(font->color()));
00250
00251 font->GetShadow(offset, color, alpha);
00252 offset.setX(offset.x() + 1);
00253 font->SetShadow(!font->hasShadow(), offset, differentColor(color),
00254 255 - alpha);
00255
00256 font->GetOutline(color, size, alpha);
00257 font->SetOutline(!font->hasOutline(), differentColor(color),
00258 size + 1, 255 - alpha);
00259
00260 bg->SetFillBrush(bg->m_fillBrush == Qt::NoBrush ?
00261 Qt::SolidPattern : Qt::NoBrush);
00262 }
00263
00264 void SubtitleFormat::Load(const QString &family,
00265 const CC708CharacterAttribute &attr)
00266 {
00267
00268 MythUIType *baseParent = new MythUIType(NULL, "base");
00269 m_cleanup += baseParent;
00270 MythFontProperties *providerBaseFont;
00271 MythUIShape *providerBaseShape;
00272 CreateProviderDefault(family, attr, baseParent, false,
00273 &providerBaseFont, &providerBaseShape);
00274
00275
00276 MythUIType *negParent = new MythUIType(NULL, "base");
00277 m_cleanup += negParent;
00278 MythFontProperties *negFont;
00279 MythUIShape *negBG;
00280 CreateProviderDefault(family, attr, negParent, true, &negFont, &negBG);
00281
00282 bool posResult =
00283 XMLParseBase::LoadWindowFromXML(kSubFileName, kSubWindowName,
00284 baseParent);
00285 bool negResult =
00286 XMLParseBase::LoadWindowFromXML(kSubFileName, kSubWindowName,
00287 negParent);
00288 if (!posResult || !negResult)
00289 LOG(VB_VBI, LOG_INFO,
00290 QString("Couldn't load theme file %1").arg(kSubFileName));
00291 QString prefix = MakePrefix(family, attr);
00292 MythFontProperties *resultFont = baseParent->GetFont(prefix);
00293 if (!resultFont)
00294 resultFont = providerBaseFont;
00295 MythUIShape *resultBG =
00296 dynamic_cast<MythUIShape *>(baseParent->GetChild(prefix));
00297 if (!resultBG)
00298 resultBG = providerBaseShape;
00299 MythFontProperties *testFont = negParent->GetFont(prefix);
00300 if (!testFont)
00301 testFont = negFont;
00302 MythUIShape *testBG =
00303 dynamic_cast<MythUIShape *>(negParent->GetChild(prefix));
00304 if (!testBG)
00305 testBG = negBG;
00306 m_fontMap[prefix] = resultFont;
00307 m_shapeMap[prefix] = resultBG;
00308 LOG(VB_VBI, LOG_DEBUG,
00309 QString("providerBaseFont = %1").arg(fontToString(providerBaseFont)));
00310 LOG(VB_VBI, LOG_DEBUG,
00311 QString("negFont = %1").arg(fontToString(negFont)));
00312 LOG(VB_VBI, LOG_DEBUG,
00313 QString("resultFont = %1").arg(fontToString(resultFont)));
00314 LOG(VB_VBI, LOG_DEBUG,
00315 QString("testFont = %1").arg(fontToString(testFont)));
00316 m_changeMap[prefix] = Diff(family, attr, resultFont, testFont,
00317 resultBG, testBG);
00318 if (!IsUnlocked(prefix, kSubAttrPixelsize))
00319 m_pixelSizeMap[prefix] = resultFont->GetFace()->pixelSize();
00320
00321 delete negFont;
00322 }
00323
00324 QSet<QString> SubtitleFormat::Diff(const QString &family,
00325 const CC708CharacterAttribute &attr,
00326 MythFontProperties *font1,
00327 MythFontProperties *font2,
00328 MythUIShape *bg1,
00329 MythUIShape *bg2)
00330 {
00331 bool is708 = (family == kSubFamily708);
00332 QSet<QString> result;
00333 QFont *face1 = font1->GetFace();
00334 QFont *face2 = font2->GetFace();
00335 if (face1->italic() != face2->italic())
00336 result << kSubAttrItalics;
00337 if (face1->weight() != face2->weight())
00338 result << kSubAttrBold;
00339 if (face1->underline() != face2->underline())
00340 result << kSubAttrUnderline;
00341 if (face1->pixelSize() != face2->pixelSize())
00342 result << kSubAttrPixelsize;
00343 if (font1->color() != font2->color())
00344 result << kSubAttrColor;
00345 if (is708 && font1->hasShadow() != font2->hasShadow())
00346 {
00347 result << kSubAttrShadow;
00348 QPoint offset1, offset2;
00349 QColor color1, color2;
00350 int alpha1, alpha2;
00351 font1->GetShadow(offset1, color1, alpha1);
00352 font2->GetShadow(offset2, color2, alpha2);
00353 if (offset1 != offset2)
00354 result << kSubAttrShadowoffset;
00355 if (color1 != color2)
00356 result << kSubAttrShadowcolor;
00357 if (alpha1 != alpha2)
00358 result << kSubAttrShadowalpha;
00359 }
00360 if (is708 && font1->hasOutline() != font2->hasOutline())
00361 {
00362 result << kSubAttrOutline;
00363 QColor color1, color2;
00364 int size1, size2;
00365 int alpha1, alpha2;
00366 font1->GetOutline(color1, size1, alpha1);
00367 font2->GetOutline(color2, size2, alpha2);
00368 if (color1 != color2)
00369 result << kSubAttrOutlinecolor;
00370 if (size1 != size2)
00371 result << kSubAttrOutlinesize;
00372 if (alpha1 != alpha2)
00373 result << kSubAttrOutlinealpha;
00374 }
00375 if (bg1->m_fillBrush != bg2->m_fillBrush)
00376 result << kSubAttrBGfill;
00377
00378 QString values = "";
00379 QSet<QString>::const_iterator i = result.constBegin();
00380 for (; i != result.constEnd(); ++i)
00381 values += " " + (*i);
00382 LOG(VB_VBI, LOG_INFO,
00383 QString("Subtitle family %1 allows provider to change:%2")
00384 .arg(MakePrefix(family, attr)).arg(values));
00385
00386 return result;
00387 }
00388
00389 MythFontProperties *
00390 SubtitleFormat::GetFont(const QString &family,
00391 const CC708CharacterAttribute &attr,
00392 int pixelSize, bool isTeletext,
00393 int zoom, int stretch)
00394 {
00395 int origPixelSize = pixelSize;
00396 float scale = zoom / 100.0;
00397 if (isTeletext)
00398 scale = scale * 17 / 25;
00399 if ((attr.pen_size & 0x3) == k708AttrSizeSmall)
00400 scale = scale * 32 / 42;
00401 else if ((attr.pen_size & 0x3) == k708AttrSizeLarge)
00402 scale = scale * 42 / 32;
00403
00404 QString prefix = MakePrefix(family, attr);
00405 if (!m_fontMap.contains(prefix))
00406 Load(family, attr);
00407 MythFontProperties *result = m_fontMap[prefix];
00408
00409
00410
00411 if (!IsUnlocked(prefix, kSubAttrPixelsize))
00412 pixelSize = m_pixelSizeMap[prefix];
00413 pixelSize *= scale;
00414 result->GetFace()->setPixelSize(pixelSize);
00415
00416 result->GetFace()->setStretch(stretch);
00417 if (IsUnlocked(prefix, kSubAttrItalics))
00418 result->GetFace()->setItalic(attr.italics);
00419 if (IsUnlocked(prefix, kSubAttrUnderline))
00420 result->GetFace()->setUnderline(attr.underline);
00421 if (IsUnlocked(prefix, kSubAttrBold))
00422 result->GetFace()->setBold(attr.boldface);
00423 if (IsUnlocked(prefix, kSubAttrColor))
00424 result->SetColor(attr.GetFGColor());
00425 if (IsUnlocked(prefix, kSubAttrShadow))
00426 {
00427 QPoint offset;
00428 QColor color;
00429 int alpha;
00430 bool shadow = result->hasShadow();
00431 result->GetShadow(offset, color, alpha);
00432 if (IsUnlocked(prefix, kSubAttrShadowcolor))
00433 color = attr.GetEdgeColor();
00434 if (IsUnlocked(prefix, kSubAttrShadowalpha))
00435 alpha = attr.GetFGAlpha();
00436 if (IsUnlocked(prefix, kSubAttrShadowoffset))
00437 {
00438 int off = pixelSize / 20;
00439 offset = QPoint(off, off);
00440 if (attr.edge_type == k708AttrEdgeLeftDropShadow)
00441 {
00442 shadow = true;
00443 offset.setX(-off);
00444 }
00445 else if (attr.edge_type == k708AttrEdgeRightDropShadow)
00446 shadow = true;
00447 else
00448 shadow = false;
00449 }
00450 result->SetShadow(shadow, offset, color, alpha);
00451 }
00452 if (IsUnlocked(prefix, kSubAttrOutline))
00453 {
00454 QColor color;
00455 int off;
00456 int alpha;
00457 bool outline = result->hasOutline();
00458 result->GetOutline(color, off, alpha);
00459 if (IsUnlocked(prefix, kSubAttrOutlinecolor))
00460 color = attr.GetEdgeColor();
00461 if (IsUnlocked(prefix, kSubAttrOutlinealpha))
00462 alpha = attr.GetFGAlpha();
00463 if (IsUnlocked(prefix, kSubAttrOutlinesize))
00464 {
00465 if (attr.edge_type == k708AttrEdgeUniform ||
00466 attr.edge_type == k708AttrEdgeRaised ||
00467 attr.edge_type == k708AttrEdgeDepressed)
00468 {
00469 outline = true;
00470 off = pixelSize / 20;
00471 }
00472 else
00473 outline = false;
00474 }
00475 result->SetOutline(outline, color, off, alpha);
00476 }
00477 LOG(VB_VBI, LOG_DEBUG,
00478 QString("GetFont(family=%1, prefix=%2, orig pixelSize=%3, "
00479 "new pixelSize=%4 zoom=%5) = %6")
00480 .arg(family).arg(prefix).arg(origPixelSize).arg(pixelSize)
00481 .arg(zoom).arg(fontToString(result)));
00482 return result;
00483 }
00484
00485 MythUIShape *
00486 SubtitleFormat::GetBackground(MythUIType *parent, const QString &name,
00487 const QString &family,
00488 const CC708CharacterAttribute &attr)
00489 {
00490 QString prefix = MakePrefix(family, attr);
00491 if (!m_shapeMap.contains(prefix))
00492 Load(family, attr);
00493 MythUIShape *result = new MythUIShape(parent, name);
00494 result->CopyFrom(m_shapeMap[prefix]);
00495 if (family == kSubFamily708)
00496 {
00497 if (IsUnlocked(prefix, kSubAttrBGfill))
00498 {
00499 result->SetFillBrush(QBrush(attr.GetBGColor()));
00500 }
00501 }
00502 else if (family == kSubFamilyTeletext)
00503 {
00504
00505 }
00506 LOG(VB_VBI, LOG_DEBUG,
00507 QString("GetBackground(prefix=%1) = "
00508 "{type=%2 alpha=%3 brushstyle=%4 brushcolor=%5}")
00509 .arg(prefix).arg(result->m_type).arg(result->GetAlpha())
00510 .arg(result->m_fillBrush.style())
00511 .arg(srtColorString(result->m_fillBrush.color())));
00512 return result;
00513 }
00514
00516
00517 static const float PAD_WIDTH = 0.5;
00518 static const float PAD_HEIGHT = 0.04;
00519
00520
00521
00522
00523 static const float LINE_SPACING = (20.0 / 17.0);
00524
00525 SubtitleScreen::SubtitleScreen(MythPlayer *player, const char * name,
00526 int fontStretch) :
00527 MythScreenType((MythScreenType*)NULL, name),
00528 m_player(player), m_subreader(NULL), m_608reader(NULL),
00529 m_708reader(NULL), m_safeArea(QRect()),
00530 m_removeHTML(QRegExp("</?.+>")), m_subtitleType(kDisplayNone),
00531 m_textFontZoom(100), m_textFontZoomPrev(100), m_refreshArea(false),
00532 m_fontStretch(fontStretch),
00533 m_format(new SubtitleFormat)
00534 {
00535 m_removeHTML.setMinimal(true);
00536
00537 #ifdef USING_LIBASS
00538 m_assLibrary = NULL;
00539 m_assRenderer = NULL;
00540 m_assTrackNum = -1;
00541 m_assTrack = NULL;
00542 m_assFontCount = 0;
00543 #endif
00544 }
00545
00546 SubtitleScreen::~SubtitleScreen(void)
00547 {
00548 ClearAllSubtitles();
00549 delete m_format;
00550 #ifdef USING_LIBASS
00551 CleanupAssLibrary();
00552 #endif
00553 }
00554
00555 void SubtitleScreen::EnableSubtitles(int type, bool forced_only)
00556 {
00557 if (forced_only)
00558 {
00559 SetVisible(true);
00560 SetArea(MythRect());
00561 return;
00562 }
00563
00564 m_subtitleType = type;
00565 if (m_subreader)
00566 {
00567 m_subreader->EnableAVSubtitles(kDisplayAVSubtitle == m_subtitleType);
00568 m_subreader->EnableTextSubtitles(kDisplayTextSubtitle == m_subtitleType);
00569 m_subreader->EnableRawTextSubtitles(kDisplayRawTextSubtitle == m_subtitleType);
00570 }
00571 if (m_608reader)
00572 m_608reader->SetEnabled(kDisplayCC608 == m_subtitleType);
00573 if (m_708reader)
00574 m_708reader->SetEnabled(kDisplayCC708 == m_subtitleType);
00575 ClearAllSubtitles();
00576 SetVisible(m_subtitleType != kDisplayNone);
00577 SetArea(MythRect());
00578 m_textFontZoom = gCoreContext->GetNumSetting("OSDCC708TextZoom", 100);
00579 switch (m_subtitleType)
00580 {
00581 case kDisplayTextSubtitle:
00582 case kDisplayRawTextSubtitle:
00583 m_family = kSubFamilyText;
00584 break;
00585 case kDisplayCC608:
00586 m_family = kSubFamily608;
00587 break;
00588 case kDisplayCC708:
00589 m_family = kSubFamily708;
00590 break;
00591 case kDisplayAVSubtitle:
00592 m_family = kSubFamilyAV;
00593 m_textFontZoom = gCoreContext->GetNumSetting("OSDAVSubZoom", 100);
00594 break;
00595 }
00596 m_textFontZoomPrev = m_textFontZoom;
00597 }
00598
00599 void SubtitleScreen::DisableForcedSubtitles(void)
00600 {
00601 if (kDisplayNone != m_subtitleType)
00602 return;
00603 ClearAllSubtitles();
00604 SetVisible(false);
00605 SetArea(MythRect());
00606 }
00607
00608 bool SubtitleScreen::Create(void)
00609 {
00610 if (!m_player)
00611 return false;
00612
00613 m_subreader = m_player->GetSubReader();
00614 m_608reader = m_player->GetCC608Reader();
00615 m_708reader = m_player->GetCC708Reader();
00616 if (!m_subreader)
00617 LOG(VB_GENERAL, LOG_WARNING, LOC + "Failed to get subtitle reader.");
00618 if (!m_608reader)
00619 LOG(VB_GENERAL, LOG_WARNING, LOC + "Failed to get CEA-608 reader.");
00620 if (!m_708reader)
00621 LOG(VB_GENERAL, LOG_WARNING, LOC + "Failed to get CEA-708 reader.");
00622
00623 return true;
00624 }
00625
00626 void SubtitleScreen::Pulse(void)
00627 {
00628 ExpireSubtitles();
00629
00630 DisplayAVSubtitles();
00631
00632 if (kDisplayTextSubtitle == m_subtitleType)
00633 DisplayTextSubtitles();
00634 else if (kDisplayCC608 == m_subtitleType)
00635 DisplayCC608Subtitles();
00636 else if (kDisplayCC708 == m_subtitleType)
00637 DisplayCC708Subtitles();
00638 else if (kDisplayRawTextSubtitle == m_subtitleType)
00639 DisplayRawTextSubtitles();
00640
00641 OptimiseDisplayedArea();
00642 MythScreenType::Pulse();
00643 m_refreshArea = false;
00644 m_textFontZoomPrev = m_textFontZoom;
00645 }
00646
00647 void SubtitleScreen::ClearAllSubtitles(void)
00648 {
00649 ClearNonDisplayedSubtitles();
00650 ClearDisplayedSubtitles();
00651 #ifdef USING_LIBASS
00652 if (m_assTrack)
00653 ass_flush_events(m_assTrack);
00654 #endif
00655 }
00656
00657 void SubtitleScreen::ClearNonDisplayedSubtitles(void)
00658 {
00659 if (m_subreader && (kDisplayAVSubtitle == m_subtitleType))
00660 m_subreader->ClearAVSubtitles();
00661 if (m_subreader && (kDisplayRawTextSubtitle == m_subtitleType))
00662 m_subreader->ClearRawTextSubtitles();
00663 if (m_608reader && (kDisplayCC608 == m_subtitleType))
00664 m_608reader->ClearBuffers(true, true);
00665 if (m_708reader && (kDisplayCC708 == m_subtitleType))
00666 m_708reader->ClearBuffers();
00667 }
00668
00669 void SubtitleScreen::ClearDisplayedSubtitles(void)
00670 {
00671 for (int i = 0; i < 8; i++)
00672 Clear708Cache(i);
00673 DeleteAllChildren();
00674 m_expireTimes.clear();
00675 m_avsubCache.clear();
00676 SetRedraw();
00677 }
00678
00679 void SubtitleScreen::ExpireSubtitles(void)
00680 {
00681 VideoOutput *videoOut = m_player->GetVideoOutput();
00682 VideoFrame *currentFrame = videoOut ? videoOut->GetLastShownFrame() : NULL;
00683 long long now = currentFrame ? currentFrame->timecode : LLONG_MAX;
00684 QMutableHashIterator<MythUIType*, long long> it(m_expireTimes);
00685 while (it.hasNext())
00686 {
00687 it.next();
00688 if (it.value() < now)
00689 {
00690 m_avsubCache.remove(it.key());
00691 DeleteChild(it.key());
00692 it.remove();
00693 SetRedraw();
00694 }
00695 }
00696 }
00697
00698 void SubtitleScreen::OptimiseDisplayedArea(void)
00699 {
00700 if (!m_refreshArea)
00701 return;
00702
00703 QRegion visible;
00704 QListIterator<MythUIType *> i(m_ChildrenList);
00705 while (i.hasNext())
00706 {
00707 MythUIType *img = i.next();
00708 visible = visible.united(img->GetArea());
00709 }
00710
00711 if (visible.isEmpty())
00712 return;
00713
00714 QRect bounding = visible.boundingRect();
00715 bounding = bounding.translated(m_safeArea.topLeft());
00716 bounding = m_safeArea.intersected(bounding);
00717 int left = m_safeArea.left() - bounding.left();
00718 int top = m_safeArea.top() - bounding.top();
00719 SetArea(MythRect(bounding));
00720
00721 i.toFront();
00722 while (i.hasNext())
00723 {
00724 MythUIType *img = i.next();
00725 img->SetArea(img->GetArea().translated(left, top));
00726 }
00727 }
00728
00729 void SubtitleScreen::DisplayAVSubtitles(void)
00730 {
00731 if (!m_player || !m_subreader)
00732 return;
00733
00734
00735
00736 if (m_textFontZoom != m_textFontZoomPrev)
00737 {
00738 double factor = m_textFontZoom / (double)m_textFontZoomPrev;
00739 QHash<MythUIType*, MythImage*>::iterator it;
00740 for (it = m_avsubCache.begin(); it != m_avsubCache.end(); ++it)
00741 {
00742 MythUIImage *image = dynamic_cast<MythUIImage *>(it.key());
00743 if (image)
00744 {
00745 QSize size = it.value()->size();
00746 size *= factor;
00747 it.value()->Resize(size);
00748 }
00749 }
00750 m_refreshArea = true;
00751 }
00752
00753 AVSubtitles* subs = m_subreader->GetAVSubtitles();
00754 QMutexLocker lock(&(subs->lock));
00755 if (subs->buffers.empty() && (kDisplayAVSubtitle != m_subtitleType))
00756 return;
00757
00758 VideoOutput *videoOut = m_player->GetVideoOutput();
00759 VideoFrame *currentFrame = videoOut ? videoOut->GetLastShownFrame() : NULL;
00760
00761 if (!currentFrame || !videoOut)
00762 return;
00763
00764 float tmp = 0.0;
00765 QRect dummy;
00766 videoOut->GetOSDBounds(dummy, m_safeArea, tmp, tmp, tmp);
00767
00768 while (!subs->buffers.empty())
00769 {
00770 const AVSubtitle subtitle = subs->buffers.front();
00771 if (subtitle.start_display_time > currentFrame->timecode)
00772 break;
00773
00774 long long displayfor = subtitle.end_display_time -
00775 subtitle.start_display_time;
00776 if (displayfor == 0)
00777 displayfor = 60000;
00778 displayfor = (displayfor < 50) ? 50 : displayfor;
00779 long long late = currentFrame->timecode -
00780 subtitle.start_display_time;
00781
00782 ClearDisplayedSubtitles();
00783 subs->buffers.pop_front();
00784 for (std::size_t i = 0; i < subtitle.num_rects; ++i)
00785 {
00786 AVSubtitleRect* rect = subtitle.rects[i];
00787
00788 bool displaysub = true;
00789 if (subs->buffers.size() > 0 &&
00790 subs->buffers.front().end_display_time <
00791 currentFrame->timecode)
00792 {
00793 displaysub = false;
00794 }
00795
00796 if (displaysub && rect->type == SUBTITLE_BITMAP)
00797 {
00798 QRect display(rect->display_x, rect->display_y,
00799 rect->display_w, rect->display_h);
00800
00801
00802
00803
00804
00805 int right = rect->x + rect->w;
00806 int bottom = rect->y + rect->h;
00807 if (subs->fixPosition || (currentFrame->height < bottom) ||
00808 (currentFrame->width < right) ||
00809 !display.width() || !display.height())
00810 {
00811 int sd_height = 576;
00812 if ((m_player->GetFrameRate() > 26.0f ||
00813 m_player->GetFrameRate() < 24.0f) && bottom <= 480)
00814 sd_height = 480;
00815 int height = ((currentFrame->height <= sd_height) &&
00816 (bottom <= sd_height)) ? sd_height :
00817 ((currentFrame->height <= 720) && bottom <= 720)
00818 ? 720 : 1080;
00819 int width = ((currentFrame->width <= 720) &&
00820 (right <= 720)) ? 720 :
00821 ((currentFrame->width <= 1280) &&
00822 (right <= 1280)) ? 1280 : 1920;
00823 display = QRect(0, 0, width, height);
00824 }
00825
00826
00827 QRect bbox;
00828 int uh = display.height() / 2 - rect->y;
00829 int lh;
00830 long long displayuntil = currentFrame->timecode + displayfor;
00831 if (uh > 0)
00832 {
00833 bbox = QRect(0, 0, rect->w, uh);
00834 uh = DisplayScaledAVSubtitles(rect, bbox, true, display,
00835 subtitle.forced,
00836 QString("avsub%1t").arg(i),
00837 displayuntil, late);
00838 }
00839 else
00840 uh = 0;
00841 lh = rect->h - uh;
00842 if (lh > 0)
00843 {
00844 bbox = QRect(0, uh, rect->w, lh);
00845 lh = DisplayScaledAVSubtitles(rect, bbox, false, display,
00846 subtitle.forced,
00847 QString("avsub%1b").arg(i),
00848 displayuntil, late);
00849 }
00850 }
00851 #ifdef USING_LIBASS
00852 else if (displaysub && rect->type == SUBTITLE_ASS)
00853 {
00854 InitialiseAssTrack(m_player->GetDecoder()->GetTrack(kTrackTypeSubtitle));
00855 AddAssEvent(rect->ass);
00856 }
00857 #endif
00858 }
00859 m_subreader->FreeAVSubtitle(subtitle);
00860 }
00861 #ifdef USING_LIBASS
00862 RenderAssTrack(currentFrame->timecode);
00863 #endif
00864 }
00865
00866 int SubtitleScreen::DisplayScaledAVSubtitles(const AVSubtitleRect *rect,
00867 QRect &bbox, bool top,
00868 QRect &display, int forced,
00869 QString imagename,
00870 long long displayuntil,
00871 long long late)
00872 {
00873
00874
00875
00876 int xmin, xmax, ymin, ymax;
00877 int ylast, ysplit;
00878 bool prev_empty = false;
00879
00880
00881 xmin = bbox.right();
00882 xmax = bbox.left();
00883 ymin = bbox.bottom();
00884 ymax = bbox.top();
00885 ylast = bbox.top();
00886 ysplit = bbox.bottom();
00887
00888
00889 for (int y = bbox.top(); y <= bbox.bottom(); ++y)
00890 {
00891 if (y >= rect->h)
00892 {
00893
00894 if (!prev_empty)
00895 ylast = y;
00896 break;
00897 }
00898
00899 bool empty = true;
00900 for (int x = bbox.left(); x <= bbox.right(); ++x)
00901 {
00902 const uint8_t color =
00903 rect->pict.data[0][y * rect->pict.linesize[0] + x];
00904 const uint32_t pixel = *((uint32_t *)rect->pict.data[1] + color);
00905 if (pixel & 0xff000000)
00906 {
00907 empty = false;
00908 if (x < xmin)
00909 xmin = x;
00910 if (x > xmax)
00911 xmax = x;
00912 }
00913 }
00914
00915 if (!empty)
00916 {
00917 if (y < ymin)
00918 ymin = y;
00919 if (y > ymax)
00920 ymax = y;
00921 }
00922 else if (!prev_empty)
00923 {
00924
00925 ylast = y;
00926 }
00927 prev_empty = empty;
00928 }
00929
00930 if (ymax <= ymin)
00931 return 0;
00932
00933 if (top)
00934 {
00935 if (ylast < ymin)
00936
00937 return 0;
00938
00939 if (ymax == bbox.bottom())
00940 {
00941 ymax = ylast;
00942 ysplit = ylast;
00943 }
00944 }
00945
00946
00947 bbox.setLeft(xmin);
00948 bbox.setRight(xmax);
00949 bbox.setTop(ymin);
00950 bbox.setBottom(ymax);
00951
00952
00953
00954
00955
00956 QRect orig_rect(bbox.left(), bbox.top(), bbox.width(), bbox.height());
00957
00958 QImage qImage(bbox.width(), bbox.height(), QImage::Format_ARGB32);
00959 for (int y = 0; y < bbox.height(); ++y)
00960 {
00961 int ysrc = y + bbox.top();
00962 for (int x = 0; x < bbox.width(); ++x)
00963 {
00964 int xsrc = x + bbox.left();
00965 const uint8_t color =
00966 rect->pict.data[0][ysrc * rect->pict.linesize[0] + xsrc];
00967 const uint32_t pixel = *((uint32_t *)rect->pict.data[1] + color);
00968 qImage.setPixel(x, y, pixel);
00969 }
00970 }
00971
00972
00973 bbox.translate(rect->x, rect->y);
00974
00975
00976 bbox.setWidth(bbox.width() * m_textFontZoom / 100);
00977 bbox.setHeight(bbox.height() * m_textFontZoom / 100);
00978
00979 VideoOutput *videoOut = m_player->GetVideoOutput();
00980 QRect scaled = videoOut->GetImageRect(bbox, &display);
00981
00982 if (scaled.size() != orig_rect.size())
00983 qImage = qImage.scaled(scaled.width(), scaled.height(),
00984 Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
00985
00986 int hsize = m_safeArea.width();
00987 int vsize = m_safeArea.height();
00988
00989 scaled.moveLeft(((100 - m_textFontZoom) * hsize / 2 +
00990 m_textFontZoom * scaled.left()) /
00991 100);
00992 if (top)
00993
00994 scaled.moveTop(scaled.top() * m_textFontZoom / 100);
00995 else
00996
00997 scaled.moveTop(((100 - m_textFontZoom) * vsize +
00998 m_textFontZoom * scaled.top()) /
00999 100);
01000
01001
01002 MythPainter *osd_painter = videoOut->GetOSDPainter();
01003 MythImage *image = NULL;
01004 if (osd_painter)
01005 image = osd_painter->GetFormatImage();
01006
01007 MythUIImage *uiimage = NULL;
01008 if (image)
01009 {
01010 image->Assign(qImage);
01011 uiimage = new MythUIImage(this, imagename);
01012 if (uiimage)
01013 {
01014 m_refreshArea = true;
01015 uiimage->SetImage(image);
01016 uiimage->SetArea(MythRect(scaled));
01017 m_expireTimes.insert(uiimage, displayuntil);
01018 m_avsubCache.insert(uiimage, image);
01019 }
01020 }
01021 if (uiimage)
01022 {
01023 LOG(VB_PLAYBACK, LOG_INFO, LOC +
01024 QString("Display %1AV sub until %2ms")
01025 .arg(forced ? "FORCED " : "")
01026 .arg(displayuntil));
01027 if (late > 50)
01028 LOG(VB_PLAYBACK, LOG_INFO, LOC +
01029 QString("AV Sub was %1ms late").arg(late));
01030 }
01031
01032 return (ysplit + 1);
01033 }
01034
01035 void SubtitleScreen::DisplayTextSubtitles(void)
01036 {
01037 if (!m_player || !m_subreader)
01038 return;
01039
01040 bool changed = (m_textFontZoom != m_textFontZoomPrev);
01041 VideoOutput *vo = m_player->GetVideoOutput();
01042 if (!vo)
01043 return;
01044 m_safeArea = vo->GetSafeRect();
01045
01046 VideoFrame *currentFrame = vo->GetLastShownFrame();
01047 if (!currentFrame)
01048 return;
01049
01050 TextSubtitles *subs = m_subreader->GetTextSubtitles();
01051 subs->Lock();
01052 uint64_t playPos = 0;
01053 if (subs->IsFrameBasedTiming())
01054 {
01055
01056
01057
01058 playPos = currentFrame->frameNumber;
01059 }
01060 else
01061 {
01062
01063
01064
01065
01066
01067
01068
01069
01070
01071
01072
01073
01074 playPos = m_player->GetDecoder()->NormalizeVideoTimecode(currentFrame->timecode);
01075 }
01076 if (playPos != 0)
01077 changed |= subs->HasSubtitleChanged(playPos);
01078 if (!changed)
01079 {
01080 subs->Unlock();
01081 return;
01082 }
01083
01084 DeleteAllChildren();
01085 SetRedraw();
01086 if (playPos == 0)
01087 {
01088 subs->Unlock();
01089 return;
01090 }
01091
01092 QStringList rawsubs = subs->GetSubtitles(playPos);
01093 if (rawsubs.empty())
01094 {
01095 subs->Unlock();
01096 return;
01097 }
01098
01099 subs->Unlock();
01100 DrawTextSubtitles(rawsubs, 0, 0);
01101 }
01102
01103 void SubtitleScreen::DisplayRawTextSubtitles(void)
01104 {
01105 if (!m_player || !m_subreader)
01106 return;
01107
01108 uint64_t duration;
01109 QStringList subs = m_subreader->GetRawTextSubtitles(duration);
01110 if (subs.empty())
01111 return;
01112
01113 VideoOutput *vo = m_player->GetVideoOutput();
01114 if (!vo)
01115 return;
01116
01117 VideoFrame *currentFrame = vo->GetLastShownFrame();
01118 if (!currentFrame)
01119 return;
01120
01121 m_safeArea = vo->GetSafeRect();
01122
01123
01124 DeleteAllChildren();
01125 DrawTextSubtitles(subs, currentFrame->timecode, duration);
01126 }
01127
01128 void SubtitleScreen::DrawTextSubtitles(QStringList &wrappedsubs,
01129 uint64_t start, uint64_t duration)
01130 {
01131 FormattedTextSubtitle fsub(m_safeArea, this);
01132 fsub.InitFromSRT(wrappedsubs, m_textFontZoom);
01133 fsub.WrapLongLines();
01134 fsub.Layout();
01135 m_refreshArea = fsub.Draw(m_family, NULL, start, duration) || m_refreshArea;
01136 }
01137
01138 void SubtitleScreen::DisplayDVDButton(AVSubtitle* dvdButton, QRect &buttonPos)
01139 {
01140 if (!dvdButton || !m_player)
01141 return;
01142
01143 VideoOutput *vo = m_player->GetVideoOutput();
01144 if (!vo)
01145 return;
01146
01147 DeleteAllChildren();
01148 SetRedraw();
01149
01150 float tmp = 0.0;
01151 QRect dummy;
01152 vo->GetOSDBounds(dummy, m_safeArea, tmp, tmp, tmp);
01153
01154 AVSubtitleRect *hl_button = dvdButton->rects[0];
01155 uint h = hl_button->h;
01156 uint w = hl_button->w;
01157 QRect rect = QRect(hl_button->x, hl_button->y, w, h);
01158 QImage bg_image(hl_button->pict.data[0], w, h, w, QImage::Format_Indexed8);
01159 uint32_t *bgpalette = (uint32_t *)(hl_button->pict.data[1]);
01160
01161 QVector<uint32_t> bg_palette(4);
01162 for (int i = 0; i < 4; i++)
01163 bg_palette[i] = bgpalette[i];
01164 bg_image.setColorTable(bg_palette);
01165
01166
01167 const QRect fg_rect(buttonPos.translated(-hl_button->x, -hl_button->y));
01168 QImage fg_image = bg_image.copy(fg_rect);
01169 QVector<uint32_t> fg_palette(4);
01170 uint32_t *fgpalette = (uint32_t *)(dvdButton->rects[1]->pict.data[1]);
01171 if (fgpalette)
01172 {
01173 for (int i = 0; i < 4; i++)
01174 fg_palette[i] = fgpalette[i];
01175 fg_image.setColorTable(fg_palette);
01176 }
01177
01178 bg_image = bg_image.convertToFormat(QImage::Format_ARGB32);
01179 fg_image = fg_image.convertToFormat(QImage::Format_ARGB32);
01180
01181
01182 for (int x=fg_rect.x(); x < fg_rect.x()+fg_rect.width(); ++x)
01183 {
01184 if ((x < 0) || (x > hl_button->w))
01185 continue;
01186 for (int y=fg_rect.y(); y < fg_rect.y()+fg_rect.height(); ++y)
01187 {
01188 if ((y < 0) || (y > hl_button->h))
01189 continue;
01190 bg_image.setPixel(x, y, fg_image.pixel(x-fg_rect.x(),y-fg_rect.y()));
01191 }
01192 }
01193
01194 AddScaledImage(bg_image, rect);
01195 }
01196
01202 static QString extract_cc608(
01203 QString &text, bool teletextmode, int &color,
01204 bool &isItalic, bool &isUnderline)
01205 {
01206 QString result;
01207 QString orig(text);
01208
01209 if (teletextmode)
01210 {
01211 result = text;
01212 text = QString::null;
01213 return result;
01214 }
01215
01216
01217 if (text.length() >= 1 && text[0] >= 0x7000)
01218 {
01219 int op = text[0].unicode() - 0x7000;
01220 isUnderline = (op & 0x1);
01221 switch (op & ~1)
01222 {
01223 case 0x0e:
01224
01225 isItalic = true;
01226 break;
01227 case 0x1e:
01228 color = op >> 1;
01229 isItalic = true;
01230 break;
01231 case 0x20:
01232
01233
01234 break;
01235 default:
01236 color = (op & 0xf) >> 1;
01237 isItalic = false;
01238 break;
01239 }
01240 text = text.mid(1);
01241 }
01242
01243
01244
01245 int nextControl = text.indexOf(QRegExp("[\\x7000-\\x7fff]"));
01246 if (nextControl < 0)
01247 {
01248 result = text;
01249 text = QString::null;
01250 }
01251 else
01252 {
01253 result = text.left(nextControl);
01254
01255
01256
01257
01258
01259 if (text[nextControl] < (0x7000 + 0x10))
01260 result += " ";
01261 text = text.mid(nextControl);
01262 }
01263
01264 return result;
01265 }
01266
01267 void SubtitleScreen::DisplayCC608Subtitles(void)
01268 {
01269 if (!m_608reader)
01270 return;
01271
01272 bool changed = (m_textFontZoom != m_textFontZoomPrev);
01273
01274 if (!m_player || !m_player->GetVideoOutput())
01275 return;
01276 m_safeArea = m_player->GetVideoOutput()->GetSafeRect();
01277
01278 CC608Buffer* textlist = m_608reader->GetOutputText(changed);
01279 if (!changed)
01280 return;
01281
01282 if (textlist)
01283 textlist->lock.lock();
01284
01285 DeleteAllChildren();
01286
01287 if (!textlist)
01288 return;
01289
01290 if (textlist->buffers.empty())
01291 {
01292 SetRedraw();
01293 textlist->lock.unlock();
01294 return;
01295 }
01296
01297 FormattedTextSubtitle fsub(m_safeArea, this);
01298 fsub.InitFromCC608(textlist->buffers, m_textFontZoom);
01299 fsub.Layout608();
01300 fsub.Layout();
01301 m_refreshArea = fsub.Draw(m_family) || m_refreshArea;
01302 textlist->lock.unlock();
01303 }
01304
01305 void SubtitleScreen::DisplayCC708Subtitles(void)
01306 {
01307 if (!m_708reader)
01308 return;
01309
01310 CC708Service *cc708service = m_708reader->GetCurrentService();
01311 float video_aspect = 1.77777f;
01312 bool changed = false;
01313 if (m_player && m_player->GetVideoOutput())
01314 {
01315 video_aspect = m_player->GetVideoAspect();
01316 QRect oldsafe = m_safeArea;
01317 m_safeArea = m_player->GetVideoOutput()->GetSafeRect();
01318 changed = (oldsafe != m_safeArea ||
01319 m_textFontZoom != m_textFontZoomPrev);
01320 if (changed)
01321 {
01322 for (uint i = 0; i < 8; i++)
01323 cc708service->windows[i].changed = true;
01324 }
01325 }
01326 else
01327 {
01328 return;
01329 }
01330
01331 for (uint i = 0; i < 8; i++)
01332 {
01333 CC708Window &win = cc708service->windows[i];
01334 if (win.exists && win.visible && !win.changed)
01335 continue;
01336
01337 Clear708Cache(i);
01338 if (!win.exists || !win.visible)
01339 continue;
01340
01341 QMutexLocker locker(&win.lock);
01342 vector<CC708String*> list = win.GetStrings();
01343 if (!list.empty())
01344 {
01345 FormattedTextSubtitle fsub(m_safeArea, this);
01346 fsub.InitFromCC708(win, i, list, video_aspect, m_textFontZoom);
01347 fsub.Layout();
01348
01349
01350 if (win.GetFillAlpha())
01351 {
01352 QBrush fill(win.GetFillColor(), Qt::SolidPattern);
01353 MythUIShape *shape =
01354 new MythUIShape(this, QString("cc708bg%1").arg(i));
01355 shape->SetFillBrush(fill);
01356 shape->SetArea(MythRect(fsub.m_bounds));
01357 m_708imageCache[i].append(shape);
01358 m_refreshArea = true;
01359 }
01360 m_refreshArea =
01361 fsub.Draw(m_family, &m_708imageCache[i]) || m_refreshArea;
01362 }
01363 for (uint j = 0; j < list.size(); j++)
01364 delete list[j];
01365 win.changed = false;
01366 }
01367 }
01368
01369 void SubtitleScreen::Clear708Cache(int num)
01370 {
01371 if (!m_708imageCache[num].isEmpty())
01372 {
01373 foreach(MythUIType* image, m_708imageCache[num])
01374 DeleteChild(image);
01375 m_708imageCache[num].clear();
01376 }
01377 }
01378
01379 void SubtitleScreen::AddScaledImage(QImage &img, QRect &pos)
01380 {
01381 VideoOutput *vo = m_player->GetVideoOutput();
01382 if (!vo)
01383 return;
01384
01385 QRect scaled = vo->GetImageRect(pos);
01386 if (scaled.size() != pos.size())
01387 {
01388 img = img.scaled(scaled.width(), scaled.height(),
01389 Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
01390 }
01391
01392 MythPainter *osd_painter = vo->GetOSDPainter();
01393 MythImage* image = NULL;
01394 if (osd_painter)
01395 image = osd_painter->GetFormatImage();
01396
01397 if (image)
01398 {
01399 image->Assign(img);
01400 MythUIImage *uiimage = new MythUIImage(this, "dvd_button");
01401 if (uiimage)
01402 {
01403 m_refreshArea = true;
01404 uiimage->SetImage(image);
01405 uiimage->SetArea(MythRect(scaled));
01406 }
01407 }
01408 }
01409
01410 MythFontProperties* SubtitleScreen::GetFont(CC708CharacterAttribute attr,
01411 bool teletext) const
01412 {
01413 return m_format->GetFont(m_family, attr, m_fontSize,
01414 teletext, m_textFontZoom, m_fontStretch);
01415 }
01416
01417 void SubtitleScreen::SetZoom(int percent)
01418 {
01419 m_textFontZoom = percent;
01420 if (m_family == kSubFamilyAV)
01421 gCoreContext->SaveSetting("OSDAVSubZoom", percent);
01422 else
01423 gCoreContext->SaveSetting("OSDCC708TextZoom", percent);
01424 }
01425
01426 int SubtitleScreen::GetZoom(void)
01427 {
01428 return m_textFontZoom;
01429 }
01430
01431 static QString srtColorString(QColor color)
01432 {
01433 return QString("#%1%2%3")
01434 .arg(color.red(), 2, 16, QLatin1Char('0'))
01435 .arg(color.green(), 2, 16, QLatin1Char('0'))
01436 .arg(color.blue(), 2, 16, QLatin1Char('0'));
01437 }
01438
01439 void FormattedTextSubtitle::InitFromCC608(vector<CC608Text*> &buffers,
01440 int textFontZoom)
01441 {
01442 static const QColor clr[8] =
01443 {
01444 Qt::white, Qt::green, Qt::blue, Qt::cyan,
01445 Qt::red, Qt::yellow, Qt::magenta, Qt::white,
01446 };
01447
01448 if (buffers.empty())
01449 return;
01450 vector<CC608Text*>::iterator i = buffers.begin();
01451 bool teletextmode = (*i)->teletextmode;
01452
01453 int xscale = teletextmode ? 40 : 36;
01454 int yscale = teletextmode ? 25 : 17;
01455
01456
01457
01458
01459 int pixelSize = m_safeArea.height() / (17 * LINE_SPACING);
01460 int fontwidth = 0;
01461 int xmid = 0;
01462 if (parent)
01463 {
01464 parent->SetFontSize(pixelSize);
01465 CC708CharacterAttribute def_attr(false, false, false, clr[0]);
01466 QFont *font = parent->GetFont(def_attr, teletextmode)->GetFace();
01467 QFontMetrics fm(*font);
01468 fontwidth = fm.averageCharWidth();
01469 xmid = m_safeArea.width() / 2;
01470
01471 if (textFontZoom >= 100)
01472 xscale = m_safeArea.width() / fontwidth;
01473 }
01474
01475 for (; i != buffers.end(); ++i)
01476 {
01477 CC608Text *cc = (*i);
01478 int color = 0;
01479 bool isItalic = false, isUnderline = false;
01480 const bool isBold = false;
01481 QString text(cc->text);
01482
01483 int orig_x = teletextmode ? cc->y : cc->x;
01484
01485
01486
01487 int x;
01488 if (xmid)
01489
01490 x = xmid + (orig_x - xscale / 2) * fontwidth;
01491 else
01492
01493 x = (orig_x + 3) * m_safeArea.width() / xscale;
01494
01495 int orig_y = teletextmode ? cc->x : cc->y;
01496 int y;
01497 if (orig_y < yscale / 2)
01498
01499 y = (orig_y * m_safeArea.height() * textFontZoom / (yscale * 100));
01500 else
01501
01502 y = m_safeArea.height() -
01503 ((yscale - orig_y - 0.5) * m_safeArea.height() * textFontZoom /
01504 (yscale * 100));
01505
01506 FormattedTextLine line(x, y, orig_x, orig_y);
01507 while (!text.isNull())
01508 {
01509 QString captionText =
01510 extract_cc608(text, cc->teletextmode,
01511 color, isItalic, isUnderline);
01512 CC708CharacterAttribute attr(isItalic, isBold, isUnderline,
01513 clr[min(max(0, color), 7)]);
01514 FormattedTextChunk chunk(captionText, attr, parent,
01515 cc->teletextmode);
01516 line.chunks += chunk;
01517 LOG(VB_VBI, LOG_INFO,
01518 QString("Adding cc608 chunk (%1,%2): %3")
01519 .arg(cc->x).arg(cc->y).arg(chunk.ToLogString()));
01520 }
01521 m_lines += line;
01522 }
01523 }
01524
01525 void FormattedTextSubtitle::InitFromCC708(const CC708Window &win, int num,
01526 const vector<CC708String*> &list,
01527 float aspect,
01528 int textFontZoom)
01529 {
01530 LOG(VB_VBI, LOG_INFO,LOC +
01531 QString("Display Win %1, Anchor_id %2, x_anch %3, y_anch %4, "
01532 "relative %5")
01533 .arg(num).arg(win.anchor_point).arg(win.anchor_horizontal)
01534 .arg(win.anchor_vertical).arg(win.relative_pos));
01535 int pixelSize = m_safeArea.height() / 20;
01536 if (parent)
01537 parent->SetFontSize(pixelSize);
01538
01539 float xrange = win.relative_pos ? 100.0f :
01540 (aspect > 1.4f) ? 210.0f : 160.0f;
01541 float yrange = win.relative_pos ? 100.0f : 75.0f;
01542 float xmult = (float)m_safeArea.width() / xrange;
01543 float ymult = (float)m_safeArea.height() / yrange;
01544 uint anchor_x = (uint)(xmult * (float)win.anchor_horizontal);
01545 uint anchor_y = (uint)(ymult * (float)win.anchor_vertical);
01546 m_xAnchorPoint = win.anchor_point % 3;
01547 m_yAnchorPoint = win.anchor_point / 3;
01548 m_xAnchor = anchor_x;
01549 m_yAnchor = anchor_y;
01550
01551 for (uint i = 0; i < list.size(); i++)
01552 {
01553 if (list[i]->y >= (uint)m_lines.size())
01554 m_lines.resize(list[i]->y + 1);
01555 FormattedTextChunk chunk(list[i]->str, list[i]->attr, parent);
01556 m_lines[list[i]->y].chunks += chunk;
01557 LOG(VB_VBI, LOG_INFO, QString("Adding cc708 chunk: win %1 row %2: %3")
01558 .arg(num).arg(i).arg(chunk.ToLogString()));
01559 }
01560 }
01561
01562 void FormattedTextSubtitle::InitFromSRT(QStringList &subs, int textFontZoom)
01563 {
01564
01565
01566
01567
01568
01569
01570
01571
01572
01573
01574
01575
01576
01577
01578 int pixelSize = m_safeArea.height() / 20;
01579 if (parent)
01580 parent->SetFontSize(pixelSize);
01581 m_xAnchorPoint = 1;
01582 m_yAnchorPoint = 2;
01583 m_xAnchor = m_safeArea.width() / 2;
01584 m_yAnchor = m_safeArea.height();
01585
01586 bool isItalic = false;
01587 bool isBold = false;
01588 bool isUnderline = false;
01589 QColor color(Qt::white);
01590 QRegExp htmlTag("</?.+>");
01591 QString htmlPrefix("<font color=\""), htmlSuffix("\">");
01592 htmlTag.setMinimal(true);
01593 foreach (QString subtitle, subs)
01594 {
01595 FormattedTextLine line;
01596 QString text(subtitle);
01597 while (!text.isEmpty())
01598 {
01599 int pos = text.indexOf(htmlTag);
01600 if (pos != 0)
01601 {
01602 CC708CharacterAttribute attr(isItalic, isBold, isUnderline,
01603 color);
01604 FormattedTextChunk chunk(text.left(pos), attr, parent);
01605 line.chunks += chunk;
01606 text = (pos < 0 ? "" : text.mid(pos));
01607 LOG(VB_VBI, LOG_INFO, QString("Adding SRT chunk: %1")
01608 .arg(chunk.ToLogString()));
01609 }
01610 if (pos >= 0)
01611 {
01612 int htmlLen = htmlTag.matchedLength();
01613 QString html = text.left(htmlLen).toLower();
01614 text = text.mid(htmlLen);
01615 if (html == "<i>")
01616 isItalic = true;
01617 else if (html == "</i>")
01618 isItalic = false;
01619 else if (html.startsWith(htmlPrefix) &&
01620 html.endsWith(htmlSuffix))
01621 {
01622 int colorLen = html.length() -
01623 (htmlPrefix.length() + htmlSuffix.length());
01624 QString colorString(
01625 html.mid(htmlPrefix.length(), colorLen));
01626 QColor newColor(colorString);
01627 if (newColor.isValid())
01628 {
01629 color = newColor;
01630 }
01631 else
01632 {
01633 LOG(VB_VBI, LOG_INFO,
01634 QString("Ignoring invalid SRT color specification: "
01635 "'%1'").arg(colorString));
01636 }
01637 }
01638 else if (html == "</font>")
01639 color = Qt::white;
01640 else if (html == "<b>")
01641 isBold = true;
01642 else if (html == "</b>")
01643 isBold = false;
01644 else if (html == "<u>")
01645 isUnderline = true;
01646 else if (html == "</u>")
01647 isUnderline = false;
01648 else
01649 {
01650 LOG(VB_VBI, LOG_INFO,
01651 QString("Ignoring unknown SRT formatting: '%1'")
01652 .arg(html));
01653 }
01654
01655 LOG(VB_VBI, LOG_INFO,
01656 QString("SRT formatting change '%1', "
01657 "new ital=%2 bold=%3 uline=%4 color=%5)")
01658 .arg(html).arg(isItalic).arg(isBold).arg(isUnderline)
01659 .arg(srtColorString(color)));
01660 }
01661 }
01662 m_lines += line;
01663 }
01664 }
01665
01666 bool FormattedTextChunk::Split(FormattedTextChunk &newChunk)
01667 {
01668 LOG(VB_VBI, LOG_INFO,
01669 QString("Attempting to split chunk '%1'").arg(text));
01670 int lastSpace = text.lastIndexOf(' ', -2);
01671 if (lastSpace < 0)
01672 {
01673 LOG(VB_VBI, LOG_INFO,
01674 QString("Failed to split chunk '%1'").arg(text));
01675 return false;
01676 }
01677 newChunk.isTeletext = isTeletext;
01678 newChunk.parent = parent;
01679 newChunk.format = format;
01680 newChunk.text = text.mid(lastSpace + 1).trimmed() + ' ';
01681 text = text.left(lastSpace).trimmed();
01682 LOG(VB_VBI, LOG_INFO,
01683 QString("Split chunk into '%1' + '%2'").arg(text).arg(newChunk.text));
01684 return true;
01685 }
01686
01687 QString FormattedTextChunk::ToLogString(void) const
01688 {
01689 QString str("");
01690 str += QString("fg=%1.%2 ")
01691 .arg(srtColorString(format.GetFGColor()))
01692 .arg(format.GetFGAlpha());
01693 str += QString("bg=%1.%2 ")
01694 .arg(srtColorString(format.GetBGColor()))
01695 .arg(format.GetBGAlpha());
01696 str += QString("edge=%1.%2 ")
01697 .arg(srtColorString(format.GetEdgeColor()))
01698 .arg(format.edge_type);
01699 str += QString("off=%1 pensize=%2 ")
01700 .arg(format.offset)
01701 .arg(format.pen_size);
01702 str += QString("it=%1 ul=%2 bf=%3 ")
01703 .arg(format.italics)
01704 .arg(format.underline)
01705 .arg(format.boldface);
01706 str += QString("font=%1 ").arg(format.font_tag);
01707 str += QString(" text='%1'").arg(text);
01708 return str;
01709 }
01710
01711 void FormattedTextSubtitle::WrapLongLines(void)
01712 {
01713 int maxWidth = m_safeArea.width();
01714 for (int i = 0; i < m_lines.size(); i++)
01715 {
01716 int width = m_lines[i].CalcSize().width();
01717
01718
01719 while (width > maxWidth && m_lines[i].chunks.size() > 1)
01720 {
01721 width -= m_lines[i].chunks.back().CalcSize().width();
01722
01723 if (m_lines.size() == i + 1)
01724 m_lines += FormattedTextLine(m_lines[i].x_indent,
01725 m_lines[i].y_indent);
01726 m_lines[i+1].chunks.prepend(m_lines[i].chunks.takeLast());
01727 LOG(VB_VBI, LOG_INFO,
01728 QString("Wrapping chunk to next line: '%1'")
01729 .arg(m_lines[i+1].chunks[0].text));
01730 }
01731
01732
01733 bool isSplitPossible = true;
01734 while (width > maxWidth && isSplitPossible)
01735 {
01736 FormattedTextChunk newChunk;
01737 isSplitPossible = m_lines[i].chunks.back().Split(newChunk);
01738 if (isSplitPossible)
01739 {
01740
01741 if (m_lines.size() == i + 1)
01742 m_lines += FormattedTextLine(m_lines[i].x_indent,
01743 m_lines[i].y_indent);
01744 m_lines[i+1].chunks.prepend(newChunk);
01745 width = m_lines[i].CalcSize().width();
01746 }
01747 }
01748 }
01749 }
01750
01751
01752
01753
01754
01755 void FormattedTextSubtitle::Layout608(void)
01756 {
01757 int i;
01758 int totalHeight = 0;
01759 int totalSpace = 0;
01760 int firstY = 0;
01761 int prevY = 0;
01762 QVector<int> heights(m_lines.size());
01763 QVector<int> spaceBefore(m_lines.size());
01764
01765 for (i = 0; i < m_lines.size(); i++)
01766 {
01767 m_lines[i].y_indent = max(m_lines[i].y_indent, prevY);
01768 int y = m_lines[i].y_indent;
01769 if (i == 0)
01770 firstY = prevY = y;
01771 int height = m_lines[i].CalcSize().height();
01772 heights[i] = height;
01773 spaceBefore[i] = y - prevY;
01774 totalSpace += (y - prevY);
01775 prevY = y + height;
01776 totalHeight += height;
01777 }
01778 int safeHeight = m_safeArea.height();
01779 int overage = min(totalHeight - safeHeight, totalSpace);
01780
01781
01782
01783 if (overage > 0 && totalSpace > 0)
01784 {
01785 float shrink = (totalSpace - overage) / (float)totalSpace;
01786 prevY = firstY;
01787 for (i = 0; i < m_lines.size(); i++)
01788 {
01789 m_lines[i].y_indent = prevY + spaceBefore[i] * shrink;
01790 prevY = m_lines[i].y_indent + heights[i];
01791 }
01792 }
01793
01794
01795 int shift = min(firstY, max(0, prevY - safeHeight));
01796 for (i = 0; i < m_lines.size(); i++)
01797 m_lines[i].y_indent -= shift;
01798 }
01799
01800
01801
01802
01803
01804 void FormattedTextSubtitle::Layout(void)
01805 {
01806
01807 int anchor_width = 0;
01808 int anchor_height = 0;
01809 for (int i = 0; i < m_lines.size(); i++)
01810 {
01811 QSize sz = m_lines[i].CalcSize(LINE_SPACING);
01812 anchor_width = max(anchor_width, sz.width());
01813 anchor_height += sz.height();
01814 }
01815
01816
01817 int anchor_x = m_xAnchor;
01818 int anchor_y = m_yAnchor;
01819 if (m_xAnchorPoint == 1)
01820 anchor_x -= anchor_width / 2;
01821 else if (m_xAnchorPoint == 2)
01822 anchor_x -= anchor_width;
01823 if (m_yAnchorPoint == 1)
01824 anchor_y -= anchor_height / 2;
01825 else if (m_yAnchorPoint == 2)
01826 anchor_y -= anchor_height;
01827
01828
01829 anchor_y = max(0, min(anchor_y, m_safeArea.height() - anchor_height));
01830 anchor_x = max(0, min(anchor_x, m_safeArea.width() - anchor_width));
01831
01832 m_bounds = QRect(anchor_x, anchor_y, anchor_width, anchor_height);
01833
01834
01835 int y = anchor_y;
01836 for (int i = 0; i < m_lines.size(); i++)
01837 {
01838 if (m_lines[i].x_indent < 0)
01839 m_lines[i].x_indent = anchor_x;
01840 if (m_lines[i].y_indent < 0)
01841 m_lines[i].y_indent = y;
01842 y += m_lines[i].CalcSize(LINE_SPACING).height();
01843
01844 while (!m_lines[i].chunks.isEmpty() &&
01845 m_lines[i].chunks.first().text.trimmed().isEmpty())
01846 {
01847 m_lines[i].x_indent +=
01848 m_lines[i].chunks.first().CalcSize().width();
01849 m_lines[i].chunks.removeFirst();
01850 }
01851
01852 while (!m_lines[i].chunks.isEmpty() &&
01853 m_lines[i].chunks.last().text.trimmed().isEmpty())
01854 {
01855 m_lines[i].chunks.removeLast();
01856 }
01857
01858
01859
01860 if (!m_lines[i].chunks.isEmpty())
01861 {
01862 QString *str = &m_lines[i].chunks.last().text;
01863 int idx = str->length() - 1;
01864 while (idx >= 0 && str->at(idx) == ' ')
01865 --idx;
01866 str->truncate(idx + 1);
01867 }
01868 }
01869 }
01870
01871
01872
01873
01874 bool FormattedTextSubtitle::Draw(const QString &base,
01875 QList<MythUIType*> *imageCache,
01876 uint64_t start, uint64_t duration) const
01877 {
01878 bool result = false;
01879 QVector<MythUISimpleText *> bringToFront;
01880
01881 for (int i = 0; i < m_lines.size(); i++)
01882 {
01883 int x = m_lines[i].x_indent;
01884 int y = m_lines[i].y_indent;
01885 int height = m_lines[i].CalcSize().height();
01886 QList<FormattedTextChunk>::const_iterator chunk;
01887 bool first = true;
01888 for (chunk = m_lines[i].chunks.constBegin();
01889 chunk != m_lines[i].chunks.constEnd();
01890 ++chunk)
01891 {
01892 MythFontProperties *mythfont =
01893 parent->GetFont((*chunk).format, (*chunk).isTeletext);
01894 if (!mythfont)
01895 continue;
01896 QFontMetrics font(*(mythfont->GetFace()));
01897
01898
01899
01900
01901
01902 int count = 0;
01903 while (count < (*chunk).text.length() &&
01904 (*chunk).text.at(count) == ' ')
01905 {
01906 ++count;
01907 }
01908 int x_adjust = count * font.width(" ");
01909 int leftPadding = (*chunk).CalcPadding(true);
01910 int rightPadding = (*chunk).CalcPadding(false);
01911
01912 if (first)
01913 x += leftPadding;
01914 QSize chunk_sz = (*chunk).CalcSize();
01915 QRect bgrect(x - leftPadding, y,
01916 chunk_sz.width() + leftPadding + rightPadding,
01917 height);
01918
01919 if (first)
01920 bgrect.setLeft(bgrect.left() + x_adjust);
01921 MythUIShape *bgshape =
01922 parent->m_format->
01923 GetBackground(parent,
01924 QString("subbg%1x%2@%3,%4")
01925 .arg(chunk_sz.width()).arg(height)
01926 .arg(x).arg(y),
01927 base, (*chunk).format);
01928 bgshape->SetArea(MythRect(bgrect));
01929 if (imageCache)
01930 imageCache->append(bgshape);
01931 if (duration > 0)
01932 parent->RegisterExpiration(bgshape, start + duration);
01933 result = true;
01934
01935
01936
01937 QRect rect(x + x_adjust, y,
01938 chunk_sz.width() - x_adjust + rightPadding, height);
01939
01940 MythUISimpleText *text =
01941 new MythUISimpleText((*chunk).text, *mythfont, rect,
01942 Qt::AlignLeft, (MythUIType*)parent,
01943 QString("subtxt%1x%2@%3,%4")
01944 .arg(chunk_sz.width())
01945 .arg(height)
01946 .arg(x).arg(y));
01947 bringToFront.append(text);
01948 if (imageCache)
01949 imageCache->append(text);
01950 if (duration > 0)
01951 parent->RegisterExpiration(text, start + duration);
01952 result = true;
01953
01954 LOG(VB_VBI, LOG_INFO,
01955 QString("Drawing chunk at (%1,%2): %3")
01956 .arg(x).arg(y).arg((*chunk).ToLogString()));
01957
01958 x += chunk_sz.width();
01959 first = false;
01960 }
01961 }
01962
01963
01964 for (int i = 0; i < bringToFront.size(); i++)
01965 bringToFront.at(i)->MoveToTop();
01966 return result;
01967 }
01968
01969 QStringList FormattedTextSubtitle::ToSRT(void) const
01970 {
01971 QStringList result;
01972 for (int i = 0; i < m_lines.size(); i++)
01973 {
01974 QString line;
01975 if (m_lines[i].orig_x > 0)
01976 line.fill(' ', m_lines[i].orig_x);
01977 QList<FormattedTextChunk>::const_iterator chunk;
01978 for (chunk = m_lines[i].chunks.constBegin();
01979 chunk != m_lines[i].chunks.constEnd();
01980 ++chunk)
01981 {
01982 const QString &text = (*chunk).text;
01983 const CC708CharacterAttribute &attr = (*chunk).format;
01984 bool isBlank = !attr.underline && text.trimmed().isEmpty();
01985 if (!isBlank)
01986 {
01987 if (attr.boldface)
01988 line += "<b>";
01989 if (attr.italics)
01990 line += "<i>";
01991 if (attr.underline)
01992 line += "<u>";
01993 if (attr.GetFGColor() != Qt::white)
01994 line += QString("<font color=\"%1\">")
01995 .arg(srtColorString(attr.GetFGColor()));
01996 }
01997 line += text;
01998 if (!isBlank)
01999 {
02000 if (attr.GetFGColor() != Qt::white)
02001 line += QString("</font>");
02002 if (attr.underline)
02003 line += "</u>";
02004 if (attr.italics)
02005 line += "</i>";
02006 if (attr.boldface)
02007 line += "</b>";
02008 }
02009 }
02010 if (!line.trimmed().isEmpty())
02011 result += line;
02012 }
02013 return result;
02014 }
02015
02016
02017
02018
02019
02020 static QSize CalcShadowOffsetPadding(MythFontProperties *mythfont)
02021 {
02022 QColor color;
02023 int alpha;
02024 int outlineSize = 0;
02025 int shadowWidth = 0, shadowHeight = 0;
02026 if (mythfont->hasOutline())
02027 {
02028 mythfont->GetOutline(color, outlineSize, alpha);
02029 }
02030 if (mythfont->hasShadow())
02031 {
02032 QPoint shadowOffset;
02033 mythfont->GetShadow(shadowOffset, color, alpha);
02034 shadowWidth = abs(shadowOffset.x());
02035 shadowHeight = abs(shadowOffset.y());
02036
02037 shadowWidth = max(shadowWidth, outlineSize);
02038 shadowHeight = max(shadowHeight, outlineSize);
02039 }
02040 return QSize(shadowWidth + outlineSize, shadowHeight + outlineSize);
02041 }
02042
02043 QSize SubtitleScreen::CalcTextSize(const QString &text,
02044 const CC708CharacterAttribute &format,
02045 bool teletext,
02046 float layoutSpacing) const
02047 {
02048 MythFontProperties *mythfont = GetFont(format, teletext);
02049 QFont *font = mythfont->GetFace();
02050 QFontMetrics fm(*font);
02051 int width = fm.width(text);
02052 int height = fm.height() * (1 + PAD_HEIGHT);
02053 if (layoutSpacing > 0 && !text.trimmed().isEmpty())
02054 height = max(height, (int)(font->pixelSize() * layoutSpacing));
02055 height += CalcShadowOffsetPadding(mythfont).height();
02056 return QSize(width, height);
02057 }
02058
02059
02060
02061
02062 int SubtitleScreen::CalcPadding(const CC708CharacterAttribute &format,
02063 bool teletext, bool isLeft) const
02064 {
02065 MythFontProperties *mythfont = GetFont(format, teletext);
02066 QFont *font = mythfont->GetFace();
02067 QFontMetrics fm(*font);
02068 int result = fm.maxWidth() * PAD_WIDTH;
02069 if (!isLeft)
02070 result += CalcShadowOffsetPadding(mythfont).width();
02071 return result;
02072 }
02073
02074 QString SubtitleScreen::GetTeletextFontName(void)
02075 {
02076 SubtitleFormat format;
02077 CC708CharacterAttribute attr(false, false, false, Qt::white);
02078 MythFontProperties *mythfont =
02079 format.GetFont(kSubFamilyTeletext, attr, 20, false, 100, 100);
02080 return mythfont->face().family();
02081 }
02082
02083 #ifdef USING_LIBASS
02084 static void myth_libass_log(int level, const char *fmt, va_list vl, void *ctx)
02085 {
02086 static QString full_line("libass:");
02087 static const int msg_len = 255;
02088 static QMutex string_lock;
02089 uint64_t verbose_mask = VB_GENERAL;
02090 LogLevel_t verbose_level = LOG_INFO;
02091
02092 switch (level)
02093 {
02094 case 0:
02095 verbose_level = LOG_EMERG;
02096 break;
02097 case 1:
02098 verbose_level = LOG_ERR;
02099 break;
02100 case 2:
02101 verbose_level = LOG_WARNING;
02102 break;
02103 case 4:
02104 verbose_level = LOG_INFO;
02105 break;
02106 case 6:
02107 case 7:
02108 verbose_level = LOG_DEBUG;
02109 break;
02110 default:
02111 return;
02112 }
02113
02114 if (!VERBOSE_LEVEL_CHECK(verbose_mask, verbose_level))
02115 return;
02116
02117 string_lock.lock();
02118
02119 char str[msg_len+1];
02120 int bytes = vsnprintf(str, msg_len+1, fmt, vl);
02121
02122 if (bytes > msg_len)
02123 {
02124 LOG(VB_GENERAL, LOG_ERR,
02125 QString("libASS log output truncated %1 of %2 bytes written")
02126 .arg(msg_len).arg(bytes));
02127 str[msg_len-1] = '\n';
02128 }
02129
02130 full_line += QString(str);
02131 if (full_line.endsWith("\n"))
02132 {
02133 LOG(verbose_mask, verbose_level, full_line.trimmed());
02134 full_line.truncate(0);
02135 }
02136 string_lock.unlock();
02137 }
02138
02139 bool SubtitleScreen::InitialiseAssLibrary(void)
02140 {
02141 if (m_assLibrary && m_assRenderer)
02142 return true;
02143
02144 if (!m_assLibrary)
02145 {
02146 m_assLibrary = ass_library_init();
02147 if (!m_assLibrary)
02148 return false;
02149
02150 ass_set_message_cb(m_assLibrary, myth_libass_log, NULL);
02151 ass_set_extract_fonts(m_assLibrary, true);
02152 LOG(VB_PLAYBACK, LOG_INFO, LOC + "Initialised libass object.");
02153 }
02154
02155 LoadAssFonts();
02156
02157 if (!m_assRenderer)
02158 {
02159 m_assRenderer = ass_renderer_init(m_assLibrary);
02160 if (!m_assRenderer)
02161 return false;
02162
02163 ass_set_fonts(m_assRenderer, NULL, "sans-serif", 1, NULL, 1);
02164 ass_set_hinting(m_assRenderer, ASS_HINTING_LIGHT);
02165 LOG(VB_PLAYBACK, LOG_INFO, LOC + "Initialised libass renderer.");
02166 }
02167
02168 return true;
02169 }
02170
02171 void SubtitleScreen::LoadAssFonts(void)
02172 {
02173 if (!m_assLibrary || !m_player)
02174 return;
02175
02176 uint count = m_player->GetDecoder()->GetTrackCount(kTrackTypeAttachment);
02177 if (m_assFontCount == count)
02178 return;
02179
02180 ass_clear_fonts(m_assLibrary);
02181 m_assFontCount = 0;
02182
02183
02184 for (uint i = 0; i < count; ++i)
02185 {
02186 QByteArray filename;
02187 QByteArray font;
02188 m_player->GetDecoder()->GetAttachmentData(i, filename, font);
02189 ass_add_font(m_assLibrary, filename.data(), font.data(), font.size());
02190 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Retrieved font '%1'")
02191 .arg(filename.constData()));
02192 m_assFontCount++;
02193 }
02194 }
02195
02196 void SubtitleScreen::CleanupAssLibrary(void)
02197 {
02198 CleanupAssTrack();
02199
02200 if (m_assRenderer)
02201 ass_renderer_done(m_assRenderer);
02202 m_assRenderer = NULL;
02203
02204 if (m_assLibrary)
02205 {
02206 ass_clear_fonts(m_assLibrary);
02207 m_assFontCount = 0;
02208 ass_library_done(m_assLibrary);
02209 }
02210 m_assLibrary = NULL;
02211 }
02212
02213 void SubtitleScreen::InitialiseAssTrack(int tracknum)
02214 {
02215 if (!InitialiseAssLibrary() || !m_player)
02216 return;
02217
02218 if (tracknum == m_assTrackNum && m_assTrack)
02219 return;
02220
02221 LoadAssFonts();
02222 CleanupAssTrack();
02223 m_assTrack = ass_new_track(m_assLibrary);
02224 m_assTrackNum = tracknum;
02225
02226 QByteArray header = m_player->GetDecoder()->GetSubHeader(tracknum);
02227 if (!header.isNull())
02228 ass_process_codec_private(m_assTrack, header.data(), header.size());
02229
02230 m_safeArea = m_player->GetVideoOutput()->GetMHEGBounds();
02231 ResizeAssRenderer();
02232 }
02233
02234 void SubtitleScreen::CleanupAssTrack(void)
02235 {
02236 if (m_assTrack)
02237 ass_free_track(m_assTrack);
02238 m_assTrack = NULL;
02239 }
02240
02241 void SubtitleScreen::AddAssEvent(char *event)
02242 {
02243 if (m_assTrack && event)
02244 ass_process_data(m_assTrack, event, strlen(event));
02245 }
02246
02247 void SubtitleScreen::ResizeAssRenderer(void)
02248 {
02249
02250 ass_set_frame_size(m_assRenderer, m_safeArea.width(), m_safeArea.height());
02251 ass_set_margins(m_assRenderer, 0, 0, 0, 0);
02252 ass_set_use_margins(m_assRenderer, true);
02253 ass_set_font_scale(m_assRenderer, 1.0);
02254 }
02255
02256 void SubtitleScreen::RenderAssTrack(uint64_t timecode)
02257 {
02258 if (!m_player || !m_assRenderer || !m_assTrack)
02259 return;
02260
02261 VideoOutput *vo = m_player->GetVideoOutput();
02262 if (!vo )
02263 return;
02264
02265 QRect oldscreen = m_safeArea;
02266 m_safeArea = vo->GetMHEGBounds();
02267 if (oldscreen != m_safeArea)
02268 ResizeAssRenderer();
02269
02270 int changed = 0;
02271 ASS_Image *images = ass_render_frame(m_assRenderer, m_assTrack,
02272 timecode, &changed);
02273 if (!changed)
02274 return;
02275
02276 MythPainter *osd_painter = vo->GetOSDPainter();
02277 if (!osd_painter)
02278 return;
02279
02280 int count = 0;
02281 DeleteAllChildren();
02282 SetRedraw();
02283 while (images)
02284 {
02285 if (images->w == 0 || images->h == 0)
02286 {
02287 images = images->next;
02288 continue;
02289 }
02290
02291 uint8_t alpha = images->color & 0xFF;
02292 uint8_t blue = images->color >> 8 & 0xFF;
02293 uint8_t green = images->color >> 16 & 0xFF;
02294 uint8_t red = images->color >> 24 & 0xFF;
02295
02296 if (alpha == 255)
02297 {
02298 images = images->next;
02299 continue;
02300 }
02301
02302 QSize img_size(images->w, images->h);
02303 QRect img_rect(images->dst_x,images->dst_y,
02304 images->w, images->h);
02305 QImage qImage(img_size, QImage::Format_ARGB32);
02306 qImage.fill(0x00000000);
02307
02308 unsigned char *src = images->bitmap;
02309 for (int y = 0; y < images->h; ++y)
02310 {
02311 for (int x = 0; x < images->w; ++x)
02312 {
02313 uint8_t value = src[x];
02314 if (value)
02315 {
02316 uint32_t pixel = (value * (255 - alpha) / 255 << 24) |
02317 (red << 16) | (green << 8) | blue;
02318 qImage.setPixel(x, y, pixel);
02319 }
02320 }
02321 src += images->stride;
02322 }
02323
02324 MythImage* image = NULL;
02325 MythUIImage *uiimage = NULL;
02326
02327 if (osd_painter)
02328 image = osd_painter->GetFormatImage();
02329
02330 if (image)
02331 {
02332 image->Assign(qImage);
02333 QString name = QString("asssub%1").arg(count);
02334 uiimage = new MythUIImage(this, name);
02335 if (uiimage)
02336 {
02337 m_refreshArea = true;
02338 uiimage->SetImage(image);
02339 uiimage->SetArea(MythRect(img_rect));
02340 }
02341 }
02342 images = images->next;
02343 count++;
02344 }
02345 }
02346 #endif // USING_LIBASS