Repository: yt-dlp/yt-dlp

Issue:
Unexpected change to Firefox `--cookies-from-browser` default behaviour
### 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 bug unrelated to a specific site
- [X] I've verified that I'm running yt-dlp version **2022.08.19** ([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/ytdl-org/youtube-dl#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)

### Provide a description that is worded well enough to be understood

On my Firefox setup I run automatic temporary containers with a few whitelisted sites in their own containers (e.g. Youtube). This worked fine before by default with `--cookies-from-browser` (assuming I didn't have multiple different containers of the same site).

This no longer works by default in master (see log), assuming regression after https://github.com/yt-dlp/yt-dlp/commit/9bd13fe5bbe1df6bb01d4edb68f2c63a4812bf94

It appears the default behaviour has been changed from extracting all cookies from all containers to ~~just using the most recent container~~? This is wildly different behaviour and is likely to cause many issues :confused:

We should maybe look at either reverting this default or having an option to revert it (compat-opt?).

### 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

Master:
```shell
yt-dlp --cookies-from-browser firefox :ytsubs --ignore-config -v
[debug] Command-line config: ['--cookies-from-browser', 'firefox', ':ytsubs', '--ignore-config', '-v']
[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.08.19.2 [48c88e088]
[debug] Lazy loading extractors is disabled
[debug] Python 3.10.5 (CPython 64bit) - Linux-5.19.1-3-MANJARO-x86_64-with-glibc2.36 (glibc 2.36)
[debug] Checking exe version: ffmpeg -bsfs
[debug] Checking exe version: ffprobe -bsfs
[debug] exe versions: ffmpeg 5.0.1 (setts), ffprobe 5.0.1, phantomjs 2.1.1, rtmpdump 2.4
[debug] Optional libraries: Cryptodome-3.15.0, brotli-1.0.9, certifi-2022.06.15, mutagen-1.45.1, secretstorage-3.3.2, sqlite3-2.6.0, websockets-10.3
[Cookies] Extracting cookies from firefox
[debug] Extracting cookies from: "/home/coletdjnz/.mozilla/firefox/xxx-default/cookies.sqlite"
[Cookies] Extracted 23 cookies from firefox
[debug] Proxy map: {}
[debug] Loaded 1664 extractors
[debug] [youtube:subscriptions] Extracting URL: :ytsubs
[debug] [youtube:tab] Extracting URL: https://www.youtube.com/feed/subscriptions
[youtube:tab] subscriptions: Downloading webpage
[debug] [youtube:tab] Final URL: https://www.youtube.com/feed/subscriptions
[download] Downloading playlist: subscriptions
[youtube:tab] Playlist subscriptions: Downloading 0 videos of 0
[download] Finished downloading playlist: subscriptions
```

Release:
```
yt-dlp --cookies-from-browser firefox :ytsubs --ignore-config -v
[debug] Command-line config: ['--cookies-from-browser', 'firefox', ':ytsubs', '--ignore-config', '-v']
[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.08.19 [48c88e0] (pip)
[debug] Python 3.10.5 (CPython 64bit) - Linux-5.19.1-3-MANJARO-x86_64-with-glibc2.36 (glibc 2.36)
[debug] Checking exe version: ffmpeg -bsfs
[debug] Checking exe version: ffprobe -bsfs
[debug] exe versions: ffmpeg 5.0.1 (setts), ffprobe 5.0.1, phantomjs 2.1.1, rtmpdump 2.4
[debug] Optional libraries: Cryptodome-3.15.0, brotli-1.0.9, certifi-2022.06.15, mutagen-1.45.1, secretstorage-3.3.2, sqlite3-2.6.0, websockets-10.3
[Cookies] Extracting cookies from firefox
[debug] Extracting cookies from: "/home/coletdjnz/.mozilla/firefox/xxx-default/cookies.sqlite"
[Cookies] Extracted 1739 cookies from firefox
[debug] Proxy map: {}
[debug] [youtube:subscriptions] Extracting URL: :ytsubs
[debug] [youtube:tab] Extracting URL: https://www.youtube.com/feed/subscriptions
[youtube:tab] subscriptions: Downloading webpage
[debug] [youtube:tab] Final URL: https://www.youtube.com/feed/subscriptions
[download] Downloading playlist: subscriptions
[debug] [youtube:tab] Extracted SAPISID cookie
[youtube:tab] subscriptions page 1: Downloading API JSON
[youtube:tab] subscriptions page 2: Downloading API JSON
```




Discussion:
> to just using the most recent container?

By default, it is using cookies **without any container** now. I think the previous default is buggy, but if we want to, it can be reinstated. Some way to select the "no container" cookies will need to be added though

cc @bashonly 

Resolving patch:
diff --git a/yt_dlp/__init__.py b/yt_dlp/__init__.py
--- a/yt_dlp/__init__.py
+++ b/yt_dlp/__init__.py
@@ -347,23 +347,25 @@ def parse_chapters(name, value):
     # Cookies from browser
     if opts.cookiesfrombrowser:
         container = None
-        mobj = re.match(r'(?P<name>[^+:]+)(\s*\+\s*(?P<keyring>[^:]+))?(\s*:(?P<profile>.+))?', opts.cookiesfrombrowser)
+        mobj = re.fullmatch(r'''(?x)
+            (?P<name>[^+:]+)
+            (?:\s*\+\s*(?P<keyring>[^:]+))?
+            (?:\s*:\s*(?P<profile>.+?))?
+            (?:\s*::\s*(?P<container>.+))?
+        ''', opts.cookiesfrombrowser)
         if mobj is None:
             raise ValueError(f'invalid cookies from browser arguments: {opts.cookiesfrombrowser}')
-        browser_name, keyring, profile = mobj.group('name', 'keyring', 'profile')
+        browser_name, keyring, profile, container = mobj.group('name', 'keyring', 'profile', 'container')
         browser_name = browser_name.lower()
         if browser_name not in SUPPORTED_BROWSERS:
             raise ValueError(f'unsupported browser specified for cookies: "{browser_name}". '
                              f'Supported browsers are: {", ".join(sorted(SUPPORTED_BROWSERS))}')
-        elif profile and browser_name == 'firefox':
-            if ':' in profile and not os.path.exists(profile):
-                profile, container = profile.split(':', 1)
         if keyring is not None:
             keyring = keyring.upper()
             if keyring not in SUPPORTED_KEYRINGS:
                 raise ValueError(f'unsupported keyring specified for cookies: "{keyring}". '
                                  f'Supported keyrings are: {", ".join(sorted(SUPPORTED_KEYRINGS))}')
-        opts.cookiesfrombrowser = (browser_name, profile, keyring, container)
+        opts.cookiesfrombrowser = (browser_name, profile or None, keyring, container or None)
 
     # MetadataParser
     def metadataparser_actions(f):
diff --git a/yt_dlp/cookies.py b/yt_dlp/cookies.py
--- a/yt_dlp/cookies.py
+++ b/yt_dlp/cookies.py
@@ -128,9 +128,14 @@ def _extract_firefox_cookies(profile, container, logger):
     else:
         search_root = os.path.join(_firefox_browser_dir(), profile)
 
+    cookie_database_path = _find_most_recently_used_file(search_root, 'cookies.sqlite', logger)
+    if cookie_database_path is None:
+        raise FileNotFoundError(f'could not find firefox cookies database in {search_root}')
+    logger.debug(f'Extracting cookies from: "{cookie_database_path}"')
+
     container_id = None
-    if container is not None:
-        containers_path = os.path.join(search_root, 'containers.json')
+    if container not in (None, 'none'):
+        containers_path = os.path.join(os.path.dirname(cookie_database_path), 'containers.json')
         if not os.path.isfile(containers_path) or not os.access(containers_path, os.R_OK):
             raise FileNotFoundError(f'could not read containers.json in {search_root}')
         with open(containers_path, 'r') as containers:
@@ -142,26 +147,21 @@ def _extract_firefox_cookies(profile, container, logger):
         if not isinstance(container_id, int):
             raise ValueError(f'could not find firefox container "{container}" in containers.json')
 
-    cookie_database_path = _find_most_recently_used_file(search_root, 'cookies.sqlite', logger)
-    if cookie_database_path is None:
-        raise FileNotFoundError(f'could not find firefox cookies database in {search_root}')
-    logger.debug(f'Extracting cookies from: "{cookie_database_path}"')
-
     with tempfile.TemporaryDirectory(prefix='yt_dlp') as tmpdir:
         cursor = None
         try:
             cursor = _open_database_copy(cookie_database_path, tmpdir)
-            origin_attributes = ''
             if isinstance(container_id, int):
-                origin_attributes = f'^userContextId={container_id}'
                 logger.debug(
                     f'Only loading cookies from firefox container "{container}", ID {container_id}')
-            try:
                 cursor.execute(
-                    'SELECT host, name, value, path, expiry, isSecure FROM moz_cookies WHERE originAttributes=?',
-                    (origin_attributes, ))
-            except sqlite3.OperationalError:
-                logger.debug('Database exception, loading all cookies')
+                    'SELECT host, name, value, path, expiry, isSecure FROM moz_cookies WHERE originAttributes LIKE ? OR originAttributes LIKE ?',
+                    (f'%userContextId={container_id}', f'%userContextId={container_id}&%'))
+            elif container == 'none':
+                logger.debug('Only loading cookies not belonging to any container')
+                cursor.execute(
+                    'SELECT host, name, value, path, expiry, isSecure FROM moz_cookies WHERE NOT INSTR(originAttributes,"userContextId=")')
+            else:
                 cursor.execute('SELECT host, name, value, path, expiry, isSecure FROM moz_cookies')
             jar = YoutubeDLCookieJar()
             with _create_progress_bar(logger) as progress_bar:
diff --git a/yt_dlp/options.py b/yt_dlp/options.py
--- a/yt_dlp/options.py
+++ b/yt_dlp/options.py
@@ -1400,14 +1400,15 @@ def _alias_callback(option, opt_str, value, parser, opts, nargs):
         help='Do not read/dump cookies from/to file (default)')
     filesystem.add_option(
         '--cookies-from-browser',
-        dest='cookiesfrombrowser', metavar='BROWSER[+KEYRING][:PROFILE[:CONTAINER]]',
+        dest='cookiesfrombrowser', metavar='BROWSER[+KEYRING][:PROFILE][::CONTAINER]',
         help=(
-            'The name of the browser and (optionally) the name/path of the profile to load cookies from '
-            '(and container name if Firefox) separated by a ":". '
+            'The name of the browser to load cookies from. '
             f'Currently supported browsers are: {", ".join(sorted(SUPPORTED_BROWSERS))}. '
-            'By default, the default container of the most recently accessed profile is used. '
-            'The keyring used for decrypting Chromium cookies on Linux can be '
-            '(optionally) specified after the browser name separated by a "+". '
+            'Optionally, the KEYRING used for decrypting Chromium cookies on Linux, '
+            'the name/path of the PROFILE to load cookies from, '
+            'and the CONTAINER name (if Firefox) ("none" for no container) '
+            'can be given with their respective seperators. '
+            'By default, all containers of the most recently accessed profile are used. '
             f'Currently supported keyrings are: {", ".join(map(str.lower, sorted(SUPPORTED_KEYRINGS)))}'))
     filesystem.add_option(
         '--no-cookies-from-browser',


Test patch: