|
MythTV
0.26-pre
|
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 // Class SubtitleFormat manages fonts and backgrounds for subtitles. 00015 // 00016 // Formatting is specified by the theme in the new file 00017 // osd_subtitle.xml, in the osd_subtitle window. Subtitle types are 00018 // text, teletext, 608, 708_0, 708_1, ..., 708_7. Each subtitle type 00019 // has a fontdef component for the text and a shape component for the 00020 // background. By default, the attributes of a subtitle type come 00021 // from either the provider (e.g. font color, italics) or the system 00022 // default (e.g. font family). These attributes can be overridden by 00023 // the xml file. 00024 // 00025 // The fontdef name and the background shape name are both simply the 00026 // name of the subtitle type. The fontdef and shape should ultimately 00027 // inherit from the special value "provider", otherwise all provider 00028 // values will be ignored. 00029 // 00030 // The following example forces .srt subtitles to be rendered in 00031 // yellow text, FreeSans font, black shadow, with a translucent black 00032 // background. The fontdef and shape names are "text" since the 00033 // subtitles come from a .srt file. Note that in this example, color 00034 // formatting controls in the .srt file will be ignored due to the 00035 // explicit setting of yellow text. 00036 // 00037 // <?xml version="1.0" encoding="utf-8"?> 00038 // <!DOCTYPE mythuitheme SYSTEM "http://www.mythtv.org/schema/mythuitheme.dtd"> 00039 // <mythuitheme> 00040 // <window name="osd_subtitle"> 00041 // <fontdef name="text" face="FreeSans" from="provider"> 00042 // <color>#FFFF00</color> 00043 // <shadowoffset>2,2</shadowoffset> 00044 // <shadowcolor>#000000</shadowcolor> 00045 // </fontdef> 00046 // <shape name="text" from="provider"> 00047 // <fill color="#000000" alpha="100" /> 00048 // </shape> 00049 // </window> 00050 // </mythuitheme> 00051 // 00052 // All elements/attributes of fontdef and shape can be used. Note 00053 // however that area and position are explicitly ignored, as these are 00054 // dictated by the subtitle layout. 00055 // 00056 // This is implemented with almost no MythUI changes. Two copies of a 00057 // "provider" object are created, with default/representative provider 00058 // attributes. One copy is then "complemented" to have a different 00059 // value for each attribute that a provider might change. The 00060 // osd_subtitle.xml file is loaded twice, once with respect to each 00061 // provider object, and the desired fontdef or shape is looked up. 00062 // The two fontdefs or shapes are compared attribute by attribute, and 00063 // each attribute that is different is an attribute that the provider 00064 // may modify, whereas each identical attribute represents one that is 00065 // fixed by the theme. 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; // just to be safe 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", // default 00202 "FreeMono", // mono serif 00203 "DejaVu Serif", // prop serif 00204 "Droid Sans Mono", // mono sans 00205 "Liberation Sans", // prop sans 00206 "Purisa", // casual 00207 "URW Chancery L", // cursive 00208 "Impact" // capitals 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 // Change everything (with respect to the == operator) that the 00237 // provider might define or override. 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 // Widgets for the actual values 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 // Widgets for the "negative" values 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 // Apply the scaling factor to pixelSize even if the theme 00410 // explicitly sets pixelSize. 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 // add code here when teletextscreen.cpp is refactored 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 // Normal font height is designed to be 1/20 of safe area height, with 00521 // extra blank space between lines to make 17 lines within the safe 00522 // area. 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(); // allow forced subtitles to work 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 // Resize subtitles if the zoom factor has changed since the last 00735 // update. 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 // XSUB and some DVD/DVB subs are based on the original video 00802 // size before the video was converted. We need to guess the 00803 // original size and allow for the difference 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 // split into upper/lower to allow zooming 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 // split image vertically if it spans middle of display 00874 // - split point is empty line nearest the middle 00875 // crop image to reduce scaling time 00876 int xmin, xmax, ymin, ymax; 00877 int ylast, ysplit; 00878 bool prev_empty = false; 00879 00880 // initialize to opposite edges 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 // find bounds of active image 00889 for (int y = bbox.top(); y <= bbox.bottom(); ++y) 00890 { 00891 if (y >= rect->h) 00892 { 00893 // end of image 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 // remember uppermost empty line 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 // no empty lines 00937 return 0; 00938 00939 if (ymax == bbox.bottom()) 00940 { 00941 ymax = ylast; 00942 ysplit = ylast; 00943 } 00944 } 00945 00946 // set new bounds 00947 bbox.setLeft(xmin); 00948 bbox.setRight(xmax); 00949 bbox.setTop(ymin); 00950 bbox.setBottom(ymax); 00951 00952 // copy active region 00953 // AVSubtitleRect's image data's not guaranteed to be 4 byte 00954 // aligned. 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 // translate to absolute coordinates 00973 bbox.translate(rect->x, rect->y); 00974 00975 // scale and move according to zoom factor 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 // anchor up 00994 scaled.moveTop(scaled.top() * m_textFontZoom / 100); 00995 else 00996 // anchor down 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 // frame based subtitles get out of synch after running mythcommflag 01056 // for the file, i.e., the following number is wrong and does not 01057 // match the subtitle frame numbers: 01058 playPos = currentFrame->frameNumber; 01059 } 01060 else 01061 { 01062 // Use timecodes for time based SRT subtitles. Feeding this into 01063 // NormalizeVideoTimecode() should adjust for non-zero start times 01064 // and wraps. For MPEG, wraps will occur just once every 26.5 hours 01065 // and other formats less frequently so this should be sufficient. 01066 // Note: timecodes should now always be valid even in the case 01067 // when a frame doesn't have a valid timestamp. If an exception is 01068 // found where this is not true then we need to use the frameNumber 01069 // when timecode is not defined by uncommenting the following lines. 01070 //if (currentFrame->timecode == 0) 01071 // playPos = (uint64_t) 01072 // ((currentFrame->frameNumber / video_frame_rate) * 1000); 01073 //else 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 // delete old subs that may still be on screen 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 // copy button region of background image 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 // set pixel of highlight area to highlight color 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 // Handle an initial control sequence. 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 // color unchanged 01225 isItalic = true; 01226 break; 01227 case 0x1e: 01228 color = op >> 1; 01229 isItalic = true; 01230 break; 01231 case 0x20: 01232 // color unchanged 01233 // italics unchanged 01234 break; 01235 default: 01236 color = (op & 0xf) >> 1; 01237 isItalic = false; 01238 break; 01239 } 01240 text = text.mid(1); 01241 } 01242 01243 // Copy the string into the result, up to the next control 01244 // character. 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 // Print the space character before handling the next control 01255 // character, otherwise the space character will be lost due 01256 // to the text.trimmed() operation in the MythUISimpleText 01257 // constructor, combined with the left-justification of 01258 // captions. 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 // Draw the window background after calculating bounding 01349 // rectangle in Layout() 01350 if (win.GetFillAlpha()) // TODO border? 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 // For the purpose of pixel size calculation, use a normalized 01456 // size based on 17 non-teletext rows rather than 25. The 01457 // shrinking is done in GetFont so that the theme can supply a 01458 // uniform normalized pixelsize value. 01459 int pixelSize = m_safeArea.height() / (/*yscale*/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 // Disable centering for zoom factor >= 100% 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 // position as if we use a fixed size font 01485 // - font size already has zoom factor applied 01486 01487 int x; 01488 if (xmid) 01489 // center horizontally 01490 x = xmid + (orig_x - xscale / 2) * fontwidth; 01491 else 01492 // fallback 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 // top half -- anchor up 01499 y = (orig_y * m_safeArea.height() * textFontZoom / (yscale * 100)); 01500 else 01501 // bottom half -- anchor down 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 // Does a simplistic parsing of HTML tags from the strings. 01565 // Nesting is not implemented. Stray whitespace may cause 01566 // legitimate tags to be ignored. All other HTML tags are 01567 // stripped and ignored. 01568 // 01569 // <i> - enable italics 01570 // </i> - disable italics 01571 // <b> - enable boldface 01572 // </b> - disable boldface 01573 // <u> - enable underline 01574 // </u> - disable underline 01575 // <font color="#xxyyzz"> - change font color 01576 // </font> - reset font color to white 01577 01578 int pixelSize = m_safeArea.height() / 20; 01579 if (parent) 01580 parent->SetFontSize(pixelSize); 01581 m_xAnchorPoint = 1; // center 01582 m_yAnchorPoint = 2; // bottom 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) // don't add a zero-length string 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); // -2 to ignore trailing space 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 // Move entire chunks to the next line as necessary. Leave at 01718 // least one chunk on the current line. 01719 while (width > maxWidth && m_lines[i].chunks.size() > 1) 01720 { 01721 width -= m_lines[i].chunks.back().CalcSize().width(); 01722 // Make sure there's a next line to wrap into. 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 // Split the last chunk until width is small enough or until 01732 // no more splits are possible. 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 // Make sure there's a next line to split into. 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 // Adjusts the Y coordinates to avoid overlap, which could happen as a 01752 // result of a large text zoom factor. Then, if the total height 01753 // exceeds the safe area, compresses each piece of vertical blank 01754 // space proportionally to make it fit. 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 // Calculate totalHeight and totalSpace 01765 for (i = 0; i < m_lines.size(); i++) 01766 { 01767 m_lines[i].y_indent = max(m_lines[i].y_indent, prevY); // avoid overlap 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 // Recalculate Y coordinates, applying the shrink factor to space 01782 // between each line. 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 // Shift Y coordinates back up into the safe area. 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 // Resolves any TBD x_indent and y_indent values in FormattedTextLine 01801 // objects. Calculates m_bounds. Prunes most leading and all 01802 // trailing whitespace from each line so that displaying with a black 01803 // background doesn't look clumsy. 01804 void FormattedTextSubtitle::Layout(void) 01805 { 01806 // Calculate dimensions of bounding rectangle 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 // Adjust the anchor point according to actual width and height 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 // Shift the anchor point back into the safe area if necessary/possible. 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 // Fill in missing coordinates 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 // Prune leading all-whitespace chunks. 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 // Prune trailing all-whitespace chunks. 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 // Trim trailing whitespace from last chunk. (Trimming 01858 // leading whitespace from all chunks is handled in the Draw() 01859 // routine.) 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 // Returns true if anything new was drawn, false if not. The caller 01872 // should call SubtitleScreen::OptimiseDisplayedArea() if true is 01873 // returned. 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 // If the chunk starts with whitespace, the leading 01898 // whitespace ultimately gets lost due to the 01899 // text.trimmed() operation in the MythUISimpleText 01900 // constructor. To compensate, we manually indent the 01901 // chunk accordingly. 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 // Account for extra padding before the first chunk. 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 // Don't draw a background behind leading spaces. 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 // Shift to the right to account for leading spaces that 01935 // are removed by the MythUISimpleText constructor. Also 01936 // add in padding at the end to avoid clipping. 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 // Move each chunk of text to the front so that it isn't clipped 01963 // by the preceding chunk's background. 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 // The QFontMetrics class does not account for the MythFontProperties 02017 // shadow and offset properties. This method calculates the 02018 // additional padding to the right and below that is needed for proper 02019 // bounding box computation. 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 // Shadow and outline overlap, so don't just add them. 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 // Padding calculation is different depending on whether the padding 02060 // is on the left side or the right side of the text. Padding on the 02061 // right needs to add the shadow and offset padding. 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: //MSGL_FATAL 02095 verbose_level = LOG_EMERG; 02096 break; 02097 case 1: //MSGL_ERR 02098 verbose_level = LOG_ERR; 02099 break; 02100 case 2: //MSGL_WARN 02101 verbose_level = LOG_WARNING; 02102 break; 02103 case 4: //MSGL_INFO 02104 verbose_level = LOG_INFO; 02105 break; 02106 case 6: //MSGL_V 02107 case 7: //MSGL_DBG2 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 // check for truncated messages and fix them 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 // TODO these need checking and/or reinitialising after a stream change 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 // TODO this probably won't work properly for anamorphic content and XVideo 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
1.7.6.1