diff --git a/components/captionTask.bs b/components/captionTask.bs index eb391b79..9b9f2a09 100644 --- a/components/captionTask.bs +++ b/components/captionTask.bs @@ -20,7 +20,8 @@ sub init() m.captionList = [] m.reader = createObject("roUrlTransfer") m.font = CreateObject("roSGNode", "Font") - m.tags = CreateObject("roRegex", "{\\an\d*}|<.*?>|<.*?>", "s") + m.undesirableTags = CreateObject("roRegex", "{\\an\d*}|<.*?>|<(?!\/?font\b).*?>", "s") + m.fontColor = CreateObject("roRegex", "color\s*?[=]\s*?""?(\S*)""?\b", "i") ' Caption Style m.fontSizeDict = { "Default": 60, "Large": 60, "Extra Large": 70, "Medium": 50, "Small": 40, "Extra Small": 30 } @@ -44,7 +45,6 @@ sub setFont() if fs.Exists("tmp:/font") m.font.uri = "tmp:/font" - m.font.size = m.fontSize else m.font = "font:LargeSystemFont" end if @@ -64,18 +64,9 @@ sub fetchCaption() end if end sub -function newlabel(txt) - label = CreateObject("roSGNode", "Label") - label.text = txt - label.font = m.font - label.font.size = m.fontSize - label.color = m.textColor - return label -end function - -function newLayoutGroup(labels) +function newLayoutGroup(label) newlg = CreateObject("roSGNode", "LayoutGroup") - newlg.appendchildren(labels) + newlg.appendchild(label) newlg.layoutDirection = "vert" newlg.horizalignment = "left" newlg.vertalignment = "top" @@ -103,7 +94,6 @@ function newRect(lg, id as string) return rect end function - sub updateCaption() if LCase(m.top.playerState.right(1)) = "w" m.top.playerState = m.top.playerState.left(len (m.top.playerState) - 1) @@ -120,15 +110,147 @@ sub updateCaption() for each entry in m.captionList if entry["start"] <= m.top.currentPos and m.top.currentPos < entry["end"] - labels = [] - for each text in entry["text"] - labels.push(newlabel(text)) + fullText = entry["text"].Join(Chr(10)) + + ' look for style tags + drawingStyles = { + "default": { + "fontUri": m.font, + "fontSize": m.fontSize, + "color": m.textColor + } + } + openTags = [] + styledText = string.EMPTY + parts = fullText.Split("<") + firstIter = true + for each part in parts + if firstIter + ' first part will either be "" if it starts with a tag, + ' or plain text before the first tag, so just add it and continue + styledText += part + firstIter = false + continue for + end if + tagEnd = part.Instr(0, ">") + nextNewLine = part.Instr(0, Chr(10)) + if tagEnd = -1 or (nextNewLine <> -1 and nextNewLine < tagEnd) + ' no end bracket on the same line - treat as literal less-than sign + styledText += `<${part}` + continue for + end if + tagValue = LCase(part.Mid(0, tagEnd).Trim()) + replaceTagWith = string.EMPTY + if tagValue.StartsWith("font") + ' opening font tag + colorName = invalid + color = m.fontColor.Match(tagValue) + if color.Count() > 1 + color = color[1].Replace("""", string.EMPTY).Trim() + if color.StartsWith("#") + ' hex color code (must be 3 or 6 digits) + sanitizedColor = "#" + colorChars = color.Mid(1).Split(string.EMPTY) + isShortColor = colorChars.Count() = 3 + for each c in colorChars + if c >= "0" and c <= "9" or c >= "a" and c <= "f" + sanitizedColor += c + if isShortColor + sanitizedColor += c + end if + end if + end for + if sanitizedColor.Len() = 7 + color = sanitizedColor + colorName = color + end if + else if color.StartsWith("rgb(") or color.StartsWith("rgba(") + ' rgb or rgba color code (alpha is ignored) + rgbValues = color.Mid(color.Instr("(") + 1).Replace(")", string.EMPTY).Split(",") + if rgbValues.Count() >= 3 + r = rgbValues[0].Trim() + g = rgbValues[1].Trim() + b = rgbValues[2].Trim() + ' verify they're numbers + ' can't rely on val() because it returns 0 and the text will end up black + if r.Left(1) >= "0" and r.Left(1) <= "9" and g.Left(1) >= "0" and g.Left(1) <= "9" and b.Left(1) >= "0" and b.Left(1) <= "9" + r = val(r, 10) + g = val(g, 10) + b = val(b, 10) + if r >= 0 and r <= 255 and g >= 0 and g <= 255 and b >= 0 and b <= 255 + color = "#%02x%02x%02x".Format(r, g, b) + colorName = color + end if + end if + end if + else + ' is it the name of a color? + cssColor = chainLookup(m.global.constants.cssColorDict, color) + if isValid(cssColor) + colorName = color + color = cssColor + end if + end if + if isValid(colorName) + drawingStyles.AddReplace(colorName, { + "fontUri": m.font, + "fontSize": m.fontSize, + "color": `${color}${m.textOpac}` + }) + replaceTagWith += `${colorName}` + end if + end if + ' keep track of invalid tags + if not isValid(colorName) + openTags.push(colorName) + end if + else if tagValue.StartsWith("/font") + ' closing tag + if openTags.Count() > 0 + openTag = openTags.pop() + if isValid(openTag) + replaceTagWith = `/${openTag}` + end if + end if + end if + if replaceTagWith.Len() > 0 + ' MultiStyleLabel doesn't support nested tags, so if this is nested, + ' we need to close the previous one before opening a new one, + ' and reopen it after + isClosing = replaceTagWith.StartsWith("/") + lastValidTag = invalid + for i = openTags.count() - 1 to 0 step -1 + ' do we have a valid tag still open? + if isValid(openTags[i]) + lastValidTag = openTags[i] + exit for + end if + end for + if isValid(lastValidTag) + if isClosing + styledText += `<${replaceTagWith}><${lastValidTag}>` + else + openTags.push(replaceTagWith) + styledText += `<${replaceTagWith}>` + end if + else + if not isClosing + openTags.push(replaceTagWith) + end if + styledText += `<${replaceTagWith}>` + end if + end if + styledText += part.Mid(tagEnd + 1) end for - lines = newLayoutGroup(labels) + label = CreateObject("roSGNode", "MultiStyleLabel") + label.drawingStyles = drawingStyles + label.text = styledText + label.horizAlign = "center" + lines = newLayoutGroup(label) rect = newRect(lines, entry.LookupCI("id")) - finalStyles = prepareStyles(entry["styles"], rect, labels.count()) + finalStyles = prepareStyles(entry["styles"], rect, entry["text"].count()) rect.translation = finalStyles.translation adjustForCaptionOverlaps(rect, captions) @@ -317,7 +439,7 @@ function parseVTT(lines) if isNewLine trimmed = lines[i].trim() - finalText = m.tags.replaceAll(trimmed, "") + finalText = m.undesirableTags.replaceAll(trimmed, "") ' We reached a blank line if isStringEqual(finalText, string.EMPTY) diff --git a/source/static/whatsNew/3.1.8.json b/source/static/whatsNew/3.1.8.json index ea9adafb..a8fa5728 100644 --- a/source/static/whatsNew/3.1.8.json +++ b/source/static/whatsNew/3.1.8.json @@ -30,5 +30,9 @@ { "description": "Add setting to select how many items are loaded together on library screens", "author": "brianpardy" + }, + { + "description": "Support font color tags in subtitles", + "author": "gabeluci" } ] diff --git a/source/utils/globals.bs b/source/utils/globals.bs index dc13af95..1ba26656 100644 --- a/source/utils/globals.bs +++ b/source/utils/globals.bs @@ -22,7 +22,9 @@ sub setConstants() descending_white: "pkg:/images/icons/down_white.png", check_black: "pkg:/images/icons/check_black.png", check_white: "pkg:/images/icons/check_white.png" - } + }, + + cssColorDict: { "aliceblue": "#f0f8ff", "antiquewhite": "#faebd7", "aqua": "#00ffff", "aquamarine": "#7fffd4", "azure": "#f0ffff", "beige": "#f5f5dc", "bisque": "#ffe4c4", "black": "#000000", "blanchedalmond": "#ffebcd", "blue": "#0000ff", "blueviolet": "#8a2be2", "brown": "#a52a2a", "burlywood": "#deb887", "cadetblue": "#5f9ea0", "chartreuse": "#7fff00", "chocolate": "#d2691e", "coral": "#ff7f50", "cornflowerblue": "#6495ed", "cornsilk": "#fff8dc", "crimson": "#dc143c", "cyan": "#00ffff", "darkblue": "#00008b", "darkcyan": "#008b8b", "darkgoldenrod": "#b8860b", "darkgray": "#a9a9a9", "darkgreen": "#006400", "darkgrey": "#a9a9a9", "darkkhaki": "#bdb76b", "darkmagenta": "#8b008b", "darkolivegreen": "#556b2f", "darkorange": "#ff8c00", "darkorchid": "#9932cc", "darkred": "#8b0000", "darksalmon": "#e9967a", "darkseagreen": "#8fbc8f", "darkslateblue": "#483d8b", "darkslategray": "#2f4f4f", "darkslategrey": "#2f4f4f", "darkturquoise": "#00ced1", "darkviolet": "#9400d3", "deeppink": "#ff1493", "deepskyblue": "#00bfff", "dimgray": "#696969", "dimgrey": "#696969", "dodgerblue": "#1e90ff", "firebrick": "#b22222", "floralwhite": "#fffaf0", "forestgreen": "#228b22", "fuchsia": "#ff00ff", "gainsboro": "#dcdcdc", "ghostwhite": "#f8f8ff", "gold": "#ffd700", "goldenrod": "#daa520", "gray": "#808080", "green": "#008000", "greenyellow": "#adff2f", "grey": "#808080", "honeydew": "#f0fff0", "hotpink": "#ff69b4", "indianred": "#cd5c5c", "indigo": "#4b0082", "ivory": "#fffff0", "khaki": "#f0e68c", "lavender": "#e6e6fa", "lavenderblush": "#fff0f5", "lawngreen": "#7cfc00", "lemonchiffon": "#fffacd", "lightblue": "#add8e6", "lightcoral": "#f08080", "lightcyan": "#e0ffff", "lightgoldenrodyellow": "#fafad2", "lightgray": "#d3d3d3", "lightgreen": "#90ee90", "lightgrey": "#d3d3d3", "lightpink": "#ffb6c1", "lightsalmon": "#ffa07a", "lightseagreen": "#20b2aa", "lightskyblue": "#87cefa", "lightslategray": "#778899", "lightslategrey": "#778899", "lightsteelblue": "#b0c4de", "lightyellow": "#ffffe0", "lime": "#00ff00", "limegreen": "#32cd32", "linen": "#faf0e6", "magenta": "#ff00ff", "maroon": "#800000", "mediumaquamarine": "#66cdaa", "mediumblue": "#0000cd", "mediumorchid": "#ba55d3", "mediumpurple": "#9370db", "mediumseagreen": "#3cb371", "mediumslateblue": "#7b68ee", "mediumspringgreen": "#00fa9a", "mediumturquoise": "#48d1cc", "mediumvioletred": "#c71585", "midnightblue": "#191970", "mintcream": "#f5fffa", "mistyrose": "#ffe4e1", "moccasin": "#ffe4b5", "navajowhite": "#ffdead", "navy": "#000080", "oldlace": "#fdf5e6", "olive": "#808000", "olivedrab": "#6b8e23", "orange": "#ffa500", "orangered": "#ff4500", "orchid": "#da70d6", "palegoldenrod": "#eee8aa", "palegreen": "#98fb98", "paleturquoise": "#afeeee", "palevioletred": "#db7093", "papayawhip": "#ffefd5", "peachpuff": "#ffdab9", "peru": "#cd853f", "pink": "#ffc0cb", "plum": "#dda0dd", "powderblue": "#b0e0e6", "purple": "#800080", "red": "#ff0000", "rosybrown": "#bc8f8f", "royalblue": "#4169e1", "saddlebrown": "#8b4513", "salmon": "#fa8072", "sandybrown": "#f4a460", "seagreen": "#2e8b57", "seashell": "#fff5ee", "sienna": "#a0522d", "silver": "#c0c0c0", "skyblue": "#87ceeb", "slateblue": "#6a5acd", "slategray": "#708090", "slategrey": "#708090", "snow": "#fffafa", "springgreen": "#00ff7f", "steelblue": "#4682b4", "tan": "#d2b48c", "teal": "#008080", "thistle": "#d8bfd8", "tomato": "#ff6347", "turquoise": "#40e0d0", "violet": "#ee82ee", "wheat": "#f5deb3", "white": "#ffffff", "whitesmoke": "#f5f5f5", "yellow": "#ffff00", "yellowgreen": "#9acd32" } } }) end sub