2024-01-13 11:57:11 +01:00
|
|
|
import glob
|
2023-12-19 23:58:57 +01:00
|
|
|
import os
|
|
|
|
|
import re
|
|
|
|
|
from collections.abc import Callable
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
def path_base():
|
|
|
|
|
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("./")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def path_file_log():
|
|
|
|
|
return os.path.join(path_base(), ".tidal-dl-ng.log")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def path_file_token():
|
|
|
|
|
return os.path.join(path_base(), ".tidal-dl-ng_token.json")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def path_file_settings():
|
|
|
|
|
return os.path.join(path_base(), ".tidal-dl-ng_settings.json")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
fn_format = get_fn_format(media)
|
|
|
|
|
|
|
|
|
|
for _matchNum, match in enumerate(matches, start=1):
|
|
|
|
|
template_str = match.group()
|
|
|
|
|
result_fmt = fn_format(match.group(1), media)
|
|
|
|
|
|
|
|
|
|
if result_fmt:
|
|
|
|
|
value = sanitize_filename(result_fmt)
|
|
|
|
|
result = result.replace(template_str, value)
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def format_str_track(name: str, media: Track) -> str | bool:
|
|
|
|
|
result: str | bool = False
|
|
|
|
|
|
|
|
|
|
if name == "track_num":
|
|
|
|
|
result = str(media.track_num).rjust(2, "0")
|
|
|
|
|
elif name == "artist_name":
|
|
|
|
|
result = ", ".join(artist.name for artist in media.artists)
|
|
|
|
|
elif name == "track_title":
|
|
|
|
|
result = media.name
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def format_str_album(name: str, media: Album) -> str | bool:
|
|
|
|
|
result: str | bool = False
|
|
|
|
|
|
|
|
|
|
if name == "album_title":
|
|
|
|
|
result = media.name
|
|
|
|
|
elif name == "artist_name":
|
|
|
|
|
result = media.artist.name
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def format_str_playlist(name: str, media: Playlist) -> str | bool:
|
|
|
|
|
result: str | bool = False
|
|
|
|
|
|
|
|
|
|
if name == "playlist_name":
|
|
|
|
|
result = media.name
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def format_str_mix(name: str, media: Mix) -> str | bool:
|
|
|
|
|
result: str | bool = False
|
|
|
|
|
|
|
|
|
|
if name == "mix_name":
|
|
|
|
|
result = media.title
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def format_str_video(name: str, media: Video) -> str | bool:
|
|
|
|
|
result: str | bool = False
|
|
|
|
|
|
|
|
|
|
if name == "artist_name":
|
|
|
|
|
result = ", ".join(artist.name for artist in media.artists)
|
|
|
|
|
elif name == "track_title":
|
|
|
|
|
result = media.name
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_fn_format(media: Track | Album | Playlist | UserPlaylist | Video | Mix) -> Callable:
|
|
|
|
|
result = None
|
|
|
|
|
|
|
|
|
|
if isinstance(media, Track):
|
|
|
|
|
result = format_str_track
|
|
|
|
|
elif isinstance(media, Album):
|
|
|
|
|
result = format_str_album
|
2024-01-12 11:20:10 +01:00
|
|
|
elif isinstance(media, Playlist | UserPlaylist):
|
2023-12-19 23:58:57 +01:00
|
|
|
result = format_str_playlist
|
|
|
|
|
elif isinstance(media, Mix):
|
|
|
|
|
result = format_str_mix
|
|
|
|
|
elif isinstance(media, Video):
|
|
|
|
|
result = format_str_video
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
2024-01-13 11:57:11 +01:00
|
|
|
def path_file_sanitize(path_file: str, adapt: bool = False) -> (bool, str):
|
|
|
|
|
# Split into path and filename
|
|
|
|
|
pathname, filename = os.path.split(path_file)
|
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:
|
2024-01-13 16:26:33 +01:00
|
|
|
pathname_sanitized = 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 = Path.home()
|
|
|
|
|
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:
|
2024-01-13 16:26:33 +01:00
|
|
|
filename_sanitized = sanitize_filename(filename, replacement_text=" ", validate_after_sanitize=True, platform="auto")
|
2024-01-13 11:57:11 +01:00
|
|
|
filename_sanitized_extension = Path(filename_sanitized).suffix
|
|
|
|
|
|
|
|
|
|
# Check if the file extension was removed by shortening the filename length
|
|
|
|
|
if filename_sanitized_extension == "":
|
|
|
|
|
# Add the original file extension
|
|
|
|
|
file_extension = "_" + Path(path_file).suffix
|
|
|
|
|
filename_sanitized = filename_sanitized[: -len(file_extension)] + file_extension
|
|
|
|
|
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 = os.path.join(pathname_sanitized, filename_sanitized)
|
2023-12-19 23:58:57 +01:00
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
2024-01-13 11:57:11 +01:00
|
|
|
def check_file_exists(path_file: str, extension_ignore: bool = False):
|
|
|
|
|
if extension_ignore:
|
|
|
|
|
path_file = Path(path_file).stem + ".*"
|
2023-12-19 23:58:57 +01:00
|
|
|
|
2024-01-13 12:04:35 +01:00
|
|
|
# TODO: Check what happens is (no) files .
|
|
|
|
|
result = bool(glob.glob(path_file))
|
2023-12-19 23:58:57 +01:00
|
|
|
|
|
|
|
|
return result
|