Expanding video browser profile selection to match archive feature. Adding toggle to disable downloading missing video comments.

This commit is contained in:
Roland 2026-02-19 16:15:18 -07:00
parent 298a67159a
commit ae17152e79
No known key found for this signature in database
GPG key ID: 1BA620CE6C9A4680
4 changed files with 266 additions and 128 deletions

View file

@ -37,6 +37,31 @@ DEFAULT_DOWNLOAD_FREQUENCY = Seconds.week
REQUIRED_OPTIONS = ['name', 'directory']
# Browser profile discovery configuration.
# Maps browser identifiers to their config paths and yt-dlp names.
BROWSER_CONFIGS = {
'brave': {
'yt_dlp_name': 'brave',
'config_path': '.config/BraveSoftware/Brave-Browser',
'profile_key': 'brave_profiles',
},
'chrome': {
'yt_dlp_name': 'chrome',
'config_path': '.config/google-chrome',
'profile_key': 'chrome_profiles',
},
'chromium': {
'yt_dlp_name': 'chromium',
'config_path': '.config/chromium',
'profile_key': 'chromium_profiles',
},
'firefox': {
'yt_dlp_name': 'firefox',
'config_path': '.mozilla/firefox',
'profile_key': 'firefox_profiles',
},
}
@dataclasses.dataclass
class VideoInfoJSON:
@ -508,6 +533,7 @@ class VideoDownloaderConfigValidator:
yt_dlp_extra_args: str = ''
always_use_browser_profile: bool = False
browser_profile: str = ''
download_missing_video_comments: bool = False
def __post_init__(self):
allowed_fields = {i.name for i in dataclasses.fields(VideoDownloaderConfigYtDlpOptionsValidator)}
@ -536,6 +562,7 @@ class VideoDownloaderConfig(ConfigFile):
yt_dlp_extra_args='',
always_use_browser_profile=False,
browser_profile='',
download_missing_video_comments=False,
)
validator = VideoDownloaderConfigValidator
@ -619,6 +646,14 @@ class VideoDownloaderConfig(ConfigFile):
def always_use_browser_profile(self, value: bool):
self.update({**self._config, 'always_use_browser_profile': value})
@property
def download_missing_video_comments(self) -> bool:
return self._config['download_missing_video_comments']
@download_missing_video_comments.setter
def download_missing_video_comments(self, value: bool):
self.update({**self._config, 'download_missing_video_comments': value})
def import_config(self, file: pathlib.Path = None, send_events=False):
super().import_config(file, send_events)
self.successful_import = True
@ -1003,42 +1038,58 @@ def format_videos_destination(channel_name: str = None, channel_tag: str = None,
return videos_destination
def _discover_chromium_profiles(browser_dir: pathlib.Path, profile_list: list):
"""Discover profiles for Chromium-based browsers (Chromium, Chrome, Brave)."""
if not browser_dir.is_dir():
return
default_profile = browser_dir / 'Default'
if default_profile.is_dir():
profile_list.append(default_profile)
for profile in browser_dir.glob('Profile *'):
if profile.is_dir():
profile_list.append(profile)
def _discover_firefox_profiles(browser_dir: pathlib.Path, profile_list: list):
"""Discover profiles for Firefox using profiles.ini."""
profiles_file = browser_dir / 'profiles.ini'
if not profiles_file.is_file():
return
profiles_content = profiles_file.read_text()
default_profiles = re.findall(r'Default=(.*)', profiles_content)
for profile_path in default_profiles:
full_path = browser_dir / profile_path
if full_path.is_dir():
profile_list.append(full_path)
def get_browser_profiles(home: pathlib.Path = WROLPI_HOME) -> dict:
"""Searches the provided home directory for Chromium and Firefox profiles."""
"""Searches the provided home directory for browser profiles from all supported browsers."""
logger.debug(f'Searching for browser profiles in {home}')
profiles = dict(chromium_profiles=[], firefox_profiles=[])
profiles = {config['profile_key']: [] for config in BROWSER_CONFIGS.values()}
chromium_directory = home / '.config/chromium'
default_chromium_profile = chromium_directory / 'Default'
if default_chromium_profile.is_dir():
profiles['chromium_profiles'].append(default_chromium_profile)
chromium_profiles = chromium_directory.glob('Profile *')
for chromium_profile in chromium_profiles:
if chromium_profile.is_dir():
profiles['chromium_profiles'].append(chromium_profile)
firefox_profiles_directory = home / '.mozilla/firefox'
firefox_profiles_file = firefox_profiles_directory / 'profiles.ini'
if firefox_profiles_file.is_file():
# Read the profile.ini and get the default profile path.
firefox_profiles_file = firefox_profiles_file.read_text()
default_firefox_profile = re.findall(r'Default=(.*)', firefox_profiles_file)
if default_firefox_profile:
default_firefox_profile = firefox_profiles_directory / default_firefox_profile[0]
if default_firefox_profile.is_dir():
profiles['firefox_profiles'].append(default_firefox_profile)
for browser_key, config in BROWSER_CONFIGS.items():
browser_dir = home / config['config_path']
if browser_key == 'firefox':
_discover_firefox_profiles(browser_dir, profiles[config['profile_key']])
else:
_discover_chromium_profiles(browser_dir, profiles[config['profile_key']])
return profiles
def browser_profile_to_yt_dlp_arg(profile: pathlib.Path) -> str:
"""Takes a Path and returns a string that can be used as a yt-dlp `--cookies-from-browser` argument."""
*_, browser, profile = str(profile).split('/')
return f'{browser}:{profile}'
profile_str = str(profile)
for browser_key, config in BROWSER_CONFIGS.items():
if config['config_path'] in profile_str:
return f"{config['yt_dlp_name']}:{profile.name}"
# Fallback to original behavior for unknown browsers
*_, browser, profile_name = profile_str.split('/')
return f'{browser}:{profile_name}'
def format_video_filename(

View file

@ -371,7 +371,12 @@ async def test_videos_downloader_config_api(async_client, test_directory, test_v
@pytest.mark.parametrize('file_name,expected', [
('/home/wrolpi/.config/chromium/Default', 'chromium:Default'),
('/home/wrolpi/.config/chromium/Profile 1', 'chromium:Profile 1'),
('/home/wrolpi/.mozilla/firefox/29el0wk0.default-release', 'firefox:29el0wk0.default-release'),
('/home/wrolpi/.config/BraveSoftware/Brave-Browser/Default', 'brave:Default'),
('/home/wrolpi/.config/BraveSoftware/Brave-Browser/Profile 1', 'brave:Profile 1'),
('/home/wrolpi/.config/google-chrome/Default', 'chrome:Default'),
('/home/wrolpi/.config/google-chrome/Profile 1', 'chrome:Profile 1'),
])
def test_browser_profile_to_yt_dlp_arg(file_name, expected):
"""
@ -381,6 +386,60 @@ def test_browser_profile_to_yt_dlp_arg(file_name, expected):
assert lib.browser_profile_to_yt_dlp_arg(file_name) == expected
def test_get_browser_profiles(test_directory):
"""Test discovery of browser profiles for all supported browsers."""
# Create a mock home directory with browser profiles
home = test_directory / 'home'
# Chromium profiles
chromium_dir = home / '.config/chromium'
(chromium_dir / 'Default').mkdir(parents=True)
(chromium_dir / 'Profile 1').mkdir(parents=True)
# Chrome profiles
chrome_dir = home / '.config/google-chrome'
(chrome_dir / 'Default').mkdir(parents=True)
(chrome_dir / 'Profile 2').mkdir(parents=True)
# Brave profiles
brave_dir = home / '.config/BraveSoftware/Brave-Browser'
(brave_dir / 'Default').mkdir(parents=True)
# Firefox profiles
firefox_dir = home / '.mozilla/firefox'
firefox_dir.mkdir(parents=True)
firefox_profile_dir = firefox_dir / 'abc123.default-release'
firefox_profile_dir.mkdir()
profiles_ini = firefox_dir / 'profiles.ini'
profiles_ini.write_text('[Profile0]\nDefault=abc123.default-release\n')
profiles = lib.get_browser_profiles(home)
# Verify all browser profile keys exist
assert 'chromium_profiles' in profiles
assert 'chrome_profiles' in profiles
assert 'brave_profiles' in profiles
assert 'firefox_profiles' in profiles
# Verify Chromium profiles discovered
chromium_names = [p.name for p in profiles['chromium_profiles']]
assert 'Default' in chromium_names
assert 'Profile 1' in chromium_names
# Verify Chrome profiles discovered
chrome_names = [p.name for p in profiles['chrome_profiles']]
assert 'Default' in chrome_names
assert 'Profile 2' in chrome_names
# Verify Brave profiles discovered
brave_names = [p.name for p in profiles['brave_profiles']]
assert 'Default' in brave_names
# Verify Firefox profiles discovered
firefox_names = [p.name for p in profiles['firefox_profiles']]
assert 'abc123.default-release' in firefox_names
def test_video_location(test_session, video_factory, channel_factory, async_client):
# Video location without a channel, it opens the preview.
video1 = video_factory(title='vid1')