Repository: yt-dlp/yt-dlp

Issue:
ERROR: Parse error at position 8 (near '149\n00:00:00.000 -->') when downloading subtitles from SRGSSR
### DO NOT REMOVE OR SKIP THE ISSUE TEMPLATE

- [X] I understand that I will be **blocked** if I *intentionally* remove or skip any mandatory\* field

### Checklist

- [X] I'm reporting that yt-dlp is broken on a **supported** site
- [X] I've verified that I'm running yt-dlp version **2023.06.22** ([update instructions](https://github.com/yt-dlp/yt-dlp#update)) or later (specify commit)
- [X] I've checked that all provided URLs are playable in a browser with the same IP and same login details
- [X] I've checked that all URLs and arguments with special characters are [properly quoted or escaped](https://github.com/yt-dlp/yt-dlp/wiki/FAQ#video-url-contains-an-ampersand--and-im-getting-some-strange-output-1-2839-or-v-is-not-recognized-as-an-internal-or-external-command)
- [X] I've searched [known issues](https://github.com/yt-dlp/yt-dlp/issues/3766) and the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues **including closed ones**. DO NOT post duplicates
- [X] I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
- [X] I've read about [sharing account credentials](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#are-you-willing-to-share-account-details-if-needed) and I'm willing to share it if required

### Region

Switzerland

### Provide a description that is worded well enough to be understood

Downloading at least some videos with subtitles from Swiss national TV's web site fails with a parse error in the subtitles:

```
> yt-dlp --all-subs --convert-subs vtt 'https://www.srf.ch/play/tv/-/video/-?urn=urn:srf:video:679f823a-08d1-425f-a66d-ab8f00a4f6ac'
[SRGSSRPlay] Extracting URL: https://www.srf.ch/play/tv/-/video/-?urn=urn:srf:video:679f823a-08d1-425f-a66d-ab8f00a4f6ac
[SRGSSR] Extracting URL: srgssr:srf:video:679f823a-08d1-425f-a66d-ab8f00a4f6ac
[SRGSSR] 679f823a-08d1-425f-a66d-ab8f00a4f6ac: Downloading JSON metadata
[SRGSSR] 679f823a-08d1-425f-a66d-ab8f00a4f6ac: Downloading m3u8 information
[info] 679f823a-08d1-425f-a66d-ab8f00a4f6ac: Downloading subtitles: de
[info] 679f823a-08d1-425f-a66d-ab8f00a4f6ac: Downloading 1 format(s): HLS-H264-SD-2123
[info] Writing video subtitles to: Horrende Medi-Preise treibt kranke Schweizer nach Indien [679f823a-08d1-425f-a66d-ab8f00a4f6ac].de.vtt
[hlsnative] Downloading m3u8 manifest
[hlsnative] Total fragments: 102
[download] Destination: Horrende Medi-Preise treibt kranke Schweizer nach Indien [679f823a-08d1-425f-a66d-ab8f00a4f6ac].de.vtt
[download]   1.0% of ~  26.99KiB at    4.07KiB/s ETA 00:06 (frag 1/102)ERROR: Parse error at position 8 (near '149\n00:00:00.000 -->')
```

It turns out the parser fails, because there is a trailing space after the cue timings. The VTT fragment that's downloaded looks like:

```
WEBVTT

149
00:00:00.000 --> 00:00:00.028 
<font color="#ffffff">zeigen wir im nächsten "Kassensturz".</font>

150
00:00:01.328 --> 00:00:06.588 
<font color="#ffffff">Das ist ein wichtiges Medikament</font>
<font color="#ffffff">für Patienten mit Hepatitis C:</font>
```

and contains a trailing space after 00:00:00.028 and 00:00:06.588 . This causes CueBlock.parse() in yt_dlp/webvtt.py to `return None` when it doesn't find a newline after the second timestamp and optional cue settings.


I've made a fork and prepared a pull request with a way to fix this: Just add a line to consume any spaces after the optional cue settings. I'll like the pull request as soon as I've created it.

However, there might be better ways to fix this:

* "consume" trailing whitespace as part of `m2 = parser.consume(cls._REGEX_SETTINGS)`?
* ignore/remove any trailing whitespace in all the lines of VTT fragments?
* treat such subtitles as "invalid" and disregard them, but still proceed downloading the actual video?


### Provide verbose output that clearly demonstrates the problem

- [X] Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
- [ ] If using API, add `'verbose': True` to `YoutubeDL` params instead
- [X] Copy the WHOLE output (starting with `[debug] Command-line config`) and insert it below

### Complete Verbose Output

```shell
[debug] Command-line config: ['-vU', '--all-subs', '--convert-subs', 'vtt', 'https://www.srf.ch/play/tv/-/video/-?urn=urn:srf:video:679f823a-08d1-425f-a66d-ab8f00a4f6ac']
[debug] Encodings: locale UTF-8, fs utf-8, pref UTF-8, out utf-8, error utf-8, screen utf-8
[debug] yt-dlp version stable@2023.06.22 [812cdfa06] (zip)
[debug] Python 3.9.2 (CPython x86_64 64bit) - Linux-5.10.0-23-amd64-x86_64-with-glibc2.31 (OpenSSL 1.1.1n  15 Mar 2022, glibc 2.31)
[debug] exe versions: ffmpeg 4.4.1 (setts), ffprobe 4.4.1, phantomjs 2.1.1, rtmpdump 2.4
[debug] Optional libraries: certifi-2020.06.20, pyxattr-0.7.2, sqlite3-2.6.0
[debug] Proxy map: {}
[debug] Loaded 1851 extractors
[debug] Fetching release info: https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest
Available version: stable@2023.06.22, Current version: stable@2023.06.22
Current Build Hash: 924464eb831bdb99d854b9d47ff9e1c0870eab276113adaf8a74a8561ad332b9
yt-dlp is up to date (stable@2023.06.22)
[SRGSSRPlay] Extracting URL: https://www.srf.ch/play/tv/-/video/-?urn=urn:srf:video:679f823a-08d1-425f-a66d-ab8f00a4f6ac
[SRGSSR] Extracting URL: srgssr:srf:video:679f823a-08d1-425f-a66d-ab8f00a4f6ac
[SRGSSR] 679f823a-08d1-425f-a66d-ab8f00a4f6ac: Downloading JSON metadata
[SRGSSR] 679f823a-08d1-425f-a66d-ab8f00a4f6ac: Downloading m3u8 information
[info] 679f823a-08d1-425f-a66d-ab8f00a4f6ac: Downloading subtitles: de
[debug] Formats sorted by: hasvid, ie_pref, lang, quality, res, fps, hdr:12(7), vcodec:vp9.2(10), channels, acodec, size, br, asr, proto, vext, aext, hasaud, source, id
[debug] Default format spec: bestvideo*+bestaudio/best
[info] 679f823a-08d1-425f-a66d-ab8f00a4f6ac: Downloading 1 format(s): HLS-H264-SD-2123
[info] Writing video subtitles to: Horrende Medi-Preise treibt kranke Schweizer nach Indien [679f823a-08d1-425f-a66d-ab8f00a4f6ac].de.vtt
[debug] Invoking hlsnative downloader on "https://srf-vod-amd.akamaized.net/world/hls/kassensturz/2016/04/kassensturz_20160405_210750_v_webcast_h264_,q40,q10,q20,q30,q50,.mp4.csmil/hdntl=exp=1688056982~acl=%2f*~data=hdntl,541.682-1550.558~hmac=a4a5fba0799e9be87fbcd6c2fdd1df67fb903089554b3f6e11b010711cd4a81f/index-f6.m3u8?start=541.682&end=1550.558&caption=srf/d2ec6deb-8dc5-40ef-a560-612cf32458c7/episode/de/vod/vod.m3u8:de:Deutsch:sdh&webvttbaseurl=subtitles.eai-general.aws.srf.ch"
[hlsnative] Downloading m3u8 manifest
[hlsnative] Total fragments: 102
[download] Destination: Horrende Medi-Preise treibt kranke Schweizer nach Indien [679f823a-08d1-425f-a66d-ab8f00a4f6ac].de.vtt
[download]   1.0% of ~  26.99KiB at    6.68KiB/s ETA 00:03 (frag 1/102)ERROR: Parse error at position 8 (near '149\n00:00:00.000 -->')
Traceback (most recent call last):
  File "/home/ftc/h/../yt-dlp/yt_dlp/YoutubeDL.py", line 1555, in wrapper
    return func(self, *args, **kwargs)
  File "/home/ftc/h/../yt-dlp/yt_dlp/YoutubeDL.py", line 1652, in __extract_info
    return self.process_ie_result(ie_result, download, extra_info)
  File "/home/ftc/h/../yt-dlp/yt_dlp/YoutubeDL.py", line 1711, in process_ie_result
    ie_result = self.process_video_result(ie_result, download=download)
  File "/home/ftc/h/../yt-dlp/yt_dlp/YoutubeDL.py", line 2821, in process_video_result
    self.process_info(new_info)
  File "/home/ftc/h/../yt-dlp/yt_dlp/YoutubeDL.py", line 3075, in process_info
    sub_files = self._write_subtitles(info_dict, temp_filename)
  File "/home/ftc/h/../yt-dlp/yt_dlp/YoutubeDL.py", line 4070, in _write_subtitles
    self.dl(sub_filename, sub_copy, subtitle=True)
  File "/home/ftc/h/../yt-dlp/yt_dlp/YoutubeDL.py", line 3008, in dl
    return fd.download(name, new_info, subtitle)
  File "/home/ftc/h/../yt-dlp/yt_dlp/downloader/common.py", line 455, in download
    ret = self.real_download(filename, info_dict)
  File "/home/ftc/h/../yt-dlp/yt_dlp/downloader/hls.py", line 362, in real_download
    self.download_and_append_fragments(
  File "/home/ftc/h/../yt-dlp/yt_dlp/downloader/fragment.py", line 532, in download_and_append_fragments
    result = append_fragment(
  File "/home/ftc/h/../yt-dlp/yt_dlp/downloader/fragment.py", line 491, in append_fragment
    self._append_fragment(ctx, pack_func(frag_content, frag_index))
  File "/home/ftc/h/../yt-dlp/yt_dlp/downloader/hls.py", line 277, in pack_fragment
    for block in webvtt.parse_fragment(frag_content):
  File "/home/ftc/h/../yt-dlp/yt_dlp/webvtt.py", line 397, in parse_fragment
    raise ParseError(parser)
yt_dlp.webvtt.ParseError: Parse error at position 8 (near '149\n00:00:00.000 -->')
```



Discussion:
the pull request that implements one way to fix this is https://github.com/yt-dlp/yt-dlp/pull/7454

Resolving patch:
diff --git a/yt_dlp/webvtt.py b/yt_dlp/webvtt.py
--- a/yt_dlp/webvtt.py
+++ b/yt_dlp/webvtt.py
@@ -95,6 +95,7 @@ def __init__(self, parser):
 _REGEX_EOF = re.compile(r'\Z')
 _REGEX_NL = re.compile(r'(?:\r\n|[\r\n]|$)')
 _REGEX_BLANK = re.compile(r'(?:\r\n|[\r\n])+')
+_REGEX_OPTIONAL_WHITESPACE = re.compile(r'[ \t]*')
 
 
 def _parse_ts(ts):
@@ -285,6 +286,7 @@ def parse(cls, parser):
         m1 = parser.consume(_REGEX_TS)
         if not m1:
             return None
+        parser.consume(_REGEX_OPTIONAL_WHITESPACE)
         m2 = parser.consume(cls._REGEX_SETTINGS)
         if not parser.consume(_REGEX_NL):
             return None


Test patch: