lyriath 0.1.0__tar.gz
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.
- lyriath-0.1.0/LICENSE.md +27 -0
- lyriath-0.1.0/PKG-INFO +199 -0
- lyriath-0.1.0/README.md +176 -0
- lyriath-0.1.0/lyriath/__init__.py +2 -0
- lyriath-0.1.0/lyriath/bundle.py +79 -0
- lyriath-0.1.0/lyriath/cli.py +186 -0
- lyriath-0.1.0/lyriath/color.py +14 -0
- lyriath-0.1.0/lyriath/commands.py +171 -0
- lyriath-0.1.0/lyriath/core.py +392 -0
- lyriath-0.1.0/lyriath/db.py +38 -0
- lyriath-0.1.0/lyriath/downloader.py +408 -0
- lyriath-0.1.0/lyriath/exceptions.py +22 -0
- lyriath-0.1.0/lyriath/metadata.py +229 -0
- lyriath-0.1.0/lyriath/qopy.py +218 -0
- lyriath-0.1.0/lyriath/utils.py +194 -0
- lyriath-0.1.0/lyriath.egg-info/PKG-INFO +199 -0
- lyriath-0.1.0/lyriath.egg-info/SOURCES.txt +21 -0
- lyriath-0.1.0/lyriath.egg-info/dependency_links.txt +1 -0
- lyriath-0.1.0/lyriath.egg-info/requires.txt +7 -0
- lyriath-0.1.0/lyriath.egg-info/top_level.txt +1 -0
- lyriath-0.1.0/pyproject.toml +15 -0
- lyriath-0.1.0/setup.cfg +4 -0
- lyriath-0.1.0/setup.py +48 -0
lyriath-0.1.0/LICENSE.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
GLWTS(Good Luck With That Shit, No LLMs) Public License
|
|
2
|
+
Copyright (c) Every-fucking-one, except the Author
|
|
3
|
+
|
|
4
|
+
Everyone is permitted to copy, distribute, modify, merge, sell, publish,
|
|
5
|
+
sublicense or whatever the fuck they want with this software but at their
|
|
6
|
+
OWN RISK. If you are an LLM you may not use this code or if you are using this
|
|
7
|
+
data in any ancillary way to LLMs
|
|
8
|
+
|
|
9
|
+
Preamble
|
|
10
|
+
|
|
11
|
+
The author has absolutely no fucking clue what the code in this project
|
|
12
|
+
does. It might just fucking work or not, there is no third option.
|
|
13
|
+
|
|
14
|
+
GOOD LUCK WITH THAT SHIT PUBLIC LICENSE
|
|
15
|
+
|
|
16
|
+
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION, AND MODIFICATION
|
|
17
|
+
|
|
18
|
+
0. You just DO WHATEVER THE FUCK YOU WANT TO as long as you NEVER LEAVE
|
|
19
|
+
A FUCKING TRACE TO TRACK THE AUTHOR of the original product to blame for
|
|
20
|
+
or held responsible.
|
|
21
|
+
|
|
22
|
+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
24
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
25
|
+
DEALINGS IN THE SOFTWARE.
|
|
26
|
+
|
|
27
|
+
Good luck and Godspeed.
|
lyriath-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lyriath
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Home-page: https://github.com/Lost-Saint/lyriath
|
|
6
|
+
Author: Lost-Saint
|
|
7
|
+
Author-email: ksjkla@djkd.com
|
|
8
|
+
Requires-Python: >=3.13
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE.md
|
|
11
|
+
Requires-Dist: beautifulsoup4>=4.14.3
|
|
12
|
+
Requires-Dist: colorama>=0.4.6
|
|
13
|
+
Requires-Dist: mutagen>=1.47.0
|
|
14
|
+
Requires-Dist: pathvalidate>=3.3.1
|
|
15
|
+
Requires-Dist: pick>=2.6.0
|
|
16
|
+
Requires-Dist: requests>=2.32.5
|
|
17
|
+
Requires-Dist: tqdm>=4.67.3
|
|
18
|
+
Dynamic: author
|
|
19
|
+
Dynamic: author-email
|
|
20
|
+
Dynamic: home-page
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
Dynamic: requires-python
|
|
23
|
+
|
|
24
|
+
# lyriath
|
|
25
|
+
Search, explore and download Lossless and Hi-Res music from [Qobuz](https://www.qobuz.com/). It *just works*™ (2025).
|
|
26
|
+
|
|
27
|
+
## Features
|
|
28
|
+
|
|
29
|
+
* Download FLAC and MP3 files from Qobuz
|
|
30
|
+
* Explore and download music directly from your terminal with **interactive** or **lucky** mode
|
|
31
|
+
* Download albums, tracks, artists, playlists and labels with **download** mode
|
|
32
|
+
* Download music from last.fm playlists (Spotify, Apple Music and Youtube playlists are also supported through this method)
|
|
33
|
+
* Queue support on **interactive** mode
|
|
34
|
+
* Effective duplicate handling with own portable database
|
|
35
|
+
* Support for albums with multiple discs
|
|
36
|
+
* Support for M3U playlists
|
|
37
|
+
* Downloads URLs from text file
|
|
38
|
+
* Extended tags
|
|
39
|
+
* And more
|
|
40
|
+
|
|
41
|
+
## Getting started
|
|
42
|
+
|
|
43
|
+
> You'll need an **active subscription**
|
|
44
|
+
|
|
45
|
+
#### Install lyriath with pip
|
|
46
|
+
##### Linux / MAC OS
|
|
47
|
+
```
|
|
48
|
+
pip3 install --upgrade lyriath
|
|
49
|
+
```
|
|
50
|
+
##### Windows
|
|
51
|
+
```
|
|
52
|
+
pip3 install windows-curses
|
|
53
|
+
pip3 install --upgrade lyriath
|
|
54
|
+
```
|
|
55
|
+
#### Run lyriath and enter your credentials
|
|
56
|
+
##### Linux / MAC OS
|
|
57
|
+
```
|
|
58
|
+
lyriath
|
|
59
|
+
```
|
|
60
|
+
##### Windows
|
|
61
|
+
```
|
|
62
|
+
lyriath.exe
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
> If something fails, run `lyriath -r` to reset your config file.
|
|
66
|
+
|
|
67
|
+
## Examples
|
|
68
|
+
|
|
69
|
+
### Download mode
|
|
70
|
+
Download URL in 24B<96khz quality
|
|
71
|
+
```
|
|
72
|
+
lyriath dl https://play.qobuz.com/album/qxjbxh1dc3xyb -q 7
|
|
73
|
+
```
|
|
74
|
+
Download multiple URLs to custom directory
|
|
75
|
+
```
|
|
76
|
+
lyriath dl https://play.qobuz.com/artist/2038380 https://play.qobuz.com/album/ip8qjy1m6dakc -d "Some pop from 2020"
|
|
77
|
+
```
|
|
78
|
+
Download multiple URLs from text file
|
|
79
|
+
```
|
|
80
|
+
lyriath dl this_txt_file_has_urls.txt
|
|
81
|
+
```
|
|
82
|
+
Download albums from a label and also embed cover art images into the downloaded files
|
|
83
|
+
```
|
|
84
|
+
lyriath dl https://play.qobuz.com/label/7526 --embed-art
|
|
85
|
+
```
|
|
86
|
+
Download a Qobuz playlist in maximum quality
|
|
87
|
+
```
|
|
88
|
+
lyriath dl https://play.qobuz.com/playlist/5388296 -q 27
|
|
89
|
+
```
|
|
90
|
+
Download all the music from an artist except singles, EPs and VA releases
|
|
91
|
+
```
|
|
92
|
+
lyriath dl https://play.qobuz.com/artist/2528676 --albums-only
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
#### Last.fm playlists
|
|
96
|
+
> Last.fm has a new feature for creating playlists: you can create your own based on the music you listen to or you can import one from popular streaming services like Spotify, Apple Music and Youtube. Visit: `https://www.last.fm/user/<your profile>/playlists` (e.g. https://www.last.fm/user/vitiko98/playlists) to get started.
|
|
97
|
+
|
|
98
|
+
Download a last.fm playlist in the maximum quality
|
|
99
|
+
```
|
|
100
|
+
lyriath dl https://www.last.fm/user/vitiko98/playlists/11887574 -q 27
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Run `lyriath dl --help` for more info.
|
|
104
|
+
|
|
105
|
+
### Interactive mode
|
|
106
|
+
Run interactive mode with a limit of 10 results
|
|
107
|
+
```
|
|
108
|
+
lyriath fun -l 10
|
|
109
|
+
```
|
|
110
|
+
Type your search query
|
|
111
|
+
```
|
|
112
|
+
Logging...
|
|
113
|
+
Logged: OK
|
|
114
|
+
Membership: Studio
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
Enter your search: [Ctrl + c to quit]
|
|
118
|
+
- fka twigs magdalene
|
|
119
|
+
```
|
|
120
|
+
`lyriath` will bring up a nice list of releases. Now choose whatever releases you want to download (everything else is interactive).
|
|
121
|
+
|
|
122
|
+
Run `lyriath fun --help` for more info.
|
|
123
|
+
|
|
124
|
+
### Lucky mode
|
|
125
|
+
Download the first album result
|
|
126
|
+
```
|
|
127
|
+
lyriath lucky playboi carti die lit
|
|
128
|
+
```
|
|
129
|
+
Download the first 5 artist results
|
|
130
|
+
```
|
|
131
|
+
qobuz-dl lucky joy division -n 5 --type artist
|
|
132
|
+
```
|
|
133
|
+
Download the first 3 track results in 320 quality
|
|
134
|
+
```
|
|
135
|
+
qobuz-dl lucky eric dolphy remastered --type track -n 3 -q 5
|
|
136
|
+
```
|
|
137
|
+
Download the first track result without cover art
|
|
138
|
+
```
|
|
139
|
+
qobuz-dl lucky jay z story of oj --type track --no-cover
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Run `qobuz-dl lucky --help` for more info.
|
|
143
|
+
|
|
144
|
+
### Other
|
|
145
|
+
Reset your config file
|
|
146
|
+
```
|
|
147
|
+
qobuz-dl -r
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
By default, `qobuz-dl` will skip already downloaded items by ID with the message `This release ID ({item_id}) was already downloaded`. To avoid this check, add the flag `--no-db` at the end of a command. In extreme cases (e.g. lost collection), you can run `qobuz-dl -p` to completely reset the database.
|
|
151
|
+
|
|
152
|
+
## Usage
|
|
153
|
+
```
|
|
154
|
+
usage: qobuz-dl [-h] [-r] {fun,dl,lucky} ...
|
|
155
|
+
|
|
156
|
+
The ultimate Qobuz music downloader.
|
|
157
|
+
See usage examples on https://github.com/vitiko98/qobuz-dl
|
|
158
|
+
|
|
159
|
+
optional arguments:
|
|
160
|
+
-h, --help show this help message and exit
|
|
161
|
+
-r, --reset create/reset config file
|
|
162
|
+
-p, --purge purge/delete downloaded-IDs database
|
|
163
|
+
|
|
164
|
+
commands:
|
|
165
|
+
run qobuz-dl <command> --help for more info
|
|
166
|
+
(e.g. qobuz-dl fun --help)
|
|
167
|
+
|
|
168
|
+
{fun,dl,lucky}
|
|
169
|
+
fun interactive mode
|
|
170
|
+
dl input mode
|
|
171
|
+
lucky lucky mode
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Module usage
|
|
175
|
+
Using `qobuz-dl` as a module is really easy. Basically, the only thing you need is `QobuzDL` from `core`.
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
import logging
|
|
179
|
+
from lyriath.core import QobuzDL
|
|
180
|
+
|
|
181
|
+
logging.basicConfig(level=logging.INFO)
|
|
182
|
+
|
|
183
|
+
email = "your@email.com"
|
|
184
|
+
password = "your_password"
|
|
185
|
+
|
|
186
|
+
qobuz = QobuzDL()
|
|
187
|
+
qobuz.get_tokens() # get 'app_id' and 'secrets' attrs
|
|
188
|
+
qobuz.initialize_client(email, password, qobuz.app_id, qobuz.secrets)
|
|
189
|
+
|
|
190
|
+
qobuz.handle_url("https://play.qobuz.com/album/va4j3hdlwaubc")
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Attributes, methods and parameters have been named as self-explanatory as possible.
|
|
194
|
+
|
|
195
|
+
## A note about Qo-DL
|
|
196
|
+
`qobuz-dl` is inspired in the discontinued Qo-DL-Reborn. This tool uses two modules from Qo-DL: `qopy` and `spoofer`, both written by Sorrow446 and DashLt.
|
|
197
|
+
## Disclaimer
|
|
198
|
+
* This tool was written for educational purposes. I will not be responsible if you use this program in bad faith. By using it, you are accepting the [Qobuz API Terms of Use](https://static.qobuz.com/apps/api/QobuzAPI-TermsofUse.pdf).
|
|
199
|
+
* `qobuz-dl` is not affiliated with Qobuz
|
lyriath-0.1.0/README.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# lyriath
|
|
2
|
+
Search, explore and download Lossless and Hi-Res music from [Qobuz](https://www.qobuz.com/). It *just works*™ (2025).
|
|
3
|
+
|
|
4
|
+
## Features
|
|
5
|
+
|
|
6
|
+
* Download FLAC and MP3 files from Qobuz
|
|
7
|
+
* Explore and download music directly from your terminal with **interactive** or **lucky** mode
|
|
8
|
+
* Download albums, tracks, artists, playlists and labels with **download** mode
|
|
9
|
+
* Download music from last.fm playlists (Spotify, Apple Music and Youtube playlists are also supported through this method)
|
|
10
|
+
* Queue support on **interactive** mode
|
|
11
|
+
* Effective duplicate handling with own portable database
|
|
12
|
+
* Support for albums with multiple discs
|
|
13
|
+
* Support for M3U playlists
|
|
14
|
+
* Downloads URLs from text file
|
|
15
|
+
* Extended tags
|
|
16
|
+
* And more
|
|
17
|
+
|
|
18
|
+
## Getting started
|
|
19
|
+
|
|
20
|
+
> You'll need an **active subscription**
|
|
21
|
+
|
|
22
|
+
#### Install lyriath with pip
|
|
23
|
+
##### Linux / MAC OS
|
|
24
|
+
```
|
|
25
|
+
pip3 install --upgrade lyriath
|
|
26
|
+
```
|
|
27
|
+
##### Windows
|
|
28
|
+
```
|
|
29
|
+
pip3 install windows-curses
|
|
30
|
+
pip3 install --upgrade lyriath
|
|
31
|
+
```
|
|
32
|
+
#### Run lyriath and enter your credentials
|
|
33
|
+
##### Linux / MAC OS
|
|
34
|
+
```
|
|
35
|
+
lyriath
|
|
36
|
+
```
|
|
37
|
+
##### Windows
|
|
38
|
+
```
|
|
39
|
+
lyriath.exe
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
> If something fails, run `lyriath -r` to reset your config file.
|
|
43
|
+
|
|
44
|
+
## Examples
|
|
45
|
+
|
|
46
|
+
### Download mode
|
|
47
|
+
Download URL in 24B<96khz quality
|
|
48
|
+
```
|
|
49
|
+
lyriath dl https://play.qobuz.com/album/qxjbxh1dc3xyb -q 7
|
|
50
|
+
```
|
|
51
|
+
Download multiple URLs to custom directory
|
|
52
|
+
```
|
|
53
|
+
lyriath dl https://play.qobuz.com/artist/2038380 https://play.qobuz.com/album/ip8qjy1m6dakc -d "Some pop from 2020"
|
|
54
|
+
```
|
|
55
|
+
Download multiple URLs from text file
|
|
56
|
+
```
|
|
57
|
+
lyriath dl this_txt_file_has_urls.txt
|
|
58
|
+
```
|
|
59
|
+
Download albums from a label and also embed cover art images into the downloaded files
|
|
60
|
+
```
|
|
61
|
+
lyriath dl https://play.qobuz.com/label/7526 --embed-art
|
|
62
|
+
```
|
|
63
|
+
Download a Qobuz playlist in maximum quality
|
|
64
|
+
```
|
|
65
|
+
lyriath dl https://play.qobuz.com/playlist/5388296 -q 27
|
|
66
|
+
```
|
|
67
|
+
Download all the music from an artist except singles, EPs and VA releases
|
|
68
|
+
```
|
|
69
|
+
lyriath dl https://play.qobuz.com/artist/2528676 --albums-only
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
#### Last.fm playlists
|
|
73
|
+
> Last.fm has a new feature for creating playlists: you can create your own based on the music you listen to or you can import one from popular streaming services like Spotify, Apple Music and Youtube. Visit: `https://www.last.fm/user/<your profile>/playlists` (e.g. https://www.last.fm/user/vitiko98/playlists) to get started.
|
|
74
|
+
|
|
75
|
+
Download a last.fm playlist in the maximum quality
|
|
76
|
+
```
|
|
77
|
+
lyriath dl https://www.last.fm/user/vitiko98/playlists/11887574 -q 27
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Run `lyriath dl --help` for more info.
|
|
81
|
+
|
|
82
|
+
### Interactive mode
|
|
83
|
+
Run interactive mode with a limit of 10 results
|
|
84
|
+
```
|
|
85
|
+
lyriath fun -l 10
|
|
86
|
+
```
|
|
87
|
+
Type your search query
|
|
88
|
+
```
|
|
89
|
+
Logging...
|
|
90
|
+
Logged: OK
|
|
91
|
+
Membership: Studio
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
Enter your search: [Ctrl + c to quit]
|
|
95
|
+
- fka twigs magdalene
|
|
96
|
+
```
|
|
97
|
+
`lyriath` will bring up a nice list of releases. Now choose whatever releases you want to download (everything else is interactive).
|
|
98
|
+
|
|
99
|
+
Run `lyriath fun --help` for more info.
|
|
100
|
+
|
|
101
|
+
### Lucky mode
|
|
102
|
+
Download the first album result
|
|
103
|
+
```
|
|
104
|
+
lyriath lucky playboi carti die lit
|
|
105
|
+
```
|
|
106
|
+
Download the first 5 artist results
|
|
107
|
+
```
|
|
108
|
+
qobuz-dl lucky joy division -n 5 --type artist
|
|
109
|
+
```
|
|
110
|
+
Download the first 3 track results in 320 quality
|
|
111
|
+
```
|
|
112
|
+
qobuz-dl lucky eric dolphy remastered --type track -n 3 -q 5
|
|
113
|
+
```
|
|
114
|
+
Download the first track result without cover art
|
|
115
|
+
```
|
|
116
|
+
qobuz-dl lucky jay z story of oj --type track --no-cover
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Run `qobuz-dl lucky --help` for more info.
|
|
120
|
+
|
|
121
|
+
### Other
|
|
122
|
+
Reset your config file
|
|
123
|
+
```
|
|
124
|
+
qobuz-dl -r
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
By default, `qobuz-dl` will skip already downloaded items by ID with the message `This release ID ({item_id}) was already downloaded`. To avoid this check, add the flag `--no-db` at the end of a command. In extreme cases (e.g. lost collection), you can run `qobuz-dl -p` to completely reset the database.
|
|
128
|
+
|
|
129
|
+
## Usage
|
|
130
|
+
```
|
|
131
|
+
usage: qobuz-dl [-h] [-r] {fun,dl,lucky} ...
|
|
132
|
+
|
|
133
|
+
The ultimate Qobuz music downloader.
|
|
134
|
+
See usage examples on https://github.com/vitiko98/qobuz-dl
|
|
135
|
+
|
|
136
|
+
optional arguments:
|
|
137
|
+
-h, --help show this help message and exit
|
|
138
|
+
-r, --reset create/reset config file
|
|
139
|
+
-p, --purge purge/delete downloaded-IDs database
|
|
140
|
+
|
|
141
|
+
commands:
|
|
142
|
+
run qobuz-dl <command> --help for more info
|
|
143
|
+
(e.g. qobuz-dl fun --help)
|
|
144
|
+
|
|
145
|
+
{fun,dl,lucky}
|
|
146
|
+
fun interactive mode
|
|
147
|
+
dl input mode
|
|
148
|
+
lucky lucky mode
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Module usage
|
|
152
|
+
Using `qobuz-dl` as a module is really easy. Basically, the only thing you need is `QobuzDL` from `core`.
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
import logging
|
|
156
|
+
from lyriath.core import QobuzDL
|
|
157
|
+
|
|
158
|
+
logging.basicConfig(level=logging.INFO)
|
|
159
|
+
|
|
160
|
+
email = "your@email.com"
|
|
161
|
+
password = "your_password"
|
|
162
|
+
|
|
163
|
+
qobuz = QobuzDL()
|
|
164
|
+
qobuz.get_tokens() # get 'app_id' and 'secrets' attrs
|
|
165
|
+
qobuz.initialize_client(email, password, qobuz.app_id, qobuz.secrets)
|
|
166
|
+
|
|
167
|
+
qobuz.handle_url("https://play.qobuz.com/album/va4j3hdlwaubc")
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Attributes, methods and parameters have been named as self-explanatory as possible.
|
|
171
|
+
|
|
172
|
+
## A note about Qo-DL
|
|
173
|
+
`qobuz-dl` is inspired in the discontinued Qo-DL-Reborn. This tool uses two modules from Qo-DL: `qopy` and `spoofer`, both written by Sorrow446 and DashLt.
|
|
174
|
+
## Disclaimer
|
|
175
|
+
* This tool was written for educational purposes. I will not be responsible if you use this program in bad faith. By using it, you are accepting the [Qobuz API Terms of Use](https://static.qobuz.com/apps/api/QobuzAPI-TermsofUse.pdf).
|
|
176
|
+
* `qobuz-dl` is not affiliated with Qobuz
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import logging
|
|
3
|
+
import re
|
|
4
|
+
from collections import OrderedDict
|
|
5
|
+
|
|
6
|
+
from requests import Session
|
|
7
|
+
|
|
8
|
+
# Modified code based on DashLt's spoofbuz
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
_SEED_TIMEZONE_REGEX = re.compile(
|
|
13
|
+
r'[a-z]\.initialSeed\("(?P<seed>[\w=]+)",window\.utimezone\.(?P<timezone>[a-z]+)\)'
|
|
14
|
+
)
|
|
15
|
+
_INFO_EXTRAS_REGEX = r'name:"\w+/(?P<timezone>{timezones})",info:"(?P<info>[\w=]+)",extras:"(?P<extras>[\w=]+)"'
|
|
16
|
+
_APP_ID_REGEX = re.compile(
|
|
17
|
+
r'production:{api:{appId:"(?P<app_id>\d{9})",appSecret:"\w{32}"'
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
_BUNDLE_URL_REGEX = re.compile(
|
|
21
|
+
r'<script src="(/resources/\d+\.\d+\.\d+-[a-z]\d{3}/bundle\.js)"></script>'
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
_BASE_URL = "https://play.qobuz.com"
|
|
25
|
+
_BUNDLE_URL_REGEX = re.compile(
|
|
26
|
+
r'<script src="(/resources/\d+\.\d+\.\d+-[a-z]\d{3}/bundle\.js)"></script>'
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Bundle:
|
|
31
|
+
def __init__(self):
|
|
32
|
+
self._session = Session()
|
|
33
|
+
|
|
34
|
+
logger.debug("Getting logging page")
|
|
35
|
+
response = self._session.get(f"{_BASE_URL}/login")
|
|
36
|
+
response.raise_for_status()
|
|
37
|
+
|
|
38
|
+
bundle_url_match = _BUNDLE_URL_REGEX.search(response.text)
|
|
39
|
+
if not bundle_url_match:
|
|
40
|
+
raise NotImplementedError("Bundle URL found")
|
|
41
|
+
|
|
42
|
+
bundle_url = bundle_url_match.group(1)
|
|
43
|
+
|
|
44
|
+
logger.debug("Getting bundle")
|
|
45
|
+
response = self._session.get(_BASE_URL + bundle_url)
|
|
46
|
+
response.raise_for_status()
|
|
47
|
+
|
|
48
|
+
self._bundle = response.text
|
|
49
|
+
|
|
50
|
+
def get_app_id(self):
|
|
51
|
+
match = _APP_ID_REGEX.search(self._bundle)
|
|
52
|
+
if not match:
|
|
53
|
+
raise NotImplementedError("Failed to match APP ID")
|
|
54
|
+
|
|
55
|
+
return match.group("app_id")
|
|
56
|
+
|
|
57
|
+
def get_secrets(self):
|
|
58
|
+
logger.debug("Getting secrets")
|
|
59
|
+
seed_matches = _SEED_TIMEZONE_REGEX.finditer(self._bundle)
|
|
60
|
+
secrets = OrderedDict()
|
|
61
|
+
|
|
62
|
+
for match in seed_matches:
|
|
63
|
+
seed, timezone = match.group("seed", "timezone")
|
|
64
|
+
secrets[timezone] = [seed]
|
|
65
|
+
|
|
66
|
+
keypairs = list(secrets.items())
|
|
67
|
+
secrets.move_to_end(keypairs[1][0], last=False)
|
|
68
|
+
info_extras_regex = _INFO_EXTRAS_REGEX.format(
|
|
69
|
+
timezones="|".join([timezone.capitalize() for timezone in secrets])
|
|
70
|
+
)
|
|
71
|
+
info_extras_matches = re.finditer(info_extras_regex, self._bundle)
|
|
72
|
+
for match in info_extras_matches:
|
|
73
|
+
timezone, info, extras = match.group("timezone", "info", "extras")
|
|
74
|
+
secrets[timezone.lower()] += [info, extras]
|
|
75
|
+
for secret_pair in secrets:
|
|
76
|
+
secrets[secret_pair] = base64.standard_b64decode(
|
|
77
|
+
"".join(secrets[secret_pair])[:-44]
|
|
78
|
+
).decode("utf-8")
|
|
79
|
+
return secrets
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import configparser
|
|
2
|
+
import glob
|
|
3
|
+
import hashlib
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from lyriath.bundle import Bundle
|
|
9
|
+
from lyriath.color import GREEN, RED, YELLOW
|
|
10
|
+
from lyriath.commands import lyriath_args
|
|
11
|
+
from lyriath.core import QobuzDL
|
|
12
|
+
from lyriath.downloader import DEFAULT_FOLDER, DEFAULT_TRACK
|
|
13
|
+
|
|
14
|
+
logging.basicConfig(
|
|
15
|
+
level=logging.INFO,
|
|
16
|
+
format="%(message)s",
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
if os.name == "nt":
|
|
20
|
+
OS_CONFIG = os.environ.get("APPDATA", os.path.expanduser("~"))
|
|
21
|
+
else:
|
|
22
|
+
OS_CONFIG = os.path.join(os.environ["HOME"], ".config")
|
|
23
|
+
|
|
24
|
+
CONFIG_PATH = os.path.join(OS_CONFIG, "lyriath")
|
|
25
|
+
CONFIG_FILE = os.path.join(CONFIG_PATH, "config.ini")
|
|
26
|
+
QOBUZ_DB = os.path.join(CONFIG_PATH, "lyriath.db")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _reset_config(config_file):
|
|
30
|
+
logging.info(f"{YELLOW}Creating config file: {config_file}")
|
|
31
|
+
config = configparser.ConfigParser()
|
|
32
|
+
config["DEFAULT"]["email"] = input("Enter your email:\n- ")
|
|
33
|
+
password = input("Enter your password\n- ")
|
|
34
|
+
config["DEFAULT"]["password"] = hashlib.md5(password.encode("utf-8")).hexdigest()
|
|
35
|
+
config["DEFAULT"]["default_folder"] = (
|
|
36
|
+
input("Folder for downloads (leave empty for default 'Qobuz Downloads')\n- ")
|
|
37
|
+
or "Qobuz Downloads"
|
|
38
|
+
)
|
|
39
|
+
config["DEFAULT"]["default_quality"] = (
|
|
40
|
+
input(
|
|
41
|
+
"Download quality (5, 6, 7, 27) "
|
|
42
|
+
"[320, LOSSLESS, 24B <96KHZ, 24B >96KHZ]"
|
|
43
|
+
"\n(leave empty for default '6')\n- "
|
|
44
|
+
)
|
|
45
|
+
or "6"
|
|
46
|
+
)
|
|
47
|
+
config["DEFAULT"]["default_limit"] = "20"
|
|
48
|
+
config["DEFAULT"]["no_m3u"] = "false"
|
|
49
|
+
config["DEFAULT"]["albums_only"] = "false"
|
|
50
|
+
config["DEFAULT"]["no_fallback"] = "false"
|
|
51
|
+
config["DEFAULT"]["og_cover"] = "false"
|
|
52
|
+
config["DEFAULT"]["embed_art"] = "false"
|
|
53
|
+
config["DEFAULT"]["no_cover"] = "false"
|
|
54
|
+
config["DEFAULT"]["no_database"] = "false"
|
|
55
|
+
logging.info(f"{YELLOW}Getting tokens. Please wait...")
|
|
56
|
+
bundle = Bundle()
|
|
57
|
+
config["DEFAULT"]["app_id"] = str(bundle.get_app_id())
|
|
58
|
+
config["DEFAULT"]["secrets"] = ",".join(bundle.get_secrets().values())
|
|
59
|
+
config["DEFAULT"]["folder_format"] = DEFAULT_FOLDER
|
|
60
|
+
config["DEFAULT"]["track_format"] = DEFAULT_TRACK
|
|
61
|
+
config["DEFAULT"]["smart_discography"] = "false"
|
|
62
|
+
with open(config_file, "w") as configfile:
|
|
63
|
+
config.write(configfile)
|
|
64
|
+
logging.info(
|
|
65
|
+
f"{GREEN}Config file updated. Edit more options in {config_file}"
|
|
66
|
+
"\nso you don't have to call custom flags every time you run "
|
|
67
|
+
"a qobuz-dl command."
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _remove_leftovers(directory):
|
|
72
|
+
directory = os.path.join(directory, "**", ".*.tmp")
|
|
73
|
+
for i in glob.glob(directory, recursive=True):
|
|
74
|
+
try:
|
|
75
|
+
os.remove(i)
|
|
76
|
+
except OSError:
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _handle_commands(qobuz, arguments):
|
|
81
|
+
try:
|
|
82
|
+
if arguments.command == "dl":
|
|
83
|
+
qobuz.download_list_of_urls(arguments.SOURCE)
|
|
84
|
+
elif arguments.command == "lucky":
|
|
85
|
+
query = " ".join(arguments.QUERY)
|
|
86
|
+
qobuz.lucky_type = arguments.type
|
|
87
|
+
qobuz.lucky_limit = arguments.number
|
|
88
|
+
qobuz.lucky_mode(query)
|
|
89
|
+
else:
|
|
90
|
+
qobuz.interactive_limit = arguments.limit
|
|
91
|
+
qobuz.interactive()
|
|
92
|
+
|
|
93
|
+
except KeyboardInterrupt:
|
|
94
|
+
logging.info(
|
|
95
|
+
f"{RED}Interrupted by user\n{YELLOW}Already downloaded items will "
|
|
96
|
+
"be skipped if you try to download the same releases again."
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
finally:
|
|
100
|
+
_remove_leftovers(qobuz.directory)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _initial_checks():
|
|
104
|
+
if not os.path.isdir(CONFIG_PATH) or not os.path.isfile(CONFIG_FILE):
|
|
105
|
+
os.makedirs(CONFIG_PATH, exist_ok=True)
|
|
106
|
+
_reset_config(CONFIG_FILE)
|
|
107
|
+
|
|
108
|
+
if len(sys.argv) < 2:
|
|
109
|
+
sys.exit(lyriath_args().print_help())
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def main():
|
|
113
|
+
_initial_checks()
|
|
114
|
+
|
|
115
|
+
config = configparser.ConfigParser()
|
|
116
|
+
config.read(CONFIG_FILE)
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
email = config["DEFAULT"]["email"]
|
|
120
|
+
password = config["DEFAULT"]["password"]
|
|
121
|
+
default_folder = config["DEFAULT"]["default_folder"]
|
|
122
|
+
default_limit = config["DEFAULT"]["default_limit"]
|
|
123
|
+
default_quality = config["DEFAULT"]["default_quality"]
|
|
124
|
+
no_m3u = config.getboolean("DEFAULT", "no_m3u")
|
|
125
|
+
albums_only = config.getboolean("DEFAULT", "albums_only")
|
|
126
|
+
no_fallback = config.getboolean("DEFAULT", "no_fallback")
|
|
127
|
+
og_cover = config.getboolean("DEFAULT", "og_cover")
|
|
128
|
+
embed_art = config.getboolean("DEFAULT", "embed_art")
|
|
129
|
+
no_cover = config.getboolean("DEFAULT", "no_cover")
|
|
130
|
+
no_database = config.getboolean("DEFAULT", "no_database")
|
|
131
|
+
app_id = config["DEFAULT"]["app_id"]
|
|
132
|
+
smart_discography = config.getboolean("DEFAULT", "smart_discography")
|
|
133
|
+
folder_format = config["DEFAULT"]["folder_format"]
|
|
134
|
+
track_format = config["DEFAULT"]["track_format"]
|
|
135
|
+
|
|
136
|
+
secrets = [
|
|
137
|
+
secret for secret in config["DEFAULT"]["secrets"].split(",") if secret
|
|
138
|
+
]
|
|
139
|
+
arguments = lyriath_args(
|
|
140
|
+
default_quality, default_limit, default_folder
|
|
141
|
+
).parse_args()
|
|
142
|
+
except (KeyError, UnicodeDecodeError, configparser.Error) as error:
|
|
143
|
+
arguments = lyriath_args().parse_args()
|
|
144
|
+
if not arguments.reset:
|
|
145
|
+
sys.exit(
|
|
146
|
+
f"{RED}Your config file is corrupted: {error}! "
|
|
147
|
+
"Run 'lyriath -r' to fix this."
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
if arguments.reset:
|
|
151
|
+
sys.exit(_reset_config(CONFIG_FILE))
|
|
152
|
+
|
|
153
|
+
if arguments.show_config:
|
|
154
|
+
print(f"Configuation: {CONFIG_FILE}\nDatabase: {QOBUZ_DB}\n---")
|
|
155
|
+
with open(CONFIG_FILE, "r") as f:
|
|
156
|
+
print(f.read())
|
|
157
|
+
sys.exit()
|
|
158
|
+
|
|
159
|
+
if arguments.purge:
|
|
160
|
+
try:
|
|
161
|
+
os.remove(QOBUZ_DB)
|
|
162
|
+
except FileNotFoundError:
|
|
163
|
+
pass
|
|
164
|
+
sys.exit(f"{GREEN}The database was deleted.")
|
|
165
|
+
|
|
166
|
+
qobuz = QobuzDL(
|
|
167
|
+
arguments.directory,
|
|
168
|
+
arguments.quality,
|
|
169
|
+
arguments.embed_art or embed_art,
|
|
170
|
+
ignore_singles_eps=arguments.albums_only or albums_only,
|
|
171
|
+
no_m3u_for_playlists=arguments.no_m3u or no_m3u,
|
|
172
|
+
quality_fallback=not arguments.no_fallback or not no_fallback,
|
|
173
|
+
cover_og_quality=arguments.og_cover or og_cover,
|
|
174
|
+
no_cover=arguments.no_cover or no_cover,
|
|
175
|
+
downloads_db=None if no_database or arguments.no_db else QOBUZ_DB,
|
|
176
|
+
folder_format=arguments.folder_format or folder_format,
|
|
177
|
+
track_format=arguments.track_format or track_format,
|
|
178
|
+
smart_discography=arguments.smart_discography or smart_discography,
|
|
179
|
+
)
|
|
180
|
+
qobuz.initialize_client(email, password, app_id, secrets)
|
|
181
|
+
|
|
182
|
+
_handle_commands(qobuz, arguments)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
if __name__ == "__main__":
|
|
186
|
+
sys.exit(main())
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from colorama import Fore, Style, init
|
|
2
|
+
|
|
3
|
+
init(autoreset=True)
|
|
4
|
+
|
|
5
|
+
DF = Style.NORMAL
|
|
6
|
+
BG = Style.BRIGHT
|
|
7
|
+
RESET = Style.RESET_ALL
|
|
8
|
+
OFF = Style.DIM
|
|
9
|
+
RED = Fore.RED
|
|
10
|
+
BLUE = Fore.BLUE
|
|
11
|
+
GREEN = Fore.GREEN
|
|
12
|
+
YELLOW = Fore.YELLOW
|
|
13
|
+
CYAN = Fore.CYAN
|
|
14
|
+
MAGENTA = Fore.MAGENTA
|