tidaler 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tidaler/__init__.py +145 -0
- tidaler/api.py +121 -0
- tidaler/cli.py +626 -0
- tidaler/config.py +293 -0
- tidaler/constants.py +102 -0
- tidaler/dialog.py +360 -0
- tidaler/download.py +1797 -0
- tidaler/gui.py +2626 -0
- tidaler/helper/__init__.py +0 -0
- tidaler/helper/camelot.py +395 -0
- tidaler/helper/cli.py +45 -0
- tidaler/helper/decorator.py +22 -0
- tidaler/helper/decryption.py +63 -0
- tidaler/helper/exceptions.py +14 -0
- tidaler/helper/gui.py +225 -0
- tidaler/helper/path.py +691 -0
- tidaler/helper/tidal.py +278 -0
- tidaler/helper/wrapper.py +26 -0
- tidaler/logger.py +65 -0
- tidaler/metadata.py +226 -0
- tidaler/model/__init__.py +0 -0
- tidaler/model/cfg.py +147 -0
- tidaler/model/downloader.py +24 -0
- tidaler/model/gui_data.py +50 -0
- tidaler/model/meta.py +14 -0
- tidaler/ui/__init__.py +0 -0
- tidaler/ui/default_album_image.png +0 -0
- tidaler/ui/dialog_login.py +119 -0
- tidaler/ui/dialog_login.ui +195 -0
- tidaler/ui/dialog_settings.py +660 -0
- tidaler/ui/dialog_settings.ui +846 -0
- tidaler/ui/dialog_version.py +161 -0
- tidaler/ui/dialog_version.ui +227 -0
- tidaler/ui/dummy_register.py +33 -0
- tidaler/ui/dummy_wiggly.py +68 -0
- tidaler/ui/icon.icns +0 -0
- tidaler/ui/icon.ico +0 -0
- tidaler/ui/icon16.png +0 -0
- tidaler/ui/icon256.png +0 -0
- tidaler/ui/icon32.png +0 -0
- tidaler/ui/icon48.png +0 -0
- tidaler/ui/icon512.png +0 -0
- tidaler/ui/icon64.png +0 -0
- tidaler/ui/main.py +607 -0
- tidaler/ui/main.ui +823 -0
- tidaler/ui/spinner.py +221 -0
- tidaler/worker.py +34 -0
- tidaler-0.1.0.dist-info/METADATA +256 -0
- tidaler-0.1.0.dist-info/RECORD +52 -0
- tidaler-0.1.0.dist-info/WHEEL +4 -0
- tidaler-0.1.0.dist-info/entry_points.txt +6 -0
- tidaler-0.1.0.dist-info/licenses/LICENSE +661 -0
tidaler/__init__.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
import importlib.metadata
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from urllib.parse import urlparse
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
import toml
|
|
8
|
+
|
|
9
|
+
from tidaler.constants import REQUESTS_TIMEOUT_SEC
|
|
10
|
+
from tidaler.model.meta import ProjectInformation, ReleaseLatest
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def metadata_project() -> ProjectInformation:
|
|
14
|
+
result: ProjectInformation
|
|
15
|
+
file_path: Path = Path(__file__)
|
|
16
|
+
tmp_result: dict = {}
|
|
17
|
+
|
|
18
|
+
paths: list[Path] = [
|
|
19
|
+
file_path.parent,
|
|
20
|
+
file_path.parent.parent,
|
|
21
|
+
file_path.parent.parent.parent,
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
for pyproject_toml_dir in paths:
|
|
25
|
+
pyproject_toml_file: Path = pyproject_toml_dir / "pyproject.toml"
|
|
26
|
+
|
|
27
|
+
if pyproject_toml_file.is_file():
|
|
28
|
+
tmp_result = toml.load(pyproject_toml_file)
|
|
29
|
+
|
|
30
|
+
break
|
|
31
|
+
|
|
32
|
+
if tmp_result:
|
|
33
|
+
result = ProjectInformation(
|
|
34
|
+
version=tmp_result["project"]["version"], repository_url=tmp_result["project"]["urls"]["repository"]
|
|
35
|
+
)
|
|
36
|
+
else:
|
|
37
|
+
try:
|
|
38
|
+
meta_info = importlib.metadata.metadata(name_package())
|
|
39
|
+
repo_url = meta_info["Home-page"]
|
|
40
|
+
|
|
41
|
+
if not repo_url:
|
|
42
|
+
urls = meta_info.get_all("Project-URL")
|
|
43
|
+
# attempt to parse, else use hardcoded fallback
|
|
44
|
+
repo_url = next(
|
|
45
|
+
(url.split(", ")[1] for url in urls if url.startswith("Repository")),
|
|
46
|
+
"https://github.com/exislow/tidal-dl-ng",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
result = ProjectInformation(version=meta_info["Version"], repository_url=repo_url)
|
|
50
|
+
except Exception:
|
|
51
|
+
result = ProjectInformation(version="0.0.0", repository_url="https://anerroroccur.ed/sorry/for/that")
|
|
52
|
+
|
|
53
|
+
return result
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def version_app() -> str:
|
|
57
|
+
metadata: ProjectInformation = metadata_project()
|
|
58
|
+
version: str = metadata.version
|
|
59
|
+
|
|
60
|
+
return version
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def repository_url() -> str:
|
|
64
|
+
metadata: ProjectInformation = metadata_project()
|
|
65
|
+
url_repo: str = metadata.repository_url
|
|
66
|
+
|
|
67
|
+
return url_repo
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def repository_path() -> str:
|
|
71
|
+
url_repo: str = repository_url()
|
|
72
|
+
url_path: str = urlparse(url_repo).path
|
|
73
|
+
|
|
74
|
+
return url_path
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def latest_version_information() -> ReleaseLatest:
|
|
78
|
+
release_info: ReleaseLatest
|
|
79
|
+
repo_path: str = repository_path()
|
|
80
|
+
url: str = f"https://api.github.com/repos{repo_path}/releases/latest"
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
response = requests.get(url, timeout=REQUESTS_TIMEOUT_SEC)
|
|
84
|
+
response.raise_for_status()
|
|
85
|
+
|
|
86
|
+
release_info_json: dict = response.json()
|
|
87
|
+
|
|
88
|
+
release_info = ReleaseLatest(
|
|
89
|
+
version=release_info_json["tag_name"],
|
|
90
|
+
url=release_info_json["html_url"],
|
|
91
|
+
release_info=release_info_json["body"],
|
|
92
|
+
)
|
|
93
|
+
except (requests.RequestException, KeyError, ValueError):
|
|
94
|
+
release_info = ReleaseLatest(
|
|
95
|
+
version="v0.0.0",
|
|
96
|
+
url=url,
|
|
97
|
+
release_info=f"Something went wrong calling {url}. Check your internet connection.",
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
return release_info
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def name_package() -> str:
|
|
104
|
+
package_name: str = __package__ or __name__
|
|
105
|
+
|
|
106
|
+
return package_name
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def is_dev_env() -> bool:
|
|
110
|
+
package_name: str = name_package()
|
|
111
|
+
result: bool = False
|
|
112
|
+
|
|
113
|
+
# Check if package is running from source code == dev mode
|
|
114
|
+
# If package is not running in Nuitka environment, try to import it from pip libraries.
|
|
115
|
+
# If this also fails, it is dev mode.
|
|
116
|
+
if "__compiled__" not in globals():
|
|
117
|
+
try:
|
|
118
|
+
importlib.metadata.version(package_name)
|
|
119
|
+
except Exception:
|
|
120
|
+
# If package is not installed
|
|
121
|
+
result = True
|
|
122
|
+
|
|
123
|
+
return result
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def name_app() -> str:
|
|
127
|
+
app_name: str = name_package()
|
|
128
|
+
is_dev: bool = is_dev_env()
|
|
129
|
+
|
|
130
|
+
if is_dev:
|
|
131
|
+
app_name += "-dev"
|
|
132
|
+
|
|
133
|
+
return app_name
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
__name_display__ = name_app()
|
|
137
|
+
__version__ = version_app()
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def update_available() -> tuple[bool, ReleaseLatest]:
|
|
141
|
+
latest_info: ReleaseLatest = latest_version_information()
|
|
142
|
+
version_current: str = f"v{__version__}"
|
|
143
|
+
|
|
144
|
+
result = version_current not in [latest_info.version, "v0.0.0"]
|
|
145
|
+
return result, latest_info
|
tidaler/api.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
|
|
5
|
+
# See also
|
|
6
|
+
# https://github.com/yaronzz/Tidal-Media-Downloader/commit/1d5b8cd8f65fd1def45d6406778248249d6dfbdf
|
|
7
|
+
# https://github.com/yaronzz/Tidal-Media-Downloader/pull/840
|
|
8
|
+
# https://github.com/nathom/streamrip/tree/main/streamrip
|
|
9
|
+
# https://github.com/arnesongit/plugin.audio.tidal2/blob/e9429d601d0c303d775d05a19a66415b57479d87/resources/lib/tidal2/tidalapi/__init__.py#L86
|
|
10
|
+
|
|
11
|
+
# TODO: Implement this into `Download`: Session should randomize the usage.
|
|
12
|
+
__KEYS_JSON__ = """
|
|
13
|
+
{
|
|
14
|
+
"version": "1.0.1",
|
|
15
|
+
"keys": [
|
|
16
|
+
// Invalid
|
|
17
|
+
{
|
|
18
|
+
"platform": "Fire TV",
|
|
19
|
+
"formats": "Normal/High/HiFi(No Master)",
|
|
20
|
+
"clientId": "OmDtrzFgyVVL6uW56OnFA2COiabqm",
|
|
21
|
+
"clientSecret": "zxen1r3pO0hgtOC7j6twMo9UAqngGrmRiWpV7QC1zJ8=",
|
|
22
|
+
"valid": "False",
|
|
23
|
+
"from": "Fokka-Engineering (https://github.com/Fokka-Engineering/libopenTIDAL/blob/655528e26e4f3ee2c426c06ea5b8440cf27abc4a/README.md#example)"
|
|
24
|
+
},
|
|
25
|
+
// Only max MQA.
|
|
26
|
+
{
|
|
27
|
+
"platform": "Fire TV",
|
|
28
|
+
"formats": "Master-Only(Else Error)",
|
|
29
|
+
"clientId": "7m7Ap0JC9j1cOM3n",
|
|
30
|
+
"clientSecret": "vRAdA108tlvkJpTsGZS8rGZ7xTlbJ0qaZ2K9saEzsgY=",
|
|
31
|
+
"valid": "True",
|
|
32
|
+
"from": "Dniel97 (https://github.com/Dniel97/RedSea/blob/4ba02b88cee33aeb735725cb854be6c66ff372d4/config/settings.example.py#L68)"
|
|
33
|
+
},
|
|
34
|
+
// Invalid
|
|
35
|
+
{
|
|
36
|
+
"platform": "Android TV",
|
|
37
|
+
"formats": "Normal/High/HiFi(No Master)",
|
|
38
|
+
"clientId": "Pzd0ExNVHkyZLiYN",
|
|
39
|
+
"clientSecret": "W7X6UvBaho+XOi1MUeCX6ewv2zTdSOV3Y7qC3p3675I=",
|
|
40
|
+
"valid": "False",
|
|
41
|
+
"from": ""
|
|
42
|
+
},
|
|
43
|
+
// Invalid
|
|
44
|
+
{
|
|
45
|
+
"platform": "TV",
|
|
46
|
+
"formats": "Normal/High/HiFi/Master",
|
|
47
|
+
"clientId": "8SEZWa4J1NVC5U5Y",
|
|
48
|
+
"clientSecret": "owUYDkxddz+9FpvGX24DlxECNtFEMBxipU0lBfrbq60=",
|
|
49
|
+
"valid": "False",
|
|
50
|
+
"from": "morguldir (https://github.com/morguldir/python-tidal/commit/50f1afcd2079efb2b4cf694ef5a7d67fdf619d09)"
|
|
51
|
+
},
|
|
52
|
+
// Invalid
|
|
53
|
+
{
|
|
54
|
+
"platform": "Android Auto",
|
|
55
|
+
"formats": "Normal/High/HiFi/Master",
|
|
56
|
+
"clientId": "zU4XHVVkc2tDPo4t",
|
|
57
|
+
"clientSecret": "VJKhDFqJPqvsPVNBV6ukXTJmwlvbttP7wlMlrc72se4=",
|
|
58
|
+
"valid": "True",
|
|
59
|
+
"from": "1nikolas (https://github.com/yaronzz/Tidal-Media-Downloader/pull/840)"
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
"""
|
|
64
|
+
__API_KEYS__ = json.loads(__KEYS_JSON__)
|
|
65
|
+
__ERROR_KEY__ = (
|
|
66
|
+
{
|
|
67
|
+
"platform": "None",
|
|
68
|
+
"formats": "",
|
|
69
|
+
"clientId": "",
|
|
70
|
+
"clientSecret": "",
|
|
71
|
+
"valid": "False",
|
|
72
|
+
},
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
from tidaler.constants import REQUESTS_TIMEOUT_SEC
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def getNum():
|
|
79
|
+
return len(__API_KEYS__["keys"])
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def getItem(index: int):
|
|
83
|
+
if index < 0 or index >= len(__API_KEYS__["keys"]):
|
|
84
|
+
return __ERROR_KEY__
|
|
85
|
+
return __API_KEYS__["keys"][index]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def isItemValid(index: int):
|
|
89
|
+
item = getItem(index)
|
|
90
|
+
return item["valid"] == "True"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def getItems():
|
|
94
|
+
return __API_KEYS__["keys"]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def getLimitIndexs():
|
|
98
|
+
array = []
|
|
99
|
+
for i in range(len(__API_KEYS__["keys"])):
|
|
100
|
+
array.append(str(i))
|
|
101
|
+
return array
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def getVersion():
|
|
105
|
+
return __API_KEYS__["version"]
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# Load from gist
|
|
109
|
+
try:
|
|
110
|
+
respond = requests.get(
|
|
111
|
+
"https://api.github.com/gists/48d01f5a24b4b7b37f19443977c22cd6", timeout=REQUESTS_TIMEOUT_SEC
|
|
112
|
+
)
|
|
113
|
+
respond.raise_for_status()
|
|
114
|
+
|
|
115
|
+
if respond.status_code == 200:
|
|
116
|
+
content = respond.json()["files"]["tidal-api-key.json"]["content"]
|
|
117
|
+
__API_KEYS__ = json.loads(content)
|
|
118
|
+
except requests.RequestException as e:
|
|
119
|
+
# Failed to load API keys from gist, will use fallback keys
|
|
120
|
+
print(f"Failed to load API keys from gist: {e}")
|
|
121
|
+
pass
|