Skip to content

Commit

Permalink
Merge pull request #42 from oskvr37/dev
Browse files Browse the repository at this point in the history
merge dev
  • Loading branch information
oskvr37 authored Nov 6, 2024
2 parents 186fc4e + d702d7c commit 306d3ba
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 13 deletions.
17 changes: 17 additions & 0 deletions scripts/sync.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import logging

from tiddl import initLogging


# TODO: implement library synchronization #35

# let user choose what to sync (tracks, albums, playlists, artists)


def main():
initLogging(colored_logging=True, silent=False, verbose=False)
logging.info("syncing library...")


if __name__ == "__main__":
main()
6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
name="tiddl",
version="1.7.0",
description="TIDDL (Tidal Downloader) is a Python CLI application that allows downloading Tidal tracks.",
long_description=open('README.md', encoding="utf-8").read(),
long_description_content_type='text/markdown',
long_description=open("README.md", encoding="utf-8").read(),
long_description_content_type="text/markdown",
readme="README.md",
author="oskvr37",
packages=find_packages(),
entry_points={
"console_scripts": ["tiddl=tiddl:main"],
"console_scripts": ["tiddl=tiddl:main", "tiddl-sync=scripts.sync:main"],
},
)
17 changes: 14 additions & 3 deletions tiddl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from .api import TidalApi
from .auth import getDeviceAuth, getToken, refreshToken
from .config import Config, HOME_DIRECTORY
from .config import Config
from .download import downloadTrackStream, Cover
from .parser import QUALITY_ARGS, parser
from .types import TRACK_QUALITY, TrackQuality, Track
Expand All @@ -17,14 +17,17 @@
setMetadata,
convertToFlac,
initLogging,
parseFileInput,
)

SAVE_COVER = True


def main():
args = parser.parse_args()
initLogging(args.silent, args.verbose, HOME_DIRECTORY, not args.no_color)
initLogging(
silent=args.silent, verbose=args.verbose, colored_logging=not args.no_color
)

logger = logging.getLogger("TIDDL")
logger.debug(args)
Expand All @@ -39,6 +42,7 @@ def main():
if args.quality
else config["settings"]["track_quality"]
)
file_extension = args.file_extension or config["settings"]["file_extension"]

if args.save_options:
logger.info("saving new settings...")
Expand All @@ -48,6 +52,7 @@ def main():
"download_path": download_path,
"track_quality": track_quality,
"track_template": track_template,
"file_extension": file_extension,
}
}
).get("settings")
Expand Down Expand Up @@ -114,6 +119,9 @@ def main():

user_inputs: list[str] = args.input

file_inputs = parseFileInput(args.input_file)
user_inputs.extend(file_inputs)

if len(user_inputs) == 0:
logger.warning("no ID nor URL provided")
return
Expand Down Expand Up @@ -171,7 +179,10 @@ def downloadTrack(
stream["manifestMimeType"],
)

track_path = convertToFlac(track_path)
if file_extension:
track_path = convertToFlac(
source_path=track_path, file_extension=file_extension
)

if not cover_data:
cover = Cover(track["album"]["cover"])
Expand Down
7 changes: 7 additions & 0 deletions tiddl/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
AlbumItems,
Playlist,
PlaylistItems,
Favorites,
)

API_URL = "https://api.tidal.com/v1"
Expand Down Expand Up @@ -87,3 +88,9 @@ def getPlaylistItems(self, uuid: str, limit=50, offset=0) -> PlaylistItems:
f"playlists/{uuid}/items",
{"countryCode": self.country_code, "limit": limit, "offset": offset},
)

def getFavorites(self) -> Favorites:
return self._request(
f"users/{self.user_id}/favorites/ids",
{"countryCode": self.country_code},
)
4 changes: 3 additions & 1 deletion tiddl/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class Settings(TypedDict, total=False):
track_template: str
album_template: str
playlist_template: str
file_extension: str


class User(TypedDict, total=False):
Expand All @@ -34,11 +35,12 @@ class ConfigData(TypedDict, total=False):
"refresh_token": "",
"token_expires_at": 0,
"settings": {
"download_path": "tidal_download",
"download_path": f"{HOME_DIRECTORY}/tidal_download",
"track_quality": "HIGH",
"track_template": "{artist}/{title}",
"album_template": "{artist}/{album}/{title}",
"playlist_template": "{playlist}/{title}",
"file_extension": ""
},
"user": {"user_id": "", "country_code": ""},
}
Expand Down
19 changes: 19 additions & 0 deletions tiddl/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ def shouldNotColor() -> bool:
dest="download_path",
)

parser.add_argument(
"-e",
type=str,
nargs="?",
const=True,
help="choose file extension",
dest="file_extension",
)

QUALITY_ARGS: dict[str, TrackQuality] = {
details["arg"]: quality for quality, details in TRACK_QUALITY.items()
}
Expand All @@ -67,6 +76,16 @@ def shouldNotColor() -> bool:
action="store_true",
)

parser.add_argument(
"-i",
type=str,
nargs="?",
const=True,
help="choose a file with urls (.txt file separated with newlines or .json list)",
dest="input_file",
default="",
)

parser.add_argument(
"--no-skip",
help="dont skip already downloaded tracks",
Expand Down
8 changes: 8 additions & 0 deletions tiddl/types/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,11 @@ class _PlaylistItem(TypedDict):

class PlaylistItems(Items):
items: List[_PlaylistItem]


class Favorites(TypedDict):
PLAYLIST: List[str]
ALBUM: List[str]
VIDEO: List[str]
TRACK: List[str]
ARTIST: List[str]
37 changes: 31 additions & 6 deletions tiddl/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import re
import os
import json
import logging
import subprocess

Expand All @@ -8,6 +9,7 @@
from mutagen.easymp4 import EasyMP4 as MutagenMP4

from .types.track import Track
from .config import HOME_DIRECTORY

RESOURCE = Literal["track", "album", "artist", "playlist"]
RESOURCE_LIST: List[RESOURCE] = list(get_args(RESOURCE))
Expand All @@ -16,6 +18,26 @@
logger = logging.getLogger("utils")


def parseFileInput(file: str) -> list[str]:
_, file_extension = os.path.splitext(file)
urls_set: set[str] = set()

if file_extension == ".txt":
with open(file) as f:
data = f.read()
urls_set.update(data.splitlines())
elif file_extension == ".json":
with open(file) as f:
data = json.load(f)
urls_set.update(data)
else:
logger.warning(f"a file with '{file_extension}' extension is not supported!")

filtered_urls = [url for url in urls_set if type(url) == str]

return filtered_urls


def parseURL(url: str) -> tuple[RESOURCE, str]:
# remove trailing slash
url = url.rstrip("/")
Expand Down Expand Up @@ -48,7 +70,7 @@ class FormattedTrack(TypedDict):
def formatFilename(template: str, track: Track, playlist=""):
artists = [artist["name"] for artist in track["artists"]]
formatted_track: FormattedTrack = {
"album": track["album"]["title"].strip(),
"album": re.sub(r'[<>:"|?*/\\]', "_", track["album"]["title"].strip()),
"artist": track["artist"]["name"].strip(),
"artists": ", ".join(artists).strip(),
"id": str(track["id"]).strip(),
Expand Down Expand Up @@ -130,16 +152,16 @@ def setMetadata(file_path: str, track: Track, cover_data=b""):
metadata.save()


def convertToFlac(source_path: str, remove_source=True):
def convertToFlac(source_path: str, file_extension: str, remove_source=True):
source_dir, source_extension = os.path.splitext(source_path)
dest_path = f"{source_dir}.flac"
dest_path = f"{source_dir}.{file_extension}"

logger.debug((source_path, source_dir, source_extension, dest_path))

if source_extension != ".m4a":
if source_extension == f".{file_extension}":
return source_path

logger.debug(f"converting `{source_path}` to FLAC")
logger.debug(f"converting `{source_path}` to `{file_extension}`")
command = ["ffmpeg", "-i", source_path, dest_path]
result = subprocess.run(
command, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
Expand Down Expand Up @@ -225,7 +247,9 @@ def __init__(self, colored=True) -> None:
self.CROSSED = ""


def initLogging(silent: bool, verbose: bool, directory: str, colored_logging=True):
def initLogging(
silent: bool, verbose: bool, directory=HOME_DIRECTORY, colored_logging=True
):
c = Colors(colored_logging)

class StreamFormatter(logging.Formatter):
Expand All @@ -243,6 +267,7 @@ def format(self, record):
return formatter.format(record) + c.RESET

stream_handler = logging.StreamHandler()

file_handler = logging.FileHandler(f"{directory}/tiddl.log", "a", "utf-8")

if silent:
Expand Down

0 comments on commit 306d3ba

Please sign in to comment.