Repository: yt-dlp/yt-dlp

Issue:
[tiktok] ERROR: The extracted extension ('eng-US.creator_caption') is unusual and will be skipped for safety reasons.
### 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 have **updated yt-dlp to nightly or master** ([update instructions](https://github.com/yt-dlp/yt-dlp#update-channels))
- [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)
- [ ] 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

Earth

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

Some subtitles from tiktok have an unusual extension, and causes the video to not be downloaded since it's raised as an error. 

I used `--compat-opt allow-unsafe-ext` to force the download and it contained the following:

`{"utterances":[{"text":"Casey Casey Casey","start_time":853,"end_time":2286,"words":null,"text_size":22,"text_color":"#ffffffff","bg_color":"#00000000","alignment":0,"source_width":0.5533,"source_height":0.0568},{"text":"Alien vs. Predator","start_time":20721,"end_time":22080,"words":null,"text_size":22,"text_color":"#ffffffff","bg_color":"#00000000","alignment":0,"source_width":0.5047,"source_height":0.0568},{"text":" a Mexican arguing with a Catholic priest","start_time":15656,"end_time":19938,"words":null,"text_size":22,"text_color":"#ffffffff","bg_color":"#00000000","alignment":0,"source_width":0.7199,"source_height":0.1047},{"text":"no...","start_time":15133,"end_time":15655,"words":null,"text_size":22,"text_color":"#ffffffff","bg_color":"#00000000","alignment":0,"source_width":0.1547,"source_height":0.0568},{"text":"what do you call a Mexican arguing with a Catholic priest?","start_time":4533,"end_time":9486,"words":null,"text_size":22,"text_color":"#ffffffff","bg_color":"#00000000","alignment":0,"source_width":0.8303,"source_height":0.1047},{"text":"oh my god it's hot","start_time":22081,"end_time":23218,"words":null,"text_size":22,"text_color":"#ffffffff","bg_color":"#00000000","alignment":0,"source_width":0.5065,"source_height":0.0568}]}`

### 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', '--write-subs', 'https://www.tiktok.com/@gliceron/video/7236544964514123050']
[debug] Encodings: locale UTF-8, fs utf-8, pref UTF-8, out utf-8, error utf-8, screen utf-8
[debug] yt-dlp version nightly@2024.07.19.025855 from yt-dlp/yt-dlp-nightly-builds [a3bab4752] (zip)
[debug] Python 3.9.19 (CPython amd64 64bit) - FreeBSD-13.2-RELEASE-p1-amd64-64bit-ELF (OpenSSL 1.1.1w-freebsd  11 Sep 2023, libc 7)
[debug] exe versions: ffmpeg 6.1.1 (setts), ffprobe 6.1.1, rtmpdump 2.4
[debug] Optional libraries: Cryptodome-3.15.0, brotli-1.0.9, certifi-2022.12.07, mutagen-1.46.0, requests-2.32.3, sqlite3-3.46.0, urllib3-2.1.0, websockets-12.0
[debug] Proxy map: {}
[debug] Request Handlers: urllib, requests, websockets
[debug] Plugin directories: ['/home/weird/.local/lib/python3.9/site-packages/yt_dlp_plugins']
[debug] Loaded 1829 extractors
[debug] Fetching release info: https://api.github.com/repos/yt-dlp/yt-dlp-nightly-builds/releases/latest
Latest version: nightly@2024.07.19.025855 from yt-dlp/yt-dlp-nightly-builds
yt-dlp is up to date (nightly@2024.07.19.025855 from yt-dlp/yt-dlp-nightly-builds)
[TikTok] Extracting URL: https://www.tiktok.com/@gliceron/video/7236544964514123050
[TikTok] 7236544964514123050: Downloading webpage
[debug] [TikTok] Found universal data for rehydration
[info] 7236544964514123050: Downloading subtitles: eng-US
[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] 7236544964514123050: Downloading 1 format(s): bytevc1_1080p_1516867-1
ERROR: The extracted extension ('eng-US.creator_caption') is unusual and will be skipped for safety reasons. If you believe this is an error, please report this issue on  https://github.com/yt-dlp/yt-dlp/issues?q= , filling out the appropriate issue template. Confirm you are on the latest version using  yt-dlp -U
Traceback (most recent call last):
  File "/home/weird/bin/yt-dlp-nightly/yt_dlp/YoutubeDL.py", line 179, in wrapper
    return func(self, *args, **kwargs)
  File "/home/weird/bin/yt-dlp-nightly/yt_dlp/YoutubeDL.py", line 3271, in process_info
    sub_files = self._write_subtitles(info_dict, temp_filename)
  File "/home/weird/bin/yt-dlp-nightly/yt_dlp/YoutubeDL.py", line 4328, in _write_subtitles
    sub_filename = subtitles_filename(filename, sub_lang, sub_format, info_dict.get('ext'))
  File "/home/weird/bin/yt-dlp-nightly/yt_dlp/utils/_utils.py", line 1262, in subtitles_filename
  File "/home/weird/bin/yt-dlp-nightly/yt_dlp/utils/_utils.py", line 2097, in _change_extension
  File "/home/weird/bin/yt-dlp-nightly/yt_dlp/utils/_utils.py", line 5167, in sanitize_extension
yt_dlp.utils._UnsafeExtensionError: unsafe file extension: 'eng-US.creator_caption'
```


Discussion:

Patch:
diff --git a/yt_dlp/extractor/tiktok.py b/yt_dlp/extractor/tiktok.py
index aa1dcecf6..9d823a315 100644
--- a/yt_dlp/extractor/tiktok.py
+++ b/yt_dlp/extractor/tiktok.py
@@ -23,7 +23,6 @@
     mimetype2ext,
     parse_qs,
     qualities,
-    remove_start,
     srt_subtitles_timecode,
     str_or_none,
     traverse_obj,
@@ -254,7 +253,16 @@ def _extract_web_data_and_status(self, url, video_id, fatal=True):
 
     def _get_subtitles(self, aweme_detail, aweme_id, user_name):
         # TODO: Extract text positioning info
+
+        EXT_MAP = {  # From lowest to highest preference
+            'creator_caption': 'json',
+            'srt': 'srt',
+            'webvtt': 'vtt',
+        }
+        preference = qualities(tuple(EXT_MAP.values()))
+
         subtitles = {}
+
         # aweme/detail endpoint subs
         captions_info = traverse_obj(
             aweme_detail, ('interaction_stickers', ..., 'auto_video_caption_info', 'auto_captions', ...), expected_type=dict)
@@ -278,8 +286,8 @@ def _get_subtitles(self, aweme_detail, aweme_id, user_name):
                 if not caption.get('url'):
                     continue
                 subtitles.setdefault(caption.get('lang') or 'en', []).append({
-                    'ext': remove_start(caption.get('caption_format'), 'web'),
                     'url': caption['url'],
+                    'ext': EXT_MAP.get(caption.get('Format')),
                 })
         # webpage subs
         if not subtitles:
@@ -288,9 +296,14 @@ def _get_subtitles(self, aweme_detail, aweme_id, user_name):
                     self._create_url(user_name, aweme_id), aweme_id, fatal=False)
             for caption in traverse_obj(aweme_detail, ('video', 'subtitleInfos', lambda _, v: v['Url'])):
                 subtitles.setdefault(caption.get('LanguageCodeName') or 'en', []).append({
-                    'ext': remove_start(caption.get('Format'), 'web'),
                     'url': caption['Url'],
+                    'ext': EXT_MAP.get(caption.get('Format')),
                 })
+
+        # Deprioritize creator_caption json since it can't be embedded or used by media players
+        for lang, subs_list in subtitles.items():
+            subtitles[lang] = sorted(subs_list, key=lambda x: preference(x['ext']))
+
         return subtitles
 
     def _parse_url_key(self, url_key):
diff --git a/yt_dlp/extractor/youtube.py b/yt_dlp/extractor/youtube.py
index 53aca3816..7364e8a2e 100644
--- a/yt_dlp/extractor/youtube.py
+++ b/yt_dlp/extractor/youtube.py
@@ -3131,7 +3131,14 @@ def _decrypt_nsig(self, s, video_id, player_url):
 
     def _extract_n_function_name(self, jscode):
         funcname, idx = self._search_regex(
-            r'''(?x)(?:\.get\("n"\)\)&&\(b=|b=String\.fromCharCode\(110\),c=a\.get\(b\)\)&&\(c=)
+            r'''(?x)
+            (?:
+                \.get\("n"\)\)&&\(b=|
+                (?:
+                    b=String\.fromCharCode\(110\)|
+                    ([a-zA-Z0-9$.]+)&&\(b="nn"\[\+\1\]
+                ),c=a\.get\(b\)\)&&\(c=
+            )
             (?P<nfunc>[a-zA-Z0-9$]+)(?:\[(?P<idx>\d+)\])?\([a-zA-Z0-9]\)''',
             jscode, 'Initial JS player n function name', group=('nfunc', 'idx'))
         if not idx:


Test patch:
diff --git a/test/test_youtube_signature.py b/test/test_youtube_signature.py
index a14bef511..ae167d16d 100644
--- a/test/test_youtube_signature.py
+++ b/test/test_youtube_signature.py
@@ -171,6 +171,10 @@
         'https://www.youtube.com/s/player/b22ef6e7/player_ias.vflset/en_US/base.js',
         'b6HcntHGkvBLk_FRf', 'kNPW6A7FyP2l8A',
     ),
+    (
+        'https://www.youtube.com/s/player/3400486c/player_ias.vflset/en_US/base.js',
+        'lL46g3XifCKUZn1Xfw', 'z767lhet6V2Skl',
+    ),
 ]