From: smlee Date: Thu, 17 Mar 2016 10:33:22 +0000 (+0900) Subject: Support customized subtitles. X-Git-Url: http://code.vuplus.com/gitweb/?p=vuplus_dvbapp;a=commitdiff_plain;h=059fdda03072b1be0d67a525f20284df68a84a3c Support customized subtitles. - font color, size, opacity, edge style, - background color, opacity, - subtitle position, align, etc. - add quick menu for subtitle --- diff --git a/data/menu.xml b/data/menu.xml index 03e582c..5c8cbb2 100755 --- a/data/menu.xml +++ b/data/menu.xml @@ -50,6 +50,7 @@ + diff --git a/data/setup.xml b/data/setup.xml index 442fe13..6100565 100755 --- a/data/setup.xml +++ b/data/setup.xml @@ -62,6 +62,27 @@ config.recording.ascii_filenames config.recording.filename_composition + + config.subtitles.subtitle_fontcolor + config.subtitles.subtitle_fontsize + config.subtitles.subtitle_bgcolor + config.subtitles.subtitle_bgopacity + config.subtitles.subtitle_edgestyle + config.subtitles.subtitle_edgestyle_level + config.subtitles.subtitle_opacity + config.subtitles.subtitle_original_position + config.subtitles.subtitle_alignment + config.subtitles.subtitle_position + config.subtitles.dvb_subtitles_centered + config.subtitles.subtitle_noPTSrecordingdelay + config.subtitles.subtitle_bad_timing_delay + config.subtitles.pango_subtitle_fontswitch + config.subtitles.colourise_dialogs + config.subtitles.subtitle_rewrap + config.subtitles.pango_subtitles_delay + config.subtitles.pango_subtitles_fps + config.subtitles.pango_autoturnon + config.usage.hdd_standby diff --git a/lib/base/nconfig.cpp b/lib/base/nconfig.cpp index 106558a..8207b71 100644 --- a/lib/base/nconfig.cpp +++ b/lib/base/nconfig.cpp @@ -33,3 +33,25 @@ RESULT ePythonConfigQuery::getConfigValue(const char *key, std::string &value) } return -1; } + +int ePythonConfigQuery::getConfigIntValue(const char *key, int defaultvalue) +{ + int result = defaultvalue; + std::string value; + + if (!getConfigValue(key, value)) + result = (value != "") ? atoi(value.c_str()) : result; + + return result; +} + +bool ePythonConfigQuery::getConfigBoolValue(const char *key, bool defaultvalue) +{ + bool result = defaultvalue; + std::string value; + + if (!getConfigValue(key, value)) + result = (value == "True" || value == "true"); + + return result; +} diff --git a/lib/base/nconfig.h b/lib/base/nconfig.h index 3667439..4674af6 100644 --- a/lib/base/nconfig.h +++ b/lib/base/nconfig.h @@ -12,6 +12,8 @@ public: static void setQueryFunc(SWIG_PYOBJECT(ePyObject) func); #ifndef SWIG static RESULT getConfigValue(const char *key, std::string &value); + static int getConfigIntValue(const char *key, int defaultvalue = 0); + static bool getConfigBoolValue(const char *key, bool defaultvalue = false); #endif }; diff --git a/lib/dvb/subtitle.cpp b/lib/dvb/subtitle.cpp index 06bb266..78b1874 100644 --- a/lib/dvb/subtitle.cpp +++ b/lib/dvb/subtitle.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include void bitstream_init(bitstream *bit, const void *buffer, int size) @@ -56,7 +57,8 @@ static int extract_pts(pts_t &pts, __u8 *pkt) void eDVBSubtitleParser::subtitle_process_line(subtitle_region *region, subtitle_region_object *object, int line, __u8 *data, int len) { - int x = object->object_horizontal_position; + bool subcentered = ePythonConfigQuery::getConfigBoolValue("config.subtitles.dvb_subtitles_centered"); + int x = subcentered ? (region->width - len) /2 : object->object_horizontal_position; int y = object->object_vertical_position + line; if (x + len > region->width) { @@ -72,6 +74,14 @@ void eDVBSubtitleParser::subtitle_process_line(subtitle_region *region, subtitle } // eDebug("inserting %d bytes (into region %d)", len, region->region_id); // eDebug("put data to buffer %p", &(*region->buffer)); + if (subcentered && region->region_id && line < 3) + { + for (int i = 0; i < len; i++) + if(data[i] <= 8) + { + data[i] = 0; + } + } memcpy((__u8*)region->buffer->surface->data + region->buffer->surface->stride * y + x, data, len); } @@ -1173,6 +1183,8 @@ void eDVBSubtitleParser::subtitle_redraw(int page_id) break; } + int bgopacity = ePythonConfigQuery::getConfigIntValue("config.subtitles.subtitle_bgopacity"); + int fontcolor = ePythonConfigQuery::getConfigIntValue("config.subtitles.subtitle_fontcolor"); for (int i=0; igetGlyphBitmap(&font, glyph_index, sbit); } +FT_Error Font::getGlyphImage(FT_ULong glyph_index, FT_Glyph *glyph, FT_Glyph *borderglyph, int bordersize) +{ + return renderer->getGlyphImage(&font, glyph_index, glyph, borderglyph, bordersize); +} + Font::~Font() { } DEFINE_REF(eTextPara); -int eTextPara::appendGlyph(Font *current_font, FT_Face current_face, FT_UInt glyphIndex, int flags, int rflags) +int eTextPara::appendGlyph(Font *current_font, FT_Face current_face, FT_UInt glyphIndex, int flags, int rflags, int border, bool activate_newcolor, unsigned long newcolor) { - FTC_SBit glyph; - if (current_font->getGlyphBitmap(glyphIndex, &glyph)) - return 1; + int xadvance, left, top, height; + pGlyph ng; + int xborder = 0; + + if (border) + { + /* TODO: scale border radius with current_font scaling */ + if (current_font->getGlyphImage(glyphIndex, &ng.image, &ng.borderimage, 64 * border)) + return 1; + if (ng.image && ng.image->format != FT_GLYPH_FORMAT_BITMAP) + { + FT_Glyph_To_Bitmap(&ng.image, FT_RENDER_MODE_NORMAL, NULL, 1); + if (ng.image->format != FT_GLYPH_FORMAT_BITMAP) return 1; + } + if (ng.borderimage && ng.borderimage->format != FT_GLYPH_FORMAT_BITMAP) + { + FT_Glyph_To_Bitmap(&ng.borderimage, FT_RENDER_MODE_NORMAL, NULL, 1); + if (ng.borderimage->format != FT_GLYPH_FORMAT_BITMAP) return 1; + } + FT_BitmapGlyph glyph = NULL; + if (ng.borderimage) + { + xadvance = ng.borderimage->advance.x; + if (!previous) + { + /* Move the first character, to make sure the border does not get cut off by the boundingbox (xborder is in pixel units, so just divide the width difference by two) */ + xborder = (((FT_BitmapGlyph)ng.borderimage)->bitmap.width - ((FT_BitmapGlyph)ng.image)->bitmap.width) / 2; + } + glyph = (FT_BitmapGlyph)ng.borderimage; + } + else if (ng.image) + { + xadvance = ng.image->advance.x; + glyph = (FT_BitmapGlyph)ng.image; + } + else + { + return 1; + } + xadvance >>= 16; + + left = glyph->left; + top = glyph->top; + height = glyph->bitmap.rows; + } + else + { + FTC_SBit glyph; + if (current_font->getGlyphBitmap(glyphIndex, &glyph)) + return 1; + + xadvance = glyph->xadvance; + left = glyph->left; + top = glyph->top; + height = glyph->height; + } int nx=cursor.x(); - nx+=glyph->xadvance; + nx+=xadvance; if ( (rflags&RS_WRAP) && @@ -338,8 +427,7 @@ int eTextPara::appendGlyph(Font *current_font, FT_Face current_face, FT_UInt gly } } - int xadvance=glyph->xadvance, kern=0; - + int kern=0; if (previous && use_kerning) { FT_Vector delta; @@ -347,21 +435,26 @@ int eTextPara::appendGlyph(Font *current_font, FT_Face current_face, FT_UInt gly kern=delta.x>>6; } - pGlyph ng; - ng.bbox.setLeft( ((flags&GS_ISFIRST)|(cursor.x()-1))+glyph->left ); - ng.bbox.setTop( cursor.y() - glyph->top ); - ng.bbox.setWidth( glyph->width ); - ng.bbox.setHeight( glyph->height ); + ng.bbox.setLeft( ((flags&GS_ISFIRST)|(cursor.x()-1))+left ); + ng.bbox.setTop( cursor.y() - top ); + ng.bbox.setHeight( height ); - xadvance += kern; + xadvance += kern + xborder; ng.bbox.setWidth(xadvance); - ng.x = cursor.x()+kern; + ng.x = cursor.x()+kern+xborder; ng.y = cursor.y(); ng.w = xadvance; ng.font = current_font; ng.glyph_index = glyphIndex; ng.flags = flags; + + if (activate_newcolor) + { + ng.flags |= GS_COLORCHANGE; + ng.newcolor = newcolor; + } + glyphs.push_back(ng); ++charCount; @@ -483,7 +576,7 @@ void eTextPara::setFont(Font *fnt, Font *replacement) void shape (std::vector &string, const std::vector &text); -int eTextPara::renderString(const char *string, int rflags) +int eTextPara::renderString(const char *string, int rflags, int border) { singleLock s(ftlock); @@ -572,7 +665,9 @@ int eTextPara::renderString(const char *string, int rflags) uc_visual.assign(target, target+size); glyphs.reserve(size); - + + unsigned long newcolor = 0; + bool activate_newcolor = false; int nextflags = 0; for (std::vector::const_iterator i(uc_visual.begin()); @@ -601,6 +696,24 @@ int eTextPara::renderString(const char *string, int rflags) case 'r': i++; goto nprint; + case 'c': + { + char color[8]; + int codeidx; + for (codeidx = 0; codeidx < 8; codeidx++) + { + if ((i + 2 + codeidx) == uc_visual.end()) break; + color[codeidx] = (char)((*(i + 2 + codeidx)) & 0xff); + } + if (codeidx == 8) + { + newcolor = gRGB(color).argb(); + activate_newcolor = true; + isprintable = 0; + i += 1 + codeidx; + } + break; + } default: ; } @@ -656,9 +769,11 @@ nprint: isprintable=0; if (!index) eDebug("unicode U+%4lx not present", chr); else - appendGlyph(replacement_font, replacement_face, index, flags, rflags); + appendGlyph(replacement_font, replacement_face, index, flags, rflags, border, activate_newcolor, newcolor); } else - appendGlyph(current_font, current_face, index, flags, rflags); + appendGlyph(current_font, current_face, index, flags, rflags, border, activate_newcolor, newcolor); + + activate_newcolor = false; } } bboxValid=false; @@ -679,8 +794,10 @@ nprint: isprintable=0; return 0; } -void eTextPara::blit(gDC &dc, const ePoint &offset, const gRGB &background, const gRGB &foreground) +void eTextPara::blit(gDC &dc, const ePoint &offset, const gRGB &background, const gRGB &foreground, bool border) { + if (glyphs.empty()) return; + singleLock s(ftlock); if (!current_font) @@ -700,8 +817,9 @@ void eTextPara::blit(gDC &dc, const ePoint &offset, const gRGB &background, cons ePtr target; dc.getPixmap(target); gSurface *surface = target->surface; + gRGB currentforeground = foreground; - register int opcode; + register int opcode = -1; gColor *lookup8, lookup8_invert[16]; gColor *lookup8_normal=0; @@ -709,115 +827,152 @@ void eTextPara::blit(gDC &dc, const ePoint &offset, const gRGB &background, cons __u16 lookup16_normal[16], lookup16_invert[16], *lookup16; __u32 lookup32_normal[16], lookup32_invert[16], *lookup32; - if (surface->bpp == 8) - { - if (surface->clut.data) - { - lookup8_normal=getColor(surface->clut, background, foreground).lookup; - - int i; - for (i=0; i<16; ++i) - lookup8_invert[i] = lookup8_normal[i^0xF]; - - opcode=0; - } else - opcode=1; - } else if (surface->bpp == 16) - { - opcode=2; - for (int i=0; i<16; ++i) - { -#define BLEND(y, x, a) (y + (((x-y) * a)>>8)) - unsigned char da = background.a, dr = background.r, dg = background.g, db = background.b; - int sa = i * 16; - if (sa < 256) - { - dr = BLEND(background.r, foreground.r, sa) & 0xFF; - dg = BLEND(background.g, foreground.g, sa) & 0xFF; - db = BLEND(background.b, foreground.b, sa) & 0xFF; - } -#undef BLEND -#if BYTE_ORDER == LITTLE_ENDIAN - lookup16_normal[i] = bswap_16(((db >> 3) << 11) | ((dg >> 2) << 5) | (dr >> 3)); -#else - lookup16_normal[i] = ((db >> 3) << 11) | ((dg >> 2) << 5) | (dr >> 3); -#endif - da ^= 0xFF; - } - for (int i=0; i<16; ++i) - lookup16_invert[i]=lookup16_normal[i^0xF]; - } else if (surface->bpp == 32) - { - opcode=3; - for (int i=0; i<16; ++i) - { -#define BLEND(y, x, a) (y + (((x-y) * a)>>8)) - - unsigned char da = background.a, dr = background.r, dg = background.g, db = background.b; - int sa = i * 16; - if (sa < 256) - { - da = BLEND(background.a, foreground.a, sa) & 0xFF; - dr = BLEND(background.r, foreground.r, sa) & 0xFF; - dg = BLEND(background.g, foreground.g, sa) & 0xFF; - db = BLEND(background.b, foreground.b, sa) & 0xFF; - } -#undef BLEND - da ^= 0xFF; - lookup32_normal[i]=db | (dg << 8) | (dr << 16) | (da << 24);; - } - for (int i=0; i<16; ++i) - lookup32_invert[i]=lookup32_normal[i^0xF]; - } else - { - eWarning("can't render to %dbpp", surface->bpp); - return; - } - gRegion area(eRect(0, 0, surface->x, surface->y)); gRegion clip = dc.getClip() & area; int buffer_stride=surface->stride; - for (unsigned int c = 0; c < clip.rects.size(); ++c) + bool setcolor = true; + std::list::reverse_iterator line_offs_it(lineOffsets.rbegin()); + std::list::iterator line_chars_it(lineChars.begin()); + int line_offs=0; + int line_chars=0; + for (glyphString::iterator i(glyphs.begin()); i != glyphs.end(); ++i, --line_chars) { - std::list::reverse_iterator line_offs_it(lineOffsets.rbegin()); - std::list::iterator line_chars_it(lineChars.begin()); - int line_offs=0; - int line_chars=0; - for (glyphString::iterator i(glyphs.begin()); i != glyphs.end(); ++i, --line_chars) + while(!line_chars) + { + line_offs = *(line_offs_it++); + line_chars = *(line_chars_it++); + } + if (i->flags & GS_COLORCHANGE) { - while(!line_chars) + /* don't do colorchanges in borders */ + if (!border) { - line_offs = *(line_offs_it++); - line_chars = *(line_chars_it++); + currentforeground = i->newcolor; + setcolor = true; } + } + if (setcolor) + { + setcolor = false; + if (surface->bpp == 8) + { + if (surface->clut.data) + { + lookup8_normal=getColor(surface->clut, background, currentforeground).lookup; - if (i->flags & GS_SOFTHYPHEN) - continue; + int i; + for (i=0; i<16; ++i) + lookup8_invert[i] = lookup8_normal[i^0xF]; - if (!(i->flags & GS_INVERT)) + opcode=0; + } else + opcode=1; + } else if (surface->bpp == 16) { - lookup8 = lookup8_normal; - lookup16 = lookup16_normal; - lookup32 = lookup32_normal; + opcode=2; + for (int i = 0; i != 16; ++i) + { +#define BLEND(y, x, a) (y + (((x-y) * a)>>8)) + unsigned char da = background.a, dr = background.r, dg = background.g, db = background.b; + int sa = i * 16; + if (sa < 256) + { + dr = BLEND(background.r, foreground.r, sa) & 0xFF; + dg = BLEND(background.g, foreground.g, sa) & 0xFF; + db = BLEND(background.b, foreground.b, sa) & 0xFF; + } +#undef BLEND +#if BYTE_ORDER == LITTLE_ENDIAN + lookup16_normal[i] = bswap_16(((db >> 3) << 11) | ((dg >> 2) << 5) | (dr >> 3)); +#else + lookup16_normal[i] = ((db >> 3) << 11) | ((dg >> 2) << 5) | (dr >> 3); +#endif + da ^= 0xFF; + } + for (int i=0; i<16; ++i) + lookup16_invert[i]=lookup16_normal[i^0xF]; + } else if (surface->bpp == 32) + { + opcode=3; + for (int i=0; i<16; ++i) + { +#define BLEND(y, x, a) (y + (((x-y) * a)>>8)) + + unsigned char da = background.a, dr = background.r, dg = background.g, db = background.b; + int sa = i * 16; + if (sa < 256) + { + da = BLEND(background.a, currentforeground.a, sa) & 0xFF; + dr = BLEND(background.r, currentforeground.r, sa) & 0xFF; + dg = BLEND(background.g, currentforeground.g, sa) & 0xFF; + db = BLEND(background.b, currentforeground.b, sa) & 0xFF; + } +#undef BLEND + da ^= 0xFF; + lookup32_normal[i]=db | (dg << 8) | (dr << 16) | (da << 24);; + } + for (int i=0; i<16; ++i) + lookup32_invert[i]=lookup32_normal[i^0xF]; } else { - lookup8 = lookup8_invert; - lookup16 = lookup16_invert; - lookup32 = lookup32_invert; + eWarning("can't render to %dbpp", surface->bpp); + return; } + } + if (i->flags & GS_SOFTHYPHEN) + continue; + + if (!(i->flags & GS_INVERT)) + { + lookup8 = lookup8_normal; + lookup16 = lookup16_normal; + lookup32 = lookup32_normal; + } else + { + lookup8 = lookup8_invert; + lookup16 = lookup16_invert; + lookup32 = lookup32_invert; + } + int rxbase, rybase; + __u8 *dbase; + __u8 *sbase; + int sxbase; + int sybase; + int pitch; + if (i->image) + { + FT_BitmapGlyph glyph = border ? (FT_BitmapGlyph)i->borderimage : (FT_BitmapGlyph)i->image; + if (!glyph->bitmap.buffer) continue; + rxbase = i->x + glyph->left + offset.x(); + rybase = (doTopBottomReordering ? line_offs : i->y) - glyph->top + offset.y(); + sbase = glyph->bitmap.buffer; + sxbase = glyph->bitmap.width; + sybase = glyph->bitmap.rows; + pitch = glyph->bitmap.pitch; + } + else + { static FTC_SBit glyph_bitmap; if (fontRenderClass::instance->getGlyphBitmap(&i->font->font, i->glyph_index, &glyph_bitmap)) continue; - int rx=i->x+glyph_bitmap->left + offset.x(); - int ry=(doTopBottomReordering ? line_offs : i->y) - glyph_bitmap->top + offset.y(); - - __u8 *d=(__u8*)(surface->data)+buffer_stride*ry+rx*surface->bypp; - __u8 *s=glyph_bitmap->buffer; - register int sx=glyph_bitmap->width; - int sy=glyph_bitmap->height; + rxbase=i->x+glyph_bitmap->left + offset.x(); + rybase=(doTopBottomReordering ? line_offs : i->y) - glyph_bitmap->top + offset.y(); + sbase=glyph_bitmap->buffer; + sxbase=glyph_bitmap->width; + sybase=glyph_bitmap->height; + pitch = glyph_bitmap->pitch; + } + dbase = (__u8*)(surface->data)+buffer_stride*rybase+rxbase*surface->bypp; + for (unsigned int c = 0; c < clip.rects.size(); ++c) + { + int rx = rxbase, ry = rybase; + __u8 *d = dbase; + __u8 *s = sbase; + register int sx = sxbase; + int sy = sybase; if ((sy+ry) >= clip.rects[c].bottom()) sy=clip.rects[c].bottom()-ry; if ((sx+rx) >= clip.rects[c].right()) @@ -833,78 +988,90 @@ void eTextPara::blit(gDC &dc, const ePoint &offset, const gRGB &background, cons if (ry < clip.rects[c].top()) { int diff=clip.rects[c].top()-ry; - s+=diff*glyph_bitmap->pitch; + s+=diff*pitch; sy-=diff; ry+=diff; d+=diff*buffer_stride; } - if (sx>0) + if ((sx>0) && (sy>0)) { - switch(opcode) { + int extra_source_stride = pitch - sx; + switch (opcode) + { case 0: // 4bit lookup to 8bit - for (int ay=0; ay>4; if(b) - *td++=lookup8[b]; - else - td++; + *td=lookup8[b]; + ++td; } - s+=glyph_bitmap->pitch-sx; - d+=buffer_stride; + s += extra_source_stride; + td += extra_buffer_stride; + } } break; case 1: // 8bit direct - for (int ay=0; aypitch-sx; - d+=buffer_stride; + s += extra_source_stride; + td += extra_buffer_stride; + } } break; case 2: // 16bit - for (int ay=0; ay>4; - if(b) - *td++=lookup16[b]; - else - td++; - } - s+=glyph_bitmap->pitch-sx; - d+=buffer_stride; + int extra_buffer_stride = (buffer_stride >> 1) - sx; + register __u16 *td = (__u16*)d; + for (int ay = 0; ay != sy; ay++) + { + register int ax; + for (ax = 0; ax != sx; ax++) + { + register int b = (*s++) >> 4; + if (b) + *td = lookup16[b]; + ++td; + } + s += extra_source_stride; + td += extra_buffer_stride; + } } break; case 3: // 32bit - for (int ay=0; ay> 2) - sx; + register __u32 *td=(__u32*)d; + for (int ay = 0; ay < sy; ay++) + { register int ax; for (ax=0; ax>4; if(b) - *td++=lookup32[b]; - else - td++; + *td=lookup32[b]; + ++td; } - s+=glyph_bitmap->pitch-sx; - d+=buffer_stride; + s += extra_source_stride; + td += extra_buffer_stride; } + } + break; default: break; } @@ -1002,6 +1169,11 @@ void eTextPara::clear() current_font = 0; replacement_font = 0; + for (unsigned int i = 0; i < glyphs.size(); i++) + { + if (glyphs[i].image) FT_Done_Glyph(glyphs[i].image); + if (glyphs[i].borderimage) FT_Done_Glyph(glyphs[i].borderimage); + } glyphs.clear(); } diff --git a/lib/gdi/font.h b/lib/gdi/font.h index 6b82183..a00a248 100644 --- a/lib/gdi/font.h +++ b/lib/gdi/font.h @@ -8,6 +8,7 @@ #include FT_CACHE_H #include FT_CACHE_IMAGE_H #include FT_CACHE_SMALL_BITMAPS_H +#include FT_STROKER_H typedef FTC_ImageCache FTC_Image_Cache; typedef FTC_ImageTypeRec FTC_Image_Desc; typedef FTC_SBitCache FTC_SBit_Cache; @@ -48,9 +49,12 @@ class fontRenderClass FTC_Manager cacheManager; /* the cache manager */ FTC_Image_Cache imageCache; /* the glyph image cache */ FTC_SBit_Cache sbitsCache; /* the glyph small bitmaps cache */ + FT_Stroker stroker; + int strokerRadius; FTC_FaceID getFaceID(const std::string &face); FT_Error getGlyphBitmap(FTC_Image_Desc *font, FT_ULong glyph_index, FTC_SBit *sbit); + FT_Error getGlyphImage(FTC_Image_Desc *font, FT_ULong glyph_index, FT_Glyph *glyph, FT_Glyph *borderglyph, int bordersize); static fontRenderClass *instance; #else fontRenderClass(); @@ -81,15 +85,23 @@ public: #define GS_INVERT 8 #define GS_SOFTHYPHEN 16 #define GS_HYPHEN 32 +#define GS_COLORCHANGE 64 #define GS_CANBREAK (GS_ISSPACE|GS_SOFTHYPHEN|GS_HYPHEN) struct pGlyph { int x, y, w; + unsigned long newcolor; ePtr font; FT_ULong glyph_index; int flags; eRect bbox; + FT_Glyph image, borderimage; + pGlyph() + { + image = NULL; + borderimage = NULL; + } }; typedef std::vector glyphString; @@ -117,7 +129,7 @@ class eTextPara: public iObject int charCount; bool doTopBottomReordering; - int appendGlyph(Font *current_font, FT_Face current_face, FT_UInt glyphIndex, int flags, int rflags); + int appendGlyph(Font *current_font, FT_Face current_face, FT_UInt glyphIndex, int flags, int rflags, int border, bool activate_newcolor, unsigned long newcolor); void newLine(int flags); void setFont(Font *font, Font *replacement_font); eRect boundBox; @@ -137,11 +149,11 @@ public: static void forceReplacementGlyph(int unicode) { forced_replaces.insert(unicode); } void setFont(const gFont *font); - int renderString(const char *string, int flags=0); + int renderString(const char *string, int flags=0, int border=0); - void blit(gDC &dc, const ePoint &offset, const gRGB &background, const gRGB &foreground); + void blit(gDC &dc, const ePoint &offset, const gRGB &background, const gRGB &foreground, bool border = false); enum { @@ -193,6 +205,7 @@ public: FTC_Image_Desc font; fontRenderClass *renderer; FT_Error getGlyphBitmap(FT_ULong glyph_index, FTC_SBit *sbit); + FT_Error getGlyphImage(FT_ULong glyph_index, FT_Glyph *glyph, FT_Glyph *borderglyph, int bordersize); FT_Face face; FT_Size size; diff --git a/lib/gdi/gpixmap.h b/lib/gdi/gpixmap.h index 87fb502..202cd01 100644 --- a/lib/gdi/gpixmap.h +++ b/lib/gdi/gpixmap.h @@ -18,6 +18,23 @@ struct gRGB gRGB(unsigned long val): b(val&0xFF), g((val>>8)&0xFF), r((val>>16)&0xFF), a((val>>24)&0xFF) // ARGB { } + gRGB(const char *colorstring) + { + unsigned long val = 0; + if (colorstring) + { + for (int i = 0; i < 8; i++) + { + if (i) val <<= 4; + if (!colorstring[i]) break; + val |= (colorstring[i]) & 0x0f; + } + } + b = val&0xFF; + g = (val>>8)&0xFF; + r = (val>>16)&0xFF; + a = (val>>24)&0xFF; + } gRGB(): b(0), g(0), r(0), a(0) { } @@ -56,6 +73,22 @@ struct gRGB { return (b == c.b) && (g == c.g) && (r == c.r) && (a == c.a); } + bool operator != (const gRGB &c) const + { + return (b != c.b) || (g != c.g) || (r != c.r) || (a != c.a); + } + operator const std::string () const + { + unsigned long val = (a<<24)|(r<<16)|(g<<8)|b; + std::string escapecolor = "\\c"; + escapecolor.resize(10); + for (int i = 9; i >= 2; i--) + { + escapecolor[i] = 0x40 | (val & 0xf); + val >>= 4; + } + return escapecolor; + } }; #ifndef SWIG diff --git a/lib/gdi/grc.cpp b/lib/gdi/grc.cpp index ce33171..ffb4c30 100644 --- a/lib/gdi/grc.cpp +++ b/lib/gdi/grc.cpp @@ -331,7 +331,7 @@ void gPainter::setFont(gFont *font) m_rc->submit(o); } -void gPainter::renderText(const eRect &pos, const std::string &string, int flags) +void gPainter::renderText(const eRect &pos, const std::string &string, int flags, gRGB bordercolor, int border) { if ( m_dc->islocked() ) return; @@ -342,6 +342,8 @@ void gPainter::renderText(const eRect &pos, const std::string &string, int flags o.parm.renderText->area = pos; o.parm.renderText->text = string.empty()?0:strdup(string.c_str()); o.parm.renderText->flags = flags; + o.parm.renderText->border = border; + o.parm.renderText->bordercolor = bordercolor; m_rc->submit(o); } @@ -699,7 +701,7 @@ void gDC::exec(const gOpcode *o) int flags = o->parm.renderText->flags; ASSERT(m_current_font); para->setFont(m_current_font); - para->renderString(o->parm.renderText->text, (flags & gPainter::RT_WRAP) ? RS_WRAP : 0); + para->renderString(o->parm.renderText->text, (flags & gPainter::RT_WRAP) ? RS_WRAP : 0, o->parm.renderText->border); if (o->parm.renderText->text) free(o->parm.renderText->text); if (flags & gPainter::RT_HALIGN_RIGHT) @@ -718,8 +720,14 @@ void gDC::exec(const gOpcode *o) int correction = vcentered_top - bbox.top(); offset += ePoint(0, correction); } - - para->blit(*this, offset, m_background_color_rgb, m_foreground_color_rgb); + + if (o->parm.renderText->border) + { + para->blit(*this, offset, m_background_color_rgb, o->parm.renderText->bordercolor, true); + para->blit(*this, offset, o->parm.renderText->bordercolor, m_foreground_color_rgb); + } + else + para->blit(*this, offset, m_background_color_rgb, m_foreground_color_rgb); delete o->parm.renderText; break; } diff --git a/lib/gdi/grc.h b/lib/gdi/grc.h index 16f7db7..bcd2cf4 100644 --- a/lib/gdi/grc.h +++ b/lib/gdi/grc.h @@ -89,6 +89,8 @@ struct gOpcode eRect area; char *text; int flags; + int border; + gRGB bordercolor; } *renderText; struct prenderPara @@ -243,7 +245,7 @@ public: RT_WRAP = 32 }; - void renderText(const eRect &position, const std::string &string, int flags=0); + void renderText(const eRect &position, const std::string &string, int flags=0, gRGB bordercolor=gRGB(), int border=0); void renderPara(eTextPara *para, ePoint offset=ePoint(0, 0)); diff --git a/lib/gui/esubtitle.cpp b/lib/gui/esubtitle.cpp index 594ed8a..00bf12d 100644 --- a/lib/gui/esubtitle.cpp +++ b/lib/gui/esubtitle.cpp @@ -1,6 +1,9 @@ #include +#include #include +#include #include +#include /* ok, here's much room for improvements. @@ -25,12 +28,14 @@ eSubtitleWidget::eSubtitleWidget(eWidget *parent) #define startX 50 void eSubtitleWidget::setPage(const eDVBTeletextSubtitlePage &p) { + eDVBTeletextSubtitlePage newpage = p; m_page = p; + m_page.clear(); m_page_ok = 1; invalidate(m_visible_region); // invalidate old visible regions m_visible_region.rects.clear(); - int elements = m_page.m_elements.size(); + unsigned int elements = newpage.m_elements.size(); if (elements) { int startY = elements > 1 @@ -39,11 +44,78 @@ void eSubtitleWidget::setPage(const eDVBTeletextSubtitlePage &p) int width = size().width() - startX * 2; int height = size().height() - startY; int size_per_element = height / (elements ? elements : 1); + bool original_position = ePythonConfigQuery::getConfigBoolValue("config.subtitles.subtitle_original_position"); + gRGB color; + bool original_colors = false; + switch (ePythonConfigQuery::getConfigIntValue("config.subtitles.subtitle_fontcolor", 0)) + { + default: + case 0: /* use original teletext colors */ + color = newpage.m_elements[0].m_color; + original_colors = true; + break; + case FONTCOLOR_WHITE: + color = gRGB(255, 255, 255); + break; + case FONTCOLOR_YELLOW: + color = gRGB(255, 255, 0); + break; + case FONTCOLOR_GREEN: + color = gRGB(0, 255, 0); + break; + case FONTCOLOR_CYAN: + color = gRGB(0, 255, 255); + break; + case FONTCOLOR_BLUE: + color = gRGB(0, 0, 255); + break; + case FONTCOLOR_MAGNETA: + color = gRGB(255, 0, 255); + break; + case FONTCOLOR_RED: + color = gRGB(255, 0, 0); + break; + case FONTCOLOR_BLACK: + color = gRGB(0, 0, 0); + break; + } + color.a = ePythonConfigQuery::getConfigIntValue("config.subtitles.subtitle_opacity"); + + int line = newpage.m_elements[0].m_source_line; + int currentelement = 0; + m_page.m_elements.push_back(eDVBTeletextSubtitlePageElement(color, "", line)); for (int i=0; isize().height()/720; + else if (lowerborder == 50) + lowerborder -= 50 * getDesktop(0)->size().height()/720; + area.setTop(size_per_element * i + startY - lowerborder); + } + else + area.setTop(size_per_element * i + startY); area.setWidth(width); area.setHeight(size_per_element); m_visible_region.rects.push_back(area); @@ -59,8 +131,20 @@ void eSubtitleWidget::setPage(const eDVBSubtitlePage &p) m_dvb_page = p; invalidate(m_visible_region); // invalidate old visible regions m_visible_region.rects.clear(); + int line = 0; + bool original_position = ePythonConfigQuery::getConfigBoolValue("config.subtitles.subtitle_original_position"); for (std::list::iterator it(m_dvb_page.m_regions.begin()); it != m_dvb_page.m_regions.end(); ++it) { + if (!original_position) + { + int lines = m_dvb_page.m_regions.size(); + int lowerborder = ePythonConfigQuery::getConfigIntValue("config.subtitles.subtitle_position", -1); + if (lowerborder >= 0) + { + it->m_position = ePoint(it->m_position.x(), p.m_display_size.height() - (lines - line) * it->m_pixmap->size().height() - lowerborder); + } + line++; + } eDebug("add %d %d %d %d", it->m_position.x(), it->m_position.y(), it->m_pixmap->size().width(), it->m_pixmap->size().height()); eDebug("disp width %d, disp height %d", p.m_display_size.width(), p.m_display_size.height()); eRect r = eRect(it->m_position, it->m_pixmap->size()); @@ -79,7 +163,55 @@ void eSubtitleWidget::setPage(const ePangoSubtitlePage &p) invalidate(m_visible_region); // invalidate old visible regions m_visible_region.rects.clear(); + bool rewrap_enabled = ePythonConfigQuery::getConfigBoolValue("config.subtitles.subtitle_rewrap"); + bool colourise_dialogs_enabled = ePythonConfigQuery::getConfigBoolValue("config.subtitles.colourise_dialogs"); + bool original_position = ePythonConfigQuery::getConfigBoolValue("config.subtitles.subtitle_original_position"); + int elements = m_pango_page.m_elements.size(); + + if(rewrap_enabled | colourise_dialogs_enabled) + { + size_t ix, colourise_dialogs_current = 0; + std::vector colourise_dialogs_colours; + std::string replacement; + std::string alignmentValue; + ePythonConfigQuery::getConfigValue("config.subtitles.subtitle_alignment", alignmentValue); + bool alignment_center = (alignmentValue == "center"); + + if(colourise_dialogs_enabled) + { + colourise_dialogs_colours.push_back((std::string)gRGB(0xff, 0xff, 0x00)); // yellow + colourise_dialogs_colours.push_back((std::string)gRGB(0x00, 0xff, 0xff)); // cyan + colourise_dialogs_colours.push_back((std::string)gRGB(0xff, 0x00, 0xff)); // magenta + colourise_dialogs_colours.push_back((std::string)gRGB(0x00, 0xff, 0x00)); // green + colourise_dialogs_colours.push_back((std::string)gRGB(0xff, 0xaa, 0xaa)); // light red + colourise_dialogs_colours.push_back((std::string)gRGB(0xaa, 0xaa, 0xff)); // light blue + } + + for (int i=0; i= colourise_dialogs_colours.size()) + colourise_dialogs_current = 0; + } + } + } + } + if (elements) { int startY = elements > 1 @@ -92,7 +224,17 @@ void eSubtitleWidget::setPage(const ePangoSubtitlePage &p) { eRect &area = m_pango_page.m_elements[i].m_area; area.setLeft(startX); - area.setTop(size_per_element * i + startY); + if (!original_position) + { + int lowerborder = ePythonConfigQuery::getConfigIntValue("config.subtitles.subtitle_position", 50); + if (lowerborder == 0) + lowerborder -= 100 * getDesktop(0)->size().height()/720; + else if (lowerborder == 50) + lowerborder -= 50 * getDesktop(0)->size().height()/720; + area.setTop(size_per_element * i + startY - lowerborder); + } + else + area.setTop(size_per_element * i + startY); area.setWidth(width); area.setHeight(size_per_element); m_visible_region.rects.push_back(area); @@ -140,6 +282,22 @@ int eSubtitleWidget::event(int event, void *data, void *data2) getStyle(style); eWidget::event(event, data, data2); + std::string alignmentValue; + int rt_halignment_flag; + ePythonConfigQuery::getConfigValue("config.subtitles.subtitle_alignment", alignmentValue); + if (alignmentValue == "right") + rt_halignment_flag = gPainter::RT_HALIGN_RIGHT; + else if (alignmentValue == "left") + rt_halignment_flag = gPainter::RT_HALIGN_LEFT; + else + rt_halignment_flag = gPainter::RT_HALIGN_CENTER; + + int fontsize = ePythonConfigQuery::getConfigIntValue("config.subtitles.subtitle_fontsize", 34) * getDesktop(0)->size().width()/1280; + int edgestyle = ePythonConfigQuery::getConfigIntValue("config.subtitles.subtitle_edgestyle"); + int borderwidth = (edgestyle == FONTSTYLE_UNIFORM) ? ePythonConfigQuery::getConfigIntValue("config.subtitles.subtitle_edgestyle_level") : 0; + gRGB bordercolor = gRGB(); + bool original_colors = false; + if (m_pixmap) { eRect r = m_pixmap_dest; @@ -148,20 +306,135 @@ int eSubtitleWidget::event(int event, void *data, void *data2) } else if (m_page_ok) { int elements = m_page.m_elements.size(); + + subtitleStyles[Subtitle_TTX].font->pointSize = fontsize; + painter.setFont(subtitleStyles[Subtitle_TTX].font); for (int i=0; isetFont(subtitleStyles[Subtitle_TTX].font); + para->renderString(element.m_text.c_str(), RS_WRAP); + + eRect bgbox = para->getBoundBox(); + int bgboxWidth = bgbox.width(); + int bgboxHeight = bgbox.height(); + if (alignmentValue == "left") + bgbox.setLeft(area.left() - padding - borderwidth); + else if (alignmentValue == "right") + bgbox.setLeft(area.left() + area.width() - bgboxWidth - padding - borderwidth); + else + bgbox.setLeft(area.left() + area.width() / 2 - bgboxWidth / 2 - padding - borderwidth); + bgbox.setTop(area.top() + area.height() / 2 - bgboxHeight / 2 - padding * 2 - borderwidth); + bgbox.setWidth(bgboxWidth + padding * 2 + borderwidth * 2); + bgbox.setHeight(bgboxHeight + padding * 3 + borderwidth * 2); + + switch (ePythonConfigQuery::getConfigIntValue("config.subtitles.subtitle_bgcolor", 0)) + { + case BGCOLOR_WHITE: + bg_r = 255; + bg_g = 255; + bg_b = 255; + break; + case BGCOLOR_YELLOW: + bg_r = 255; + bg_g = 255; + bg_b = 0; + break; + case BGCOLOR_GREEN: + bg_r = 0; + bg_g = 255; + bg_b = 0; + break; + case BGCOLOR_CYAN: + bg_r = 0; + bg_g = 255; + bg_b = 255; + break; + case BGCOLOR_BLUE: + bg_r = 0; + bg_g = 0; + bg_b = 255; + break; + case BGCOLOR_MAGNETA: + bg_r = 255; + bg_g = 0; + bg_b = 255; + break; + case BGCOLOR_RED: + bg_r = 255; + bg_g = 0; + bg_b = 0; + break; + case BGCOLOR_BLACK: + default: + bg_r = 0; + bg_g = 0; + bg_b = 0; + break; + } + bg_a = ePythonConfigQuery::getConfigIntValue("config.subtitles.subtitle_bgopacity", 0); + + painter.setForegroundColor(gRGB(bg_r, bg_g, bg_b, bg_a)); + painter.fill(bgbox); + } + + int offset = ePythonConfigQuery::getConfigIntValue("config.subtitles.subtitle_edgestyle_level", 3); + switch(edgestyle) + { + default: + case FONTSTYLE_NONE: + offset = 0; + borderwidth = 0; + break; + case FONTSTYLE_RAISED: + { + eRect shadow = area; + ePoint shadow_offset = ePoint(-offset, -offset); + shadow.moveBy(shadow_offset); + painter.setForegroundColor(subtitleStyles[Subtitle_TTX].shadow_color); + painter.renderText(shadow, element.m_text, gPainter::RT_WRAP|gPainter::RT_VALIGN_CENTER|rt_halignment_flag); + } + break; + case FONTSTYLE_DEPRESSED: + { + eRect shadow = area; + ePoint shadow_offset = ePoint(offset, offset); + shadow.moveBy(shadow_offset); + painter.setForegroundColor(subtitleStyles[Subtitle_TTX].shadow_color); + painter.renderText(shadow, element.m_text, gPainter::RT_WRAP|gPainter::RT_VALIGN_CENTER|rt_halignment_flag); + } + break; + case FONTSTYLE_UNIFORM: + { + if (borderwidth > 0) + { + if (fontcolor.r == 0 && fontcolor.g == 0 && fontcolor.b == 0) + { + gRGB tmp_border_white = gRGB(255,255,255); + bordercolor = tmp_border_white; + } + else if (bg_r == 0 && bg_g == 0 && bg_b == 0) + { + borderwidth = 0; + } + } + } + break; + } + if ( !subtitleStyles[Subtitle_TTX].have_foreground_color ) painter.setForegroundColor(element.m_color); else painter.setForegroundColor(subtitleStyles[Subtitle_TTX].foreground_color); - painter.renderText(area, element.m_text, gPainter::RT_WRAP|gPainter::RT_VALIGN_CENTER|gPainter::RT_HALIGN_CENTER); + painter.renderText(area, element.m_text, gPainter::RT_WRAP|gPainter::RT_VALIGN_CENTER|rt_halignment_flag, bordercolor, borderwidth); } } else if (m_pango_page_ok) @@ -174,34 +447,209 @@ int eSubtitleWidget::event(int event, void *data, void *data2) face = Subtitle_Regular; ePangoSubtitlePageElement &element = m_pango_page.m_elements[i]; std::string text = element.m_pango_line; - std::string::size_type loc = text.find("<", 0 ); - if ( loc != std::string::npos ) + text = replace_all(text, "'", "'"); + text = replace_all(text, """, "\""); + text = replace_all(text, "&", "&"); + text = replace_all(text, "<", "<"); + text = replace_all(text, ">", ">"); + + std::string shadow_text = text; + if (edgestyle == FONTSTYLE_RAISED || edgestyle == FONTSTYLE_DEPRESSED) { - switch (char(text.at(1))) - { - case 'i': + shadow_text = replace_all(shadow_text, "", ""); + shadow_text = replace_all(shadow_text, "", ""); + shadow_text = replace_all(shadow_text, "", ""); + shadow_text = replace_all(shadow_text, "", ""); + shadow_text = replace_all(shadow_text, "", ""); + shadow_text = replace_all(shadow_text, "", ""); + } + + if (ePythonConfigQuery::getConfigBoolValue("config.subtitles.pango_subtitle_fontswitch")) + { + if (text.find("") != std::string::npos || text.find("") != std::string::npos) face = Subtitle_Italic; - break; - case 'b': + else if (text.find("") != std::string::npos || text.find("") != std::string::npos) face = Subtitle_Bold; - break; + } + int subtitleColors = ePythonConfigQuery::getConfigIntValue("config.subtitles.subtitle_fontcolor", 1); + if (!subtitleColors) + { + text = replace_all(text, "", gRGB(255,255,0)); + text = replace_all(text, "", gRGB(0,255,255)); + text = replace_all(text, "", (std::string) gRGB(0,255,0)); + text = replace_all(text, "", (std::string) gRGB(255,255,255)); + text = replace_all(text, "", (std::string) gRGB(255,255,255)); + text = replace_all(text, "", (std::string) gRGB(255,255,255)); } - text = text.substr(3, text.length()-7); + else + { + text = replace_all(text, "", ""); + text = replace_all(text, "", ""); + text = replace_all(text, "", ""); + text = replace_all(text, "", ""); + text = replace_all(text, "", ""); + text = replace_all(text, "", ""); } - text = replace_all(text, "'", "'"); - text = replace_all(text, """, "\""); - text = replace_all(text, "&", "&"); + + gRGB fontcolor = (!subtitleStyles[face].have_foreground_color) ? element.m_color : subtitleStyles[face].foreground_color; + switch (subtitleColors) + { + default: + case 0: /* use original colors */ + original_colors = true; + break; + case FONTCOLOR_WHITE: + fontcolor = gRGB(255, 255, 255); + break; + case FONTCOLOR_YELLOW: + fontcolor = gRGB(255, 255, 0); + break; + case FONTCOLOR_GREEN: + fontcolor = gRGB(0, 255, 0); + break; + case FONTCOLOR_CYAN: + fontcolor = gRGB(0, 255, 255); + break; + case FONTCOLOR_BLUE: + fontcolor = gRGB(0, 0, 255); + break; + case FONTCOLOR_MAGNETA: + fontcolor = gRGB(255, 0, 255); + break; + case FONTCOLOR_RED: + fontcolor = gRGB(255, 0, 0); + break; + case FONTCOLOR_BLACK: + fontcolor = gRGB(0, 0, 0); + break; + } + fontcolor.a = ePythonConfigQuery::getConfigIntValue("config.subtitles.subtitle_opacity"); + if (!original_colors) + text = (std::string)fontcolor + text; + + subtitleStyles[face].font->pointSize = fontsize; painter.setFont(subtitleStyles[face].font); eRect &area = element.m_area; - eRect shadow = area; - shadow.moveBy(subtitleStyles[face].shadow_offset); - painter.setForegroundColor(subtitleStyles[face].shadow_color); - painter.renderText(shadow, text, gPainter::RT_WRAP|gPainter::RT_VALIGN_CENTER|gPainter::RT_HALIGN_CENTER); + int bg_r, bg_g, bg_b, bg_a; + if (ePythonConfigQuery::getConfigIntValue("config.subtitles.subtitle_bgopacity") < 0xFF) + { + unsigned int padding = 10; + eTextPara *para = new eTextPara(area); + para->setFont(subtitleStyles[face].font); + para->renderString(text.c_str(), RS_WRAP); + + eRect bgbox = para->getBoundBox(); + int bgboxWidth = bgbox.width(); + int bgboxHeight = bgbox.height(); + if (alignmentValue == "left") + bgbox.setLeft(area.left() - padding - borderwidth); + else if (alignmentValue == "right") + bgbox.setLeft(area.left() + area.width() - bgboxWidth - padding - borderwidth); + else + bgbox.setLeft(area.left() + area.width() / 2 - bgboxWidth / 2 - padding - borderwidth); + bgbox.setTop(area.top() + area.height() / 2 - bgboxHeight / 2 - padding * 2 - borderwidth); + bgbox.setWidth(bgboxWidth + padding * 2 + borderwidth * 2); + bgbox.setHeight(bgboxHeight + padding * 3 + borderwidth * 2); + + switch (ePythonConfigQuery::getConfigIntValue("config.subtitles.subtitle_bgcolor", 0)) + { + case BGCOLOR_WHITE: + bg_r = 255; + bg_g = 255; + bg_b = 255; + break; + case BGCOLOR_YELLOW: + bg_r = 255; + bg_g = 255; + bg_b = 0; + break; + case BGCOLOR_GREEN: + bg_r = 0; + bg_g = 255; + bg_b = 0; + break; + case BGCOLOR_CYAN: + bg_r = 0; + bg_g = 255; + bg_b = 255; + break; + case BGCOLOR_BLUE: + bg_r = 0; + bg_g = 0; + bg_b = 255; + break; + case BGCOLOR_MAGNETA: + bg_r = 255; + bg_g = 0; + bg_b = 255; + break; + case BGCOLOR_RED: + bg_r = 255; + bg_g = 0; + bg_b = 0; + break; + case BGCOLOR_BLACK: + default: + bg_r = 0; + bg_g = 0; + bg_b = 0; + break; + } + bg_a = ePythonConfigQuery::getConfigIntValue("config.subtitles.subtitle_bgopacity", 0); + + painter.setForegroundColor(gRGB(bg_r, bg_g, bg_b, bg_a)); + painter.fill(bgbox); + } + + int offset = ePythonConfigQuery::getConfigIntValue("config.subtitles.subtitle_edgestyle_level", 3); + switch(edgestyle) + { + default: + case FONTSTYLE_NONE: + offset = 0; + borderwidth = 0; + break; + case FONTSTYLE_RAISED: + { + eRect shadow = area; + ePoint shadow_offset = ePoint(-offset, -offset); + shadow.moveBy(shadow_offset); + painter.setForegroundColor(subtitleStyles[face].shadow_color); + painter.renderText(shadow, shadow_text, gPainter::RT_WRAP|gPainter::RT_VALIGN_CENTER|rt_halignment_flag); + } + break; + case FONTSTYLE_DEPRESSED: + { + eRect shadow = area; + ePoint shadow_offset = ePoint(offset, offset); + shadow.moveBy(shadow_offset); + painter.setForegroundColor(subtitleStyles[face].shadow_color); + painter.renderText(shadow, shadow_text, gPainter::RT_WRAP|gPainter::RT_VALIGN_CENTER|rt_halignment_flag); + } + break; + case FONTSTYLE_UNIFORM: + { + if (borderwidth > 0) + { + if (fontcolor.r == 0 && fontcolor.g == 0 && fontcolor.b == 0) + { + gRGB tmp_border_white = gRGB(255,255,255); + bordercolor = tmp_border_white; + } + else if (bg_r == 0 && bg_g == 0 && bg_b == 0) + { + borderwidth = 0; + } + } + } + break; + } + if ( !subtitleStyles[face].have_foreground_color && element.m_have_color ) painter.setForegroundColor(element.m_color); else painter.setForegroundColor(subtitleStyles[face].foreground_color); - painter.renderText(area, text, gPainter::RT_WRAP|gPainter::RT_VALIGN_CENTER|gPainter::RT_HALIGN_CENTER); + painter.renderText(area, text, gPainter::RT_WRAP|gPainter::RT_VALIGN_CENTER|rt_halignment_flag, bordercolor, borderwidth); } } else if (m_dvb_page_ok) diff --git a/lib/gui/esubtitle.h b/lib/gui/esubtitle.h index cdad728..7f97a90 100644 --- a/lib/gui/esubtitle.h +++ b/lib/gui/esubtitle.h @@ -5,6 +5,14 @@ #include #include +enum +{ + FONTSTYLE_NONE = 0, + FONTSTYLE_RAISED, + FONTSTYLE_DEPRESSED, + FONTSTYLE_UNIFORM, +}; + struct ePangoSubtitlePageElement { gRGB m_color; diff --git a/lib/python/Components/UsageConfig.py b/lib/python/Components/UsageConfig.py index b98be60..fc7ae83 100644 --- a/lib/python/Components/UsageConfig.py +++ b/lib/python/Components/UsageConfig.py @@ -142,6 +142,76 @@ def InitUsageConfig(): config.seek.speeds_backward.addNotifier(updateEnterBackward, immediate_feedback = False) + config.subtitles = ConfigSubsection() + config.subtitles.subtitle_fontcolor = ConfigSelection(default = "0", choices = [ + ("0", _("default")), + ("1", _("white")), + ("2", _("yellow")), + ("3", _("green")), + ("4", _("cyan")), + ("5", _("blue")), + ("6", _("magneta")), + ("7", _("red")), + ("8", _("black")) ]) + config.subtitles.subtitle_fontsize = ConfigSelection(choices = ["%d" % x for x in range(16,101) if not x % 2], default = "20") + config.subtitles.subtitle_bgcolor = ConfigSelection(default = "0", choices = [ + ("0", _("black")), + ("1", _("red")), + ("2", _("magneta")), + ("3", _("blue")), + ("4", _("cyan")), + ("5", _("green")), + ("6", _("yellow")), + ("7", _("white"))]) + config.subtitles.subtitle_bgopacity = ConfigSelection(default = "225", choices = [ + ("0", _("No transparency")), + ("25", "10%"), + ("50", "20%"), + ("75", "30%"), + ("100", "40%"), + ("125", "50%"), + ("150", "60%"), + ("175", "70%"), + ("200", "80%"), + ("225", "90%"), + ("255", _("Full transparency"))]) + config.subtitles.subtitle_edgestyle = ConfigSelection(default = "2", choices = [ + ("0", "None"), + ("1", "Raised"), + ("2", "Depressed"), + ("3", "Uniform")]) + config.subtitles.subtitle_edgestyle_level = ConfigSelection(choices = ["0", "1", "2", "3", "4", "5"], default = "3") + config.subtitles.subtitle_opacity = ConfigSelection(default = "0", choices = [ + ("0", _("No transparency")), + ("75", "25%"), + ("150", "50%")]) + config.subtitles.subtitle_original_position = ConfigYesNo(default = True) + config.subtitles.subtitle_alignment = ConfigSelection(choices = [("left", _("left")), ("center", _("center")), ("right", _("right"))], default = "center") + config.subtitles.subtitle_position = ConfigSelection( choices = ["0", "50", "100", "150", "200", "250", "300", "350", "400", "450", "500", "550", "600"], default = "50") + + config.subtitles.dvb_subtitles_centered = ConfigYesNo(default = False) + + subtitle_delay_choicelist = [] + for i in range(-900000, 1845000, 45000): + if i == 0: + subtitle_delay_choicelist.append(("0", _("No delay"))) + else: + subtitle_delay_choicelist.append((str(i), "%2.1f sec" % (i / 90000.))) + config.subtitles.subtitle_noPTSrecordingdelay = ConfigSelection(default = "315000", choices = subtitle_delay_choicelist) + config.subtitles.subtitle_bad_timing_delay = ConfigSelection(default = "0", choices = subtitle_delay_choicelist) + config.subtitles.subtitle_rewrap = ConfigYesNo(default = False) + config.subtitles.colourise_dialogs = ConfigYesNo(default = False) + config.subtitles.pango_subtitle_fontswitch = ConfigYesNo(default = True) + config.subtitles.pango_subtitles_delay = ConfigSelection(default = "0", choices = subtitle_delay_choicelist) + config.subtitles.pango_subtitles_fps = ConfigSelection(default = "1", choices = [ + ("1", _("Original")), + ("23976", _("23.976")), + ("24000", _("24")), + ("25000", _("25")), + ("29970", _("29.97")), + ("30000", _("30"))]) + config.subtitles.pango_autoturnon = ConfigYesNo(default = True) + def updateChoices(sel, choices): if choices: defval = None diff --git a/lib/python/Screens/AudioSelection.py b/lib/python/Screens/AudioSelection.py index b251ef7..537aab4 100755 --- a/lib/python/Screens/AudioSelection.py +++ b/lib/python/Screens/AudioSelection.py @@ -1,15 +1,17 @@ from Screen import Screen +from Screens.Setup import getConfigMenuItem from Components.ServiceEventTracker import ServiceEventTracker from Components.ActionMap import NumberActionMap from Components.ConfigList import ConfigListScreen from Components.ChoiceList import ChoiceList, ChoiceEntryComponent from Components.config import config, ConfigSubsection, getConfigListEntry, ConfigNothing, ConfigSelection, ConfigOnOff +from Components.Label import Label from Components.MultiContent import MultiContentEntryText from Components.Sources.List import List from Components.Sources.Boolean import Boolean from Components.SystemInfo import SystemInfo -from enigma import iPlayableService +from enigma import iPlayableService, eTimer, eSize from Tools.ISO639 import LanguageCodes from Tools.BoundFunction import boundFunction @@ -129,9 +131,13 @@ class AudioSelection(Screen, ConfigListScreen): elif self.settings.menupage.getValue() == PAGE_SUBTITLES: self.setTitle(_("Subtitle selection")) conflist.append(('',)) - conflist.append(('',)) self["key_red"].setBoolean(False) - self["key_green"].setBoolean(False) + if self.infobar.selected_subtitle: + conflist.append(getConfigListEntry(_("Subtitle Quickmenu"), ConfigNothing())) + self["key_green"].setBoolean(True) + else: + conflist.append(('',)) + self["key_green"].setBoolean(False) if self.subtitlesEnabled(): sel = self.infobar.selected_subtitle @@ -249,7 +255,10 @@ class AudioSelection(Screen, ConfigListScreen): def keyRight(self, config = False): if config or self.focus == FOCUS_CONFIG: if self["config"].getCurrentIndex() < 3: - ConfigListScreen.keyRight(self) + if self["config"].getCurrentIndex() == 1 and self.settings.menupage.getValue() == PAGE_SUBTITLES and self.infobar.selected_subtitle: + self.session.open(QuickSubtitlesConfigMenu, self.infobar) + else: + ConfigListScreen.keyRight(self) elif hasattr(self, "plugincallfunc"): self.plugincallfunc() if self.focus == FOCUS_STREAMS and self["streams"].count() and config == False: @@ -329,3 +338,106 @@ class SubtitleSelection(AudioSelection): def __init__(self, session, infobar=None): AudioSelection.__init__(self, session, infobar, page=PAGE_SUBTITLES) self.skinName = ["AudioSelection"] + +class QuickSubtitlesConfigMenu(ConfigListScreen, Screen): + skin = """ + + + + """ + + def __init__(self, session, infobar): + Screen.__init__(self, session) + self.skin = QuickSubtitlesConfigMenu.skin + self.infobar = infobar or self.session.infobar + + self.wait = eTimer() + self.wait.timeout.get().append(self.resyncSubtitles) + + self["videofps"] = Label("") + + sub = self.infobar.selected_subtitle + if sub[0] == 0: # dvb + menu = [ + getConfigMenuItem("config.subtitles.subtitle_fontcolor"), + getConfigMenuItem("config.subtitles.dvb_subtitles_centered"), + getConfigMenuItem("config.subtitles.subtitle_bgopacity"), + getConfigMenuItem("config.subtitles.subtitle_original_position"), + getConfigMenuItem("config.subtitles.subtitle_position"), + getConfigMenuItem("config.subtitles.subtitle_bad_timing_delay"), + getConfigMenuItem("config.subtitles.subtitle_noPTSrecordingdelay"), + ] + elif sub[0] == 1: # teletext + menu = [ + getConfigMenuItem("config.subtitles.subtitle_fontcolor"), + getConfigMenuItem("config.subtitles.subtitle_fontsize"), + getConfigMenuItem("config.subtitles.subtitle_opacity"), + getConfigMenuItem("config.subtitles.subtitle_bgcolor"), + getConfigMenuItem("config.subtitles.subtitle_bgopacity"), + getConfigMenuItem("config.subtitles.subtitle_edgestyle"), + getConfigMenuItem("config.subtitles.subtitle_edgestyle_level"), + getConfigMenuItem("config.subtitles.subtitle_original_position"), + getConfigMenuItem("config.subtitles.subtitle_alignment"), + getConfigMenuItem("config.subtitles.subtitle_position"), + getConfigMenuItem("config.subtitles.subtitle_bad_timing_delay"), + getConfigMenuItem("config.subtitles.subtitle_noPTSrecordingdelay"), + ] + else: # pango + menu = [ + getConfigMenuItem("config.subtitles.subtitle_fontcolor"), + getConfigMenuItem("config.subtitles.subtitle_fontsize"), + getConfigMenuItem("config.subtitles.subtitle_opacity"), + getConfigMenuItem("config.subtitles.subtitle_bgcolor"), + getConfigMenuItem("config.subtitles.subtitle_bgopacity"), + getConfigMenuItem("config.subtitles.subtitle_edgestyle"), + getConfigMenuItem("config.subtitles.subtitle_edgestyle_level"), + getConfigMenuItem("config.subtitles.subtitle_original_position"), + getConfigMenuItem("config.subtitles.subtitle_alignment"), + getConfigMenuItem("config.subtitles.subtitle_position"), + getConfigMenuItem("config.subtitles.pango_subtitle_fontswitch"), + getConfigMenuItem("config.subtitles.colourise_dialogs"), + getConfigMenuItem("config.subtitles.subtitle_rewrap"), + getConfigMenuItem("config.subtitles.pango_subtitles_delay"), + getConfigMenuItem("config.subtitles.pango_subtitles_fps"), + ] + self["videofps"].setText(_("Video: %s fps") % (self.getFps().rstrip(".000"))) + + ConfigListScreen.__init__(self, menu, self.session, on_change = self.changedEntry) + + self["actions"] = NumberActionMap(["SetupActions"], + { + "cancel": self.cancel, + "ok": self.ok, + },-2) + + self.onLayoutFinish.append(self.layoutFinished) + + def layoutFinished(self): + if not self["videofps"].text: + self.instance.resize(eSize(self.instance.size().width(), self["config"].l.getItemSize().height()*len(self["config"].getList()) + 10)) + + def changedEntry(self): + if self["config"].getCurrent() in [getConfigMenuItem("config.subtitles.pango_subtitles_delay"),getConfigMenuItem("config.subtitles.pango_subtitles_fps")]: + self.wait.start(500, True) + + def resyncSubtitles(self): + self.infobar.setSeekState(self.infobar.SEEK_STATE_PAUSE) + self.infobar.setSeekState(self.infobar.SEEK_STATE_PLAY) + + def getFps(self): + from enigma import iServiceInformation + service = self.session.nav.getCurrentService() + info = service and service.info() + if not info: + return "" + fps = info.getInfo(iServiceInformation.sFrameRate) + if fps > 0: + return "%6.3f" % (fps/1000.) + return "" + + def cancel(self): + self.close() + + def ok(self): + config.subtitles.save() + self.close() diff --git a/lib/python/Screens/InfoBarGenerics.py b/lib/python/Screens/InfoBarGenerics.py index c36eb9c..186b07a 100755 --- a/lib/python/Screens/InfoBarGenerics.py +++ b/lib/python/Screens/InfoBarGenerics.py @@ -2254,8 +2254,7 @@ class InfoBarSubtitleSupport(object): self.__selected_subtitle = None def __updatedInfo(self): - if not self.cached_subtitle_checked: - self.cached_subtitle_checked = True + if not self.__selected_subtitle: subtitle = self.getCurrentServiceSubtitle() self.setSelectedSubtitle(subtitle and subtitle.getCachedSubtitle()) if self.__selected_subtitle: @@ -2276,7 +2275,7 @@ class InfoBarSubtitleSupport(object): else: if subtitle: subtitle.disableSubtitles(self.subtitle_window.instance) - self.__selected_subtitle = False + self.__selected_subtitle = None self.__subtitles_enabled = False self.subtitle_window.hide() diff --git a/lib/python/Screens/Setup.py b/lib/python/Screens/Setup.py index 2e83327..7e4f03a 100755 --- a/lib/python/Screens/Setup.py +++ b/lib/python/Screens/Setup.py @@ -19,6 +19,12 @@ except: setupdom = xml.etree.cElementTree.parse(setupfile) setupfile.close() +def getConfigMenuItem(configElement): + for item in setupdom.getroot().findall('./setup/item/.'): + if item.text == configElement: + return _(item.attrib["text"]), eval(configElement) + return "", None + class SetupError(Exception): def __init__(self, message): self.msg = message diff --git a/lib/service/servicedvb.cpp b/lib/service/servicedvb.cpp index 07827ec..3f580fe 100755 --- a/lib/service/servicedvb.cpp +++ b/lib/service/servicedvb.cpp @@ -3051,11 +3051,30 @@ void eDVBServicePlay::newSubtitlePage(const eDVBTeletextSubtitlePage &page) { if (m_subtitle_widget) { + int subtitledelay = 0; pts_t pos = 0; if (m_decoder) m_decoder->getPTS(0, pos); + if (m_is_pvr || m_timeshift_enabled) + { + eDebug("Subtitle in recording/timeshift"); + subtitledelay = ePythonConfigQuery::getConfigIntValue("config.subtitles.subtitle_noPTSrecordingdelay", 315000); + } + else + { + /* check the setting for subtitle delay in live playback, either with pos, or without pos */ + subtitledelay = ePythonConfigQuery::getConfigIntValue("config.subtitles.subtitle_bad_timing_delay", 0); + } + eDebug("got new subtitle page %lld %lld %d", pos, page.m_pts, page.m_have_pts); - m_subtitle_pages.push_back(page); + eDVBTeletextSubtitlePage tmppage = page; + tmppage.m_have_pts = true; + + if (abs(tmppage.m_pts - pos) > 20*90000) + tmppage.m_pts = pos; // fix abnormal pos diffs + + tmppage.m_pts += subtitledelay; + m_subtitle_pages.push_back(tmppage); checkSubtitleTiming(); } } @@ -3139,7 +3158,29 @@ void eDVBServicePlay::newDVBSubtitlePage(const eDVBSubtitlePage &p) if (m_decoder) m_decoder->getPTS(0, pos); eDebug("got new subtitle page %lld %lld", pos, p.m_show_time); - m_dvb_subtitle_pages.push_back(p); + if ( abs(pos-p.m_show_time)>1800000 && (m_is_pvr || m_timeshift_enabled)) + { + eDebug("[eDVBServicePlay] Subtitle without PTS and recording"); + int subtitledelay = ePythonConfigQuery::getConfigIntValue("config.subtitles.subtitle_noPTSrecordingdelay", 315000); + + eDVBSubtitlePage tmppage; + tmppage = p; + tmppage.m_show_time = pos + subtitledelay; + m_dvb_subtitle_pages.push_back(tmppage); + } + else + { + int subtitledelay = ePythonConfigQuery::getConfigIntValue("config.subtitles.subtitle_bad_timing_delay", 0); + if (subtitledelay != 0) + { + eDVBSubtitlePage tmppage; + tmppage = p; + tmppage.m_show_time += subtitledelay; + m_dvb_subtitle_pages.push_back(tmppage); + } + else + m_dvb_subtitle_pages.push_back(p); + } checkSubtitleTiming(); } } diff --git a/lib/service/servicemp3.cpp b/lib/service/servicemp3.cpp index 0437f59..fc23d00 100755 --- a/lib/service/servicemp3.cpp +++ b/lib/service/servicemp3.cpp @@ -1593,7 +1593,7 @@ void eServiceMP3::gstBusCall(GstBus *bus, GstMessage *msg) m_subtitleStreams.push_back(subs); g_free (g_lang); } - m_event((iPlayableService*)this, evUpdatedEventInfo); + m_event((iPlayableService*)this, evUpdatedInfo); if ( m_errorInfo.missing_codec != "" ) { @@ -1913,6 +1913,13 @@ void eServiceMP3::pullSubtitle(GstBuffer *buffer) { if ( subType < stVOB ) { + int delay = ePythonConfigQuery::getConfigIntValue("config.subtitles.pango_subtitles_delay"); + int subtitle_fps = ePythonConfigQuery::getConfigIntValue("config.subtitles.pango_subtitles_fps"); + + double convert_fps = 1.0; + if (subtitle_fps > 1 && m_framerate > 0) + convert_fps = subtitle_fps / (double)m_framerate; + unsigned char line[len+1]; SubtitlePage page; #if GST_VERSION_MAJOR < 1 @@ -1927,7 +1934,7 @@ void eServiceMP3::pullSubtitle(GstBuffer *buffer) gRGB rgbcol(0xD0,0xD0,0xD0); page.type = SubtitlePage::Pango; page.pango_page.m_elements.push_back(ePangoSubtitlePageElement(rgbcol, (const char*)line)); - page.pango_page.m_show_pts = buf_pos / 11111LL; + page.pango_page.m_show_pts = buf_pos / 11111LL + convert_fps + delay; page.pango_page.m_timeout = duration_ns / 1000000; m_subtitle_pages.push_back(page); m_subtitle_sync_timer->start(1, true); @@ -2110,6 +2117,26 @@ RESULT eServiceMP3::disableSubtitles(eWidget *parent) PyObject *eServiceMP3::getCachedSubtitle() { // eDebug("eServiceMP3::getCachedSubtitle"); + bool autoturnon = ePythonConfigQuery::getConfigBoolValue("config.subtitles.pango_autoturnon", true); + if (!autoturnon) + Py_RETURN_NONE; + + if (!m_subtitleStreams.empty()) + { + int index = 0; + if (m_currentSubtitleStream >= 0 && m_currentSubtitleStream < (int)m_subtitleStreams.size()) + { + index = m_currentSubtitleStream; + } + ePyObject tuple = PyTuple_New(5); + PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(2)); + PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(index)); + PyTuple_SET_ITEM(tuple, 2, PyInt_FromLong(int(m_subtitleStreams[index].type))); + PyTuple_SET_ITEM(tuple, 3, PyInt_FromLong(0)); + PyTuple_SET_ITEM(tuple, 4, PyString_FromString(m_subtitleStreams[index].language_code.c_str())); + return tuple; + } + Py_RETURN_NONE; }