Repository: yt-dlp/yt-dlp

Issue:
bin extension in the 'original' format of some wistia videos is problematic with postprocessor
### DO NOT REMOVE OR SKIP THE ISSUE TEMPLATE

- [X] I understand that I will be **blocked** if I remove or skip any mandatory\* field

### Checklist

- [X] I'm reporting a broken site
- [X] I've verified that I'm running yt-dlp version **2022.09.01** ([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 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

_No response_

### Provide a description that is worded well enough to be understood

URL is from the testcase
Ran with `--no-update` because I use a package manager building from git
You may notice that I connected over https for /embed/ despite the wistia extractor using http for this. I just edited it in my local install temporarily. It doesn't change the outcome though. If there is no particular need for connecting over http then I suggest changing the wistia extractor to use https for /embed/.
Commit 3c757d5ed2527b17881eb65c67ddbe0d1335771f

Some (not all) videos in Wistia have the 'original' format with a bin extension:
```
[debug] Command-line config: ['--no-update', '--proxy', 'socks5://127.0.0.1:9050', '--verbose', '--list-formats', '--batch-file', '-']
Reading URLs from STDIN - EOF (Ctrl+D) to end:
wistia:5vd7p4bct5
[debug] Batch file urls: ['wistia:5vd7p4bct5']
[debug] Encodings: locale UTF-8, fs utf-8, pref UTF-8, out utf-8, error utf-8, screen utf-8
[debug] yt-dlp version 2022.09.01 [5d7c7d656]
[debug] Lazy loading extractors is disabled
[debug] Python 3.10.7 (CPython 64bit) - Linux-5.19.8-arch1-1-x86_64-with-glibc2.36 (glibc 2.36)
[debug] Checking exe version: ffmpeg -bsfs
[debug] Checking exe version: ffprobe -bsfs
[debug] exe versions: ffmpeg N-108187-gb11813708d (setts), ffprobe N-108187-gb11813708d
[debug] Optional libraries: Cryptodome-3.12.0, certifi-2022.09.14, mutagen-1.45.1, sqlite3-2.6.0, websockets-10.3
[debug] Proxy map: {'http': 'socks5://127.0.0.1:9050', 'https': 'socks5://127.0.0.1:9050'}
[debug] Loaded 1673 extractors
[debug] [Wistia] Extracting URL: wistia:5vd7p4bct5
[Wistia] 5vd7p4bct5: Downloading JSON metadata
[debug] Formats sorted by: hasvid, ie_pref, lang, quality, res, fps, hdr:12(7), vcodec:vp9.2(10), channels, acodec, filesize, fs_approx, tbr, vbr, abr, asr, proto, vext, aext, hasaud, source, id
[info] Available formats for 5vd7p4bct5:
ID          EXT RESOLUTION │  FILESIZE  TBR PROTO │ VCODEC   VBR ACODEC  ABR
────────────────────────────────────────────────────────────────────────────
mp4-224p    mp4 400x224    │  13.53MiB 177k https │ h264    177k unknown  0k
iphone-360p mp4 640x360    │  17.62MiB 231k https │ h264    231k unknown  0k
md_mp4-540p mp4 960x540    │  24.58MiB 323k https │ h264    323k unknown  0k
hd_mp4-720p mp4 1280x720   │  34.16MiB 449k https │ h264    449k unknown  0k
original    bin 1280x720   │  28.42MiB 373k https │ unknown 373k unknown  0k
```
Downloading it you get an mp4 container with h264 and aac, like the other formats.

This causes various problems in the postprocessor.

Using embed-subtitle:
```
[debug] Command-line config: ['--no-update', '--proxy', 'socks5://127.0.0.1:9050', '--verbose', '--embed-subs', '--batch-file', '-']
Reading URLs from STDIN - EOF (Ctrl+D) to end:
wistia:5vd7p4bct5
[debug] Batch file urls: ['wistia:5vd7p4bct5']
[debug] Encodings: locale UTF-8, fs utf-8, pref UTF-8, out utf-8, error utf-8, screen utf-8
[debug] yt-dlp version 2022.09.01 [5d7c7d656]
[debug] Lazy loading extractors is disabled
[debug] Python 3.10.7 (CPython 64bit) - Linux-5.19.8-arch1-1-x86_64-with-glibc2.36 (glibc 2.36)
[debug] Checking exe version: ffmpeg -bsfs
[debug] Checking exe version: ffprobe -bsfs
[debug] exe versions: ffmpeg N-108187-gb11813708d (setts), ffprobe N-108187-gb11813708d
[debug] Optional libraries: Cryptodome-3.12.0, certifi-2022.09.14, mutagen-1.45.1, sqlite3-2.6.0, websockets-10.3
[debug] Proxy map: {'http': 'socks5://127.0.0.1:9050', 'https': 'socks5://127.0.0.1:9050'}
[debug] Loaded 1673 extractors
[debug] [Wistia] Extracting URL: wistia:5vd7p4bct5
[Wistia] 5vd7p4bct5: Downloading JSON metadata
[debug] Formats sorted by: hasvid, ie_pref, lang, quality, res, fps, hdr:12(7), vcodec:vp9.2(10), channels, acodec, filesize, fs_approx, tbr, vbr, abr, asr, proto, vext, aext, hasaud, source, id
[info] 5vd7p4bct5: Downloading subtitles: eng
[debug] Default format spec: bestvideo*+bestaudio/best
[info] 5vd7p4bct5: Downloading 1 format(s): original
[info] Writing video subtitles to: paywall_north-american-exploration-failed-colonies-of-spain-france-england [5vd7p4bct5].eng.vtt
[debug] Invoking http downloader on "https://fast.wistia.net/embed/captions/5vd7p4bct5.vtt?language=eng"
[download] Destination: paywall_north-american-exploration-failed-colonies-of-spain-france-england [5vd7p4bct5].eng.vtt
[download] 100% of 14.01KiB in    00:03 at 4.28KiB/s
[debug] Invoking http downloader on "https://embed-ssl.wistia.com/deliveries/0b5ac32a7b795b91180891d14a673f47.bin"
[download] Destination: paywall_north-american-exploration-failed-colonies-of-spain-france-england [5vd7p4bct5].bin
[download] 100% of 28.42MiB in    02:36 at 185.69KiB/s
[EmbedSubtitle] Subtitles can only be embedded in mp4, mov, m4a, webm, mkv, mka files
```
You get the embedsubtitle warning because the file extension is bin (despite this the subtitle file was still embedded, ffmpeg doesn't care about the file extension).

embed-metadata no-embed-info-json:
```
[debug] Command-line config: ['--no-update', '--proxy', 'socks5://127.0.0.1:9050', '--verbose', '--embed-metadata', '--no-embed-info-json', '--batch-file', '-']
Reading URLs from STDIN - EOF (Ctrl+D) to end:
wistia:5vd7p4bct5
[debug] Batch file urls: ['wistia:5vd7p4bct5']
[debug] Encodings: locale UTF-8, fs utf-8, pref UTF-8, out utf-8, error utf-8, screen utf-8
[debug] yt-dlp version 2022.09.01 [5d7c7d656]
[debug] Lazy loading extractors is disabled
[debug] Python 3.10.7 (CPython 64bit) - Linux-5.19.8-arch1-1-x86_64-with-glibc2.36 (glibc 2.36)
[debug] Checking exe version: ffmpeg -bsfs
[debug] Checking exe version: ffprobe -bsfs
[debug] exe versions: ffmpeg N-108187-gb11813708d (setts), ffprobe N-108187-gb11813708d
[debug] Optional libraries: Cryptodome-3.12.0, certifi-2022.09.14, mutagen-1.45.1, sqlite3-2.6.0, websockets-10.3
[debug] Proxy map: {'http': 'socks5://127.0.0.1:9050', 'https': 'socks5://127.0.0.1:9050'}
[debug] Loaded 1673 extractors
[debug] [Wistia] Extracting URL: wistia:5vd7p4bct5
[Wistia] 5vd7p4bct5: Downloading JSON metadata
[debug] Formats sorted by: hasvid, ie_pref, lang, quality, res, fps, hdr:12(7), vcodec:vp9.2(10), channels, acodec, filesize, fs_approx, tbr, vbr, abr, asr, proto, vext, aext, hasaud, source, id
[debug] Default format spec: bestvideo*+bestaudio/best
[info] 5vd7p4bct5: Downloading 1 format(s): original
[debug] Invoking http downloader on "https://embed-ssl.wistia.com/deliveries/0b5ac32a7b795b91180891d14a673f47.bin"
[download] Destination: paywall_north-american-exploration-failed-colonies-of-spain-france-england [5vd7p4bct5].bin
[download] 100% of 28.42MiB in    01:53 at 255.38KiB/s
[Metadata] Adding metadata to "paywall_north-american-exploration-failed-colonies-of-spain-france-england [5vd7p4bct5].bin"
[debug] ffmpeg command line: ffmpeg -y -loglevel repeat+info -i 'file:paywall_north-american-exploration-failed-colonies-of-spain-france-england [5vd7p4bct5].bin' -map 0 -dn -ignore_unknown -c copy -write_id3v1 1 -metadata title=paywall_north-american-exploration-failed-colonies-of-spain-france-england -metadata date=20220915 -metadata 'description=a Paywall Videos video' -metadata 'synopsis=a Paywall Videos video' -metadata purl=wistia:5vd7p4bct5 -metadata comment=wistia:5vd7p4bct5 -movflags +faststart 'file:paywall_north-american-exploration-failed-colonies-of-spain-france-england [5vd7p4bct5].temp.bin'
[debug] ffmpeg version N-108187-gb11813708d Copyright (c) 2000-2022 the FFmpeg developers
  built with gcc 12.2.0 (GCC)
  configuration: --prefix=/usr --disable-debug --disable-static --disable-stripping --enable-amf --enable-avisynth --enable-cuda-llvm --enable-lto --enable-fontconfig --enable-gmp --enable-gnutls --enable-gpl --enable-ladspa --enable-libaom --enable-libass --enable-libbluray --enable-libdav1d --enable-libdrm --enable-libfreetype --enable-libfribidi --enable-libgsm --enable-libiec61883 --enable-libjack --enable-libmfx --enable-libmodplug --enable-libmp3lame --enable-libopencore_amrnb --enable-libopencore_amrwb --enable-libopenjpeg --enable-libopus --enable-libpulse --enable-librav1e --enable-librsvg --enable-libsoxr --enable-libspeex --enable-libsrt --enable-libssh --enable-libsvtav1 --enable-libtheora --enable-libv4l2 --enable-libvidstab --enable-libvmaf --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxcb --enable-libxml2 --enable-libxvid --enable-libzimg --enable-shared --enable-version3 --enable-libjxl --enable-vaapi --enable-lcms2
  libavutil      57. 36.101 / 57. 36.101
  libavcodec     59. 44.100 / 59. 44.100
  libavformat    59. 32.100 / 59. 32.100
  libavdevice    59.  8.101 / 59.  8.101
  libavfilter     8. 49.100 /  8. 49.100
  libswscale      6.  8.112 /  6.  8.112
  libswresample   4.  9.100 /  4.  9.100
  libpostproc    56.  7.100 / 56.  7.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'file:paywall_north-american-exploration-failed-colonies-of-spain-france-england [5vd7p4bct5].bin':
  Metadata:
    major_brand     : qt  
    minor_version   : 512
    compatible_brands: qt  
    encoder         : Lavf57.40.101
  Duration: 00:10:23.00, start: 0.000000, bitrate: 382 kb/s
  Stream #0:0[0x1](eng): Video: h264 (High) (avc1 / 0x31637661), yuv420p(progressive), 1280x720 [SAR 1:1 DAR 16:9], 308 kb/s, 30 fps, 30 tbr, 15360 tbn (default)
    Metadata:
      handler_name    : VideoHandler
      vendor_id       : FFMP
      encoder         : Lavc57.48.101 libx264
  Stream #0:1[0x2](eng): Audio: aac (LC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 65 kb/s (default)
    Metadata:
      handler_name    : SoundHandler
      vendor_id       : [0][0][0][0]
[NULL @ 0x55f2a0972240] Unable to find a suitable output format for 'file:paywall_north-american-exploration-failed-colonies-of-spain-france-england [5vd7p4bct5].temp.bin'
file:paywall_north-american-exploration-failed-colonies-of-spain-france-england [5vd7p4bct5].temp.bin: Invalid argument

ERROR: Postprocessing: file:paywall_north-american-exploration-failed-colonies-of-spain-france-england [5vd7p4bct5].temp.bin: Invalid argument
Traceback (most recent call last):
  File "/usr/lib/python3.10/site-packages/yt_dlp/YoutubeDL.py", line 3270, in process_info
    replace_info_dict(self.post_process(dl_filename, info_dict, files_to_move))
  File "/usr/lib/python3.10/site-packages/yt_dlp/YoutubeDL.py", line 3447, in post_process
    info = self.run_all_pps('post_process', info, additional_pps=info.get('__postprocessors'))
  File "/usr/lib/python3.10/site-packages/yt_dlp/YoutubeDL.py", line 3429, in run_all_pps
    info = self.run_pp(pp, info)
  File "/usr/lib/python3.10/site-packages/yt_dlp/YoutubeDL.py", line 3408, in run_pp
    files_to_delete, infodict = pp.run(infodict)
  File "/usr/lib/python3.10/site-packages/yt_dlp/postprocessor/common.py", line 24, in run
    ret = func(self, info, *args, **kwargs)
  File "/usr/lib/python3.10/site-packages/yt_dlp/postprocessor/common.py", line 129, in wrapper
    return func(self, info)
  File "/usr/lib/python3.10/site-packages/yt_dlp/postprocessor/ffmpeg.py", line 702, in run
    self.run_ffmpeg_multiple_files(
  File "/usr/lib/python3.10/site-packages/yt_dlp/postprocessor/ffmpeg.py", line 323, in run_ffmpeg_multiple_files
    return self.real_run_ffmpeg(
  File "/usr/lib/python3.10/site-packages/yt_dlp/postprocessor/ffmpeg.py", line 361, in real_run_ffmpeg
    raise FFmpegPostProcessorError(stderr.strip().splitlines()[-1])
yt_dlp.postprocessor.ffmpeg.FFmpegPostProcessorError: file:paywall_north-american-exploration-failed-colonies-of-spain-france-england [5vd7p4bct5].temp.bin: Invalid argument
```
yt-dlp's usage of ffmpeg is that the output format is determined by the extension so that it stays the same between conversion, but because the extension is bin, ffmpeg doesn't know what output format to use in the conversion.

write-thumbnail:
```
[debug] Command-line config: ['--no-update', '--proxy', 'socks5://127.0.0.1:9050', '--verbose', '--write-thumbnail', '--batch-file', '-']
Reading URLs from STDIN - EOF (Ctrl+D) to end:
wistia:5vd7p4bct5
[debug] Batch file urls: ['wistia:5vd7p4bct5']
[debug] Encodings: locale UTF-8, fs utf-8, pref UTF-8, out utf-8, error utf-8, screen utf-8
[debug] yt-dlp version 2022.09.01 [5d7c7d656]
[debug] Lazy loading extractors is disabled
[debug] Python 3.10.7 (CPython 64bit) - Linux-5.19.8-arch1-1-x86_64-with-glibc2.36 (glibc 2.36)
[debug] Checking exe version: ffmpeg -bsfs
[debug] Checking exe version: ffprobe -bsfs
[debug] exe versions: ffmpeg N-108187-gb11813708d (setts), ffprobe N-108187-gb11813708d
[debug] Optional libraries: Cryptodome-3.12.0, certifi-2022.09.14, mutagen-1.45.1, sqlite3-2.6.0, websockets-10.3
[debug] Proxy map: {'http': 'socks5://127.0.0.1:9050', 'https': 'socks5://127.0.0.1:9050'}
[debug] Loaded 1673 extractors
[debug] [Wistia] Extracting URL: wistia:5vd7p4bct5
[Wistia] 5vd7p4bct5: Downloading JSON metadata
[debug] Formats sorted by: hasvid, ie_pref, lang, quality, res, fps, hdr:12(7), vcodec:vp9.2(10), channels, acodec, filesize, fs_approx, tbr, vbr, abr, asr, proto, vext, aext, hasaud, source, id
[debug] Default format spec: bestvideo*+bestaudio/best
[info] 5vd7p4bct5: Downloading 1 format(s): original
[info] Downloading video thumbnail 0 ...
[info] Writing video thumbnail 0 to: paywall_north-american-exploration-failed-colonies-of-spain-france-england [5vd7p4bct5].bin
[debug] Invoking http downloader on "https://embed-ssl.wistia.com/deliveries/0b5ac32a7b795b91180891d14a673f47.bin"
[download] paywall_north-american-exploration-failed-colonies-of-spain-france-england [5vd7p4bct5].bin has already been downloaded
[download] 100% of 73.77KiB
```
yt-dlp thinks the video is already downloaded because the thumbnails also share the bin extension.

Extension should probably be renamed for each format internally in the extractor. Or something that avoids the extension being bin.

### Provide verbose output that clearly demonstrates the problem

- [X] Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
- [X] Copy the WHOLE output (starting with `[debug] Command-line config`) and insert it below

### Complete Verbose Output

```shell
see above
```



Discussion:
Looking at the embed config json (e.g. https://fast.wistia.com/embed/medias/a6ndpko1wg.json), the 'original' (0) section doesn't specify an ext or any other key stating that it is a video ("Original File"). Similar situation with the thumbnail.

Possible solutions I can think of are:
1. Assume original file/thumbnail is an mp4/jpg
2. Check file type based on magic test, but I'm not sure of any modules in the Python standard library. Can we introduce a new optional dependency? Or maybe implement snippets from a standalone library like https://github.com/h2non/filetype.py/blob/master/filetype/types/image.py (MIT)?

Edit: Forgot that the 2nd option is obviously not possible in the extractor, probably possible to workaround with a personal postprocessor plugin that runs before the rest maybe, I'll have to look into that

Resolving patch:
diff --git a/yt_dlp/extractor/wistia.py b/yt_dlp/extractor/wistia.py
--- a/yt_dlp/extractor/wistia.py
+++ b/yt_dlp/extractor/wistia.py
@@ -6,12 +6,15 @@
 from .common import InfoExtractor
 from ..utils import (
     ExtractorError,
+    HEADRequest,
+    determine_ext,
     float_or_none,
     int_or_none,
     parse_qs,
     traverse_obj,
     try_get,
     update_url_query,
+    urlhandle_detect_ext,
 )
 
 
@@ -34,6 +37,16 @@ def _download_embed_config(self, config_type, config_id, referer):
 
         return embed_config
 
+    def _get_real_ext(self, url):
+        ext = determine_ext(url, default_ext='bin')
+        if ext == 'bin':
+            urlh = self._request_webpage(
+                HEADRequest(url), None, note='Checking media extension',
+                errnote='HEAD request returned error', fatal=False)
+            if urlh:
+                ext = urlhandle_detect_ext(urlh, default='bin')
+        return 'mp4' if ext == 'mov' else ext
+
     def _extract_media(self, embed_config):
         data = embed_config['media']
         video_id = data['hashedId']
@@ -51,13 +64,13 @@ def _extract_media(self, embed_config):
                 continue
             elif atype in ('still', 'still_image'):
                 thumbnails.append({
-                    'url': aurl,
+                    'url': aurl.replace('.bin', f'.{self._get_real_ext(aurl)}'),
                     'width': int_or_none(a.get('width')),
                     'height': int_or_none(a.get('height')),
                     'filesize': int_or_none(a.get('size')),
                 })
             else:
-                aext = a.get('ext')
+                aext = a.get('ext') or self._get_real_ext(aurl)
                 display_name = a.get('display_name')
                 format_id = atype
                 if atype and atype.endswith('_video') and display_name:
@@ -169,26 +182,26 @@ class WistiaIE(WistiaBaseIE):
         'md5': '10c1ce9c4dde638202513ed17a3767bd',
         'info_dict': {
             'id': 'a6ndpko1wg',
-            'ext': 'bin',
+            'ext': 'mp4',
             'title': 'Episode 2: Boxed Water\'s retention is thirsty',
             'upload_date': '20210324',
             'description': 'md5:da5994c2c2d254833b412469d9666b7a',
             'duration': 966.0,
             'timestamp': 1616614369,
-            'thumbnail': 'https://embed-ssl.wistia.com/deliveries/53dc60239348dc9b9fba3755173ea4c2.bin',
+            'thumbnail': 'https://embed-ssl.wistia.com/deliveries/53dc60239348dc9b9fba3755173ea4c2.png',
         }
     }, {
         'url': 'wistia:5vd7p4bct5',
         'md5': 'b9676d24bf30945d97060638fbfe77f0',
         'info_dict': {
             'id': '5vd7p4bct5',
-            'ext': 'bin',
+            'ext': 'mp4',
             'title': 'md5:eaa9f64c4efd7b5f098b9b6118597679',
             'description': 'md5:a9bea0315f0616aa5df2dc413ddcdd0f',
             'upload_date': '20220915',
             'timestamp': 1663258727,
             'duration': 623.019,
-            'thumbnail': r're:https?://embed(?:-ssl)?.wistia.com/.+\.(?:jpg|bin)$',
+            'thumbnail': r're:https?://embed(?:-ssl)?.wistia.com/.+\.jpg$',
         },
     }, {
         'url': 'wistia:sh7fpupwlt',
@@ -208,25 +221,25 @@ class WistiaIE(WistiaBaseIE):
         'url': 'https://www.weidert.com/blog/wistia-channels-video-marketing-tool',
         'info_dict': {
             'id': 'cqwukac3z1',
-            'ext': 'bin',
+            'ext': 'mp4',
             'title': 'How Wistia Channels Can Help Capture Inbound Value From Your Video Content',
             'duration': 158.125,
             'timestamp': 1618974400,
             'description': 'md5:27abc99a758573560be72600ef95cece',
             'upload_date': '20210421',
-            'thumbnail': 'https://embed-ssl.wistia.com/deliveries/6c551820ae950cdee2306d6cbe9ef742.bin',
+            'thumbnail': 'https://embed-ssl.wistia.com/deliveries/6c551820ae950cdee2306d6cbe9ef742.jpg',
         }
     }, {
         'url': 'https://study.com/academy/lesson/north-american-exploration-failed-colonies-of-spain-france-england.html#lesson',
         'md5': 'b9676d24bf30945d97060638fbfe77f0',
         'info_dict': {
             'id': '5vd7p4bct5',
-            'ext': 'bin',
+            'ext': 'mp4',
             'title': 'paywall_north-american-exploration-failed-colonies-of-spain-france-england',
             'upload_date': '20220915',
             'timestamp': 1663258727,
             'duration': 623.019,
-            'thumbnail': 'https://embed-ssl.wistia.com/deliveries/83e6ec693e2c05a0ce65809cbaead86a.bin',
+            'thumbnail': 'https://embed-ssl.wistia.com/deliveries/83e6ec693e2c05a0ce65809cbaead86a.jpg',
             'description': 'a Paywall Videos video',
         },
     }]
@@ -302,9 +315,9 @@ class WistiaChannelIE(WistiaBaseIE):
         'url': 'https://fast.wistia.net/embed/channel/3802iirk0l?wchannelid=3802iirk0l&wmediaid=sp5dqjzw3n',
         'info_dict': {
             'id': 'sp5dqjzw3n',
-            'ext': 'bin',
+            'ext': 'mp4',
             'title': 'The Roof S2: The Modern CRO',
-            'thumbnail': 'https://embed-ssl.wistia.com/deliveries/dadfa9233eaa505d5e0c85c23ff70741.bin',
+            'thumbnail': 'https://embed-ssl.wistia.com/deliveries/dadfa9233eaa505d5e0c85c23ff70741.png',
             'duration': 86.487,
             'description': 'A sales leader on The Roof? Man, they really must be letting anyone up here this season.\n',
             'timestamp': 1619790290,
@@ -334,12 +347,12 @@ class WistiaChannelIE(WistiaBaseIE):
         'info_dict': {
             'id': 'pz0m0l0if3',
             'title': 'A Framework for Improving Product Team Performance',
-            'ext': 'bin',
+            'ext': 'mp4',
             'timestamp': 1653935275,
             'upload_date': '20220530',
             'description': 'Learn how to help your company improve and achieve your product related goals.',
             'duration': 1854.39,
-            'thumbnail': 'https://embed-ssl.wistia.com/deliveries/12fd19e56413d9d6f04e2185c16a6f8854e25226.bin',
+            'thumbnail': 'https://embed-ssl.wistia.com/deliveries/12fd19e56413d9d6f04e2185c16a6f8854e25226.png',
         },
         'params': {'noplaylist': True, 'skip_download': True},
     }]
diff --git a/yt_dlp/utils.py b/yt_dlp/utils.py
--- a/yt_dlp/utils.py
+++ b/yt_dlp/utils.py
@@ -3480,67 +3480,93 @@ def error_to_str(err):
     return f'{type(err).__name__}: {err}'
 
 
-def mimetype2ext(mt):
-    if mt is None:
+def mimetype2ext(mt, default=NO_DEFAULT):
+    if not isinstance(mt, str):
+        if default is not NO_DEFAULT:
+            return default
         return None
 
-    mt, _, params = mt.partition(';')
-    mt = mt.strip()
-
-    FULL_MAP = {
-        'audio/mp4': 'm4a',
-        # Per RFC 3003, audio/mpeg can be .mp1, .mp2 or .mp3. Here use .mp3 as
-        # it's the most popular one
-        'audio/mpeg': 'mp3',
-        'audio/x-wav': 'wav',
-        'audio/wav': 'wav',
-        'audio/wave': 'wav',
-    }
-
-    ext = FULL_MAP.get(mt)
-    if ext is not None:
-        return ext
-
-    SUBTYPE_MAP = {
+    MAP = {
+        # video
         '3gpp': '3gp',
-        'smptett+xml': 'tt',
-        'ttaf+xml': 'dfxp',
-        'ttml+xml': 'ttml',
+        'mp2t': 'ts',
+        'mp4': 'mp4',
+        'mpeg': 'mpeg',
+        'mpegurl': 'm3u8',
+        'quicktime': 'mov',
+        'webm': 'webm',
+        'vp9': 'vp9',
         'x-flv': 'flv',
+        'x-m4v': 'm4v',
+        'x-matroska': 'mkv',
+        'x-mng': 'mng',
         'x-mp4-fragmented': 'mp4',
-        'x-ms-sami': 'sami',
+        'x-ms-asf': 'asf',
         'x-ms-wmv': 'wmv',
-        'mpegurl': 'm3u8',
-        'x-mpegurl': 'm3u8',
-        'vnd.apple.mpegurl': 'm3u8',
+        'x-msvideo': 'avi',
+
+        # application (streaming playlists)
         'dash+xml': 'mpd',
         'f4m+xml': 'f4m',
         'hds+xml': 'f4m',
+        'vnd.apple.mpegurl': 'm3u8',
         'vnd.ms-sstr+xml': 'ism',
-        'quicktime': 'mov',
-        'mp2t': 'ts',
+        'x-mpegurl': 'm3u8',
+
+        # audio
+        'audio/mp4': 'm4a',
+        # Per RFC 3003, audio/mpeg can be .mp1, .mp2 or .mp3.
+        # Using .mp3 as it's the most popular one
+        'audio/mpeg': 'mp3',
+        'audio/webm': 'weba',
+        'audio/x-matroska': 'mka',
+        'audio/x-mpegurl': 'm3u',
+        'midi': 'mid',
+        'ogg': 'ogg',
+        'wav': 'wav',
+        'wave': 'wav',
+        'x-aac': 'aac',
+        'x-flac': 'flac',
+        'x-m4a': 'm4a',
+        'x-realaudio': 'ra',
         'x-wav': 'wav',
-        'filmstrip+json': 'fs',
-        'svg+xml': 'svg',
-    }
 
-    _, _, subtype = mt.rpartition('/')
-    ext = SUBTYPE_MAP.get(subtype.lower())
-    if ext is not None:
-        return ext
+        # image
+        'avif': 'avif',
+        'bmp': 'bmp',
+        'gif': 'gif',
+        'jpeg': 'jpg',
+        'png': 'png',
+        'svg+xml': 'svg',
+        'tiff': 'tif',
+        'vnd.wap.wbmp': 'wbmp',
+        'webp': 'webp',
+        'x-icon': 'ico',
+        'x-jng': 'jng',
+        'x-ms-bmp': 'bmp',
+
+        # caption
+        'filmstrip+json': 'fs',
+        'smptett+xml': 'tt',
+        'ttaf+xml': 'dfxp',
+        'ttml+xml': 'ttml',
+        'x-ms-sami': 'sami',
 
-    SUFFIX_MAP = {
+        # misc
+        'gzip': 'gz',
         'json': 'json',
         'xml': 'xml',
         'zip': 'zip',
-        'gzip': 'gz',
     }
 
-    _, _, suffix = subtype.partition('+')
-    ext = SUFFIX_MAP.get(suffix)
-    if ext is not None:
-        return ext
+    mimetype = mt.partition(';')[0].strip().lower()
+    _, _, subtype = mimetype.rpartition('/')
 
+    ext = traverse_obj(MAP, mimetype, subtype, subtype.rsplit('+')[-1])
+    if ext:
+        return ext
+    elif default is not NO_DEFAULT:
+        return default
     return subtype.replace('+', '.')
 
 
@@ -3634,7 +3660,7 @@ def get_compatible_ext(*, vcodecs, acodecs, vexts, aexts, preferences=None):
     return 'mkv' if allow_mkv else preferences[-1]
 
 
-def urlhandle_detect_ext(url_handle):
+def urlhandle_detect_ext(url_handle, default=NO_DEFAULT):
     getheader = url_handle.headers.get
 
     cd = getheader('Content-Disposition')
@@ -3645,7 +3671,13 @@ def urlhandle_detect_ext(url_handle):
             if e:
                 return e
 
-    return mimetype2ext(getheader('Content-Type'))
+    meta_ext = getheader('x-amz-meta-name')
+    if meta_ext:
+        e = meta_ext.rpartition('.')[2]
+        if e:
+            return e
+
+    return mimetype2ext(getheader('Content-Type'), default=default)
 
 
 def encode_data_uri(data, mime_type):


Test patch: