feat: render inline markup in subtitle cue text - #958
Open
Ortes wants to merge 1 commit into
Open
Conversation
Cue text extracted from WebVTT and SubRip files carries inline markup, and chewie rendered it with Text(text.toString()), so viewers read "<i>The law is the law.</i>" instead of italics. parseSubtitleMarkup turns a cue into an InlineSpan: <b>, <i>, <u> and <font color> are applied on top of the caller's style, the remaining WebVTT cue tags are dropped while their text is kept, and character escapes are decoded. It parses the whole cue at once so a tag may span a line break, and it is lenient — "5 < 10" and "<3" are left alone, an unclosed tag runs to the end of the cue, and a stray closing tag is ignored. Restyling subtitles used to mean replacing the renderer through subtitleBuilder, which is also the only place markup could ever have been handled. SubtitleStyle now covers text style, alignment, padding and the box itself while chewie keeps rendering the cue, so presentation no longer costs you semantics. subtitleBuilder is untouched: it still receives the cue exactly as supplied, and parseSubtitleMarkup is exported so it can opt back in. The three control skins duplicated the subtitle box; they now share SubtitleOverlay and differ only in the margin they pass it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
Cue text extracted from WebVTT and SubRip files carries inline markup. Chewie renders cues with
Text(text.toString()), so a viewer readsinstead of italics. Measured on a real library of 51 films: 126
<i>…</i>pairs across 3 539 cues (~4 %), 37 of them spanning two lines inside a single cue.<i>is not stray junk — WebVTT specifies a cue text tag grammar (<b>,<i>,<u>,<c.class>,<v Speaker>,<lang xx>,<ruby>/<rt>, timestamp tags, and&-style escapes), and SubRip conventionally uses<b>,<i>,<u>and<font color>. Every other player handles this in the player: ExoPlayer'sCue.textis aSpannedproduced by its Subrip/WebVTT decoders, and browsers render WebVTT natively.There is a second problem behind it.
subtitleBuilderis currently the only way to change how a subtitle looks, and it replaces the renderer wholesale — so styling subtitles has always been all-or-nothing, and it is also the only place markup could ever have been handled.What this adds
parseSubtitleMarkup(String cueText, {TextStyle style})→TextSpan, exported fromchewie.dart.<b>,<i>,<u>and<font color="#rrggbb">merge onto the caller's style, so a custom font size and colour survive.& < > ‎ ‏and numeric escapes are decoded, in text only, so<i>shows literal angle brackets rather than turning into a tag.<only starts a tag when a well-formed tag follows on the same line, so5 < 10and<3come out verbatim. An unclosed tag runs to the end of the cue and a stray closing tag is ignored; neither throws.SubtitleStyle, a newChewieController.subtitleStylefield coveringtextStyle,textAlign,padding,decorationand arenderMarkupopt-out. It changes how the default box looks without giving up markup rendering. Its defaults reproduce the current box exactly, so setting nothing changes nothing.SubtitleOverlay, an internal widget the three control skins now share. They duplicated the samePadding/Container/Textblock; they now differ only in the margin they pass in.The three tiers end up as: default (markup, zero config) →
subtitleStyle(presentation, markup kept) →subtitleBuilder(everything yours).Backward compatibility
subtitleBuilderis untouched — same signature, same precedence, and it still receives the cue exactly as supplied, tags and all, before any parsing. There is a test asserting precisely that.SubtitleStyle's defaults are the current hard-coded literals, including leavingtextStyle.colorunset so it keeps inheriting from the ambientDefaultTextStyle.SubtitleStyle(renderMarkup: false)restores the literal output for anyone who wants it.Text.richrather than a bareRichText, so text scaling keeps working as it does withTexttoday.Considered and left out
A
richSubtitleBuilder(BuildContext, InlineSpan)hook. It would add a second builder with its own precedence rules to an already large controller, for a case the exported parser covers in one line inside the existingsubtitleBuilder. Happy to add it as a follow-up if you'd rather have it.Tests
45 new tests across
test/subtitle_markup_test.dart,test/subtitle_style_test.dartandtest/subtitle_overlay_test.dart, covering each tag, nesting, style composition, the multi-line case, every escape, all the leniency cases, andsubtitleBuilderback-compat.dart format,flutter analyze libandflutter testare clean.README's Subtitles section and the example app are updated. CHANGELOG is left alone since entries are added at release time.