summaryrefslogtreecommitdiff
path: root/tidal_dl_ng/helper
diff options
context:
space:
mode:
authorexislow <exislow@users.noreply.github.com>2025-07-03 09:57:26 +0200
committerGitHub <noreply@github.com>2025-07-03 09:57:26 +0200
commit7da0d4c41e4abb6ec03ee1b0f64474ee970a230e (patch)
treea5e625565e2439ad92c590f7f9d2aeaf49824fe9 /tidal_dl_ng/helper
parent29ea5c51825628dbe96ce4404488b87d5fadb80d (diff)
parent4662d18fdfc25d42d496cf6abb1c8d44be0c76dd (diff)
downloadtidal-dl-ng-7da0d4c41e4abb6ec03ee1b0f64474ee970a230e.tar.gz
Merge pull request #478 from exislow/ai-refactoring
Diffstat (limited to 'tidal_dl_ng/helper')
-rw-r--r--tidal_dl_ng/helper/decryption.py12
-rw-r--r--tidal_dl_ng/helper/path.py581
2 files changed, 402 insertions, 191 deletions
diff --git a/tidal_dl_ng/helper/decryption.py b/tidal_dl_ng/helper/decryption.py
index 17ea19b..2f38fc5 100644
--- a/tidal_dl_ng/helper/decryption.py
+++ b/tidal_dl_ng/helper/decryption.py
@@ -7,9 +7,17 @@ from Crypto.Util import Counter
def decrypt_security_token(security_token: str) -> (str, str):
"""
- Decrypts security token into key and nonce pair
+ The `decrypt_security_token` function decrypts a security token into a key and nonce pair using AES
+ encryption.
- security_token should match the securityToken value from the web response
+ Args:
+ security_token (str): The `security_token` parameter in the `decrypt_security_token` function is a
+ string that represents an encrypted security token. This function decrypts the security token into a
+ key and nonce pair using AES encryption. security_token should match the securityToken value from the web response.
+
+ Returns:
+ The `decrypt_security_token` function returns a tuple containing the key and nonce extracted from
+ the decrypted security token.
"""
# Do not change this
diff --git a/tidal_dl_ng/helper/path.py b/tidal_dl_ng/helper/path.py
index 29347cb..12ffdce 100644
--- a/tidal_dl_ng/helper/path.py
+++ b/tidal_dl_ng/helper/path.py
@@ -24,6 +24,11 @@ from tidal_dl_ng.helper.tidal import name_builder_album_artist, name_builder_art
def path_home() -> str:
+ """Get the home directory path.
+
+ Returns:
+ str: The home directory path.
+ """
if "XDG_CONFIG_HOME" in os.environ:
return os.environ["XDG_CONFIG_HOME"]
elif "HOME" in os.environ:
@@ -35,6 +40,11 @@ def path_home() -> str:
def path_config_base() -> str:
+ """Get the base configuration path.
+
+ Returns:
+ str: The base configuration path.
+ """
# https://wiki.archlinux.org/title/XDG_Base_Directory
# X11 workaround: If user specified config path is set, do not point to "~/.config"
path_user_custom: str = os.environ.get("XDG_CONFIG_HOME", "")
@@ -45,14 +55,29 @@ def path_config_base() -> str:
def path_file_log() -> str:
+ """Get the path to the log file.
+
+ Returns:
+ str: The log file path.
+ """
return os.path.join(path_config_base(), "app.log")
def path_file_token() -> str:
+ """Get the path to the token file.
+
+ Returns:
+ str: The token file path.
+ """
return os.path.join(path_config_base(), "token.json")
def path_file_settings() -> str:
+ """Get the path to the settings file.
+
+ Returns:
+ str: The settings file path.
+ """
return os.path.join(path_config_base(), "settings.json")
@@ -91,130 +116,300 @@ def format_str_media(
list_pos: int = 0,
list_total: int = 0,
) -> str:
- result: str = name
+ """Format a string based on media attributes.
+ Args:
+ name (str): The format template name.
+ media (Track | Album | Playlist | UserPlaylist | Video | Mix): The media object.
+ album_track_num_pad_min (int): Minimum padding for track numbers. Defaults to 0.
+ list_pos (int): Position in a list. Defaults to 0.
+ list_total (int): Total items in a list. Defaults to 0.
+
+ Returns:
+ str: The formatted string.
+ """
try:
- match name:
- case "artist_name":
- if isinstance(media, Track | Video):
- if hasattr(media, "artists"):
- result = name_builder_artist(media)
- elif hasattr(media, "artist"):
- result = media.artist.name
- case "album_artist":
- result = name_builder_album_artist(media, first_only=True)
- case "album_artists":
- result = name_builder_album_artist(media)
- case "track_title":
- if isinstance(media, Track | Video):
- result = name_builder_title(media)
- case "mix_name":
- if isinstance(media, Mix):
- result = media.title
- case "playlist_name":
- if isinstance(media, Playlist | UserPlaylist):
- result = media.name
- case "album_title":
- if isinstance(media, Album):
- result = media.name
- elif isinstance(media, Track):
- result = media.album.name
- case "album_track_num":
- if isinstance(media, Track | Video):
- result = calculate_number_padding(
- album_track_num_pad_min,
- media.track_num,
- media.album.num_tracks if hasattr(media, "album") else 1,
- )
- case "album_num_tracks":
- if isinstance(media, Track | Video):
- result = str(media.album.num_tracks if hasattr(media, "album") else 1)
- case "track_id":
- if isinstance(media, Track | Video):
- result = str(media.id)
- case "playlist_id":
- if isinstance(media, Playlist):
- result = str(media.id)
- case "album_id":
- if isinstance(media, Album):
- result = str(media.id)
- elif isinstance(media, Track):
- result = str(media.album.id)
- case "track_duration_seconds":
- if isinstance(media, Track | Video):
- result = str(media.duration)
- case "track_duration_minutes":
- if isinstance(media, Track | Video):
- m, s = divmod(media.duration, 60)
- result = f"{m:01d}:{s:02d}"
- case "album_duration_seconds":
- if isinstance(media, Album):
- result = str(media.duration)
- case "album_duration_minutes":
- if isinstance(media, Album):
- m, s = divmod(media.duration, 60)
- result = f"{m:01d}:{s:02d}"
- case "playlist_duration_seconds":
- if isinstance(media, Album):
- result = str(media.duration)
- case "playlist_duration_minutes":
- if isinstance(media, Album):
- m, s = divmod(media.duration, 60)
- result = f"{m:01d}:{s:02d}"
- case "album_year":
- if isinstance(media, Album):
- result = str(media.year)
- elif isinstance(media, Track):
- result = str(media.album.year)
- case "video_quality":
- if isinstance(media, Video):
- result = media.video_quality
- case "track_quality":
- if isinstance(media, Track):
- result = ", ".join(tag for tag in media.media_metadata_tags)
- case "track_explicit":
- if isinstance(media, Track | Video):
- result = FORMAT_TEMPLATE_EXPLICIT if media.explicit else ""
- case "album_explicit":
- if isinstance(media, Album):
- result = FORMAT_TEMPLATE_EXPLICIT if media.explicit else ""
- case "album_num_volumes":
- if isinstance(media, Album):
- result = str(media.num_volumes)
- case "track_volume_num":
- if isinstance(media, Track | Video):
- result = str(media.volume_num)
- case "track_volume_num_optional":
- if isinstance(media, Track | Video):
- num_volumes: int = media.album.num_volumes if hasattr(media, "album") else 1
- result = "" if num_volumes == 1 else str(media.volume_num)
- case "track_volume_num_optional_CD":
- if isinstance(media, Track | Video):
- num_volumes: int = media.album.num_volumes if hasattr(media, "album") else 1
- result = "" if num_volumes == 1 else f"CD{media.volume_num!s}"
- case "isrc":
- if isinstance(media, Track):
- result = media.isrc
- case "list_pos":
- if isinstance(media, Track | Video):
- # TODO: Rename `album_track_num_pad_min` globally.
- result = calculate_number_padding(album_track_num_pad_min, list_pos, list_total)
+ # Try each formatter function in sequence
+ for formatter in (
+ _format_names,
+ _format_numbers,
+ _format_ids,
+ _format_durations,
+ _format_metadata,
+ _format_volumes,
+ ):
+ result = formatter(name, media, album_track_num_pad_min, list_pos, list_total)
+ if result is not None:
+ return result
except Exception as e:
# TODO: Implement better exception logging.
print(e)
- pass
+ return name
+
+
+def _format_artist_names(
+ name: str,
+ media: Track | Album | Playlist | UserPlaylist | Video | Mix,
+ *_args,
+) -> str | None:
+ """Handle artist name-related format strings.
+
+ Args:
+ name (str): The format string name to check.
+ media (Track | Album | Playlist | UserPlaylist | Video | Mix): The media object to extract artist information from.
+ *_args (Any): Additional arguments (not used).
+
+ Returns:
+ str | None: The formatted artist name or None if the format string is not artist-related.
+ """
+ if name == "artist_name" and isinstance(media, Track | Video):
+ if hasattr(media, "artists"):
+ return name_builder_artist(media)
+ elif hasattr(media, "artist"):
+ return media.artist.name
+ elif name == "album_artist":
+ return name_builder_album_artist(media, first_only=True)
+ elif name == "album_artists":
+ return name_builder_album_artist(media)
+ return None
+
+
+def _format_titles(
+ name: str,
+ media: Track | Album | Playlist | UserPlaylist | Video | Mix,
+ *_args,
+) -> str | None:
+ """Handle title-related format strings.
+
+ Args:
+ name (str): The format string name to check.
+ media (Track | Album | Playlist | UserPlaylist | Video | Mix): The media object to extract title information from.
+ *_args (Any): Additional arguments (not used).
+
+ Returns:
+ str | None: The formatted title or None if the format string is not title-related.
+ """
+ if name == "track_title" and isinstance(media, Track | Video):
+ return name_builder_title(media)
+ elif name == "mix_name" and isinstance(media, Mix):
+ return media.title
+ elif name == "playlist_name" and isinstance(media, Playlist | UserPlaylist):
+ return media.name
+ elif name == "album_title":
+ if isinstance(media, Album):
+ return media.name
+ elif isinstance(media, Track):
+ return media.album.name
+ return None
+
+
+def _format_names(
+ name: str,
+ media: Track | Album | Playlist | UserPlaylist | Video | Mix,
+ *_args,
+) -> str | None:
+ """Handle name-related format strings.
+
+ Args:
+ name (str): The format string name to check.
+ media (Track | Album | Playlist | UserPlaylist | Video | Mix): The media object to extract name information from.
+ *_args (Any): Additional arguments (not used).
+
+ Returns:
+ str | None: The formatted name or None if the format string is not name-related.
+ """
+ # First try artist name formats
+ result = _format_artist_names(name, media)
+ if result is not None:
+ return result
+
+ # Then try title formats
+ return _format_titles(name, media)
+
+
+def _format_numbers(
+ name: str,
+ media: Track | Album | Playlist | UserPlaylist | Video | Mix,
+ album_track_num_pad_min: int,
+ list_pos: int,
+ list_total: int,
+ *_args,
+) -> str | None:
+ """Handle number-related format strings.
+
+ Args:
+ name (str): The format string name to check.
+ media (Track | Album | Playlist | UserPlaylist | Video | Mix): The media object to extract number information from.
+ album_track_num_pad_min (int): Minimum padding for track numbers.
+ list_pos (int): Position in a list.
+ list_total (int): Total items in a list.
+ *_args (Any): Additional arguments (not used).
+
+ Returns:
+ str | None: The formatted number or None if the format string is not number-related.
+ """
+ if name == "album_track_num" and isinstance(media, Track | Video):
+ return calculate_number_padding(
+ album_track_num_pad_min,
+ media.track_num,
+ media.album.num_tracks if hasattr(media, "album") else 1,
+ )
+ elif name == "album_num_tracks" and isinstance(media, Track | Video):
+ return str(media.album.num_tracks if hasattr(media, "album") else 1)
+ elif name == "list_pos" and isinstance(media, Track | Video):
+ # TODO: Rename `album_track_num_pad_min` globally.
+ return calculate_number_padding(album_track_num_pad_min, list_pos, list_total)
+ return None
+
+
+def _format_ids(
+ name: str,
+ media: Track | Album | Playlist | UserPlaylist | Video | Mix,
+ *_args,
+) -> str | None:
+ """Handle ID-related format strings.
+
+ Args:
+ name (str): The format string name to check.
+ media (Track | Album | Playlist | UserPlaylist | Video | Mix): The media object to extract ID information from.
+ *_args (Any): Additional arguments (not used).
+
+ Returns:
+ str | None: The formatted ID or None if the format string is not ID-related.
+ """
+ # Handle track and playlist IDs
+ if (name == "track_id" and isinstance(media, Track | Video)) or (
+ name == "playlist_id" and isinstance(media, Playlist)
+ ):
+ return str(media.id)
+ # Handle album IDs
+ elif name == "album_id":
+ if isinstance(media, Album):
+ return str(media.id)
+ elif isinstance(media, Track):
+ return str(media.album.id)
+ # Handle ISRC
+ elif name == "isrc" and isinstance(media, Track):
+ return media.isrc
+ return None
+
+
+def _format_durations(
+ name: str,
+ media: Track | Album | Playlist | UserPlaylist | Video | Mix,
+ *_args,
+) -> str | None:
+ """Handle duration-related format strings.
+
+ Args:
+ name (str): The format string name to check.
+ media (Track | Album | Playlist | UserPlaylist | Video | Mix): The media object to extract duration information from.
+ *_args (Any): Additional arguments (not used).
+
+ Returns:
+ str | None: The formatted duration or None if the format string is not duration-related.
+ """
+ # Format track durations
+ if name == "track_duration_seconds" and isinstance(media, Track | Video):
+ return str(media.duration)
+ elif name == "track_duration_minutes" and isinstance(media, Track | Video):
+ m, s = divmod(media.duration, 60)
+ return f"{m:01d}:{s:02d}"
+
+ # Format album durations
+ elif name == "album_duration_seconds" and isinstance(media, Album):
+ return str(media.duration)
+ elif name == "album_duration_minutes" and isinstance(media, Album):
+ m, s = divmod(media.duration, 60)
+ return f"{m:01d}:{s:02d}"
+
+ # Format playlist durations
+ elif name == "playlist_duration_seconds" and isinstance(media, Album):
+ return str(media.duration)
+ elif name == "playlist_duration_minutes" and isinstance(media, Album):
+ m, s = divmod(media.duration, 60)
+ return f"{m:01d}:{s:02d}"
+
+ # Format year
+ elif name == "album_year":
+ if isinstance(media, Album):
+ return str(media.year)
+ elif isinstance(media, Track):
+ return str(media.album.year)
+ return None
+
+
+def _format_metadata(
+ name: str,
+ media: Track | Album | Playlist | UserPlaylist | Video | Mix,
+ *_args,
+) -> str | None:
+ """Handle metadata-related format strings.
+
+ Args:
+ name (str): The format string name to check.
+ media (Track | Album | Playlist | UserPlaylist | Video | Mix): The media object to extract metadata information from.
+ *_args (Any): Additional arguments (not used).
+
+ Returns:
+ str | None: The formatted metadata or None if the format string is not metadata-related.
+ """
+ if name == "video_quality" and isinstance(media, Video):
+ return media.video_quality
+ elif name == "track_quality" and isinstance(media, Track):
+ return ", ".join(tag for tag in media.media_metadata_tags if tag is not None)
+ elif (name == "track_explicit" and isinstance(media, Track | Video)) or (
+ name == "album_explicit" and isinstance(media, Album)
+ ):
+ return FORMAT_TEMPLATE_EXPLICIT if media.explicit else ""
+ return None
- return result
+
+def _format_volumes(
+ name: str,
+ media: Track | Album | Playlist | UserPlaylist | Video | Mix,
+ *_args,
+) -> str | None:
+ """Handle volume-related format strings.
+
+ Args:
+ name (str): The format string name to check.
+ media (Track | Album | Playlist | UserPlaylist | Video | Mix): The media object to extract volume information from.
+ *_args (Any): Additional arguments (not used).
+
+ Returns:
+ str | None: The formatted volume information or None if the format string is not volume-related.
+ """
+ if name == "album_num_volumes" and isinstance(media, Album):
+ return str(media.num_volumes)
+ elif name == "track_volume_num" and isinstance(media, Track | Video):
+ return str(media.volume_num)
+ elif name == "track_volume_num_optional" and isinstance(media, Track | Video):
+ num_volumes: int = media.album.num_volumes if hasattr(media, "album") else 1
+ return "" if num_volumes == 1 else str(media.volume_num)
+ elif name == "track_volume_num_optional_CD" and isinstance(media, Track | Video):
+ num_volumes: int = media.album.num_volumes if hasattr(media, "album") else 1
+ return "" if num_volumes == 1 else f"CD{media.volume_num!s}"
+ return None
def calculate_number_padding(padding_minimum: int, item_position: int, items_max: int) -> str:
+ """Calculate the padded number string for an item.
+
+ Args:
+ padding_minimum (int): Minimum number of digits for padding.
+ item_position (int): The position of the item.
+ items_max (int): The maximum number of items.
+
+ Returns:
+ str: The padded number string.
+ """
result: str
if items_max > 0:
- count_digits: int = int(math.log10(items_max)) + 1
- count_digits_computed: int = count_digits if count_digits > padding_minimum else padding_minimum
- result = str(item_position).zfill(count_digits_computed)
+ count_digits = max(int(math.log10(items_max)) + 1, padding_minimum)
+ result = str(item_position).zfill(count_digits)
else:
result = str(item_position)
@@ -224,6 +419,15 @@ def calculate_number_padding(padding_minimum: int, item_position: int, items_max
def get_format_template(
media: Track | Album | Playlist | UserPlaylist | Video | Mix | MediaType, settings
) -> str | bool:
+ """Get the format template for a given media type.
+
+ Args:
+ media (Track | Album | Playlist | UserPlaylist | Video | Mix | MediaType): The media object or type.
+ settings: The settings object containing format templates.
+
+ Returns:
+ str | bool: The format template string or False if not found.
+ """
result = False
if isinstance(media, Track) or media == MediaType.TRACK:
@@ -241,83 +445,59 @@ def get_format_template(
def path_file_sanitize(path_file: pathlib.Path, adapt: bool = False, uniquify: bool = False) -> pathlib.Path:
- sanitized_filename: str = path_file.name
- sanitized_path: pathlib.Path = path_file.parent
- result: pathlib.Path
+ """Sanitize a file path to ensure it is valid and optionally make it unique.
- # Sanitize filename and make sure it does not exceed FILENAME_LENGTH_MAX
- try:
- # sanitize_filename can shorten the file name actually
- sanitized_filename = sanitize_filename(
- sanitized_filename, replacement_text="_", validate_after_sanitize=True, platform="auto"
- )
+ Args:
+ path_file (pathlib.Path): The file path to sanitize.
+ adapt (bool, optional): Whether to adapt the path in case of errors. Defaults to False.
+ uniquify (bool, optional): Whether to make the file name unique. Defaults to False.
- # Check if the file extension was removed by shortening the filename length
- if not sanitized_filename.endswith(path_file.suffix):
- # Add the original file extension
- file_suffix: str = FILENAME_SANITIZE_PLACEHOLDER + path_file.suffix
- sanitized_filename = sanitized_filename[: -len(file_suffix)] + file_suffix
- except ValidationError as e:
- if adapt:
- # TODO: Implement proper exception handling and logging.
- # Hacky stuff, since the sanitizing function does not shorten the filename (filename too long)
- if str(e).startswith("[PV1101]"):
- byte_ct: int = len(sanitized_filename.encode(sys.getfilesystemencoding())) - FILENAME_LENGTH_MAX
- sanitized_filename = (
- sanitized_filename[: -byte_ct - len(FILENAME_SANITIZE_PLACEHOLDER) - len(path_file.suffix)]
- + FILENAME_SANITIZE_PLACEHOLDER
- + path_file.suffix
- )
- else:
- raise
- else:
- raise
+ Returns:
+ pathlib.Path: The sanitized file path.
+ """
+ sanitized_filename = sanitize_filename(
+ path_file.name, replacement_text="_", validate_after_sanitize=True, platform="auto"
+ )
- # Sanitize the path.
- # First sanitize sanitize each part of the path. Each part of the path is not allowed to be longer then 'PC_NAME_MAX'.
- sanitized_parts = []
+ if not sanitized_filename.endswith(path_file.suffix):
+ sanitized_filename = (
+ sanitized_filename[: -len(path_file.suffix)] + FILENAME_SANITIZE_PLACEHOLDER + path_file.suffix
+ )
- for part in sanitized_path.parts:
- if part in sanitized_path.anchor:
- sanitized_parts.append(part)
- else:
- sanitized_parts.append(
+ sanitized_path = pathlib.Path(
+ *[
+ (
sanitize_filename(part, replacement_text="_", validate_after_sanitize=True, platform="auto")
+ if part not in path_file.anchor
+ else part
)
+ for part in path_file.parent.parts
+ ]
+ )
- sanitized_path = pathlib.Path(*sanitized_parts)
-
- # Then sanitize the whole path itself. The whole path is not allowed to be longer than 'PC_NAME_MAX'.
try:
- sanitized_path: pathlib.Path = sanitize_filepath(
+ sanitized_path = sanitize_filepath(
sanitized_path, replacement_text="_", validate_after_sanitize=True, platform="auto"
)
except ValidationError as e:
- # If adaption of path is allowed in case of an error set path to HOME.
- if adapt:
- if str(e).startswith("[PV1101]"):
- sanitized_path = pathlib.Path.home()
- else:
- raise
+ if adapt and str(e).startswith("[PV1101]"):
+ sanitized_path = pathlib.Path.home()
else:
raise
result = sanitized_path / sanitized_filename
- # Uniquify
- if uniquify:
- result = path_file_uniquify(result)
-
- return result
+ return path_file_uniquify(result) if uniquify else result
def path_file_uniquify(path_file: pathlib.Path) -> pathlib.Path:
- """Checks whether the file exists, if so it tries to return an unique name suffix.
+ """Ensure a file path is unique by appending a suffix if necessary.
+
+ Args:
+ path_file (pathlib.Path): The file path to uniquify.
- :param path_file: Path to file name which shall be unique.
- :type path_file: pathlib.Path
- :return: Unique file name with path for given input.
- :rtype: pathlib.Path
+ Returns:
+ pathlib.Path: The unique file path.
"""
unique_suffix: str = file_unique_suffix(path_file)
@@ -333,7 +513,16 @@ def path_file_uniquify(path_file: pathlib.Path) -> pathlib.Path:
return path_file
-def file_unique_suffix(path_file: pathlib.Path, seperator: str = "_") -> str:
+def file_unique_suffix(path_file: pathlib.Path, separator: str = "_") -> str:
+ """Generate a unique suffix for a file path.
+
+ Args:
+ path_file (pathlib.Path): The file path to check for uniqueness.
+ separator (str, optional): The separator to use for the suffix. Defaults to "_".
+
+ Returns:
+ str: The unique suffix, or an empty string if not needed.
+ """
threshold_zfill: int = len(str(UNIQUIFY_THRESHOLD))
count: int = 0
path_file_tmp: pathlib.Path = deepcopy(path_file)
@@ -341,46 +530,60 @@ def file_unique_suffix(path_file: pathlib.Path, seperator: str = "_") -> str:
while check_file_exists(path_file_tmp) and count < UNIQUIFY_THRESHOLD:
count += 1
- unique_suffix = seperator + str(count).zfill(threshold_zfill)
+ unique_suffix = separator + str(count).zfill(threshold_zfill)
path_file_tmp = path_file.parent / (path_file.stem + unique_suffix + path_file.suffix)
return unique_suffix
def check_file_exists(path_file: pathlib.Path, extension_ignore: bool = False) -> bool:
+ """Check if a file exists.
+
+ Args:
+ path_file (pathlib.Path): The file path to check.
+ extension_ignore (bool, optional): Whether to ignore the file extension. Defaults to False.
+
+ Returns:
+ bool: True if the file exists, False otherwise.
+ """
if extension_ignore:
path_file_stem: str = pathlib.Path(path_file).stem
path_parent: pathlib.Path = pathlib.Path(path_file).parent
- path_files: [str] = []
+ path_files: list[str] = []
- for extension in AudioExtensions:
- path_files.append(str(path_parent.joinpath(path_file_stem + extension)))
+ path_files.extend(str(path_parent.joinpath(path_file_stem + extension)) for extension in AudioExtensions)
else:
- path_files: [str] = [path_file]
+ path_files: list[str] = [str(path_file)]
- result = bool(sum([[True] if os.path.isfile(_file) else [] for _file in path_files], []))
+ return any(os.path.isfile(_file) for _file in path_files)
- return result
+def resource_path(relative_path: str) -> str:
+ """Get the absolute path to a resource.
-def resource_path(relative_path):
- try:
- base_path = sys._MEIPASS
- except Exception:
- base_path = os.path.abspath(".")
+ Args:
+ relative_path: The relative path to the resource.
+
+ Returns:
+ str: The absolute path to the resource.
+ """
+ # PyInstaller creates a temp folder and stores path in _MEIPASS
+ base_path = getattr(sys, "_MEIPASS", os.path.abspath("."))
return os.path.join(base_path, relative_path)
def url_to_filename(url: str) -> str:
- """Return basename corresponding to url.
- >>> print(url_to_filename('http://example.com/path/to/file%C3%80?opt=1'))
- fileƀ
- >>> print(url_to_filename('http://example.com/slash%2fname')) # '/' in name
- Taken from https://gist.github.com/zed/c2168b9c52b032b5fb7d
- Traceback (most recent call last):
- ...
- ValueError
+ """Convert a URL to a valid filename.
+
+ Args:
+ url (str): The URL to convert.
+
+ Returns:
+ str: The corresponding filename.
+
+ Raises:
+ ValueError: If the URL contains invalid characters for a filename.
"""
urlpath: str = urlsplit(url).path
basename: str = posixpath.basename(unquote(urlpath))