Files
tidal-dl/tidal_dl_ng/helper/path.py
T

264 lines
9.6 KiB
Python
Raw Normal View History

2024-01-13 11:57:11 +01:00
import glob
import math
2023-12-19 23:58:57 +01:00
import os
import re
from pathlib import Path, PosixPath
2023-12-19 23:58:57 +01:00
2024-01-13 11:57:11 +01:00
from pathvalidate import sanitize_filename, sanitize_filepath
from pathvalidate.error import ValidationError
2024-01-12 08:15:54 +01:00
from tidalapi import Album, Mix, Playlist, Track, UserPlaylist, Video
2023-12-19 23:58:57 +01:00
2024-01-14 23:09:43 +01:00
from tidal_dl_ng import __name_display__
from tidal_dl_ng.constants import FILENAME_SANITIZE_PLACEHOLDER, UNIQUIFY_THRESHOLD, AudioExtensions, MediaType
from tidal_dl_ng.helper.tidal import name_builder_artist, name_builder_title, name_builder_album_artist
2023-12-19 23:58:57 +01:00
2024-01-14 23:09:43 +01:00
def path_home() -> str:
2023-12-19 23:58:57 +01:00
if "XDG_CONFIG_HOME" in os.environ:
return os.environ["XDG_CONFIG_HOME"]
elif "HOME" in os.environ:
return os.environ["HOME"]
elif "HOMEDRIVE" in os.environ and "HOMEPATH" in os.environ:
return os.path.join(os.environ["HOMEDRIVE"], os.environ["HOMEPATH"])
else:
return os.path.abspath("./")
2024-01-23 09:51:59 +01:00
def path_config_base() -> str:
2024-01-14 23:09:43 +01:00
path_config: str = ".config"
path_base: str = os.path.join(path_home(), path_config, __name_display__)
return path_base
2023-12-19 23:58:57 +01:00
2024-01-14 23:09:43 +01:00
def path_file_log() -> str:
# TODO: Remove this soon. Only for migration to new dir.
old = os.path.join(path_home(), ".tidal-dl-ng.log")
if os.path.isfile(old):
2024-01-23 09:51:59 +01:00
os.makedirs(path_config_base(), exist_ok=True)
os.rename(old, os.path.join(path_config_base(), "app.log"))
2024-01-14 23:09:43 +01:00
2024-01-23 09:51:59 +01:00
return os.path.join(path_config_base(), "app.log")
2023-12-19 23:58:57 +01:00
2024-01-14 23:09:43 +01:00
def path_file_token() -> str:
# TODO: Remove this soon. Only for migration to new dir.
old = os.path.join(path_home(), ".tidal-dl-ng_token.json")
if os.path.isfile(old):
2024-01-23 09:51:59 +01:00
os.makedirs(path_config_base(), exist_ok=True)
os.rename(old, os.path.join(path_config_base(), "token.json"))
2024-01-14 23:09:43 +01:00
2024-01-23 09:51:59 +01:00
return os.path.join(path_config_base(), "token.json")
2024-01-14 23:09:43 +01:00
def path_file_settings() -> str:
# TODO: Remove this soon. Only for migration to new dir.
old = os.path.join(path_home(), ".tidal-dl-ng_settings.json")
if os.path.isfile(old):
2024-01-23 09:51:59 +01:00
os.makedirs(path_config_base(), exist_ok=True)
os.rename(old, os.path.join(path_config_base(), "settings.json"))
2024-01-14 23:09:43 +01:00
2024-01-23 09:51:59 +01:00
return os.path.join(path_config_base(), "settings.json")
2023-12-19 23:58:57 +01:00
def format_path_media(fmt_template: str, media: Track | Album | Playlist | UserPlaylist | Video | Mix) -> str:
result = fmt_template
# Search track format template for placeholder.
regex = r"\{(.+?)\}"
matches = re.finditer(regex, fmt_template, re.MULTILINE)
for _matchNum, match in enumerate(matches, start=1):
template_str = match.group()
result_fmt = format_str_media(match.group(1), media)
2023-12-19 23:58:57 +01:00
if result_fmt != match.group(1):
2023-12-19 23:58:57 +01:00
value = sanitize_filename(result_fmt)
result = result.replace(template_str, value)
return result
def format_str_media(name: str, media: Track | Album | Playlist | UserPlaylist | Video | Mix) -> str:
result: str = name
2023-12-19 23:58:57 +01:00
try:
if name == "artist_name":
if hasattr(media, "artists"):
2024-03-14 06:30:47 +01:00
result = name_builder_artist(media)
elif hasattr(media, "artist"):
result = media.artist.name
elif name == "album_artist":
result = name_builder_album_artist(media)
elif name == "track_title":
if isinstance(media, Track | Video):
2024-03-14 06:30:47 +01:00
result = name_builder_title(media)
elif name == "mix_name":
if isinstance(media, Mix):
result = media.title
elif name == "playlist_name":
if isinstance(media, Playlist | UserPlaylist):
result = media.name
elif name == "album_title":
if isinstance(media, Album):
result = media.name
elif isinstance(media, Track):
result = media.album.name
elif name == "album_track_num":
if isinstance(media, Track | Video):
num_tracks: int = media.album.num_tracks if hasattr(media, "album") else 1
count_digits: int = int(math.log10(num_tracks)) + 1
result = str(media.track_num).zfill(count_digits)
elif name == "album_num_tracks":
if isinstance(media, Track | Video):
result = str(media.album.num_tracks if hasattr(media, "album") else 1)
elif name == "track_id":
if isinstance(media, Track | Video):
result = media.id
elif name == "playlist_id":
if isinstance(media, Playlist):
result = media.id
elif name == "track_duration_seconds":
if isinstance(media, Track | Video):
result = str(media.duration)
elif name == "track_duration_minutes":
if isinstance(media, Track | Video):
m, s = divmod(media.duration, 60)
result = f"{m:01d}:{s:02d}"
elif name == "album_duration_seconds":
if isinstance(media, Album):
result = str(media.duration)
elif name == "album_duration_minutes":
if isinstance(media, Album):
m, s = divmod(media.duration, 60)
result = f"{m:01d}:{s:02d}"
elif name == "playlist_duration_seconds":
if isinstance(media, Album):
result = str(media.duration)
elif name == "playlist_duration_minutes":
if isinstance(media, Album):
m, s = divmod(media.duration, 60)
result = f"{m:01d}:{s:02d}"
elif name == "album_year":
if isinstance(media, Album):
result = str(media.release_date.year)
elif name == "video_quality":
if isinstance(media, Video):
result = media.video_quality
elif name == "track_quality":
if isinstance(media, Track):
result = ", ".join(tag for tag in media.media_metadata_tags)
except Exception as e:
# TODO: Implement better exception logging.
print(e)
pass
2023-12-19 23:58:57 +01:00
return result
2024-01-14 23:09:43 +01:00
def get_format_template(
media: Track | Album | Playlist | UserPlaylist | Video | Mix | MediaType, settings
) -> str | bool:
result = False
2024-01-16 06:33:23 +01:00
if isinstance(media, Track) or media == MediaType.TRACK:
2024-01-14 23:09:43 +01:00
result = settings.data.format_track
2024-01-16 06:33:23 +01:00
elif isinstance(media, Album) or media == MediaType.ALBUM:
2024-01-14 23:09:43 +01:00
result = settings.data.format_album
2024-01-16 06:33:23 +01:00
elif isinstance(media, Playlist | UserPlaylist) or media == MediaType.PLAYLIST:
2024-01-14 23:09:43 +01:00
result = settings.data.format_playlist
2024-01-16 06:33:23 +01:00
elif isinstance(media, Mix) or media == MediaType.MIX:
2024-01-14 23:09:43 +01:00
result = settings.data.format_mix
2024-01-16 06:33:23 +01:00
elif isinstance(media, Video) or media == MediaType.VIDEO:
2024-01-14 23:09:43 +01:00
result = settings.data.format_video
return result
def path_file_sanitize(path_file: str, adapt: bool = False, uniquify: bool = False) -> (bool, str):
2024-01-13 11:57:11 +01:00
# Split into path and filename
pathname, filename = os.path.split(path_file)
file_extension: str = Path(path_file).suffix
2023-12-19 23:58:57 +01:00
2024-01-13 11:57:11 +01:00
# Sanitize path
2023-12-19 23:58:57 +01:00
try:
pathname_sanitized: str = sanitize_filepath(
pathname, replacement_text=" ", validate_after_sanitize=True, platform="auto"
)
2024-01-13 12:04:35 +01:00
except ValidationError:
2024-01-13 11:57:11 +01:00
# If adaption of path is allowed in case of an error set path to HOME.
if adapt:
pathname_sanitized: str = Path.home()
2024-01-13 11:57:11 +01:00
else:
2024-01-13 12:04:35 +01:00
raise
2023-12-19 23:58:57 +01:00
2024-01-13 11:57:11 +01:00
# Sanitize filename
2023-12-19 23:58:57 +01:00
try:
filename_sanitized: str = sanitize_filename(
filename, replacement_text=" ", validate_after_sanitize=True, platform="auto"
)
2024-01-13 11:57:11 +01:00
# Check if the file extension was removed by shortening the filename length
if not filename_sanitized.endswith(file_extension):
2024-01-13 11:57:11 +01:00
# Add the original file extension
file_suffix: str = FILENAME_SANITIZE_PLACEHOLDER + file_extension
filename_sanitized = filename_sanitized[: -len(file_suffix)] + file_suffix
2024-01-13 11:57:11 +01:00
except ValidationError as e:
2024-01-13 12:04:35 +01:00
# TODO: Implement proper exception handling and logging.
print(e)
raise
2024-01-13 11:57:11 +01:00
# Join path and filename
result: str = os.path.join(pathname_sanitized, filename_sanitized)
# Uniquify
if uniquify:
unique_suffix: str = file_unique_suffix(result)
if unique_suffix:
file_suffix = unique_suffix + file_extension
# For most OS filename has a character limit of 255.
filename_sanitized = (
filename_sanitized[: -len(file_suffix)] + file_suffix
if len(filename_sanitized + unique_suffix) > 255
else filename_sanitized[: -len(file_extension)] + file_suffix
)
# Join path and filename
result = os.path.join(pathname_sanitized, filename_sanitized)
2023-12-19 23:58:57 +01:00
return result
def file_unique_suffix(path_file: str, seperator: str = "_") -> str:
threshold_zfill: int = len(str(UNIQUIFY_THRESHOLD))
count: int = 0
path_file_tmp: str = path_file
unique_suffix: str = ""
while check_file_exists(path_file_tmp) and count < UNIQUIFY_THRESHOLD:
count += 1
unique_suffix = seperator + str(count).zfill(threshold_zfill)
filename, file_extension = os.path.splitext(path_file_tmp)
path_file_tmp = filename + unique_suffix + file_extension
return unique_suffix
def check_file_exists(path_file: str, extension_ignore: bool = False) -> bool:
2024-01-13 11:57:11 +01:00
if extension_ignore:
path_file_stem: str = Path(path_file).stem
path_parent: PosixPath = Path(path_file).parent
path_files: [str] = []
2023-12-19 23:58:57 +01:00
for extension in AudioExtensions:
path_files.append(str(path_parent.joinpath(path_file_stem + extension.value)))
else:
path_files: [str] = [path_file]
result = bool(sum([glob.glob(_file) for _file in path_files], []))
2023-12-19 23:58:57 +01:00
return result