-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[video_player] VTT Support #2878
Changes from 7 commits
9788b63
a66f5d7
6481331
8f8b095
da1c6e4
1a21621
0d3549e
b018ea9
b92f305
1573670
1e53fc5
9034263
d9647a9
2ebf99f
43abfd6
a44edea
d92e820
10e717c
98e21c1
e146366
c05a6f1
50044cc
2aacc0f
928bfb4
ee9e13a
bf3e958
91bba0f
459b7f8
b43b1a8
db547b6
7864399
7afa2c2
a8a5994
0fdc514
1ca27a7
39ba0ae
7391c73
00efa43
8e1dcad
9fdcb05
f0e286b
1350f8e
c72a068
226b9c6
4554771
0b7dde4
7aa17ba
2f686c2
5d09170
78098ca
46aecd8
cacf4dc
5524931
110c115
808b1a0
a041f54
2a16e2e
acf7417
de3c973
495b37c
fc0d049
9db4ec1
c5bbba6
7bb0d53
d47ea7f
4c66c38
db79449
fa0d6dc
a4fcbf7
61a22a5
14468e5
0d68872
c97876c
6c457a5
5ffff31
c8f7c2c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| WEBVTT | ||
|
|
||
| 00:00:00,200 --> 00:00:01,750 | ||
| [ Birds chirping ] | ||
|
|
||
| 00:00:02,300 --> 00:00:05,000 | ||
| [ Buzzing ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,191 @@ | ||
| // Copyright 2020 The Chromium Authors. All rights reserved. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This needs to be "Copyright 2013 The Flutter Authors." in both new files, which is why
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
| // Use of this source code is governed by a BSD-style license that can be | ||
| // found in the LICENSE file. | ||
|
|
||
| import 'dart:convert'; | ||
|
cyanglaz marked this conversation as resolved.
|
||
|
|
||
| import 'package:html/dom.dart'; | ||
|
|
||
| import 'closed_caption_file.dart'; | ||
| import 'package:html/parser.dart' as html_parser; | ||
|
|
||
| /// Represents a [ClosedCaptionFile], parsed from the WebVtt file format. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WebVTT
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated so all the appearance of WebVtt are WebVTT now. |
||
| /// See: https://en.wikipedia.org/wiki/WebVtt | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This link is broken; it's
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
| class WebVttCaptionFile extends ClosedCaptionFile { | ||
| /// Parses a string into a [ClosedCaptionFile], assuming [fileContents] is in | ||
| /// the WebVtt file format. | ||
| /// * See: https://en.wikipedia.org/wiki/WebVtt | ||
| WebVttCaptionFile(this.fileContents) | ||
|
ferrazrx marked this conversation as resolved.
Outdated
|
||
| : _captions = _parseCaptionsFromWebVttString(fileContents); | ||
|
|
||
| /// The entire body of the Vtt file. | ||
| final String fileContents; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this public?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It was probably a copy paste from SubRipCaptionFile in sub_rip.dart. Removed I'm not sure how to keep track of this tho. We might forgot about this when doing breaking changes in the plugin. |
||
|
|
||
| @override | ||
| List<Caption> get captions => _captions; | ||
|
|
||
| final List<Caption> _captions; | ||
| } | ||
|
|
||
| List<Caption> _parseCaptionsFromWebVttString(String file) { | ||
| final List<Caption> captions = <Caption>[]; | ||
|
|
||
| /// Ignore metadata | ||
| List<String> metadata = ['HEADER', 'NOTE', 'REGION', 'WEBVTT']; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: |
||
|
|
||
| int number = 1; | ||
|
ferrazrx marked this conversation as resolved.
Outdated
|
||
| for (List<String> captionLines in _readWebVttFile(file)) { | ||
| if (captionLines.length < 2) continue; | ||
|
cyanglaz marked this conversation as resolved.
|
||
|
|
||
| String metadaType = captionLines[0]?.split(' ')[0]; | ||
| if (metadata.contains(metadaType)) continue; | ||
|
|
||
| // Caption has header | ||
| bool hasHeader = captionLines.length > 2; | ||
| if (hasHeader) { | ||
| number = int.parse(captionLines[0]); | ||
| } | ||
|
|
||
| final int captionNumber = number; | ||
| final _StartAndEnd startAndEnd = _StartAndEnd.fromWebVttString( | ||
| hasHeader ? captionLines[1] : captionLines[0], | ||
| ); | ||
|
|
||
| final String text = captionLines.sublist(hasHeader ? 2 : 1).join('\n'); | ||
|
|
||
| /// TODO: Handle text formats | ||
| /// Some captions comes with anotations (information about who/how is the speech being delivered) and styles tags. | ||
| /// E.g: | ||
| /// <v.first.loud Neil deGrasse Tyson><i>Laughs</i> | ||
| final String textWithoutFormat = _parseHtmlString(text); | ||
|
|
||
| final Caption newCaption = Caption( | ||
| number: captionNumber, | ||
| start: startAndEnd.start, | ||
| end: startAndEnd.end, | ||
| text: textWithoutFormat, | ||
| ); | ||
|
|
||
| if (newCaption.start != null && newCaption.end != null) { | ||
| captions.add(newCaption); | ||
| number++; | ||
| } | ||
| } | ||
|
|
||
| return captions; | ||
| } | ||
|
|
||
| class _StartAndEnd { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a weird name; could we call it
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another copy paste from sub_rip, will fix it there too |
||
| final Duration start; | ||
| final Duration end; | ||
|
|
||
| _StartAndEnd(this.start, this.end); | ||
|
|
||
| // Assumes format from an Vtt file. | ||
| // For example: | ||
| // 00:09.000 --> 00:11.000 | ||
| static _StartAndEnd fromWebVttString(String line) { | ||
| final RegExp format = | ||
| RegExp(_webVttTimeStamp + _webVttArrow + _webVttTimeStamp); | ||
|
|
||
| if (!format.hasMatch(line)) { | ||
| return _StartAndEnd(null, null); | ||
| } | ||
|
|
||
| final List<String> times = line.split(_webVttArrow); | ||
|
|
||
| final Duration start = _parseWebVttTimestamp(times[0]); | ||
| final Duration end = _parseWebVttTimestamp(times[1]); | ||
|
|
||
| return _StartAndEnd(start, end); | ||
| } | ||
| } | ||
|
|
||
| String _parseHtmlString(String htmlString) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's call this
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
| final Document document = html_parser.parse(htmlString); | ||
| final String parsedString = | ||
| html_parser.parse(document.body.text).documentElement.text; | ||
| return parsedString; | ||
| } | ||
|
|
||
| // Parses a time stamp in an Vtt file into a Duration. | ||
| // For example: | ||
| // | ||
| // _parseWebVttimestamp('00:01:08.430') | ||
| // returns | ||
| // Duration(hours: 0, minutes: 1, seconds: 8, milliseconds: 430) | ||
|
cyanglaz marked this conversation as resolved.
|
||
| Duration _parseWebVttTimestamp(String timestampString) { | ||
| if (!RegExp(_webVttTimeStamp).hasMatch(timestampString)) { | ||
| return null; | ||
| } | ||
|
|
||
| final List<String> dotSections = timestampString.split('.'); | ||
| final List<String> hoursMinutesSeconds = dotSections[0].split(':'); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit:
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
|
|
||
| int hours = 0; | ||
| int minutes = 0; | ||
| int seconds = 0; | ||
| List<String> styles; | ||
|
|
||
| if (hoursMinutesSeconds.length > 2) { | ||
|
ferrazrx marked this conversation as resolved.
Outdated
|
||
| // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds] | ||
| hours = int.parse(hoursMinutesSeconds[0]); | ||
| minutes = int.parse(hoursMinutesSeconds[1]); | ||
| seconds = int.parse(hoursMinutesSeconds[2]); | ||
| } else if (int.parse(hoursMinutesSeconds[0]) > 59) { | ||
| // Timestamp takes the form of [hours]:[minutes].[milliseconds] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seemed deeply strange to me so I checked the spec, and this isn't a valid timestamp. We shouldn't handle invalid values unless there's substantial real-world evidence that lots of people have written this (which seems unlikely, because even if other parsers allow this it is incapable of expressing any time within the first 59 hours of a video) |
||
| // First position is hours as it's over 59. | ||
| hours = int.parse(hoursMinutesSeconds[0]); | ||
| minutes = int.parse(hoursMinutesSeconds[1]); | ||
| } else { | ||
| // Timestamp takes the form of [minutes]:[seconds].[milliseconds] | ||
| minutes = int.parse(hoursMinutesSeconds[0]); | ||
| seconds = int.parse(hoursMinutesSeconds[1]); | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Once the above is removed, this can all condense to: It would be good to add a safety check that the length is either 2 or 3 and returns null first, as well.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
|
|
||
| List<String> milisecondsStyles = dotSections[1].split(" "); | ||
|
|
||
| /// TODO: Handle styles | ||
| /// Some captions comes with styles about where/how the caption should be rendered. | ||
| /// E.g: | ||
| /// 00:32.500 --> 00:33.500 align:left size:50% | ||
| if (milisecondsStyles.length > 1) { | ||
| styles = milisecondsStyles.sublist(1); | ||
| } | ||
| int milliseconds = int.parse(milisecondsStyles[0]); | ||
|
|
||
| return Duration( | ||
| hours: hours, | ||
| minutes: minutes, | ||
| seconds: seconds, | ||
| milliseconds: milliseconds, | ||
| ); | ||
| } | ||
|
|
||
| // Reads on Vtt file and splits it into Lists of strings where each list is one | ||
| // caption. | ||
| List<List<String>> _readWebVttFile(String file) { | ||
| final List<String> lines = LineSplitter.split(file).toList(); | ||
|
|
||
| final List<List<String>> captionStrings = <List<String>>[]; | ||
| List<String> currentCaption = <String>[]; | ||
| int lineIndex = 0; | ||
| for (final String line in lines) { | ||
| final bool isLineBlank = line.trim().isEmpty; | ||
| if (!isLineBlank) { | ||
| currentCaption.add(line); | ||
| } | ||
|
|
||
| if (isLineBlank || lineIndex == lines.length - 1) { | ||
| captionStrings.add(currentCaption); | ||
| currentCaption = <String>[]; | ||
| } | ||
|
|
||
| lineIndex += 1; | ||
| } | ||
|
|
||
| return captionStrings; | ||
| } | ||
|
|
||
| const String _webVttTimeStamp = r'(\d+):(\d{2})(:\d{2})?\.(\d{3})'; | ||
| const String _webVttArrow = r' --> '; | ||
Uh oh!
There was an error while loading. Please reload this page.